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"
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
43static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs);
44static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
45
46void
48{
49 memset(mask, value, sizeof(*mask));
50}
51
52/* same as httpHeaderPutStr, but formats the string using snprintf first */
53void
54httpHeaderPutStrf(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 */
64static void
65httpHeaderPutStrvf(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
75void
77{
79 assert(hdr && ent_len >= 0);
80 httpHdrContRangeSet(cr, spec, ent_len);
81 hdr->putContRange(cr);
82 delete cr;
83}
84
90bool
91httpHeaderHasConnDir(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
101 return strListIsMember(&list, directive, ',') != 0;
102#endif
103
104 // else, no connection header for it to exist in
105 return false;
106}
107
109const char *
110getStringPrefix(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
122int
123httpHeaderParseInt(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
136bool
137httpHeaderParseOffset(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
167int
168httpHeaderParseQuotedString(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
237SBuf
238Http::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
246SBuf
247httpHeaderQuoteString(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
285static 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, nullptr);
301
302 checklist.al = al;
303 if (al && al->reply) {
304 checklist.reply = al->reply.getRaw();
305 HTTPMSGLOCK(checklist.reply);
306 }
307
308 // XXX: The two "It was denied" clauses below mishandle cases with no
309 // matching rules, violating the "If no rules within the set have matching
310 // ACLs, the header field is left as is" promise in squid.conf.
311 // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade().
312 if (checklist.fastCheck().allowed()) {
313 /* aclCheckFast returns true for allow. */
314 debugs(66, 7, "checklist for mangler is positive. Mangle");
315 retval = 1;
316 } else if (nullptr == hm->replacement) {
317 /* It was denied, and we don't have any replacement */
318 debugs(66, 7, "checklist denied, we have no replacement. Pass");
319 // XXX: We said "Pass", but the caller will delete on zero retval.
320 retval = 0;
321 } else {
322 /* It was denied, but we have a replacement. Replace the
323 * header on the fly, and return that the new header
324 * is allowed.
325 */
326 debugs(66, 7, "checklist denied but we have replacement. Replace");
327 e->value = hm->replacement;
328 retval = 1;
329 }
330
331 return retval;
332}
333
335void
337{
340
341 /* check with anonymizer tables */
342 HeaderManglers *hms = nullptr;
343 HeaderWithAclList *headersAdd = nullptr;
344
345 switch (req_or_rep) {
346 case ROR_REQUEST:
348 headersAdd = Config.request_header_add;
349 break;
350 case ROR_REPLY:
352 headersAdd = Config.reply_header_add;
353 break;
354 }
355
356 if (hms) {
357 int headers_deleted = 0;
358 while ((e = l->getEntry(&p))) {
359 if (httpHdrMangle(e, request, hms, al) == 0)
360 l->delAt(p, headers_deleted);
361 }
362
363 if (headers_deleted)
364 l->refreshMask();
365 }
366
367 if (headersAdd && !headersAdd->empty()) {
368 httpHdrAdd(l, request, al, *headersAdd);
369 }
370}
371
372static
374{
377}
378
379static
380void header_mangler_dump_access(StoreEntry * entry, const char *option,
381 const headerMangler &m, const char *name)
382{
383 if (m.access_list != nullptr) {
384 storeAppendPrintf(entry, "%s ", option);
385 dump_acl_access(entry, name, m.access_list);
386 }
387}
388
389static
390void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
391 const headerMangler &m, const char *name)
392{
393 if (m.replacement)
394 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
395}
396
398{
399 memset(known, 0, sizeof(known));
400 memset(&all, 0, sizeof(all));
401}
402
404{
405 for (auto i : WholeEnum<Http::HdrType>())
407
408 for (auto i : custom)
409 header_mangler_clean(i.second);
410
412}
413
414void
415HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
416{
417 for (auto id : WholeEnum<Http::HdrType>())
418 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
419
420 for (auto i : custom)
421 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
422
423 header_mangler_dump_access(entry, name, all, "All");
424}
425
426void
427HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
428{
429 for (auto id : WholeEnum<Http::HdrType>()) {
430 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
431 }
432
433 for (auto i: custom) {
434 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
435 }
436
437 header_mangler_dump_replacement(entry, name, all, "All");
438}
439
441HeaderManglers::track(const char *name)
442{
443 if (strcmp(name, "All") == 0)
444 return &all;
445
447
448 if (id != Http::HdrType::BAD_HDR)
449 return &known[id];
450
451 if (strcmp(name, "Other") == 0)
453
454 return &custom[name];
455}
456
457void
458HeaderManglers::setReplacement(const char *name, const char *value)
459{
460 // for backword compatibility, we allow replacements to be configured
461 // for headers w/o access rules, but such replacements are ignored
462 headerMangler *m = track(name);
463
464 safe_free(m->replacement); // overwrite old value if any
465 m->replacement = xstrdup(value);
466}
467
468const headerMangler *
470{
471 // a known header with a configured ACL list
474 return &known[e.id];
475
476 // a custom header
477 if (e.id == Http::HdrType::OTHER) {
478 // does it have an ACL list configured?
479 // Optimize: use a name type that we do not need to convert to here
480 SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
481 const ManglersByName::const_iterator i = custom.find(tmp.c_str());
482 if (i != custom.end())
483 return &i->second;
484 }
485
486 // Next-to-last resort: "Other" rules match any custom header
489
490 // Last resort: "All" rules match any header
491 if (all.access_list)
492 return &all;
493
494 return nullptr;
495}
496
497void
499{
500 ACLFilledChecklist checklist(nullptr, request, nullptr);
501
502 checklist.al = al;
503 if (al && al->reply) {
504 checklist.reply = al->reply.getRaw();
505 HTTPMSGLOCK(checklist.reply);
506 }
507
508 for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
509 if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) {
510 const char *fieldValue = nullptr;
511 MemBuf mb;
512 if (hwa->quoted) {
513 if (al != nullptr) {
514 mb.init();
515 hwa->valueFormat->assemble(mb, al, 0);
516 fieldValue = mb.content();
517 }
518 } else {
519 fieldValue = hwa->fieldValue.c_str();
520 }
521
522 if (!fieldValue || fieldValue[0] == '\0')
523 fieldValue = "-";
524
525 HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue);
526 heads->addEntry(e);
527 }
528 }
529}
530
#define Here()
source code location of the caller
Definition: Here.h:15
HttpHdrContRange * httpHdrContRangeCreate(void)
void httpHdrContRangeSet(HttpHdrContRange *cr, HttpHdrRangeSpec spec, int64_t ent_len)
char HttpHeaderMask[12]
bool httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
SBuf httpHeaderQuoteString(const char *raw)
quotes string using RFC 7230 quoted-string rules
static int httpHdrMangle(HttpHeaderEntry *e, HttpRequest *request, HeaderManglers *hms, const AccessLogEntryPointer &al)
int httpHeaderParseInt(const char *start, int *value)
static void header_mangler_dump_replacement(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
#define SHORT_PREFIX_SIZE
static void header_mangler_clean(headerMangler &m)
void httpHeaderAddContRange(HttpHeader *hdr, HttpHdrRangeSpec spec, int64_t ent_len)
void httpHeaderPutStrf(HttpHeader *hdr, Http::HdrType id, const char *fmt,...)
const char * getStringPrefix(const char *str, size_t sz)
static void header_mangler_dump_access(StoreEntry *entry, const char *option, const headerMangler &m, const char *name)
void httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep)
static void httpHeaderPutStrvf(HttpHeader *hdr, Http::HdrType id, const char *fmt, va_list vargs)
int httpHeaderParseQuotedString(const char *start, const int len, String *val)
void httpHeaderMaskInit(HttpHeaderMask *mask, int value)
static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd)
bool httpHeaderHasConnDir(const HttpHeader *hdr, const SBuf &directive)
req_or_rep_t
@ ROR_REPLY
@ ROR_REQUEST
std::list< HeaderWithAcl > HeaderWithAclList
ssize_t HttpHeaderPos
Definition: HttpHeader.h:45
#define HttpHeaderInitPos
Definition: HttpHeader.h:48
class SquidConfig Config
Definition: SquidConfig.cc:12
int strListIsMember(const String *list, const SBuf &m, char del)
Definition: StrList.cc:46
SBuf StringToSBuf(const String &s)
create a new SBuf from a String by copying contents
Definition: StringConvert.h:17
#define assert(EX)
Definition: assert.h:17
Acl::Answer const & fastCheck()
Definition: Checklist.cc:332
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
HttpReplyPointer reply
bool allowed() const
Definition: Acl.h:156
A collection of headerMangler objects for a given message kind.
void dumpAccess(StoreEntry *entry, const char *optionName) const
report the *_header_access part of the configuration
const headerMangler * find(const HttpHeaderEntry &e) const
returns a header mangler for field e or nil if none was specified
void setReplacement(const char *name, const char *replacementValue)
updates mangler for the named header with a replacement value
ManglersByName custom
one mangler for each custom header
headerMangler all
configured if some mangling ACL applies to all header names
headerMangler * track(const char *name)
returns a mangler for the named header (known or custom)
void dumpReplacement(StoreEntry *entry, const char *optionName) const
report the *_header_replace part of the configuration
headerMangler known[static_cast< int >(Http::HdrType::enumEnd_)]
one mangler for each known header
Http::HdrType id
Definition: HttpHeader.h:66
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:996
void delAt(HttpHeaderPos pos, int &headers_deleted)
Definition: HttpHeader.cc:695
String getList(Http::HdrType id) const
Definition: HttpHeader.cc:789
void putContRange(const HttpHdrContRange *cr)
Definition: HttpHeader.cc:1027
void refreshMask()
Definition: HttpHeader.cc:723
HttpHeaderEntry * getEntry(HttpHeaderPos *pos) const
Definition: HttpHeader.cc:584
void addEntry(HttpHeaderEntry *e)
Definition: HttpHeader.cc:737
const HeaderTableRecord & lookup(const char *buf, const std::size_t len) const
look record type up by name (C-string and length)
Definition: MemBuf.h:24
void clean()
Definition: MemBuf.cc:110
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
char * buf
Definition: MemBuf.h:134
char * content()
start of the added data
Definition: MemBuf.h:41
void vappendf(const char *fmt, va_list ap) override
Definition: MemBuf.cc:251
C * getRaw() const
Definition: RefCount.h:89
Definition: SBuf.h:94
const char * c_str()
Definition: SBuf.cc:516
SBuf & append(const SBuf &S)
Definition: SBuf.cc:185
HeaderWithAclList * request_header_add
request_header_add access list
Definition: SquidConfig.h:465
HeaderManglers * request_header_access
request_header_access and request_header_replace
Definition: SquidConfig.h:461
HeaderManglers * reply_header_access
reply_header_access and reply_header_replace
Definition: SquidConfig.h:463
HeaderWithAclList * reply_header_add
reply_header_add access list
Definition: SquidConfig.h:467
void clean()
Definition: String.cc:103
void assign(const char *str, int len)
Definition: String.cc:78
char const * termedBuf() const
Definition: SquidString.h:92
void append(char const *buf, int len)
Definition: String.cc:130
an std::runtime_error with thrower location info
Definition: TextException.h:21
acl_access * access_list
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
void aclDestroyAccessList(acl_access **list)
Definition: Gadgets.cc:276
void dump_acl_access(StoreEntry *entry, const char *name, acl_access *head)
Definition: cache_cf.cc:1516
void HTTPMSGLOCK(Http::Message *a)
Definition: Message.h:161
SBuf SlowlyParseQuotedString(const char *description, const char *start, size_t length)
@ PROXY_CONNECTION
bool any_HdrType_enum_value(const Http::HdrType id)
match any known header type, including OTHER and BAD
const HeaderLookupTable_t HeaderLookupTable
#define xstrdup
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:68
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:841
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition: strtoll.c:61
#define safe_free(x)
Definition: xalloc.h:73
#define xisdigit(x)
Definition: xis.h:18
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors