From fa9915dca8f1a97dd4b77a6f11286e72a2b95074 Mon Sep 17 00:00:00 2001 From: Ken Coar Date: Sat, 5 Jan 2002 17:13:03 +0000 Subject: [PATCH] Replace automatic ETag generation with configurable algorithm defined by FileETag directive. Keywords are All, None, [+|-]INode, [+|-]Size, [+|-]MTime. Server farms with content fanout may want to use 'FileETag MTime Size' to keep the system- variant inode from being included and busting caches. PR: 7010 Submitted by: Based on an idea proposed by Phil Dietz git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x@92738 13f79535-47bb-0310-9956-ffa450edef68 --- src/CHANGES | 8 ++ src/include/http_core.h | 22 ++++++ src/main/http_core.c | 160 ++++++++++++++++++++++++++++++++++++++- src/main/http_protocol.c | 58 ++++++++++++-- 4 files changed, 241 insertions(+), 7 deletions(-) diff --git a/src/CHANGES b/src/CHANGES index 7d91c1ff096..3556cf12e91 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -1,5 +1,13 @@ Changes with Apache 1.3.23 + *) Add FileETag directive to control fields used when constructing + an ETag for a file-based resource. Historically the inode, + size, and mtimehave been used, but the inode factor broke + caching for systems with content fan-out across multiple + back-end servers. Now the fields used in the construction + can be controlled by configuration directives. + [Ken Coar, from a patch by Phil Dietz] + *) NetWare: Fixed the access forbidden problem when requesting an empty directory rather than showing the empty listing. [Charles Goldman, Guenter Knauf ] diff --git a/src/include/http_core.h b/src/include/http_core.h index 1635c9ee759..83586de961d 100644 --- a/src/include/http_core.h +++ b/src/include/http_core.h @@ -180,6 +180,21 @@ extern API_VAR_EXPORT module core_module; typedef unsigned char allow_options_t; typedef unsigned char overrides_t; +/* + * Bits of info that go into making an ETag for a file + * document. Why a long? Because char historically + * proved too short for Options, and int can be different + * sizes on different platforms. + */ +typedef unsigned long etag_components_t; + +#define ETAG_UNSET 0 +#define ETAG_NONE (1 << 0) +#define ETAG_MTIME (1 << 1) +#define ETAG_INODE (1 << 2) +#define ETAG_SIZE (1 << 3) +#define ETAG_BACKWARD (ETAG_MTIME | ETAG_INODE | ETAG_SIZE) +#define ETAG_ALL (ETAG_MTIME | ETAG_INODE | ETAG_SIZE) typedef struct { /* path of the directory/regex/etc. see also d_is_fnmatch below */ @@ -309,6 +324,13 @@ typedef struct { #endif #endif /* CHARSET_EBCDIC */ + /* + * What attributes/data should be included in ETag generation? + */ + etag_components_t etag_bits; + etag_components_t etag_add; + etag_components_t etag_remove; + } core_dir_config; /* Per-server core configuration */ diff --git a/src/main/http_core.c b/src/main/http_core.c index b35880c617b..bd2e3e3eb18 100644 --- a/src/main/http_core.c +++ b/src/main/http_core.c @@ -170,6 +170,13 @@ static void *create_core_dir_config(pool *a, char *dir) #endif #endif /* CHARSET_EBCDIC */ + /* + * Flag for use of inodes in ETags. + */ + conf->etag_bits = ETAG_UNSET; + conf->etag_add = ETAG_UNSET; + conf->etag_remove = ETAG_UNSET; + return (void *)conf; } @@ -194,7 +201,7 @@ static void *merge_core_dir_configs(pool *a, void *basev, void *newv) conf->d_is_fnmatch = new->d_is_fnmatch; conf->d_components = new->d_components; conf->r = new->r; - + if (new->opts & OPT_UNSET) { /* there was no explicit setting of new->opts, so we merge * preserve the invariant (opts_add & opts_remove) == 0 @@ -319,6 +326,26 @@ static void *merge_core_dir_configs(pool *a, void *basev, void *newv) #endif #endif /* CHARSET_EBCDIC */ + /* + * Now merge the setting of the FileETag directive. + */ + if (new->etag_bits == ETAG_UNSET) { + conf->etag_add = + (conf->etag_add & (~ new->etag_remove)) | new->etag_add; + conf->etag_remove = + (conf->opts_remove & (~ new->etag_add)) | new->etag_remove; + conf->etag_bits = + (conf->etag_bits & (~ conf->etag_remove)) | conf->etag_add; + } + else { + conf->etag_bits = new->etag_bits; + conf->etag_add = new->etag_add; + conf->etag_remove = new->etag_remove; + } + if (conf->etag_bits != ETAG_NONE) { + conf->etag_bits &= (~ ETAG_NONE); + } + return (void*)conf; } @@ -2985,6 +3012,135 @@ set_debug_header(cmd_parms *cmd, core_dir_config *m, int arg) #endif #endif /* CHARSET_EBCDIC */ +/* + * Note whether file inodes may be used when forming ETag values. + * It would be nicer to do this as an ITERATE, but then we couldn't + * remember the +/- state properly. + */ +static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, + const char *args_p) +{ + core_dir_config *cfg; + etag_components_t bit; + char action; + char *token; + const char *args; + int valid; + int first; + int explicit; + + cfg = (core_dir_config *) mconfig; + + args = args_p; + first = 1; + explicit = 0; + while (args[0] != '\0') { + action = '*'; + bit = ETAG_UNSET; + valid = 1; + token = ap_getword_conf(cmd->pool, &args); + if ((*token == '+') || (*token == '-')) { + action = *token; + token++; + } + else { + /* + * The occurrence of an absolute setting wipes + * out any previous relative ones. The first such + * occurrence forgets any inherited ones, too. + */ + if (first) { + cfg->etag_bits = ETAG_UNSET; + cfg->etag_add = ETAG_UNSET; + cfg->etag_remove = ETAG_UNSET; + first = 0; + } + } + + if (strcasecmp(token, "None") == 0) { + if (action != '*') { + valid = 0; + } + else { + cfg->etag_bits = bit = ETAG_NONE; + explicit = 1; + } + } + else if (strcasecmp(token, "All") == 0) { + if (action != '*') { + valid = 0; + } + else { + explicit = 1; + cfg->etag_bits = bit = ETAG_ALL; + } + } + else if (strcasecmp(token, "Size") == 0) { + bit = ETAG_SIZE; + } + else if ((strcasecmp(token, "LMTime") == 0) + || (strcasecmp(token, "MTime") == 0) + || (strcasecmp(token, "LastModified") == 0)) { + bit = ETAG_MTIME; + } + else if (strcasecmp(token, "INode") == 0) { + bit = ETAG_INODE; + } + else { + return ap_pstrcat(cmd->pool, "Unknown keyword '", + token, "' for ", cmd->cmd->name, + " directive", NULL); + } + + if (! valid) { + return ap_pstrcat(cmd->pool, cmd->cmd->name, " keyword '", + token, "' cannot be used with '+' or '-'", + NULL); + } + + if (action == '+') { + /* + * Make sure it's in the 'add' list and absent from the + * 'subtract' list. + */ + cfg->etag_add |= bit; + cfg->etag_remove &= (~ bit); + } + else if (action == '-') { + cfg->etag_remove |= bit; + cfg->etag_add &= (~ bit); + } + else { + /* + * Non-relative values wipe out any + or - values + * accumulated so far. + */ + cfg->etag_bits |= bit; + cfg->etag_add = ETAG_UNSET; + cfg->etag_remove = ETAG_UNSET; + explicit = 1; + } + } + + /* + * Any setting at all will clear the 'None' and 'Unset' bits. + */ + + if (cfg->etag_add != ETAG_UNSET) { + cfg->etag_add &= (~ ETAG_UNSET); + } + if (cfg->etag_remove != ETAG_UNSET) { + cfg->etag_remove &= (~ ETAG_UNSET); + } + if (explicit) { + cfg->etag_bits &= (~ ETAG_UNSET); + if ((cfg->etag_bits & ETAG_NONE) != ETAG_NONE) { + cfg->etag_bits &= (~ ETAG_NONE); + } + } + return NULL; +} + /* Note --- ErrorDocument will now work from .htaccess files. * The AllowOverride of Fileinfo allows webmasters to turn it off */ @@ -3276,6 +3432,8 @@ static const command_rec core_cmds[] = { #endif #endif /* CHARSET_EBCDIC */ +{ "FileETag", set_etag_bits, NULL, OR_FILEINFO, RAW_ARGS, + "Specify components used to construct a file's ETag"}, { NULL } }; diff --git a/src/main/http_protocol.c b/src/main/http_protocol.c index 2c31d7852c2..a9cee504a3b 100644 --- a/src/main/http_protocol.c +++ b/src/main/http_protocol.c @@ -649,7 +649,15 @@ API_EXPORT(char *) ap_make_etag(request_rec *r, int force_weak) { char *etag; char *weak; + core_dir_config *cfg; + etag_components_t etag_bits; + cfg = (core_dir_config *)ap_get_module_config(r->per_dir_config, + &core_module); + etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add; + if (etag_bits == ETAG_UNSET) { + etag_bits = ETAG_BACKWARD; + } /* * Make an ETag header out of various pieces of information. We use * the last-modified date and, if we have a real file, the @@ -666,11 +674,43 @@ API_EXPORT(char *) ap_make_etag(request_rec *r, int force_weak) weak = ((r->request_time - r->mtime > 1) && !force_weak) ? "" : "W/"; if (r->finfo.st_mode != 0) { - etag = ap_psprintf(r->pool, - "%s\"%lx-%lx-%lx\"", weak, - (unsigned long) r->finfo.st_ino, - (unsigned long) r->finfo.st_size, - (unsigned long) r->mtime); + char **ent; + array_header *components; + int i; + + /* + * If it's a file (or we wouldn't be here) and no ETags + * should be set for files, return an empty string and + * note it for ap_send_header_field() to ignore. + */ + if (etag_bits & ETAG_NONE) { + ap_table_setn(r->notes, "no-etag", "omit"); + return ""; + } + + components = ap_make_array(r->pool, 4, sizeof(char *)); + if (etag_bits & ETAG_INODE) { + ent = (char **) ap_push_array(components); + *ent = ap_psprintf(r->pool, "%lx", + (unsigned long) r->finfo.st_ino); + } + if (etag_bits & ETAG_SIZE) { + ent = (char **) ap_push_array(components); + *ent = ap_psprintf(r->pool, "%lx", + (unsigned long) r->finfo.st_size); + } + if (etag_bits & ETAG_MTIME) { + ent = (char **) ap_push_array(components); + *ent = ap_psprintf(r->pool, "%lx", (unsigned long) r->mtime); + } + ent = (char **) components->elts; + etag = ap_pstrcat(r->pool, weak, "\"", NULL); + for (i = 0; i < components->nelts; ++i) { + etag = ap_psprintf(r->pool, "%s%s%s", etag, + (i == 0 ? "" : "-"), + ent[i]); + } + etag = ap_pstrcat(r->pool, etag, "\"", NULL); } else { etag = ap_psprintf(r->pool, "%s\"%lx\"", weak, @@ -1458,8 +1498,14 @@ API_EXPORT(int) ap_index_of_response(int status) * It returns true unless there was a write error of some kind. */ API_EXPORT_NONSTD(int) ap_send_header_field(request_rec *r, - const char *fieldname, const char *fieldval) + const char *fieldname, + const char *fieldval) { + if (strcasecmp(fieldname, "ETag") == 0) { + if (ap_table_get(r->notes, "no-etag") != NULL) { + return 1; + } + } return (0 < ap_rvputs(r, fieldname, ": ", fieldval, CRLF, NULL)); } -- 2.47.2