Blame modules/http/http_etag.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
#include "apr_strings.h"
Packit 90a5c9
#include "apr_thread_proc.h"    /* for RLIMIT stuff */
Packit 90a5c9
Packit 90a5c9
#define APR_WANT_STRFUNC
Packit 90a5c9
#include "apr_want.h"
Packit 90a5c9
Packit 90a5c9
#include "httpd.h"
Packit 90a5c9
#include "http_config.h"
Packit 90a5c9
#include "http_connection.h"
Packit 90a5c9
#include "http_core.h"
Packit 90a5c9
#include "http_protocol.h"   /* For index_of_response().  Grump. */
Packit 90a5c9
#include "http_request.h"
Packit 90a5c9
Packit 90a5c9
/* Generate the human-readable hex representation of an apr_uint64_t
Packit 90a5c9
 * (basically a faster version of 'sprintf("%llx")')
Packit 90a5c9
 */
Packit 90a5c9
#define HEX_DIGITS "0123456789abcdef"
Packit 90a5c9
static char *etag_uint64_to_hex(char *next, apr_uint64_t u)
Packit 90a5c9
{
Packit 90a5c9
    int printing = 0;
Packit 90a5c9
    int shift = sizeof(apr_uint64_t) * 8 - 4;
Packit 90a5c9
    do {
Packit 90a5c9
        unsigned short next_digit = (unsigned short)
Packit 90a5c9
                                    ((u >> shift) & (apr_uint64_t)0xf);
Packit 90a5c9
        if (next_digit) {
Packit 90a5c9
            *next++ = HEX_DIGITS[next_digit];
Packit 90a5c9
            printing = 1;
Packit 90a5c9
        }
Packit 90a5c9
        else if (printing) {
Packit 90a5c9
            *next++ = HEX_DIGITS[next_digit];
Packit 90a5c9
        }
Packit 90a5c9
        shift -= 4;
Packit 90a5c9
    } while (shift);
Packit 90a5c9
    *next++ = HEX_DIGITS[u & (apr_uint64_t)0xf];
Packit 90a5c9
    return next;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
#define ETAG_WEAK "W/"
Packit 90a5c9
#define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2)
Packit 90a5c9
/*
Packit 90a5c9
 * Construct an entity tag (ETag) from resource information.  If it's a real
Packit 90a5c9
 * file, build in some of the file characteristics.  If the modification time
Packit 90a5c9
 * is newer than (request-time minus 1 second), mark the ETag as weak - it
Packit 90a5c9
 * could be modified again in as short an interval.  We rationalize the
Packit 90a5c9
 * modification time we're given to keep it from being in the future.
Packit 90a5c9
 */
Packit 90a5c9
AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
Packit 90a5c9
{
Packit 90a5c9
    char *weak;
Packit 90a5c9
    apr_size_t weak_len;
Packit 90a5c9
    char *etag;
Packit 90a5c9
    char *next;
Packit 90a5c9
    core_dir_config *cfg;
Packit 90a5c9
    etag_components_t etag_bits;
Packit 90a5c9
    etag_components_t bits_added;
Packit 90a5c9
Packit 90a5c9
    cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
Packit 90a5c9
    etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * If it's a file (or we wouldn't be here) and no ETags
Packit 90a5c9
     * should be set for files, return an empty string and
Packit 90a5c9
     * note it for the header-sender to ignore.
Packit 90a5c9
     */
Packit 90a5c9
    if (etag_bits & ETAG_NONE) {
Packit 90a5c9
        apr_table_setn(r->notes, "no-etag", "omit");
Packit 90a5c9
        return "";
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (etag_bits == ETAG_UNSET) {
Packit 90a5c9
        etag_bits = ETAG_BACKWARD;
Packit 90a5c9
    }
Packit 90a5c9
    /*
Packit 90a5c9
     * Make an ETag header out of various pieces of information. We use
Packit 90a5c9
     * the last-modified date and, if we have a real file, the
Packit 90a5c9
     * length and inode number - note that this doesn't have to match
Packit 90a5c9
     * the content-length (i.e. includes), it just has to be unique
Packit 90a5c9
     * for the file.
Packit 90a5c9
     *
Packit 90a5c9
     * If the request was made within a second of the last-modified date,
Packit 90a5c9
     * we send a weak tag instead of a strong one, since it could
Packit 90a5c9
     * be modified again later in the second, and the validation
Packit 90a5c9
     * would be incorrect.
Packit 90a5c9
     */
Packit 90a5c9
    if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
Packit 90a5c9
        !force_weak) {
Packit 90a5c9
        weak = NULL;
Packit 90a5c9
        weak_len = 0;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        weak = ETAG_WEAK;
Packit 90a5c9
        weak_len = sizeof(ETAG_WEAK);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (r->finfo.filetype != APR_NOFILE) {
Packit 90a5c9
        /*
Packit 90a5c9
         * ETag gets set to [W/]"inode-size-mtime", modulo any
Packit 90a5c9
         * FileETag keywords.
Packit 90a5c9
         */
Packit 90a5c9
        etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
Packit 90a5c9
                          3 * CHARS_PER_UINT64 + 1);
Packit 90a5c9
        next = etag;
Packit 90a5c9
        if (weak) {
Packit 90a5c9
            while (*weak) {
Packit 90a5c9
                *next++ = *weak++;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        *next++ = '"';
Packit 90a5c9
        bits_added = 0;
Packit 90a5c9
        if (etag_bits & ETAG_INODE) {
Packit 90a5c9
            next = etag_uint64_to_hex(next, r->finfo.inode);
Packit 90a5c9
            bits_added |= ETAG_INODE;
Packit 90a5c9
        }
Packit 90a5c9
        if (etag_bits & ETAG_SIZE) {
Packit 90a5c9
            if (bits_added != 0) {
Packit 90a5c9
                *next++ = '-';
Packit 90a5c9
            }
Packit 90a5c9
            next = etag_uint64_to_hex(next, r->finfo.size);
Packit 90a5c9
            bits_added |= ETAG_SIZE;
Packit 90a5c9
        }
Packit 90a5c9
        if (etag_bits & ETAG_MTIME) {
Packit 90a5c9
            if (bits_added != 0) {
Packit 90a5c9
                *next++ = '-';
Packit 90a5c9
            }
Packit 90a5c9
            next = etag_uint64_to_hex(next, r->mtime);
Packit 90a5c9
        }
Packit 90a5c9
        *next++ = '"';
Packit 90a5c9
        *next = '\0';
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /*
Packit 90a5c9
         * Not a file document, so just use the mtime: [W/]"mtime"
Packit 90a5c9
         */
Packit 90a5c9
        etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
Packit 90a5c9
                          CHARS_PER_UINT64 + 1);
Packit 90a5c9
        next = etag;
Packit 90a5c9
        if (weak) {
Packit 90a5c9
            while (*weak) {
Packit 90a5c9
                *next++ = *weak++;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        *next++ = '"';
Packit 90a5c9
        next = etag_uint64_to_hex(next, r->mtime);
Packit 90a5c9
        *next++ = '"';
Packit 90a5c9
        *next = '\0';
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return etag;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
AP_DECLARE(void) ap_set_etag(request_rec *r)
Packit 90a5c9
{
Packit 90a5c9
    char *etag;
Packit 90a5c9
    char *variant_etag, *vlv;
Packit 90a5c9
    int vlv_weak;
Packit 90a5c9
Packit 90a5c9
    if (!r->vlist_validator) {
Packit 90a5c9
        etag = ap_make_etag(r, 0);
Packit 90a5c9
Packit 90a5c9
        /* If we get a blank etag back, don't set the header. */
Packit 90a5c9
        if (!etag[0]) {
Packit 90a5c9
            return;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /* If we have a variant list validator (vlv) due to the
Packit 90a5c9
         * response being negotiated, then we create a structured
Packit 90a5c9
         * entity tag which merges the variant etag with the variant
Packit 90a5c9
         * list validator (vlv).  This merging makes revalidation
Packit 90a5c9
         * somewhat safer, ensures that caches which can deal with
Packit 90a5c9
         * Vary will (eventually) be updated if the set of variants is
Packit 90a5c9
         * changed, and is also a protocol requirement for transparent
Packit 90a5c9
         * content negotiation.
Packit 90a5c9
         */
Packit 90a5c9
Packit 90a5c9
        /* if the variant list validator is weak, we make the whole
Packit 90a5c9
         * structured etag weak.  If we would not, then clients could
Packit 90a5c9
         * have problems merging range responses if we have different
Packit 90a5c9
         * variants with the same non-globally-unique strong etag.
Packit 90a5c9
         */
Packit 90a5c9
Packit 90a5c9
        vlv = r->vlist_validator;
Packit 90a5c9
        vlv_weak = (vlv[0] == 'W');
Packit 90a5c9
Packit 90a5c9
        variant_etag = ap_make_etag(r, vlv_weak);
Packit 90a5c9
Packit 90a5c9
        /* If we get a blank etag back, don't append vlv and stop now. */
Packit 90a5c9
        if (!variant_etag[0]) {
Packit 90a5c9
            return;
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* merge variant_etag and vlv into a structured etag */
Packit 90a5c9
        variant_etag[strlen(variant_etag) - 1] = '\0';
Packit 90a5c9
        if (vlv_weak) {
Packit 90a5c9
            vlv += 3;
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            vlv++;
Packit 90a5c9
        }
Packit 90a5c9
        etag = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    apr_table_setn(r->headers_out, "ETag", etag);
Packit 90a5c9
}