]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHeaderTools.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / HttpHeaderTools.cc
CommitLineData
7faf2bdb 1/*
f70aedc4 2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
e25c139f 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.
7faf2bdb 7 */
8
bbc27441
AJ
9/* DEBUG: section 66 HTTP Header Tools */
10
582c2af2 11#include "squid.h"
c0941a6a 12#include "acl/FilledChecklist.h"
3b07476b 13#include "acl/Gadgets.h"
81ab22b6 14#include "base/EnumIterator.h"
582c2af2 15#include "client_side.h"
602d9612 16#include "client_side_request.h"
f4698e0b 17#include "comm/Connection.h"
27bc2077 18#include "compat/strtoll.h"
d7f4a0b7 19#include "ConfigParser.h"
f4698e0b 20#include "fde.h"
602d9612 21#include "globals.h"
4277583a 22#include "http/RegisteredHeaders.h"
d3dddfb5 23#include "http/Stream.h"
27bc2077
AJ
24#include "HttpHdrContRange.h"
25#include "HttpHeader.h"
79cb238d 26#include "HttpHeaderFieldInfo.h"
3b07476b
CT
27#include "HttpHeaderTools.h"
28#include "HttpRequest.h"
0eb49b6d 29#include "MemBuf.h"
4d5904f7 30#include "SquidConfig.h"
582c2af2 31#include "Store.h"
28204b3b 32#include "StrList.h"
582c2af2 33
cb4f4424 34#if USE_OPENSSL
f4698e0b
CT
35#include "ssl/support.h"
36#endif
582c2af2 37
f4698e0b 38#include <algorithm>
1a30fdf5 39#include <cerrno>
f4698e0b 40#include <string>
7faf2bdb 41
789217a2 42static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs);
cde8f31b 43static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
de336bbe 44
d8b249ef 45void
97474590 46httpHeaderMaskInit(HttpHeaderMask * mask, int value)
d8b249ef 47{
97474590 48 memset(mask, value, sizeof(*mask));
d8b249ef 49}
50
2246b732 51/* same as httpHeaderPutStr, but formats the string using snprintf first */
2246b732 52void
789217a2 53httpHeaderPutStrf(HttpHeader * hdr, Http::HdrType id, const char *fmt,...)
2246b732 54{
9bc73deb 55 va_list args;
56 va_start(args, fmt);
62e76326 57
2246b732 58 httpHeaderPutStrvf(hdr, id, fmt, args);
59 va_end(args);
60}
61
62/* used by httpHeaderPutStrf */
63static void
789217a2 64httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs)
2246b732 65{
2246b732 66 MemBuf mb;
2fe7eff9 67 mb.init();
8ed3716a 68 mb.vappendf(fmt, vargs);
a9925b40 69 hdr->putStr(id, mb.buf);
2fe7eff9 70 mb.clean();
2246b732 71}
72
9035d1d5 73/** wrapper arrounf PutContRange */
d192d11f 74void
47f6e231 75httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
d192d11f 76{
77 HttpHdrContRange *cr = httpHdrContRangeCreate();
78 assert(hdr && ent_len >= 0);
79 httpHdrContRangeSet(cr, spec, ent_len);
a9925b40 80 hdr->putContRange(cr);
3c670b50 81 delete cr;
d192d11f 82}
83
9035d1d5 84/**
1c4feb50
AJ
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
99edd1c3 88 */
1c4feb50 89bool
d5f18517 90httpHeaderHasConnDir(const HttpHeader * hdr, const SBuf &directive)
99edd1c3 91{
30abd221 92 String list;
1c4feb50 93
9bc73deb 94 /* what type of header do we have? */
1c4feb50
AJ
95 if (hdr->getList(Http::HdrType::CONNECTION, &list))
96 return strListIsMember(&list, directive, ',') != 0;
62e76326 97
95e78500 98#if USE_HTTP_VIOLATIONS
1c4feb50
AJ
99 if (hdr->getList(Http::HdrType::PROXY_CONNECTION, &list))
100 return strListIsMember(&list, directive, ',') != 0;
95e78500 101#endif
30abd221 102
1c4feb50
AJ
103 // else, no connection header for it to exist in
104 return false;
99edd1c3 105}
106
b50e327b 107/** handy to printf prefixes of potentially very long buffers */
7faf2bdb 108const char *
81858ebc 109getStringPrefix(const char *str, size_t sz)
7faf2bdb 110{
d8b249ef 111#define SHORT_PREFIX_SIZE 512
7faf2bdb 112 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
81858ebc 113 xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
7faf2bdb 114 return buf;
115}
b5107edb 116
b50e327b 117/**
2f8abb64 118 * parses an int field, complains if something went wrong, returns true on
b5107edb 119 * success
120 */
121int
122httpHeaderParseInt(const char *start, int *value)
123{
124 assert(value);
125 *value = atoi(start);
62e76326 126
b6a2f15e 127 if (!*value && !xisdigit(*start)) {
bf8fe701 128 debugs(66, 2, "failed to parse an int header field near '" << start << "'");
62e76326 129 return 0;
b5107edb 130 }
62e76326 131
b5107edb 132 return 1;
133}
134
a1b9ec20
AR
135bool
136httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
47f6e231 137{
a1b9ec20 138 char *end = nullptr;
66e255bc 139 errno = 0;
a1b9ec20
AR
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;
81ab22b6 152 }
47f6e231 153 *value = res;
a1b9ec20
AR
154 if (endPtr)
155 *endPtr = end;
81ab22b6 156 debugs(66, 7, "offset " << start << " parsed as " << res);
a1b9ec20 157 return true;
47f6e231 158}
159
b50e327b
AJ
160/**
161 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
43ae1d95 162 * something went wrong, returns non-zero on success.
34460e19 163 * Un-escapes quoted-pair characters found within the string.
229af339 164 * start should point at the first double-quote.
43ae1d95 165 */
166int
34460e19 167httpHeaderParseQuotedString(const char *start, const int len, String *val)
43ae1d95 168{
169 const char *end, *pos;
30abd221 170 val->clean();
9abd1514 171 if (*start != '"') {
5f5ddef6 172 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
6d97f5f1 173 return 0;
9abd1514 174 }
43ae1d95 175 pos = start + 1;
176
34460e19 177 while (*pos != '"' && len > (pos-start)) {
5f5ddef6
CT
178
179 if (*pos =='\r') {
95dc7ff4 180 ++pos;
5f5ddef6
CT
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') {
95dc7ff4 190 ++pos;
5f5ddef6
CT
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(" ");
95dc7ff4 198 ++pos;
5f5ddef6
CT
199 debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
200 continue;
201 }
202
a0133f10 203 bool quoted = (*pos == '\\');
5f5ddef6 204 if (quoted) {
95dc7ff4 205 ++pos;
5f5ddef6
CT
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 }
6d97f5f1 211 }
34460e19 212 end = pos;
c4b86597 213 while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
95dc7ff4 214 ++end;
c4b86597 215 if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
5f5ddef6 216 debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
34460e19
AJ
217 << " bytes into '" << start << "'");
218 val->clean();
219 return 0;
220 }
6d97f5f1
A
221 val->append(pos, end-pos);
222 pos = end;
43ae1d95 223 }
5f5ddef6
CT
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 }
9abd1514 230 /* Make sure it's defined even if empty "" */
b38b26cb 231 if (!val->termedBuf())
2fe0439c 232 val->assign("", 0);
9abd1514 233 return 1;
43ae1d95 234}
235
e7ce227f 236SBuf
22381a6b
AR
237httpHeaderQuoteString(const char *raw)
238{
239 assert(raw);
240
3cc0f4e7
AR
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
e7ce227f
AR
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)
22381a6b
AR
246 bool needInnerQuote = false;
247 for (const char *s = raw; !needInnerQuote && *s; ++s)
248 needInnerQuote = *s == '"' || *s == '\\';
249
e7ce227f 250 SBuf quotedStr;
22381a6b
AR
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 }
e7ce227f 262
22381a6b
AR
263 quotedStr.append('"');
264 return quotedStr;
265}
266
b50e327b
AJ
267/**
268 * Checks the anonymizer (header_access) configuration.
4f4611bf 269 *
b50e327b
AJ
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
6bccf575 274 */
2d72d4fd 275static int
82a9ea6c 276httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms, const AccessLogEntryPointer &al)
6bccf575 277{
e9b1e111 278 int retval;
279
6bccf575 280 assert(e);
8c01ada0 281
001d55dc 282 const headerMangler *hm = hms->find(*e);
3b07476b 283
b50e327b 284 /* mangler or checklist went away. default allow */
af6a12ee 285 if (!hm || !hm->access_list) {
81ab22b6 286 debugs(66, 7, "couldn't find mangler or access list. Allowing");
b50e327b
AJ
287 return 1;
288 }
289
c0941a6a 290 ACLFilledChecklist checklist(hm->access_list, request, NULL);
62e76326 291
82a9ea6c
EB
292 checklist.al = al;
293 if (al && al->reply) {
294 checklist.reply = al->reply.getRaw();
295 HTTPMSGLOCK(checklist.reply);
296 }
297
1c2b4465
CT
298 // XXX: The two "It was denied" clauses below mishandle cases with no
299 // matching rules, violating the "If no rules within the set have matching
300 // ACLs, the header field is left as is" promise in squid.conf.
301 // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade().
06bf5384 302 if (checklist.fastCheck().allowed()) {
b50e327b 303 /* aclCheckFast returns true for allow. */
81ab22b6 304 debugs(66, 7, "checklist for mangler is positive. Mangle");
62e76326 305 retval = 1;
a453662f 306 } else if (NULL == hm->replacement) {
62e76326 307 /* It was denied, and we don't have any replacement */
81ab22b6 308 debugs(66, 7, "checklist denied, we have no replacement. Pass");
1c2b4465 309 // XXX: We said "Pass", but the caller will delete on zero retval.
62e76326 310 retval = 0;
a453662f 311 } else {
62e76326 312 /* It was denied, but we have a replacement. Replace the
313 * header on the fly, and return that the new header
314 * is allowed.
315 */
81ab22b6 316 debugs(66, 7, "checklist denied but we have replacement. Replace");
62e76326 317 e->value = hm->replacement;
318 retval = 1;
a453662f 319 }
e9b1e111 320
e9b1e111 321 return retval;
6bccf575 322}
323
b50e327b 324/** Mangles headers for a list of headers. */
6bccf575 325void
cde8f31b 326httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
6bccf575 327{
328 HttpHeaderEntry *e;
329 HttpHeaderPos p = HttpHeaderInitPos;
62e76326 330
cde8f31b
NH
331 /* check with anonymizer tables */
332 HeaderManglers *hms = nullptr;
333 HeaderWithAclList *headersAdd = nullptr;
334
335 switch (req_or_rep) {
b7290021
SM
336 case ROR_REQUEST:
337 hms = Config.request_header_access;
338 headersAdd = Config.request_header_add;
339 break;
340 case ROR_REPLY:
341 hms = Config.reply_header_access;
342 headersAdd = Config.reply_header_add;
343 break;
cde8f31b
NH
344 }
345
346 if (hms) {
347 int headers_deleted = 0;
348 while ((e = l->getEntry(&p))) {
82a9ea6c 349 if (httpHdrMangle(e, request, hms, al) == 0)
cde8f31b
NH
350 l->delAt(p, headers_deleted);
351 }
ba9fb01d 352
cde8f31b
NH
353 if (headers_deleted)
354 l->refreshMask();
355 }
356
357 if (headersAdd && !headersAdd->empty()) {
358 httpHdrAdd(l, request, al, *headersAdd);
359 }
6bccf575 360}
5967c0bf 361
3b07476b 362static
001d55dc 363void header_mangler_clean(headerMangler &m)
3b07476b
CT
364{
365 aclDestroyAccessList(&m.access_list);
366 safe_free(m.replacement);
367}
368
369static
370void header_mangler_dump_access(StoreEntry * entry, const char *option,
001d55dc 371 const headerMangler &m, const char *name)
3b07476b
CT
372{
373 if (m.access_list != NULL) {
374 storeAppendPrintf(entry, "%s ", option);
375 dump_acl_access(entry, name, m.access_list);
376 }
377}
378
379static
380void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
001d55dc 381 const headerMangler &m, const char *name)
3b07476b
CT
382{
383 if (m.replacement)
384 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
385}
386
387HeaderManglers::HeaderManglers()
388{
389 memset(known, 0, sizeof(known));
390 memset(&all, 0, sizeof(all));
391}
392
393HeaderManglers::~HeaderManglers()
394{
81ab22b6 395 for (auto i : WholeEnum<Http::HdrType>())
3b07476b
CT
396 header_mangler_clean(known[i]);
397
81ab22b6
FC
398 for (auto i : custom)
399 header_mangler_clean(i.second);
3b07476b
CT
400
401 header_mangler_clean(all);
402}
403
404void
405HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
5967c0bf 406{
81ab22b6
FC
407 for (auto id : WholeEnum<Http::HdrType>())
408 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
5967c0bf 409
81ab22b6
FC
410 for (auto i : custom)
411 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
3b07476b
CT
412
413 header_mangler_dump_access(entry, name, all, "All");
5967c0bf 414}
3b07476b
CT
415
416void
417HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
418{
81ab22b6
FC
419 for (auto id : WholeEnum<Http::HdrType>()) {
420 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
3b07476b
CT
421 }
422
81ab22b6
FC
423 for (auto i: custom) {
424 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
3b07476b
CT
425 }
426
427 header_mangler_dump_replacement(entry, name, all, "All");
428}
429
001d55dc 430headerMangler *
3b07476b
CT
431HeaderManglers::track(const char *name)
432{
81ab22b6
FC
433 if (strcmp(name, "All") == 0)
434 return &all;
3b07476b 435
81ab22b6 436 const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id;
3b07476b 437
81ab22b6
FC
438 if (id != Http::HdrType::BAD_HDR)
439 return &known[id];
440
441 if (strcmp(name, "Other") == 0)
442 return &known[Http::HdrType::OTHER];
3b07476b 443
81ab22b6 444 return &custom[name];
3b07476b
CT
445}
446
447void
448HeaderManglers::setReplacement(const char *name, const char *value)
449{
450 // for backword compatibility, we allow replacements to be configured
451 // for headers w/o access rules, but such replacements are ignored
001d55dc 452 headerMangler *m = track(name);
3b07476b
CT
453
454 safe_free(m->replacement); // overwrite old value if any
455 m->replacement = xstrdup(value);
456}
457
001d55dc 458const headerMangler *
3b07476b
CT
459HeaderManglers::find(const HttpHeaderEntry &e) const
460{
461 // a known header with a configured ACL list
1da82544 462 if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) &&
d3abcb0d 463 known[e.id].access_list)
3b07476b
CT
464 return &known[e.id];
465
466 // a custom header
789217a2 467 if (e.id == Http::HdrType::OTHER) {
3b07476b
CT
468 // does it have an ACL list configured?
469 // Optimize: use a name type that we do not need to convert to here
d5f18517
AJ
470 SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
471 const ManglersByName::const_iterator i = custom.find(tmp.c_str());
3b07476b
CT
472 if (i != custom.end())
473 return &i->second;
474 }
475
476 // Next-to-last resort: "Other" rules match any custom header
789217a2
FC
477 if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list)
478 return &known[Http::HdrType::OTHER];
3b07476b
CT
479
480 // Last resort: "All" rules match any header
481 if (all.access_list)
482 return &all;
483
484 return NULL;
485}
486
f4698e0b 487void
4bf68cfa 488httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
f4698e0b
CT
489{
490 ACLFilledChecklist checklist(NULL, request, NULL);
491
b6f3c19a
CT
492 checklist.al = al;
493 if (al && al->reply) {
494 checklist.reply = al->reply.getRaw();
495 HTTPMSGLOCK(checklist.reply);
496 }
497
f4698e0b 498 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
06bf5384 499 if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
f4698e0b
CT
500 const char *fieldValue = NULL;
501 MemBuf mb;
502 if (hwa->quoted) {
4bf68cfa 503 if (al != NULL) {
f4698e0b 504 mb.init();
4bf68cfa 505 hwa->valueFormat->assemble(mb, al, 0);
f4698e0b
CT
506 fieldValue = mb.content();
507 }
508 } else {
509 fieldValue = hwa->fieldValue.c_str();
510 }
511
512 if (!fieldValue || fieldValue[0] == '\0')
513 fieldValue = "-";
514
d5f18517 515 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue);
f4698e0b
CT
516 heads->addEntry(e);
517 }
518 }
519}
f53969cc 520