1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr_strings.h"
18#include "apr_thread_proc.h"    /* for RLIMIT stuff */
19
20#define APR_WANT_STRFUNC
21#include "apr_want.h"
22
23#define CORE_PRIVATE
24#include "httpd.h"
25#include "http_config.h"
26#include "http_connection.h"
27#include "http_core.h"
28#include "http_protocol.h"   /* For index_of_response().  Grump. */
29#include "http_request.h"
30
31/* Generate the human-readable hex representation of an apr_uint64_t
32 * (basically a faster version of 'sprintf("%llx")')
33 */
34#define HEX_DIGITS "0123456789abcdef"
35static char *etag_uint64_to_hex(char *next, apr_uint64_t u)
36{
37    int printing = 0;
38    int shift = sizeof(apr_uint64_t) * 8 - 4;
39    do {
40        unsigned short next_digit = (unsigned short)
41                                    ((u >> shift) & (apr_uint64_t)0xf);
42        if (next_digit) {
43            *next++ = HEX_DIGITS[next_digit];
44            printing = 1;
45        }
46        else if (printing) {
47            *next++ = HEX_DIGITS[next_digit];
48        }
49        shift -= 4;
50    } while (shift);
51    *next++ = HEX_DIGITS[u & (apr_uint64_t)0xf];
52    return next;
53}
54
55#define ETAG_WEAK "W/"
56#define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2)
57/*
58 * Construct an entity tag (ETag) from resource information.  If it's a real
59 * file, build in some of the file characteristics.  If the modification time
60 * is newer than (request-time minus 1 second), mark the ETag as weak - it
61 * could be modified again in as short an interval.  We rationalize the
62 * modification time we're given to keep it from being in the future.
63 */
64AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
65{
66    char *weak;
67    apr_size_t weak_len;
68    char *etag;
69    char *next;
70    core_dir_config *cfg;
71    etag_components_t etag_bits;
72    etag_components_t bits_added;
73
74    cfg = (core_dir_config *)ap_get_module_config(r->per_dir_config,
75                                                  &core_module);
76    etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
77
78    /*
79     * If it's a file (or we wouldn't be here) and no ETags
80     * should be set for files, return an empty string and
81     * note it for the header-sender to ignore.
82     */
83    if (etag_bits & ETAG_NONE) {
84        apr_table_setn(r->notes, "no-etag", "omit");
85        return "";
86    }
87
88    if (etag_bits == ETAG_UNSET) {
89        etag_bits = ETAG_BACKWARD;
90    }
91    /*
92     * Make an ETag header out of various pieces of information. We use
93     * the last-modified date and, if we have a real file, the
94     * length and inode number - note that this doesn't have to match
95     * the content-length (i.e. includes), it just has to be unique
96     * for the file.
97     *
98     * If the request was made within a second of the last-modified date,
99     * we send a weak tag instead of a strong one, since it could
100     * be modified again later in the second, and the validation
101     * would be incorrect.
102     */
103    if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
104        !force_weak) {
105        weak = NULL;
106        weak_len = 0;
107    }
108    else {
109        weak = ETAG_WEAK;
110        weak_len = sizeof(ETAG_WEAK);
111    }
112
113    if (r->finfo.filetype != 0) {
114        /*
115         * ETag gets set to [W/]"inode-size-mtime", modulo any
116         * FileETag keywords.
117         */
118        etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
119                          3 * CHARS_PER_UINT64 + 1);
120        next = etag;
121        if (weak) {
122            while (*weak) {
123                *next++ = *weak++;
124            }
125        }
126        *next++ = '"';
127        bits_added = 0;
128        if (etag_bits & ETAG_INODE) {
129            next = etag_uint64_to_hex(next, r->finfo.inode);
130            bits_added |= ETAG_INODE;
131        }
132        if (etag_bits & ETAG_SIZE) {
133            if (bits_added != 0) {
134                *next++ = '-';
135            }
136            next = etag_uint64_to_hex(next, r->finfo.size);
137            bits_added |= ETAG_SIZE;
138        }
139        if (etag_bits & ETAG_MTIME) {
140            if (bits_added != 0) {
141                *next++ = '-';
142            }
143            next = etag_uint64_to_hex(next, r->mtime);
144        }
145        *next++ = '"';
146        *next = '\0';
147    }
148    else {
149        /*
150         * Not a file document, so just use the mtime: [W/]"mtime"
151         */
152        etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
153                          CHARS_PER_UINT64 + 1);
154        next = etag;
155        if (weak) {
156            while (*weak) {
157                *next++ = *weak++;
158            }
159        }
160        *next++ = '"';
161        next = etag_uint64_to_hex(next, r->mtime);
162        *next++ = '"';
163        *next = '\0';
164    }
165
166    return etag;
167}
168
169AP_DECLARE(void) ap_set_etag(request_rec *r)
170{
171    char *etag;
172    char *variant_etag, *vlv;
173    int vlv_weak;
174
175    if (!r->vlist_validator) {
176        etag = ap_make_etag(r, 0);
177
178        /* If we get a blank etag back, don't set the header. */
179        if (!etag[0]) {
180            return;
181        }
182    }
183    else {
184        /* If we have a variant list validator (vlv) due to the
185         * response being negotiated, then we create a structured
186         * entity tag which merges the variant etag with the variant
187         * list validator (vlv).  This merging makes revalidation
188         * somewhat safer, ensures that caches which can deal with
189         * Vary will (eventually) be updated if the set of variants is
190         * changed, and is also a protocol requirement for transparent
191         * content negotiation.
192         */
193
194        /* if the variant list validator is weak, we make the whole
195         * structured etag weak.  If we would not, then clients could
196         * have problems merging range responses if we have different
197         * variants with the same non-globally-unique strong etag.
198         */
199
200        vlv = r->vlist_validator;
201        vlv_weak = (vlv[0] == 'W');
202
203        variant_etag = ap_make_etag(r, vlv_weak);
204
205        /* If we get a blank etag back, don't append vlv and stop now. */
206        if (!variant_etag[0]) {
207            return;
208        }
209
210        /* merge variant_etag and vlv into a structured etag */
211        variant_etag[strlen(variant_etag) - 1] = '\0';
212        if (vlv_weak) {
213            vlv += 3;
214        }
215        else {
216            vlv++;
217        }
218        etag = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
219    }
220
221    apr_table_setn(r->headers_out, "ETag", etag);
222}
223