1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mod_cache.h"
19 #include <ap_provider.h>
21 /* -------------------------------------------------------------- */
23 extern module AP_MODULE_DECLARE_DATA cache_module
;
25 /* Determine if "url" matches the hostname, scheme and port and path
26 * in "filter". All but the path comparisons are case-insensitive.
28 static int uri_meets_conditions(apr_uri_t filter
, int pathlen
, apr_uri_t url
)
30 /* Compare the hostnames */
35 else if (strcasecmp(filter
.hostname
, url
.hostname
)) {
40 /* Compare the schemes */
45 else if (strcasecmp(filter
.scheme
, url
.scheme
)) {
50 /* Compare the ports */
52 if (url
.port_str
&& filter
.port
!= url
.port
) {
55 /* NOTE: ap_port_of_scheme will return 0 if given NULL input */
56 else if (filter
.port
!= apr_uri_port_of_scheme(url
.scheme
)) {
60 else if(url
.port_str
&& filter
.scheme
) {
61 if (apr_uri_port_of_scheme(filter
.scheme
) == url
.port
) {
66 /* For HTTP caching purposes, an empty (NULL) path is equivalent to
67 * a single "/" path. RFCs 3986/2396
70 if (*filter
.path
== '/' && pathlen
== 1) {
78 /* Url has met all of the filter conditions so far, determine
81 return !strncmp(filter
.path
, url
.path
, pathlen
);
84 CACHE_DECLARE(cache_provider_list
*)ap_cache_get_providers(request_rec
*r
,
85 cache_server_conf
*conf
,
88 cache_provider_list
*providers
= NULL
;
91 /* loop through all the cacheenable entries */
92 for (i
= 0; i
< conf
->cacheenable
->nelts
; i
++) {
93 struct cache_enable
*ent
=
94 (struct cache_enable
*)conf
->cacheenable
->elts
;
95 if (uri_meets_conditions(ent
[i
].url
, ent
[i
].pathlen
, uri
)) {
96 /* Fetch from global config and add to the list. */
97 cache_provider
*provider
;
98 provider
= ap_lookup_provider(CACHE_PROVIDER_GROUP
, ent
[i
].type
,
104 cache_provider_list
*newp
;
105 newp
= apr_pcalloc(r
->pool
, sizeof(cache_provider_list
));
106 newp
->provider_name
= ent
[i
].type
;
107 newp
->provider
= provider
;
113 cache_provider_list
*last
= providers
;
124 /* then loop through all the cachedisable entries
125 * Looking for urls that contain the full cachedisable url and possibly
127 * This means we are disabling cachedisable url and below...
129 for (i
= 0; i
< conf
->cachedisable
->nelts
; i
++) {
130 struct cache_disable
*ent
=
131 (struct cache_disable
*)conf
->cachedisable
->elts
;
132 if (uri_meets_conditions(ent
[i
].url
, ent
[i
].pathlen
, uri
)) {
133 /* Stop searching now. */
142 /* do a HTTP/1.1 age calculation */
143 CACHE_DECLARE(apr_int64_t
) ap_cache_current_age(cache_info
*info
,
144 const apr_time_t age_value
,
147 apr_time_t apparent_age
, corrected_received_age
, response_delay
,
148 corrected_initial_age
, resident_time
, current_age
,
151 age_value_usec
= apr_time_from_sec(age_value
);
153 /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
155 apparent_age
= MAX(0, info
->response_time
- info
->date
);
156 corrected_received_age
= MAX(apparent_age
, age_value_usec
);
157 response_delay
= info
->response_time
- info
->request_time
;
158 corrected_initial_age
= corrected_received_age
+ response_delay
;
159 resident_time
= now
- info
->response_time
;
160 current_age
= corrected_initial_age
+ resident_time
;
162 return apr_time_sec(current_age
);
165 CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t
*h
,
168 apr_int64_t age
, maxage_req
, maxage_cresp
, maxage
, smaxage
, maxstale
;
169 apr_int64_t minfresh
;
170 const char *cc_cresp
, *cc_req
;
172 const char *agestr
= NULL
;
173 const char *expstr
= NULL
;
175 apr_time_t age_c
= 0;
176 cache_info
*info
= &(h
->cache_obj
->info
);
177 cache_server_conf
*conf
=
178 (cache_server_conf
*)ap_get_module_config(r
->server
->module_config
,
182 * We now want to check if our cached data is still fresh. This depends
183 * on a few things, in this order:
185 * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in
186 * either the request or the cached response means that we must
187 * revalidate the request unconditionally, overriding any expiration
188 * mechanism. It's equivalent to max-age=0,must-revalidate.
190 * - RFC2616 14.32 Pragma: no-cache This is treated the same as
191 * Cache-Control: no-cache.
193 * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
194 * proxy-revalidate if the max-stale request header exists, modify the
195 * stale calculations below so that an object can be at most <max-stale>
196 * seconds stale before we request a revalidation, _UNLESS_ a
197 * must-revalidate or proxy-revalidate cached response header exists to
198 * stop us doing this.
200 * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
201 * maximum age an object can be before it is considered stale. This
202 * directive has the effect of proxy|must revalidate, which in turn means
203 * simple ignore any max-stale setting.
205 * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
206 * requests and responses. If both are specified, the smaller of the two
209 * - RFC2616 14.21 Expires: if this request header exists in the cached
210 * entity, and it's value is in the past, it has expired.
214 /* This value comes from the client's initial request. */
215 cc_req
= apr_table_get(r
->headers_in
, "Cache-Control");
216 pragma
= apr_table_get(r
->headers_in
, "Pragma");
218 if (ap_cache_liststr(NULL
, pragma
, "no-cache", NULL
)
219 || ap_cache_liststr(NULL
, cc_req
, "no-cache", NULL
)) {
221 if (!conf
->ignorecachecontrol
) {
222 /* Treat as stale, causing revalidation */
226 ap_log_error(APLOG_MARK
, APLOG_INFO
, 0, r
->server
,
227 "Incoming request is asking for a uncached version of "
228 "%s, but we know better and are ignoring it",
232 /* These come from the cached entity. */
233 cc_cresp
= apr_table_get(h
->resp_hdrs
, "Cache-Control");
234 expstr
= apr_table_get(h
->resp_hdrs
, "Expires");
236 if (ap_cache_liststr(NULL
, cc_cresp
, "no-cache", NULL
)) {
238 * The cached entity contained Cache-Control: no-cache, so treat as
239 * stale causing revalidation
244 if ((agestr
= apr_table_get(h
->resp_hdrs
, "Age"))) {
245 age_c
= apr_atoi64(agestr
);
248 /* calculate age of object */
249 age
= ap_cache_current_age(info
, age_c
, r
->request_time
);
251 /* extract s-maxage */
252 if (cc_cresp
&& ap_cache_liststr(r
->pool
, cc_cresp
, "s-maxage", &val
)
254 smaxage
= apr_atoi64(val
);
260 /* extract max-age from request */
261 if (!conf
->ignorecachecontrol
262 && cc_req
&& ap_cache_liststr(r
->pool
, cc_req
, "max-age", &val
)
264 maxage_req
= apr_atoi64(val
);
270 /* extract max-age from response */
271 if (cc_cresp
&& ap_cache_liststr(r
->pool
, cc_cresp
, "max-age", &val
)
273 maxage_cresp
= apr_atoi64(val
);
280 * if both maxage request and response, the smaller one takes priority
282 if (maxage_req
== -1) {
283 maxage
= maxage_cresp
;
285 else if (maxage_cresp
== -1) {
289 maxage
= MIN(maxage_req
, maxage_cresp
);
292 /* extract max-stale */
293 if (cc_req
&& ap_cache_liststr(r
->pool
, cc_req
, "max-stale", &val
)) {
295 maxstale
= apr_atoi64(val
);
299 * If no value is assigned to max-stale, then the client is willing
300 * to accept a stale response of any age (RFC2616 14.9.3). We will
301 * set it to one year in this case as this situation is somewhat
302 * similar to a "never expires" Expires header (RFC2616 14.21)
303 * which is set to a date one year from the time the response is
306 maxstale
= APR_INT64_C(86400*365);
313 /* extract min-fresh */
314 if (!conf
->ignorecachecontrol
315 && cc_req
&& ap_cache_liststr(r
->pool
, cc_req
, "min-fresh", &val
)
317 minfresh
= apr_atoi64(val
);
323 /* override maxstale if must-revalidate or proxy-revalidate */
324 if (maxstale
&& ((cc_cresp
&&
325 ap_cache_liststr(NULL
, cc_cresp
,
326 "must-revalidate", NULL
)) ||
328 ap_cache_liststr(NULL
, cc_cresp
,
329 "proxy-revalidate", NULL
)))) {
333 /* handle expiration */
334 if (((smaxage
!= -1) && (age
< (smaxage
- minfresh
))) ||
335 ((maxage
!= -1) && (age
< (maxage
+ maxstale
- minfresh
))) ||
336 ((smaxage
== -1) && (maxage
== -1) &&
337 (info
->expire
!= APR_DATE_BAD
) &&
338 (age
< (apr_time_sec(info
->expire
- info
->date
) + maxstale
- minfresh
)))) {
339 const char *warn_head
;
341 warn_head
= apr_table_get(h
->resp_hdrs
, "Warning");
343 /* it's fresh darlings... */
344 /* set age header on response */
345 apr_table_set(h
->resp_hdrs
, "Age",
346 apr_psprintf(r
->pool
, "%lu", (unsigned long)age
));
348 /* add warning if maxstale overrode freshness calculation */
349 if (!(((smaxage
!= -1) && age
< smaxage
) ||
350 ((maxage
!= -1) && age
< maxage
) ||
351 (info
->expire
!= APR_DATE_BAD
&&
352 (apr_time_sec(info
->expire
- info
->date
)) > age
))) {
353 /* make sure we don't stomp on a previous warning */
354 if ((warn_head
== NULL
) ||
355 ((warn_head
!= NULL
) && (ap_strstr_c(warn_head
, "110") == NULL
))) {
356 apr_table_merge(h
->resp_hdrs
, "Warning",
357 "110 Response is stale");
361 * If none of Expires, Cache-Control: max-age, or Cache-Control:
362 * s-maxage appears in the response, and the respose header age
363 * calculated is more than 24 hours add the warning 113
365 if ((maxage_cresp
== -1) && (smaxage
== -1) &&
366 (expstr
== NULL
) && (age
> 86400)) {
368 /* Make sure we don't stomp on a previous warning, and don't dup
369 * a 113 marning that is already present. Also, make sure to add
370 * the new warning to the correct *headers_out location.
372 if ((warn_head
== NULL
) ||
373 ((warn_head
!= NULL
) && (ap_strstr_c(warn_head
, "113") == NULL
))) {
374 apr_table_merge(h
->resp_hdrs
, "Warning",
375 "113 Heuristic expiration");
378 return 1; /* Cache object is fresh (enough) */
381 return 0; /* Cache object is stale */
385 * list is a comma-separated list of case-insensitive tokens, with
386 * optional whitespace around the tokens.
387 * The return returns 1 if the token val is found in the list, or 0
390 CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t
*p
, const char *list
,
391 const char *key
, char **val
)
400 key_len
= strlen(key
);
405 /* skip whitespace and commas to find the start of the next key */
406 while (*next
&& (apr_isspace(*next
) || (*next
== ','))) {
414 if (!strncasecmp(next
, key
, key_len
)) {
415 /* this field matches the key (though it might just be
416 * a prefix match, so make sure the match is followed
417 * by either a space or an equals sign)
420 if (!*next
|| (*next
== '=') || apr_isspace(*next
) ||
424 while (*next
&& (*next
!= '=') && (*next
!= ',')) {
429 while (*next
&& apr_isspace(*next
)) {
436 const char *val_start
= next
;
437 while (*next
&& !apr_isspace(*next
) &&
441 *val
= apr_pstrmemdup(p
, val_start
,
453 /* skip to the next field */
459 } while (*next
!= ',');
463 /* return each comma separated token, one at a time */
464 CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t
*p
, const char *list
,
470 s
= ap_strchr_c(list
, ',');
475 while (apr_isspace(*s
))
481 while (i
> 0 && apr_isspace(list
[i
- 1]))
486 return apr_pstrndup(p
, list
, i
);
492 * Converts apr_time_t expressed as hex digits to
495 CACHE_DECLARE(apr_time_t
) ap_cache_hex2usec(const char *x
)
499 for (i
= 0, j
= 0; i
< sizeof(j
) * 2; i
++) {
504 else if (apr_isupper(ch
))
505 j
|= ch
- ('A' - 10);
507 j
|= ch
- ('a' - 10);
513 * Converts apr_time_t to apr_time_t expressed as hex digits.
515 CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j
, char *y
)
519 for (i
= (sizeof(j
) * 2)-1; i
>= 0; i
--) {
523 y
[i
] = ch
+ ('A' - 10);
527 y
[sizeof(j
) * 2] = '\0';
530 static void cache_hash(const char *it
, char *val
, int ndepth
, int nlength
)
532 apr_md5_ctx_t context
;
533 unsigned char digest
[16];
537 static const char enc_table
[64] =
538 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
540 apr_md5_init(&context
);
541 apr_md5_update(&context
, (const unsigned char *) it
, strlen(it
));
542 apr_md5_final(digest
, &context
);
544 /* encode 128 bits as 22 characters, using a modified uuencoding
545 * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
546 * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
548 for (i
= 0, k
= 0; i
< 15; i
+= 3) {
549 x
= (digest
[i
] << 16) | (digest
[i
+ 1] << 8) | digest
[i
+ 2];
550 tmp
[k
++] = enc_table
[x
>> 18];
551 tmp
[k
++] = enc_table
[(x
>> 12) & 0x3f];
552 tmp
[k
++] = enc_table
[(x
>> 6) & 0x3f];
553 tmp
[k
++] = enc_table
[x
& 0x3f];
558 tmp
[k
++] = enc_table
[x
>> 2]; /* use up 6 bits */
559 tmp
[k
++] = enc_table
[(x
<< 4) & 0x3f];
561 /* now split into directory levels */
562 for (i
= k
= d
= 0; d
< ndepth
; ++d
) {
563 memcpy(&val
[i
], &tmp
[k
], nlength
);
565 val
[i
+ nlength
] = '/';
568 memcpy(&val
[i
], &tmp
[k
], 22 - k
);
569 val
[i
+ 22 - k
] = '\0';
572 CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t
*p
, int dirlevels
,
573 int dirlength
, const char *name
)
576 cache_hash(name
, hashfile
, dirlevels
, dirlength
);
577 return apr_pstrdup(p
, hashfile
);
581 * Create a new table consisting of those elements from an
582 * headers table that are allowed to be stored in a cache.
584 CACHE_DECLARE(apr_table_t
*)ap_cache_cacheable_headers(apr_pool_t
*pool
,
588 cache_server_conf
*conf
;
591 apr_table_t
*headers_out
;
593 /* Short circuit the common case that there are not
594 * (yet) any headers populated.
597 return apr_table_make(pool
, 10);
600 /* Make a copy of the headers, and remove from
601 * the copy any hop-by-hop headers, as defined in Section
604 headers_out
= apr_table_copy(pool
, t
);
606 apr_table_unset(headers_out
, "Connection");
607 apr_table_unset(headers_out
, "Keep-Alive");
608 apr_table_unset(headers_out
, "Proxy-Authenticate");
609 apr_table_unset(headers_out
, "Proxy-Authorization");
610 apr_table_unset(headers_out
, "TE");
611 apr_table_unset(headers_out
, "Trailers");
612 apr_table_unset(headers_out
, "Transfer-Encoding");
613 apr_table_unset(headers_out
, "Upgrade");
615 conf
= (cache_server_conf
*)ap_get_module_config(s
->module_config
,
618 /* Remove the user defined headers set with CacheIgnoreHeaders.
619 * This may break RFC 2616 compliance on behalf of the administrator.
621 header
= (char **)conf
->ignore_headers
->elts
;
622 for (i
= 0; i
< conf
->ignore_headers
->nelts
; i
++) {
623 apr_table_unset(headers_out
, header
[i
]);
629 * Legacy call - functionally equivalent to ap_cache_cacheable_headers.
630 * @deprecated @see ap_cache_cacheable_headers
632 CACHE_DECLARE(apr_table_t
*)ap_cache_cacheable_hdrs_out(apr_pool_t
*p
,
636 return ap_cache_cacheable_headers(p
,t
,s
);
640 * Create a new table consisting of those elements from an input
641 * headers table that are allowed to be stored in a cache.
643 CACHE_DECLARE(apr_table_t
*)ap_cache_cacheable_headers_in(request_rec
*r
)
645 return ap_cache_cacheable_headers(r
->pool
, r
->headers_in
, r
->server
);
649 * Create a new table consisting of those elements from an output
650 * headers table that are allowed to be stored in a cache;
651 * ensure there is a content type and capture any errors.
653 CACHE_DECLARE(apr_table_t
*)ap_cache_cacheable_headers_out(request_rec
*r
)
655 apr_table_t
*headers_out
;
657 headers_out
= apr_table_overlay(r
->pool
, r
->headers_out
,
660 apr_table_clear(r
->err_headers_out
);
662 headers_out
= ap_cache_cacheable_headers(r
->pool
, headers_out
,
665 if (!apr_table_get(headers_out
, "Content-Type")
666 && r
->content_type
) {
667 apr_table_setn(headers_out
, "Content-Type",
668 ap_make_content_type(r
, r
->content_type
));
671 if (!apr_table_get(headers_out
, "Content-Encoding")
672 && r
->content_encoding
) {
673 apr_table_setn(headers_out
, "Content-Encoding",
674 r
->content_encoding
);