]>
Commit | Line | Data |
---|---|---|
43ae1d95 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 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. | |
129 | targets.emplace_front(target); | |
130 | sct = &targets.front(); | |
43ae1d95 | 131 | } |
132 | ||
133 | safe_free (temp); | |
134 | ||
4277583a | 135 | if (sct->isSet(type)) { |
43ae1d95 | 136 | if (type != SC_OTHER) |
7ec92ef0 | 137 | debugs(90, 2, "ignoring duplicate control-directive near '" << item << "' in '" << *str << "'"); |
43ae1d95 | 138 | |
4277583a | 139 | ++ scHeaderStats[type].repCount; |
43ae1d95 | 140 | |
141 | continue; | |
142 | } | |
143 | ||
45a58345 | 144 | /* process directives */ |
43ae1d95 | 145 | switch (type) { |
45a58345 FC |
146 | case SC_NO_STORE: |
147 | sct->noStore(true); | |
148 | break; | |
43ae1d95 | 149 | |
45a58345 FC |
150 | case SC_NO_STORE_REMOTE: |
151 | sct->noStoreRemote(true); | |
43ae1d95 | 152 | break; |
153 | ||
633b9845 A |
154 | case SC_MAX_AGE: { |
155 | int ma; | |
156 | if (p && httpHeaderParseInt(p, &ma)) { | |
157 | sct->maxAge(ma); | |
76d0bd38 AJ |
158 | |
159 | if ((p = strchr (p, '+'))) { | |
160 | int ms; | |
161 | ++p; //skip the + char | |
162 | if (httpHeaderParseInt(p, &ms)) { | |
163 | sct->maxStale(ms); | |
164 | } else { | |
165 | debugs(90, 2, "sc: invalid max-stale specs near '" << item << "'"); | |
166 | sct->clearMaxStale(); | |
167 | /* leave the max-age alone */ | |
168 | } | |
169 | } | |
633b9845 A |
170 | } else { |
171 | debugs(90, 2, "sc: invalid max-age specs near '" << item << "'"); | |
172 | sct->clearMaxAge(); | |
173 | } | |
174 | ||
633b9845 A |
175 | break; |
176 | } | |
45a58345 | 177 | |
43ae1d95 | 178 | case SC_CONTENT: |
179 | ||
633b9845 A |
180 | if ( p && httpHeaderParseQuotedString(p, vlen, &sct->content_)) { |
181 | sct->setMask(SC_CONTENT,true); // ugly but saves a copy | |
182 | } else { | |
bf8fe701 | 183 | debugs(90, 2, "sc: invalid content= quoted string near '" << item << "'"); |
45a58345 | 184 | sct->clearContent(); |
43ae1d95 | 185 | } |
633b9845 | 186 | break; |
43ae1d95 | 187 | |
45a58345 | 188 | case SC_OTHER: |
43ae1d95 | 189 | default: |
190 | break; | |
191 | } | |
192 | } | |
193 | ||
24673a9e | 194 | return !sc->targets.empty(); |
43ae1d95 | 195 | } |
196 | ||
24673a9e | 197 | /// XXX: this function should be in HttpHdrScTarget.cc |
43ae1d95 | 198 | void |
17802cf1 | 199 | HttpHdrScTarget::packInto(Packable * p) const |
43ae1d95 | 200 | { |
201 | http_hdr_sc_type flag; | |
202 | int pcount = 0; | |
45a58345 | 203 | assert (p); |
43ae1d95 | 204 | |
205 | for (flag = SC_NO_STORE; flag < SC_ENUM_END; ++flag) { | |
45a58345 | 206 | if (isSet(flag) && flag != SC_OTHER) { |
43ae1d95 | 207 | |
208 | /* print option name */ | |
4277583a | 209 | p->appendf((pcount ? ", %s" : "%s"), ScAttrs[flag].name); |
43ae1d95 | 210 | |
211 | /* handle options with values */ | |
212 | ||
213 | if (flag == SC_MAX_AGE) | |
4391cd15 | 214 | p->appendf("=%d", (int) max_age); |
43ae1d95 | 215 | |
216 | if (flag == SC_CONTENT) | |
4391cd15 | 217 | p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(content_)); |
43ae1d95 | 218 | |
95dc7ff4 | 219 | ++pcount; |
43ae1d95 | 220 | } |
221 | } | |
222 | ||
45a58345 | 223 | if (hasTarget()) |
4391cd15 | 224 | p->appendf(";" SQUIDSTRINGPH, SQUIDSTRINGPRINT(target)); |
43ae1d95 | 225 | } |
226 | ||
227 | void | |
17802cf1 | 228 | HttpHdrSc::packInto(Packable * p) const |
43ae1d95 | 229 | { |
45a58345 | 230 | assert(p); |
24673a9e AJ |
231 | for (const auto &t : targets) { |
232 | t.packInto(p); | |
43ae1d95 | 233 | } |
234 | } | |
235 | ||
43ae1d95 | 236 | /* negative max_age will clean old max_Age setting */ |
237 | void | |
45a58345 | 238 | HttpHdrSc::setMaxAge(char const *target, int max_age) |
43ae1d95 | 239 | { |
45a58345 | 240 | HttpHdrScTarget *sct = findTarget(target); |
43ae1d95 | 241 | |
242 | if (!sct) { | |
24673a9e AJ |
243 | targets.emplace_back(target); |
244 | sct = &targets.back(); | |
43ae1d95 | 245 | } |
246 | ||
45a58345 | 247 | sct->maxAge(max_age); |
43ae1d95 | 248 | } |
249 | ||
250 | void | |
45a58345 | 251 | HttpHdrSc::updateStats(StatHist * hist) const |
43ae1d95 | 252 | { |
24673a9e AJ |
253 | for (auto &t : targets) { |
254 | t.updateStats(hist); | |
43ae1d95 | 255 | } |
256 | } | |
257 | ||
258 | void | |
ced8def3 | 259 | httpHdrScTargetStatDumper(StoreEntry * sentry, int, double val, double, int count) |
43ae1d95 | 260 | { |
261 | extern const HttpHeaderStat *dump_stat; /* argh! */ | |
262 | const int id = (int) val; | |
4277583a FC |
263 | const bool valid_id = id >= 0 && id < SC_ENUM_END; |
264 | const char *name = valid_id ? ScAttrs[id].name : "INVALID"; | |
43ae1d95 | 265 | |
266 | if (count || valid_id) | |
267 | storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", | |
268 | id, name, count, xdiv(count, dump_stat->scParsedCount)); | |
269 | } | |
270 | ||
271 | void | |
ced8def3 | 272 | httpHdrScStatDumper(StoreEntry * sentry, int, double val, double, int count) |
43ae1d95 | 273 | { |
f53969cc | 274 | extern const HttpHeaderStat *dump_stat; /* argh! */ |
43ae1d95 | 275 | const int id = (int) val; |
4277583a FC |
276 | const bool valid_id = id >= 0 && id < SC_ENUM_END; |
277 | const char *name = valid_id ? ScAttrs[id].name : "INVALID"; | |
43ae1d95 | 278 | |
279 | if (count || valid_id) | |
280 | storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", | |
281 | id, name, count, xdiv(count, dump_stat->scParsedCount)); | |
282 | } | |
283 | ||
284 | HttpHdrScTarget * | |
45a58345 | 285 | HttpHdrSc::findTarget(const char *target) |
43ae1d95 | 286 | { |
24673a9e AJ |
287 | for (auto &sct : targets) { |
288 | if (sct.target.cmp(target) == 0) | |
289 | return &sct; | |
43ae1d95 | 290 | } |
291 | ||
aee3523a | 292 | return nullptr; |
43ae1d95 | 293 | } |
294 | ||
295 | HttpHdrScTarget * | |
45a58345 | 296 | HttpHdrSc::getMergedTarget(const char *ourtarget) |
43ae1d95 | 297 | { |
45a58345 | 298 | HttpHdrScTarget *sctus = findTarget(ourtarget); |
aee3523a | 299 | HttpHdrScTarget *sctgeneric = findTarget(nullptr); |
43ae1d95 | 300 | |
24673a9e AJ |
301 | /* W3C Edge Architecture Specification 1.0 section 3 |
302 | * | |
7c25db36 | 303 | * "If more than one is targeted at a surrogate, the most specific applies. |
24673a9e AJ |
304 | * For example, |
305 | * Surrogate-Control: max-age=60, no-store;abc | |
306 | * The surrogate that identified itself as 'abc' would apply no-store; | |
307 | * others would apply max-age=60. | |
308 | * | |
309 | * XXX: the if statements below will *merge* the no-store and max-age settings. | |
310 | */ | |
43ae1d95 | 311 | if (sctgeneric || sctus) { |
aee3523a | 312 | HttpHdrScTarget *sctusable = new HttpHdrScTarget(nullptr); |
43ae1d95 | 313 | |
314 | if (sctgeneric) | |
45a58345 | 315 | sctusable->mergeWith(sctgeneric); |
43ae1d95 | 316 | |
317 | if (sctus) | |
45a58345 | 318 | sctusable->mergeWith(sctus); |
43ae1d95 | 319 | |
320 | return sctusable; | |
321 | } | |
322 | ||
aee3523a | 323 | return nullptr; |
43ae1d95 | 324 | } |
70ac5b29 | 325 |