home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1999 April / PCO0499.ISO / filesbbs / os2 / apach134.arj / APACH134.ZIP / src / modules / standard / mod_expires.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-01-01  |  16.8 KB  |  511 lines

  1. /* ====================================================================
  2.  * Copyright (c) 1995-1999 The Apache Group.  All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer. 
  10.  *
  11.  * 2. Redistributions in binary form must reproduce the above copyright
  12.  *    notice, this list of conditions and the following disclaimer in
  13.  *    the documentation and/or other materials provided with the
  14.  *    distribution.
  15.  *
  16.  * 3. All advertising materials mentioning features or use of this
  17.  *    software must display the following acknowledgment:
  18.  *    "This product includes software developed by the Apache Group
  19.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  20.  *
  21.  * 4. The names "Apache Server" and "Apache Group" must not be used to
  22.  *    endorse or promote products derived from this software without
  23.  *    prior written permission. For written permission, please contact
  24.  *    apache@apache.org.
  25.  *
  26.  * 5. Products derived from this software may not be called "Apache"
  27.  *    nor may "Apache" appear in their names without prior written
  28.  *    permission of the Apache Group.
  29.  *
  30.  * 6. Redistributions of any form whatsoever must retain the following
  31.  *    acknowledgment:
  32.  *    "This product includes software developed by the Apache Group
  33.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  34.  *
  35.  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
  36.  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  37.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  38.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
  39.  * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  41.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  42.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  43.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  44.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  45.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  46.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  47.  * ====================================================================
  48.  *
  49.  * This software consists of voluntary contributions made by many
  50.  * individuals on behalf of the Apache Group and was originally based
  51.  * on public domain software written at the National Center for
  52.  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
  53.  * For more information on the Apache Group and the Apache HTTP server
  54.  * project, please see <http://www.apache.org/>.
  55.  *
  56.  */
  57.  
  58. /*
  59.  * mod_expires.c
  60.  * version 0.0.11
  61.  * status beta
  62.  * 
  63.  * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 26.Jan.96
  64.  *
  65.  * This module allows you to control the form of the Expires: header
  66.  * that Apache issues for each access.  Directives can appear in
  67.  * configuration files or in .htaccess files so expiry semantics can
  68.  * be defined on a per-directory basis.  
  69.  *
  70.  * DIRECTIVE SYNTAX
  71.  *
  72.  * Valid directives are:
  73.  *
  74.  *     ExpiresActive on | off
  75.  *     ExpiresDefault <code><seconds>
  76.  *     ExpiresByType type/encoding <code><seconds>
  77.  *
  78.  * Valid values for <code> are:
  79.  *
  80.  *     'M'      expires header shows file modification date + <seconds>
  81.  *     'A'      expires header shows access time + <seconds>
  82.  *
  83.  *              [I'm not sure which of these is best under different
  84.  *              circumstances, I guess it's for other people to explore.
  85.  *              The effects may be indistinguishable for a number of cases]
  86.  *
  87.  * <seconds> should be an integer value [acceptable to atoi()]
  88.  *
  89.  * There is NO space between the <code> and <seconds>.
  90.  *
  91.  * For example, a directory which contains information which changes
  92.  * frequently might contain:
  93.  *
  94.  *     # reports generated by cron every hour.  don't let caches
  95.  *     # hold onto stale information
  96.  *     ExpiresDefault M3600
  97.  *
  98.  * Another example, our html pages can change all the time, the gifs
  99.  * tend not to change often:
  100.  * 
  101.  *     # pages are hot (1 week), images are cold (1 month)
  102.  *     ExpiresByType text/html A604800
  103.  *     ExpiresByType image/gif A2592000
  104.  *
  105.  * Expires can be turned on for all URLs on the server by placing the
  106.  * following directive in a conf file:
  107.  *
  108.  *     ExpiresActive on
  109.  *
  110.  * ExpiresActive can also appear in .htaccess files, enabling the
  111.  * behaviour to be turned on or off for each chosen directory.
  112.  *
  113.  *     # turn off Expires behaviour in this directory
  114.  *     # and subdirectories
  115.  *     ExpiresActive off
  116.  *
  117.  * Directives defined for a directory are valid in subdirectories
  118.  * unless explicitly overridden by new directives in the subdirectory
  119.  * .htaccess files.
  120.  *
  121.  * ALTERNATIVE DIRECTIVE SYNTAX
  122.  *
  123.  * Directives can also be defined in a more readable syntax of the form:
  124.  *
  125.  *     ExpiresDefault "<base> [plus] {<num> <type>}*"
  126.  *     ExpiresByType type/encoding "<base> [plus] {<num> <type>}*"
  127.  *
  128.  * where <base> is one of:
  129.  *      access  
  130.  *      now             equivalent to 'access'
  131.  *      modification
  132.  *
  133.  * where the 'plus' keyword is optional
  134.  *
  135.  * where <num> should be an integer value [acceptable to atoi()]
  136.  *
  137.  * where <type> is one of:
  138.  *      years
  139.  *      months
  140.  *      weeks
  141.  *      days
  142.  *      hours
  143.  *      minutes
  144.  *      seconds
  145.  *
  146.  * For example, any of the following directives can be used to make
  147.  * documents expire 1 month after being accessed, by default:
  148.  *
  149.  *      ExpiresDefault "access plus 1 month"
  150.  *      ExpiresDefault "access plus 4 weeks"
  151.  *      ExpiresDefault "access plus 30 days"
  152.  *
  153.  * The expiry time can be fine-tuned by adding several '<num> <type>'
  154.  * clauses:
  155.  *
  156.  *      ExpiresByType text/html "access plus 1 month 15 days 2 hours"
  157.  *      ExpiresByType image/gif "modification plus 5 hours 3 minutes"
  158.  *
  159.  * ---
  160.  *
  161.  * Change-log:
  162.  * 29.Jan.96    Hardened the add_* functions.  Server will now bail out
  163.  *              if bad directives are given in the conf files.
  164.  * 02.Feb.96    Returns DECLINED if not 'ExpiresActive on', giving other
  165.  *              expires-aware modules a chance to play with the same
  166.  *              directives. [Michael Rutman]
  167.  * 03.Feb.96    Call tzset() before localtime().  Trying to get the module
  168.  *              to work properly in non GMT timezones.
  169.  * 12.Feb.96    Modified directive syntax to allow more readable commands:
  170.  *                ExpiresDefault "now plus 10 days 20 seconds"
  171.  *                ExpiresDefault "access plus 30 days"
  172.  *                ExpiresDefault "modification plus 1 year 10 months 30 days"
  173.  * 13.Feb.96    Fix call to table_get() with NULL 2nd parameter [Rob Hartill]
  174.  * 19.Feb.96    Call gm_timestr_822() to get time formatted correctly, can't
  175.  *              rely on presence of HTTP_TIME_FORMAT in Apache 1.1+.
  176.  * 21.Feb.96    This version (0.0.9) reverses assumptions made in 0.0.8
  177.  *              about star/star handlers.  Reverting to 0.0.7 behaviour.
  178.  * 08.Jun.96    allows ExpiresDefault to be used with responses that use 
  179.  *              the DefaultType by not DECLINING, but instead skipping 
  180.  *              the table_get check and then looking for an ExpiresDefault.
  181.  *              [Rob Hartill]
  182.  * 04.Nov.96    'const' definitions added.
  183.  *
  184.  * TODO
  185.  * add support for Cache-Control: max-age=20 from the HTTP/1.1
  186.  * proposal (in this case, a ttl of 20 seconds) [ask roy]
  187.  * add per-file expiry and explicit expiry times - duplicates some
  188.  * of the mod_cern_meta.c functionality.  eg:
  189.  *              ExpiresExplicit index.html "modification plus 30 days"
  190.  *
  191.  * BUGS
  192.  * Hi, welcome to the internet.
  193.  */
  194.  
  195. #include <ctype.h>
  196. #include "httpd.h"
  197. #include "http_config.h"
  198. #include "http_log.h"
  199.  
  200. typedef struct {
  201.     int active;
  202.     char *expiresdefault;
  203.     table *expiresbytype;
  204. } expires_dir_config;
  205.  
  206. /* from mod_dir, why is this alias used?
  207.  */
  208. #define DIR_CMD_PERMS OR_INDEXES
  209.  
  210. #define ACTIVE_ON       1
  211. #define ACTIVE_OFF      0
  212. #define ACTIVE_DONTCARE 2
  213.  
  214. module MODULE_VAR_EXPORT expires_module;
  215.  
  216. static void *create_dir_expires_config(pool *p, char *dummy)
  217. {
  218.     expires_dir_config *new =
  219.     (expires_dir_config *) ap_pcalloc(p, sizeof(expires_dir_config));
  220.     new->active = ACTIVE_DONTCARE;
  221.     new->expiresdefault = "";
  222.     new->expiresbytype = ap_make_table(p, 4);
  223.     return (void *) new;
  224. }
  225.  
  226. static const char *set_expiresactive(cmd_parms *cmd, expires_dir_config * dir_config, int arg)
  227. {
  228.     /* if we're here at all it's because someone explicitly
  229.      * set the active flag
  230.      */
  231.     dir_config->active = ACTIVE_ON;
  232.     if (arg == 0) {
  233.         dir_config->active = ACTIVE_OFF;
  234.     };
  235.     return NULL;
  236. }
  237.  
  238. /* check_code() parse 'code' and return NULL or an error response
  239.  * string.  If we return NULL then real_code contains code converted
  240.  * to the cnnnn format.
  241.  */
  242. static char *check_code(pool *p, const char *code, char **real_code)
  243. {
  244.     char *word;
  245.     char base = 'X';
  246.     int modifier = 0;
  247.     int num = 0;
  248.     int factor = 0;
  249.  
  250.     /* 0.0.4 compatibility?
  251.      */
  252.     if ((code[0] == 'A') || (code[0] == 'M')) {
  253.         *real_code = (char *)code;
  254.         return NULL;
  255.     };
  256.  
  257.     /* <base> [plus] {<num> <type>}*
  258.      */
  259.  
  260.     /* <base>
  261.      */
  262.     word = ap_getword_conf(p, &code);
  263.     if (!strncasecmp(word, "now", 1) ||
  264.         !strncasecmp(word, "access", 1)) {
  265.         base = 'A';
  266.     }
  267.     else if (!strncasecmp(word, "modification", 1)) {
  268.         base = 'M';
  269.     }
  270.     else {
  271.         return ap_pstrcat(p, "bad expires code, unrecognised <base> '",
  272.                        word, "'", NULL);
  273.     };
  274.  
  275.     /* [plus]
  276.      */
  277.     word = ap_getword_conf(p, &code);
  278.     if (!strncasecmp(word, "plus", 1)) {
  279.         word = ap_getword_conf(p, &code);
  280.     };
  281.  
  282.     /* {<num> <type>}*
  283.      */
  284.     while (word[0]) {
  285.         /* <num>
  286.          */
  287.         if (ap_isdigit(word[0])) {
  288.             num = atoi(word);
  289.         }
  290.         else {
  291.             return ap_pstrcat(p, "bad expires code, numeric value expected <num> '",
  292.                            word, "'", NULL);
  293.         };
  294.  
  295.         /* <type>
  296.          */
  297.         word = ap_getword_conf(p, &code);
  298.         if (word[0]) {
  299.             /* do nothing */
  300.         }
  301.         else {
  302.             return ap_pstrcat(p, "bad expires code, missing <type>", NULL);
  303.         };
  304.  
  305.         factor = 0;
  306.         if (!strncasecmp(word, "years", 1)) {
  307.             factor = 60 * 60 * 24 * 365;
  308.         }
  309.         else if (!strncasecmp(word, "months", 2)) {
  310.             factor = 60 * 60 * 24 * 30;
  311.         }
  312.         else if (!strncasecmp(word, "weeks", 1)) {
  313.             factor = 60 * 60 * 24 * 7;
  314.         }
  315.         else if (!strncasecmp(word, "days", 1)) {
  316.             factor = 60 * 60 * 24;
  317.         }
  318.         else if (!strncasecmp(word, "hours", 1)) {
  319.             factor = 60 * 60;
  320.         }
  321.         else if (!strncasecmp(word, "minutes", 2)) {
  322.             factor = 60;
  323.         }
  324.         else if (!strncasecmp(word, "seconds", 1)) {
  325.             factor = 1;
  326.         }
  327.         else {
  328.             return ap_pstrcat(p, "bad expires code, unrecognised <type>",
  329.                            "'", word, "'", NULL);
  330.         };
  331.  
  332.         modifier = modifier + factor * num;
  333.  
  334.         /* next <num>
  335.          */
  336.         word = ap_getword_conf(p, &code);
  337.     };
  338.  
  339.     *real_code = ap_psprintf(p, "%c%d", base, modifier);
  340.  
  341.     return NULL;
  342. }
  343.  
  344. static const char *set_expiresbytype(cmd_parms *cmd, expires_dir_config * dir_config, char *mime, char *code)
  345. {
  346.     char *response, *real_code;
  347.  
  348.     if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
  349.         ap_table_setn(dir_config->expiresbytype, mime, real_code);
  350.         return NULL;
  351.     };
  352.     return ap_pstrcat(cmd->pool,
  353.                  "'ExpiresByType ", mime, " ", code, "': ", response, NULL);
  354. }
  355.  
  356. static const char *set_expiresdefault(cmd_parms *cmd, expires_dir_config * dir_config, char *code)
  357. {
  358.     char *response, *real_code;
  359.  
  360.     if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
  361.         dir_config->expiresdefault = real_code;
  362.         return NULL;
  363.     };
  364.     return ap_pstrcat(cmd->pool,
  365.                    "'ExpiresDefault ", code, "': ", response, NULL);
  366. }
  367.  
  368. static const command_rec expires_cmds[] =
  369. {
  370.     {"ExpiresActive", set_expiresactive, NULL, DIR_CMD_PERMS, FLAG,
  371.      "Limited to 'on' or 'off'"},
  372.     {"ExpiresBytype", set_expiresbytype, NULL, DIR_CMD_PERMS, TAKE2,
  373.      "a MIME type followed by an expiry date code"},
  374.     {"ExpiresDefault", set_expiresdefault, NULL, DIR_CMD_PERMS, TAKE1,
  375.      "an expiry date code"},
  376.     {NULL}
  377. };
  378.  
  379. static void *merge_expires_dir_configs(pool *p, void *basev, void *addv)
  380. {
  381.     expires_dir_config *new = (expires_dir_config *) ap_pcalloc(p, sizeof(expires_dir_config));
  382.     expires_dir_config *base = (expires_dir_config *) basev;
  383.     expires_dir_config *add = (expires_dir_config *) addv;
  384.  
  385.     if (add->active == ACTIVE_DONTCARE) {
  386.         new->active = base->active;
  387.     }
  388.     else {
  389.         new->active = add->active;
  390.     };
  391.  
  392.     if (add->expiresdefault != '\0') {
  393.         new->expiresdefault = add->expiresdefault;
  394.     };
  395.  
  396.     new->expiresbytype = ap_overlay_tables(p, add->expiresbytype,
  397.                                         base->expiresbytype);
  398.     return new;
  399. }
  400.  
  401. static int add_expires(request_rec *r)
  402. {
  403.     expires_dir_config *conf;
  404.     char *code;
  405.     time_t base;
  406.     time_t additional;
  407.     time_t expires;
  408.     char age[20];
  409.  
  410.     if (ap_is_HTTP_ERROR(r->status))       /* Don't add Expires headers to errors */
  411.         return DECLINED;
  412.  
  413.     if (r->main != NULL)        /* Say no to subrequests */
  414.         return DECLINED;
  415.  
  416.     conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config, &expires_module);
  417.     if (conf == NULL) {
  418.         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  419.                     "internal error: %s", r->filename);
  420.         return SERVER_ERROR;
  421.     };
  422.  
  423.     if (conf->active != ACTIVE_ON)
  424.         return DECLINED;
  425.  
  426.     /* we perhaps could use the default_type(r) in its place but that
  427.      * may be 2nd guesing the desired configuration...  calling table_get
  428.      * with a NULL key will SEGV us
  429.      *
  430.      * I still don't know *why* r->content_type would ever be NULL, this
  431.      * is possibly a result of fixups being called in many different
  432.      * places.  Fixups is probably the wrong place to be doing all this
  433.      * work...  Bah.
  434.      *
  435.      * Changed as of 08.Jun.96 don't DECLINE, look for an ExpiresDefault.
  436.      */
  437.     if (r->content_type == NULL)
  438.         code = NULL;
  439.     else
  440.         code = (char *) ap_table_get(conf->expiresbytype, r->content_type);
  441.  
  442.     if (code == NULL) {
  443.         /* no expires defined for that type, is there a default? */
  444.         code = conf->expiresdefault;
  445.  
  446.         if (code[0] == '\0')
  447.             return OK;
  448.     };
  449.  
  450.     /* we have our code */
  451.  
  452.     switch (code[0]) {
  453.     case 'M':
  454.     if (r->finfo.st_mode == 0) { 
  455.         /* file doesn't exist on disk, so we can't do anything based on
  456.          * modification time.  Note that this does _not_ log an error.
  457.          */
  458.         return DECLINED;
  459.     }
  460.         base = r->finfo.st_mtime;
  461.         additional = atoi(&code[1]);
  462.         break;
  463.     case 'A':
  464.         /* there's been some discussion and it's possible that 
  465.          * 'access time' will be stored in request structure
  466.          */
  467.         base = r->request_time;
  468.         additional = atoi(&code[1]);
  469.         break;
  470.     default:
  471.         /* expecting the add_* routines to be case-hardened this 
  472.          * is just a reminder that module is beta
  473.          */
  474.         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  475.                     "internal error: bad expires code: %s", r->filename);
  476.         return SERVER_ERROR;
  477.     };
  478.  
  479.     expires = base + additional;
  480.     ap_snprintf(age, sizeof(age), "max-age=%d", (int) expires - (int) r->request_time);
  481.     ap_table_setn(r->headers_out, "Cache-Control", ap_pstrdup(r->pool, age));
  482.     tzset();                    /* redundant? called implicitly by localtime, at least 
  483.                                  * under FreeBSD
  484.                                  */
  485.     ap_table_setn(r->headers_out, "Expires", ap_gm_timestr_822(r->pool, expires));
  486.     return OK;
  487. }
  488.  
  489. module MODULE_VAR_EXPORT expires_module =
  490. {
  491.     STANDARD_MODULE_STUFF,
  492.     NULL,                       /* initializer */
  493.     create_dir_expires_config,  /* dir config creater */
  494.     merge_expires_dir_configs,  /* dir merger --- default is to override */
  495.     NULL,                       /* server config */
  496.     NULL,                       /* merge server configs */
  497.     expires_cmds,               /* command table */
  498.     NULL,                       /* handlers */
  499.     NULL,                       /* filename translation */
  500.     NULL,                       /* check_user_id */
  501.     NULL,                       /* check auth */
  502.     NULL,                       /* check access */
  503.     NULL,                       /* type_checker */
  504.     add_expires,                /* fixups */
  505.     NULL,                       /* logger */
  506.     NULL,                       /* header parser */
  507.     NULL,                       /* child_init */
  508.     NULL,                       /* child_exit */
  509.     NULL                        /* post read-request */
  510. };
  511.