1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
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.
7  */
8 
9 /* DEBUG: section 65    HTTP Cache Control Header */
10 
11 #include "squid.h"
12 #include "base/LookupTable.h"
13 #include "HttpHdrCc.h"
14 #include "HttpHeader.h"
15 #include "HttpHeaderFieldStat.h"
16 #include "HttpHeaderStat.h"
17 #include "HttpHeaderTools.h"
18 #include "sbuf/SBuf.h"
19 #include "StatHist.h"
20 #include "Store.h"
21 #include "StrList.h"
22 #include "util.h"
23 
24 #include <map>
25 #include <vector>
26 #include <ostream>
27 
28 // invariant: row[j].id == j
29 static LookupTable<HttpHdrCcType>::Record CcAttrs[] = {
30     {"public", HttpHdrCcType::CC_PUBLIC},
31     {"private", HttpHdrCcType::CC_PRIVATE},
32     {"no-cache", HttpHdrCcType::CC_NO_CACHE},
33     {"no-store", HttpHdrCcType::CC_NO_STORE},
34     {"no-transform", HttpHdrCcType::CC_NO_TRANSFORM},
35     {"must-revalidate", HttpHdrCcType::CC_MUST_REVALIDATE},
36     {"proxy-revalidate", HttpHdrCcType::CC_PROXY_REVALIDATE},
37     {"max-age", HttpHdrCcType::CC_MAX_AGE},
38     {"s-maxage", HttpHdrCcType::CC_S_MAXAGE},
39     {"max-stale", HttpHdrCcType::CC_MAX_STALE},
40     {"min-fresh", HttpHdrCcType::CC_MIN_FRESH},
41     {"only-if-cached", HttpHdrCcType::CC_ONLY_IF_CACHED},
42     {"stale-if-error", HttpHdrCcType::CC_STALE_IF_ERROR},
43     {"immutable", HttpHdrCcType::CC_IMMUTABLE},
44     {"Other,", HttpHdrCcType::CC_OTHER}, /* ',' will protect from matches */
45     {nullptr, HttpHdrCcType::CC_ENUM_END}
46 };
47 LookupTable<HttpHdrCcType> ccLookupTable(HttpHdrCcType::CC_OTHER,CcAttrs);
48 std::vector<HttpHeaderFieldStat> ccHeaderStats(HttpHdrCcType::CC_ENUM_END);
49 
50 /// used to walk a table of http_header_cc_type structs
operator ++(HttpHdrCcType & aHeader)51 HttpHdrCcType &operator++ (HttpHdrCcType &aHeader)
52 {
53     int tmp = (int)aHeader;
54     aHeader = (HttpHdrCcType)(++tmp);
55     return aHeader;
56 }
57 
58 /// Module initialization hook
59 void
httpHdrCcInitModule(void)60 httpHdrCcInitModule(void)
61 {
62     // check invariant on initialization table
63     for (unsigned int j = 0; CcAttrs[j].name != nullptr; ++j) {
64         assert (static_cast<int>(CcAttrs[j].id) == j);
65     }
66 }
67 
68 void
clear()69 HttpHdrCc::clear()
70 {
71     *this=HttpHdrCc();
72 }
73 
74 bool
parse(const String & str)75 HttpHdrCc::parse(const String & str)
76 {
77     const char *item;
78     const char *p;      /* '=' parameter */
79     const char *pos = NULL;
80     int ilen;
81     int nlen;
82 
83     /* iterate through comma separated list */
84 
85     while (strListGetItem(&str, ',', &item, &ilen, &pos)) {
86         /* isolate directive name */
87 
88         if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
89             nlen = p - item;
90             ++p;
91         } else {
92             nlen = ilen;
93         }
94 
95         /* find type */
96         const HttpHdrCcType type = ccLookupTable.lookup(SBuf(item,nlen));
97 
98         // ignore known duplicate directives
99         if (isSet(type)) {
100             if (type != HttpHdrCcType::CC_OTHER) {
101                 debugs(65, 2, "hdr cc: ignoring duplicate cache-directive: near '" << item << "' in '" << str << "'");
102                 ++ ccHeaderStats[type].repCount;
103                 continue;
104             }
105         }
106 
107         /* special-case-parsing and attribute-setting */
108         switch (type) {
109 
110         case HttpHdrCcType::CC_MAX_AGE:
111             if (!p || !httpHeaderParseInt(p, &max_age) || max_age < 0) {
112                 debugs(65, 2, "cc: invalid max-age specs near '" << item << "'");
113                 clearMaxAge();
114             } else {
115                 setMask(type,true);
116             }
117             break;
118 
119         case HttpHdrCcType::CC_S_MAXAGE:
120             if (!p || !httpHeaderParseInt(p, &s_maxage) || s_maxage < 0) {
121                 debugs(65, 2, "cc: invalid s-maxage specs near '" << item << "'");
122                 clearSMaxAge();
123             } else {
124                 setMask(type,true);
125             }
126             break;
127 
128         case HttpHdrCcType::CC_MAX_STALE:
129             if (!p || !httpHeaderParseInt(p, &max_stale) || max_stale < 0) {
130                 debugs(65, 2, "cc: max-stale directive is valid without value");
131                 maxStale(MAX_STALE_ANY);
132             } else {
133                 setMask(type,true);
134             }
135             break;
136 
137         case HttpHdrCcType::CC_MIN_FRESH:
138             if (!p || !httpHeaderParseInt(p, &min_fresh) || min_fresh < 0) {
139                 debugs(65, 2, "cc: invalid min-fresh specs near '" << item << "'");
140                 clearMinFresh();
141             } else {
142                 setMask(type,true);
143             }
144             break;
145 
146         case HttpHdrCcType::CC_STALE_IF_ERROR:
147             if (!p || !httpHeaderParseInt(p, &stale_if_error) || stale_if_error < 0) {
148                 debugs(65, 2, "cc: invalid stale-if-error specs near '" << item << "'");
149                 clearStaleIfError();
150             } else {
151                 setMask(type,true);
152             }
153             break;
154 
155         case HttpHdrCcType::CC_PRIVATE: {
156             String temp;
157             if (!p)  {
158                 // Value parameter is optional.
159                 private_.clean();
160             }            else if (/* p &&*/ httpHeaderParseQuotedString(p, (ilen-nlen-1), &temp)) {
161                 private_.append(temp);
162             }            else {
163                 debugs(65, 2, "cc: invalid private= specs near '" << item << "'");
164             }
165             // to be safe we ignore broken parameters, but always remember the 'private' part.
166             setMask(type,true);
167         }
168         break;
169 
170         case HttpHdrCcType::CC_NO_CACHE: {
171             String temp;
172             if (!p) {
173                 // On Requests, missing value parameter is expected syntax.
174                 // On Responses, value parameter is optional.
175                 setMask(type,true);
176                 no_cache.clean();
177             } else if (/* p &&*/ httpHeaderParseQuotedString(p, (ilen-nlen-1), &temp)) {
178                 // On Requests, a value parameter is invalid syntax.
179                 // XXX: identify when parsing request header and dump err message here.
180                 setMask(type,true);
181                 no_cache.append(temp);
182             } else {
183                 debugs(65, 2, "cc: invalid no-cache= specs near '" << item << "'");
184             }
185         }
186         break;
187 
188         case HttpHdrCcType::CC_PUBLIC:
189             Public(true);
190             break;
191         case HttpHdrCcType::CC_NO_STORE:
192             noStore(true);
193             break;
194         case HttpHdrCcType::CC_NO_TRANSFORM:
195             noTransform(true);
196             break;
197         case HttpHdrCcType::CC_MUST_REVALIDATE:
198             mustRevalidate(true);
199             break;
200         case HttpHdrCcType::CC_PROXY_REVALIDATE:
201             proxyRevalidate(true);
202             break;
203         case HttpHdrCcType::CC_ONLY_IF_CACHED:
204             onlyIfCached(true);
205             break;
206         case HttpHdrCcType::CC_IMMUTABLE:
207             Immutable(true);
208             break;
209 
210         case HttpHdrCcType::CC_OTHER:
211             if (other.size())
212                 other.append(", ");
213 
214             other.append(item, ilen);
215             break;
216 
217         default:
218             /* note that we ignore most of '=' specs (RFCVIOLATION) */
219             break;
220         }
221     }
222 
223     return (mask != 0);
224 }
225 
226 void
packInto(Packable * p) const227 HttpHdrCc::packInto(Packable * p) const
228 {
229     // optimization: if the mask is empty do nothing
230     if (mask==0)
231         return;
232 
233     HttpHdrCcType flag;
234     int pcount = 0;
235     assert(p);
236 
237     for (flag = HttpHdrCcType::CC_PUBLIC; flag < HttpHdrCcType::CC_ENUM_END; ++flag) {
238         if (isSet(flag) && flag != HttpHdrCcType::CC_OTHER) {
239 
240             /* print option name for all options */
241             p->appendf((pcount ? ", %s": "%s") , CcAttrs[flag].name);
242 
243             /* for all options having values, "=value" after the name */
244             switch (flag) {
245             case HttpHdrCcType::CC_PUBLIC:
246                 break;
247             case HttpHdrCcType::CC_PRIVATE:
248                 if (private_.size())
249                     p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(private_));
250                 break;
251 
252             case HttpHdrCcType::CC_NO_CACHE:
253                 if (no_cache.size())
254                     p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(no_cache));
255                 break;
256             case HttpHdrCcType::CC_NO_STORE:
257                 break;
258             case HttpHdrCcType::CC_NO_TRANSFORM:
259                 break;
260             case HttpHdrCcType::CC_MUST_REVALIDATE:
261                 break;
262             case HttpHdrCcType::CC_PROXY_REVALIDATE:
263                 break;
264             case HttpHdrCcType::CC_MAX_AGE:
265                 p->appendf("=%d", max_age);
266                 break;
267             case HttpHdrCcType::CC_S_MAXAGE:
268                 p->appendf("=%d", s_maxage);
269                 break;
270             case HttpHdrCcType::CC_MAX_STALE:
271                 /* max-stale's value is optional.
272                   If we didn't receive it, don't send it */
273                 if (max_stale != MAX_STALE_ANY)
274                     p->appendf("=%d", max_stale);
275                 break;
276             case HttpHdrCcType::CC_MIN_FRESH:
277                 p->appendf("=%d", min_fresh);
278                 break;
279             case HttpHdrCcType::CC_ONLY_IF_CACHED:
280                 break;
281             case HttpHdrCcType::CC_STALE_IF_ERROR:
282                 p->appendf("=%d", stale_if_error);
283                 break;
284             case HttpHdrCcType::CC_IMMUTABLE:
285                 break;
286             case HttpHdrCcType::CC_OTHER:
287             case HttpHdrCcType::CC_ENUM_END:
288                 // done below after the loop
289                 break;
290             }
291 
292             ++pcount;
293         }
294     }
295 
296     if (other.size() != 0)
297         p->appendf((pcount ? ", " SQUIDSTRINGPH : SQUIDSTRINGPH), SQUIDSTRINGPRINT(other));
298 }
299 
300 void
httpHdrCcUpdateStats(const HttpHdrCc * cc,StatHist * hist)301 httpHdrCcUpdateStats(const HttpHdrCc * cc, StatHist * hist)
302 {
303     assert(cc);
304 
305     for (HttpHdrCcType c = HttpHdrCcType::CC_PUBLIC; c < HttpHdrCcType::CC_ENUM_END; ++c)
306         if (cc->isSet(c))
307             hist->count(c);
308 }
309 
310 void
httpHdrCcStatDumper(StoreEntry * sentry,int,double val,double,int count)311 httpHdrCcStatDumper(StoreEntry * sentry, int, double val, double, int count)
312 {
313     extern const HttpHeaderStat *dump_stat; /* argh! */
314     const int id = static_cast<int>(val);
315     const bool valid_id = id >= 0 && id < static_cast<int>(HttpHdrCcType::CC_ENUM_END);
316     const char *name = valid_id ? CcAttrs[id].name : "INVALID";
317 
318     if (count || valid_id)
319         storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
320                           id, name, count, xdiv(count, dump_stat->ccParsedCount));
321 }
322 
323 std::ostream &
operator <<(std::ostream & s,HttpHdrCcType c)324 operator<< (std::ostream &s, HttpHdrCcType c)
325 {
326     const unsigned char ic = static_cast<int>(c);
327     if (c < HttpHdrCcType::CC_ENUM_END)
328         s << CcAttrs[ic].name << '[' << ic << ']' ;
329     else
330         s << "*invalid hdrcc* [" << ic << ']';
331     return s;
332 }
333 
334 #if !_USE_INLINE_
335 #include "HttpHdrCc.cci"
336 #endif
337 
338