]>
Commit | Line | Data |
---|---|---|
7faf2bdb | 1 | /* |
1f7b830e | 2 | * Copyright (C) 1996-2025 The Squid Software Foundation and contributors |
7faf2bdb | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7faf2bdb | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 65 HTTP Cache Control Header */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
e3aef579 | 12 | #include "base/EnumIterator.h" |
ea295fe4 | 13 | #include "base/LookupTable.h" |
602d9612 | 14 | #include "HttpHdrCc.h" |
e6ccf245 | 15 | #include "HttpHeader.h" |
e1656dc4 | 16 | #include "HttpHeaderStat.h" |
a5bac1d2 | 17 | #include "HttpHeaderTools.h" |
65e41a45 | 18 | #include "sbuf/SBuf.h" |
e3aef579 | 19 | #include "SquidMath.h" |
00a7574e FC |
20 | #include "StatHist.h" |
21 | #include "Store.h" | |
28204b3b | 22 | #include "StrList.h" |
ed6e9fb9 | 23 | #include "util.h" |
ce394734 | 24 | |
ce394734 | 25 | #include <map> |
ea295fe4 | 26 | #include <vector> |
e3aef579 | 27 | #include <optional> |
6b1c9146 | 28 | #include <ostream> |
ea295fe4 | 29 | |
e3aef579 | 30 | constexpr LookupTable<HttpHdrCcType>::Record attrsList[] = { |
1da82544 FC |
31 | {"public", HttpHdrCcType::CC_PUBLIC}, |
32 | {"private", HttpHdrCcType::CC_PRIVATE}, | |
33 | {"no-cache", HttpHdrCcType::CC_NO_CACHE}, | |
34 | {"no-store", HttpHdrCcType::CC_NO_STORE}, | |
35 | {"no-transform", HttpHdrCcType::CC_NO_TRANSFORM}, | |
36 | {"must-revalidate", HttpHdrCcType::CC_MUST_REVALIDATE}, | |
37 | {"proxy-revalidate", HttpHdrCcType::CC_PROXY_REVALIDATE}, | |
38 | {"max-age", HttpHdrCcType::CC_MAX_AGE}, | |
39 | {"s-maxage", HttpHdrCcType::CC_S_MAXAGE}, | |
40 | {"max-stale", HttpHdrCcType::CC_MAX_STALE}, | |
41 | {"min-fresh", HttpHdrCcType::CC_MIN_FRESH}, | |
42 | {"only-if-cached", HttpHdrCcType::CC_ONLY_IF_CACHED}, | |
43 | {"stale-if-error", HttpHdrCcType::CC_STALE_IF_ERROR}, | |
caf65351 | 44 | {"immutable", HttpHdrCcType::CC_IMMUTABLE}, |
1da82544 FC |
45 | {"Other,", HttpHdrCcType::CC_OTHER}, /* ',' will protect from matches */ |
46 | {nullptr, HttpHdrCcType::CC_ENUM_END} | |
26735116 | 47 | }; |
e3aef579 FC |
48 | |
49 | constexpr const auto & | |
50 | CcAttrs() { | |
51 | // TODO: Move these compile-time checks into LookupTable | |
52 | ConstexprForEnum<HttpHdrCcType::CC_PUBLIC, HttpHdrCcType::CC_ENUM_END>([](const auto ev) { | |
53 | const auto idx = static_cast<std::underlying_type<HttpHdrCcType>::type>(ev); | |
54 | // invariant: each row has a name except the last one | |
55 | static_assert(!attrsList[idx].name == (ev == HttpHdrCcType::CC_ENUM_END)); | |
56 | // invariant: row[idx].id == idx | |
57 | static_assert(attrsList[idx].id == ev); | |
58 | }); | |
59 | return attrsList; | |
60 | } | |
61 | ||
62 | static auto | |
63 | ccTypeByName(const SBuf &name) { | |
64 | const static auto table = new LookupTable<HttpHdrCcType>(HttpHdrCcType::CC_OTHER, CcAttrs()); | |
65 | return table->lookup(name); | |
66 | } | |
67 | ||
68 | /// Safely converts an integer into a Cache-Control directive name. | |
69 | /// \returns std::nullopt if the given integer is not a valid index of a named attrsList entry | |
70 | template <typename RawId> | |
71 | static std::optional<const char *> | |
72 | ccNameByType(const RawId rawId) | |
73 | { | |
74 | // TODO: Store a by-ID index in (and move this functionality into) LookupTable. | |
75 | if (!Less(rawId, 0) && Less(rawId, int(HttpHdrCcType::CC_ENUM_END))) { | |
76 | const auto idx = static_cast<std::underlying_type<HttpHdrCcType>::type>(rawId); | |
77 | return CcAttrs()[idx].name; | |
78 | } | |
79 | return std::nullopt; | |
80 | } | |
81 | ||
d74ad83f | 82 | /// used to walk a table of http_header_cc_type structs |
8b082ed9 FC |
83 | static HttpHdrCcType & |
84 | operator++ (HttpHdrCcType &aHeader) | |
e6ccf245 | 85 | { |
1f1ae50a | 86 | int tmp = (int)aHeader; |
1da82544 | 87 | aHeader = (HttpHdrCcType)(++tmp); |
e6ccf245 | 88 | return aHeader; |
89 | } | |
90 | ||
ce394734 FC |
91 | void |
92 | HttpHdrCc::clear() | |
7faf2bdb | 93 | { |
7ebe76de | 94 | *this=HttpHdrCc(); |
7faf2bdb | 95 | } |
96 | ||
22056ef8 AJ |
97 | /// set a data member to a new value, and set the corresponding mask-bit. |
98 | /// if setting is false, then the mask-bit is cleared. | |
99 | void | |
100 | HttpHdrCc::setValue(int32_t &value, int32_t new_value, HttpHdrCcType hdr, bool setting) | |
101 | { | |
102 | if (setting) { | |
103 | if (new_value < 0) { | |
104 | debugs(65, 3, "rejecting negative-value Cache-Control directive " << hdr | |
105 | << " value " << new_value); | |
106 | return; | |
107 | } | |
108 | } else { | |
109 | new_value = -1; //rely on the convention that "unknown" is -1 | |
110 | } | |
111 | ||
112 | value = new_value; | |
113 | setMask(hdr,setting); | |
114 | } | |
115 | ||
ce394734 | 116 | bool |
7ebe76de | 117 | HttpHdrCc::parse(const String & str) |
7faf2bdb | 118 | { |
119 | const char *item; | |
f53969cc | 120 | const char *p; /* '=' parameter */ |
aee3523a | 121 | const char *pos = nullptr; |
7faf2bdb | 122 | int ilen; |
1b2e9616 | 123 | int nlen; |
7faf2bdb | 124 | |
7faf2bdb | 125 | /* iterate through comma separated list */ |
62e76326 | 126 | |
ce394734 | 127 | while (strListGetItem(&str, ',', &item, &ilen, &pos)) { |
1b2e9616 | 128 | /* isolate directive name */ |
62e76326 | 129 | |
a38ec4b1 FC |
130 | if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) { |
131 | nlen = p - item; | |
132 | ++p; | |
133 | } else { | |
1b2e9616 | 134 | nlen = ilen; |
a38ec4b1 | 135 | } |
62e76326 | 136 | |
137 | /* find type */ | |
e3aef579 | 138 | const auto type = ccTypeByName(SBuf(item, nlen)); |
62e76326 | 139 | |
aa62670a | 140 | // ignore known duplicate directives |
f9517ad8 | 141 | if (isSet(type)) { |
1da82544 | 142 | if (type != HttpHdrCcType::CC_OTHER) { |
e8466ea9 | 143 | debugs(65, 2, "hdr cc: ignoring duplicate cache-directive: near '" << item << "' in '" << str << "'"); |
aa62670a AR |
144 | continue; |
145 | } | |
62e76326 | 146 | } |
147 | ||
4ce6e3b5 | 148 | /* special-case-parsing and attribute-setting */ |
62e76326 | 149 | switch (type) { |
150 | ||
1da82544 | 151 | case HttpHdrCcType::CC_MAX_AGE: |
d74ad83f | 152 | if (!p || !httpHeaderParseInt(p, &max_age) || max_age < 0) { |
bf8fe701 | 153 | debugs(65, 2, "cc: invalid max-age specs near '" << item << "'"); |
4ce6e3b5 | 154 | clearMaxAge(); |
1ba0611a | 155 | } else { |
d74ad83f | 156 | setMask(type,true); |
62e76326 | 157 | } |
62e76326 | 158 | break; |
159 | ||
1da82544 | 160 | case HttpHdrCcType::CC_S_MAXAGE: |
d74ad83f | 161 | if (!p || !httpHeaderParseInt(p, &s_maxage) || s_maxage < 0) { |
bf8fe701 | 162 | debugs(65, 2, "cc: invalid s-maxage specs near '" << item << "'"); |
4ce6e3b5 | 163 | clearSMaxAge(); |
d74ad83f FC |
164 | } else { |
165 | setMask(type,true); | |
62e76326 | 166 | } |
62e76326 | 167 | break; |
168 | ||
1da82544 | 169 | case HttpHdrCcType::CC_MAX_STALE: |
d74ad83f | 170 | if (!p || !httpHeaderParseInt(p, &max_stale) || max_stale < 0) { |
bf8fe701 | 171 | debugs(65, 2, "cc: max-stale directive is valid without value"); |
d74ad83f FC |
172 | maxStale(MAX_STALE_ANY); |
173 | } else { | |
174 | setMask(type,true); | |
62e76326 | 175 | } |
62e76326 | 176 | break; |
177 | ||
1da82544 | 178 | case HttpHdrCcType::CC_MIN_FRESH: |
d74ad83f | 179 | if (!p || !httpHeaderParseInt(p, &min_fresh) || min_fresh < 0) { |
64f8c2cb | 180 | debugs(65, 2, "cc: invalid min-fresh specs near '" << item << "'"); |
4ce6e3b5 | 181 | clearMinFresh(); |
d74ad83f FC |
182 | } else { |
183 | setMask(type,true); | |
64f8c2cb | 184 | } |
64f8c2cb AR |
185 | break; |
186 | ||
1da82544 | 187 | case HttpHdrCcType::CC_STALE_IF_ERROR: |
d74ad83f | 188 | if (!p || !httpHeaderParseInt(p, &stale_if_error) || stale_if_error < 0) { |
65fd3895 | 189 | debugs(65, 2, "cc: invalid stale-if-error specs near '" << item << "'"); |
4ce6e3b5 | 190 | clearStaleIfError(); |
d74ad83f FC |
191 | } else { |
192 | setMask(type,true); | |
65fd3895 AJ |
193 | } |
194 | break; | |
195 | ||
1da82544 | 196 | case HttpHdrCcType::CC_PRIVATE: { |
1259f9cf AJ |
197 | String temp; |
198 | if (!p) { | |
199 | // Value parameter is optional. | |
200 | private_.clean(); | |
201 | } else if (/* p &&*/ httpHeaderParseQuotedString(p, (ilen-nlen-1), &temp)) { | |
202 | private_.append(temp); | |
203 | } else { | |
204 | debugs(65, 2, "cc: invalid private= specs near '" << item << "'"); | |
205 | } | |
206 | // to be safe we ignore broken parameters, but always remember the 'private' part. | |
207 | setMask(type,true); | |
208 | } | |
209 | break; | |
210 | ||
1da82544 | 211 | case HttpHdrCcType::CC_NO_CACHE: { |
1259f9cf AJ |
212 | String temp; |
213 | if (!p) { | |
214 | // On Requests, missing value parameter is expected syntax. | |
215 | // On Responses, value parameter is optional. | |
216 | setMask(type,true); | |
217 | no_cache.clean(); | |
218 | } else if (/* p &&*/ httpHeaderParseQuotedString(p, (ilen-nlen-1), &temp)) { | |
219 | // On Requests, a value parameter is invalid syntax. | |
220 | // XXX: identify when parsing request header and dump err message here. | |
221 | setMask(type,true); | |
222 | no_cache.append(temp); | |
223 | } else { | |
224 | debugs(65, 2, "cc: invalid no-cache= specs near '" << item << "'"); | |
225 | } | |
226 | } | |
227 | break; | |
228 | ||
1da82544 | 229 | case HttpHdrCcType::CC_PUBLIC: |
77da1817 A |
230 | Public(true); |
231 | break; | |
1da82544 | 232 | case HttpHdrCcType::CC_NO_STORE: |
77da1817 A |
233 | noStore(true); |
234 | break; | |
1da82544 | 235 | case HttpHdrCcType::CC_NO_TRANSFORM: |
77da1817 A |
236 | noTransform(true); |
237 | break; | |
1da82544 | 238 | case HttpHdrCcType::CC_MUST_REVALIDATE: |
77da1817 A |
239 | mustRevalidate(true); |
240 | break; | |
1da82544 | 241 | case HttpHdrCcType::CC_PROXY_REVALIDATE: |
77da1817 A |
242 | proxyRevalidate(true); |
243 | break; | |
1da82544 | 244 | case HttpHdrCcType::CC_ONLY_IF_CACHED: |
77da1817 A |
245 | onlyIfCached(true); |
246 | break; | |
caf65351 AJ |
247 | case HttpHdrCcType::CC_IMMUTABLE: |
248 | Immutable(true); | |
249 | break; | |
1b2e9616 | 250 | |
1da82544 | 251 | case HttpHdrCcType::CC_OTHER: |
f9517ad8 FC |
252 | if (other.size()) |
253 | other.append(", "); | |
1b2e9616 | 254 | |
f9517ad8 | 255 | other.append(item, ilen); |
1b2e9616 | 256 | break; |
257 | ||
62e76326 | 258 | default: |
1b2e9616 | 259 | /* note that we ignore most of '=' specs (RFCVIOLATION) */ |
62e76326 | 260 | break; |
261 | } | |
7faf2bdb | 262 | } |
62e76326 | 263 | |
f9517ad8 | 264 | return (mask != 0); |
7faf2bdb | 265 | } |
266 | ||
7faf2bdb | 267 | void |
17802cf1 | 268 | HttpHdrCc::packInto(Packable * p) const |
7faf2bdb | 269 | { |
2c8c98ad FC |
270 | // optimization: if the mask is empty do nothing |
271 | if (mask==0) | |
272 | return; | |
273 | ||
ce9c4e4c | 274 | HttpHdrCcType flag; |
7faf2bdb | 275 | int pcount = 0; |
4ce6e3b5 | 276 | assert(p); |
62e76326 | 277 | |
1da82544 FC |
278 | for (flag = HttpHdrCcType::CC_PUBLIC; flag < HttpHdrCcType::CC_ENUM_END; ++flag) { |
279 | if (isSet(flag) && flag != HttpHdrCcType::CC_OTHER) { | |
62e76326 | 280 | |
c397963f | 281 | /* print option name for all options */ |
e3aef579 | 282 | p->appendf((pcount ? ", %s": "%s"), *ccNameByType(flag)); |
91274dd0 | 283 | |
c397963f FC |
284 | /* for all options having values, "=value" after the name */ |
285 | switch (flag) { | |
145c72f4 AJ |
286 | case HttpHdrCcType::CC_PUBLIC: |
287 | break; | |
288 | case HttpHdrCcType::CC_PRIVATE: | |
810d879f EB |
289 | if (private_.size()) |
290 | p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(private_)); | |
145c72f4 AJ |
291 | break; |
292 | ||
293 | case HttpHdrCcType::CC_NO_CACHE: | |
810d879f EB |
294 | if (no_cache.size()) |
295 | p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(no_cache)); | |
145c72f4 AJ |
296 | break; |
297 | case HttpHdrCcType::CC_NO_STORE: | |
298 | break; | |
299 | case HttpHdrCcType::CC_NO_TRANSFORM: | |
300 | break; | |
301 | case HttpHdrCcType::CC_MUST_REVALIDATE: | |
302 | break; | |
303 | case HttpHdrCcType::CC_PROXY_REVALIDATE: | |
304 | break; | |
1da82544 | 305 | case HttpHdrCcType::CC_MAX_AGE: |
810d879f | 306 | p->appendf("=%d", max_age); |
c397963f | 307 | break; |
1da82544 | 308 | case HttpHdrCcType::CC_S_MAXAGE: |
810d879f | 309 | p->appendf("=%d", s_maxage); |
c397963f | 310 | break; |
1da82544 | 311 | case HttpHdrCcType::CC_MAX_STALE: |
c397963f FC |
312 | /* max-stale's value is optional. |
313 | If we didn't receive it, don't send it */ | |
810d879f EB |
314 | if (max_stale != MAX_STALE_ANY) |
315 | p->appendf("=%d", max_stale); | |
c397963f | 316 | break; |
1da82544 | 317 | case HttpHdrCcType::CC_MIN_FRESH: |
810d879f | 318 | p->appendf("=%d", min_fresh); |
c397963f | 319 | break; |
145c72f4 AJ |
320 | case HttpHdrCcType::CC_ONLY_IF_CACHED: |
321 | break; | |
322 | case HttpHdrCcType::CC_STALE_IF_ERROR: | |
810d879f | 323 | p->appendf("=%d", stale_if_error); |
145c72f4 | 324 | break; |
caf65351 AJ |
325 | case HttpHdrCcType::CC_IMMUTABLE: |
326 | break; | |
145c72f4 AJ |
327 | case HttpHdrCcType::CC_OTHER: |
328 | case HttpHdrCcType::CC_ENUM_END: | |
329 | // done below after the loop | |
c397963f FC |
330 | break; |
331 | } | |
64f8c2cb | 332 | |
7ebe76de | 333 | ++pcount; |
62e76326 | 334 | } |
7faf2bdb | 335 | } |
1b2e9616 | 336 | |
4ce6e3b5 | 337 | if (other.size() != 0) |
4391cd15 | 338 | p->appendf((pcount ? ", " SQUIDSTRINGPH : SQUIDSTRINGPH), SQUIDSTRINGPRINT(other)); |
7faf2bdb | 339 | } |
340 | ||
7faf2bdb | 341 | void |
342 | httpHdrCcUpdateStats(const HttpHdrCc * cc, StatHist * hist) | |
343 | { | |
7faf2bdb | 344 | assert(cc); |
62e76326 | 345 | |
1da82544 | 346 | for (HttpHdrCcType c = HttpHdrCcType::CC_PUBLIC; c < HttpHdrCcType::CC_ENUM_END; ++c) |
f9517ad8 | 347 | if (cc->isSet(c)) |
f30f7998 | 348 | hist->count(c); |
7faf2bdb | 349 | } |
350 | ||
351 | void | |
ced8def3 | 352 | httpHdrCcStatDumper(StoreEntry * sentry, int, double val, double, int count) |
7faf2bdb | 353 | { |
f53969cc | 354 | extern const HttpHeaderStat *dump_stat; /* argh! */ |
383154d7 | 355 | const int id = static_cast<int>(val); |
e3aef579 FC |
356 | const auto name = ccNameByType(id); |
357 | if (count || name) | |
62e76326 | 358 | storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", |
e3aef579 | 359 | id, name.value_or("INVALID"), count, xdiv(count, dump_stat->ccParsedCount)); |
7faf2bdb | 360 | } |
1ba0611a | 361 | |
6b1c9146 FC |
362 | std::ostream & |
363 | operator<< (std::ostream &s, HttpHdrCcType c) | |
364 | { | |
e3aef579 | 365 | s << ccNameByType(c).value_or("INVALID") << '[' << static_cast<int>(c) << ']'; |
6b1c9146 FC |
366 | return s; |
367 | } | |
368 |