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

Contents of /hydra/src/get.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.15 - (show annotations)
Sun Sep 29 11:00:04 2002 UTC (21 years, 6 months ago) by nmav
Branch: MAIN
Changes since 1.14: +82 -8 lines
File MIME type: text/plain
Added support for If-Range, If-Match, If-None-Match HTTP/1.1 header
fields. The server also generates ETag headers for static content using
the last modified field of the file, and the file size.

Fixed the behaviour of the range parser, when a bogus range was received.
Now it does not send any message, it silently ignores the bogus range.

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.14 2002/09/29 08:02:56 nmav Exp $*/
25
26 #include "boa.h"
27 #include "socket.h"
28
29 /* local prototypes */
30 int get_cachedir_file(request * req, struct stat *statbuf);
31 int index_directory(request * req, char *dest_filename);
32 static int check_if_stuff( request* req);
33
34 /*
35 * Name: init_get
36 * Description: Initializes a non-script GET or HEAD request.
37 *
38 * Return values:
39 * 0: finished or error, request will be freed
40 * 1: successfully initialized, added to ready queue
41 */
42
43 int init_get(server_params * params, request * req)
44 {
45 int data_fd, saved_errno;
46 struct stat statbuf;
47 volatile int bytes;
48
49 data_fd = open(req->pathname, O_RDONLY);
50 saved_errno = errno; /* might not get used */
51
52 if (data_fd == -1) {
53 log_error_doc(req);
54 errno = saved_errno;
55 perror("document open");
56
57 if (saved_errno == ENOENT)
58 send_r_not_found(req);
59 else if (saved_errno == EACCES)
60 send_r_forbidden(req);
61 else
62 send_r_bad_request(req);
63 return 0;
64 }
65
66 if (fstat( data_fd, &statbuf) == -1) {
67 /* this is quite impossible, since the file
68 * was opened before.
69 */
70 close(data_fd);
71 send_r_not_found(req);
72 return 0;
73 }
74
75 if (S_ISDIR(statbuf.st_mode)) { /* directory */
76 close(data_fd); /* close dir */
77
78 if (req->pathname[strlen(req->pathname) - 1] != '/') {
79 char buffer[3 * MAX_PATH_LENGTH + 128];
80
81 create_url( buffer, sizeof(buffer), req->secure, req->hostname,
82 params->server_s[req->secure].port, req->request_uri);
83
84 send_r_moved_perm(req, buffer);
85 return 0;
86 }
87 data_fd = get_dir(req, &statbuf); /* updates statbuf */
88
89 if (data_fd == -1) /* couldn't do it */
90 return 0; /* errors reported by get_dir */
91 else if (data_fd <= 1)
92 /* data_fd == 0 -> close it down, 1 -> continue */
93 return data_fd;
94 /* else, data_fd contains the fd of the file... */
95 }
96
97 req->filesize = statbuf.st_size;
98 req->last_modified = statbuf.st_mtime;
99
100 /* Check the If-Match, If-Modified etc stuff.
101 */
102 if (check_if_stuff( req)==0) {
103 close( data_fd);
104 return 0;
105 }
106 /* Move on */
107
108 if (req->range_stop == 0)
109 req->range_stop = statbuf.st_size;
110
111 /* out of range! */
112 if (req->range_start > statbuf.st_size ||
113 req->range_stop > statbuf.st_size ||
114 req->range_stop < req->range_start) {
115 send_r_range_unsatisfiable(req);
116 close(data_fd);
117 return 0;
118 }
119
120 if (req->method == M_HEAD || req->filesize == 0) {
121 send_r_request_ok(req);
122 close(data_fd);
123 return 0;
124 }
125
126 if (req->range_stop > max_file_size_cache) {
127
128 if ( req->range_start == 0 && req->range_stop == statbuf.st_size)
129 send_r_request_ok(req); /* All's well */
130 else {
131 /* if ranges were used, then lseek to the start given
132 */
133 if ( lseek( data_fd, req->range_start, SEEK_SET) == (off_t) -1) {
134 close(data_fd);
135 send_r_not_found(req);
136 return 0;
137 }
138 send_r_request_partial(req);/* All's well */
139 }
140
141 req->status = PIPE_READ;
142 req->cgi_status = CGI_BUFFER;
143 req->data_fd = data_fd;
144 req_flush(req); /* this should *always* complete due to
145 the size of the I/O buffers */
146 req->header_line = req->header_end = req->buffer;
147 req->pipe_range_stop = req->range_stop;
148 return 1;
149 }
150
151 if (req->range_stop == 0) { /* done */
152 send_r_request_ok(req); /* All's well *so far* */
153 close(data_fd);
154 return 1;
155 }
156
157 /* NOTE: I (Jon Nelson) tried performing a read(2)
158 * into the output buffer provided the file data would
159 * fit, before mmapping, and if successful, writing that
160 * and stopping there -- all to avoid the cost
161 * of a mmap. Oddly, it was *slower* in benchmarks.
162 */
163 if (max_files_cache > 0) {
164 req->mmap_entry_var = find_mmap(data_fd, &statbuf);
165 if (req->mmap_entry_var == NULL) {
166 req->buffer_end = 0;
167 if (errno == ENOENT)
168 send_r_not_found(req);
169 else if (errno == EACCES)
170 send_r_forbidden(req);
171 else
172 send_r_bad_request(req);
173 close(data_fd);
174 return 0;
175 }
176 req->data_mem = req->mmap_entry_var->mmap;
177 } else { /* File caching is disabled.
178 */
179 req->data_mem = mmap(0, req->range_stop, PROT_READ, MAP_OPTIONS, data_fd, 0);
180 }
181
182 close(data_fd); /* close data file */
183
184 if ( req->data_mem == MAP_FAILED) {
185 boa_perror(req, "mmap");
186 return 0;
187 }
188
189 if ( req->range_start == 0 && req->range_stop == statbuf.st_size)
190 send_r_request_ok(req); /* All's well */
191 else
192 send_r_request_partial(req);/* All's well */
193
194 bytes = BUFFER_SIZE - req->buffer_end;
195
196 /* bytes is now how much the buffer can hold
197 * after the headers
198 */
199 req->filepos = req->range_start;
200
201 if (bytes > 0) {
202 if (bytes > req->range_stop - req->range_start)
203 bytes = req->range_stop - req->range_start;
204
205 if (setjmp(params->env) == 0) {
206 params->handle_sigbus = 1;
207 memcpy(req->buffer + req->buffer_end, &req->data_mem[req->filepos],
208 bytes);
209 params->handle_sigbus = 0;
210 /* OK, SIGBUS **after** this point is very bad! */
211 } else {
212 char buf[30];
213 /* sigbus! */
214 log_error_doc(req);
215 reset_output_buffer(req);
216 send_r_error(req);
217 get_commonlog_time( buf);
218 fprintf(stderr, "%sGot SIGBUS in memcpy!\n",
219 buf);
220 return 0;
221 }
222 req->buffer_end += bytes;
223 req->filepos += bytes;
224 if (req->range_stop == req->filepos) {
225 req_flush(req);
226 req->status = DONE;
227 }
228 }
229
230 /* We lose statbuf here, so make sure response has been sent */
231 return 1;
232 }
233
234 /*
235 * Name: check_if_stuff
236 * Description: Checks the If-Match, If-None-Match headers
237 *
238 * req->last_modified, and req->filesize MUST have been set
239 * before calling this function.
240 *
241 * Return values:
242 * 1: Successful, continue sending the file
243 * 0: unsuccessful. We send the appropriate stuff. Close the connection.
244 */
245
246 static int check_if_stuff( request* req)
247 {
248 int comp;
249
250 if (req->if_modified_since) {
251 if ( !modified_since(req->last_modified, req->if_modified_since)) {
252 send_r_not_modified(req);
253 return 0;
254 }
255 }
256
257 if (req->etag == NULL || req->if_type == 0)
258 return 1;
259
260 /* In the case of If-Range, we do not accept modification
261 * dates. Use the ETag!!
262 */
263
264 if (strcmp( req->etag, "\"*\"") == 0) {
265 comp = 0; /* comparison is always ok */
266 } else {
267 char buffer[MAX_ETAG_LENGTH + 3];
268
269 sprintf( buffer, "\"%lu-%lu\"", req->last_modified % 10000,
270 req->filesize % 10000);
271
272 comp = strcmp( req->etag, buffer);
273 /* mod 10000 is used to allow no more than 4 characters -- max is 9999
274 */
275 }
276
277 if (req->if_type == IF_MATCH) {
278 if (comp == 0) return 1;
279 send_r_precondition_failed( req);
280 return 0;
281 }
282
283 if (req->if_type == IF_RANGE) {
284 if (comp == 0) return 1;
285
286 /* Ok, but send the whole file, because
287 * it has been changed.
288 */
289 req->range_start = req->range_stop = 0;
290 return 1;
291 }
292
293 if (req->if_type == IF_NONE_MATCH) {
294 if (comp == 0) {
295 send_r_precondition_failed( req);
296 return 0;
297 }
298 return 1;
299 }
300
301 /* Unsupported type ? */
302
303 return 1; /* do the request */
304 }
305
306 /*
307 * Name: process_get
308 * Description: Writes a chunk of data to the socket.
309 *
310 * Return values:
311 * -1: request blocked, move to blocked queue
312 * 0: EOF or error, close it down
313 * 1: successful write, recycle in ready queue
314 */
315
316 int process_get(server_params * params, request * req)
317 {
318 int bytes_written;
319 volatile int bytes_to_write;
320
321 bytes_to_write = req->range_stop - req->filepos;
322 if (bytes_to_write > SOCKETBUF_SIZE)
323 bytes_to_write = SOCKETBUF_SIZE;
324
325
326 if (setjmp(params->env) == 0) {
327 params->handle_sigbus = 1;
328
329 bytes_written =
330 socket_send(req, req->data_mem + req->filepos, bytes_to_write);
331
332 params->handle_sigbus = 0;
333 /* OK, SIGBUS **after** this point is very bad! */
334 } else {
335 char buf[30];
336 /* sigbus! */
337 log_error_doc(req);
338 /* sending an error here is inappropriate
339 * if we are here, the file is mmapped, and thus,
340 * a content-length has been sent. If we send fewer bytes
341 * the client knows there has been a problem.
342 * We run the risk of accidentally sending the right number
343 * of bytes (or a few too many) and the client
344 * won't be the wiser.
345 */
346 req->status = DEAD;
347 get_commonlog_time( buf);
348 fprintf(stderr, "%sGot SIGBUS in write(2)!\n", buf);
349 return 0;
350 }
351
352 if (bytes_written < 0) {
353 if (errno == EWOULDBLOCK || errno == EAGAIN)
354 return -1;
355 /* request blocked at the pipe level, but keep going */
356 else {
357 if (errno != EPIPE) {
358 log_error_doc(req);
359 /* Can generate lots of log entries, */
360 perror("write");
361 /* OK to disable if your logs get too big */
362 }
363 req->status = DEAD;
364 return 0;
365 }
366 }
367 req->filepos += bytes_written;
368
369 if (req->filepos == req->range_stop) { /* EOF */
370 return 0;
371 } else
372 return 1; /* more to do */
373 }
374
375 /*
376 * Name: get_dir
377 * Description: Called from process_get if the request is a directory.
378 * statbuf must describe directory on input, since we may need its
379 * device, inode, and mtime.
380 * statbuf is updated, since we may need to check mtimes of a cache.
381 * returns:
382 * -1 error
383 * 0 cgi (either gunzip or auto-generated)
384 * >0 file descriptor of file
385 */
386
387 int get_dir(request * req, struct stat *statbuf)
388 {
389
390 char* directory_index;
391 int data_fd;
392
393 directory_index = find_and_open_directory_index( req->pathname, 0, &data_fd);
394
395 if (directory_index) { /* look for index.html first?? */
396 if (data_fd != -1) { /* user's index file */
397 int ret;
398
399 /* Check if we can execute the file
400 */
401
402 strcat(req->request_uri, directory_index);
403
404 ret = is_executable_cgi( req, directory_index);
405 if ( ret != 0) { /* it is a CGI */
406 close( data_fd); /* we don't need it */
407 if (ret == -1) {
408 send_r_error(req);
409 return -1;
410 }
411 return init_cgi(req);
412 }
413
414 /* Not a cgi */
415
416 fstat(data_fd, statbuf);
417 return data_fd;
418 }
419 if (errno == EACCES) {
420 send_r_forbidden(req);
421 return -1;
422 } else if (errno != ENOENT) {
423 /* if there is an error *other* than EACCES or ENOENT */
424 send_r_not_found(req);
425 return -1;
426 }
427 }
428
429 /* only here if index.html, index.html.gz don't exist */
430 if (dirmaker != NULL) { /* don't look for index.html... maybe automake? */
431 req->response_status = R_REQUEST_OK;
432 SQUASH_KA(req);
433
434 /* the indexer should take care of all headers */
435 if (!req->simple) {
436 req_write(req, HTTP_VERSION" 200 OK\r\n");
437 print_http_headers(req);
438 print_last_modified(req);
439 req_write(req, "Content-Type: " TEXT_HTML CRLF CRLF);
440 req_flush(req);
441 }
442 if (req->method == M_HEAD)
443 return 0;
444
445 req->is_cgi = INDEXER_CGI;
446 return init_cgi(req);
447 /* in this case, 0 means success */
448 } else if (cachedir) {
449 return get_cachedir_file(req, statbuf);
450 } else { /* neither index.html nor autogenerate are allowed */
451 send_r_forbidden(req);
452 return -1; /* nothing worked */
453 }
454 }
455
456 int get_cachedir_file(request * req, struct stat *statbuf)
457 {
458
459 char pathname_with_index[MAX_PATH_LENGTH];
460 int data_fd;
461 time_t real_dir_mtime;
462
463 real_dir_mtime = statbuf->st_mtime;
464 sprintf(pathname_with_index, "%s/dir.%d.%ld",
465 cachedir, (int) statbuf->st_dev, statbuf->st_ino);
466 data_fd = open(pathname_with_index, O_RDONLY);
467
468 if (data_fd != -1) { /* index cache */
469
470 fstat(data_fd, statbuf);
471 if (statbuf->st_mtime > real_dir_mtime) {
472 statbuf->st_mtime = real_dir_mtime; /* lie */
473 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
474 return data_fd;
475 }
476 close(data_fd);
477 unlink(pathname_with_index); /* cache is stale, delete it */
478 }
479 if (index_directory(req, pathname_with_index) == -1)
480 return -1;
481
482 data_fd = open(pathname_with_index, O_RDONLY); /* Last chance */
483 if (data_fd != -1) {
484 strcpy(req->request_uri, find_default_directory_index()); /* for mimetype */
485 fstat(data_fd, statbuf);
486 statbuf->st_mtime = real_dir_mtime; /* lie */
487 return data_fd;
488 }
489
490 boa_perror(req, "re-opening dircache");
491 return -1; /* Nothing worked. */
492
493 }
494
495 /*
496 * Name: index_directory
497 * Description: Called from get_cachedir_file if a directory html
498 * has to be generated on the fly
499 * returns -1 for problem, else 0
500 * This version is the fastest, ugliest, and most accurate yet.
501 * It solves the "stale size or type" problem by not ever giving
502 * the size or type. This also speeds it up since no per-file
503 * stat() is required.
504 */
505
506 int index_directory(request * req, char *dest_filename)
507 {
508 DIR *request_dir;
509 FILE *fdstream;
510 struct dirent *dirbuf;
511 int bytes = 0;
512 char *escname = NULL;
513
514 if (chdir(req->pathname) == -1) {
515 if (errno == EACCES || errno == EPERM) {
516 send_r_forbidden(req);
517 } else {
518 log_error_doc(req);
519 perror("chdir");
520 send_r_bad_request(req);
521 }
522 return -1;
523 }
524
525 request_dir = opendir(".");
526 if (request_dir == NULL) {
527 int errno_save = errno;
528 send_r_error(req);
529 log_error_time();
530 fprintf(stderr, "directory \"%s\": ", req->pathname);
531 errno = errno_save;
532 perror("opendir");
533 return -1;
534 }
535
536 fdstream = fopen(dest_filename, "w");
537 if (fdstream == NULL) {
538 boa_perror(req, "dircache fopen");
539 closedir(request_dir);
540 return -1;
541 }
542
543 bytes += fprintf(fdstream,
544 "<HTML><HEAD>\n<TITLE>Index of %s</TITLE>\n</HEAD>\n\n",
545 req->request_uri);
546 bytes +=
547 fprintf(fdstream, "<BODY>\n\n<H2>Index of %s</H2>\n\n<PRE>\n",
548 req->request_uri);
549
550 while ((dirbuf = readdir(request_dir))) {
551 if (!strcmp(dirbuf->d_name, "."))
552 continue;
553
554 if (!strcmp(dirbuf->d_name, "..")) {
555 bytes += fprintf(fdstream,
556 " [DIR] <A HREF=\"../\">Parent Directory</A>\n");
557 continue;
558 }
559
560 if ((escname = escape_string(dirbuf->d_name, NULL)) != NULL) {
561 bytes += fprintf(fdstream, " <A HREF=\"%s\">%s</A>\n",
562 escname, dirbuf->d_name);
563 free(escname);
564 escname = NULL;
565 }
566 }
567 closedir(request_dir);
568 bytes += fprintf(fdstream, "</PRE>\n\n</BODY>\n</HTML>\n");
569
570 fclose(fdstream);
571
572 chdir(server_root);
573
574 req->filesize = bytes; /* for logging transfer size */
575 return 0; /* success */
576 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26