/* * select a specific URL entity in the cache * * It is possible to store more than one entity per URL. Content * negotiation is used to select an entity. Once an entity is * selected, details of it are stored in the per request * config to save time when serving the request later. * * This function returns OK if successful, DECLINED if no * cached entity fits the bill. */ int cache_select(cache_request_rec *cache, request_rec *r) { cache_provider_list *list; apr_status_t rv; cache_handle_t *h; if (!cache) { /* This should never happen */ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693) "cache: No cache request information available for key" " generation"); return DECLINED; } /* if no-cache, we can't serve from the cache, but we may store to the * cache. */ if (!ap_cache_check_no_cache(cache, r)) { return DECLINED; } if (!cache->key) { rv = cache_generate_key(r, r->pool, &cache->key); if (rv != APR_SUCCESS) { return DECLINED; } } /* go through the cache types till we get a match */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); list = cache->providers; while (list) { switch ((rv = list->provider->open_entity(h, r, cache->key))) { case OK: { char *vary = NULL; int mismatch = 0; char *last = NULL; if (list->provider->recall_headers(h, r) != APR_SUCCESS) { /* try again with next cache type */ list = list->next; continue; } /* * Check Content-Negotiation - Vary * * At this point we need to make sure that the object we found in * the cache is the same object that would be delivered to the * client, when the effects of content negotiation are taken into * effect. * * In plain english, we want to make sure that a language-negotiated * document in one language is not given to a client asking for a * language negotiated document in a different language by mistake. * * This code makes the assumption that the storage manager will * cache the req_hdrs if the response contains a Vary * header. * * RFC2616 13.6 and 14.44 describe the Vary mechanism. */ vary = cache_strqtok( apr_pstrdup(r->pool, cache_table_getm(r->pool, h->resp_hdrs, "Vary")), CACHE_SEPARATOR, &last); while (vary) { const char *h1, *h2; /* * is this header in the request and the header in the cached * request identical? If not, we give up and do a straight get */ h1 = cache_table_getm(r->pool, r->headers_in, vary); h2 = cache_table_getm(r->pool, h->req_hdrs, vary); if (h1 == h2) { /* both headers NULL, so a match - do nothing */ } else if (h1 && h2 && !strcmp(h1, h2)) { /* both headers exist and are equal - do nothing */ } else { /* headers do not match, so Vary failed */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00694) "cache_select(): Vary header mismatch."); mismatch = 1; break; } vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last); } /* no vary match, try next provider */ if (mismatch) { /* try again with next cache type */ list = list->next; continue; } 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? */ if (mismatch || !cache_check_freshness(h, cache, r)) { const char *etag, *lastmod; /* Cache-Control: only-if-cached and revalidation required, try * the next provider */ if (cache->control_in.only_if_cached) { /* try again with next cache type */ list = list->next; continue; } /* set aside the stale entry for accessing later */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); cache->stale_handle = h; /* if no existing conditionals, use conditionals of our own */ if (!mismatch) { 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); /* 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"); 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. */ 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"); } } /* ready to revalidate, pretend we were never here */ return DECLINED; } /* Okay, this response looks okay. Merge in our stuff and go. */ cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0); cache->handle = h; return OK; } case DECLINED: { /* try again with next cache type */ list = list->next; continue; } default: { /* oo-er! an error */ return rv; } } } /* if Cache-Control: only-if-cached, and not cached, return 504 */ if (cache->control_in.only_if_cached) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696) "cache: 'only-if-cached' requested and no cached entity, " "returning 504 Gateway Timeout for: %s", r->uri); return HTTP_GATEWAY_TIME_OUT; } return DECLINED; }
/** * Parse the Cache-Control and Pragma headers in one go, marking * which tokens appear within the header. Populate the structure * passed in. */ int ap_cache_control(request_rec *r, cache_control_t *cc, const char *cc_header, const char *pragma_header, apr_table_t *headers) { char *last; if (cc->parsed) { return cc->cache_control || cc->pragma; } cc->parsed = 1; cc->max_age_value = -1; cc->max_stale_value = -1; cc->min_fresh_value = -1; cc->s_maxage_value = -1; if (pragma_header) { char *header = apr_pstrdup(r->pool, pragma_header); const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); while (token) { /* handle most common quickest case... */ if (!strcmp(token, "no-cache")) { cc->no_cache = 1; } /* ...then try slowest case */ else if (!strcasecmp(token, "no-cache")) { cc->no_cache = 1; } token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); } cc->pragma = 1; } if (cc_header) { char *endp; apr_off_t offt; char *header = apr_pstrdup(r->pool, cc_header); const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); while (token) { switch (token[0]) { case 'n': case 'N': { /* handle most common quickest cases... */ if (!strcmp(token, "no-cache")) { cc->no_cache = 1; } else if (!strcmp(token, "no-store")) { cc->no_store = 1; } /* ...then try slowest cases */ else if (!strncasecmp(token, "no-cache", 8)) { if (token[8] == '=') { cc->no_cache_header = 1; } else if (!token[8]) { cc->no_cache = 1; } break; } else if (!strcasecmp(token, "no-store")) { cc->no_store = 1; } else if (!strcasecmp(token, "no-transform")) { cc->no_transform = 1; } break; } case 'm': case 'M': { /* handle most common quickest cases... */ if (!strcmp(token, "max-age=0")) { cc->max_age = 1; cc->max_age_value = 0; } else if (!strcmp(token, "must-revalidate")) { cc->must_revalidate = 1; } /* ...then try slowest cases */ else if (!strncasecmp(token, "max-age", 7)) { if (token[7] == '=' && !apr_strtoff(&offt, token + 8, &endp, 10) && endp > token + 8 && !*endp) { cc->max_age = 1; cc->max_age_value = offt; } break; } else if (!strncasecmp(token, "max-stale", 9)) { if (token[9] == '=' && !apr_strtoff(&offt, token + 10, &endp, 10) && endp > token + 10 && !*endp) { cc->max_stale = 1; cc->max_stale_value = offt; } else if (!token[9]) { cc->max_stale = 1; cc->max_stale_value = -1; } break; } else if (!strncasecmp(token, "min-fresh", 9)) { if (token[9] == '=' && !apr_strtoff(&offt, token + 10, &endp, 10) && endp > token + 10 && !*endp) { cc->min_fresh = 1; cc->min_fresh_value = offt; } break; } else if (!strcasecmp(token, "must-revalidate")) { cc->must_revalidate = 1; } break; } case 'o': case 'O': { if (!strcasecmp(token, "only-if-cached")) { cc->only_if_cached = 1; } break; } case 'p': case 'P': { /* handle most common quickest cases... */ if (!strcmp(token, "private")) { cc->private = 1; } /* ...then try slowest cases */ else if (!strcasecmp(token, "public")) { cc->public = 1; } else if (!strncasecmp(token, "private", 7)) { if (token[7] == '=') { cc->private_header = 1; } else if (!token[7]) { cc->private = 1; } break; }