]> granicus.if.org Git - apache/blob - modules/cache/cache_util.c
* Style police. No functional changes.
[apache] / modules / cache / cache_util.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include "mod_cache.h"
18
19 #include <ap_provider.h>
20
21 /* -------------------------------------------------------------- */
22
23 extern module AP_MODULE_DECLARE_DATA cache_module;
24
25 /* Determine if "url" matches the hostname, scheme and port and path
26  * in "filter". All but the path comparisons are case-insensitive.
27  */
28 static int uri_meets_conditions(apr_uri_t filter, int pathlen, apr_uri_t url)
29 {
30     /* Compare the hostnames */
31     if(filter.hostname) {
32         if (!url.hostname) {
33             return 0;
34         }
35         else if (strcasecmp(filter.hostname, url.hostname)) {
36             return 0;
37         }
38     }
39
40     /* Compare the schemes */
41     if(filter.scheme) {
42         if (!url.scheme) {
43             return 0;
44         }
45         else if (strcasecmp(filter.scheme, url.scheme)) {
46             return 0;
47         }
48     }
49
50     /* Compare the ports */
51     if(filter.port_str) {
52         if (url.port_str && filter.port != url.port) {
53             return 0;
54         }
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)) {
57             return 0;
58         }
59     }
60     else if(url.port_str && filter.scheme) {
61         if (apr_uri_port_of_scheme(filter.scheme) == url.port) {
62             return 0;
63         }
64     }
65
66     /* For HTTP caching purposes, an empty (NULL) path is equivalent to
67      * a single "/" path. RFCs 3986/2396
68      */
69     if (!url.path) {
70         if (*filter.path == '/' && pathlen == 1) {
71             return 1;
72         }
73         else {
74             return 0;
75         }
76     }
77
78     /* Url has met all of the filter conditions so far, determine
79      * if the paths match.
80      */
81     return !strncmp(filter.path, url.path, pathlen);
82 }
83
84 CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r,
85                                                   cache_server_conf *conf,
86                                                   apr_uri_t uri)
87 {
88     cache_provider_list *providers = NULL;
89     int i;
90
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,
99                                           "0");
100             if (!provider) {
101                 /* Log an error! */
102             }
103             else {
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;
108
109                 if (!providers) {
110                     providers = newp;
111                 }
112                 else {
113                     cache_provider_list *last = providers;
114
115                     while (last->next) {
116                         last = last->next;
117                     }
118                     last->next = newp;
119                 }
120             }
121         }
122     }
123
124     /* then loop through all the cachedisable entries
125      * Looking for urls that contain the full cachedisable url and possibly
126      * more.
127      * This means we are disabling cachedisable url and below...
128      */
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. */
134             return NULL;
135         }
136     }
137
138     return providers;
139 }
140
141
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,
145                                                 apr_time_t now)
146 {
147     apr_time_t apparent_age, corrected_received_age, response_delay,
148                corrected_initial_age, resident_time, current_age,
149                age_value_usec;
150
151     age_value_usec = apr_time_from_sec(age_value);
152
153     /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
154
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;
161
162     return apr_time_sec(current_age);
163 }
164
165 CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h,
166                                             request_rec *r)
167 {
168     apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
169     apr_int64_t minfresh;
170     const char *cc_cresp, *cc_req;
171     const char *pragma;
172     const char *agestr = NULL;
173     const char *expstr = NULL;
174     char *val;
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,
179                                                 &cache_module);
180
181     /*
182      * We now want to check if our cached data is still fresh. This depends
183      * on a few things, in this order:
184      *
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.
189      *
190      * - RFC2616 14.32 Pragma: no-cache This is treated the same as
191      * Cache-Control: no-cache.
192      *
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.
199      *
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.
204      *
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
207      * takes priority.
208      *
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.
211      *
212      */
213
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");
217
218     if (ap_cache_liststr(NULL, pragma, "no-cache", NULL)
219         || ap_cache_liststr(NULL, cc_req, "no-cache", NULL)) {
220
221         if (!conf->ignorecachecontrol) {
222             /* Treat as stale, causing revalidation */
223             return 0;
224         }
225
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",
229                      r->unparsed_uri);
230     }
231
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");
235
236     if (ap_cache_liststr(NULL, cc_cresp, "no-cache", NULL)) {
237         /*
238          * The cached entity contained Cache-Control: no-cache, so treat as
239          * stale causing revalidation
240          */
241         return 0;
242     }
243
244     if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) {
245         age_c = apr_atoi64(agestr);
246     }
247
248     /* calculate age of object */
249     age = ap_cache_current_age(info, age_c, r->request_time);
250
251     /* extract s-maxage */
252     if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)
253         && val != NULL) {
254         smaxage = apr_atoi64(val);
255     }
256     else {
257         smaxage = -1;
258     }
259
260     /* extract max-age from request */
261     if (!conf->ignorecachecontrol
262         && cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)
263         && val != NULL) {
264         maxage_req = apr_atoi64(val);
265     }
266     else {
267         maxage_req = -1;
268     }
269
270     /* extract max-age from response */
271     if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)
272         && val != NULL) {
273         maxage_cresp = apr_atoi64(val);
274     }
275     else {
276         maxage_cresp = -1;
277     }
278
279     /*
280      * if both maxage request and response, the smaller one takes priority
281      */
282     if (maxage_req == -1) {
283         maxage = maxage_cresp;
284     }
285     else if (maxage_cresp == -1) {
286         maxage = maxage_req;
287     }
288     else {
289         maxage = MIN(maxage_req, maxage_cresp);
290     }
291
292     /* extract max-stale */
293     if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) {
294         if(val != NULL) {
295             maxstale = apr_atoi64(val);
296         }
297         else {
298             /*
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
304              * sent in this case.
305              */
306             maxstale = APR_INT64_C(86400*365);
307         }
308     }
309     else {
310         maxstale = 0;
311     }
312
313     /* extract min-fresh */
314     if (!conf->ignorecachecontrol
315         && cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)
316         && val != NULL) {
317         minfresh = apr_atoi64(val);
318     }
319     else {
320         minfresh = 0;
321     }
322
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)) ||
327                      (cc_cresp &&
328                       ap_cache_liststr(NULL, cc_cresp,
329                                        "proxy-revalidate", NULL)))) {
330         maxstale = 0;
331     }
332
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;
340
341         warn_head = apr_table_get(h->resp_hdrs, "Warning");
342
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));
347
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");
358             }
359         }
360         /*
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
364          */
365         if ((maxage_cresp == -1) && (smaxage == -1) &&
366             (expstr == NULL) && (age > 86400)) {
367
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.
371              */
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");
376             }
377         }
378         return 1;    /* Cache object is fresh (enough) */
379     }
380
381     return 0;        /* Cache object is stale */
382 }
383
384 /*
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
388  * otherwise.
389  */
390 CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
391                                     const char *key, char **val)
392 {
393     apr_size_t key_len;
394     const char *next;
395
396     if (!list) {
397         return 0;
398     }
399
400     key_len = strlen(key);
401     next = list;
402
403     for (;;) {
404
405         /* skip whitespace and commas to find the start of the next key */
406         while (*next && (apr_isspace(*next) || (*next == ','))) {
407             next++;
408         }
409
410         if (!*next) {
411             return 0;
412         }
413
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)
418              */
419             next += key_len;
420             if (!*next || (*next == '=') || apr_isspace(*next) ||
421                 (*next == ',')) {
422                 /* valid match */
423                 if (val) {
424                     while (*next && (*next != '=') && (*next != ',')) {
425                         next++;
426                     }
427                     if (*next == '=') {
428                         next++;
429                         while (*next && apr_isspace(*next )) {
430                             next++;
431                         }
432                         if (!*next) {
433                             *val = NULL;
434                         }
435                         else {
436                             const char *val_start = next;
437                             while (*next && !apr_isspace(*next) &&
438                                    (*next != ',')) {
439                                 next++;
440                             }
441                             *val = apr_pstrmemdup(p, val_start,
442                                                   next - val_start);
443                         }
444                     }
445                     else {
446                         *val = NULL;
447                     }
448                 }
449                 return 1;
450             }
451         }
452
453         /* skip to the next field */
454         do {
455             next++;
456             if (!*next) {
457                 return 0;
458             }
459         } while (*next != ',');
460     }
461 }
462
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,
465                                            const char **str)
466 {
467     apr_size_t i;
468     const char *s;
469
470     s = ap_strchr_c(list, ',');
471     if (s != NULL) {
472         i = s - list;
473         do
474             s++;
475         while (apr_isspace(*s))
476             ; /* noop */
477     }
478     else
479         i = strlen(list);
480
481     while (i > 0 && apr_isspace(list[i - 1]))
482         i--;
483
484     *str = s;
485     if (i)
486         return apr_pstrndup(p, list, i);
487     else
488         return NULL;
489 }
490
491 /*
492  * Converts apr_time_t expressed as hex digits to
493  * a true apr_time_t.
494  */
495 CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
496 {
497     int i, ch;
498     apr_time_t j;
499     for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
500         ch = x[i];
501         j <<= 4;
502         if (apr_isdigit(ch))
503             j |= ch - '0';
504         else if (apr_isupper(ch))
505             j |= ch - ('A' - 10);
506         else
507             j |= ch - ('a' - 10);
508     }
509     return j;
510 }
511
512 /*
513  * Converts apr_time_t to apr_time_t expressed as hex digits.
514  */
515 CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
516 {
517     int i, ch;
518
519     for (i = (sizeof(j) * 2)-1; i >= 0; i--) {
520         ch = (int)(j & 0xF);
521         j >>= 4;
522         if (ch >= 10)
523             y[i] = ch + ('A' - 10);
524         else
525             y[i] = ch + '0';
526     }
527     y[sizeof(j) * 2] = '\0';
528 }
529
530 static void cache_hash(const char *it, char *val, int ndepth, int nlength)
531 {
532     apr_md5_ctx_t context;
533     unsigned char digest[16];
534     char tmp[22];
535     int i, k, d;
536     unsigned int x;
537     static const char enc_table[64] =
538     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
539
540     apr_md5_init(&context);
541     apr_md5_update(&context, (const unsigned char *) it, strlen(it));
542     apr_md5_final(digest, &context);
543
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
547      */
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];
554     }
555
556     /* one byte left */
557     x = digest[15];
558     tmp[k++] = enc_table[x >> 2];    /* use up 6 bits */
559     tmp[k++] = enc_table[(x << 4) & 0x3f];
560
561     /* now split into directory levels */
562     for (i = k = d = 0; d < ndepth; ++d) {
563         memcpy(&val[i], &tmp[k], nlength);
564         k += nlength;
565         val[i + nlength] = '/';
566         i += nlength + 1;
567     }
568     memcpy(&val[i], &tmp[k], 22 - k);
569     val[i + 22 - k] = '\0';
570 }
571
572 CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
573                                             int dirlength, const char *name)
574 {
575     char hashfile[66];
576     cache_hash(name, hashfile, dirlevels, dirlength);
577     return apr_pstrdup(p, hashfile);
578 }
579
580 /*
581  * Create a new table consisting of those elements from an 
582  * headers table that are allowed to be stored in a cache.
583  */
584 CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
585                                                         apr_table_t *t,
586                                                         server_rec *s)
587 {
588     cache_server_conf *conf;
589     char **header;
590     int i;
591
592     /* Short circuit the common case that there are not
593      * (yet) any headers populated.
594      */
595     if (t == NULL) {
596         return apr_table_make(pool, 10);
597     };
598
599     /* Make a copy of the headers, and remove from
600      * the copy any hop-by-hop headers, as defined in Section
601      * 13.5.1 of RFC 2616
602      */
603     apr_table_t *headers_out;
604     headers_out = apr_table_copy(pool, t);
605
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");
614
615     conf = (cache_server_conf *)ap_get_module_config(s->module_config,
616                                                      &cache_module);
617
618     /* Remove the user defined headers set with CacheIgnoreHeaders.
619      * This may break RFC 2616 compliance on behalf of the administrator.
620      */
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]);
624     }
625     return headers_out;
626 }
627
628 /*
629  * Legacy call - functionally equivalent to ap_cache_cacheable_headers.
630  * @deprecated @see ap_cache_cacheable_headers
631  */
632 CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *p,
633                                                         apr_table_t *t,
634                                                         server_rec *s)
635 {
636     return ap_cache_cacheable_headers(p,t,s);
637 }
638
639 /*
640  * Create a new table consisting of those elements from an input
641  * headers table that are allowed to be stored in a cache.
642  */
643 CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec * r)
644 {
645     return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server);
646 }
647
648 /*
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.
652  */
653 CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec * r)
654 {
655     apr_table_t *headers_out;
656
657     headers_out = ap_cache_cacheable_headers(r->pool, r->headers_out,
658                                                   r->server);
659
660     if (!apr_table_get(headers_out, "Content-Type")
661         && r->content_type) {
662         apr_table_setn(headers_out, "Content-Type",
663                        ap_make_content_type(r, r->content_type));
664     }
665
666     headers_out = apr_table_overlay(r->pool, headers_out,
667                                     r->err_headers_out);
668
669     return headers_out;
670 }