]>
Commit | Line | Data |
---|---|---|
43ae1d95 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
43ae1d95 | 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. | |
43ae1d95 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 90 HTTP Cache Control Header */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
4277583a | 12 | #include "base/LookupTable.h" |
1da82544 | 13 | //#include "HttpHdrSc.h" // pulled in by HttpHdrScTarget.h |
96d2a063 | 14 | #include "HttpHdrScTarget.h" |
43ae1d95 | 15 | #include "HttpHeader.h" |
db2de30a | 16 | #include "HttpHeaderFieldStat.h" |
e1656dc4 | 17 | #include "HttpHeaderStat.h" |
a5bac1d2 | 18 | #include "HttpHeaderTools.h" |
582c2af2 | 19 | #include "Store.h" |
28204b3b | 20 | #include "StrList.h" |
ed6e9fb9 | 21 | #include "util.h" |
43ae1d95 | 22 | |
45a58345 | 23 | #include <map> |
4277583a | 24 | #include <vector> |
45a58345 | 25 | |
43ae1d95 | 26 | /* this table is used for parsing surrogate control header */ |
45a58345 | 27 | /* order must match that of enum http_hdr_sc_type. The constraint is verified at initialization time */ |
9837567d | 28 | // TODO: implement constraint |
4277583a FC |
29 | static const LookupTable<http_hdr_sc_type>::Record ScAttrs[] { |
30 | {"no-store", SC_NO_STORE}, | |
31 | {"no-store-remote", SC_NO_STORE_REMOTE}, | |
32 | {"max-age", SC_MAX_AGE}, | |
33 | {"content", SC_CONTENT}, | |
34 | {"Other,", SC_OTHER}, /* ',' will protect from matches */ | |
35 | {nullptr, SC_ENUM_END} /* SC_ENUM_END taken as invalid value */ | |
26ac0430 | 36 | }; |
4277583a FC |
37 | LookupTable<http_hdr_sc_type> scLookupTable(SC_ENUM_END, ScAttrs); |
38 | std::vector<HttpHeaderFieldStat> scHeaderStats(SC_ENUM_END); | |
43ae1d95 | 39 | |
a9cfbb83 | 40 | // used when iterating over flags |
43ae1d95 | 41 | http_hdr_sc_type &operator++ (http_hdr_sc_type &aHeader) |
42 | { | |
a9cfbb83 FC |
43 | int tmp = static_cast<int>(aHeader); |
44 | aHeader = static_cast<http_hdr_sc_type>(++tmp); | |
43ae1d95 | 45 | return aHeader; |
46 | } | |
47 | ||
43ae1d95 | 48 | void |
49 | httpHdrScInitModule(void) | |
50 | { | |
4277583a FC |
51 | // check invariant on ScAttrs |
52 | for (int i = 0; ScAttrs[i].name != nullptr; ++i) | |
53 | assert(i == ScAttrs[i].id); | |
43ae1d95 | 54 | } |
55 | ||
43ae1d95 | 56 | /* implementation */ |
57 | ||
43ae1d95 | 58 | /* creates an sc object from a 0-terminating string */ |
59 | HttpHdrSc * | |
45a58345 | 60 | httpHdrScParseCreate(const String & str) |
43ae1d95 | 61 | { |
45a58345 | 62 | HttpHdrSc *sc = new HttpHdrSc(); |
43ae1d95 | 63 | |
45a58345 FC |
64 | if (!sc->parse(&str)) { |
65 | delete sc; | |
aee3523a | 66 | sc = nullptr; |
43ae1d95 | 67 | } |
68 | ||
69 | return sc; | |
70 | } | |
71 | ||
72 | /* parses a 0-terminating string and inits sc */ | |
45a58345 FC |
73 | bool |
74 | HttpHdrSc::parse(const String * str) | |
43ae1d95 | 75 | { |
45a58345 | 76 | HttpHdrSc * sc=this; |
43ae1d95 | 77 | const char *item; |
f53969cc | 78 | const char *p; /* '=' parameter */ |
aee3523a AR |
79 | const char *pos = nullptr; |
80 | const char *target = nullptr; /* ;foo */ | |
81 | const char *temp = nullptr; /* temp buffer */ | |
4277583a | 82 | http_hdr_sc_type type; |
34460e19 | 83 | int ilen, vlen; |
43ae1d95 | 84 | int initiallen; |
85 | HttpHdrScTarget *sct; | |
45a58345 | 86 | assert(str); |
43ae1d95 | 87 | |
88 | /* iterate through comma separated list */ | |
89 | ||
90 | while (strListGetItem(str, ',', &item, &ilen, &pos)) { | |
91 | initiallen = ilen; | |
34460e19 | 92 | vlen = 0; |
43ae1d95 | 93 | /* decrease ilen to still match the token for '=' statements */ |
94 | ||
34460e19 | 95 | if ((p = strchr(item, '=')) && (p - item < ilen)) { |
7c0f9f7e | 96 | vlen = ilen - (p + 1 - item); |
34460e19 | 97 | ilen = p - item; |
95dc7ff4 | 98 | ++p; |
34460e19 | 99 | } |
43ae1d95 | 100 | |
2f8abb64 | 101 | /* decrease ilen to still match the token for ';' qualified non '=' statements */ |
a38ec4b1 FC |
102 | else if ((p = strchr(item, ';')) && (p - item < ilen)) { |
103 | ilen = p - item; | |
104 | ++p; | |
105 | } | |
43ae1d95 | 106 | |
107 | /* find type */ | |
4277583a | 108 | type = scLookupTable.lookup(SBuf(item,ilen)); |
43ae1d95 | 109 | |
4277583a | 110 | if (type == SC_ENUM_END) { |
7ec92ef0 | 111 | debugs(90, 2, "unknown control-directive near '" << item << "' in '" << *str << "'"); |
43ae1d95 | 112 | type = SC_OTHER; |
113 | } | |
114 | ||
115 | /* Is this a targeted directive? */ | |
2f8abb64 | 116 | /* TODO: remove the temporary usage and use memrchr and the information we have instead */ |
43ae1d95 | 117 | temp = xstrndup (item, initiallen + 1); |
118 | ||
119 | if (!((target = strrchr (temp, ';')) && !strchr (target, '"') && *(target + 1) != '\0')) | |
aee3523a | 120 | target = nullptr; |
43ae1d95 | 121 | else |
122 | ++target; | |
123 | ||
45a58345 | 124 | sct = sc->findTarget(target); |
43ae1d95 | 125 | |
126 | if (!sct) { | |
24673a9e AJ |
127 | // XXX: if parse is left-to-right over field-value this should be emplace_back() |
128 | // currently placing on the front reverses the order of headers passed on downstream. | |
09835feb | 129 | sct = &targets.emplace_front(target); |
43ae1d95 | 130 | } |
131 | ||
132 | safe_free (temp); | |
133 | ||
4277583a | 134 | if (sct->isSet(type)) { |
43ae1d95 | 135 | if (type != SC_OTHER) |
7ec92ef0 | 136 | debugs(90, 2, "ignoring duplicate control-directive near '" << item << "' in '" << *str << "'"); |
43ae1d95 | 137 | |
4277583a | 138 | ++ scHeaderStats[type].repCount; |
43ae1d95 | 139 | |
140 | continue; | |
141 | } | |
142 | ||
45a58345 | 143 | /* process directives */ |
43ae1d95 | 144 | switch (type) { |
45a58345 FC |
145 | case SC_NO_STORE: |
146 | sct->noStore(true); | |
147 | break; | |
43ae1d95 | 148 | |
45a58345 FC |
149 | case SC_NO_STORE_REMOTE: |
150 | sct->noStoreRemote(true); | |
43ae1d95 | 151 | break; |
152 | ||
633b9845 A |
153 | case SC_MAX_AGE: { |
154 | int ma; | |
155 | if (p && httpHeaderParseInt(p, &ma)) { | |
156 | sct->maxAge(ma); | |
76d0bd38 AJ |
157 | |
158 | if ((p = strchr (p, '+'))) { | |
159 | int ms; | |
160 | ++p; //skip the + char | |
161 | if (httpHeaderParseInt(p, &ms)) { | |
162 | sct->maxStale(ms); | |
163 | } else { | |
164 | debugs(90, 2, "sc: invalid max-stale specs near '" << item << "'"); | |
165 | sct->clearMaxStale(); | |
166 | /* leave the max-age alone */ | |
167 | } | |
168 | } | |
633b9845 A |
169 | } else { |
170 | debugs(90, 2, "sc: invalid max-age specs near '" << item << "'"); | |
171 | sct->clearMaxAge(); | |
172 | } | |
173 | ||
633b9845 A |
174 | break; |
175 | } | |
45a58345 | 176 | |
43ae1d95 | 177 | case SC_CONTENT: |
178 | ||
633b9845 A |
179 | if ( p && httpHeaderParseQuotedString(p, vlen, &sct->content_)) { |
180 | sct->setMask(SC_CONTENT,true); // ugly but saves a copy | |
181 | } else { | |
bf8fe701 | 182 | debugs(90, 2, "sc: invalid content= quoted string near '" << item << "'"); |
45a58345 | 183 | sct->clearContent(); |
43ae1d95 | 184 | } |
633b9845 | 185 | break; |
43ae1d95 | 186 | |
45a58345 | 187 | case SC_OTHER: |
43ae1d95 | 188 | default: |
189 | break; | |
190 | } | |
191 | } | |
192 | ||
24673a9e | 193 | return !sc->targets.empty(); |
43ae1d95 | 194 | } |
195 | ||
24673a9e | 196 | /// XXX: this function should be in HttpHdrScTarget.cc |
43ae1d95 | 197 | void |
17802cf1 | 198 | HttpHdrScTarget::packInto(Packable * p) const |
43ae1d95 | 199 | { |
200 | http_hdr_sc_type flag; | |
201 | int pcount = 0; | |
45a58345 | 202 | assert (p); |
43ae1d95 | 203 | |
204 | for (flag = SC_NO_STORE; flag < SC_ENUM_END; ++flag) { | |
45a58345 | 205 | if (isSet(flag) && flag != SC_OTHER) { |
43ae1d95 | 206 | |
207 | /* print option name */ | |
4277583a | 208 | p->appendf((pcount ? ", %s" : "%s"), ScAttrs[flag].name); |
43ae1d95 | 209 | |
210 | /* handle options with values */ | |
211 | ||
212 | if (flag == SC_MAX_AGE) | |
4391cd15 | 213 | p->appendf("=%d", (int) max_age); |
43ae1d95 | 214 | |
215 | if (flag == SC_CONTENT) | |
4391cd15 | 216 | p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(content_)); |
43ae1d95 | 217 | |
95dc7ff4 | 218 | ++pcount; |
43ae1d95 | 219 | } |
220 | } | |
221 | ||
45a58345 | 222 | if (hasTarget()) |
4391cd15 | 223 | p->appendf(";" SQUIDSTRINGPH, SQUIDSTRINGPRINT(target)); |
43ae1d95 | 224 | } |
225 | ||
226 | void | |
17802cf1 | 227 | HttpHdrSc::packInto(Packable * p) const |
43ae1d95 | 228 | { |
45a58345 | 229 | assert(p); |
24673a9e AJ |
230 | for (const auto &t : targets) { |
231 | t.packInto(p); | |
43ae1d95 | 232 | } |
233 | } | |
234 | ||
43ae1d95 | 235 | /* negative max_age will clean old max_Age setting */ |
236 | void | |
45a58345 | 237 | HttpHdrSc::setMaxAge(char const *target, int max_age) |
43ae1d95 | 238 | { |
45a58345 | 239 | HttpHdrScTarget *sct = findTarget(target); |
43ae1d95 | 240 | |
241 | if (!sct) { | |
09835feb | 242 | sct = &targets.emplace_back(target); |
43ae1d95 | 243 | } |
244 | ||
45a58345 | 245 | sct->maxAge(max_age); |
43ae1d95 | 246 | } |
247 | ||
248 | void | |
45a58345 | 249 | HttpHdrSc::updateStats(StatHist * hist) const |
43ae1d95 | 250 | { |
24673a9e AJ |
251 | for (auto &t : targets) { |
252 | t.updateStats(hist); | |
43ae1d95 | 253 | } |
254 | } | |
255 | ||
256 | void | |
ced8def3 | 257 | httpHdrScTargetStatDumper(StoreEntry * sentry, int, double val, double, int count) |
43ae1d95 | 258 | { |
259 | extern const HttpHeaderStat *dump_stat; /* argh! */ | |
260 | const int id = (int) val; | |
4277583a FC |
261 | const bool valid_id = id >= 0 && id < SC_ENUM_END; |
262 | const char *name = valid_id ? ScAttrs[id].name : "INVALID"; | |
43ae1d95 | 263 | |
264 | if (count || valid_id) | |
265 | storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", | |
266 | id, name, count, xdiv(count, dump_stat->scParsedCount)); | |
267 | } | |
268 | ||
269 | void | |
ced8def3 | 270 | httpHdrScStatDumper(StoreEntry * sentry, int, double val, double, int count) |
43ae1d95 | 271 | { |
f53969cc | 272 | extern const HttpHeaderStat *dump_stat; /* argh! */ |
43ae1d95 | 273 | const int id = (int) val; |
4277583a FC |
274 | const bool valid_id = id >= 0 && id < SC_ENUM_END; |
275 | const char *name = valid_id ? ScAttrs[id].name : "INVALID"; | |
43ae1d95 | 276 | |
277 | if (count || valid_id) | |
278 | storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", | |
279 | id, name, count, xdiv(count, dump_stat->scParsedCount)); | |
280 | } | |
281 | ||
282 | HttpHdrScTarget * | |
45a58345 | 283 | HttpHdrSc::findTarget(const char *target) |
43ae1d95 | 284 | { |
24673a9e AJ |
285 | for (auto &sct : targets) { |
286 | if (sct.target.cmp(target) == 0) | |
287 | return &sct; | |
43ae1d95 | 288 | } |
289 | ||
aee3523a | 290 | return nullptr; |
43ae1d95 | 291 | } |
292 | ||
293 | HttpHdrScTarget * | |
45a58345 | 294 | HttpHdrSc::getMergedTarget(const char *ourtarget) |
43ae1d95 | 295 | { |
45a58345 | 296 | HttpHdrScTarget *sctus = findTarget(ourtarget); |
aee3523a | 297 | HttpHdrScTarget *sctgeneric = findTarget(nullptr); |
43ae1d95 | 298 | |
24673a9e AJ |
299 | /* W3C Edge Architecture Specification 1.0 section 3 |
300 | * | |
7c25db36 | 301 | * "If more than one is targeted at a surrogate, the most specific applies. |
24673a9e AJ |
302 | * For example, |
303 | * Surrogate-Control: max-age=60, no-store;abc | |
304 | * The surrogate that identified itself as 'abc' would apply no-store; | |
305 | * others would apply max-age=60. | |
306 | * | |
307 | * XXX: the if statements below will *merge* the no-store and max-age settings. | |
308 | */ | |
43ae1d95 | 309 | if (sctgeneric || sctus) { |
aee3523a | 310 | HttpHdrScTarget *sctusable = new HttpHdrScTarget(nullptr); |
43ae1d95 | 311 | |
312 | if (sctgeneric) | |
45a58345 | 313 | sctusable->mergeWith(sctgeneric); |
43ae1d95 | 314 | |
315 | if (sctus) | |
45a58345 | 316 | sctusable->mergeWith(sctus); |
43ae1d95 | 317 | |
318 | return sctusable; | |
319 | } | |
320 | ||
aee3523a | 321 | return nullptr; |
43ae1d95 | 322 | } |
70ac5b29 | 323 |