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