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 66    HTTP Header Tools */
10 
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "acl/Gadgets.h"
14 #include "base/EnumIterator.h"
15 #include "client_side.h"
16 #include "client_side_request.h"
17 #include "comm/Connection.h"
18 #include "compat/strtoll.h"
19 #include "ConfigParser.h"
20 #include "fde.h"
21 #include "globals.h"
22 #include "http/RegisteredHeaders.h"
23 #include "http/Stream.h"
24 #include "HttpHdrContRange.h"
25 #include "HttpHeader.h"
26 #include "HttpHeaderFieldInfo.h"
27 #include "HttpHeaderTools.h"
28 #include "HttpRequest.h"
29 #include "MemBuf.h"
30 #include "SquidConfig.h"
31 #include "Store.h"
32 #include "StrList.h"
33 
34 #if USE_OPENSSL
35 #include "ssl/support.h"
36 #endif
37 
38 #include <algorithm>
39 #include <cerrno>
40 #include <string>
41 
42 static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs);
43 static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
44 
45 void
httpHeaderMaskInit(HttpHeaderMask * mask,int value)46 httpHeaderMaskInit(HttpHeaderMask * mask, int value)
47 {
48     memset(mask, value, sizeof(*mask));
49 }
50 
51 /* same as httpHeaderPutStr, but formats the string using snprintf first */
52 void
httpHeaderPutStrf(HttpHeader * hdr,Http::HdrType id,const char * fmt,...)53 httpHeaderPutStrf(HttpHeader * hdr, Http::HdrType id, const char *fmt,...)
54 {
55     va_list args;
56     va_start(args, fmt);
57 
58     httpHeaderPutStrvf(hdr, id, fmt, args);
59     va_end(args);
60 }
61 
62 /* used by httpHeaderPutStrf */
63 static void
httpHeaderPutStrvf(HttpHeader * hdr,Http::HdrType id,const char * fmt,va_list vargs)64 httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs)
65 {
66     MemBuf mb;
67     mb.init();
68     mb.vappendf(fmt, vargs);
69     hdr->putStr(id, mb.buf);
70     mb.clean();
71 }
72 
73 /** wrapper arrounf PutContRange */
74 void
httpHeaderAddContRange(HttpHeader * hdr,HttpHdrRangeSpec spec,int64_t ent_len)75 httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
76 {
77     HttpHdrContRange *cr = httpHdrContRangeCreate();
78     assert(hdr && ent_len >= 0);
79     httpHdrContRangeSet(cr, spec, ent_len);
80     hdr->putContRange(cr);
81     delete cr;
82 }
83 
84 /**
85  * \return true if a given directive is found in the Connection header field-value.
86  *
87  * \note if no Connection header exists we may check the Proxy-Connection header
88  */
89 bool
httpHeaderHasConnDir(const HttpHeader * hdr,const char * directive)90 httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive)
91 {
92     String list;
93 
94     /* what type of header do we have? */
95     if (hdr->getList(Http::HdrType::CONNECTION, &list))
96         return strListIsMember(&list, directive, ',') != 0;
97 
98 #if USE_HTTP_VIOLATIONS
99     if (hdr->getList(Http::HdrType::PROXY_CONNECTION, &list))
100         return strListIsMember(&list, directive, ',') != 0;
101 #endif
102 
103     // else, no connection header for it to exist in
104     return false;
105 }
106 
107 /** handy to printf prefixes of potentially very long buffers */
108 const char *
getStringPrefix(const char * str,size_t sz)109 getStringPrefix(const char *str, size_t sz)
110 {
111 #define SHORT_PREFIX_SIZE 512
112     LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
113     xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
114     return buf;
115 }
116 
117 /**
118  * parses an int field, complains if soemthing went wrong, returns true on
119  * success
120  */
121 int
httpHeaderParseInt(const char * start,int * value)122 httpHeaderParseInt(const char *start, int *value)
123 {
124     assert(value);
125     *value = atoi(start);
126 
127     if (!*value && !xisdigit(*start)) {
128         debugs(66, 2, "failed to parse an int header field near '" << start << "'");
129         return 0;
130     }
131 
132     return 1;
133 }
134 
135 bool
httpHeaderParseOffset(const char * start,int64_t * value,char ** endPtr)136 httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
137 {
138     char *end = nullptr;
139     errno = 0;
140     const int64_t res = strtoll(start, &end, 10);
141     if (errno && !res) {
142         debugs(66, 7, "failed to parse malformed offset in " << start);
143         return false;
144     }
145     if (errno == ERANGE && (res == LLONG_MIN || res == LLONG_MAX)) { // no overflow
146         debugs(66, 7, "failed to parse huge offset in " << start);
147         return false;
148     }
149     if (start == end) {
150         debugs(66, 7, "failed to parse empty offset");
151         return false;
152     }
153     *value = res;
154     if (endPtr)
155         *endPtr = end;
156     debugs(66, 7, "offset " << start << " parsed as " << res);
157     return true;
158 }
159 
160 /**
161  * Parses a quoted-string field (RFC 2616 section 2.2), complains if
162  * something went wrong, returns non-zero on success.
163  * Un-escapes quoted-pair characters found within the string.
164  * start should point at the first double-quote.
165  */
166 int
httpHeaderParseQuotedString(const char * start,const int len,String * val)167 httpHeaderParseQuotedString(const char *start, const int len, String *val)
168 {
169     const char *end, *pos;
170     val->clean();
171     if (*start != '"') {
172         debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
173         return 0;
174     }
175     pos = start + 1;
176 
177     while (*pos != '"' && len > (pos-start)) {
178 
179         if (*pos =='\r') {
180             ++pos;
181             if ((pos-start) > len || *pos != '\n') {
182                 debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
183                        << " bytes into '" << start << "'");
184                 val->clean();
185                 return 0;
186             }
187         }
188 
189         if (*pos == '\n') {
190             ++pos;
191             if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
192                 debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
193                 val->clean();
194                 return 0;
195             }
196             // TODO: replace the entire LWS with a space
197             val->append(" ");
198             ++pos;
199             debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
200             continue;
201         }
202 
203         bool quoted = (*pos == '\\');
204         if (quoted) {
205             ++pos;
206             if (!*pos || (pos-start) > len) {
207                 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
208                 val->clean();
209                 return 0;
210             }
211         }
212         end = pos;
213         while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
214             ++end;
215         if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
216             debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
217                    << " bytes into '" << start << "'");
218             val->clean();
219             return 0;
220         }
221         val->append(pos, end-pos);
222         pos = end;
223     }
224 
225     if (*pos != '\"') {
226         debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
227         val->clean();
228         return 0;
229     }
230     /* Make sure it's defined even if empty "" */
231     if (!val->termedBuf())
232         val->limitInit("", 0);
233     return 1;
234 }
235 
236 SBuf
httpHeaderQuoteString(const char * raw)237 httpHeaderQuoteString(const char *raw)
238 {
239     assert(raw);
240 
241     // TODO: Optimize by appending a sequence of characters instead of a char.
242     // This optimization may be easier with Tokenizer after raw becomes SBuf.
243 
244     // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
245     // quoted-string except where necessary" (i.e., DQUOTE and backslash)
246     bool needInnerQuote = false;
247     for (const char *s = raw; !needInnerQuote &&  *s; ++s)
248         needInnerQuote = *s == '"' || *s == '\\';
249 
250     SBuf quotedStr;
251     quotedStr.append('"');
252 
253     if (needInnerQuote) {
254         for (const char *s = raw; *s; ++s) {
255             if (*s == '"' || *s == '\\')
256                 quotedStr.append('\\');
257             quotedStr.append(*s);
258         }
259     } else {
260         quotedStr.append(raw);
261     }
262 
263     quotedStr.append('"');
264     return quotedStr;
265 }
266 
267 /**
268  * Checks the anonymizer (header_access) configuration.
269  *
270  * \retval 0    Header is explicitly blocked for removal
271  * \retval 1    Header is explicitly allowed
272  * \retval 1    Header has been replaced, the current version can be used.
273  * \retval 1    Header has no access controls to test
274  */
275 static int
httpHdrMangle(HttpHeaderEntry * e,HttpRequest * request,HeaderManglers * hms)276 httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms)
277 {
278     int retval;
279 
280     assert(e);
281 
282     const headerMangler *hm = hms->find(*e);
283 
284     /* mangler or checklist went away. default allow */
285     if (!hm || !hm->access_list) {
286         debugs(66, 7, "couldn't find mangler or access list. Allowing");
287         return 1;
288     }
289 
290     ACLFilledChecklist checklist(hm->access_list, request, NULL);
291 
292     if (checklist.fastCheck().allowed()) {
293         /* aclCheckFast returns true for allow. */
294         debugs(66, 7, "checklist for mangler is positive. Mangle");
295         retval = 1;
296     } else if (NULL == hm->replacement) {
297         /* It was denied, and we don't have any replacement */
298         debugs(66, 7, "checklist denied, we have no replacement. Pass");
299         retval = 0;
300     } else {
301         /* It was denied, but we have a replacement. Replace the
302          * header on the fly, and return that the new header
303          * is allowed.
304          */
305         debugs(66, 7, "checklist denied but we have replacement. Replace");
306         e->value = hm->replacement;
307         retval = 1;
308     }
309 
310     return retval;
311 }
312 
313 /** Mangles headers for a list of headers. */
314 void
httpHdrMangleList(HttpHeader * l,HttpRequest * request,const AccessLogEntryPointer & al,req_or_rep_t req_or_rep)315 httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
316 {
317     HttpHeaderEntry *e;
318     HttpHeaderPos p = HttpHeaderInitPos;
319 
320     /* check with anonymizer tables */
321     HeaderManglers *hms = nullptr;
322     HeaderWithAclList *headersAdd = nullptr;
323 
324     switch (req_or_rep) {
325     case ROR_REQUEST:
326         hms = Config.request_header_access;
327         headersAdd = Config.request_header_add;
328         break;
329     case ROR_REPLY:
330         hms = Config.reply_header_access;
331         headersAdd = Config.reply_header_add;
332         break;
333     }
334 
335     if (hms) {
336         int headers_deleted = 0;
337         while ((e = l->getEntry(&p))) {
338             if (0 == httpHdrMangle(e, request, hms))
339                 l->delAt(p, headers_deleted);
340         }
341 
342         if (headers_deleted)
343             l->refreshMask();
344     }
345 
346     if (headersAdd && !headersAdd->empty()) {
347         httpHdrAdd(l, request, al, *headersAdd);
348     }
349 }
350 
351 static
header_mangler_clean(headerMangler & m)352 void header_mangler_clean(headerMangler &m)
353 {
354     aclDestroyAccessList(&m.access_list);
355     safe_free(m.replacement);
356 }
357 
358 static
header_mangler_dump_access(StoreEntry * entry,const char * option,const headerMangler & m,const char * name)359 void header_mangler_dump_access(StoreEntry * entry, const char *option,
360                                 const headerMangler &m, const char *name)
361 {
362     if (m.access_list != NULL) {
363         storeAppendPrintf(entry, "%s ", option);
364         dump_acl_access(entry, name, m.access_list);
365     }
366 }
367 
368 static
header_mangler_dump_replacement(StoreEntry * entry,const char * option,const headerMangler & m,const char * name)369 void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
370                                      const headerMangler &m, const char *name)
371 {
372     if (m.replacement)
373         storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
374 }
375 
HeaderManglers()376 HeaderManglers::HeaderManglers()
377 {
378     memset(known, 0, sizeof(known));
379     memset(&all, 0, sizeof(all));
380 }
381 
~HeaderManglers()382 HeaderManglers::~HeaderManglers()
383 {
384     for (auto i : WholeEnum<Http::HdrType>())
385         header_mangler_clean(known[i]);
386 
387     for (auto i : custom)
388         header_mangler_clean(i.second);
389 
390     header_mangler_clean(all);
391 }
392 
393 void
dumpAccess(StoreEntry * entry,const char * name) const394 HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
395 {
396     for (auto id : WholeEnum<Http::HdrType>())
397         header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
398 
399     for (auto i : custom)
400         header_mangler_dump_access(entry, name, i.second, i.first.c_str());
401 
402     header_mangler_dump_access(entry, name, all, "All");
403 }
404 
405 void
dumpReplacement(StoreEntry * entry,const char * name) const406 HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
407 {
408     for (auto id : WholeEnum<Http::HdrType>()) {
409         header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
410     }
411 
412     for (auto i: custom) {
413         header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
414     }
415 
416     header_mangler_dump_replacement(entry, name, all, "All");
417 }
418 
419 headerMangler *
track(const char * name)420 HeaderManglers::track(const char *name)
421 {
422     if (strcmp(name, "All") == 0)
423         return &all;
424 
425     const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id;
426 
427     if (id != Http::HdrType::BAD_HDR)
428         return &known[id];
429 
430     if (strcmp(name, "Other") == 0)
431         return &known[Http::HdrType::OTHER];
432 
433     return &custom[name];
434 }
435 
436 void
setReplacement(const char * name,const char * value)437 HeaderManglers::setReplacement(const char *name, const char *value)
438 {
439     // for backword compatibility, we allow replacements to be configured
440     // for headers w/o access rules, but such replacements are ignored
441     headerMangler *m = track(name);
442 
443     safe_free(m->replacement); // overwrite old value if any
444     m->replacement = xstrdup(value);
445 }
446 
447 const headerMangler *
find(const HttpHeaderEntry & e) const448 HeaderManglers::find(const HttpHeaderEntry &e) const
449 {
450     // a known header with a configured ACL list
451     if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) &&
452             known[e.id].access_list)
453         return &known[e.id];
454 
455     // a custom header
456     if (e.id == Http::HdrType::OTHER) {
457         // does it have an ACL list configured?
458         // Optimize: use a name type that we do not need to convert to here
459         const ManglersByName::const_iterator i = custom.find(e.name.termedBuf());
460         if (i != custom.end())
461             return &i->second;
462     }
463 
464     // Next-to-last resort: "Other" rules match any custom header
465     if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list)
466         return &known[Http::HdrType::OTHER];
467 
468     // Last resort: "All" rules match any header
469     if (all.access_list)
470         return &all;
471 
472     return NULL;
473 }
474 
475 void
httpHdrAdd(HttpHeader * heads,HttpRequest * request,const AccessLogEntryPointer & al,HeaderWithAclList & headersAdd)476 httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
477 {
478     ACLFilledChecklist checklist(NULL, request, NULL);
479 
480     checklist.al = al;
481     if (al && al->reply) {
482         checklist.reply = al->reply;
483         HTTPMSGLOCK(checklist.reply);
484     }
485 
486     for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
487         if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
488             const char *fieldValue = NULL;
489             MemBuf mb;
490             if (hwa->quoted) {
491                 if (al != NULL) {
492                     mb.init();
493                     hwa->valueFormat->assemble(mb, al, 0);
494                     fieldValue = mb.content();
495                 }
496             } else {
497                 fieldValue = hwa->fieldValue.c_str();
498             }
499 
500             if (!fieldValue || fieldValue[0] == '\0')
501                 fieldValue = "-";
502 
503             HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
504                     fieldValue);
505             heads->addEntry(e);
506         }
507     }
508 }
509 
510