*/
AP_DECLARE(void) ap_set_last_modified(request_rec *r);
+typedef enum {
+ AP_CONDITION_NONE,
+ AP_CONDITION_NOMATCH,
+ AP_CONDITION_WEAK,
+ AP_CONDITION_STRONG
+} ap_condition_e;
+
+/**
+ * Tests conditional request rules for the If-Match header.
+ * @param r The current request
+ * @param headers The response headers to check against
+ * @return AP_CONDITION_NONE if the header is missing, AP_CONDITION_NOMATCH
+ * if the header does not match, AP_CONDITION_STRONG for a strong
+ * match. Weak matches are not permitted for the If-Match header.
+ */
+AP_DECLARE(ap_condition_e) ap_condition_if_match(request_rec *r,
+ apr_table_t *headers);
+
+/**
+ * Tests conditional request rules for the If-Unmodified-Since header.
+ * @param r The current request
+ * @param headers The response headers to check against
+ * @return AP_CONDITION_NONE if the header is missing, AP_CONDITION_NOMATCH
+ * if the header does not match, AP_CONDITION_WEAK if a weak match
+ * was present and allowed by RFC2616, AP_CONDITION_STRONG for a
+ * strong match.
+ */
+AP_DECLARE(ap_condition_e) ap_condition_if_unmodified_since(request_rec *r,
+ apr_table_t *headers);
+
+/**
+ * Tests conditional request rules for the If-None-Match header.
+ * @param r The current request
+ * @param headers The response headers to check against
+ * @return AP_CONDITION_NONE if the header is missing, AP_CONDITION_NOMATCH
+ * if the header does not match, AP_CONDITION_WEAK if a weak match
+ * was present and allowed by RFC2616, AP_CONDITION_STRONG for a
+ * strong match.
+ */
+AP_DECLARE(ap_condition_e) ap_condition_if_none_match(request_rec *r,
+ apr_table_t *headers);
+
+/**
+ * Tests conditional request rules for the If-Modified-Since header.
+ * @param r The current request
+ * @param headers The response headers to check against
+ * @return AP_CONDITION_NONE if the header is missing, AP_CONDITION_NOMATCH
+ * if the header does not match, AP_CONDITION_WEAK if a weak match
+ * was present and allowed by RFC2616, AP_CONDITION_STRONG for a
+ * strong match.
+ */
+AP_DECLARE(ap_condition_e) ap_condition_if_modified_since(request_rec *r,
+ apr_table_t *headers);
+
+/**
+ * Tests conditional request rules for the If-Range header.
+ * @param r The current request
+ * @param headers The response headers to check against
+ * @return AP_CONDITION_NONE if either the If-Range or Range header is
+ * missing, AP_CONDITION_NOMATCH if the header does not match,
+ * AP_CONDITION_STRONG for a strong match. Weak matches are not
+ * permitted for the If-Range header.
+ */
+AP_DECLARE(ap_condition_e) ap_condition_if_range(request_rec *r,
+ apr_table_t *headers);
+
/**
* Implements condition GET rules for HTTP/1.1 specification. This function
* inspects the client headers and determines if the response fulfills
switch ((rv = list->provider->open_entity(h, r, cache->key))) {
case OK: {
char *vary = NULL;
- int fresh, mismatch = 0;
+ int mismatch = 0;
char *last = NULL;
if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
cache->provider = list->provider;
cache->provider_name = list->provider_name;
+ /*
+ * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified
+ * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request
+ * that includes both a Last-Modified date and one or more entity tags as
+ * cache validators, MUST NOT return a locally cached response to the
+ * client unless that cached response is consistent with all of the
+ * conditional header fields in the request.
+ */
+ if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH
+ || ap_condition_if_unmodified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_none_match(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_modified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) {
+ mismatch = 1;
+ }
+
/* Is our cached response fresh enough? */
- fresh = cache_check_freshness(h, cache, r);
- if (!fresh) {
+ if (mismatch || !cache_check_freshness(h, cache, r)) {
const char *etag, *lastmod;
/* Cache-Control: only-if-cached and revalidation required, try
r->headers_in);
cache->stale_handle = h;
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695)
- "Cached response for %s isn't fresh. Adding/replacing "
- "conditional request headers.", r->uri);
+ /* if no existing conditionals, use conditionals of our own */
+ if (!mismatch) {
- /* We can only revalidate with our own conditionals: remove the
- * conditions from the original request.
- */
- apr_table_unset(r->headers_in, "If-Match");
- apr_table_unset(r->headers_in, "If-Modified-Since");
- apr_table_unset(r->headers_in, "If-None-Match");
- apr_table_unset(r->headers_in, "If-Range");
- apr_table_unset(r->headers_in, "If-Unmodified-Since");
-
- etag = apr_table_get(h->resp_hdrs, "ETag");
- lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
-
- if (etag || lastmod) {
- /* If we have a cached etag and/or Last-Modified add in
- * our own conditionals.
- */
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding "
+ "conditional request headers.", r->uri);
- if (etag) {
- apr_table_set(r->headers_in, "If-None-Match", etag);
- }
+ /* Remove existing conditionals that might conflict with ours */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
- if (lastmod) {
- apr_table_set(r->headers_in, "If-Modified-Since",
- lastmod);
- }
+ etag = apr_table_get(h->resp_hdrs, "ETag");
+ lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
- /*
- * Do not do Range requests with our own conditionals: If
- * we get 304 the Range does not matter and otherwise the
- * entity changed and we want to have the complete entity
- */
- apr_table_unset(r->headers_in, "Range");
+ if (etag || lastmod) {
+ /* If we have a cached etag and/or Last-Modified add in
+ * our own conditionals.
+ */
+
+ if (etag) {
+ apr_table_set(r->headers_in, "If-None-Match", etag);
+ }
+
+ if (lastmod) {
+ apr_table_set(r->headers_in, "If-Modified-Since",
+ lastmod);
+ }
+
+ /*
+ * Do not do Range requests with our own conditionals: If
+ * we get 304 the Range does not matter and otherwise the
+ * entity changed and we want to have the complete entity
+ */
+ apr_table_unset(r->headers_in, "Range");
+
+ }
}
return 0;
}
+AP_DECLARE(ap_condition_e) ap_condition_if_match(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_match, *etag;
+
+ /* A server MUST use the strong comparison function (see section 13.3.3)
+ * to compare the entity tags in If-Match.
+ */
+ if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
+ if (if_match[0] == '*'
+ || ((etag = apr_table_get(headers, "ETag")) == NULL
+ && !ap_find_etag_strong(r->pool, if_match, etag))) {
+ return AP_CONDITION_STRONG;
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_unmodified_since(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_unmodified;
+
+ if_unmodified = apr_table_get(r->headers_in, "If-Unmodified-Since");
+ if (if_unmodified) {
+ apr_int64_t mtime, reqtime;
+
+ apr_time_t ius = apr_time_sec(apr_date_parse_http(if_unmodified));
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ if ((ius != APR_DATE_BAD) && (mtime > ius)) {
+ if (reqtime < mtime + 60) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_none_match(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_nonematch, *etag;
+
+ if_nonematch = apr_table_get(r->headers_in, "If-None-Match");
+ if (if_nonematch != NULL) {
+
+ if (if_nonematch[0] == '*') {
+ return AP_CONDITION_STRONG;
+ }
+
+ /* See section 13.3.3 for rules on how to determine if two entities tags
+ * match. The weak comparison function can only be used with GET or HEAD
+ * requests.
+ */
+ if (r->method_number == M_GET) {
+ if ((etag = apr_table_get(headers, "ETag")) != NULL) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ if (ap_find_etag_strong(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ if (ap_find_etag_weak(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ }
+ }
+
+ else if ((etag = apr_table_get(headers, "ETag")) != NULL
+ && ap_find_etag_strong(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ return AP_CONDITION_NOMATCH;
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_modified_since(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_modified_since;
+
+ if ((if_modified_since = apr_table_get(r->headers_in, "If-Modified-Since"))
+ != NULL) {
+ apr_int64_t mtime;
+ apr_int64_t ims, reqtime;
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ ims = apr_time_sec(apr_date_parse_http(if_modified_since));
+
+ if (ims >= mtime && ims <= reqtime) {
+ if (reqtime < mtime + 60) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_range(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_range, *etag;
+
+ if ((if_range = apr_table_get(r->headers_in, "If-Range"))
+ && apr_table_get(r->headers_in, "Range")) {
+ if (if_range[0] == '"') {
+
+ if ((etag = apr_table_get(headers, "ETag"))
+ && !strcmp(if_range, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+
+ }
+ else {
+ apr_int64_t mtime;
+ apr_int64_t rtime, reqtime;
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ rtime = apr_time_sec(apr_date_parse_http(if_range));
+
+ if (rtime == mtime) {
+ if (reqtime < mtime + 60) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
AP_DECLARE(int) ap_meets_conditions(request_rec *r)
{
- const char *etag;
- const char *if_match, *if_modified_since, *if_unmodified, *if_nonematch;
- apr_time_t tmp_time;
- apr_int64_t mtime;
- int not_modified = 0;
+ int not_modified = -1; /* unset by default */
+ ap_condition_e cond;
/* Check for conditional requests --- note that we only want to do
* this if we are successful so far and we are not processing a
return OK;
}
- etag = apr_table_get(r->headers_out, "ETag");
-
- /* All of our comparisons must be in seconds, because that's the
- * highest time resolution the HTTP specification allows.
- */
- /* XXX: we should define a "time unset" constant */
- tmp_time = ((r->mtime != 0) ? r->mtime : apr_time_now());
- mtime = apr_time_sec(tmp_time);
-
/* If an If-Match request-header field was given
* AND the field value is not "*" (meaning match anything)
* AND if our strong ETag does not match any entity tag in that field,
* respond with a status of 412 (Precondition Failed).
*/
- if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
- if (if_match[0] != '*'
- && (etag == NULL
- || !ap_find_etag_strong(r->pool, if_match, etag))) {
- return HTTP_PRECONDITION_FAILED;
- }
+ cond = ap_condition_if_match(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ return HTTP_PRECONDITION_FAILED;
}
- else {
- /* 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).
- */
- if_unmodified = apr_table_get(r->headers_in, "If-Unmodified-Since");
- if (if_unmodified != NULL) {
- apr_time_t ius = apr_date_parse_http(if_unmodified);
- if ((ius != APR_DATE_BAD) && (mtime > apr_time_sec(ius))) {
- 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
+ * specified in this field, then the server MUST
+ * respond with a status of 412 (Precondition Failed).
+ */
+ cond = ap_condition_if_unmodified_since(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ return HTTP_PRECONDITION_FAILED;
}
/* If an If-None-Match request-header field was given
* GET or HEAD allow weak etag comparison, all other methods require
* strong comparison. We can only use weak if it's not a range request.
*/
- if_nonematch = apr_table_get(r->headers_in, "If-None-Match");
- if (if_nonematch != NULL) {
+ cond = ap_condition_if_none_match(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
if (r->method_number == M_GET) {
- if (if_nonematch[0] == '*') {
+ if (not_modified) {
not_modified = 1;
}
- else if (etag != NULL) {
- if (apr_table_get(r->headers_in, "Range")) {
- not_modified = ap_find_etag_strong(r->pool, if_nonematch,
- etag);
- }
- else {
- not_modified = ap_find_etag_weak(r->pool, if_nonematch,
- etag);
- }
- }
}
- else if (if_nonematch[0] == '*'
- || (etag != NULL
- && ap_find_etag_strong(r->pool, if_nonematch, etag))) {
+ else {
return HTTP_PRECONDITION_FAILED;
}
}
* respond with a status of 304 (Not Modified).
* A date later than the server's current request time is invalid.
*/
- if (r->method_number == M_GET
- && (not_modified || !if_nonematch)
- && (if_modified_since =
- apr_table_get(r->headers_in,
- "If-Modified-Since")) != NULL) {
- apr_time_t ims_time;
- apr_int64_t ims, reqtime;
-
- ims_time = apr_date_parse_http(if_modified_since);
- ims = apr_time_sec(ims_time);
- reqtime = apr_time_sec(r->request_time);
+ cond = ap_condition_if_modified_since(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ if (r->method_number == M_GET) {
+ if (not_modified) {
+ not_modified = 1;
+ }
+ }
+ }
- not_modified = ims >= mtime && ims <= reqtime;
+ /* If an If-Range and an Range header is present, we must return
+ * 200 OK. The byterange filter will convert it to a range response.
+ */
+ cond = ap_condition_if_range(r, r->headers_out);
+ if (cond > AP_CONDITION_NONE) {
+ return OK;
}
- if (not_modified) {
+ if (not_modified == 1) {
return HTTP_NOT_MODIFIED;
}