bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / metadata / mod_expires.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 /*
18  * mod_expires.c
19  * version 0.0.11
20  * status beta
21  * 
22  * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 26.Jan.96
23  *
24  * This module allows you to control the form of the Expires: header
25  * that Apache issues for each access.  Directives can appear in
26  * configuration files or in .htaccess files so expiry semantics can
27  * be defined on a per-directory basis.  
28  *
29  * DIRECTIVE SYNTAX
30  *
31  * Valid directives are:
32  *
33  *     ExpiresActive on | off
34  *     ExpiresDefault <code><seconds>
35  *     ExpiresByType type/encoding <code><seconds>
36  *
37  * Valid values for <code> are:
38  *
39  *     'M'      expires header shows file modification date + <seconds>
40  *     'A'      expires header shows access time + <seconds>
41  *
42  *              [I'm not sure which of these is best under different
43  *              circumstances, I guess it's for other people to explore.
44  *              The effects may be indistinguishable for a number of cases]
45  *
46  * <seconds> should be an integer value [acceptable to atoi()]
47  *
48  * There is NO space between the <code> and <seconds>.
49  *
50  * For example, a directory which contains information which changes
51  * frequently might contain:
52  *
53  *     # reports generated by cron every hour.  don't let caches
54  *     # hold onto stale information
55  *     ExpiresDefault M3600
56  *
57  * Another example, our html pages can change all the time, the gifs
58  * tend not to change often:
59  * 
60  *     # pages are hot (1 week), images are cold (1 month)
61  *     ExpiresByType text/html A604800
62  *     ExpiresByType image/gif A2592000
63  *
64  * Expires can be turned on for all URLs on the server by placing the
65  * following directive in a conf file:
66  *
67  *     ExpiresActive on
68  *
69  * ExpiresActive can also appear in .htaccess files, enabling the
70  * behaviour to be turned on or off for each chosen directory.
71  *
72  *     # turn off Expires behaviour in this directory
73  *     # and subdirectories
74  *     ExpiresActive off
75  *
76  * Directives defined for a directory are valid in subdirectories
77  * unless explicitly overridden by new directives in the subdirectory
78  * .htaccess files.
79  *
80  * ALTERNATIVE DIRECTIVE SYNTAX
81  *
82  * Directives can also be defined in a more readable syntax of the form:
83  *
84  *     ExpiresDefault "<base> [plus] {<num> <type>}*"
85  *     ExpiresByType type/encoding "<base> [plus] {<num> <type>}*"
86  *
87  * where <base> is one of:
88  *      access  
89  *      now             equivalent to 'access'
90  *      modification
91  *
92  * where the 'plus' keyword is optional
93  *
94  * where <num> should be an integer value [acceptable to atoi()]
95  *
96  * where <type> is one of:
97  *      years
98  *      months
99  *      weeks
100  *      days
101  *      hours
102  *      minutes
103  *      seconds
104  *
105  * For example, any of the following directives can be used to make
106  * documents expire 1 month after being accessed, by default:
107  *
108  *      ExpiresDefault "access plus 1 month"
109  *      ExpiresDefault "access plus 4 weeks"
110  *      ExpiresDefault "access plus 30 days"
111  *
112  * The expiry time can be fine-tuned by adding several '<num> <type>'
113  * clauses:
114  *
115  *      ExpiresByType text/html "access plus 1 month 15 days 2 hours"
116  *      ExpiresByType image/gif "modification plus 5 hours 3 minutes"
117  *
118  * ---
119  *
120  * Change-log:
121  * 29.Jan.96    Hardened the add_* functions.  Server will now bail out
122  *              if bad directives are given in the conf files.
123  * 02.Feb.96    Returns DECLINED if not 'ExpiresActive on', giving other
124  *              expires-aware modules a chance to play with the same
125  *              directives. [Michael Rutman]
126  * 03.Feb.96    Call tzset() before localtime().  Trying to get the module
127  *              to work properly in non GMT timezones.
128  * 12.Feb.96    Modified directive syntax to allow more readable commands:
129  *                ExpiresDefault "now plus 10 days 20 seconds"
130  *                ExpiresDefault "access plus 30 days"
131  *                ExpiresDefault "modification plus 1 year 10 months 30 days"
132  * 13.Feb.96    Fix call to table_get() with NULL 2nd parameter [Rob Hartill]
133  * 19.Feb.96    Call gm_timestr_822() to get time formatted correctly, can't
134  *              rely on presence of HTTP_TIME_FORMAT in Apache 1.1+.
135  * 21.Feb.96    This version (0.0.9) reverses assumptions made in 0.0.8
136  *              about star/star handlers.  Reverting to 0.0.7 behaviour.
137  * 08.Jun.96    allows ExpiresDefault to be used with responses that use 
138  *              the DefaultType by not DECLINING, but instead skipping 
139  *              the table_get check and then looking for an ExpiresDefault.
140  *              [Rob Hartill]
141  * 04.Nov.96    'const' definitions added.
142  *
143  * TODO
144  * add support for Cache-Control: max-age=20 from the HTTP/1.1
145  * proposal (in this case, a ttl of 20 seconds) [ask roy]
146  * add per-file expiry and explicit expiry times - duplicates some
147  * of the mod_cern_meta.c functionality.  eg:
148  *              ExpiresExplicit index.html "modification plus 30 days"
149  *
150  * BUGS
151  * Hi, welcome to the internet.
152  */
153
154 #include "apr.h"
155 #include "apr_strings.h"
156 #include "apr_lib.h"
157
158 #define APR_WANT_STRFUNC
159 #include "apr_want.h"
160
161 #include "ap_config.h"
162 #include "httpd.h"
163 #include "http_config.h"
164 #include "http_log.h"
165 #include "http_request.h"
166 #include "http_protocol.h"
167
168 typedef struct {
169     int active;
170     int wildcards;
171     char *expiresdefault;
172     apr_table_t *expiresbytype;
173 } expires_dir_config;
174
175 /* from mod_dir, why is this alias used?
176  */
177 #define DIR_CMD_PERMS OR_INDEXES
178
179 #define ACTIVE_ON       1
180 #define ACTIVE_OFF      0
181 #define ACTIVE_DONTCARE 2
182
183 module AP_MODULE_DECLARE_DATA expires_module;
184
185 static void *create_dir_expires_config(apr_pool_t *p, char *dummy)
186 {
187     expires_dir_config *new =
188     (expires_dir_config *) apr_pcalloc(p, sizeof(expires_dir_config));
189     new->active = ACTIVE_DONTCARE;
190     new->wildcards = 0;
191     new->expiresdefault = NULL;
192     new->expiresbytype = apr_table_make(p, 4);
193     return (void *) new;
194 }
195
196 static const char *set_expiresactive(cmd_parms *cmd, void *in_dir_config, int arg)
197 {
198     expires_dir_config *dir_config = in_dir_config;
199
200     /* if we're here at all it's because someone explicitly
201      * set the active flag
202      */
203     dir_config->active = ACTIVE_ON;
204     if (arg == 0) {
205         dir_config->active = ACTIVE_OFF;
206     }
207     return NULL;
208 }
209
210 /* check_code() parse 'code' and return NULL or an error response
211  * string.  If we return NULL then real_code contains code converted
212  * to the cnnnn format.
213  */
214 static char *check_code(apr_pool_t *p, const char *code, char **real_code)
215 {
216     char *word;
217     char base = 'X';
218     int modifier = 0;
219     int num = 0;
220     int factor = 0;
221
222     /* 0.0.4 compatibility?
223      */
224     if ((code[0] == 'A') || (code[0] == 'M')) {
225         *real_code = (char *)code;
226         return NULL;
227     }
228
229     /* <base> [plus] {<num> <type>}*
230      */
231
232     /* <base>
233      */
234     word = ap_getword_conf(p, &code);
235     if (!strncasecmp(word, "now", 1) ||
236         !strncasecmp(word, "access", 1)) {
237         base = 'A';
238     }
239     else if (!strncasecmp(word, "modification", 1)) {
240         base = 'M';
241     }
242     else {
243         return apr_pstrcat(p, "bad expires code, unrecognised <base> '",
244                        word, "'", NULL);
245     }
246
247     /* [plus]
248      */
249     word = ap_getword_conf(p, &code);
250     if (!strncasecmp(word, "plus", 1)) {
251         word = ap_getword_conf(p, &code);
252     }
253
254     /* {<num> <type>}*
255      */
256     while (word[0]) {
257         /* <num>
258          */
259         if (apr_isdigit(word[0])) {
260             num = atoi(word);
261         }
262         else {
263             return apr_pstrcat(p, "bad expires code, numeric value expected <num> '",
264                            word, "'", NULL);
265         }
266
267         /* <type>
268          */
269         word = ap_getword_conf(p, &code);
270         if (word[0]) {
271             /* do nothing */
272         }
273         else {
274             return apr_pstrcat(p, "bad expires code, missing <type>", NULL);
275         }
276
277         factor = 0;
278         if (!strncasecmp(word, "years", 1)) {
279             factor = 60 * 60 * 24 * 365;
280         }
281         else if (!strncasecmp(word, "months", 2)) {
282             factor = 60 * 60 * 24 * 30;
283         }
284         else if (!strncasecmp(word, "weeks", 1)) {
285             factor = 60 * 60 * 24 * 7;
286         }
287         else if (!strncasecmp(word, "days", 1)) {
288             factor = 60 * 60 * 24;
289         }
290         else if (!strncasecmp(word, "hours", 1)) {
291             factor = 60 * 60;
292         }
293         else if (!strncasecmp(word, "minutes", 2)) {
294             factor = 60;
295         }
296         else if (!strncasecmp(word, "seconds", 1)) {
297             factor = 1;
298         }
299         else {
300             return apr_pstrcat(p, "bad expires code, unrecognised <type>",
301                            "'", word, "'", NULL);
302         }
303
304         modifier = modifier + factor * num;
305
306         /* next <num>
307          */
308         word = ap_getword_conf(p, &code);
309     }
310
311     *real_code = apr_psprintf(p, "%c%d", base, modifier);
312
313     return NULL;
314 }
315
316 static const char *set_expiresbytype(cmd_parms *cmd, void *in_dir_config,
317                                      const char *mime, const char *code)
318 {
319     expires_dir_config *dir_config = in_dir_config;
320     char *response, *real_code;
321     const char *check;
322
323     check = ap_strrchr_c(mime, '/');
324     if ((strlen(++check) == 1) && (*check == '*')) {
325         dir_config->wildcards = 1;
326     }
327     
328     if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
329         apr_table_setn(dir_config->expiresbytype, mime, real_code);
330         return NULL;
331     }
332     return apr_pstrcat(cmd->pool,
333                  "'ExpiresByType ", mime, " ", code, "': ", response, NULL);
334 }
335
336 static const char *set_expiresdefault(cmd_parms *cmd, void *in_dir_config,
337                                       const char *code)
338 {
339     expires_dir_config * dir_config = in_dir_config;
340     char *response, *real_code;
341
342     if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
343         dir_config->expiresdefault = real_code;
344         return NULL;
345     }
346     return apr_pstrcat(cmd->pool,
347                    "'ExpiresDefault ", code, "': ", response, NULL);
348 }
349
350 static const command_rec expires_cmds[] =
351 {
352     AP_INIT_FLAG("ExpiresActive", set_expiresactive, NULL, DIR_CMD_PERMS,
353                  "Limited to 'on' or 'off'"),
354     AP_INIT_TAKE2("ExpiresByType", set_expiresbytype, NULL, DIR_CMD_PERMS,
355                   "a MIME type followed by an expiry date code"),
356     AP_INIT_TAKE1("ExpiresDefault", set_expiresdefault, NULL, DIR_CMD_PERMS,
357                   "an expiry date code"),
358     {NULL}
359 };
360
361 static void *merge_expires_dir_configs(apr_pool_t *p, void *basev, void *addv)
362 {
363     expires_dir_config *new = (expires_dir_config *) apr_pcalloc(p, sizeof(expires_dir_config));
364     expires_dir_config *base = (expires_dir_config *) basev;
365     expires_dir_config *add = (expires_dir_config *) addv;
366
367     if (add->active == ACTIVE_DONTCARE) {
368         new->active = base->active;
369     }
370     else {
371         new->active = add->active;
372     }
373
374     if (add->expiresdefault != NULL) {
375         new->expiresdefault = add->expiresdefault;
376     }
377     else {
378         new->expiresdefault = base->expiresdefault;
379     }
380     new->wildcards = add->wildcards;
381     new->expiresbytype = apr_table_overlay(p, add->expiresbytype,
382                                         base->expiresbytype);
383     return new;
384 }
385
386 /*
387  * Handle the setting of the expiration response header fields according
388  * to our criteria.
389  */
390
391 static int set_expiration_fields(request_rec *r, const char *code,
392                                  apr_table_t *t)
393 {
394     apr_time_t base;
395     apr_time_t additional;
396     apr_time_t expires;
397     int additional_sec;
398     char *timestr;
399
400     switch (code[0]) {
401     case 'M':
402         if (r->finfo.filetype == 0) { 
403             /* file doesn't exist on disk, so we can't do anything based on
404              * modification time.  Note that this does _not_ log an error.
405              */
406             return DECLINED;
407         }
408         base = r->finfo.mtime;
409         additional_sec = atoi(&code[1]);
410         additional = apr_time_from_sec(additional_sec);
411         break;
412     case 'A':
413         /* there's been some discussion and it's possible that 
414          * 'access time' will be stored in request structure
415          */
416         base = r->request_time;
417         additional_sec = atoi(&code[1]);
418         additional = apr_time_from_sec(additional_sec);
419         break;
420     default:
421         /* expecting the add_* routines to be case-hardened this 
422          * is just a reminder that module is beta
423          */
424         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
425                     "internal error: bad expires code: %s", r->filename);
426         return HTTP_INTERNAL_SERVER_ERROR;
427     }
428
429     expires = base + additional;
430     if (expires < r->request_time) {
431         expires = r->request_time;
432     }
433     apr_table_mergen(t, "Cache-Control",
434                      apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
435                                   apr_time_sec(expires - r->request_time)));
436     timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
437     apr_rfc822_date(timestr, expires);
438     apr_table_setn(t, "Expires", timestr);
439     return OK;
440 }
441
442 /*
443  * Output filter to set the Expires response header field
444  * according to the content-type of the response -- if it hasn't
445  * already been set.
446  */
447 static apr_status_t expires_filter(ap_filter_t *f,
448                                    apr_bucket_brigade *b)
449 {
450     request_rec *r;
451     expires_dir_config *conf;
452     const char *expiry;
453     apr_table_t *t;
454
455     r = f->r;
456     conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
457                                                        &expires_module);
458
459     /*
460      * Check to see which output header table we should use;
461      * mod_cgi loads script fields into r->err_headers_out,
462      * for instance.
463      */
464     expiry = apr_table_get(r->err_headers_out, "Expires");
465     if (expiry != NULL) {
466         t = r->err_headers_out;
467     }
468     else {
469         expiry = apr_table_get(r->headers_out, "Expires");
470         t = r->headers_out;
471     }
472     if (expiry == NULL) {
473         /*
474          * No expiration has been set, so we can apply any managed by
475          * this module.  First, check to see if there is an applicable
476          * ExpiresByType directive.
477          */
478         expiry = apr_table_get(conf->expiresbytype, 
479                                ap_field_noparam(r->pool, r->content_type));
480         if (expiry == NULL) {
481             int usedefault = 1;
482             /*
483              * See if we have a wildcard entry for the major type.
484              */
485             if (conf->wildcards) {
486                 char *checkmime;
487                 char *spos;
488                 checkmime = apr_pstrdup(r->pool, r->content_type);
489                 spos = checkmime ? ap_strchr(checkmime, '/') : NULL;
490                 if (spos != NULL) {
491                     /*
492                      * Without a '/' character, nothing we have will match.
493                      * However, we have one.
494                      */
495                     if (strlen(++spos) > 0) {
496                         *spos++ = '*';
497                         *spos = '\0';
498                     }
499                     else {
500                         checkmime = apr_pstrcat(r->pool, checkmime, "*", NULL);
501                     }
502                     expiry = apr_table_get(conf->expiresbytype, checkmime);
503                     usedefault = (expiry == NULL);
504                 }
505             }
506             if (usedefault) {
507                 /*
508                  * Use the ExpiresDefault directive
509                  */
510                 expiry = conf->expiresdefault;
511             }
512         }
513         if (expiry != NULL) {
514             set_expiration_fields(r, expiry, t);
515         }
516     }
517     ap_remove_output_filter(f);
518     return ap_pass_brigade(f->next, b);
519 }
520
521 static void expires_insert_filter(request_rec *r)
522 {
523     expires_dir_config *conf;
524
525     /* Don't add Expires headers to errors */
526     if (ap_is_HTTP_ERROR(r->status)) {
527         return;
528     }
529     /* Say no to subrequests */
530     if (r->main != NULL) {
531         return;
532     }
533     conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
534                                                        &expires_module);
535
536     /* Check to see if the filter is enabled and if there are any applicable 
537      * config directives for this directory scope
538      */
539     if (conf->active != ACTIVE_ON ||
540         (apr_is_empty_table(conf->expiresbytype) && !conf->expiresdefault)) {
541         return;
542     }
543     ap_add_output_filter("MOD_EXPIRES", NULL, r, r->connection);
544     return;
545 }
546 static void register_hooks(apr_pool_t *p)
547 {
548     /* mod_expires needs to run *before* the cache save filter which is
549      * AP_FTYPE_CONTENT_SET-1.  Otherwise, our expires won't be honored.
550      */
551     ap_register_output_filter("MOD_EXPIRES", expires_filter, NULL,
552                               AP_FTYPE_CONTENT_SET-2);
553     ap_hook_insert_error_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
554     ap_hook_insert_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
555 }
556
557 module AP_MODULE_DECLARE_DATA expires_module =
558 {
559     STANDARD20_MODULE_STUFF,
560     create_dir_expires_config,  /* dir config creater */
561     merge_expires_dir_configs,  /* dir merger --- default is to override */
562     NULL,                       /* server config */
563     NULL,                       /* merge server configs */
564     expires_cmds,               /* command apr_table_t */
565     register_hooks              /* register hooks */
566 };