2 * Copyright (C) 1996-2014 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 "client_side.h"
15 #include "client_side_request.h"
16 #include "comm/Connection.h"
17 #include "compat/strtoll.h"
18 #include "ConfigParser.h"
21 #include "HttpHdrContRange.h"
22 #include "HttpHeader.h"
23 #include "HttpHeaderFieldInfo.h"
24 #include "HttpHeaderTools.h"
25 #include "HttpRequest.h"
27 #include "SquidConfig.h"
32 #include "ssl/support.h"
39 static void httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
);
42 httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs
* attrs
, int count
)
45 HttpHeaderFieldInfo
*table
= NULL
;
46 assert(attrs
&& count
);
49 table
= new HttpHeaderFieldInfo
[count
];
51 for (i
= 0; i
< count
; ++i
) {
52 const http_hdr_type id
= attrs
[i
].id
;
53 HttpHeaderFieldInfo
*info
= table
+ id
;
55 assert(id
>= 0 && id
< count
);
56 assert(attrs
[i
].name
);
57 assert(info
->id
== HDR_ACCEPT
&& info
->type
== ftInvalid
); /* was not set before */
58 /* copy and init fields */
60 info
->type
= attrs
[i
].type
;
61 info
->name
= attrs
[i
].name
;
62 assert(info
->name
.size());
69 httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo
* table
, int count
)
73 for (i
= 0; i
< count
; ++i
)
74 table
[i
].name
.clean();
80 httpHeaderMaskInit(HttpHeaderMask
* mask
, int value
)
82 memset(mask
, value
, sizeof(*mask
));
85 /** calculates a bit mask of a given array; does not reset mask! */
87 httpHeaderCalcMask(HttpHeaderMask
* mask
, http_hdr_type http_hdr_type_enums
[], size_t count
)
90 const int * enums
= (const int *) http_hdr_type_enums
;
91 assert(mask
&& enums
);
92 assert(count
< sizeof(*mask
) * 8); /* check for overflow */
94 for (i
= 0; i
< count
; ++i
) {
95 assert(!CBIT_TEST(*mask
, enums
[i
])); /* check for duplicates */
96 CBIT_SET(*mask
, enums
[i
]);
100 /* same as httpHeaderPutStr, but formats the string using snprintf first */
102 httpHeaderPutStrf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
,...)
107 httpHeaderPutStrvf(hdr
, id
, fmt
, args
);
111 /* used by httpHeaderPutStrf */
113 httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
)
117 mb
.vPrintf(fmt
, vargs
);
118 hdr
->putStr(id
, mb
.buf
);
122 /** wrapper arrounf PutContRange */
124 httpHeaderAddContRange(HttpHeader
* hdr
, HttpHdrRangeSpec spec
, int64_t ent_len
)
126 HttpHdrContRange
*cr
= httpHdrContRangeCreate();
127 assert(hdr
&& ent_len
>= 0);
128 httpHdrContRangeSet(cr
, spec
, ent_len
);
129 hdr
->putContRange(cr
);
130 httpHdrContRangeDestroy(cr
);
134 * return true if a given directive is found in at least one of
135 * the "connection" header-fields note: if HDR_PROXY_CONNECTION is
136 * present we ignore HDR_CONNECTION.
139 httpHeaderHasConnDir(const HttpHeader
* hdr
, const char *directive
)
143 /* what type of header do we have? */
145 #if USE_HTTP_VIOLATIONS
146 if (hdr
->has(HDR_PROXY_CONNECTION
))
147 list
= hdr
->getList(HDR_PROXY_CONNECTION
);
150 if (hdr
->has(HDR_CONNECTION
))
151 list
= hdr
->getList(HDR_CONNECTION
);
155 res
= strListIsMember(&list
, directive
, ',');
162 /** handy to printf prefixes of potentially very long buffers */
164 getStringPrefix(const char *str
, const char *end
)
166 #define SHORT_PREFIX_SIZE 512
167 LOCAL_ARRAY(char, buf
, SHORT_PREFIX_SIZE
);
168 const int sz
= 1 + (end
? end
- str
: strlen(str
));
169 xstrncpy(buf
, str
, (sz
> SHORT_PREFIX_SIZE
) ? SHORT_PREFIX_SIZE
: sz
);
174 * parses an int field, complains if soemthing went wrong, returns true on
178 httpHeaderParseInt(const char *start
, int *value
)
181 *value
= atoi(start
);
183 if (!*value
&& !xisdigit(*start
)) {
184 debugs(66, 2, "failed to parse an int header field near '" << start
<< "'");
192 httpHeaderParseOffset(const char *start
, int64_t * value
)
195 int64_t res
= strtoll(start
, NULL
, 10);
196 if (!res
&& EINVAL
== errno
) /* maybe not portable? */
203 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
204 * something went wrong, returns non-zero on success.
205 * Un-escapes quoted-pair characters found within the string.
206 * start should point at the first double-quote.
209 httpHeaderParseQuotedString(const char *start
, const int len
, String
*val
)
211 const char *end
, *pos
;
214 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
219 while (*pos
!= '"' && len
> (pos
-start
)) {
223 if ((pos
-start
) > len
|| *pos
!= '\n') {
224 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with '\\r' octet " << (start
-pos
)
225 << " bytes into '" << start
<< "'");
233 if ( (pos
-start
) > len
|| (*pos
!= ' ' && *pos
!= '\t')) {
234 debugs(66, 2, HERE
<< "failed to parse multiline quoted-string header field '" << start
<< "'");
238 // TODO: replace the entire LWS with a space
241 debugs(66, 2, HERE
<< "len < pos-start => " << len
<< " < " << (pos
-start
));
245 bool quoted
= (*pos
== '\\');
248 if (!*pos
|| (pos
-start
) > len
) {
249 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
255 while (end
< (start
+len
) && *end
!= '\\' && *end
!= '\"' && (unsigned char)*end
> 0x1F && *end
!= 0x7F)
257 if (((unsigned char)*end
<= 0x1F && *end
!= '\r' && *end
!= '\n') || *end
== 0x7F) {
258 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with CTL octet " << (start
-pos
)
259 << " bytes into '" << start
<< "'");
263 val
->append(pos
, end
-pos
);
268 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field which did not end with \" ");
272 /* Make sure it's defined even if empty "" */
273 if (!val
->termedBuf())
274 val
->limitInit("", 0);
279 httpHeaderQuoteString(const char *raw
)
283 // TODO: Optimize by appending a sequence of characters instead of a char.
284 // This optimization may be easier with Tokenizer after raw becomes SBuf.
286 // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
287 // quoted-string except where necessary" (i.e., DQUOTE and backslash)
288 bool needInnerQuote
= false;
289 for (const char *s
= raw
; !needInnerQuote
&& *s
; ++s
)
290 needInnerQuote
= *s
== '"' || *s
== '\\';
293 quotedStr
.append('"');
295 if (needInnerQuote
) {
296 for (const char *s
= raw
; *s
; ++s
) {
297 if (*s
== '"' || *s
== '\\')
298 quotedStr
.append('\\');
299 quotedStr
.append(*s
);
302 quotedStr
.append(raw
);
305 quotedStr
.append('"');
310 * Checks the anonymizer (header_access) configuration.
312 * \retval 0 Header is explicitly blocked for removal
313 * \retval 1 Header is explicitly allowed
314 * \retval 1 Header has been replaced, the current version can be used.
315 * \retval 1 Header has no access controls to test
318 httpHdrMangle(HttpHeaderEntry
* e
, HttpRequest
* request
, int req_or_rep
)
322 /* check with anonymizer tables */
323 HeaderManglers
*hms
= NULL
;
326 if (ROR_REQUEST
== req_or_rep
) {
327 hms
= Config
.request_header_access
;
328 } else if (ROR_REPLY
== req_or_rep
) {
329 hms
= Config
.reply_header_access
;
331 /* error. But let's call it "request". */
332 hms
= Config
.request_header_access
;
335 /* manglers are not configured for this message kind */
339 const headerMangler
*hm
= hms
->find(*e
);
341 /* mangler or checklist went away. default allow */
342 if (!hm
|| !hm
->access_list
) {
346 ACLFilledChecklist
checklist(hm
->access_list
, request
, NULL
);
348 if (checklist
.fastCheck() == ACCESS_ALLOWED
) {
349 /* aclCheckFast returns true for allow. */
351 } else if (NULL
== hm
->replacement
) {
352 /* It was denied, and we don't have any replacement */
355 /* It was denied, but we have a replacement. Replace the
356 * header on the fly, and return that the new header
359 e
->value
= hm
->replacement
;
366 /** Mangles headers for a list of headers. */
368 httpHdrMangleList(HttpHeader
* l
, HttpRequest
* request
, int req_or_rep
)
371 HttpHeaderPos p
= HttpHeaderInitPos
;
373 int headers_deleted
= 0;
374 while ((e
= l
->getEntry(&p
)))
375 if (0 == httpHdrMangle(e
, request
, req_or_rep
))
376 l
->delAt(p
, headers_deleted
);
383 void header_mangler_clean(headerMangler
&m
)
385 aclDestroyAccessList(&m
.access_list
);
386 safe_free(m
.replacement
);
390 void header_mangler_dump_access(StoreEntry
* entry
, const char *option
,
391 const headerMangler
&m
, const char *name
)
393 if (m
.access_list
!= NULL
) {
394 storeAppendPrintf(entry
, "%s ", option
);
395 dump_acl_access(entry
, name
, m
.access_list
);
400 void header_mangler_dump_replacement(StoreEntry
* entry
, const char *option
,
401 const headerMangler
&m
, const char *name
)
404 storeAppendPrintf(entry
, "%s %s %s\n", option
, name
, m
.replacement
);
407 HeaderManglers::HeaderManglers()
409 memset(known
, 0, sizeof(known
));
410 memset(&all
, 0, sizeof(all
));
413 HeaderManglers::~HeaderManglers()
415 for (int i
= 0; i
< HDR_ENUM_END
; ++i
)
416 header_mangler_clean(known
[i
]);
418 typedef ManglersByName::iterator MBNI
;
419 for (MBNI i
= custom
.begin(); i
!= custom
.end(); ++i
)
420 header_mangler_clean(i
->second
);
422 header_mangler_clean(all
);
426 HeaderManglers::dumpAccess(StoreEntry
* entry
, const char *name
) const
428 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
429 header_mangler_dump_access(entry
, name
, known
[i
],
430 httpHeaderNameById(i
));
433 typedef ManglersByName::const_iterator MBNCI
;
434 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
)
435 header_mangler_dump_access(entry
, name
, i
->second
, i
->first
.c_str());
437 header_mangler_dump_access(entry
, name
, all
, "All");
441 HeaderManglers::dumpReplacement(StoreEntry
* entry
, const char *name
) const
443 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
444 header_mangler_dump_replacement(entry
, name
, known
[i
],
445 httpHeaderNameById(i
));
448 typedef ManglersByName::const_iterator MBNCI
;
449 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
) {
450 header_mangler_dump_replacement(entry
, name
, i
->second
,
454 header_mangler_dump_replacement(entry
, name
, all
, "All");
458 HeaderManglers::track(const char *name
)
460 int id
= httpHeaderIdByNameDef(name
, strlen(name
));
462 if (id
== HDR_BAD_HDR
) { // special keyword or a custom header
463 if (strcmp(name
, "All") == 0)
465 else if (strcmp(name
, "Other") == 0)
469 headerMangler
*m
= NULL
;
470 if (id
== HDR_ENUM_END
) {
472 } else if (id
== HDR_BAD_HDR
) {
475 m
= &known
[id
]; // including HDR_OTHER
483 HeaderManglers::setReplacement(const char *name
, const char *value
)
485 // for backword compatibility, we allow replacements to be configured
486 // for headers w/o access rules, but such replacements are ignored
487 headerMangler
*m
= track(name
);
489 safe_free(m
->replacement
); // overwrite old value if any
490 m
->replacement
= xstrdup(value
);
493 const headerMangler
*
494 HeaderManglers::find(const HttpHeaderEntry
&e
) const
496 // a known header with a configured ACL list
497 if (e
.id
!= HDR_OTHER
&& 0 <= e
.id
&& e
.id
< HDR_ENUM_END
&&
498 known
[e
.id
].access_list
)
502 if (e
.id
== HDR_OTHER
) {
503 // does it have an ACL list configured?
504 // Optimize: use a name type that we do not need to convert to here
505 const ManglersByName::const_iterator i
= custom
.find(e
.name
.termedBuf());
506 if (i
!= custom
.end())
510 // Next-to-last resort: "Other" rules match any custom header
511 if (e
.id
== HDR_OTHER
&& known
[HDR_OTHER
].access_list
)
512 return &known
[HDR_OTHER
];
514 // Last resort: "All" rules match any header
522 httpHdrAdd(HttpHeader
*heads
, HttpRequest
*request
, const AccessLogEntryPointer
&al
, HeaderWithAclList
&headersAdd
)
524 ACLFilledChecklist
checklist(NULL
, request
, NULL
);
526 for (HeaderWithAclList::const_iterator hwa
= headersAdd
.begin(); hwa
!= headersAdd
.end(); ++hwa
) {
527 if (!hwa
->aclList
|| checklist
.fastCheck(hwa
->aclList
) == ACCESS_ALLOWED
) {
528 const char *fieldValue
= NULL
;
533 hwa
->valueFormat
->assemble(mb
, al
, 0);
534 fieldValue
= mb
.content();
537 fieldValue
= hwa
->fieldValue
.c_str();
540 if (!fieldValue
|| fieldValue
[0] == '\0')
543 HttpHeaderEntry
*e
= new HttpHeaderEntry(hwa
->fieldId
, hwa
->fieldName
.c_str(),