/[hydra]/hydra/src/get.c
ViewVC logotype

Contents of /hydra/src/get.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.32 - (show annotations)
Mon Nov 3 10:59:45 2003 UTC (20 years, 4 months ago) by nmav
Branch: MAIN
CVS Tags: hydra_0_1_6_without_hic, hydra_0_1_7, hydra_0_1_6, hydra_0_1_4, hydra_0_1_8, HEAD
Changes since 1.31: +10 -2 lines
File MIME type: text/plain
ported to gnutls 0.9.x and some other minor fixes.

1 /*
2 * Hydra, an http server
3 * Copyright (C) 1995 Paul Phillips <paulp@go2net.com>
4 * Some changes Copyright (C) 1996,99 Larry Doolittle <ldoolitt@boa.org>
5 * Some changes Copyright (C) 1996-2002 Jon Nelson <jnelson@boa.org>
6 * Portions Copyright (C) 2002 Nikos Mavroyanopoulos <nmav@gnutls.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 1, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 */
23
24 /* $Id: get.c,v 1.31 2003/01/26 11:25:39 nmav Exp $*/
25
26 #include "boa.h"
27 #include "socket.h"
28 #include "access.h"
29
30 /* local prototypes */
31 int get_cachedir_file(request * req, struct stat *statbuf);
32 int index_directory(request * req, char *dest_filename);
33 static int check_if_stuff(request * req);
34
35 /*
36 * Name: init_get
37 * Description: Initializes a non-script GET or HEAD request.
38 *
39 * Return values:
40 * 0: finished or error, request will be freed
41 * 1: successfully initialized, added to ready queue
42 */
43
44 int init_get(server_params * params, request * req)
45 {
46 int data_fd, saved_errno;
47 struct stat statbuf;
48 volatile int bytes;
49
50 #ifdef ENABLE_ACCESS_LISTS
51 if (!access_allow(req->hostname, req->pathname)) {
52 send_r_forbidden(req);
53 return 0;
54 }
55 #endif
56
57 data_fd = open(req->pathname, O_RDONLY);
58 saved_errno = errno; /* might not get used */
59
60 if (data_fd == -1) {
61 log_error_doc(req);
62 errno = saved_errno;
63 perror("document open");
64
65 if (saved_errno == ENOENT)
66 send_r_not_found(req);
67 else if (saved_errno == EACCES)
68 send_r_forbidden(req);
69 else
70 send_r_bad_request(req);
71 return 0;
72 }
73
74 if (fstat(data_fd, &statbuf) == -1) {
75 /* this is quite impossible, since the file
76 * was opened before.
77 */
78 close(data_fd);
79 send_r_not_found(req);
80 return 0;
81 }
82
83 if (S_ISDIR(statbuf.st_mode)) { /* directory */
84 close(data_fd); /* close dir */
85
86 if (req->pathname[strlen(req->pathname) - 1] != '/') {
87 char buffer[3 * MAX_PATH_LENGTH + 128];
88 char * hostname;
89
90 if (req->hostname==NULL || req->hostname[0] == 0)
91 hostname = req->local_ip_addr;
92 else
93 hostname = req->hostname;
94
95 create_url(buffer, sizeof(buffer), req->secure, hostname,
96 params->server_s[req->secure].port, req->request_uri);
97
98 send_r_moved_perm(req, buffer);
99 return 0;
100 }
101 data_fd = get_dir(req, &statbuf); /* updates statbuf */
102
103 if (data_fd == -1) /* couldn't do it */
104 return 0; /* errors reported by get_dir */
105 else if (data_fd <= 1)
106 /* data_fd == 0 -> close it down, 1 -> continue */
107 return data_fd;
108 /* else, data_fd contains the fd of the file... */
109 }
110
111 req->filesize = statbuf.st_size;
112 req->last_modified = statbuf.st_mtime;
113
114 /* Check the If-Match, If-Modified etc stuff.
115 */
116 if (req->if_types)
117 if (check_if_stuff(req) == 0) {
118 close(data_fd);
119 return 0;
120 }
121 /* Move on */
122
123 if (req->range_stop == 0)
124 req->range_stop = statbuf.st_size;
125
126 /* out of range! */
127 if (req->range_start > statbuf.st_size ||
128 req->range_stop > statbuf.st_size ||
129 req->range_stop <= req->range_start) {
130 /* here we catch illegal ranges. We also catch
131 * illegal ranges because of unsupported features
132 * where range_start == range_stop == -1.
133 */
134 send_r_range_unsatisfiable(req);
135 close(data_fd);
136 return 0;
137 }
138
139 if (req->method == M_HEAD || req->filesize == 0) {
140 send_r_request_file_ok(req);
141 close(data_fd);
142 return 0;
143 }
144
145 req->filepos = req->range_start;
146
147 if (req->range_stop > max_file_size_cache) {
148
149 if (req->range_start == 0 && req->range_stop == statbuf.st_size)
150 send_r_request_file_ok(req); /* All's well */
151 else {
152 /* if ranges were used, then lseek to the start given
153 */
154 if (lseek(data_fd, req->range_start, SEEK_SET) == (off_t) - 1) {
155 close(data_fd);
156 send_r_not_found(req);
157 return 0;
158 }
159 send_r_request_partial(req); /* All's well */
160 }
161
162 req_flush(req); /* this should *always* complete due to
163 the size of the I/O buffers */
164 req->data_fd = data_fd;
165
166 if (req->secure) {
167 req->status = PIPE_READ;
168 req->cgi_status = CGI_BUFFER;
169 } else {
170 /* This sends data directly to the socket, and cannot
171 * be used in TLS connections.
172 */
173 req->status = IOSHUFFLE;
174 }
175
176 req->header_line = req->header_end = req->buffer;
177 req->pipe_range_stop = req->range_stop;
178 return 1;
179 }
180
181 if (req->range_stop == 0) { /* done */
182 send_r_request_file_ok(req); /* All's well *so far* */
183 close(data_fd);
184 return 1;
185 }
186
187 /* NOTE: I (Jon Nelson) tried performing a read(2)
188 * into the output buffer provided the file data would
189 * fit, before mmapping, and if successful, writing that
190 * and stopping there -- all to avoid the cost
191 * of a mmap. Oddly, it was *slower* in benchmarks.
192 */
193 if (max_files_cache > 0) {
194 req->mmap_entry_var = find_mmap(data_fd, &statbuf);
195 if (req->mmap_entry_var == NULL) {
196 req->buffer_end = 0;
197 if (errno == ENOENT)
198 send_r_not_found(req);
199 else if (errno == EACCES)
200 send_r_forbidden(req);
201 else
202 send_r_bad_request(req);
203 close(data_fd);
204 return 0;
205 }
206 req->data_mem = req->mmap_entry_var->mmap;
207 } else { /* File caching is disabled.
208 */
209 req->data_mem =
210 mmap(0, req->range_stop, PROT_READ, MAP_OPTIONS, data_fd, 0);
211 }
212
213 close(data_fd); /* close data file */
214
215 if (req->data_mem == MAP_FAILED) {
216 boa_perror(req, "mmap");
217 return 0;
218 }
219
220 if (req->range_start == 0 && req->range_stop == statbuf.st_size)
221 send_r_request_file_ok(req); /* All's well */
222 else
223 send_r_request_partial(req); /* All's well */
224
225 bytes = BUFFER_SIZE - req->buffer_end;
226
227 /* bytes is now how much the buffer can hold
228 * after the headers
229 */
230
231 if (bytes > 0) {
232 if (bytes > req->range_stop - req->range_start)
233 bytes = req->range_stop - req->range_start;
234
235 if (setjmp(params->env) == 0) {
236 params->handle_sigbus = 1;
237 memcpy(req->buffer + req->buffer_end,
238 &req->data_mem[req->filepos], bytes);
239 params->handle_sigbus = 0;
240 /* OK, SIGBUS **after** this point is very bad! */
241 } else {
242 char buf[30];
243 /* sigbus! */
244 log_error_doc(req);
245 reset_output_buffer(req);
246 send_r_error(req);
247 get_commonlog_time(buf);
248 fprintf(stderr, "%sGot SIGBUS in memcpy!\n", buf);
249 return 0;
250 }
251 req->buffer_end += bytes;
252 req->filepos += bytes;
253 if (req->range_stop == req->filepos) {
254 req_flush(req);
255 req->status = DONE;
256 }
257 }
258
259 /* We lose statbuf here, so make sure response has been sent */
260 return 1;
261 }
262
263 /*
264 * Name: check_if_stuff
265 * Description: Checks the If-Match, If-None-Match headers
266 *
267 * req->last_modified, and req->filesize MUST have been set
268 * before calling this function. This function should be called
269 * if req->if_types != 0.
270 *
271 * Return values:
272 * 1: Successful, continue sending the file
273 * 0: unsuccessful. We send the appropriate stuff. Close the connection.
274 */
275
276 static int check_if_stuff(request * req)
277 {
278 int comp = 0;
279 char *broken_etag[MAX_COMMA_SEP_ELEMENTS];
280 int broken_etag_size, i;
281 char new_etag[MAX_ETAG_LENGTH];
282
283 /* Although we allow multiple If-* directives to be used, we
284 * actually use only one. The priority used is shown below.
285 */
286
287 /* First try IF_MODIFIED_SINCE
288 */
289 if (req->if_types & IF_MODIFIED_SINCE) {
290 if (!modified_since(req->last_modified, req->if_modified_since)) {
291 send_r_not_modified(req);
292 return 0;
293 }
294 return 1;
295 }
296
297
298 /* Then try IF_MATCH
299 */
300 if (req->if_types & IF_MATCH) {
301
302 /* Check for the "*"
303 */
304 if (strncmp(req->if_match_etag, "\"*\"", 3) == 0) {
305 comp = 0; /* comparison is always ok */
306 } else {
307
308 /* Create the current ETag of the file.
309 */
310 create_etag(req->filesize, req->last_modified, new_etag);
311
312 /* Check if one of the ETags sent, match ours
313 */
314 break_comma_list(req->if_match_etag, broken_etag,
315 &broken_etag_size);
316
317 comp = 1;
318 for (i = 0; i < broken_etag_size; i++) {
319 comp = strcmp(broken_etag[i], new_etag);
320 if (comp == 0) /* matches! */
321 break;
322 }
323
324
325 }
326
327 if (comp == 0)
328 return 1;
329 send_r_precondition_failed(req);
330 return 0;
331 }
332
333 /* Then try IF_RANGE
334 */
335 if (req->if_types & IF_RANGE) {
336 if (req->if_range_etag[0] == '"') { /* ETag may contain a date, if If-Range
337 * was used.
338 */
339 /* Check for the "*"
340 */
341 if (strncmp(req->if_range_etag, "\"*\"", 3) == 0) {
342 comp = 0; /* comparison is always ok */
343 } else {
344
345 /* Create the current ETag
346 */
347 create_etag(req->filesize, req->last_modified, new_etag);
348
349 /* Check if one of the ETags sent, match ours
350 */
351
352 break_comma_list(req->if_range_etag, broken_etag,
353 &broken_etag_size);
354
355 comp = 1;
356 for (i = 0; i < broken_etag_size; i++) {
357 comp = strcmp(broken_etag[i], new_etag);
358 if (comp == 0) /* matches! */
359 break;
360 }
361
362
363 }
364 } else {
365 comp = modified_since(req->last_modified, req->if_range_etag);
366 }
367
368
369 /* File didn't change */
370 if (comp == 0)
371 return 1;
372
373 /* File has been changed, but it is Ok, so send the whole
374 * file.
375 */
376 req->range_start = req->range_stop = 0;
377 return 1;
378 }
379
380 /* Then try IF_NONE_MATCH
381 */
382 if (req->if_types & IF_NONE_MATCH) {
383 /* Check for the "*"
384 */
385 if (strncmp(req->if_none_match_etag, "\"*\"", 3) == 0) {
386 comp = 0; /* comparison is always ok */
387 } else {
388
389 /* Create the current ETag
390 */
391 create_etag(req->filesize, req->last_modified, new_etag);
392
393 /* Check if one of the ETags sent, match ours
394 */
395
396 break_comma_list(req->if_none_match_etag, broken_etag,
397 &broken_etag_size);
398
399 comp = 1;
400 for (i = 0; i < broken_etag_size; i++) {
401 comp = strcmp(broken_etag[i], new_etag);
402 if (comp == 0) /* matches! */
403 break;
404 }
405
406
407 }
408
409 if (comp == 0) {
410 send_r_not_modified(req);
411 return 0;
412 } else { /* it was modified */
413 send_r_precondition_failed(req);
414 return 0;
415 }
416 }
417
418 /* Unsupported type ? */
419
420 return 1; /* do the request */
421 }
422
423 /*
424 * Name: process_get
425 * Description: Writes a chunk of data to the socket.
426 *
427 * Return values:
428 * -1: request blocked, move to blocked queue
429 * 0: EOF or error, close it down
430 * 1: successful write, recycle in ready queue
431 */
432
433 int process_get(server_params * params, request * req)
434 {
435 int bytes_written;
436 volatile int bytes_to_write;
437
438 bytes_to_write = req->range_stop - req->filepos;
439 if (bytes_to_write > system_bufsize)
440 bytes_to_write = system_bufsize;
441
442
443 if (setjmp(params->env) == 0) {
444 params->handle_sigbus = 1;
445
446 bytes_written =
447 socket_send(req, req->data_mem + req->filepos, bytes_to_write);
448
449 params->handle_sigbus = 0;
450 /* OK, SIGBUS **after** this point is very bad! */
451 } else {
452 char buf[30];
453 /* sigbus! */
454 log_error_doc(req);
455 /* sending an error here is inappropriate
456 * if we are here, the file is mmapped, and thus,
457 * a content-length has been sent. If we send fewer bytes
458 * the client knows there has been a problem.
459 * We run the risk of accidentally sending the right number
460 * of bytes (or a few too many) and the client
461 * won't be the wiser.
462 */
463 req->status = DEAD;
464 get_commonlog_time(buf);
465 fprintf(stderr, "%sGot SIGBUS in write(2)!\n", buf);
466 return 0;
467 }
468
469 if (bytes_written < 0) {
470 if (errno == EWOULDBLOCK || errno == EAGAIN)
471 return -1;
472 /* request blocked at the pipe level, but keep going */
473 else {
474 if (errno != EPIPE) {
475 log_error_doc(req);
476 /* Can generate lots of log entries, */
477 perror("write");
478 /* OK to disable if your logs get too big */
479 }
480 req->status = DEAD;
481 return 0;
482 }
483 }
484 req->filepos += bytes_written;
485
486 if (req->filepos == req->range_stop) { /* EOF */
487 return 0;
488 } else
489 return 1; /* more to do */
490 }
491
492 /*
493 * Name: get_dir
494 * Description: Called from process_get if the request is a directory.
495 * statbuf must describe directory on input, since we may need its
496 * device, inode, and mtime.
497 * statbuf is updated, since we may need to check mtimes of a cache.
498 * returns:
499 * -1 error
500 * 0 cgi (either gunzip or auto-generated)
501 * >0 file descriptor of file
502 */
503
504 int get_dir(request * req, struct stat *statbuf)
505 {
506
507 char *directory_index;
508 int data_fd;
509
510 directory_index =
511 find_and_open_directory_index(req->pathname, 0, &data_fd);
512
513 if (directory_index) { /* look for index.html first?? */
514 if (data_fd != -1) { /* user's index file */
515 int ret;
516
517 /* Check if we can execute the file
518 */
519
520 strcat(req->request_uri, directory_index);
521 req->pathname = realloc( req->pathname, strlen(req->pathname) +
522 strlen( directory_index) + 1);
523 if (req->pathname == NULL) {
524 send_r_error( req);
525 return -1;
526 }
527
528 strcat( req->pathname, directory_index);
529
530 ret = is_executable_cgi(req, directory_index);
531 if (ret != 0) { /* it is a CGI */
532 close(data_fd); /* we don't need it */
533 if (ret == -1) {
534 send_r_not_found(req);
535 return -1;
536 }
537 return init_cgi(req);
538 }
539
540 /* Not a cgi */
541
542 fstat(data_fd, statbuf);
543 return data_fd;
544 }
545 if (errno == EACCES) {
546 send_r_forbidden(req);
547 return -1;
548 } else if (errno != ENOENT) {
549 /* if there is an error *other* than EACCES or ENOENT */
550 send_r_not_found(req);
551 return -1;
552 }
553 }
554
555 /* only here if index.html, index.html.gz don't exist */
556 if (dirmaker != NULL) { /* don't look for index.html... maybe automake? */
557 req->response_status = R_REQUEST_OK;
558 SQUASH_KA(req);
559
560 /* the indexer should take care of all headers */
561 if (req->http_version > HTTP_0_9) {
562 req_write(req, HTTP_VERSION " 200 OK\r\n");
563 print_http_headers(req);
564 print_last_modified(req);
565 req_write(req, "Content-Type: " TEXT_HTML CRLF CRLF);
566 req_flush(req);
567 }
568 if (req->method == M_HEAD)
569 return 0;
570
571 req->is_cgi = INDEXER_CGI;
572 return init_cgi(req);
573 /* in this case, 0 means success */
574 } else if (cachedir) {
575 return get_cachedir_file(req, statbuf);
576 } else { /* neither index.html nor autogenerate are allowed */
577 send_r_forbidden(req);
578 return -1; /* nothing worked */
579 }
580 }
581
582 int get_cachedir_file(request * req, struct stat *statbuf)
583 {
584
585 char pathname_with_index[MAX_PATH_LENGTH];
586 int data_fd;
587 time_t real_dir_mtime;
588
589 real_dir_mtime = statbuf->st_mtime;
590 sprintf(pathname_with_index, "%s/dir.%u.%lu",
591 cachedir, (unsigned int) statbuf->st_dev,
592 (unsigned long int)statbuf->st_ino);
593 data_fd = open(pathname_with_index, O_RDONLY);
594
595 if (data_fd != -1) { /* index cache */
596
597 fstat(data_fd, statbuf);
598 if (statbuf->st_mtime > real_dir_mtime) {
599 statbuf->st_mtime = real_dir_mtime; /* lie */
600 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
601 return data_fd;
602 }
603 close(data_fd);
604 unlink(pathname_with_index); /* cache is stale, delete it */
605 }
606 if (index_directory(req, pathname_with_index) == -1)
607 return -1;
608
609 data_fd = open(pathname_with_index, O_RDONLY); /* Last chance */
610 if (data_fd != -1) {
611 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
612 fstat(data_fd, statbuf);
613 statbuf->st_mtime = real_dir_mtime; /* lie */
614 return data_fd;
615 }
616
617 boa_perror(req, "re-opening dircache");
618 return -1; /* Nothing worked. */
619
620 }
621
622 /*
623 * Name: index_directory
624 * Description: Called from get_cachedir_file if a directory html
625 * has to be generated on the fly
626 * returns -1 for problem, else 0
627 * This version is the fastest, ugliest, and most accurate yet.
628 * It solves the "stale size or type" problem by not ever giving
629 * the size or type. This also speeds it up since no per-file
630 * stat() is required.
631 */
632
633 int index_directory(request * req, char *dest_filename)
634 {
635 DIR *request_dir;
636 FILE *fdstream;
637 struct dirent *dirbuf;
638 int bytes = 0;
639 char *escname = NULL;
640
641 if (chdir(req->pathname) == -1) {
642 if (errno == EACCES || errno == EPERM) {
643 send_r_forbidden(req);
644 } else {
645 log_error_doc(req);
646 perror("chdir");
647 send_r_bad_request(req);
648 }
649 return -1;
650 }
651
652 request_dir = opendir(".");
653 if (request_dir == NULL) {
654 int errno_save = errno;
655 send_r_error(req);
656 log_error_time();
657 fprintf(stderr, "directory \"%s\": ", req->pathname);
658 errno = errno_save;
659 perror("opendir");
660 return -1;
661 }
662
663 fdstream = fopen(dest_filename, "w");
664 if (fdstream == NULL) {
665 boa_perror(req, "dircache fopen");
666 closedir(request_dir);
667 return -1;
668 }
669
670 bytes += fprintf(fdstream,
671 "<HTML><HEAD>\n<TITLE>Index of %s</TITLE>\n</HEAD>\n\n",
672 req->request_uri);
673 bytes +=
674 fprintf(fdstream, "<BODY>\n\n<H2>Index of %s</H2>\n\n<PRE>\n",
675 req->request_uri);
676
677 while ((dirbuf = readdir(request_dir))) {
678 if (!strcmp(dirbuf->d_name, "."))
679 continue;
680
681 if (!strcmp(dirbuf->d_name, "..")) {
682 bytes += fprintf(fdstream,
683 " [DIR] <A HREF=\"../\">Parent Directory</A>\n");
684 continue;
685 }
686
687 if ((escname = escape_string(dirbuf->d_name, NULL)) != NULL) {
688 bytes += fprintf(fdstream, " <A HREF=\"%s\">%s</A>\n",
689 escname, dirbuf->d_name);
690 free(escname);
691 escname = NULL;
692 }
693 }
694 closedir(request_dir);
695 bytes += fprintf(fdstream, "</PRE>\n\n</BODY>\n</HTML>\n");
696
697 fclose(fdstream);
698
699 chdir(server_root);
700
701 req->filesize = bytes; /* for logging transfer size */
702 return 0; /* success */
703 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26