]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpHeaderTools.cc
merge from trunk-r14667
[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 static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
44
45 void
46 httpHeaderMaskInit(HttpHeaderMask * mask, int value)
47 {
48 memset(mask, value, sizeof(*mask));
49 }
50
51 /* same as httpHeaderPutStr, but formats the string using snprintf first */
52 void
53 httpHeaderPutStrf(HttpHeader * hdr, Http::HdrType id, const char *fmt,...)
54 {
55 va_list args;
56 va_start(args, fmt);
57
58 httpHeaderPutStrvf(hdr, id, fmt, args);
59 va_end(args);
60 }
61
62 /* used by httpHeaderPutStrf */
63 static void
64 httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs)
65 {
66 MemBuf mb;
67 mb.init();
68 mb.vappendf(fmt, vargs);
69 hdr->putStr(id, mb.buf);
70 mb.clean();
71 }
72
73 /** wrapper arrounf PutContRange */
74 void
75 httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
76 {
77 HttpHdrContRange *cr = httpHdrContRangeCreate();
78 assert(hdr && ent_len >= 0);
79 httpHdrContRangeSet(cr, spec, ent_len);
80 hdr->putContRange(cr);
81 delete cr;
82 }
83
84 /**
85 * return true if a given directive is found in at least one of
86 * the "connection" header-fields note: if Http::HdrType::PROXY_CONNECTION is
87 * present we ignore Http::HdrType::CONNECTION.
88 */
89 int
90 httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive)
91 {
92 String list;
93 int res;
94 /* what type of header do we have? */
95
96 #if USE_HTTP_VIOLATIONS
97 if (hdr->has(Http::HdrType::PROXY_CONNECTION))
98 list = hdr->getList(Http::HdrType::PROXY_CONNECTION);
99 else
100 #endif
101 if (hdr->has(Http::HdrType::CONNECTION))
102 list = hdr->getList(Http::HdrType::CONNECTION);
103 else
104 return 0;
105
106 res = strListIsMember(&list, directive, ',');
107
108 list.clean();
109
110 return res;
111 }
112
113 /** handy to printf prefixes of potentially very long buffers */
114 const char *
115 getStringPrefix(const char *str, size_t sz)
116 {
117 #define SHORT_PREFIX_SIZE 512
118 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
119 xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
120 return buf;
121 }
122
123 /**
124 * parses an int field, complains if soemthing went wrong, returns true on
125 * success
126 */
127 int
128 httpHeaderParseInt(const char *start, int *value)
129 {
130 assert(value);
131 *value = atoi(start);
132
133 if (!*value && !xisdigit(*start)) {
134 debugs(66, 2, "failed to parse an int header field near '" << start << "'");
135 return 0;
136 }
137
138 return 1;
139 }
140
141 int
142 httpHeaderParseOffset(const char *start, int64_t * value)
143 {
144 errno = 0;
145 int64_t res = strtoll(start, NULL, 10);
146 if (!res && EINVAL == errno) { /* maybe not portable? */
147 debugs(66, 7, "failed to parse offset in " << start);
148 return 0;
149 }
150 *value = res;
151 debugs(66, 7, "offset " << start << " parsed as " << res);
152 return 1;
153 }
154
155 /**
156 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
157 * something went wrong, returns non-zero on success.
158 * Un-escapes quoted-pair characters found within the string.
159 * start should point at the first double-quote.
160 */
161 int
162 httpHeaderParseQuotedString(const char *start, const int len, String *val)
163 {
164 const char *end, *pos;
165 val->clean();
166 if (*start != '"') {
167 debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
168 return 0;
169 }
170 pos = start + 1;
171
172 while (*pos != '"' && len > (pos-start)) {
173
174 if (*pos =='\r') {
175 ++pos;
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') {
185 ++pos;
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(" ");
193 ++pos;
194 debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
195 continue;
196 }
197
198 bool quoted = (*pos == '\\');
199 if (quoted) {
200 ++pos;
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 }
206 }
207 end = pos;
208 while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
209 ++end;
210 if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
211 debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
212 << " bytes into '" << start << "'");
213 val->clean();
214 return 0;
215 }
216 val->append(pos, end-pos);
217 pos = end;
218 }
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 }
225 /* Make sure it's defined even if empty "" */
226 if (!val->termedBuf())
227 val->limitInit("", 0);
228 return 1;
229 }
230
231 SBuf
232 httpHeaderQuoteString(const char *raw)
233 {
234 assert(raw);
235
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
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)
241 bool needInnerQuote = false;
242 for (const char *s = raw; !needInnerQuote && *s; ++s)
243 needInnerQuote = *s == '"' || *s == '\\';
244
245 SBuf quotedStr;
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 }
257
258 quotedStr.append('"');
259 return quotedStr;
260 }
261
262 /**
263 * Checks the anonymizer (header_access) configuration.
264 *
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
269 */
270 static int
271 httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms)
272 {
273 int retval;
274
275 assert(e);
276
277 const headerMangler *hm = hms->find(*e);
278
279 /* mangler or checklist went away. default allow */
280 if (!hm || !hm->access_list) {
281 debugs(66, 7, "couldn't find mangler or access list. Allowing");
282 return 1;
283 }
284
285 ACLFilledChecklist checklist(hm->access_list, request, NULL);
286
287 if (checklist.fastCheck() == ACCESS_ALLOWED) {
288 /* aclCheckFast returns true for allow. */
289 debugs(66, 7, "checklist for mangler is positive. Mangle");
290 retval = 1;
291 } else if (NULL == hm->replacement) {
292 /* It was denied, and we don't have any replacement */
293 debugs(66, 7, "checklist denied, we have no replacement. Pass");
294 retval = 0;
295 } else {
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 */
300 debugs(66, 7, "checklist denied but we have replacement. Replace");
301 e->value = hm->replacement;
302 retval = 1;
303 }
304
305 return retval;
306 }
307
308 /** Mangles headers for a list of headers. */
309 void
310 httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
311 {
312 HttpHeaderEntry *e;
313 HttpHeaderPos p = HttpHeaderInitPos;
314
315 /* check with anonymizer tables */
316 HeaderManglers *hms = nullptr;
317 HeaderWithAclList *headersAdd = nullptr;
318
319 switch (req_or_rep) {
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;
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 }
336
337 if (headers_deleted)
338 l->refreshMask();
339 }
340
341 if (headersAdd && !headersAdd->empty()) {
342 httpHdrAdd(l, request, al, *headersAdd);
343 }
344 }
345
346 static
347 void header_mangler_clean(headerMangler &m)
348 {
349 aclDestroyAccessList(&m.access_list);
350 safe_free(m.replacement);
351 }
352
353 static
354 void header_mangler_dump_access(StoreEntry * entry, const char *option,
355 const headerMangler &m, const char *name)
356 {
357 if (m.access_list != NULL) {
358 storeAppendPrintf(entry, "%s ", option);
359 dump_acl_access(entry, name, m.access_list);
360 }
361 }
362
363 static
364 void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
365 const headerMangler &m, const char *name)
366 {
367 if (m.replacement)
368 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
369 }
370
371 HeaderManglers::HeaderManglers()
372 {
373 memset(known, 0, sizeof(known));
374 memset(&all, 0, sizeof(all));
375 }
376
377 HeaderManglers::~HeaderManglers()
378 {
379 for (auto i : WholeEnum<Http::HdrType>())
380 header_mangler_clean(known[i]);
381
382 for (auto i : custom)
383 header_mangler_clean(i.second);
384
385 header_mangler_clean(all);
386 }
387
388 void
389 HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
390 {
391 for (auto id : WholeEnum<Http::HdrType>())
392 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
393
394 for (auto i : custom)
395 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
396
397 header_mangler_dump_access(entry, name, all, "All");
398 }
399
400 void
401 HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
402 {
403 for (auto id : WholeEnum<Http::HdrType>()) {
404 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
405 }
406
407 for (auto i: custom) {
408 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
409 }
410
411 header_mangler_dump_replacement(entry, name, all, "All");
412 }
413
414 headerMangler *
415 HeaderManglers::track(const char *name)
416 {
417 if (strcmp(name, "All") == 0)
418 return &all;
419
420 const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id;
421
422 if (id != Http::HdrType::BAD_HDR)
423 return &known[id];
424
425 if (strcmp(name, "Other") == 0)
426 return &known[Http::HdrType::OTHER];
427
428 return &custom[name];
429 }
430
431 void
432 HeaderManglers::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
436 headerMangler *m = track(name);
437
438 safe_free(m->replacement); // overwrite old value if any
439 m->replacement = xstrdup(value);
440 }
441
442 const headerMangler *
443 HeaderManglers::find(const HttpHeaderEntry &e) const
444 {
445 // a known header with a configured ACL list
446 if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) &&
447 known[e.id].access_list)
448 return &known[e.id];
449
450 // a custom header
451 if (e.id == Http::HdrType::OTHER) {
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
460 if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list)
461 return &known[Http::HdrType::OTHER];
462
463 // Last resort: "All" rules match any header
464 if (all.access_list)
465 return &all;
466
467 return NULL;
468 }
469
470 void
471 httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
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) {
480 if (al != NULL) {
481 mb.init();
482 hwa->valueFormat->assemble(mb, al, 0);
483 fieldValue = mb.content();
484 }
485 } else {
486 fieldValue = hwa->fieldValue.c_str();
487 }
488
489 if (!fieldValue || fieldValue[0] == '\0')
490 fieldValue = "-";
491
492 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
493 fieldValue);
494 heads->addEntry(e);
495 }
496 }
497 }
498