--- mod_expires.c.ORIG 2006-07-12 01:16:05.000000000 -0700 +++ mod_expires.c 2007-03-27 23:39:37.000000000 -0700 @@ -115,6 +115,23 @@ * ExpiresByType text/html "access plus 1 month 15 days 2 hours" * ExpiresByType image/gif "modification plus 5 hours 3 minutes" * + * Finally, there is a special form for differing expiration times based + * on the age of the file, as in this example: + * + * ExpiresByType image/jpg "aged 2 days then 10 years else 1 hour" + * + * This means that JPG image files who last modification time was two days + * ago or more, are served with an expires time 10 years from the current + * access time, while those image files that are less than 2 days old are + * served with an expire time 1 hour from the current access time. + * + * The form is "aged then [else ]" + * + * The "else...." clause can be omitted, which means that files newer than + * the threshold time are not given an expires header. + * + * This form may also be used for ExpiresDefault. + * * --- * * Change-log: @@ -139,6 +156,7 @@ * the table_get check and then looking for an ExpiresDefault. * [Rob Hartill] * 04.Nov.96 'const' definitions added. + * 27.Mar.07 added the 'aged' form * * TODO * add support for Cache-Control: max-age=20 from the HTTP/1.1 @@ -194,6 +212,9 @@ return NULL; } +static const char *parse_timespan(pool *p, char *word, const char **code, unsigned int *timespan, const char *end_at); + + /* check_code() parse 'code' and return NULL or an error response * string. If we return NULL then real_code contains code converted * to the cnnnn format. @@ -203,8 +224,10 @@ char *word; char base = 'X'; int modifier = 0; - int num = 0; - int factor = 0; + const char *error; + unsigned age; + unsigned num1; + unsigned num2; /* 0.0.4 compatibility? */ @@ -213,12 +236,33 @@ return NULL; }; - /* [plus] { }* - */ + word = ap_getword_conf(p, &code); - /* + /* aged { }+ then { }+ [else { }+ ] */ + if (!strncasecmp(word, "aged", 4)) { + error = parse_timespan(p, NULL, &code, &age, "then"); + if (error) + return error; + + error = parse_timespan(p, NULL, &code, &num1, "else"); + if (error) + return error; + + num2 = 0; + if (*code) { + error = parse_timespan(p, NULL, &code, &num2, NULL); + if (error) + return error; + } + *real_code = ap_psprintf(p, ">%010d %010d %010d", age, num1, num2); + + /* ap_log_error(APLOG_MARK, APLOG_NOERRNO, NULL, "result[%s]", *real_code); */ + return NULL; + } + + + /* [plus] { }* */ - word = ap_getword_conf(p, &code); if (!strncasecmp(word, "now", 1) || !strncasecmp(word, "access", 1)) { base = 'A'; @@ -238,8 +282,25 @@ word = ap_getword_conf(p, &code); }; - /* { }* - */ + error = parse_timespan(p, word, &code, &modifier, NULL); + if (error) + return error; + + *real_code = ap_psprintf(p, "%c%d", base, modifier); + + return NULL; +} + +static const char *parse_timespan(pool *p, char *word, const char **code, unsigned int *timespan, const char *end_at) +{ + int num = 0; + int factor = 0; + + *timespan = 0; + + if (!word) + word = ap_getword_conf(p, code); + while (word[0]) { /* */ @@ -247,19 +308,15 @@ num = atoi(word); } else { - return ap_pstrcat(p, "bad expires code, numeric value expected '", - word, "'", NULL); + return ap_pstrcat(p, "bad expires code, numeric value expected , got '", + word, "'", NULL); }; /* */ - word = ap_getword_conf(p, &code); - if (word[0]) { - /* do nothing */ - } - else { + word = ap_getword_conf(p, code); + if (!word[0]) return ap_pstrcat(p, "bad expires code, missing ", NULL); - }; factor = 0; if (!strncasecmp(word, "years", 1)) { @@ -288,18 +345,19 @@ "'", word, "'", NULL); }; - modifier = modifier + factor * num; + *timespan += factor * num; /* next */ - word = ap_getword_conf(p, &code); - }; + word = ap_getword_conf(p, code); - *real_code = ap_psprintf(p, "%c%d", base, modifier); - - return NULL; + if (end_at && !strncasecmp(word, end_at, strlen(end_at))) + return(NULL); + }; + return(NULL); } + static const char *set_expiresbytype(cmd_parms *cmd, expires_dir_config * dir_config, char *mime, char *code) { char *response, *real_code; @@ -427,6 +485,35 @@ base = r->request_time; additional = atoi(&code[1]); break; + case '>': + if (r->finfo.st_mode == 0) { + /* file doesn't exist on disk, so we can't do anything based on + * modification time. Note that this does _not_ log an error. + */ + return DECLINED; + } + + /* code is of the form ">0031536000 0031536000 0000003600", where + * all numbers are 10 digits long, the first number (starting at + * code[1]) is the age threashold. If the file is at least that + * many seconds old, the second number (starting at code[12]) is + * used as "additional", while if the file is newer than that + * threshold, the third number (starting at code[23]) is used as + * "additional". */ + + if (r->request_time - r->finfo.st_mtime >= atoi(&code[1])) + additional = atoi(&code[12]); + else + additional = atoi(&code[23]); + + /* ap_log_rerror(APLOG_MARK, APLOG_NOERRNO, r, "additional = %d", additional); */ + + if (additional == 0) + return DECLINED; + else + base = r->request_time; + break; + default: /* expecting the add_* routines to be case-hardened this * is just a reminder that module is beta