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