httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From c...@decus.org (Rodent of Unusual Size)
Subject [PATCH] (PR 894,895) ETags, Last-Modified, and scripts
Date Tue, 19 Aug 1997 20:42:00 GMT
    PR#894 (by Dean) complains that set_last_modified() does too much,
    specifically futzing with ETags.  #895 (also by Dean) whinges that
    Last-Modified headers emitted by scripts don't go through
    conditional checks.

    My first pass at this caused ETags to be generated for scripts; not
    good.  Alexei raised the issue (raised more than 18 months
    previously) that maybe the server shouldn't be doing condition-checks
    for script requests.

    For the first part..  I've broken set_last_modified() into three
    routines:

     o set_last_modified(), which does the Right Stuff to rationalize
       the time and store it in headers_out
     o set_etag(), which calculates and sets the ETag in headers_out
     o meets_conditions(), which checks the headers_out fields against
       the requirements of the headers_in fields

    I've modified the places where set_last_modified() was called to use
    the new arrangement, and added a call to [the new]
    set_last_modified() and meets_conditions() to util_script.c.

    As for the second part: whether the server should be doing the
    checks.. I think that yes, it should.  If the script did it itself,
    it should generate the correct status line.  This patch does *not*
    do the checking IFF it sees no status from the script; that it
    should is just my opinion for now.

    It's unclear to me if any additional kill-the-script stuff is needed
    before blowing out due to a condition unmatch.  Probably.. so please
    treat this as a work-in-progress that will include that in a later
    revision.  If approved at all.

    Okey, now for the obligatory forty-two messages telling me I did it
    wrong and shouldn't have done it at all.. <g>  But that's okey; I'm
    rather in terra incognita here.

    #ken    :-)}

Index: core/http_core.c
===================================================================
RCS file: /export/home/cvs/apachen/src/core/http_core.c,v
retrieving revision 1.112
diff -u -r1.112 http_core.c
--- http_core.c	1997/08/18 07:19:34	1.112
+++ http_core.c	1997/08/19 20:36:05
@@ -1590,9 +1590,12 @@
         return FORBIDDEN;
     }
 	
-    if ((errstatus = set_last_modified (r, r->finfo.st_mtime))
-	|| (errstatus = set_content_length (r, r->finfo.st_size)))
-        return errstatus;
+    set_last_modified(r, r->finfo.st_mtime);
+    set_etag(r, r->finfo.st_mtime);
+    if (((errstatus = meets_conditions(r)) != OK)
+	|| (errstatus = set_content_length (r, r->finfo.st_size))) {
+	    return errstatus;
+    }
 
 #ifdef USE_MMAP_FILES
     block_alarms();
Index: core/http_protocol.c
===================================================================
RCS file: /export/home/cvs/apachen/src/core/http_protocol.c,v
retrieving revision 1.155
diff -u -r1.155 http_protocol.c
--- http_protocol.c	1997/08/18 07:19:36	1.155
+++ http_protocol.c	1997/08/19 20:36:22
@@ -348,101 +348,117 @@
     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;
+    time_t latest_real;
 
     /* For all static responses, it's almost certain that the file was
      * last modified before the beginning of the request.  So there's
      * 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);
+     * 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.
+     */
+    latest_real = (mtime < r->request_time) ? r->request_time : time(NULL);
+    return (mtime > latest_real) ? latest_real : mtime;
+}
 
-    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.
-     */
-
-    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;
+    char *last_mtime = table_get(r->headers_out, "Last-Modified");
 
-    /* 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;
+    }
 
-    /* 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).
-     */
+    mtime = (last_mtime != NULL) ? parseHTTPdate(last_mtime) : time(NULL);
 
-    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 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;
+	    }
+	}
     }
 
-    /* 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 (etag != NULL) {
+	if_nonematch = table_get(r->headers_in, "If-None-Match");
+	if (if_nonematch != NULL) {
+	    int rstatus;
+
+	    if ((if_nonematch[0] == '*')
+	        || 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 +468,62 @@
               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, time_t mtime)
+{
+    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)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);
+}
+
+/*
+ * This function sets the Last-Modified output header field to the value
+ * passed - rationalized to keep it from being in the future.
+ */
+API_EXPORT(void) set_last_modified(request_rec *r, time_t mtime)
+{
+    time_t mod_time = rationalize_mtime(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
Index: core/http_protocol.h
===================================================================
RCS file: /export/home/cvs/apachen/src/core/http_protocol.h,v
retrieving revision 1.26
diff -u -r1.26 http_protocol.h
--- http_protocol.h	1997/08/18 07:19:36	1.26
+++ http_protocol.h	1997/08/19 20:36:29
@@ -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, time_t mtime);
+API_EXPORT(void) set_last_modified(request_rec *r, time_t mtime);
 
 /* Other ways to send stuff at the client.  All of these keep track
  * of bytes_sent automatically.  This indirection is intended to make
Index: core/util_script.c
===================================================================
RCS file: /export/home/cvs/apachen/src/core/util_script.c,v
retrieving revision 1.69
diff -u -r1.69 util_script.c
--- util_script.c	1997/08/05 06:33:26	1.69
+++ util_script.c	1997/08/19 20:36:49
@@ -399,6 +399,24 @@
         else if(!strcasecmp(w,"Transfer-Encoding")) {
 	    table_set (r->headers_out, w, l);
         }   
+/*
+ * If the script gave us a Last-Modified header, run it through the checks
+ * which work against it [done in set_last_modified()].  Any conditional
+ * failures in those will cause us to bail out.  Success means all the
+ * conditions were satisfied and the appropriate response header fields have
+ * been set - so we don't need to do anything else.
+ */
+        else if (!strcasecmp(w, "Last-Modified")) {
+	    time_t mtime = parseHTTPdate(l);
+	    int slm_status;
+
+	    set_last_modified(r, mtime);
+	    slm_status = meets_conditions(r);
+
+	    if (slm_status != OK) {
+		return slm_status;
+	    }
+        }   
 
 /* The HTTP specification says that it is legal to merge duplicate
  * headers into one.  Some browsers that support Cookies don't like
Index: modules/standard/mod_include.c
===================================================================
RCS file: /export/home/cvs/apachen/src/modules/standard/mod_include.c,v
retrieving revision 1.47
diff -u -r1.47 mod_include.c
--- mod_include.c	1997/08/18 13:12:13	1.47
+++ mod_include.c	1997/08/19 20:37:33
@@ -1957,13 +1957,16 @@
         return FORBIDDEN;
     }
 
+    set_last_modified(r, r->finfo.st_mtime);
+    set_etag(r, r->finfo.st_mtime);
     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;
+        && ((errstatus = meets_conditions(r)) != OK)) {
+	return errstatus;
+    }
 
     send_http_header(r);
 

Mime
View raw message