2 * DEBUG: section 66 HTTP Header Tools
3 * AUTHOR: Alex Rousskov
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 #include "acl/FilledChecklist.h"
35 #include "acl/Gadgets.h"
36 #include "client_side.h"
37 #include "client_side_request.h"
38 #include "comm/Connection.h"
39 #include "compat/strtoll.h"
40 #include "ConfigParser.h"
43 #include "HttpHdrContRange.h"
44 #include "HttpHeader.h"
45 #include "HttpHeaderFieldInfo.h"
46 #include "HttpHeaderTools.h"
47 #include "HttpRequest.h"
49 #include "SquidConfig.h"
54 #include "ssl/support.h"
61 static void httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
);
64 httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs
* attrs
, int count
)
67 HttpHeaderFieldInfo
*table
= NULL
;
68 assert(attrs
&& count
);
71 table
= new HttpHeaderFieldInfo
[count
];
73 for (i
= 0; i
< count
; ++i
) {
74 const http_hdr_type id
= attrs
[i
].id
;
75 HttpHeaderFieldInfo
*info
= table
+ id
;
77 assert(id
>= 0 && id
< count
);
78 assert(attrs
[i
].name
);
79 assert(info
->id
== HDR_ACCEPT
&& info
->type
== ftInvalid
); /* was not set before */
80 /* copy and init fields */
82 info
->type
= attrs
[i
].type
;
83 info
->name
= attrs
[i
].name
;
84 assert(info
->name
.size());
91 httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo
* table
, int count
)
95 for (i
= 0; i
< count
; ++i
)
96 table
[i
].name
.clean();
102 httpHeaderMaskInit(HttpHeaderMask
* mask
, int value
)
104 memset(mask
, value
, sizeof(*mask
));
107 /** calculates a bit mask of a given array; does not reset mask! */
109 httpHeaderCalcMask(HttpHeaderMask
* mask
, http_hdr_type http_hdr_type_enums
[], size_t count
)
112 const int * enums
= (const int *) http_hdr_type_enums
;
113 assert(mask
&& enums
);
114 assert(count
< sizeof(*mask
) * 8); /* check for overflow */
116 for (i
= 0; i
< count
; ++i
) {
117 assert(!CBIT_TEST(*mask
, enums
[i
])); /* check for duplicates */
118 CBIT_SET(*mask
, enums
[i
]);
122 /* same as httpHeaderPutStr, but formats the string using snprintf first */
124 httpHeaderPutStrf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
,...)
129 httpHeaderPutStrvf(hdr
, id
, fmt
, args
);
133 /* used by httpHeaderPutStrf */
135 httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
)
139 mb
.vPrintf(fmt
, vargs
);
140 hdr
->putStr(id
, mb
.buf
);
144 /** wrapper arrounf PutContRange */
146 httpHeaderAddContRange(HttpHeader
* hdr
, HttpHdrRangeSpec spec
, int64_t ent_len
)
148 HttpHdrContRange
*cr
= httpHdrContRangeCreate();
149 assert(hdr
&& ent_len
>= 0);
150 httpHdrContRangeSet(cr
, spec
, ent_len
);
151 hdr
->putContRange(cr
);
152 httpHdrContRangeDestroy(cr
);
156 * return true if a given directive is found in at least one of
157 * the "connection" header-fields note: if HDR_PROXY_CONNECTION is
158 * present we ignore HDR_CONNECTION.
161 httpHeaderHasConnDir(const HttpHeader
* hdr
, const char *directive
)
165 /* what type of header do we have? */
167 #if USE_HTTP_VIOLATIONS
168 if (hdr
->has(HDR_PROXY_CONNECTION
))
169 list
= hdr
->getList(HDR_PROXY_CONNECTION
);
172 if (hdr
->has(HDR_CONNECTION
))
173 list
= hdr
->getList(HDR_CONNECTION
);
177 res
= strListIsMember(&list
, directive
, ',');
184 /** handy to printf prefixes of potentially very long buffers */
186 getStringPrefix(const char *str
, const char *end
)
188 #define SHORT_PREFIX_SIZE 512
189 LOCAL_ARRAY(char, buf
, SHORT_PREFIX_SIZE
);
190 const int sz
= 1 + (end
? end
- str
: strlen(str
));
191 xstrncpy(buf
, str
, (sz
> SHORT_PREFIX_SIZE
) ? SHORT_PREFIX_SIZE
: sz
);
196 * parses an int field, complains if soemthing went wrong, returns true on
200 httpHeaderParseInt(const char *start
, int *value
)
203 *value
= atoi(start
);
205 if (!*value
&& !xisdigit(*start
)) {
206 debugs(66, 2, "failed to parse an int header field near '" << start
<< "'");
214 httpHeaderParseOffset(const char *start
, int64_t * value
)
217 int64_t res
= strtoll(start
, NULL
, 10);
218 if (!res
&& EINVAL
== errno
) /* maybe not portable? */
225 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
226 * something went wrong, returns non-zero on success.
227 * Un-escapes quoted-pair characters found within the string.
228 * start should point at the first double-quote.
231 httpHeaderParseQuotedString(const char *start
, const int len
, String
*val
)
233 const char *end
, *pos
;
236 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
241 while (*pos
!= '"' && len
> (pos
-start
)) {
245 if ((pos
-start
) > len
|| *pos
!= '\n') {
246 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with '\\r' octet " << (start
-pos
)
247 << " bytes into '" << start
<< "'");
255 if ( (pos
-start
) > len
|| (*pos
!= ' ' && *pos
!= '\t')) {
256 debugs(66, 2, HERE
<< "failed to parse multiline quoted-string header field '" << start
<< "'");
260 // TODO: replace the entire LWS with a space
263 debugs(66, 2, HERE
<< "len < pos-start => " << len
<< " < " << (pos
-start
));
267 bool quoted
= (*pos
== '\\');
270 if (!*pos
|| (pos
-start
) > len
) {
271 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
277 while (end
< (start
+len
) && *end
!= '\\' && *end
!= '\"' && (unsigned char)*end
> 0x1F && *end
!= 0x7F)
279 if (((unsigned char)*end
<= 0x1F && *end
!= '\r' && *end
!= '\n') || *end
== 0x7F) {
280 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with CTL octet " << (start
-pos
)
281 << " bytes into '" << start
<< "'");
285 val
->append(pos
, end
-pos
);
290 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field which did not end with \" ");
294 /* Make sure it's defined even if empty "" */
295 if (!val
->termedBuf())
296 val
->limitInit("", 0);
301 httpHeaderQuoteString(const char *raw
)
305 // TODO: Optimize by appending a sequence of characters instead of a char.
306 // This optimization may be easier with Tokenizer after raw becomes SBuf.
308 // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
309 // quoted-string except where necessary" (i.e., DQUOTE and backslash)
310 bool needInnerQuote
= false;
311 for (const char *s
= raw
; !needInnerQuote
&& *s
; ++s
)
312 needInnerQuote
= *s
== '"' || *s
== '\\';
315 quotedStr
.append('"');
317 if (needInnerQuote
) {
318 for (const char *s
= raw
; *s
; ++s
) {
319 if (*s
== '"' || *s
== '\\')
320 quotedStr
.append('\\');
321 quotedStr
.append(*s
);
324 quotedStr
.append(raw
);
327 quotedStr
.append('"');
332 * Checks the anonymizer (header_access) configuration.
334 * \retval 0 Header is explicitly blocked for removal
335 * \retval 1 Header is explicitly allowed
336 * \retval 1 Header has been replaced, the current version can be used.
337 * \retval 1 Header has no access controls to test
340 httpHdrMangle(HttpHeaderEntry
* e
, HttpRequest
* request
, int req_or_rep
)
344 /* check with anonymizer tables */
345 HeaderManglers
*hms
= NULL
;
348 if (ROR_REQUEST
== req_or_rep
) {
349 hms
= Config
.request_header_access
;
350 } else if (ROR_REPLY
== req_or_rep
) {
351 hms
= Config
.reply_header_access
;
353 /* error. But let's call it "request". */
354 hms
= Config
.request_header_access
;
357 /* manglers are not configured for this message kind */
361 const headerMangler
*hm
= hms
->find(*e
);
363 /* mangler or checklist went away. default allow */
364 if (!hm
|| !hm
->access_list
) {
368 ACLFilledChecklist
checklist(hm
->access_list
, request
, NULL
);
370 if (checklist
.fastCheck() == ACCESS_ALLOWED
) {
371 /* aclCheckFast returns true for allow. */
373 } else if (NULL
== hm
->replacement
) {
374 /* It was denied, and we don't have any replacement */
377 /* It was denied, but we have a replacement. Replace the
378 * header on the fly, and return that the new header
381 e
->value
= hm
->replacement
;
388 /** Mangles headers for a list of headers. */
390 httpHdrMangleList(HttpHeader
* l
, HttpRequest
* request
, int req_or_rep
)
393 HttpHeaderPos p
= HttpHeaderInitPos
;
395 int headers_deleted
= 0;
396 while ((e
= l
->getEntry(&p
)))
397 if (0 == httpHdrMangle(e
, request
, req_or_rep
))
398 l
->delAt(p
, headers_deleted
);
405 void header_mangler_clean(headerMangler
&m
)
407 aclDestroyAccessList(&m
.access_list
);
408 safe_free(m
.replacement
);
412 void header_mangler_dump_access(StoreEntry
* entry
, const char *option
,
413 const headerMangler
&m
, const char *name
)
415 if (m
.access_list
!= NULL
) {
416 storeAppendPrintf(entry
, "%s ", option
);
417 dump_acl_access(entry
, name
, m
.access_list
);
422 void header_mangler_dump_replacement(StoreEntry
* entry
, const char *option
,
423 const headerMangler
&m
, const char *name
)
426 storeAppendPrintf(entry
, "%s %s %s\n", option
, name
, m
.replacement
);
429 HeaderManglers::HeaderManglers()
431 memset(known
, 0, sizeof(known
));
432 memset(&all
, 0, sizeof(all
));
435 HeaderManglers::~HeaderManglers()
437 for (int i
= 0; i
< HDR_ENUM_END
; ++i
)
438 header_mangler_clean(known
[i
]);
440 typedef ManglersByName::iterator MBNI
;
441 for (MBNI i
= custom
.begin(); i
!= custom
.end(); ++i
)
442 header_mangler_clean(i
->second
);
444 header_mangler_clean(all
);
448 HeaderManglers::dumpAccess(StoreEntry
* entry
, const char *name
) const
450 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
451 header_mangler_dump_access(entry
, name
, known
[i
],
452 httpHeaderNameById(i
));
455 typedef ManglersByName::const_iterator MBNCI
;
456 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
)
457 header_mangler_dump_access(entry
, name
, i
->second
, i
->first
.c_str());
459 header_mangler_dump_access(entry
, name
, all
, "All");
463 HeaderManglers::dumpReplacement(StoreEntry
* entry
, const char *name
) const
465 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
466 header_mangler_dump_replacement(entry
, name
, known
[i
],
467 httpHeaderNameById(i
));
470 typedef ManglersByName::const_iterator MBNCI
;
471 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
) {
472 header_mangler_dump_replacement(entry
, name
, i
->second
,
476 header_mangler_dump_replacement(entry
, name
, all
, "All");
480 HeaderManglers::track(const char *name
)
482 int id
= httpHeaderIdByNameDef(name
, strlen(name
));
484 if (id
== HDR_BAD_HDR
) { // special keyword or a custom header
485 if (strcmp(name
, "All") == 0)
487 else if (strcmp(name
, "Other") == 0)
491 headerMangler
*m
= NULL
;
492 if (id
== HDR_ENUM_END
) {
494 } else if (id
== HDR_BAD_HDR
) {
497 m
= &known
[id
]; // including HDR_OTHER
505 HeaderManglers::setReplacement(const char *name
, const char *value
)
507 // for backword compatibility, we allow replacements to be configured
508 // for headers w/o access rules, but such replacements are ignored
509 headerMangler
*m
= track(name
);
511 safe_free(m
->replacement
); // overwrite old value if any
512 m
->replacement
= xstrdup(value
);
515 const headerMangler
*
516 HeaderManglers::find(const HttpHeaderEntry
&e
) const
518 // a known header with a configured ACL list
519 if (e
.id
!= HDR_OTHER
&& 0 <= e
.id
&& e
.id
< HDR_ENUM_END
&&
520 known
[e
.id
].access_list
)
524 if (e
.id
== HDR_OTHER
) {
525 // does it have an ACL list configured?
526 // Optimize: use a name type that we do not need to convert to here
527 const ManglersByName::const_iterator i
= custom
.find(e
.name
.termedBuf());
528 if (i
!= custom
.end())
532 // Next-to-last resort: "Other" rules match any custom header
533 if (e
.id
== HDR_OTHER
&& known
[HDR_OTHER
].access_list
)
534 return &known
[HDR_OTHER
];
536 // Last resort: "All" rules match any header
544 httpHdrAdd(HttpHeader
*heads
, HttpRequest
*request
, const AccessLogEntryPointer
&al
, HeaderWithAclList
&headersAdd
)
546 ACLFilledChecklist
checklist(NULL
, request
, NULL
);
548 for (HeaderWithAclList::const_iterator hwa
= headersAdd
.begin(); hwa
!= headersAdd
.end(); ++hwa
) {
549 if (!hwa
->aclList
|| checklist
.fastCheck(hwa
->aclList
) == ACCESS_ALLOWED
) {
550 const char *fieldValue
= NULL
;
555 hwa
->valueFormat
->assemble(mb
, al
, 0);
556 fieldValue
= mb
.content();
559 fieldValue
= hwa
->fieldValue
.c_str();
562 if (!fieldValue
|| fieldValue
[0] == '\0')
565 HttpHeaderEntry
*e
= new HttpHeaderEntry(hwa
->fieldId
, hwa
->fieldName
.c_str(),