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&nbsp;MTime&nbsp;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&nbsp;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) {