]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHeaderTools.cc
4.0.9
[thirdparty/squid.git] / src / HttpHeaderTools.cc
CommitLineData
7faf2bdb 1/*
ef57eb7b 2 * Copyright (C) 1996-2016 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);
d192d11f 81 httpHdrContRangeDestroy(cr);
82}
83
9035d1d5 84/**
9bc73deb 85 * return true if a given directive is found in at least one of
789217a2
FC
86 * the "connection" header-fields note: if Http::HdrType::PROXY_CONNECTION is
87 * present we ignore Http::HdrType::CONNECTION.
99edd1c3 88 */
89int
5999b776 90httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive)
99edd1c3 91{
30abd221 92 String list;
9bc73deb 93 int res;
94 /* what type of header do we have? */
62e76326 95
95e78500 96#if USE_HTTP_VIOLATIONS
789217a2
FC
97 if (hdr->has(Http::HdrType::PROXY_CONNECTION))
98 list = hdr->getList(Http::HdrType::PROXY_CONNECTION);
95e78500
AJ
99 else
100#endif
789217a2
FC
101 if (hdr->has(Http::HdrType::CONNECTION))
102 list = hdr->getList(Http::HdrType::CONNECTION);
2e881a6f
A
103 else
104 return 0;
9bc73deb 105
9bc73deb 106 res = strListIsMember(&list, directive, ',');
62e76326 107
30abd221 108 list.clean();
109
9bc73deb 110 return res;
99edd1c3 111}
112
b50e327b 113/** handy to printf prefixes of potentially very long buffers */
7faf2bdb 114const char *
81858ebc 115getStringPrefix(const char *str, size_t sz)
7faf2bdb 116{
d8b249ef 117#define SHORT_PREFIX_SIZE 512
7faf2bdb 118 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
81858ebc 119 xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
7faf2bdb 120 return buf;
121}
b5107edb 122
b50e327b 123/**
b5107edb 124 * parses an int field, complains if soemthing went wrong, returns true on
125 * success
126 */
127int
128httpHeaderParseInt(const char *start, int *value)
129{
130 assert(value);
131 *value = atoi(start);
62e76326 132
b6a2f15e 133 if (!*value && !xisdigit(*start)) {
bf8fe701 134 debugs(66, 2, "failed to parse an int header field near '" << start << "'");
62e76326 135 return 0;
b5107edb 136 }
62e76326 137
b5107edb 138 return 1;
139}
140
47f6e231 141int
142httpHeaderParseOffset(const char *start, int64_t * value)
143{
66e255bc 144 errno = 0;
47f6e231 145 int64_t res = strtoll(start, NULL, 10);
81ab22b6
FC
146 if (!res && EINVAL == errno) { /* maybe not portable? */
147 debugs(66, 7, "failed to parse offset in " << start);
26ac0430 148 return 0;
81ab22b6 149 }
47f6e231 150 *value = res;
81ab22b6 151 debugs(66, 7, "offset " << start << " parsed as " << res);
47f6e231 152 return 1;
153}
154
b50e327b
AJ
155/**
156 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
43ae1d95 157 * something went wrong, returns non-zero on success.
34460e19 158 * Un-escapes quoted-pair characters found within the string.
229af339 159 * start should point at the first double-quote.
43ae1d95 160 */
161int
34460e19 162httpHeaderParseQuotedString(const char *start, const int len, String *val)
43ae1d95 163{
164 const char *end, *pos;
30abd221 165 val->clean();
9abd1514 166 if (*start != '"') {
5f5ddef6 167 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
6d97f5f1 168 return 0;
9abd1514 169 }
43ae1d95 170 pos = start + 1;
171
34460e19 172 while (*pos != '"' && len > (pos-start)) {
5f5ddef6
CT
173
174 if (*pos =='\r') {
95dc7ff4 175 ++pos;
5f5ddef6
CT
176 if ((pos-start) > len || *pos != '\n') {
177 debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
178 << " bytes into '" << start << "'");
179 val->clean();
180 return 0;
181 }
182 }
183
184 if (*pos == '\n') {
95dc7ff4 185 ++pos;
5f5ddef6
CT
186 if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
187 debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
188 val->clean();
189 return 0;
190 }
191 // TODO: replace the entire LWS with a space
192 val->append(" ");
95dc7ff4 193 ++pos;
5f5ddef6
CT
194 debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
195 continue;
196 }
197
a0133f10 198 bool quoted = (*pos == '\\');
5f5ddef6 199 if (quoted) {
95dc7ff4 200 ++pos;
5f5ddef6
CT
201 if (!*pos || (pos-start) > len) {
202 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
203 val->clean();
204 return 0;
205 }
6d97f5f1 206 }
34460e19 207 end = pos;
c4b86597 208 while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
95dc7ff4 209 ++end;
c4b86597 210 if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
5f5ddef6 211 debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
34460e19
AJ
212 << " bytes into '" << start << "'");
213 val->clean();
214 return 0;
215 }
6d97f5f1
A
216 val->append(pos, end-pos);
217 pos = end;
43ae1d95 218 }
5f5ddef6
CT
219
220 if (*pos != '\"') {
221 debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
222 val->clean();
223 return 0;
224 }
9abd1514 225 /* Make sure it's defined even if empty "" */
b38b26cb 226 if (!val->termedBuf())
6d97f5f1 227 val->limitInit("", 0);
9abd1514 228 return 1;
43ae1d95 229}
230
e7ce227f 231SBuf
22381a6b
AR
232httpHeaderQuoteString(const char *raw)
233{
234 assert(raw);
235
3cc0f4e7
AR
236 // TODO: Optimize by appending a sequence of characters instead of a char.
237 // This optimization may be easier with Tokenizer after raw becomes SBuf.
238
e7ce227f
AR
239 // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
240 // quoted-string except where necessary" (i.e., DQUOTE and backslash)
22381a6b
AR
241 bool needInnerQuote = false;
242 for (const char *s = raw; !needInnerQuote && *s; ++s)
243 needInnerQuote = *s == '"' || *s == '\\';
244
e7ce227f 245 SBuf quotedStr;
22381a6b
AR
246 quotedStr.append('"');
247
248 if (needInnerQuote) {
249 for (const char *s = raw; *s; ++s) {
250 if (*s == '"' || *s == '\\')
251 quotedStr.append('\\');
252 quotedStr.append(*s);
253 }
254 } else {
255 quotedStr.append(raw);
256 }
e7ce227f 257
22381a6b
AR
258 quotedStr.append('"');
259 return quotedStr;
260}
261
b50e327b
AJ
262/**
263 * Checks the anonymizer (header_access) configuration.
4f4611bf 264 *
b50e327b
AJ
265 * \retval 0 Header is explicitly blocked for removal
266 * \retval 1 Header is explicitly allowed
267 * \retval 1 Header has been replaced, the current version can be used.
268 * \retval 1 Header has no access controls to test
6bccf575 269 */
2d72d4fd 270static int
cde8f31b 271httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms)
6bccf575 272{
e9b1e111 273 int retval;
274
6bccf575 275 assert(e);
8c01ada0 276
001d55dc 277 const headerMangler *hm = hms->find(*e);
3b07476b 278
b50e327b 279 /* mangler or checklist went away. default allow */
af6a12ee 280 if (!hm || !hm->access_list) {
81ab22b6 281 debugs(66, 7, "couldn't find mangler or access list. Allowing");
b50e327b
AJ
282 return 1;
283 }
284
c0941a6a 285 ACLFilledChecklist checklist(hm->access_list, request, NULL);
62e76326 286
2efeb0b7 287 if (checklist.fastCheck() == ACCESS_ALLOWED) {
b50e327b 288 /* aclCheckFast returns true for allow. */
81ab22b6 289 debugs(66, 7, "checklist for mangler is positive. Mangle");
62e76326 290 retval = 1;
a453662f 291 } else if (NULL == hm->replacement) {
62e76326 292 /* It was denied, and we don't have any replacement */
81ab22b6 293 debugs(66, 7, "checklist denied, we have no replacement. Pass");
62e76326 294 retval = 0;
a453662f 295 } else {
62e76326 296 /* It was denied, but we have a replacement. Replace the
297 * header on the fly, and return that the new header
298 * is allowed.
299 */
81ab22b6 300 debugs(66, 7, "checklist denied but we have replacement. Replace");
62e76326 301 e->value = hm->replacement;
302 retval = 1;
a453662f 303 }
e9b1e111 304
e9b1e111 305 return retval;
6bccf575 306}
307
b50e327b 308/** Mangles headers for a list of headers. */
6bccf575 309void
cde8f31b 310httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
6bccf575 311{
312 HttpHeaderEntry *e;
313 HttpHeaderPos p = HttpHeaderInitPos;
62e76326 314
cde8f31b
NH
315 /* check with anonymizer tables */
316 HeaderManglers *hms = nullptr;
317 HeaderWithAclList *headersAdd = nullptr;
318
319 switch (req_or_rep) {
b7290021
SM
320 case ROR_REQUEST:
321 hms = Config.request_header_access;
322 headersAdd = Config.request_header_add;
323 break;
324 case ROR_REPLY:
325 hms = Config.reply_header_access;
326 headersAdd = Config.reply_header_add;
327 break;
cde8f31b
NH
328 }
329
330 if (hms) {
331 int headers_deleted = 0;
332 while ((e = l->getEntry(&p))) {
333 if (0 == httpHdrMangle(e, request, hms))
334 l->delAt(p, headers_deleted);
335 }
ba9fb01d 336
cde8f31b
NH
337 if (headers_deleted)
338 l->refreshMask();
339 }
340
341 if (headersAdd && !headersAdd->empty()) {
342 httpHdrAdd(l, request, al, *headersAdd);
343 }
6bccf575 344}
5967c0bf 345
3b07476b 346static
001d55dc 347void header_mangler_clean(headerMangler &m)
3b07476b
CT
348{
349 aclDestroyAccessList(&m.access_list);
350 safe_free(m.replacement);
351}
352
353static
354void header_mangler_dump_access(StoreEntry * entry, const char *option,
001d55dc 355 const headerMangler &m, const char *name)
3b07476b
CT
356{
357 if (m.access_list != NULL) {
358 storeAppendPrintf(entry, "%s ", option);
359 dump_acl_access(entry, name, m.access_list);
360 }
361}
362
363static
364void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
001d55dc 365 const headerMangler &m, const char *name)
3b07476b
CT
366{
367 if (m.replacement)
368 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
369}
370
371HeaderManglers::HeaderManglers()
372{
373 memset(known, 0, sizeof(known));
374 memset(&all, 0, sizeof(all));
375}
376
377HeaderManglers::~HeaderManglers()
378{
81ab22b6 379 for (auto i : WholeEnum<Http::HdrType>())
3b07476b
CT
380 header_mangler_clean(known[i]);
381
81ab22b6
FC
382 for (auto i : custom)
383 header_mangler_clean(i.second);
3b07476b
CT
384
385 header_mangler_clean(all);
386}
387
388void
389HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
5967c0bf 390{
81ab22b6
FC
391 for (auto id : WholeEnum<Http::HdrType>())
392 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
5967c0bf 393
81ab22b6
FC
394 for (auto i : custom)
395 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
3b07476b
CT
396
397 header_mangler_dump_access(entry, name, all, "All");
5967c0bf 398}
3b07476b
CT
399
400void
401HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
402{
81ab22b6
FC
403 for (auto id : WholeEnum<Http::HdrType>()) {
404 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
3b07476b
CT
405 }
406
81ab22b6
FC
407 for (auto i: custom) {
408 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
3b07476b
CT
409 }
410
411 header_mangler_dump_replacement(entry, name, all, "All");
412}
413
001d55dc 414headerMangler *
3b07476b
CT
415HeaderManglers::track(const char *name)
416{
81ab22b6
FC
417 if (strcmp(name, "All") == 0)
418 return &all;
3b07476b 419
81ab22b6 420 const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id;
3b07476b 421
81ab22b6
FC
422 if (id != Http::HdrType::BAD_HDR)
423 return &known[id];
424
425 if (strcmp(name, "Other") == 0)
426 return &known[Http::HdrType::OTHER];
3b07476b 427
81ab22b6 428 return &custom[name];
3b07476b
CT
429}
430
431void
432HeaderManglers::setReplacement(const char *name, const char *value)
433{
434 // for backword compatibility, we allow replacements to be configured
435 // for headers w/o access rules, but such replacements are ignored
001d55dc 436 headerMangler *m = track(name);
3b07476b
CT
437
438 safe_free(m->replacement); // overwrite old value if any
439 m->replacement = xstrdup(value);
440}
441
001d55dc 442const headerMangler *
3b07476b
CT
443HeaderManglers::find(const HttpHeaderEntry &e) const
444{
445 // a known header with a configured ACL list
1da82544 446 if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) &&
d3abcb0d 447 known[e.id].access_list)
3b07476b
CT
448 return &known[e.id];
449
450 // a custom header
789217a2 451 if (e.id == Http::HdrType::OTHER) {
3b07476b
CT
452 // does it have an ACL list configured?
453 // Optimize: use a name type that we do not need to convert to here
454 const ManglersByName::const_iterator i = custom.find(e.name.termedBuf());
455 if (i != custom.end())
456 return &i->second;
457 }
458
459 // Next-to-last resort: "Other" rules match any custom header
789217a2
FC
460 if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list)
461 return &known[Http::HdrType::OTHER];
3b07476b
CT
462
463 // Last resort: "All" rules match any header
464 if (all.access_list)
465 return &all;
466
467 return NULL;
468}
469
f4698e0b 470void
4bf68cfa 471httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
f4698e0b
CT
472{
473 ACLFilledChecklist checklist(NULL, request, NULL);
474
475 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
476 if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) {
477 const char *fieldValue = NULL;
478 MemBuf mb;
479 if (hwa->quoted) {
4bf68cfa 480 if (al != NULL) {
f4698e0b 481 mb.init();
4bf68cfa 482 hwa->valueFormat->assemble(mb, al, 0);
f4698e0b
CT
483 fieldValue = mb.content();
484 }
485 } else {
486 fieldValue = hwa->fieldValue.c_str();
487 }
488
489 if (!fieldValue || fieldValue[0] == '\0')
490 fieldValue = "-";
491
999aa5d2
A
492 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
493 fieldValue);
f4698e0b
CT
494 heads->addEntry(e);
495 }
496 }
497}
f53969cc 498