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