~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/metadata/mod_expires.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
    apr_table_mergen(t, "Cache-Control",
 
431
                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
 
432
                                  apr_time_sec(expires - r->request_time)));
 
433
    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
 
434
    apr_rfc822_date(timestr, expires);
 
435
    apr_table_setn(t, "Expires", timestr);
 
436
    return OK;
 
437
}
 
438
 
 
439
/*
 
440
 * Output filter to set the Expires response header field
 
441
 * according to the content-type of the response -- if it hasn't
 
442
 * already been set.
 
443
 */
 
444
static apr_status_t expires_filter(ap_filter_t *f,
 
445
                                   apr_bucket_brigade *b)
 
446
{
 
447
    request_rec *r;
 
448
    expires_dir_config *conf;
 
449
    const char *expiry;
 
450
    apr_table_t *t;
 
451
 
 
452
    r = f->r;
 
453
    conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
 
454
                                                       &expires_module);
 
455
 
 
456
    /*
 
457
     * Check to see which output header table we should use;
 
458
     * mod_cgi loads script fields into r->err_headers_out,
 
459
     * for instance.
 
460
     */
 
461
    expiry = apr_table_get(r->err_headers_out, "Expires");
 
462
    if (expiry != NULL) {
 
463
        t = r->err_headers_out;
 
464
    }
 
465
    else {
 
466
        expiry = apr_table_get(r->headers_out, "Expires");
 
467
        t = r->headers_out;
 
468
    }
 
469
    if (expiry == NULL) {
 
470
        /*
 
471
         * No expiration has been set, so we can apply any managed by
 
472
         * this module.  First, check to see if there is an applicable
 
473
         * ExpiresByType directive.
 
474
         */
 
475
        expiry = apr_table_get(conf->expiresbytype,
 
476
                               ap_field_noparam(r->pool, r->content_type));
 
477
        if (expiry == NULL) {
 
478
            int usedefault = 1;
 
479
            /*
 
480
             * See if we have a wildcard entry for the major type.
 
481
             */
 
482
            if (conf->wildcards) {
 
483
                char *checkmime;
 
484
                char *spos;
 
485
                checkmime = apr_pstrdup(r->pool, r->content_type);
 
486
                spos = checkmime ? ap_strchr(checkmime, '/') : NULL;
 
487
                if (spos != NULL) {
 
488
                    /*
 
489
                     * Without a '/' character, nothing we have will match.
 
490
                     * However, we have one.
 
491
                     */
 
492
                    if (strlen(++spos) > 0) {
 
493
                        *spos++ = '*';
 
494
                        *spos = '\0';
 
495
                    }
 
496
                    else {
 
497
                        checkmime = apr_pstrcat(r->pool, checkmime, "*", NULL);
 
498
                    }
 
499
                    expiry = apr_table_get(conf->expiresbytype, checkmime);
 
500
                    usedefault = (expiry == NULL);
 
501
                }
 
502
            }
 
503
            if (usedefault) {
 
504
                /*
 
505
                 * Use the ExpiresDefault directive
 
506
                 */
 
507
                expiry = conf->expiresdefault;
 
508
            }
 
509
        }
 
510
        if (expiry != NULL) {
 
511
            set_expiration_fields(r, expiry, t);
 
512
        }
 
513
    }
 
514
    ap_remove_output_filter(f);
 
515
    return ap_pass_brigade(f->next, b);
 
516
}
 
517
 
 
518
static void expires_insert_filter(request_rec *r)
 
519
{
 
520
    expires_dir_config *conf;
 
521
 
 
522
    /* Don't add Expires headers to errors */
 
523
    if (ap_is_HTTP_ERROR(r->status)) {
 
524
        return;
 
525
    }
 
526
    /* Say no to subrequests */
 
527
    if (r->main != NULL) {
 
528
        return;
 
529
    }
 
530
    conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
 
531
                                                       &expires_module);
 
532
 
 
533
    /* Check to see if the filter is enabled and if there are any applicable
 
534
     * config directives for this directory scope
 
535
     */
 
536
    if (conf->active != ACTIVE_ON ||
 
537
        (apr_is_empty_table(conf->expiresbytype) && !conf->expiresdefault)) {
 
538
        return;
 
539
    }
 
540
    ap_add_output_filter("MOD_EXPIRES", NULL, r, r->connection);
 
541
    return;
 
542
}
 
543
static void register_hooks(apr_pool_t *p)
 
544
{
 
545
    /* mod_expires needs to run *before* the cache save filter which is
 
546
     * AP_FTYPE_CONTENT_SET-1.  Otherwise, our expires won't be honored.
 
547
     */
 
548
    ap_register_output_filter("MOD_EXPIRES", expires_filter, NULL,
 
549
                              AP_FTYPE_CONTENT_SET-2);
 
550
    ap_hook_insert_error_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
 
551
    ap_hook_insert_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
 
552
}
 
553
 
 
554
module AP_MODULE_DECLARE_DATA expires_module =
 
555
{
 
556
    STANDARD20_MODULE_STUFF,
 
557
    create_dir_expires_config,  /* dir config creater */
 
558
    merge_expires_dir_configs,  /* dir merger --- default is to override */
 
559
    NULL,                       /* server config */
 
560
    NULL,                       /* merge server configs */
 
561
    expires_cmds,               /* command apr_table_t */
 
562
    register_hooks              /* register hooks */
 
563
};