]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHdrCc.cc
Simplify appending SBuf to String (#2108)
[thirdparty/squid.git] / src / HttpHdrCc.cc
CommitLineData
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 30constexpr 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
49constexpr const auto &
50CcAttrs() {
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
62static auto
63ccTypeByName(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
70template <typename RawId>
71static std::optional<const char *>
72ccNameByType(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
83static HttpHdrCcType &
84operator++ (HttpHdrCcType &aHeader)
e6ccf245 85{
1f1ae50a 86 int tmp = (int)aHeader;
1da82544 87 aHeader = (HttpHdrCcType)(++tmp);
e6ccf245 88 return aHeader;
89}
90
ce394734
FC
91void
92HttpHdrCc::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.
99void
100HttpHdrCc::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 116bool
7ebe76de 117HttpHdrCc::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 267void
17802cf1 268HttpHdrCc::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 341void
342httpHdrCcUpdateStats(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
351void
ced8def3 352httpHdrCcStatDumper(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
362std::ostream &
363operator<< (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