]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpHeaderTools.cc
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 66 HTTP Header Tools */
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"
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"
30 #include "SquidConfig.h"
35 #include "ssl/support.h"
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
);
46 httpHeaderMaskInit(HttpHeaderMask
* mask
, int value
)
48 memset(mask
, value
, sizeof(*mask
));
51 /* same as httpHeaderPutStr, but formats the string using snprintf first */
53 httpHeaderPutStrf(HttpHeader
* hdr
, Http::HdrType id
, const char *fmt
,...)
58 httpHeaderPutStrvf(hdr
, id
, fmt
, args
);
62 /* used by httpHeaderPutStrf */
64 httpHeaderPutStrvf(HttpHeader
* hdr
, Http::HdrType id
, const char *fmt
, va_list vargs
)
68 mb
.vappendf(fmt
, vargs
);
69 hdr
->putStr(id
, mb
.buf
);
73 /** wrapper arrounf PutContRange */
75 httpHeaderAddContRange(HttpHeader
* hdr
, HttpHdrRangeSpec spec
, int64_t ent_len
)
77 HttpHdrContRange
*cr
= httpHdrContRangeCreate();
78 assert(hdr
&& ent_len
>= 0);
79 httpHdrContRangeSet(cr
, spec
, ent_len
);
80 hdr
->putContRange(cr
);
85 * \return true if a given directive is found in the Connection header field-value.
87 * \note if no Connection header exists we may check the Proxy-Connection header
90 httpHeaderHasConnDir(const HttpHeader
* hdr
, const SBuf
&directive
)
94 /* what type of header do we have? */
95 if (hdr
->getList(Http::HdrType::CONNECTION
, &list
))
96 return strListIsMember(&list
, directive
, ',') != 0;
98 #if USE_HTTP_VIOLATIONS
99 if (hdr
->getList(Http::HdrType::PROXY_CONNECTION
, &list
))
100 return strListIsMember(&list
, directive
, ',') != 0;
103 // else, no connection header for it to exist in
107 /** handy to printf prefixes of potentially very long buffers */
109 getStringPrefix(const char *str
, size_t sz
)
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
);
118 * parses an int field, complains if something went wrong, returns true on
122 httpHeaderParseInt(const char *start
, int *value
)
125 *value
= atoi(start
);
127 if (!*value
&& !xisdigit(*start
)) {
128 debugs(66, 2, "failed to parse an int header field near '" << start
<< "'");
136 httpHeaderParseOffset(const char *start
, int64_t *value
, char **endPtr
)
140 const int64_t res
= strtoll(start
, &end
, 10);
142 debugs(66, 7, "failed to parse malformed offset in " << start
);
145 if (errno
== ERANGE
&& (res
== LLONG_MIN
|| res
== LLONG_MAX
)) { // no overflow
146 debugs(66, 7, "failed to parse huge offset in " << start
);
150 debugs(66, 7, "failed to parse empty offset");
156 debugs(66, 7, "offset " << start
<< " parsed as " << res
);
161 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
162 * something went wrong, returns non-zero on success.
163 * Un-escapes quoted-pair characters found within the string.
164 * start should point at the first double-quote.
167 httpHeaderParseQuotedString(const char *start
, const int len
, String
*val
)
169 const char *end
, *pos
;
172 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
177 while (*pos
!= '"' && len
> (pos
-start
)) {
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
<< "'");
191 if ( (pos
-start
) > len
|| (*pos
!= ' ' && *pos
!= '\t')) {
192 debugs(66, 2, HERE
<< "failed to parse multiline quoted-string header field '" << start
<< "'");
196 // TODO: replace the entire LWS with a space
199 debugs(66, 2, HERE
<< "len < pos-start => " << len
<< " < " << (pos
-start
));
203 bool quoted
= (*pos
== '\\');
206 if (!*pos
|| (pos
-start
) > len
) {
207 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
213 while (end
< (start
+len
) && *end
!= '\\' && *end
!= '\"' && (unsigned char)*end
> 0x1F && *end
!= 0x7F)
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
<< "'");
221 val
->append(pos
, end
-pos
);
226 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field which did not end with \" ");
230 /* Make sure it's defined even if empty "" */
231 if (!val
->termedBuf())
237 httpHeaderQuoteString(const char *raw
)
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.
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
== '\\';
251 quotedStr
.append('"');
253 if (needInnerQuote
) {
254 for (const char *s
= raw
; *s
; ++s
) {
255 if (*s
== '"' || *s
== '\\')
256 quotedStr
.append('\\');
257 quotedStr
.append(*s
);
260 quotedStr
.append(raw
);
263 quotedStr
.append('"');
268 * Checks the anonymizer (header_access) configuration.
270 * \retval 0 Header is explicitly blocked for removal
271 * \retval 1 Header is explicitly allowed
272 * \retval 1 Header has been replaced, the current version can be used.
273 * \retval 1 Header has no access controls to test
276 httpHdrMangle(HttpHeaderEntry
* e
, HttpRequest
* request
, HeaderManglers
*hms
, const AccessLogEntryPointer
&al
)
282 const headerMangler
*hm
= hms
->find(*e
);
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");
290 ACLFilledChecklist
checklist(hm
->access_list
, request
, NULL
);
293 if (al
&& al
->reply
) {
294 checklist
.reply
= al
->reply
.getRaw();
295 HTTPMSGLOCK(checklist
.reply
);
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");
306 } else if (NULL
== 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.
312 /* It was denied, but we have a replacement. Replace the
313 * header on the fly, and return that the new header
316 debugs(66, 7, "checklist denied but we have replacement. Replace");
317 e
->value
= hm
->replacement
;
324 /** Mangles headers for a list of headers. */
326 httpHdrMangleList(HttpHeader
*l
, HttpRequest
*request
, const AccessLogEntryPointer
&al
, req_or_rep_t req_or_rep
)
329 HttpHeaderPos p
= HttpHeaderInitPos
;
331 /* check with anonymizer tables */
332 HeaderManglers
*hms
= nullptr;
333 HeaderWithAclList
*headersAdd
= nullptr;
335 switch (req_or_rep
) {
337 hms
= Config
.request_header_access
;
338 headersAdd
= Config
.request_header_add
;
341 hms
= Config
.reply_header_access
;
342 headersAdd
= Config
.reply_header_add
;
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
);
357 if (headersAdd
&& !headersAdd
->empty()) {
358 httpHdrAdd(l
, request
, al
, *headersAdd
);
363 void header_mangler_clean(headerMangler
&m
)
365 aclDestroyAccessList(&m
.access_list
);
366 safe_free(m
.replacement
);
370 void header_mangler_dump_access(StoreEntry
* entry
, const char *option
,
371 const headerMangler
&m
, const char *name
)
373 if (m
.access_list
!= NULL
) {
374 storeAppendPrintf(entry
, "%s ", option
);
375 dump_acl_access(entry
, name
, m
.access_list
);
380 void header_mangler_dump_replacement(StoreEntry
* entry
, const char *option
,
381 const headerMangler
&m
, const char *name
)
384 storeAppendPrintf(entry
, "%s %s %s\n", option
, name
, m
.replacement
);
387 HeaderManglers::HeaderManglers()
389 memset(known
, 0, sizeof(known
));
390 memset(&all
, 0, sizeof(all
));
393 HeaderManglers::~HeaderManglers()
395 for (auto i
: WholeEnum
<Http::HdrType
>())
396 header_mangler_clean(known
[i
]);
398 for (auto i
: custom
)
399 header_mangler_clean(i
.second
);
401 header_mangler_clean(all
);
405 HeaderManglers::dumpAccess(StoreEntry
* entry
, const char *name
) const
407 for (auto id
: WholeEnum
<Http::HdrType
>())
408 header_mangler_dump_access(entry
, name
, known
[id
], Http::HeaderLookupTable
.lookup(id
).name
);
410 for (auto i
: custom
)
411 header_mangler_dump_access(entry
, name
, i
.second
, i
.first
.c_str());
413 header_mangler_dump_access(entry
, name
, all
, "All");
417 HeaderManglers::dumpReplacement(StoreEntry
* entry
, const char *name
) const
419 for (auto id
: WholeEnum
<Http::HdrType
>()) {
420 header_mangler_dump_replacement(entry
, name
, known
[id
], Http::HeaderLookupTable
.lookup(id
).name
);
423 for (auto i
: custom
) {
424 header_mangler_dump_replacement(entry
, name
, i
.second
, i
.first
.c_str());
427 header_mangler_dump_replacement(entry
, name
, all
, "All");
431 HeaderManglers::track(const char *name
)
433 if (strcmp(name
, "All") == 0)
436 const Http::HdrType id
= Http::HeaderLookupTable
.lookup(SBuf(name
)).id
;
438 if (id
!= Http::HdrType::BAD_HDR
)
441 if (strcmp(name
, "Other") == 0)
442 return &known
[Http::HdrType::OTHER
];
444 return &custom
[name
];
448 HeaderManglers::setReplacement(const char *name
, const char *value
)
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
);
454 safe_free(m
->replacement
); // overwrite old value if any
455 m
->replacement
= xstrdup(value
);
458 const headerMangler
*
459 HeaderManglers::find(const HttpHeaderEntry
&e
) const
461 // a known header with a configured ACL list
462 if (e
.id
!= Http::HdrType::OTHER
&& Http::any_HdrType_enum_value(e
.id
) &&
463 known
[e
.id
].access_list
)
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())
476 // Next-to-last resort: "Other" rules match any custom header
477 if (e
.id
== Http::HdrType::OTHER
&& known
[Http::HdrType::OTHER
].access_list
)
478 return &known
[Http::HdrType::OTHER
];
480 // Last resort: "All" rules match any header
488 httpHdrAdd(HttpHeader
*heads
, HttpRequest
*request
, const AccessLogEntryPointer
&al
, HeaderWithAclList
&headersAdd
)
490 ACLFilledChecklist
checklist(NULL
, request
, NULL
);
493 if (al
&& al
->reply
) {
494 checklist
.reply
= al
->reply
.getRaw();
495 HTTPMSGLOCK(checklist
.reply
);
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
= NULL
;
505 hwa
->valueFormat
->assemble(mb
, al
, 0);
506 fieldValue
= mb
.content();
509 fieldValue
= hwa
->fieldValue
.c_str();
512 if (!fieldValue
|| fieldValue
[0] == '\0')
515 HttpHeaderEntry
*e
= new HttpHeaderEntry(hwa
->fieldId
, SBuf(hwa
->fieldName
), fieldValue
);