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 <gk@gknw.de>]
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 */
#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 */
#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;
}
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
#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;
}
#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
*/
#endif
#endif /* CHARSET_EBCDIC */
+{ "FileETag", set_etag_bits, NULL, OR_FILEINFO, RAW_ARGS,
+ "Specify components used to construct a file's ETag"},
{ NULL }
};
{
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
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,
* 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));
}