]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHeaderTools.cc
Boilerplate: update copyright blurbs on Squid helpers
[thirdparty/squid.git] / src / HttpHeaderTools.cc
CommitLineData
7faf2bdb 1/*
7faf2bdb 2 * DEBUG: section 66 HTTP Header Tools
3 * AUTHOR: Alex Rousskov
4 *
2b6662ba 5 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 6 * ----------------------------------------------------------
7faf2bdb 7 *
2b6662ba 8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
7faf2bdb 16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
26ac0430 21 *
7faf2bdb 22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26ac0430 26 *
7faf2bdb 27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
cbdec147 29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 30 *
7faf2bdb 31 */
32
582c2af2 33#include "squid.h"
c0941a6a 34#include "acl/FilledChecklist.h"
3b07476b 35#include "acl/Gadgets.h"
582c2af2 36#include "client_side.h"
602d9612 37#include "client_side_request.h"
f4698e0b 38#include "comm/Connection.h"
27bc2077 39#include "compat/strtoll.h"
d7f4a0b7 40#include "ConfigParser.h"
f4698e0b 41#include "fde.h"
602d9612 42#include "globals.h"
27bc2077
AJ
43#include "HttpHdrContRange.h"
44#include "HttpHeader.h"
79cb238d 45#include "HttpHeaderFieldInfo.h"
3b07476b
CT
46#include "HttpHeaderTools.h"
47#include "HttpRequest.h"
0eb49b6d 48#include "MemBuf.h"
4d5904f7 49#include "SquidConfig.h"
582c2af2 50#include "Store.h"
28204b3b 51#include "StrList.h"
582c2af2 52
cb4f4424 53#if USE_OPENSSL
f4698e0b
CT
54#include "ssl/support.h"
55#endif
582c2af2 56
f4698e0b 57#include <algorithm>
1a30fdf5 58#include <cerrno>
f4698e0b 59#include <string>
7faf2bdb 60
2246b732 61static void httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs);
de336bbe 62
de336bbe 63HttpHeaderFieldInfo *
b644367b 64httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count)
7faf2bdb 65{
66 int i;
de336bbe 67 HttpHeaderFieldInfo *table = NULL;
68 assert(attrs && count);
69
70 /* allocate space */
0353e724 71 table = new HttpHeaderFieldInfo[count];
7faf2bdb 72
7faf2bdb 73 for (i = 0; i < count; ++i) {
62e76326 74 const http_hdr_type id = attrs[i].id;
75 HttpHeaderFieldInfo *info = table + id;
76 /* sanity checks */
77 assert(id >= 0 && id < count);
78 assert(attrs[i].name);
0353e724 79 assert(info->id == HDR_ACCEPT && info->type == ftInvalid); /* was not set before */
62e76326 80 /* copy and init fields */
81 info->id = id;
82 info->type = attrs[i].type;
83 info->name = attrs[i].name;
84 assert(info->name.size());
7faf2bdb 85 }
62e76326 86
de336bbe 87 return table;
88}
89
90void
b644367b 91httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo * table, int count)
de336bbe 92{
93 int i;
62e76326 94
de336bbe 95 for (i = 0; i < count; ++i)
30abd221 96 table[i].name.clean();
62e76326 97
7f4c8dec 98 delete [] table;
7faf2bdb 99}
100
d8b249ef 101void
97474590 102httpHeaderMaskInit(HttpHeaderMask * mask, int value)
d8b249ef 103{
97474590 104 memset(mask, value, sizeof(*mask));
d8b249ef 105}
106
b50e327b 107/** calculates a bit mask of a given array; does not reset mask! */
d8b249ef 108void
8abf232c 109httpHeaderCalcMask(HttpHeaderMask * mask, http_hdr_type http_hdr_type_enums[], size_t count)
7faf2bdb 110{
e6ccf245 111 size_t i;
8abf232c 112 const int * enums = (const int *) http_hdr_type_enums;
d8b249ef 113 assert(mask && enums);
99edd1c3 114 assert(count < sizeof(*mask) * 8); /* check for overflow */
7faf2bdb 115
116 for (i = 0; i < count; ++i) {
62e76326 117 assert(!CBIT_TEST(*mask, enums[i])); /* check for duplicates */
118 CBIT_SET(*mask, enums[i]);
7faf2bdb 119 }
7faf2bdb 120}
121
2246b732 122/* same as httpHeaderPutStr, but formats the string using snprintf first */
2246b732 123void
eeb423fb 124httpHeaderPutStrf(HttpHeader * hdr, http_hdr_type id, const char *fmt,...)
2246b732 125{
9bc73deb 126 va_list args;
127 va_start(args, fmt);
62e76326 128
2246b732 129 httpHeaderPutStrvf(hdr, id, fmt, args);
130 va_end(args);
131}
132
133/* used by httpHeaderPutStrf */
134static void
135httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs)
136{
2246b732 137 MemBuf mb;
2fe7eff9 138 mb.init();
139 mb.vPrintf(fmt, vargs);
a9925b40 140 hdr->putStr(id, mb.buf);
2fe7eff9 141 mb.clean();
2246b732 142}
143
9035d1d5 144/** wrapper arrounf PutContRange */
d192d11f 145void
47f6e231 146httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
d192d11f 147{
148 HttpHdrContRange *cr = httpHdrContRangeCreate();
149 assert(hdr && ent_len >= 0);
150 httpHdrContRangeSet(cr, spec, ent_len);
a9925b40 151 hdr->putContRange(cr);
d192d11f 152 httpHdrContRangeDestroy(cr);
153}
154
9035d1d5 155/**
9bc73deb 156 * return true if a given directive is found in at least one of
157 * the "connection" header-fields note: if HDR_PROXY_CONNECTION is
158 * present we ignore HDR_CONNECTION.
99edd1c3 159 */
160int
5999b776 161httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive)
99edd1c3 162{
30abd221 163 String list;
9bc73deb 164 int res;
165 /* what type of header do we have? */
62e76326 166
95e78500 167#if USE_HTTP_VIOLATIONS
a9925b40 168 if (hdr->has(HDR_PROXY_CONNECTION))
95e78500
AJ
169 list = hdr->getList(HDR_PROXY_CONNECTION);
170 else
171#endif
2e881a6f
A
172 if (hdr->has(HDR_CONNECTION))
173 list = hdr->getList(HDR_CONNECTION);
174 else
175 return 0;
9bc73deb 176
9bc73deb 177 res = strListIsMember(&list, directive, ',');
62e76326 178
30abd221 179 list.clean();
180
9bc73deb 181 return res;
99edd1c3 182}
183
b50e327b 184/** handy to printf prefixes of potentially very long buffers */
7faf2bdb 185const char *
d8b249ef 186getStringPrefix(const char *str, const char *end)
7faf2bdb 187{
d8b249ef 188#define SHORT_PREFIX_SIZE 512
7faf2bdb 189 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
b644367b 190 const int sz = 1 + (end ? end - str : strlen(str));
d8b249ef 191 xstrncpy(buf, str, (sz > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
7faf2bdb 192 return buf;
193}
b5107edb 194
b50e327b 195/**
b5107edb 196 * parses an int field, complains if soemthing went wrong, returns true on
197 * success
198 */
199int
200httpHeaderParseInt(const char *start, int *value)
201{
202 assert(value);
203 *value = atoi(start);
62e76326 204
b6a2f15e 205 if (!*value && !xisdigit(*start)) {
bf8fe701 206 debugs(66, 2, "failed to parse an int header field near '" << start << "'");
62e76326 207 return 0;
b5107edb 208 }
62e76326 209
b5107edb 210 return 1;
211}
212
47f6e231 213int
214httpHeaderParseOffset(const char *start, int64_t * value)
215{
66e255bc 216 errno = 0;
47f6e231 217 int64_t res = strtoll(start, NULL, 10);
218 if (!res && EINVAL == errno) /* maybe not portable? */
26ac0430 219 return 0;
47f6e231 220 *value = res;
221 return 1;
222}
223
b50e327b
AJ
224/**
225 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
43ae1d95 226 * something went wrong, returns non-zero on success.
34460e19 227 * Un-escapes quoted-pair characters found within the string.
229af339 228 * start should point at the first double-quote.
43ae1d95 229 */
230int
34460e19 231httpHeaderParseQuotedString(const char *start, const int len, String *val)
43ae1d95 232{
233 const char *end, *pos;
30abd221 234 val->clean();
9abd1514 235 if (*start != '"') {
5f5ddef6 236 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
6d97f5f1 237 return 0;
9abd1514 238 }
43ae1d95 239 pos = start + 1;
240
34460e19 241 while (*pos != '"' && len > (pos-start)) {
5f5ddef6
CT
242
243 if (*pos =='\r') {
95dc7ff4 244 ++pos;
5f5ddef6
CT
245 if ((pos-start) > len || *pos != '\n') {
246 debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
247 << " bytes into '" << start << "'");
248 val->clean();
249 return 0;
250 }
251 }
252
253 if (*pos == '\n') {
95dc7ff4 254 ++pos;
5f5ddef6
CT
255 if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
256 debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
257 val->clean();
258 return 0;
259 }
260 // TODO: replace the entire LWS with a space
261 val->append(" ");
95dc7ff4 262 ++pos;
5f5ddef6
CT
263 debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
264 continue;
265 }
266
a0133f10 267 bool quoted = (*pos == '\\');
5f5ddef6 268 if (quoted) {
95dc7ff4 269 ++pos;
5f5ddef6
CT
270 if (!*pos || (pos-start) > len) {
271 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
272 val->clean();
273 return 0;
274 }
6d97f5f1 275 }
34460e19 276 end = pos;
c4b86597 277 while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
95dc7ff4 278 ++end;
c4b86597 279 if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
5f5ddef6 280 debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
34460e19
AJ
281 << " bytes into '" << start << "'");
282 val->clean();
283 return 0;
284 }
6d97f5f1
A
285 val->append(pos, end-pos);
286 pos = end;
43ae1d95 287 }
5f5ddef6
CT
288
289 if (*pos != '\"') {
290 debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
291 val->clean();
292 return 0;
293 }
9abd1514 294 /* Make sure it's defined even if empty "" */
b38b26cb 295 if (!val->termedBuf())
6d97f5f1 296 val->limitInit("", 0);
9abd1514 297 return 1;
43ae1d95 298}
299
e7ce227f 300SBuf
22381a6b
AR
301httpHeaderQuoteString(const char *raw)
302{
303 assert(raw);
304
3cc0f4e7
AR
305 // TODO: Optimize by appending a sequence of characters instead of a char.
306 // This optimization may be easier with Tokenizer after raw becomes SBuf.
307
e7ce227f
AR
308 // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
309 // quoted-string except where necessary" (i.e., DQUOTE and backslash)
22381a6b
AR
310 bool needInnerQuote = false;
311 for (const char *s = raw; !needInnerQuote && *s; ++s)
312 needInnerQuote = *s == '"' || *s == '\\';
313
e7ce227f 314 SBuf quotedStr;
22381a6b
AR
315 quotedStr.append('"');
316
317 if (needInnerQuote) {
318 for (const char *s = raw; *s; ++s) {
319 if (*s == '"' || *s == '\\')
320 quotedStr.append('\\');
321 quotedStr.append(*s);
322 }
323 } else {
324 quotedStr.append(raw);
325 }
e7ce227f 326
22381a6b
AR
327 quotedStr.append('"');
328 return quotedStr;
329}
330
b50e327b
AJ
331/**
332 * Checks the anonymizer (header_access) configuration.
4f4611bf 333 *
b50e327b
AJ
334 * \retval 0 Header is explicitly blocked for removal
335 * \retval 1 Header is explicitly allowed
336 * \retval 1 Header has been replaced, the current version can be used.
337 * \retval 1 Header has no access controls to test
6bccf575 338 */
2d72d4fd 339static int
8c01ada0 340httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep)
6bccf575 341{
e9b1e111 342 int retval;
343
6bccf575 344 /* check with anonymizer tables */
3b07476b 345 HeaderManglers *hms = NULL;
6bccf575 346 assert(e);
8c01ada0 347
348 if (ROR_REQUEST == req_or_rep) {
3b07476b 349 hms = Config.request_header_access;
8c01ada0 350 } else if (ROR_REPLY == req_or_rep) {
3b07476b 351 hms = Config.reply_header_access;
8c01ada0 352 } else {
353 /* error. But let's call it "request". */
3b07476b 354 hms = Config.request_header_access;
8c01ada0 355 }
356
3b07476b
CT
357 /* manglers are not configured for this message kind */
358 if (!hms)
359 return 1;
360
001d55dc 361 const headerMangler *hm = hms->find(*e);
3b07476b 362
b50e327b 363 /* mangler or checklist went away. default allow */
af6a12ee 364 if (!hm || !hm->access_list) {
b50e327b
AJ
365 return 1;
366 }
367
c0941a6a 368 ACLFilledChecklist checklist(hm->access_list, request, NULL);
62e76326 369
2efeb0b7 370 if (checklist.fastCheck() == ACCESS_ALLOWED) {
b50e327b 371 /* aclCheckFast returns true for allow. */
62e76326 372 retval = 1;
a453662f 373 } else if (NULL == hm->replacement) {
62e76326 374 /* It was denied, and we don't have any replacement */
375 retval = 0;
a453662f 376 } else {
62e76326 377 /* It was denied, but we have a replacement. Replace the
378 * header on the fly, and return that the new header
379 * is allowed.
380 */
381 e->value = hm->replacement;
382 retval = 1;
a453662f 383 }
e9b1e111 384
e9b1e111 385 return retval;
6bccf575 386}
387
b50e327b 388/** Mangles headers for a list of headers. */
6bccf575 389void
8c01ada0 390httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep)
6bccf575 391{
392 HttpHeaderEntry *e;
393 HttpHeaderPos p = HttpHeaderInitPos;
62e76326 394
ba9fb01d 395 int headers_deleted = 0;
a9925b40 396 while ((e = l->getEntry(&p)))
8c01ada0 397 if (0 == httpHdrMangle(e, request, req_or_rep))
ba9fb01d 398 l->delAt(p, headers_deleted);
399
400 if (headers_deleted)
401 l->refreshMask();
6bccf575 402}
5967c0bf 403
3b07476b 404static
001d55dc 405void header_mangler_clean(headerMangler &m)
3b07476b
CT
406{
407 aclDestroyAccessList(&m.access_list);
408 safe_free(m.replacement);
409}
410
411static
412void header_mangler_dump_access(StoreEntry * entry, const char *option,
001d55dc 413 const headerMangler &m, const char *name)
3b07476b
CT
414{
415 if (m.access_list != NULL) {
416 storeAppendPrintf(entry, "%s ", option);
417 dump_acl_access(entry, name, m.access_list);
418 }
419}
420
421static
422void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
001d55dc 423 const headerMangler &m, const char *name)
3b07476b
CT
424{
425 if (m.replacement)
426 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
427}
428
429HeaderManglers::HeaderManglers()
430{
431 memset(known, 0, sizeof(known));
432 memset(&all, 0, sizeof(all));
433}
434
435HeaderManglers::~HeaderManglers()
436{
95dc7ff4 437 for (int i = 0; i < HDR_ENUM_END; ++i)
3b07476b
CT
438 header_mangler_clean(known[i]);
439
440 typedef ManglersByName::iterator MBNI;
441 for (MBNI i = custom.begin(); i != custom.end(); ++i)
442 header_mangler_clean(i->second);
443
444 header_mangler_clean(all);
445}
446
447void
448HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
5967c0bf 449{
95dc7ff4 450 for (int i = 0; i < HDR_ENUM_END; ++i) {
3b07476b
CT
451 header_mangler_dump_access(entry, name, known[i],
452 httpHeaderNameById(i));
5967c0bf 453 }
454
3b07476b
CT
455 typedef ManglersByName::const_iterator MBNCI;
456 for (MBNCI i = custom.begin(); i != custom.end(); ++i)
457 header_mangler_dump_access(entry, name, i->second, i->first.c_str());
458
459 header_mangler_dump_access(entry, name, all, "All");
5967c0bf 460}
3b07476b
CT
461
462void
463HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
464{
95dc7ff4 465 for (int i = 0; i < HDR_ENUM_END; ++i) {
3b07476b
CT
466 header_mangler_dump_replacement(entry, name, known[i],
467 httpHeaderNameById(i));
468 }
469
470 typedef ManglersByName::const_iterator MBNCI;
471 for (MBNCI i = custom.begin(); i != custom.end(); ++i) {
472 header_mangler_dump_replacement(entry, name, i->second,
473 i->first.c_str());
474 }
475
476 header_mangler_dump_replacement(entry, name, all, "All");
477}
478
001d55dc 479headerMangler *
3b07476b
CT
480HeaderManglers::track(const char *name)
481{
482 int id = httpHeaderIdByNameDef(name, strlen(name));
483
484 if (id == HDR_BAD_HDR) { // special keyword or a custom header
485 if (strcmp(name, "All") == 0)
486 id = HDR_ENUM_END;
487 else if (strcmp(name, "Other") == 0)
488 id = HDR_OTHER;
489 }
490
001d55dc 491 headerMangler *m = NULL;
3b07476b
CT
492 if (id == HDR_ENUM_END) {
493 m = &all;
d3abcb0d 494 } else if (id == HDR_BAD_HDR) {
3b07476b
CT
495 m = &custom[name];
496 } else {
497 m = &known[id]; // including HDR_OTHER
498 }
499
500 assert(m);
501 return m;
502}
503
504void
505HeaderManglers::setReplacement(const char *name, const char *value)
506{
507 // for backword compatibility, we allow replacements to be configured
508 // for headers w/o access rules, but such replacements are ignored
001d55dc 509 headerMangler *m = track(name);
3b07476b
CT
510
511 safe_free(m->replacement); // overwrite old value if any
512 m->replacement = xstrdup(value);
513}
514
001d55dc 515const headerMangler *
3b07476b
CT
516HeaderManglers::find(const HttpHeaderEntry &e) const
517{
518 // a known header with a configured ACL list
519 if (e.id != HDR_OTHER && 0 <= e.id && e.id < HDR_ENUM_END &&
d3abcb0d 520 known[e.id].access_list)
3b07476b
CT
521 return &known[e.id];
522
523 // a custom header
524 if (e.id == HDR_OTHER) {
525 // does it have an ACL list configured?
526 // Optimize: use a name type that we do not need to convert to here
527 const ManglersByName::const_iterator i = custom.find(e.name.termedBuf());
528 if (i != custom.end())
529 return &i->second;
530 }
531
532 // Next-to-last resort: "Other" rules match any custom header
533 if (e.id == HDR_OTHER && known[HDR_OTHER].access_list)
534 return &known[HDR_OTHER];
535
536 // Last resort: "All" rules match any header
537 if (all.access_list)
538 return &all;
539
540 return NULL;
541}
542
f4698e0b 543void
4bf68cfa 544httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
f4698e0b
CT
545{
546 ACLFilledChecklist checklist(NULL, request, NULL);
547
548 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
549 if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) {
550 const char *fieldValue = NULL;
551 MemBuf mb;
552 if (hwa->quoted) {
4bf68cfa 553 if (al != NULL) {
f4698e0b 554 mb.init();
4bf68cfa 555 hwa->valueFormat->assemble(mb, al, 0);
f4698e0b
CT
556 fieldValue = mb.content();
557 }
558 } else {
559 fieldValue = hwa->fieldValue.c_str();
560 }
561
562 if (!fieldValue || fieldValue[0] == '\0')
563 fieldValue = "-";
564
999aa5d2
A
565 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
566 fieldValue);
f4698e0b
CT
567 heads->addEntry(e);
568 }
569 }
570}