bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / experimental / 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 #define CORE_PRIVATE
18
19 #include "mod_cache.h"
20
21 #include <ap_provider.h>
22
23 /* -------------------------------------------------------------- */
24
25 extern module AP_MODULE_DECLARE_DATA cache_module;
26
27 /* return true if the request is conditional */
28 CACHE_DECLARE(int) ap_cache_request_is_conditional(apr_table_t *table)
29 {
30     if (apr_table_get(table, "If-Match") ||
31         apr_table_get(table, "If-None-Match") ||
32         apr_table_get(table, "If-Modified-Since") ||
33         apr_table_get(table, "If-Unmodified-Since")) {
34         return 1;
35     }
36     return 0;
37 }
38
39 CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r,
40                                                   cache_server_conf *conf, 
41                                                   const char *url)
42 {
43     cache_provider_list *providers = NULL;
44     int i;
45
46     /* we can't cache if there's no URL */
47     /* Is this case even possible?? */
48     if (!url) return NULL;
49
50     /* loop through all the cacheenable entries */
51     for (i = 0; i < conf->cacheenable->nelts; i++) {
52         struct cache_enable *ent = 
53                                 (struct cache_enable *)conf->cacheenable->elts;
54         if ((ent[i].url) && !strncasecmp(url, ent[i].url, ent[i].urllen)) {
55             /* Fetch from global config and add to the list. */
56             cache_provider *provider;
57             provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent[i].type,
58                                           "0");
59             if (!provider) {
60                 /* Log an error! */
61             }
62             else {
63                 cache_provider_list *newp;
64                 newp = apr_pcalloc(r->pool, sizeof(cache_provider_list));
65                 newp->provider_name = ent[i].type;
66                 newp->provider = provider;
67
68                 if (!providers) {
69                     providers = newp;
70                 }
71                 else {
72                     cache_provider_list *last = providers;
73
74                     while (last->next) {
75                         last = last->next;
76                     }
77                     last->next = newp;
78                 }
79             }
80         }
81     }
82
83     /* then loop through all the cachedisable entries
84      * Looking for urls that contain the full cachedisable url and possibly
85      * more.
86      * This means we are disabling cachedisable url and below...
87      */
88     for (i = 0; i < conf->cachedisable->nelts; i++) {
89         struct cache_disable *ent = 
90                                (struct cache_disable *)conf->cachedisable->elts;
91         if ((ent[i].url) && !strncasecmp(url, ent[i].url, ent[i].urllen)) {
92             /* Stop searching now. */
93             return NULL;
94         }
95     }
96
97     return providers;
98 }
99
100
101 /* do a HTTP/1.1 age calculation */
102 CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info,
103                                                 const apr_time_t age_value,
104                                                 apr_time_t now)
105 {
106     apr_time_t apparent_age, corrected_received_age, response_delay,
107                corrected_initial_age, resident_time, current_age,
108                age_value_usec;
109
110     age_value_usec = apr_time_from_sec(age_value);
111
112     /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
113
114     apparent_age = MAX(0, info->response_time - info->date);
115     corrected_received_age = MAX(apparent_age, age_value_usec);
116     response_delay = info->response_time - info->request_time;
117     corrected_initial_age = corrected_received_age + response_delay;
118     resident_time = now - info->response_time;
119     current_age = corrected_initial_age + resident_time;
120
121     return apr_time_sec(current_age);
122 }
123
124 CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h,
125                                             request_rec *r)
126 {
127     apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
128     apr_int64_t minfresh;
129     int age_in_errhdr = 0;
130     const char *cc_cresp, *cc_ceresp, *cc_req;
131     const char *agestr = NULL;
132     const char *expstr = NULL;
133     char *val;
134     apr_time_t age_c = 0;
135     cache_info *info = &(h->cache_obj->info);
136
137     /*
138      * We now want to check if our cached data is still fresh. This depends
139      * on a few things, in this order:
140      *
141      * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in
142      * either the request or the cached response means that we must
143      * revalidate the request unconditionally, overriding any expiration
144      * mechanism. It's equivalent to max-age=0,must-revalidate.
145      * 
146      * - RFC2616 14.32 Pragma: no-cache This is treated the same as
147      * Cache-Control: no-cache.
148      * 
149      * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
150      * proxy-revalidate if the max-stale request header exists, modify the
151      * stale calculations below so that an object can be at most <max-stale>
152      * seconds stale before we request a revalidation, _UNLESS_ a
153      * must-revalidate or proxy-revalidate cached response header exists to
154      * stop us doing this.
155      * 
156      * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
157      * maximum age an object can be before it is considered stale. This
158      * directive has the effect of proxy|must revalidate, which in turn means
159      * simple ignore any max-stale setting.
160      * 
161      * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
162      * requests and responses. If both are specified, the smaller of the two
163      * takes priority.
164      * 
165      * - RFC2616 14.21 Expires: if this request header exists in the cached
166      * entity, and it's value is in the past, it has expired.
167      * 
168      */
169     cc_cresp = apr_table_get(h->resp_hdrs, "Cache-Control");
170     cc_ceresp = apr_table_get(h->resp_err_hdrs, "Cache-Control");
171     cc_req = apr_table_get(h->req_hdrs, "Cache-Control");
172
173     if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) {
174         age_c = apr_atoi64(agestr);
175     }
176     else if ((agestr = apr_table_get(h->resp_err_hdrs, "Age"))) {
177         age_c = apr_atoi64(agestr);
178         age_in_errhdr = 1;
179     }
180
181     if (!(expstr = apr_table_get(h->resp_err_hdrs, "Expires"))) {
182         expstr = apr_table_get(h->resp_hdrs, "Expires");
183     }
184
185     /* calculate age of object */
186     age = ap_cache_current_age(info, age_c, r->request_time);
187
188     /* extract s-maxage */
189     if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)
190         && val != NULL) {
191         smaxage = apr_atoi64(val);
192     }
193     else if (cc_ceresp && ap_cache_liststr(r->pool, cc_ceresp, "s-maxage", &val)) {
194         smaxage = apr_atoi64(val);
195     }
196     else {
197         smaxage = -1;
198     }
199
200     /* extract max-age from request */
201     if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)
202         && val != NULL) {
203         maxage_req = apr_atoi64(val);
204     }
205     else {
206         maxage_req = -1;
207     }
208
209     /* extract max-age from response */
210     if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)
211         && val != NULL) {
212         maxage_cresp = apr_atoi64(val);
213     }
214     else if (cc_ceresp && ap_cache_liststr(r->pool, cc_ceresp, "max-age", &val)) {
215         maxage_cresp = apr_atoi64(val);
216     }
217     else
218     {
219         maxage_cresp = -1;
220     }
221
222     /*
223      * if both maxage request and response, the smaller one takes priority
224      */
225     if (-1 == maxage_req) {
226         maxage = maxage_cresp;
227     }
228     else if (-1 == maxage_cresp) {
229         maxage = maxage_req;
230     }
231     else {
232         maxage = MIN(maxage_req, maxage_cresp);
233     }
234
235     /* extract max-stale */
236     if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) {
237         if(val != NULL) {
238             maxstale = apr_atoi64(val);
239         }
240         else {
241             /*
242              * If no value is assigned to max-stale, then the client is willing
243              * to accept a stale response of any age (RFC2616 14.9.3). We will
244              * set it to one year in this case as this situation is somewhat
245              * similar to a "never expires" Expires header (RFC2616 14.21)
246              * which is set to a date one year from the time the response is
247              * sent in this case.
248              */
249             maxstale = APR_INT64_C(86400*365);
250         }
251     }
252     else {
253         maxstale = 0;
254     }
255
256     /* extract min-fresh */
257     if (cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)
258         && val != NULL) {
259         minfresh = apr_atoi64(val);
260     }
261     else {
262         minfresh = 0;
263     }
264
265     /* override maxstale if must-revalidate or proxy-revalidate */
266     if (maxstale && ((cc_cresp &&
267                       ap_cache_liststr(NULL, cc_cresp,
268                                        "must-revalidate", NULL)) ||
269                      (cc_cresp &&
270                       ap_cache_liststr(NULL, cc_cresp,
271                                        "proxy-revalidate", NULL)) ||
272                      (cc_ceresp &&
273                       ap_cache_liststr(NULL, cc_ceresp,
274                                        "must-revalidate", NULL)) ||
275                      (cc_ceresp &&
276                       ap_cache_liststr(NULL, cc_ceresp,
277                                        "proxy-revalidate", NULL)))) {
278         maxstale = 0;
279     }
280
281     /* handle expiration */
282     if (((smaxage != -1) && (age < (smaxage - minfresh))) ||
283         ((maxage != -1) && (age < (maxage + maxstale - minfresh))) ||
284         ((smaxage == -1) && (maxage == -1) &&
285          (info->expire != APR_DATE_BAD) &&
286          (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) {
287         const char *warn_head;
288         apr_table_t *head_ptr;
289
290         warn_head = apr_table_get(h->resp_hdrs, "Warning");
291         if (warn_head != NULL) {
292             head_ptr = h->resp_hdrs;
293         }
294         else {
295             warn_head = apr_table_get(h->resp_err_hdrs, "Warning");
296             head_ptr = h->resp_err_hdrs;
297         }
298
299         /* it's fresh darlings... */
300         /* set age header on response */
301         if (age_in_errhdr) {
302             apr_table_set(h->resp_err_hdrs, "Age",
303                           apr_psprintf(r->pool, "%lu", (unsigned long)age));
304         }
305         else {
306             apr_table_set(h->resp_hdrs, "Age",
307                           apr_psprintf(r->pool, "%lu", (unsigned long)age));
308         }
309
310         /* add warning if maxstale overrode freshness calculation */
311         if (!(((smaxage != -1) && age < smaxage) ||
312               ((maxage != -1) && age < maxage) ||
313               (info->expire != APR_DATE_BAD &&
314                (info->expire - info->date) > age))) {
315             /* make sure we don't stomp on a previous warning */
316             if ((warn_head == NULL) ||
317                 ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
318                 apr_table_merge(head_ptr, "Warning", "110 Response is stale");
319             }
320         }
321         /* 
322          * If none of Expires, Cache-Control: max-age, or Cache-Control: 
323          * s-maxage appears in the response, and the respose header age 
324          * calculated is more than 24 hours add the warning 113 
325          */
326         if ((maxage_cresp == -1) && (smaxage == -1) &&
327             (expstr == NULL) && (age > 86400)) {
328
329             /* Make sure we don't stomp on a previous warning, and don't dup
330              * a 113 marning that is already present. Also, make sure to add
331              * the new warning to the correct *headers_out location.
332              */
333             if ((warn_head == NULL) ||
334                 ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) {
335                 apr_table_merge(head_ptr, "Warning", "113 Heuristic expiration");
336             }
337         }
338         return 1;    /* Cache object is fresh (enough) */
339     }
340     return 0;        /* Cache object is stale */
341 }
342
343 /*
344  * list is a comma-separated list of case-insensitive tokens, with
345  * optional whitespace around the tokens.
346  * The return returns 1 if the token val is found in the list, or 0
347  * otherwise.
348  */
349 CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
350                                     const char *key, char **val)
351 {
352     apr_size_t key_len;
353     const char *next;
354
355     if (!list) {
356         return 0;
357     }
358
359     key_len = strlen(key);
360     next = list;
361
362     for (;;) {
363
364         /* skip whitespace and commas to find the start of the next key */
365         while (*next && (apr_isspace(*next) || (*next == ','))) {
366             next++;
367         }
368
369         if (!*next) {
370             return 0;
371         }
372
373         if (!strncasecmp(next, key, key_len)) {
374             /* this field matches the key (though it might just be
375              * a prefix match, so make sure the match is followed
376              * by either a space or an equals sign)
377              */
378             next += key_len;
379             if (!*next || (*next == '=') || apr_isspace(*next) ||
380                 (*next == ',')) {
381                 /* valid match */
382                 if (val) {
383                     while (*next && (*next != '=') && (*next != ',')) {
384                         next++;
385                     }
386                     if (*next == '=') {
387                         next++;
388                         while (*next && apr_isspace(*next )) {
389                             next++;
390                         }
391                         if (!*next) {
392                             *val = NULL;
393                         }
394                         else {
395                             const char *val_start = next;
396                             while (*next && !apr_isspace(*next) &&
397                                    (*next != ',')) {
398                                 next++;
399                             }
400                             *val = apr_pstrmemdup(p, val_start,
401                                                   next - val_start);
402                         }
403                     }
404                     else {
405                         *val = NULL;
406                     }
407                 }
408                 return 1;
409             }
410         }
411
412         /* skip to the next field */
413         do {
414             next++;
415             if (!*next) {
416                 return 0;
417             }
418         } while (*next != ',');
419     }
420 }
421
422 /* return each comma separated token, one at a time */
423 CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list,
424                                            const char **str)
425 {
426     apr_size_t i;
427     const char *s;
428
429     s = ap_strchr_c(list, ',');
430     if (s != NULL) {
431         i = s - list;
432         do
433             s++;
434         while (apr_isspace(*s))
435             ; /* noop */
436     }
437     else
438         i = strlen(list);
439
440     while (i > 0 && apr_isspace(list[i - 1]))
441         i--;
442
443     *str = s;
444     if (i)
445         return apr_pstrndup(p, list, i);
446     else
447         return NULL;
448 }
449
450 /*
451  * Converts apr_time_t expressed as hex digits to 
452  * a true apr_time_t.
453  */
454 CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
455 {
456     int i, ch;
457     apr_time_t j;
458     for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
459         ch = x[i];
460         j <<= 4;
461         if (apr_isdigit(ch))
462             j |= ch - '0';
463         else if (apr_isupper(ch))
464             j |= ch - ('A' - 10);
465         else
466             j |= ch - ('a' - 10);
467     }
468     return j;
469 }
470
471 /*
472  * Converts apr_time_t to apr_time_t expressed as hex digits.
473  */
474 CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
475 {
476     int i, ch;
477
478     for (i = (sizeof(j) * 2)-1; i >= 0; i--) {
479         ch = (int)(j & 0xF);
480         j >>= 4;
481         if (ch >= 10)
482             y[i] = ch + ('A' - 10);
483         else
484             y[i] = ch + '0';
485     }
486     y[sizeof(j) * 2] = '\0';
487 }
488
489 static void cache_hash(const char *it, char *val, int ndepth, int nlength)
490 {
491     apr_md5_ctx_t context;
492     unsigned char digest[16];
493     char tmp[22];
494     int i, k, d;
495     unsigned int x;
496     static const char enc_table[64] =
497     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
498
499     apr_md5_init(&context);
500     apr_md5_update(&context, (const unsigned char *) it, strlen(it));
501     apr_md5_final(digest, &context);
502
503     /* encode 128 bits as 22 characters, using a modified uuencoding 
504      * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is 
505      * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
506      */
507     for (i = 0, k = 0; i < 15; i += 3) {
508         x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
509         tmp[k++] = enc_table[x >> 18];
510         tmp[k++] = enc_table[(x >> 12) & 0x3f];
511         tmp[k++] = enc_table[(x >> 6) & 0x3f];
512         tmp[k++] = enc_table[x & 0x3f];
513     }
514
515     /* one byte left */
516     x = digest[15];
517     tmp[k++] = enc_table[x >> 2];    /* use up 6 bits */
518     tmp[k++] = enc_table[(x << 4) & 0x3f];
519
520     /* now split into directory levels */
521     for (i = k = d = 0; d < ndepth; ++d) {
522         memcpy(&val[i], &tmp[k], nlength);
523         k += nlength;
524         val[i + nlength] = '/';
525         i += nlength + 1;
526     }
527     memcpy(&val[i], &tmp[k], 22 - k);
528     val[i + 22 - k] = '\0';
529 }
530
531 CACHE_DECLARE(char *)generate_name(apr_pool_t *p, int dirlevels,
532                                    int dirlength, const char *name)
533 {
534     char hashfile[66];
535     cache_hash(name, hashfile, dirlevels, dirlength);
536     return apr_pstrdup(p, hashfile);
537 }
538
539 /* Create a new table consisting of those elements from an input
540  * headers table that are allowed to be stored in a cache.
541  */
542 CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool,
543                                                         apr_table_t *t,
544                                                         server_rec *s)
545 {
546     cache_server_conf *conf;
547     char **header;
548     int i;
549
550     /* Make a copy of the headers, and remove from
551      * the copy any hop-by-hop headers, as defined in Section
552      * 13.5.1 of RFC 2616
553      */
554     apr_table_t *headers_out;
555     headers_out = apr_table_copy(pool, t);
556     apr_table_unset(headers_out, "Connection");
557     apr_table_unset(headers_out, "Keep-Alive");
558     apr_table_unset(headers_out, "Proxy-Authenticate");
559     apr_table_unset(headers_out, "Proxy-Authorization");
560     apr_table_unset(headers_out, "TE");
561     apr_table_unset(headers_out, "Trailers");
562     apr_table_unset(headers_out, "Transfer-Encoding");
563     apr_table_unset(headers_out, "Upgrade");
564
565     conf = (cache_server_conf *)ap_get_module_config(s->module_config,
566                                                      &cache_module);
567     /* Remove the user defined headers set with CacheIgnoreHeaders.
568      * This may break RFC 2616 compliance on behalf of the administrator.
569      */
570     header = (char **)conf->ignore_headers->elts;
571     for (i = 0; i < conf->ignore_headers->nelts; i++) {
572         apr_table_unset(headers_out, header[i]);
573     }
574     return headers_out;
575 }