HttpHeaderTools.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2020 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
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 
74 void
75 httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
76 {
78  assert(hdr && ent_len >= 0);
79  httpHdrContRangeSet(cr, spec, ent_len);
80  hdr->putContRange(cr);
81  delete cr;
82 }
83 
89 bool
90 httpHeaderHasConnDir(const HttpHeader * hdr, const SBuf &directive)
91 {
92  String list;
93 
94  /* what type of header do we have? */
95  if (hdr->getList(Http::HdrType::CONNECTION, &list))
96  return strListIsMember(&list, directive, ',') != 0;
97 
98 #if USE_HTTP_VIOLATIONS
100  return strListIsMember(&list, directive, ',') != 0;
101 #endif
102 
103  // else, no connection header for it to exist in
104  return false;
105 }
106 
108 const char *
109 getStringPrefix(const char *str, size_t sz)
110 {
111 #define SHORT_PREFIX_SIZE 512
114  return buf;
115 }
116 
121 int
122 httpHeaderParseInt(const char *start, int *value)
123 {
124  assert(value);
125  *value = atoi(start);
126 
127  if (!*value && !xisdigit(*start)) {
128  debugs(66, 2, "failed to parse an int header field near '" << start << "'");
129  return 0;
130  }
131 
132  return 1;
133 }
134 
135 bool
136 httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
137 {
138  char *end = nullptr;
139  errno = 0;
140  const int64_t res = strtoll(start, &end, 10);
141  if (errno && !res) {
142  debugs(66, 7, "failed to parse malformed offset in " << start);
143  return false;
144  }
145  if (errno == ERANGE && (res == LLONG_MIN || res == LLONG_MAX)) { // no overflow
146  debugs(66, 7, "failed to parse huge offset in " << start);
147  return false;
148  }
149  if (start == end) {
150  debugs(66, 7, "failed to parse empty offset");
151  return false;
152  }
153  *value = res;
154  if (endPtr)
155  *endPtr = end;
156  debugs(66, 7, "offset " << start << " parsed as " << res);
157  return true;
158 }
159 
166 int
167 httpHeaderParseQuotedString(const char *start, const int len, String *val)
168 {
169  const char *end, *pos;
170  val->clean();
171  if (*start != '"') {
172  debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
173  return 0;
174  }
175  pos = start + 1;
176 
177  while (*pos != '"' && len > (pos-start)) {
178 
179  if (*pos =='\r') {
180  ++pos;
181  if ((pos-start) > len || *pos != '\n') {
182  debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
183  << " bytes into '" << start << "'");
184  val->clean();
185  return 0;
186  }
187  }
188 
189  if (*pos == '\n') {
190  ++pos;
191  if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
192  debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
193  val->clean();
194  return 0;
195  }
196  // TODO: replace the entire LWS with a space
197  val->append(" ");
198  ++pos;
199  debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
200  continue;
201  }
202 
203  bool quoted = (*pos == '\\');
204  if (quoted) {
205  ++pos;
206  if (!*pos || (pos-start) > len) {
207  debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
208  val->clean();
209  return 0;
210  }
211  }
212  end = pos;
213  while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
214  ++end;
215  if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
216  debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
217  << " bytes into '" << start << "'");
218  val->clean();
219  return 0;
220  }
221  val->append(pos, end-pos);
222  pos = end;
223  }
224 
225  if (*pos != '\"') {
226  debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
227  val->clean();
228  return 0;
229  }
230  /* Make sure it's defined even if empty "" */
231  if (!val->termedBuf())
232  val->assign("", 0);
233  return 1;
234 }
235 
236 SBuf
237 httpHeaderQuoteString(const char *raw)
238 {
239  assert(raw);
240 
241  // TODO: Optimize by appending a sequence of characters instead of a char.
242  // This optimization may be easier with Tokenizer after raw becomes SBuf.
243 
244  // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
245  // quoted-string except where necessary" (i.e., DQUOTE and backslash)
246  bool needInnerQuote = false;
247  for (const char *s = raw; !needInnerQuote && *s; ++s)
248  needInnerQuote = *s == '"' || *s == '\\';
249 
250  SBuf quotedStr;
251  quotedStr.append('"');
252 
253  if (needInnerQuote) {
254  for (const char *s = raw; *s; ++s) {
255  if (*s == '"' || *s == '\\')
256  quotedStr.append('\\');
257  quotedStr.append(*s);
258  }
259  } else {
260  quotedStr.append(raw);
261  }
262 
263  quotedStr.append('"');
264  return quotedStr;
265 }
266 
275 static int
277 {
278  int retval;
279 
280  assert(e);
281 
282  const headerMangler *hm = hms->find(*e);
283 
284  /* mangler or checklist went away. default allow */
285  if (!hm || !hm->access_list) {
286  debugs(66, 7, "couldn't find mangler or access list. Allowing");
287  return 1;
288  }
289 
290  ACLFilledChecklist checklist(hm->access_list, request, NULL);
291 
292  checklist.al = al;
293  if (al && al->reply) {
294  checklist.reply = al->reply.getRaw();
295  HTTPMSGLOCK(checklist.reply);
296  }
297 
298  if (checklist.fastCheck().allowed()) {
299  /* aclCheckFast returns true for allow. */
300  debugs(66, 7, "checklist for mangler is positive. Mangle");
301  retval = 1;
302  } else if (NULL == hm->replacement) {
303  /* It was denied, and we don't have any replacement */
304  debugs(66, 7, "checklist denied, we have no replacement. Pass");
305  retval = 0;
306  } else {
307  /* It was denied, but we have a replacement. Replace the
308  * header on the fly, and return that the new header
309  * is allowed.
310  */
311  debugs(66, 7, "checklist denied but we have replacement. Replace");
312  e->value = hm->replacement;
313  retval = 1;
314  }
315 
316  return retval;
317 }
318 
320 void
322 {
323  HttpHeaderEntry *e;
325 
326  /* check with anonymizer tables */
327  HeaderManglers *hms = nullptr;
328  HeaderWithAclList *headersAdd = nullptr;
329 
330  switch (req_or_rep) {
331  case ROR_REQUEST:
333  headersAdd = Config.request_header_add;
334  break;
335  case ROR_REPLY:
337  headersAdd = Config.reply_header_add;
338  break;
339  }
340 
341  if (hms) {
342  int headers_deleted = 0;
343  while ((e = l->getEntry(&p))) {
344  if (httpHdrMangle(e, request, hms, al) == 0)
345  l->delAt(p, headers_deleted);
346  }
347 
348  if (headers_deleted)
349  l->refreshMask();
350  }
351 
352  if (headersAdd && !headersAdd->empty()) {
353  httpHdrAdd(l, request, al, *headersAdd);
354  }
355 }
356 
357 static
359 {
362 }
363 
364 static
365 void header_mangler_dump_access(StoreEntry * entry, const char *option,
366  const headerMangler &m, const char *name)
367 {
368  if (m.access_list != NULL) {
369  storeAppendPrintf(entry, "%s ", option);
370  dump_acl_access(entry, name, m.access_list);
371  }
372 }
373 
374 static
375 void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
376  const headerMangler &m, const char *name)
377 {
378  if (m.replacement)
379  storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
380 }
381 
383 {
384  memset(known, 0, sizeof(known));
385  memset(&all, 0, sizeof(all));
386 }
387 
389 {
390  for (auto i : WholeEnum<Http::HdrType>())
392 
393  for (auto i : custom)
394  header_mangler_clean(i.second);
395 
397 }
398 
399 void
400 HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
401 {
402  for (auto id : WholeEnum<Http::HdrType>())
403  header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
404 
405  for (auto i : custom)
406  header_mangler_dump_access(entry, name, i.second, i.first.c_str());
407 
408  header_mangler_dump_access(entry, name, all, "All");
409 }
410 
411 void
412 HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
413 {
414  for (auto id : WholeEnum<Http::HdrType>()) {
415  header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
416  }
417 
418  for (auto i: custom) {
419  header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
420  }
421 
422  header_mangler_dump_replacement(entry, name, all, "All");
423 }
424 
426 HeaderManglers::track(const char *name)
427 {
428  if (strcmp(name, "All") == 0)
429  return &all;
430 
432 
433  if (id != Http::HdrType::BAD_HDR)
434  return &known[id];
435 
436  if (strcmp(name, "Other") == 0)
437  return &known[Http::HdrType::OTHER];
438 
439  return &custom[name];
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 *
455 {
456  // a known header with a configured ACL list
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  SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
466  const ManglersByName::const_iterator i = custom.find(tmp.c_str());
467  if (i != custom.end())
468  return &i->second;
469  }
470 
471  // Next-to-last resort: "Other" rules match any custom header
473  return &known[Http::HdrType::OTHER];
474 
475  // Last resort: "All" rules match any header
476  if (all.access_list)
477  return &all;
478 
479  return NULL;
480 }
481 
482 void
484 {
485  ACLFilledChecklist checklist(NULL, request, NULL);
486 
487  checklist.al = al;
488  if (al && al->reply) {
489  checklist.reply = al->reply.getRaw();
490  HTTPMSGLOCK(checklist.reply);
491  }
492 
493  for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
494  if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
495  const char *fieldValue = NULL;
496  MemBuf mb;
497  if (hwa->quoted) {
498  if (al != NULL) {
499  mb.init();
500  hwa->valueFormat->assemble(mb, al, 0);
501  fieldValue = mb.content();
502  }
503  } else {
504  fieldValue = hwa->fieldValue.c_str();
505  }
506 
507  if (!fieldValue || fieldValue[0] == '\0')
508  fieldValue = "-";
509 
510  HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue);
511  heads->addEntry(e);
512  }
513  }
514 }
515 
HeaderWithAclList * reply_header_add
reply_header_add access list
Definition: SquidConfig.h:480
acl_access * access_list
void refreshMask()
Definition: HttpHeader.cc:740
char * buf
Definition: MemBuf.h:134
HttpHdrContRange * httpHdrContRangeCreate(void)
HeaderWithAclList * request_header_add
request_header_add access list
Definition: SquidConfig.h:478
ssize_t HttpHeaderPos
Definition: HttpHeader.h:45
std::list< HeaderWithAcl > HeaderWithAclList
#define HttpHeaderInitPos
Definition: HttpHeader.h:48
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:881
@ PROXY_CONNECTION
ManglersByName custom
one mangler for each custom header
void httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
HttpReplyPointer reply
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:96
Definition: SBuf.h:86
static void header_mangler_dump_replacement(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
#define xstrdup
void httpHdrContRangeSet(HttpHdrContRange *cr, HttpHdrRangeSpec spec, int64_t ent_len)
const headerMangler * find(const HttpHeaderEntry &e) const
returns a header mangler for field e or nil if none was specified
String getList(Http::HdrType id) const
Definition: HttpHeader.cc:831
C * getRaw() const
Definition: RefCount.h:80
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
#define LOCAL_ARRAY(type, name, size)
Definition: leakcheck.h:18
headerMangler known[static_cast< int >(Http::HdrType::enumEnd_)]
one mangler for each known header
headerMangler * track(const char *name)
returns a mangler for the named header (known or custom)
void httpHeaderPutStrf(HttpHeader *hdr, Http::HdrType id, const char *fmt,...)
bool any_HdrType_enum_value(const Http::HdrType id)
match any known header type, including OTHER and BAD
headerMangler all
configured if some mangling ACL applies to all header names
void putContRange(const HttpHdrContRange *cr)
Definition: HttpHeader.cc:1073
int httpHeaderParseQuotedString(const char *start, const int len, String *val)
bool httpHeaderHasConnDir(const HttpHeader *hdr, const SBuf &directive)
HeaderManglers * reply_header_access
reply_header_access and reply_header_replace
Definition: SquidConfig.h:476
#define NULL
Definition: types.h:166
int strListIsMember(const String *list, const SBuf &m, char del)
Definition: StrList.cc:37
@ ROR_REQUEST
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:128
void dump_acl_access(StoreEntry *entry, const char *name, acl_access *head)
Definition: cache_cf.cc:1452
void append(char const *buf, int len)
Definition: String.cc:161
char HttpHeaderMask[12]
const Acl::Answer & fastCheck()
Definition: Checklist.cc:336
Definition: MemBuf.h:23
A collection of headerMangler objects for a given message kind.
void clean()
Definition: MemBuf.cc:113
std::ostream & HERE(std::ostream &s)
Definition: Debug.h:157
bool httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
SBuf httpHeaderQuoteString(const char *raw)
quotes string using RFC 7230 quoted-string rules
Http::HdrType id
Definition: HttpHeader.h:63
req_or_rep_t
#define safe_free(x)
Definition: xalloc.h:73
void addEntry(HttpHeaderEntry *e)
Definition: HttpHeader.cc:754
#define assert(EX)
Definition: assert.h:19
void HTTPMSGLOCK(Http::Message *a)
Definition: Message.h:160
#define xisdigit(x)
Definition: xis.h:20
const HeaderTableRecord & lookup(const char *buf, const std::size_t len) const
look record type up by name (C-string and length)
const char * c_str()
Definition: SBuf.cc:526
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition: strtoll.c:81
void const char int sz
Definition: stub_cbdata.cc:16
SBuf & append(const SBuf &S)
Definition: SBuf.cc:195
virtual void vappendf(const char *fmt, va_list ap)
Definition: MemBuf.cc:260
void dumpReplacement(StoreEntry *entry, const char *optionName) const
report the *_header_replace part of the configuration
const HeaderLookupTable_t HeaderLookupTable
void aclDestroyAccessList(acl_access **list)
Definition: Gadgets.cc:276
static void header_mangler_clean(headerMangler &m)
const char * getStringPrefix(const char *str, size_t sz)
void dumpAccess(StoreEntry *entry, const char *optionName) const
report the *_header_access part of the configuration
const char * termedBuf() const
Definition: SquidString.h:91
bool allowed() const
Definition: Acl.h:143
static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
void httpHeaderMaskInit(HttpHeaderMask *mask, int value)
char * content()
start of the added data
Definition: MemBuf.h:41
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:1041
static void header_mangler_dump_access(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
void assign(const char *str, int len)
Definition: String.cc:94
int httpHeaderParseInt(const char *start, int *value)
int unsigned int const char *desc STUB void int len
Definition: stub_fd.cc:20
#define SHORT_PREFIX_SIZE
static int httpHdrMangle(HttpHeaderEntry *e, HttpRequest *request, HeaderManglers *hms, const AccessLogEntryPointer &al)
void delAt(HttpHeaderPos pos, int &headers_deleted)
Definition: HttpHeader.cc:712
HttpHeaderEntry * getEntry(HttpHeaderPos *pos) const
Definition: HttpHeader.cc:601
HeaderManglers * request_header_access
request_header_access and request_header_replace
Definition: SquidConfig.h:474
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
void httpHeaderAddContRange(HttpHeader *hdr, HttpHdrRangeSpec spec, int64_t ent_len)
void const char * buf
Definition: stub_helper.cc:16
@ ROR_REPLY
void setReplacement(const char *name, const char *replacementValue)
updates mangler for the named header with a replacement value
class SquidConfig Config
Definition: SquidConfig.cc:12
static void httpHeaderPutStrvf(HttpHeader *hdr, Http::HdrType id, const char *fmt, va_list vargs)
void clean()
Definition: String.cc:125

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors