]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpHeaderTools.cc
681a24d69e7f9e98f27d245aa7f376d9fa4c2c61
[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, size_t sz)
165 {
166 #define SHORT_PREFIX_SIZE 512
167 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
168 xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
169 return buf;
170 }
171
172 /**
173 * parses an int field, complains if soemthing went wrong, returns true on
174 * success
175 */
176 int
177 httpHeaderParseInt(const char *start, int *value)
178 {
179 assert(value);
180 *value = atoi(start);
181
182 if (!*value && !xisdigit(*start)) {
183 debugs(66, 2, "failed to parse an int header field near '" << start << "'");
184 return 0;
185 }
186
187 return 1;
188 }
189
190 int
191 httpHeaderParseOffset(const char *start, int64_t * value)
192 {
193 errno = 0;
194 int64_t res = strtoll(start, NULL, 10);
195 if (!res && EINVAL == errno) /* maybe not portable? */
196 return 0;
197 *value = res;
198 return 1;
199 }
200
201 /**
202 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
203 * something went wrong, returns non-zero on success.
204 * Un-escapes quoted-pair characters found within the string.
205 * start should point at the first double-quote.
206 */
207 int
208 httpHeaderParseQuotedString(const char *start, const int len, String *val)
209 {
210 const char *end, *pos;
211 val->clean();
212 if (*start != '"') {
213 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
214 return 0;
215 }
216 pos = start + 1;
217
218 while (*pos != '"' && len > (pos-start)) {
219
220 if (*pos =='\r') {
221 ++pos;
222 if ((pos-start) > len || *pos != '\n') {
223 debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
224 << " bytes into '" << start << "'");
225 val->clean();
226 return 0;
227 }
228 }
229
230 if (*pos == '\n') {
231 ++pos;
232 if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
233 debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
234 val->clean();
235 return 0;
236 }
237 // TODO: replace the entire LWS with a space
238 val->append(" ");
239 ++pos;
240 debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
241 continue;
242 }
243
244 bool quoted = (*pos == '\\');
245 if (quoted) {
246 ++pos;
247 if (!*pos || (pos-start) > len) {
248 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
249 val->clean();
250 return 0;
251 }
252 }
253 end = pos;
254 while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
255 ++end;
256 if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
257 debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
258 << " bytes into '" << start << "'");
259 val->clean();
260 return 0;
261 }
262 val->append(pos, end-pos);
263 pos = end;
264 }
265
266 if (*pos != '\"') {
267 debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
268 val->clean();
269 return 0;
270 }
271 /* Make sure it's defined even if empty "" */
272 if (!val->termedBuf())
273 val->limitInit("", 0);
274 return 1;
275 }
276
277 SBuf
278 httpHeaderQuoteString(const char *raw)
279 {
280 assert(raw);
281
282 // TODO: Optimize by appending a sequence of characters instead of a char.
283 // This optimization may be easier with Tokenizer after raw becomes SBuf.
284
285 // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
286 // quoted-string except where necessary" (i.e., DQUOTE and backslash)
287 bool needInnerQuote = false;
288 for (const char *s = raw; !needInnerQuote && *s; ++s)
289 needInnerQuote = *s == '"' || *s == '\\';
290
291 SBuf quotedStr;
292 quotedStr.append('"');
293
294 if (needInnerQuote) {
295 for (const char *s = raw; *s; ++s) {
296 if (*s == '"' || *s == '\\')
297 quotedStr.append('\\');
298 quotedStr.append(*s);
299 }
300 } else {
301 quotedStr.append(raw);
302 }
303
304 quotedStr.append('"');
305 return quotedStr;
306 }
307
308 /**
309 * Checks the anonymizer (header_access) configuration.
310 *
311 * \retval 0 Header is explicitly blocked for removal
312 * \retval 1 Header is explicitly allowed
313 * \retval 1 Header has been replaced, the current version can be used.
314 * \retval 1 Header has no access controls to test
315 */
316 static int
317 httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep)
318 {
319 int retval;
320
321 /* check with anonymizer tables */
322 HeaderManglers *hms = NULL;
323 assert(e);
324
325 if (ROR_REQUEST == req_or_rep) {
326 hms = Config.request_header_access;
327 } else if (ROR_REPLY == req_or_rep) {
328 hms = Config.reply_header_access;
329 } else {
330 /* error. But let's call it "request". */
331 hms = Config.request_header_access;
332 }
333
334 /* manglers are not configured for this message kind */
335 if (!hms)
336 return 1;
337
338 const headerMangler *hm = hms->find(*e);
339
340 /* mangler or checklist went away. default allow */
341 if (!hm || !hm->access_list) {
342 return 1;
343 }
344
345 ACLFilledChecklist checklist(hm->access_list, request, NULL);
346
347 if (checklist.fastCheck() == ACCESS_ALLOWED) {
348 /* aclCheckFast returns true for allow. */
349 retval = 1;
350 } else if (NULL == hm->replacement) {
351 /* It was denied, and we don't have any replacement */
352 retval = 0;
353 } else {
354 /* It was denied, but we have a replacement. Replace the
355 * header on the fly, and return that the new header
356 * is allowed.
357 */
358 e->value = hm->replacement;
359 retval = 1;
360 }
361
362 return retval;
363 }
364
365 /** Mangles headers for a list of headers. */
366 void
367 httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep)
368 {
369 HttpHeaderEntry *e;
370 HttpHeaderPos p = HttpHeaderInitPos;
371
372 int headers_deleted = 0;
373 while ((e = l->getEntry(&p)))
374 if (0 == httpHdrMangle(e, request, req_or_rep))
375 l->delAt(p, headers_deleted);
376
377 if (headers_deleted)
378 l->refreshMask();
379 }
380
381 static
382 void header_mangler_clean(headerMangler &m)
383 {
384 aclDestroyAccessList(&m.access_list);
385 safe_free(m.replacement);
386 }
387
388 static
389 void header_mangler_dump_access(StoreEntry * entry, const char *option,
390 const headerMangler &m, const char *name)
391 {
392 if (m.access_list != NULL) {
393 storeAppendPrintf(entry, "%s ", option);
394 dump_acl_access(entry, name, m.access_list);
395 }
396 }
397
398 static
399 void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
400 const headerMangler &m, const char *name)
401 {
402 if (m.replacement)
403 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
404 }
405
406 HeaderManglers::HeaderManglers()
407 {
408 memset(known, 0, sizeof(known));
409 memset(&all, 0, sizeof(all));
410 }
411
412 HeaderManglers::~HeaderManglers()
413 {
414 for (int i = 0; i < HDR_ENUM_END; ++i)
415 header_mangler_clean(known[i]);
416
417 typedef ManglersByName::iterator MBNI;
418 for (MBNI i = custom.begin(); i != custom.end(); ++i)
419 header_mangler_clean(i->second);
420
421 header_mangler_clean(all);
422 }
423
424 void
425 HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
426 {
427 for (int i = 0; i < HDR_ENUM_END; ++i) {
428 header_mangler_dump_access(entry, name, known[i],
429 httpHeaderNameById(i));
430 }
431
432 typedef ManglersByName::const_iterator MBNCI;
433 for (MBNCI i = custom.begin(); i != custom.end(); ++i)
434 header_mangler_dump_access(entry, name, i->second, i->first.c_str());
435
436 header_mangler_dump_access(entry, name, all, "All");
437 }
438
439 void
440 HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
441 {
442 for (int i = 0; i < HDR_ENUM_END; ++i) {
443 header_mangler_dump_replacement(entry, name, known[i],
444 httpHeaderNameById(i));
445 }
446
447 typedef ManglersByName::const_iterator MBNCI;
448 for (MBNCI i = custom.begin(); i != custom.end(); ++i) {
449 header_mangler_dump_replacement(entry, name, i->second,
450 i->first.c_str());
451 }
452
453 header_mangler_dump_replacement(entry, name, all, "All");
454 }
455
456 headerMangler *
457 HeaderManglers::track(const char *name)
458 {
459 int id = httpHeaderIdByNameDef(name, strlen(name));
460
461 if (id == HDR_BAD_HDR) { // special keyword or a custom header
462 if (strcmp(name, "All") == 0)
463 id = HDR_ENUM_END;
464 else if (strcmp(name, "Other") == 0)
465 id = HDR_OTHER;
466 }
467
468 headerMangler *m = NULL;
469 if (id == HDR_ENUM_END) {
470 m = &all;
471 } else if (id == HDR_BAD_HDR) {
472 m = &custom[name];
473 } else {
474 m = &known[id]; // including HDR_OTHER
475 }
476
477 assert(m);
478 return m;
479 }
480
481 void
482 HeaderManglers::setReplacement(const char *name, const char *value)
483 {
484 // for backword compatibility, we allow replacements to be configured
485 // for headers w/o access rules, but such replacements are ignored
486 headerMangler *m = track(name);
487
488 safe_free(m->replacement); // overwrite old value if any
489 m->replacement = xstrdup(value);
490 }
491
492 const headerMangler *
493 HeaderManglers::find(const HttpHeaderEntry &e) const
494 {
495 // a known header with a configured ACL list
496 if (e.id != HDR_OTHER && 0 <= e.id && e.id < HDR_ENUM_END &&
497 known[e.id].access_list)
498 return &known[e.id];
499
500 // a custom header
501 if (e.id == HDR_OTHER) {
502 // does it have an ACL list configured?
503 // Optimize: use a name type that we do not need to convert to here
504 const ManglersByName::const_iterator i = custom.find(e.name.termedBuf());
505 if (i != custom.end())
506 return &i->second;
507 }
508
509 // Next-to-last resort: "Other" rules match any custom header
510 if (e.id == HDR_OTHER && known[HDR_OTHER].access_list)
511 return &known[HDR_OTHER];
512
513 // Last resort: "All" rules match any header
514 if (all.access_list)
515 return &all;
516
517 return NULL;
518 }
519
520 void
521 httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
522 {
523 ACLFilledChecklist checklist(NULL, request, NULL);
524
525 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
526 if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) {
527 const char *fieldValue = NULL;
528 MemBuf mb;
529 if (hwa->quoted) {
530 if (al != NULL) {
531 mb.init();
532 hwa->valueFormat->assemble(mb, al, 0);
533 fieldValue = mb.content();
534 }
535 } else {
536 fieldValue = hwa->fieldValue.c_str();
537 }
538
539 if (!fieldValue || fieldValue[0] == '\0')
540 fieldValue = "-";
541
542 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
543 fieldValue);
544 heads->addEntry(e);
545 }
546 }
547 }
548