coar 97/09/12 11:56:13
Modified: src/main http_core.c http_protocol.c http_protocol.h util_script.c src/modules/standard mod_include.c Log: Separate last-modified and etag processing into separate routines, and insert calls in appropriate places. (Lotta sins covered by that sentence..) Reviewed by: Roy Fielding Revision Changes Path 1.121 +7 -3 apachen/src/main/http_core.c Index: http_core.c =================================================================== RCS file: /export/home/cvs/apachen/src/main/http_core.c,v retrieving revision 1.120 retrieving revision 1.121 diff -u -r1.120 -r1.121 --- http_core.c 1997/09/11 18:46:44 1.120 +++ http_core.c 1997/09/12 18:56:01 1.121 @@ -1658,9 +1658,13 @@ return FORBIDDEN; } - if ((errstatus = set_last_modified (r, r->finfo.st_mtime)) - || (errstatus = set_content_length (r, r->finfo.st_size))) - return errstatus; + update_mtime (r, r->finfo.st_mtime); + set_last_modified(r); + set_etag(r); + if (((errstatus = meets_conditions(r)) != OK) + || (errstatus = set_content_length (r, r->finfo.st_size))) { + return errstatus; + } #ifdef USE_MMAP_FILES block_alarms(); 1.161 +126 -62 apachen/src/main/http_protocol.c Index: http_protocol.c =================================================================== RCS file: /export/home/cvs/apachen/src/main/http_protocol.c,v retrieving revision 1.160 retrieving revision 1.161 diff -u -r1.160 -r1.161 --- http_protocol.c 1997/09/06 01:03:33 1.160 +++ http_protocol.c 1997/09/12 18:56:02 1.161 @@ -348,10 +348,15 @@ return 0; } -API_EXPORT(int) set_last_modified(request_rec *r, time_t mtime) +/* + * Return the latest rational time from a request/mtime (modification time) + * pair. We return the mtime unless it's in the future, in which case we + * return the current time. We use the request time as a reference in order + * to limit the number of calls to time(). We don't check for futurosity + * unless the mtime is at least as new as the reference. + */ +API_EXPORT(time_t) rationalize_mtime(request_rec *r, time_t mtime) { - char *etag, weak_etag[MAX_STRING_LEN]; - char *if_match, *if_modified_since, *if_unmodified, *if_nonematch; time_t now; /* For all static responses, it's almost certain that the file was @@ -359,90 +364,97 @@ * no reason to call time(NULL) again. But if the response has been * created on demand, then it might be newer than the time the request * started. In this event we really have to call time(NULL) again - * so that we can give the clients the most accurate Last-Modified. - */ - now = (mtime <= r->request_time) ? r->request_time : time(NULL); - - table_set(r->headers_out, "Last-Modified", - gm_timestr_822(r->pool, (mtime > now) ? now : mtime)); - - /* Make an ETag header out of various pieces of information. We use - * the last-modified date and, if we have a real file, the - * length and inode number - note that this doesn't have to match - * the content-length (i.e. includes), it just has to be unique - * for the file. - * - * If the request was made within a second of the last-modified date, - * we send a weak tag instead of a strong one, since it could - * be modified again later in the second, and the validation - * would be incorrect. - */ + * so that we can give the clients the most accurate Last-Modified. If we + * were given a time in the future, we return the current time - the + * Last-Modified can't be in the future. + */ + now = (mtime < r->request_time) ? r->request_time : time(NULL); + return (mtime > now) ? now : mtime; +} - if (r->finfo.st_mode != 0) - ap_snprintf(weak_etag, sizeof(weak_etag), "W/\"%lx-%lx-%lx\"", - (unsigned long)r->finfo.st_ino, - (unsigned long)r->finfo.st_size, (unsigned long)mtime); - else - ap_snprintf(weak_etag, sizeof(weak_etag), "W/\"%lx\"", - (unsigned long)mtime); - - etag = weak_etag + ((r->request_time - mtime > 1) ? 2 : 0); - table_set(r->headers_out, "ETag", etag); +API_EXPORT(int) meets_conditions(request_rec *r) +{ + char *etag = table_get(r->headers_out, "ETag"); + char *if_match, *if_modified_since, *if_unmodified, *if_nonematch; + time_t mtime; - /* Check for conditional requests --- note that we only want to do + /* + * Check for conditional requests --- note that we only want to do * this if we are successful so far and we are not processing a * subrequest or an ErrorDocument. * - * The order of the checks is important, since etag checks are supposed + * The order of the checks is important, since ETag checks are supposed * to be more accurate than checks relative to the modification time. + * However, not all documents are guaranteed to *have* ETags, and some + * might have Last-Modified values w/o ETags, so this gets a little + * complicated. */ - - if (!is_HTTP_SUCCESS(r->status) || r->no_local_copy) + + if (!is_HTTP_SUCCESS(r->status) || r->no_local_copy) { return OK; + } + + mtime = (r->mtime != 0) ? r->mtime : time(NULL); - /* If an If-Match request-header field was given and - * if our ETag does not match any of the entity tags in that field - * and the field value is not "*" (meaning match anything), then - * respond with a status of 412 (Precondition Failed). + /* + * If an If-Match request-header field was given + * AND if our ETag does not match any of the entity tags in that field + * AND the field value is not "*" (meaning match anything), then + * respond with a status of 412 (Precondition Failed). */ if ((if_match = table_get(r->headers_in, "If-Match")) != NULL) { - if ((if_match[0] != '*') && !find_token(r->pool, if_match, etag)) - return HTTP_PRECONDITION_FAILED; + if ((etag == NULL) || + ((if_match[0] != '*') && !find_token(r->pool, if_match, etag))) { + return HTTP_PRECONDITION_FAILED; + } } - /* Else if a valid If-Unmodified-Since request-header field was given - * and the requested resource has been modified since the time + /* + * Else if a valid If-Unmodified-Since request-header field was given + * AND the requested resource has been modified since the time * specified in this field, then the server MUST - * respond with a status of 412 (Precondition Failed). + * respond with a status of 412 (Precondition Failed). */ - else if ((if_unmodified = table_get(r->headers_in, "If-Unmodified-Since")) - != NULL) { - time_t ius = parseHTTPdate(if_unmodified); - - if ((ius != BAD_DATE) && (mtime > ius)) - return HTTP_PRECONDITION_FAILED; + else { + if_unmodified = table_get(r->headers_in, "If-Unmodified-Since"); + if (if_unmodified != NULL) { + time_t ius = parseHTTPdate(if_unmodified); + + if ((ius != BAD_DATE) && (mtime > ius)) { + return HTTP_PRECONDITION_FAILED; + } + } } - /* If an If-None-Match request-header field was given and - * if our ETag matches any of the entity tags in that field or - * if the field value is "*" (meaning match anything), then + /* + * If an If-None-Match request-header field was given + * AND if our ETag matches any of the entity tags in that field + * OR if the field value is "*" (meaning match anything), then * if the request method was GET or HEAD, the server SHOULD * respond with a 304 (Not Modified) response. * For all other request methods, the server MUST * respond with a status of 412 (Precondition Failed). */ - if ((if_nonematch = table_get(r->headers_in, "If-None-Match")) != NULL) { - if ((if_nonematch[0] == '*') || find_token(r->pool,if_nonematch,etag)) - return (r->method_number == M_GET) ? HTTP_NOT_MODIFIED - : HTTP_PRECONDITION_FAILED; + if_nonematch = table_get(r->headers_in, "If-None-Match"); + if (if_nonematch != NULL) { + int rstatus; + + if ((if_nonematch[0] == '*') + || ((etag != NULL) && find_token(r->pool, if_nonematch, etag))) { + rstatus = (r->method_number == M_GET) + ? HTTP_NOT_MODIFIED + : HTTP_PRECONDITION_FAILED; + return rstatus; + } } - /* Else if a valid If-Modified-Since request-header field was given - * and it is a GET or HEAD request - * and the requested resource has not been modified since the time + /* + * Else if a valid If-Modified-Since request-header field was given + * AND it is a GET or HEAD request + * AND the requested resource has not been modified since the time * specified in this field, then the server MUST * respond with a status of 304 (Not Modified). * A date later than the server's current request time is invalid. @@ -452,11 +464,63 @@ table_get(r->headers_in, "If-Modified-Since")) != NULL)) { time_t ims = parseHTTPdate(if_modified_since); - if ((ims >= mtime) && (ims <= r->request_time)) + if ((ims >= mtime) && (ims <= r->request_time)) { return HTTP_NOT_MODIFIED; + } } - return OK; +} + +/* + * Construct an entity tag (ETag) from resource information. If it's a real + * file, build in some of the file characteristics. If the modification time + * is newer than (request-time minus 1 second), mark the ETag as weak - it + * could be modified again in as short an interval. We rationalize the + * modification time we're given to keep it from being in the future. + */ +API_EXPORT(void) set_etag(request_rec *r) +{ + char *etag, weak_etag[MAX_STRING_LEN]; + + /* + * Make an ETag header out of various pieces of information. We use + * the last-modified date and, if we have a real file, the + * length and inode number - note that this doesn't have to match + * the content-length (i.e. includes), it just has to be unique + * for the file. + * + * If the request was made within a second of the last-modified date, + * we send a weak tag instead of a strong one, since it could + * be modified again later in the second, and the validation + * would be incorrect. + */ + + if (r->finfo.st_mode != 0) { + ap_snprintf(weak_etag, sizeof(weak_etag), "W/\"%lx-%lx-%lx\"", + (unsigned long)r->finfo.st_ino, + (unsigned long)r->finfo.st_size, + (unsigned long)r->mtime); + } + else { + ap_snprintf(weak_etag, sizeof(weak_etag), "W/\"%lx\"", + (unsigned long)r->mtime); + } + + etag = weak_etag + ((r->request_time - r->mtime > 1) ? 2 : 0); + table_set(r->headers_out, "ETag", etag); +} + +/* + * This function sets the Last-Modified output header field to the value + * of the mtime field in the request structure - rationalized to keep it from + * being in the future. + */ +API_EXPORT(void) set_last_modified(request_rec *r) +{ + time_t mod_time = rationalize_mtime(r, r->mtime); + + table_set(r->headers_out, "Last-Modified", + gm_timestr_822(r->pool, mod_time)); } /* Get a line of protocol input, including any continuation lines 1.27 +3 -1 apachen/src/main/http_protocol.h Index: http_protocol.h =================================================================== RCS file: /export/home/cvs/apachen/src/main/http_protocol.h,v retrieving revision 1.26 retrieving revision 1.27 diff -u -r1.26 -r1.27 --- http_protocol.h 1997/08/18 07:19:36 1.26 +++ http_protocol.h 1997/09/12 18:56:04 1.27 @@ -95,7 +95,9 @@ API_EXPORT(int) set_content_length (request_rec *r, long length); int set_keepalive (request_rec *r); -API_EXPORT(int) set_last_modified (request_rec *r, time_t mtime); +API_EXPORT(time_t) rationalize_mtime(request_rec *r, time_t mtime); +API_EXPORT(void) set_etag(request_rec *r); +API_EXPORT(void) set_last_modified(request_rec *r); /* Other ways to send stuff at the client. All of these keep track * of bytes_sent automatically. This indirection is intended to make 1.71 +38 -2 apachen/src/main/util_script.c Index: util_script.c =================================================================== RCS file: /export/home/cvs/apachen/src/main/util_script.c,v retrieving revision 1.70 retrieving revision 1.71 diff -u -r1.70 -r1.71 --- util_script.c 1997/08/31 21:28:54 1.70 +++ util_script.c 1997/09/12 18:56:05 1.71 @@ -323,12 +323,13 @@ } -static int scan_script_header_err_core (request_rec *r, char *buffer, +static int scan_script_header_err_core(request_rec *r, char *buffer, int (*getsfunc)(char *, int, void *), void *getsfunc_data) { char x[MAX_STRING_LEN]; char *w, *l; int p; + int cgi_status = HTTP_OK; if (buffer) *buffer = '\0'; w = buffer ? buffer : x; @@ -352,9 +353,26 @@ else w[p-1] = '\0'; } + /* + * If we've finished reading the headers, check to make sure any + * HTTP/1.1 conditions are met. If so, we're done; normal processing + * will handle the script's output. If not, just return the error. + * The appropriate thing to do would be to send the script process a + * SIGPIPE to let it know we're ignoring it, close the channel to the + * script process, and *then* return the failed-to-meet-condition + * error. Otherwise we'd be waiting for the script to finish + * blithering before telling the client the output was no good. + * However, we don't have the information to do that, so we have to + * leave it to an upper layer. + */ if (w[0] == '\0') { + int cond_status = OK; + kill_timeout (r); - return OK; + if ((cgi_status == HTTP_OK) && (r->method_number == M_GET)) { + cond_status = meets_conditions(r); + } + return cond_status; } /* if we see a bogus header don't ignore it. Shout and scream */ @@ -399,6 +417,24 @@ } else if(!strcasecmp(w,"Transfer-Encoding")) { table_set (r->headers_out, w, l); + } +/* + * If the script gave us a Last-Modified header, we can't just pass it on + * blindly because of restrictions on future values. + */ + else if (!strcasecmp(w, "Last-Modified")) { + time_t mtime = parseHTTPdate(l); + + update_mtime(r, mtime); + set_last_modified(r); + } +/* + * If the script returned a specific status, that's what we'll use - otherwise + * we assume 200 OK. + */ + else if (!strcasecmp(w, "Status")) { + table_set (r->headers_out, w, l); + cgi_status = atoi(l); } /* The HTTP specification says that it is legal to merge duplicate 1.50 +8 -3 apachen/src/modules/standard/mod_include.c Index: mod_include.c =================================================================== RCS file: /export/home/cvs/apachen/src/modules/standard/mod_include.c,v retrieving revision 1.49 retrieving revision 1.50 diff -u -r1.49 -r1.50 --- mod_include.c 1997/09/05 23:11:14 1.49 +++ mod_include.c 1997/09/12 18:56:10 1.50 @@ -2008,13 +2008,18 @@ return FORBIDDEN; } - if (*state == xbithack_full + if ((*state == xbithack_full) #if !defined(__EMX__) && !defined(WIN32) /* OS/2 dosen't support Groups. */ && (r->finfo.st_mode & S_IXGRP) #endif - && (errstatus = set_last_modified (r, r->finfo.st_mtime))) - return errstatus; + ) { + update_mtime(r, r->finfo.st_mtime); + set_last_modified(r); + } + if ((errstatus = meets_conditions(r)) != OK) { + return errstatus; + } send_http_header(r);