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