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