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 "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
42static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs);
43static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd);
44
45void
47{
48 memset(mask, value, sizeof(*mask));
49}
50
51/* same as httpHeaderPutStr, but formats the string using snprintf first */
52void
53httpHeaderPutStrf(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 */
63static void
64httpHeaderPutStrvf(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
74void
76{
78 assert(hdr && ent_len >= 0);
79 httpHdrContRangeSet(cr, spec, ent_len);
80 hdr->putContRange(cr);
81 delete cr;
82}
83
89bool
90httpHeaderHasConnDir(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
108const char *
109getStringPrefix(const char *str, size_t sz)
110{
111#define SHORT_PREFIX_SIZE 512
112 LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
113 xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
114 return buf;
115}
116
121int
122httpHeaderParseInt(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
135bool
136httpHeaderParseOffset(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
166int
167httpHeaderParseQuotedString(const char *start, const int len, String *val)
168{
169 const char *end, *pos;
170 val->clean();
171 if (*start != '"') {
172 debugs(66, 2, "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, "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, "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, "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, "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, "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, "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
236SBuf
237httpHeaderQuoteString(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
275static 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, nullptr);
291
292 checklist.al = al;
293 if (al && al->reply) {
294 checklist.reply = al->reply.getRaw();
295 HTTPMSGLOCK(checklist.reply);
296 }
297
298 // XXX: The two "It was denied" clauses below mishandle cases with no
299 // matching rules, violating the "If no rules within the set have matching
300 // ACLs, the header field is left as is" promise in squid.conf.
301 // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade().
302 if (checklist.fastCheck().allowed()) {
303 /* aclCheckFast returns true for allow. */
304 debugs(66, 7, "checklist for mangler is positive. Mangle");
305 retval = 1;
306 } else if (nullptr == hm->replacement) {
307 /* It was denied, and we don't have any replacement */
308 debugs(66, 7, "checklist denied, we have no replacement. Pass");
309 // XXX: We said "Pass", but the caller will delete on zero retval.
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
325void
327{
330
331 /* check with anonymizer tables */
332 HeaderManglers *hms = nullptr;
333 HeaderWithAclList *headersAdd = nullptr;
334
335 switch (req_or_rep) {
336 case ROR_REQUEST:
338 headersAdd = Config.request_header_add;
339 break;
340 case ROR_REPLY:
342 headersAdd = Config.reply_header_add;
343 break;
344 }
345
346 if (hms) {
347 int headers_deleted = 0;
348 while ((e = l->getEntry(&p))) {
349 if (httpHdrMangle(e, request, hms, al) == 0)
350 l->delAt(p, headers_deleted);
351 }
352
353 if (headers_deleted)
354 l->refreshMask();
355 }
356
357 if (headersAdd && !headersAdd->empty()) {
358 httpHdrAdd(l, request, al, *headersAdd);
359 }
360}
361
362static
364{
367}
368
369static
370void header_mangler_dump_access(StoreEntry * entry, const char *option,
371 const headerMangler &m, const char *name)
372{
373 if (m.access_list != nullptr) {
374 storeAppendPrintf(entry, "%s ", option);
375 dump_acl_access(entry, name, m.access_list);
376 }
377}
378
379static
380void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
381 const headerMangler &m, const char *name)
382{
383 if (m.replacement)
384 storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
385}
386
388{
389 memset(known, 0, sizeof(known));
390 memset(&all, 0, sizeof(all));
391}
392
394{
395 for (auto i : WholeEnum<Http::HdrType>())
397
398 for (auto i : custom)
399 header_mangler_clean(i.second);
400
402}
403
404void
405HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
406{
407 for (auto id : WholeEnum<Http::HdrType>())
408 header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
409
410 for (auto i : custom)
411 header_mangler_dump_access(entry, name, i.second, i.first.c_str());
412
413 header_mangler_dump_access(entry, name, all, "All");
414}
415
416void
417HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
418{
419 for (auto id : WholeEnum<Http::HdrType>()) {
420 header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name);
421 }
422
423 for (auto i: custom) {
424 header_mangler_dump_replacement(entry, name, i.second, i.first.c_str());
425 }
426
427 header_mangler_dump_replacement(entry, name, all, "All");
428}
429
431HeaderManglers::track(const char *name)
432{
433 if (strcmp(name, "All") == 0)
434 return &all;
435
437
438 if (id != Http::HdrType::BAD_HDR)
439 return &known[id];
440
441 if (strcmp(name, "Other") == 0)
443
444 return &custom[name];
445}
446
447void
448HeaderManglers::setReplacement(const char *name, const char *value)
449{
450 // for backword compatibility, we allow replacements to be configured
451 // for headers w/o access rules, but such replacements are ignored
452 headerMangler *m = track(name);
453
454 safe_free(m->replacement); // overwrite old value if any
455 m->replacement = xstrdup(value);
456}
457
458const headerMangler *
460{
461 // a known header with a configured ACL list
464 return &known[e.id];
465
466 // a custom header
467 if (e.id == Http::HdrType::OTHER) {
468 // does it have an ACL list configured?
469 // Optimize: use a name type that we do not need to convert to here
470 SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates
471 const ManglersByName::const_iterator i = custom.find(tmp.c_str());
472 if (i != custom.end())
473 return &i->second;
474 }
475
476 // Next-to-last resort: "Other" rules match any custom header
479
480 // Last resort: "All" rules match any header
481 if (all.access_list)
482 return &all;
483
484 return nullptr;
485}
486
487void
489{
490 ACLFilledChecklist checklist(nullptr, request, nullptr);
491
492 checklist.al = al;
493 if (al && al->reply) {
494 checklist.reply = al->reply.getRaw();
495 HTTPMSGLOCK(checklist.reply);
496 }
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
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
#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:63
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:1027
void delAt(HttpHeaderPos pos, int &headers_deleted)
Definition: HttpHeader.cc:701
String getList(Http::HdrType id) const
Definition: HttpHeader.cc:820
void putContRange(const HttpHdrContRange *cr)
Definition: HttpHeader.cc:1059
void refreshMask()
Definition: HttpHeader.cc:729
HttpHeaderEntry * getEntry(HttpHeaderPos *pos) const
Definition: HttpHeader.cc:590
void addEntry(HttpHeaderEntry *e)
Definition: HttpHeader.cc:743
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:80
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:473
HeaderManglers * request_header_access
request_header_access and request_header_replace
Definition: SquidConfig.h:469
HeaderManglers * reply_header_access
reply_header_access and reply_header_replace
Definition: SquidConfig.h:471
HeaderWithAclList * reply_header_add
reply_header_add access list
Definition: SquidConfig.h:475
void clean()
Definition: String.cc:118
void assign(const char *str, int len)
Definition: String.cc:89
char const * termedBuf() const
Definition: SquidString.h:92
void append(char const *buf, int len)
Definition: String.cc:149
acl_access * access_list
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:193
void aclDestroyAccessList(acl_access **list)
Definition: Gadgets.cc:279
void dump_acl_access(StoreEntry *entry, const char *name, acl_access *head)
Definition: cache_cf.cc:1515
void HTTPMSGLOCK(Http::Message *a)
Definition: Message.h:160
@ 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
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:68
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:829
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:20
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors