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