HttpHeaderTools.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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 "HttpHeaderTools.h"
27 #include "HttpRequest.h"
28 #include "MemBuf.h"
29 #include "sbuf/Stream.h"
30 #include "sbuf/StringConvert.h"
31 #include "SquidConfig.h"
32 #include "Store.h"
33 #include "StrList.h"
34 
35 #if USE_OPENSSL
36 #include "ssl/support.h"
37 #endif
38 
39 #include <algorithm>
40 #include <cerrno>
41 #include <string>
42 
43 static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs);
44 static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
45 
46 void
48 {
49  memset(mask, value, sizeof(*mask));
50 }
51 
52 /* same as httpHeaderPutStr, but formats the string using snprintf first */
53 void
54 httpHeaderPutStrf(HttpHeader * hdr, Http::HdrType id, const char *fmt,...)
55 {
56  va_list args;
57  va_start(args, fmt);
58 
59  httpHeaderPutStrvf(hdr, id, fmt, args);
60  va_end(args);
61 }
62 
63 /* used by httpHeaderPutStrf */
64 static void
65 httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs)
66 {
67  MemBuf mb;
68  mb.init();
69  mb.vappendf(fmt, vargs);
70  hdr->putStr(id, mb.buf);
71  mb.clean();
72 }
73 
75 void
76 httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
77 {
79  assert(hdr && ent_len >= 0);
80  httpHdrContRangeSet(cr, spec, ent_len);
81  hdr->putContRange(cr);
82  delete cr;
83 }
84 
90 bool
91 httpHeaderHasConnDir(const HttpHeader * hdr, const SBuf &directive)
92 {
93  String list;
94 
95  /* what type of header do we have? */
96  if (hdr->getList(Http::HdrType::CONNECTION, &list))
97  return strListIsMember(&list, directive, ',') != 0;
98 
99 #if USE_HTTP_VIOLATIONS
100  if (hdr->getList(Http::HdrType::PROXY_CONNECTION, &list))
101  return strListIsMember(&list, directive, ',') != 0;
102 #endif
103 
104  // else, no connection header for it to exist in
105  return false;
106 }
107 
109 const char *
110 getStringPrefix(const char *str, size_t sz)
111 {
112 #define SHORT_PREFIX_SIZE 512
113  LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
114  xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
115  return buf;
116 }
117 
122 int
123 httpHeaderParseInt(const char *start, int *value)
124 {
125  assert(value);
126  *value = atoi(start);
127 
128  if (!*value && !xisdigit(*start)) {
129  debugs(66, 2, "failed to parse an int header field near '" << start << "'");
130  return 0;
131  }
132 
133  return 1;
134 }
135 
136 bool
137 httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
138 {
139  char *end = nullptr;
140  errno = 0;
141  const int64_t res = strtoll(start, &end, 10);
142  if (errno && !res) {
143  debugs(66, 7, "failed to parse malformed offset in " << start);
144  return false;
145  }
146  if (errno == ERANGE && (res == LLONG_MIN || res == LLONG_MAX)) { // no overflow
147  debugs(66, 7, "failed to parse huge offset in " << start);
148  return false;
149  }
150  if (start == end) {
151  debugs(66, 7, "failed to parse empty offset");
152  return false;
153  }
154  *value = res;
155  if (endPtr)
156  *endPtr = end;
157  debugs(66, 7, "offset " << start << " parsed as " << res);
158  return true;
159 }
160 
167 int
168 httpHeaderParseQuotedString(const char *start, const int len, String *val)
169 {
170  const char *end, *pos;
171  val->clean();
172  if (*start != '"') {
173  debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'");
174  return 0;
175  }
176  pos = start + 1;
177 
178  while (*pos != '"' && len > (pos-start)) {
179 
180  if (*pos =='\r') {
181  ++pos;
182  if ((pos-start) > len || *pos != '\n') {
183  debugs(66, 2, "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
184  << " bytes into '" << start << "'");
185  val->clean();
186  return 0;
187  }
188  }
189 
190  if (*pos == '\n') {
191  ++pos;
192  if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
193  debugs(66, 2, "failed to parse multiline quoted-string header field '" << start << "'");
194  val->clean();
195  return 0;
196  }
197  // TODO: replace the entire LWS with a space
198  val->append(" ");
199  ++pos;
200  debugs(66, 2, "len < pos-start => " << len << " < " << (pos-start));
201  continue;
202  }
203 
204  bool quoted = (*pos == '\\');
205  if (quoted) {
206  ++pos;
207  if (!*pos || (pos-start) > len) {
208  debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'");
209  val->clean();
210  return 0;
211  }
212  }
213  end = pos;
214  while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
215  ++end;
216  if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
217  debugs(66, 2, "failed to parse a quoted-string header field with CTL octet " << (start-pos)
218  << " bytes into '" << start << "'");
219  val->clean();
220  return 0;
221  }
222  val->append(pos, end-pos);
223  pos = end;
224  }
225 
226  if (*pos != '\"') {
227  debugs(66, 2, "failed to parse a quoted-string header field which did not end with \" ");
228  val->clean();
229  return 0;
230  }
231  /* Make sure it's defined even if empty "" */
232  if (!val->termedBuf())
233  val->assign("", 0);
234  return 1;
235 }
236 
237 SBuf
238 Http::SlowlyParseQuotedString(const char * const description, const char * const start, const size_t length)
239 {
240  String s;
241  if (!httpHeaderParseQuotedString(start, length, &s))
242  throw TextException(ToSBuf("Cannot parse ", description, " as a quoted string"), Here());
243  return StringToSBuf(s);
244 }
245 
246 SBuf
247 httpHeaderQuoteString(const char *raw)
248 {
249  assert(raw);
250 
251  // TODO: Optimize by appending a sequence of characters instead of a char.
252  // This optimization may be easier with Tokenizer after raw becomes SBuf.
253 
254  // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
255  // quoted-string except where necessary" (i.e., DQUOTE and backslash)
256  bool needInnerQuote = false;
257  for (const char *s = raw; !needInnerQuote && *s; ++s)
258  needInnerQuote = *s == '"' || *s == '\\';
259 
260  SBuf quotedStr;
261  quotedStr.append('"');
262 
263  if (needInnerQuote) {
264  for (const char *s = raw; *s; ++s) {
265  if (*s == '"' || *s == '\\')
266  quotedStr.append('\\');
267  quotedStr.append(*s);
268  }
269  } else {
270  quotedStr.append(raw);
271  }
272 
273  quotedStr.append('"');
274  return quotedStr;
275 }
276 
285 static int
287 {
288  int retval;
289 
290  assert(e);
291 
292  const headerMangler *hm = hms->find(*e);
293 
294  /* mangler or checklist went away. default allow */
295  if (!hm || !hm->access_list) {
296  debugs(66, 7, "couldn't find mangler or access list. Allowing");
297  return 1;
298  }
299 
300  ACLFilledChecklist checklist(hm->access_list, request);
301  checklist.updateAle(al);
302 
303  // XXX: The two "It was denied" clauses below mishandle cases with no
304  // matching rules, violating the "If no rules within the set have matching
305  // ACLs, the header field is left as is" promise in squid.conf.
306  // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade().
307  if (checklist.fastCheck().allowed()) {
308  /* aclCheckFast returns true for allow. */
309  debugs(66, 7, "checklist for mangler is positive. Mangle");
310  retval = 1;
311  } else if (nullptr == hm->replacement) {
312  /* It was denied, and we don't have any replacement */
313  debugs(66, 7, "checklist denied, we have no replacement. Pass");
314  // XXX: We said "Pass", but the caller will delete on zero retval.
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  debugs(66, 7, "checklist denied but we have replacement. Replace");
322  e->value = hm->replacement;
323  retval = 1;
324  }
325 
326  return retval;
327 }
328 
330 void
332 {
333  HttpHeaderEntry *e;
335 
336  /* check with anonymizer tables */
337  HeaderManglers *hms = nullptr;
338  HeaderWithAclList *headersAdd = nullptr;
339 
340  switch (req_or_rep) {
341  case ROR_REQUEST:
343  headersAdd = Config.request_header_add;
344  break;
345  case ROR_REPLY:
347  headersAdd = Config.reply_header_add;
348  break;
349  }
350 
351  if (hms) {
352  int headers_deleted = 0;
353  while ((e = l->getEntry(&p))) {
354  if (httpHdrMangle(e, request, hms, al) == 0)
355  l->delAt(p, headers_deleted);
356  }
357 
358  if (headers_deleted)
359  l->refreshMask();
360  }
361 
362  if (headersAdd && !headersAdd->empty()) {
363  httpHdrAdd(l, request, al, *headersAdd);
364  }
365 }
366 
367 static
369 {
372 }
373 
374 static
375 void header_mangler_dump_access(StoreEntry * entry, const char *option,
376  const headerMangler &m, const char *name)
377 {
378  if (m.access_list != nullptr) {
379  storeAppendPrintf(entry, "%s ", option);
380  dump_acl_access(entry, name, m.access_list);
381  }
382 }
383 
384 static
385 void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
386  const headerMangler &m, const char *name)
387 {
388  if (m.replacement)
389  storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
390 }
391 
393 {
394  memset(known, 0, sizeof(known));
395  memset(&all, 0, sizeof(all));
396 }
397 
399 {
400  for (auto i : WholeEnum<Http::HdrType>())
402 
403  for (auto i : custom)
404  header_mangler_clean(i.second);
405 
407 }
408 
409 void
410 HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
411 {
412  for (auto id : WholeEnum<Http::HdrType>())
413  header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
414 
415  for (auto i : custom)
416  header_mangler_dump_access(entry, name, i.second, i.first.c_str());
417 
418  header_mangler_dump_access(entry, name, all, "All");
419 }
420 
421 void
422 HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
423 {
424  for (auto id : WholeEnum<Http::HdrType>()) {
425  header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
426  }
427 
428  for (auto i: custom) {
429  header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
430  }
431 
432  header_mangler_dump_replacement(entry, name, all, "All");
433 }
434 
436 HeaderManglers::track(const char *name)
437 {
438  if (strcmp(name, "All") == 0)
439  return &all;
440 
442 
443  if (id != Http::HdrType::BAD_HDR)
444  return &known[id];
445 
446  if (strcmp(name, "Other") == 0)
447  return &known[Http::HdrType::OTHER];
448 
449  return &custom[name];
450 }
451 
452 void
453 HeaderManglers::setReplacement(const char *name, const char *value)
454 {
455  // for backword compatibility, we allow replacements to be configured
456  // for headers w/o access rules, but such replacements are ignored
457  headerMangler *m = track(name);
458 
459  safe_free(m->replacement); // overwrite old value if any
460  m->replacement = xstrdup(value);
461 }
462 
463 const headerMangler *
465 {
466  // a known header with a configured ACL list
468  known[e.id].access_list)
469  return &known[e.id];
470 
471  // a custom header
472  if (e.id == Http::HdrType::OTHER) {
473  // does it have an ACL list configured?
474  // Optimize: use a name type that we do not need to convert to here
475  SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
476  const ManglersByName::const_iterator i = custom.find(tmp.c_str());
477  if (i != custom.end())
478  return &i->second;
479  }
480 
481  // Next-to-last resort: "Other" rules match any custom header
483  return &known[Http::HdrType::OTHER];
484 
485  // Last resort: "All" rules match any header
486  if (all.access_list)
487  return &all;
488 
489  return nullptr;
490 }
491 
492 void
494 {
495  ACLFilledChecklist checklist(nullptr, request);
496  checklist.updateAle(al);
497 
498  for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
499  if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
500  const char *fieldValue = nullptr;
501  MemBuf mb;
502  if (hwa->quoted) {
503  if (al != nullptr) {
504  mb.init();
505  hwa->valueFormat->assemble(mb, al, 0);
506  fieldValue = mb.content();
507  }
508  } else {
509  fieldValue = hwa->fieldValue.c_str();
510  }
511 
512  if (!fieldValue || fieldValue[0] == '\0')
513  fieldValue = "-";
514 
515  HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue);
516  heads->addEntry(e);
517  }
518  }
519 }
520 
HeaderWithAclList * reply_header_add
reply_header_add access list
Definition: SquidConfig.h:467
acl_access * access_list
void refreshMask()
Definition: HttpHeader.cc:722
char * buf
Definition: MemBuf.h:134
HttpHdrContRange * httpHdrContRangeCreate(void)
#define Here()
source code location of the caller
Definition: Here.h:15
HeaderWithAclList * request_header_add
request_header_add access list
Definition: SquidConfig.h:465
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:62
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:855
@ 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)
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
Definition: SBuf.h:93
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:788
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
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:1026
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:463
int strListIsMember(const String *list, const SBuf &m, char del)
Definition: StrList.cc:46
@ ROR_REQUEST
void dump_acl_access(StoreEntry *entry, const char *name, acl_access *head)
Definition: cache_cf.cc:1511
void append(char const *buf, int len)
Definition: String.cc:130
char HttpHeaderMask[12]
const Acl::Answer & fastCheck()
Definition: Checklist.cc:298
Definition: MemBuf.h:23
SBuf StringToSBuf(const String &s)
create a new SBuf from a String by copying contents
Definition: StringConvert.h:17
A collection of headerMangler objects for a given message kind.
void clean()
Definition: MemBuf.cc:110
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:66
req_or_rep_t
#define safe_free(x)
Definition: xalloc.h:73
void addEntry(HttpHeaderEntry *e)
Definition: HttpHeader.cc:736
#define assert(EX)
Definition: assert.h:17
SBuf SlowlyParseQuotedString(const char *description, const char *start, size_t length)
#define xisdigit(x)
Definition: xis.h:18
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:516
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition: strtoll.c:61
SBuf & append(const SBuf &S)
Definition: SBuf.cc:185
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:223
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
void vappendf(const char *fmt, va_list ap) override
Definition: MemBuf.cc:251
const char * termedBuf() const
Definition: SquidString.h:92
bool allowed() const
Definition: Acl.h:82
static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
void httpHeaderMaskInit(HttpHeaderMask *mask, int value)
an std::runtime_error with thrower location info
Definition: TextException.h:20
char * content()
start of the added data
Definition: MemBuf.h:41
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:995
static void header_mangler_dump_access(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
void assign(const char *str, int len)
Definition: String.cc:78
int httpHeaderParseInt(const char *start, int *value)
#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:694
HttpHeaderEntry * getEntry(HttpHeaderPos *pos) const
Definition: HttpHeader.cc:583
HeaderManglers * request_header_access
request_header_access and request_header_replace
Definition: SquidConfig.h:461
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void updateAle(const AccessLogEntry::Pointer &)
void httpHeaderAddContRange(HttpHeader *hdr, HttpHdrRangeSpec spec, int64_t ent_len)
@ 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:103

 

Introduction

Documentation

Support

Miscellaneous