You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by mi...@apache.org on 2020/07/06 12:27:58 UTC
svn commit: r1879549 - /httpd/httpd/patches/2.4.x/httpd-etag2.patch
Author: minfrin
Date: Mon Jul 6 12:27:58 2020
New Revision: 1879549
URL: http://svn.apache.org/viewvc?rev=1879549&view=rev
Log:
Add logno to patch, add back CHANGES and ap_mmn.h.
Added:
httpd/httpd/patches/2.4.x/httpd-etag2.patch
Added: httpd/httpd/patches/2.4.x/httpd-etag2.patch
URL: http://svn.apache.org/viewvc/httpd/httpd/patches/2.4.x/httpd-etag2.patch?rev=1879549&view=auto
==============================================================================
--- httpd/httpd/patches/2.4.x/httpd-etag2.patch (added)
+++ httpd/httpd/patches/2.4.x/httpd-etag2.patch Mon Jul 6 12:27:58 2020
@@ -0,0 +1,803 @@
+Index: CHANGES
+===================================================================
+--- CHANGES (revision 1879541)
++++ CHANGES (working copy)
+@@ -1,6 +1,19 @@
+ -*- coding: utf-8 -*-
+ Changes with Apache 2.4.44
+
++ *) "[mod_dav_fs etag handling] should really honor the FileETag setting".
++ - It now does.
++ - Add "Digest" to FileETag directive, allowing a strong ETag to be
++ generated using a file digest.
++ - Add ap_make_etag_ex() and ap_set_etag_fd() to allow full control over
++ ETag generation.
++ - Add concept of "binary notes" to request_rec, allowing packed bit flags
++ to be added to a request.
++ - First binary note - AP_REQUEST_STRONG_ETAG - allows modules to force
++ the ETag to a strong ETag to comply with RFC requirements, such as those
++ mandated by various WebDAV extensions.
++ [Graham Leggett]
++
+ *) mod_proxy_fcgi: ProxyFCGISetEnvIf unsets variables when expression
+ evaluates to false. PR64365. [Michael König <mail ikoenig.net>]
+
+Index: docs/manual/mod/core.xml
+===================================================================
+--- docs/manual/mod/core.xml (revision 1879541)
++++ docs/manual/mod/core.xml (working copy)
+@@ -1865,15 +1865,18 @@
+ <highlight language="config">
+ FileETag INode MTime Size
+ </highlight></dd>
++ <dt><strong>Digest</strong></dt>
++ <dd>If a document is file-based, the <code>ETag</code> field will be
++ calculated by taking the digest over the file.</dd>
+ <dt><strong>None</strong></dt>
+ <dd>If a document is file-based, no <code>ETag</code> field will be
+ included in the response</dd>
+ </dl>
+
+- <p>The <code>INode</code>, <code>MTime</code>, and <code>Size</code>
+- keywords may be prefixed with either <code>+</code> or <code>-</code>,
+- which allow changes to be made to the default setting inherited
+- from a broader scope. Any keyword appearing without such a prefix
++ <p>The <code>INode</code>, <code>MTime</code>, <code>Size</code> and
++ <code>Digest</code> keywords may be prefixed with either <code>+</code>
++ or <code>-</code>, which allow changes to be made to the default setting
++ inherited from a broader scope. Any keyword appearing without such a prefix
+ immediately and completely cancels the inherited setting.</p>
+
+ <p>If a directory's configuration includes
+@@ -1882,18 +1885,10 @@
+ the setting for that subdirectory (which will be inherited by
+ any sub-subdirectories that don't override it) will be equivalent to
+ <code>FileETag MTime Size</code>.</p>
+- <note type="warning"><title>Warning</title>
+- Do not change the default for directories or locations that have WebDAV
+- enabled and use <module>mod_dav_fs</module> as a storage provider.
+- <module>mod_dav_fs</module> uses <code>MTime Size</code>
+- as a fixed format for <code>ETag</code> comparisons on conditional requests.
+- These conditional requests will break if the <code>ETag</code> format is
+- changed via <directive>FileETag</directive>.
+- </note>
+ <note><title>Server Side Includes</title>
+ An ETag is not generated for responses parsed by <module>mod_include</module>
+- since the response entity can change without a change of the INode, MTime, or Size
+- of the static file with embedded SSI directives.
++ since the response entity can change without a change of the INode, MTime,
++ Size or Digest of the static file with embedded SSI directives.
+ </note>
+
+ </usage>
+Index: include/ap_mmn.h
+===================================================================
+--- include/ap_mmn.h (revision 1879541)
++++ include/ap_mmn.h (working copy)
+@@ -534,6 +534,12 @@
+ * 20120211.90 (2.4.42-dev) AP_REG_DEFAULT macro in ap_regex.h
+ * 20120211.91 (2.4.42-dev) Add ap_is_chunked() in httpd.h
+ * 20120211.92 (2.4.42-dev) AP_REG_NO_DEFAULT macro in ap_regex.h
++ * 20120211.93 (2.4.44-dev) ETAG_DIGEST in http_core.h. struct etag_rec,
++ * ap_make_etag_ex() and ap_set_etag_fd() in
++ * http_protocol.h. ap_request_bnotes_t,
++ * AP_REQUEST_STRONG_ETAG, AP_REQUEST_GET_BNOTE,
++ * AP_REQUEST_SET_BNOTE and AP_REQUEST_IS_STRONG_ETAG
++ * in httpd.h.
+ */
+
+ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
+@@ -541,7 +547,7 @@
+ #ifndef MODULE_MAGIC_NUMBER_MAJOR
+ #define MODULE_MAGIC_NUMBER_MAJOR 20120211
+ #endif
+-#define MODULE_MAGIC_NUMBER_MINOR 92 /* 0...n */
++#define MODULE_MAGIC_NUMBER_MINOR 93 /* 0...n */
+
+ /**
+ * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
+Index: include/http_core.h
+===================================================================
+--- include/http_core.h (revision 1879541)
++++ include/http_core.h (working copy)
+@@ -482,12 +482,13 @@
+ */
+ 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_ALL (ETAG_MTIME | ETAG_INODE | ETAG_SIZE)
++#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_DIGEST (1 << 4)
++#define ETAG_ALL (ETAG_MTIME | ETAG_INODE | ETAG_SIZE)
+ /* This is the default value used */
+ #define ETAG_BACKWARD (ETAG_MTIME | ETAG_SIZE)
+
+Index: include/http_protocol.h
+===================================================================
+--- include/http_protocol.h (revision 1879541)
++++ include/http_protocol.h (working copy)
+@@ -145,7 +145,28 @@
+ */
+ AP_DECLARE(void) ap_setup_make_content_type(apr_pool_t *pool);
+
++/** A structure with the ingredients for a file based etag */
++typedef struct etag_rec etag_rec;
++
+ /**
++ * @brief A structure with the ingredients for a file based etag
++ */
++struct etag_rec {
++ /** Optional vary list validator */
++ const char *vlist_validator;
++ /** Time when the request started */
++ apr_time_t request_time;
++ /** finfo.protection (st_mode) set to zero if no such file */
++ apr_finfo_t *finfo;
++ /** File pathname used when generating a digest */
++ const char *pathname;
++ /** File descriptor used when generating a digest */
++ apr_file_t *fd;
++ /** Force a non-digest etag to be weak */
++ int force_weak;
++};
++
++/**
+ * Construct an entity tag from the resource information. If it's a real
+ * file, build in some of the file characteristics.
+ * @param r The current request
+@@ -156,6 +177,14 @@
+ AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak);
+
+ /**
++ * Construct an entity tag from information provided in the etag_rec
++ * structure.
++ * @param r The current request
++ * @param er The etag record, containing ingredients for the etag.
++ */
++AP_DECLARE(char *) ap_make_etag_ex(request_rec *r, etag_rec *er);
++
++/**
+ * Set the E-tag outgoing header
+ * @param r The current request
+ */
+@@ -162,6 +191,13 @@
+ AP_DECLARE(void) ap_set_etag(request_rec *r);
+
+ /**
++ * Set the E-tag outgoing header, with the option of forcing a strong ETag.
++ * @param r The current request
++ * @param fd The file descriptor
++ */
++AP_DECLARE(void) ap_set_etag_fd(request_rec *r, apr_file_t *fd);
++
++/**
+ * Set the last modified time for the file being sent
+ * @param r The current request
+ */
+@@ -733,7 +769,7 @@
+ AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r))
+
+
+-#define AP_PROTOCOL_HTTP1 "http/1.1"
++#define AP_PROTOCOL_HTTP1 "http/1.1"
+
+ /**
+ * Determine the list of protocols available for a connection/request. This may
+@@ -1014,6 +1050,7 @@
+ */
+ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers);
+
++
+ #ifdef __cplusplus
+ }
+ #endif
+Index: include/httpd.h
+===================================================================
+--- include/httpd.h (revision 1879541)
++++ include/httpd.h (working copy)
+@@ -645,8 +645,51 @@
+ /** the array used for extension methods */
+ apr_array_header_t *method_list;
+ };
++/** @} */
+
+ /**
++ * @defgroup bnotes Binary notes recognized by the server
++ * @ingroup APACHE_CORE_DAEMON
++ * @{
++ *
++ * @brief Binary notes recognized by the server.
++ */
++
++/**
++ * The type used for request binary notes.
++ */
++typedef apr_uint64_t ap_request_bnotes_t;
++
++/**
++ * These constants represent bitmasks for notes associated with this
++ * request. There are space for 64 bits in the apr_uint64_t.
++ *
++ */
++#define AP_REQUEST_STRONG_ETAG 1 >> 0
++
++/**
++ * This is a convenience macro to ease with getting specific request
++ * binary notes.
++ */
++#define AP_REQUEST_GET_BNOTE(r, mask) \
++ ((mask) & ((r)->bnotes))
++
++/**
++ * This is a convenience macro to ease with setting specific request
++ * binary notes.
++ */
++#define AP_REQUEST_SET_BNOTE(r, mask, val) \
++ (r)->bnotes = (((r)->bnotes & ~(mask)) | (val))
++
++/**
++ * Returns true if the strong etag flag is set for this request.
++ */
++#define AP_REQUEST_IS_STRONG_ETAG(r) \
++ AP_REQUEST_GET_BNOTE((r), AP_REQUEST_STRONG_ETAG)
++/** @} */
++
++
++/**
+ * @defgroup module_magic Module Magic mime types
+ * @{
+ */
+@@ -1062,6 +1105,11 @@
+ * 1 yes/success
+ */
+ int double_reverse;
++ /** Request flags associated with this request. Use
++ * AP_REQUEST_GET_FLAGS() and AP_REQUEST_SET_FLAGS() to access
++ * the elements of this field.
++ */
++ ap_request_bnotes_t bnotes;
+ };
+
+ /**
+Index: modules/dav/fs/repos.c
+===================================================================
+--- modules/dav/fs/repos.c (revision 1879541)
++++ modules/dav/fs/repos.c (working copy)
+@@ -1853,27 +1853,26 @@
+ return dav_fs_internal_walk(params, depth, 0, NULL, response);
+ }
+
+-/* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
+- * for file path.
+- * ### do we need to return weak tags sometimes?
++/* dav_fs_etag: Creates an etag for the file path.
+ */
+ static const char *dav_fs_getetag(const dav_resource *resource)
+ {
++ etag_rec er;
++
+ dav_resource_private *ctx = resource->info;
+- /* XXX: This should really honor the FileETag setting */
+
+- if (!resource->exists)
+- return apr_pstrdup(ctx->pool, "");
+-
+- if (ctx->finfo.filetype != APR_NOFILE) {
+- return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%"
+- APR_UINT64_T_HEX_FMT "\"",
+- (apr_uint64_t) ctx->finfo.size,
+- (apr_uint64_t) ctx->finfo.mtime);
++ if (!resource->exists || !ctx->r) {
++ return "";
+ }
+
+- return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"",
+- (apr_uint64_t) ctx->finfo.mtime);
++ er.vlist_validator = NULL;
++ er.request_time = ctx->r->request_time;
++ er.finfo = &ctx->finfo;
++ er.pathname = ctx->pathname;
++ er.fd = NULL;
++ er.force_weak = 0;
++
++ return ap_make_etag_ex(ctx->r, &er);
+ }
+
+ static const dav_hooks_repository dav_hooks_repository_fs =
+Index: modules/http/http_etag.c
+===================================================================
+--- modules/http/http_etag.c (revision 1879541)
++++ modules/http/http_etag.c (working copy)
+@@ -16,6 +16,9 @@
+
+ #include "apr_strings.h"
+ #include "apr_thread_proc.h" /* for RLIMIT stuff */
++#include "apr_sha1.h"
++#include "apr_base64.h"
++#include "apr_buckets.h"
+
+ #define APR_WANT_STRFUNC
+ #include "apr_want.h"
+@@ -24,9 +27,16 @@
+ #include "http_config.h"
+ #include "http_connection.h"
+ #include "http_core.h"
++#include "http_log.h"
+ #include "http_protocol.h" /* For index_of_response(). Grump. */
+ #include "http_request.h"
+
++#if APR_HAS_MMAP
++#include "apr_mmap.h"
++#endif /* APR_HAS_MMAP */
++
++#define SHA1_DIGEST_BASE64_LEN 4*(APR_SHA1_DIGESTSIZE/3)
++
+ /* Generate the human-readable hex representation of an apr_uint64_t
+ * (basically a faster version of 'sprintf("%llx")')
+ */
+@@ -53,19 +63,159 @@
+
+ #define ETAG_WEAK "W/"
+ #define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2)
++
++static void etag_start(char *etag, const char *weak, char **next)
++{
++ if (weak) {
++ while (*weak) {
++ *etag++ = *weak++;
++ }
++ }
++ *etag++ = '"';
++
++ *next = etag;
++}
++
++static void etag_end(char *next, const char *vlv, apr_size_t vlv_len)
++{
++ if (vlv) {
++ *next++ = ';';
++ apr_cpystrn(next, vlv, vlv_len);
++ }
++ else {
++ *next++ = '"';
++ *next = '\0';
++ }
++}
++
+ /*
++ * Construct a strong ETag by creating a SHA1 hash across the file content.
++ */
++static char *make_digest_etag(request_rec *r, etag_rec *er, char *vlv,
++ apr_size_t vlv_len, char *weak, apr_size_t weak_len)
++{
++ apr_sha1_ctx_t context;
++ unsigned char digest[APR_SHA1_DIGESTSIZE];
++ apr_file_t *fd = NULL;
++ core_dir_config *cfg;
++ char *etag, *next;
++ apr_bucket_brigade *bb;
++ apr_bucket *e;
++
++ apr_size_t nbytes;
++ apr_off_t offset = 0, zero = 0, len = 0;
++ apr_status_t status;
++
++ cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
++
++ if (er->fd) {
++ fd = er->fd;
++ }
++ else if (er->pathname) {
++ if ((status = apr_file_open(&fd, er->pathname, APR_READ | APR_BINARY,
++ 0, r->pool)) != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10251)
++ "Make etag: could not open %s", er->pathname);
++ return "";
++ }
++ }
++ if (!fd) {
++ return "";
++ }
++
++ if ((status = apr_file_seek(fd, APR_CUR, &offset)) != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10252)
++ "Make etag: could not seek");
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++ return "";
++ }
++
++ if ((status = apr_file_seek(fd, APR_END, &len)) != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO()
++ "Make etag: could not seek");
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++ return "";
++ }
++
++ if ((status = apr_file_seek(fd, APR_SET, &zero)) != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10253)
++ "Make etag: could not seek");
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++ return "";
++ }
++
++ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
++
++ e = apr_brigade_insert_file(bb, fd, 0, len, r->pool);
++
++#if APR_HAS_MMAP
++ if (cfg->enable_mmap == ENABLE_MMAP_OFF) {
++ (void)apr_bucket_file_enable_mmap(e, 0);
++ }
++#endif
++
++ apr_sha1_init(&context);
++ while (!APR_BRIGADE_EMPTY(bb))
++ {
++ const char *str;
++
++ e = APR_BRIGADE_FIRST(bb);
++
++ if ((status = apr_bucket_read(e, &str, &nbytes, APR_BLOCK_READ)) != APR_SUCCESS) {
++ apr_brigade_destroy(bb);
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10254)
++ "Make etag: could not read");
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++ return "";
++ }
++
++ apr_sha1_update(&context, str, nbytes);
++ apr_bucket_delete(e);
++ }
++
++ if ((status = apr_file_seek(fd, APR_SET, &offset)) != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10255)
++ "Make etag: could not seek");
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++ return "";
++ }
++ apr_sha1_final(digest, &context);
++
++ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
++ SHA1_DIGEST_BASE64_LEN + vlv_len + 4);
++
++ etag_start(etag, weak, &next);
++ next += apr_base64_encode_binary(next, digest, APR_SHA1_DIGESTSIZE) - 1;
++ etag_end(next, vlv, vlv_len);
++
++ if (er->pathname) {
++ apr_file_close(fd);
++ }
++
++ return etag;
++}
++
++/*
+ * Construct an entity tag (ETag) from resource information. If it's a real
+ * file, build in some of the file characteristics. If the modification time
+ * is newer than (request-time minus 1 second), mark the ETag as weak - it
+- * could be modified again in as short an interval. We rationalize the
+- * modification time we're given to keep it from being in the future.
++ * could be modified again in as short an interval.
+ */
+-AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
++AP_DECLARE(char *) ap_make_etag_ex(request_rec *r, etag_rec *er)
+ {
+- char *weak;
+- apr_size_t weak_len;
+- char *etag;
+- char *next;
++ char *weak = NULL;
++ apr_size_t weak_len = 0, vlv_len = 0;
++ char *etag, *next, *vlv;
+ core_dir_config *cfg;
+ etag_components_t etag_bits;
+ etag_components_t bits_added;
+@@ -73,13 +223,62 @@
+ cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+ etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
+
++ if (er->force_weak) {
++ weak = ETAG_WEAK;
++ weak_len = sizeof(ETAG_WEAK);
++ }
++
++ if (r->vlist_validator) {
++
++ /* If we have a variant list validator (vlv) due to the
++ * response being negotiated, then we create a structured
++ * entity tag which merges the variant etag with the variant
++ * list validator (vlv). This merging makes revalidation
++ * somewhat safer, ensures that caches which can deal with
++ * Vary will (eventually) be updated if the set of variants is
++ * changed, and is also a protocol requirement for transparent
++ * content negotiation.
++ */
++
++ /* if the variant list validator is weak, we make the whole
++ * structured etag weak. If we would not, then clients could
++ * have problems merging range responses if we have different
++ * variants with the same non-globally-unique strong etag.
++ */
++
++ vlv = r->vlist_validator;
++ if (vlv[0] == 'W') {
++ vlv += 3;
++ weak = ETAG_WEAK;
++ weak_len = sizeof(ETAG_WEAK);
++ }
++ else {
++ vlv++;
++ }
++ vlv_len = strlen(vlv);
++
++ }
++ else {
++ vlv = NULL;
++ vlv_len = 0;
++ }
++
+ /*
++ * Did a module flag the need for a strong etag, or did the
++ * configuration tell us to generate a digest?
++ */
++ if (er->finfo->filetype == APR_REG &&
++ (AP_REQUEST_IS_STRONG_ETAG(r) || (etag_bits & ETAG_DIGEST))) {
++
++ return make_digest_etag(r, er, vlv, vlv_len, weak, weak_len);
++ }
++
++ /*
+ * 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 the header-sender to ignore.
+ */
+ if (etag_bits & ETAG_NONE) {
+- apr_table_setn(r->notes, "no-etag", "omit");
+ return "";
+ }
+
+@@ -98,33 +297,24 @@
+ * be modified again later in the second, and the validation
+ * would be incorrect.
+ */
+- if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
+- !force_weak) {
+- weak = NULL;
+- weak_len = 0;
+- }
+- else {
++ if ((er->request_time - er->finfo->mtime < (1 * APR_USEC_PER_SEC))) {
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+
+- if (r->finfo.filetype != APR_NOFILE) {
++ if (er->finfo->filetype != APR_NOFILE) {
+ /*
+ * ETag gets set to [W/]"inode-size-mtime", modulo any
+ * FileETag keywords.
+ */
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
+- 3 * CHARS_PER_UINT64 + 1);
+- next = etag;
+- if (weak) {
+- while (*weak) {
+- *next++ = *weak++;
+- }
+- }
+- *next++ = '"';
++ 3 * CHARS_PER_UINT64 + vlv_len + 2);
++
++ etag_start(etag, weak, &next);
++
+ bits_added = 0;
+ if (etag_bits & ETAG_INODE) {
+- next = etag_uint64_to_hex(next, r->finfo.inode);
++ next = etag_uint64_to_hex(next, er->finfo->inode);
+ bits_added |= ETAG_INODE;
+ }
+ if (etag_bits & ETAG_SIZE) {
+@@ -131,7 +321,7 @@
+ if (bits_added != 0) {
+ *next++ = '-';
+ }
+- next = etag_uint64_to_hex(next, r->finfo.size);
++ next = etag_uint64_to_hex(next, er->finfo->size);
+ bits_added |= ETAG_SIZE;
+ }
+ if (etag_bits & ETAG_MTIME) {
+@@ -138,10 +328,11 @@
+ if (bits_added != 0) {
+ *next++ = '-';
+ }
+- next = etag_uint64_to_hex(next, r->mtime);
++ next = etag_uint64_to_hex(next, er->finfo->mtime);
+ }
+- *next++ = '"';
+- *next = '\0';
++
++ etag_end(next, vlv, vlv_len);
++
+ }
+ else {
+ /*
+@@ -148,73 +339,75 @@
+ * Not a file document, so just use the mtime: [W/]"mtime"
+ */
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
+- CHARS_PER_UINT64 + 1);
+- next = etag;
+- if (weak) {
+- while (*weak) {
+- *next++ = *weak++;
+- }
+- }
+- *next++ = '"';
+- next = etag_uint64_to_hex(next, r->mtime);
+- *next++ = '"';
+- *next = '\0';
++ CHARS_PER_UINT64 + vlv_len + 2);
++
++ etag_start(etag, weak, &next);
++ next = etag_uint64_to_hex(next, er->finfo->mtime);
++ etag_end(next, vlv, vlv_len);
++
+ }
+
+ return etag;
+ }
+
++AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
++{
++ etag_rec er;
++
++ er.vlist_validator = NULL;
++ er.request_time = r->request_time;
++ er.finfo = &r->finfo;
++ er.pathname = r->filename;
++ er.fd = NULL;
++ er.force_weak = force_weak;
++
++ return ap_make_etag_ex(r, &er);
++}
++
+ AP_DECLARE(void) ap_set_etag(request_rec *r)
+ {
+ char *etag;
+- char *variant_etag, *vlv;
+- int vlv_weak;
+
+- if (!r->vlist_validator) {
+- etag = ap_make_etag(r, 0);
++ etag_rec er;
+
+- /* If we get a blank etag back, don't set the header. */
+- if (!etag[0]) {
+- return;
+- }
++ er.vlist_validator = r->vlist_validator;
++ er.request_time = r->request_time;
++ er.finfo = &r->finfo;
++ er.pathname = r->filename;
++ er.fd = NULL;
++ er.force_weak = 0;
++
++ etag = ap_make_etag_ex(r, &er);
++
++ if (etag && etag[0]) {
++ apr_table_setn(r->headers_out, "ETag", etag);
+ }
+ else {
+- /* If we have a variant list validator (vlv) due to the
+- * response being negotiated, then we create a structured
+- * entity tag which merges the variant etag with the variant
+- * list validator (vlv). This merging makes revalidation
+- * somewhat safer, ensures that caches which can deal with
+- * Vary will (eventually) be updated if the set of variants is
+- * changed, and is also a protocol requirement for transparent
+- * content negotiation.
+- */
++ apr_table_setn(r->notes, "no-etag", "omit");
++ }
+
+- /* if the variant list validator is weak, we make the whole
+- * structured etag weak. If we would not, then clients could
+- * have problems merging range responses if we have different
+- * variants with the same non-globally-unique strong etag.
+- */
++}
+
+- vlv = r->vlist_validator;
+- vlv_weak = (vlv[0] == 'W');
++AP_DECLARE(void) ap_set_etag_fd(request_rec *r, apr_file_t *fd)
++{
++ char *etag;
+
+- variant_etag = ap_make_etag(r, vlv_weak);
++ etag_rec er;
+
+- /* If we get a blank etag back, don't append vlv and stop now. */
+- if (!variant_etag[0]) {
+- return;
+- }
++ er.vlist_validator = r->vlist_validator;
++ er.request_time = r->request_time;
++ er.finfo = &r->finfo;
++ er.pathname = NULL;
++ er.fd = fd;
++ er.force_weak = 0;
+
+- /* merge variant_etag and vlv into a structured etag */
+- variant_etag[strlen(variant_etag) - 1] = '\0';
+- if (vlv_weak) {
+- vlv += 3;
+- }
+- else {
+- vlv++;
+- }
+- etag = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
++ etag = ap_make_etag_ex(r, &er);
++
++ if (etag && etag[0]) {
++ apr_table_setn(r->headers_out, "ETag", etag);
+ }
++ else {
++ apr_table_setn(r->notes, "no-etag", "omit");
++ }
+
+- apr_table_setn(r->headers_out, "ETag", etag);
+ }
+Index: modules/test/mod_dialup.c
+===================================================================
+--- modules/test/mod_dialup.c (revision 1879541)
++++ modules/test/mod_dialup.c (working copy)
+@@ -171,7 +171,7 @@
+ /* copied from default handler: */
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+- ap_set_etag(r);
++ ap_set_etag_fd(r, fd);
+ ap_set_accept_ranges(r);
+ ap_set_content_length(r, r->finfo.size);
+
+Index: server/core.c
+===================================================================
+--- server/core.c (revision 1879541)
++++ server/core.c (working copy)
+@@ -2128,6 +2128,9 @@
+ else if (strcasecmp(token, "INode") == 0) {
+ bit = ETAG_INODE;
+ }
++ else if (ap_cstr_casecmp(token, "Digest") == 0) {
++ bit = ETAG_DIGEST;
++ }
+ else {
+ return apr_pstrcat(cmd->pool, "Unknown keyword '",
+ token, "' for ", cmd->cmd->name,
+@@ -4813,7 +4816,7 @@
+
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+- ap_set_etag(r);
++ ap_set_etag_fd(r, fd);
+ ap_set_accept_ranges(r);
+ ap_set_content_length(r, r->finfo.size);
+ if (bld_content_md5) {