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"
63 static void httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
);
66 httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs
* attrs
, int count
)
69 HttpHeaderFieldInfo
*table
= NULL
;
70 assert(attrs
&& count
);
73 table
= new HttpHeaderFieldInfo
[count
];
75 for (i
= 0; i
< count
; ++i
) {
76 const http_hdr_type id
= attrs
[i
].id
;
77 HttpHeaderFieldInfo
*info
= table
+ id
;
79 assert(id
>= 0 && id
< count
);
80 assert(attrs
[i
].name
);
81 assert(info
->id
== HDR_ACCEPT
&& info
->type
== ftInvalid
); /* was not set before */
82 /* copy and init fields */
84 info
->type
= attrs
[i
].type
;
85 info
->name
= attrs
[i
].name
;
86 assert(info
->name
.size());
93 httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo
* table
, int count
)
97 for (i
= 0; i
< count
; ++i
)
98 table
[i
].name
.clean();
104 httpHeaderMaskInit(HttpHeaderMask
* mask
, int value
)
106 memset(mask
, value
, sizeof(*mask
));
109 /** calculates a bit mask of a given array; does not reset mask! */
111 httpHeaderCalcMask(HttpHeaderMask
* mask
, http_hdr_type http_hdr_type_enums
[], size_t count
)
114 const int * enums
= (const int *) http_hdr_type_enums
;
115 assert(mask
&& enums
);
116 assert(count
< sizeof(*mask
) * 8); /* check for overflow */
118 for (i
= 0; i
< count
; ++i
) {
119 assert(!CBIT_TEST(*mask
, enums
[i
])); /* check for duplicates */
120 CBIT_SET(*mask
, enums
[i
]);
124 /* same as httpHeaderPutStr, but formats the string using snprintf first */
126 httpHeaderPutStrf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
,...)
131 httpHeaderPutStrvf(hdr
, id
, fmt
, args
);
135 /* used by httpHeaderPutStrf */
137 httpHeaderPutStrvf(HttpHeader
* hdr
, http_hdr_type id
, const char *fmt
, va_list vargs
)
141 mb
.vPrintf(fmt
, vargs
);
142 hdr
->putStr(id
, mb
.buf
);
146 /** wrapper arrounf PutContRange */
148 httpHeaderAddContRange(HttpHeader
* hdr
, HttpHdrRangeSpec spec
, int64_t ent_len
)
150 HttpHdrContRange
*cr
= httpHdrContRangeCreate();
151 assert(hdr
&& ent_len
>= 0);
152 httpHdrContRangeSet(cr
, spec
, ent_len
);
153 hdr
->putContRange(cr
);
154 httpHdrContRangeDestroy(cr
);
158 * return true if a given directive is found in at least one of
159 * the "connection" header-fields note: if HDR_PROXY_CONNECTION is
160 * present we ignore HDR_CONNECTION.
163 httpHeaderHasConnDir(const HttpHeader
* hdr
, const char *directive
)
167 /* what type of header do we have? */
169 #if USE_HTTP_VIOLATIONS
170 if (hdr
->has(HDR_PROXY_CONNECTION
))
171 list
= hdr
->getList(HDR_PROXY_CONNECTION
);
174 if (hdr
->has(HDR_CONNECTION
))
175 list
= hdr
->getList(HDR_CONNECTION
);
179 res
= strListIsMember(&list
, directive
, ',');
186 /** handy to printf prefixes of potentially very long buffers */
188 getStringPrefix(const char *str
, const char *end
)
190 #define SHORT_PREFIX_SIZE 512
191 LOCAL_ARRAY(char, buf
, SHORT_PREFIX_SIZE
);
192 const int sz
= 1 + (end
? end
- str
: strlen(str
));
193 xstrncpy(buf
, str
, (sz
> SHORT_PREFIX_SIZE
) ? SHORT_PREFIX_SIZE
: sz
);
198 * parses an int field, complains if soemthing went wrong, returns true on
202 httpHeaderParseInt(const char *start
, int *value
)
205 *value
= atoi(start
);
207 if (!*value
&& !xisdigit(*start
)) {
208 debugs(66, 2, "failed to parse an int header field near '" << start
<< "'");
216 httpHeaderParseOffset(const char *start
, int64_t * value
)
219 int64_t res
= strtoll(start
, NULL
, 10);
220 if (!res
&& EINVAL
== errno
) /* maybe not portable? */
227 * Parses a quoted-string field (RFC 2616 section 2.2), complains if
228 * something went wrong, returns non-zero on success.
229 * Un-escapes quoted-pair characters found within the string.
230 * start should point at the first double-quote.
233 httpHeaderParseQuotedString(const char *start
, const int len
, String
*val
)
235 const char *end
, *pos
;
238 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
243 while (*pos
!= '"' && len
> (pos
-start
)) {
247 if ((pos
-start
) > len
|| *pos
!= '\n') {
248 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with '\\r' octet " << (start
-pos
)
249 << " bytes into '" << start
<< "'");
257 if ( (pos
-start
) > len
|| (*pos
!= ' ' && *pos
!= '\t')) {
258 debugs(66, 2, HERE
<< "failed to parse multiline quoted-string header field '" << start
<< "'");
262 // TODO: replace the entire LWS with a space
265 debugs(66, 2, HERE
<< "len < pos-start => " << len
<< " < " << (pos
-start
));
269 bool quoted
= (*pos
== '\\');
272 if (!*pos
|| (pos
-start
) > len
) {
273 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field near '" << start
<< "'");
279 while (end
< (start
+len
) && *end
!= '\\' && *end
!= '\"' && (unsigned char)*end
> 0x1F && *end
!= 0x7F)
281 if (((unsigned char)*end
<= 0x1F && *end
!= '\r' && *end
!= '\n') || *end
== 0x7F) {
282 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field with CTL octet " << (start
-pos
)
283 << " bytes into '" << start
<< "'");
287 val
->append(pos
, end
-pos
);
292 debugs(66, 2, HERE
<< "failed to parse a quoted-string header field which did not end with \" ");
296 /* Make sure it's defined even if empty "" */
297 if (!val
->termedBuf())
298 val
->limitInit("", 0);
303 * Checks the anonymizer (header_access) configuration.
305 * \retval 0 Header is explicitly blocked for removal
306 * \retval 1 Header is explicitly allowed
307 * \retval 1 Header has been replaced, the current version can be used.
308 * \retval 1 Header has no access controls to test
311 httpHdrMangle(HttpHeaderEntry
* e
, HttpRequest
* request
, int req_or_rep
)
315 /* check with anonymizer tables */
316 HeaderManglers
*hms
= NULL
;
319 if (ROR_REQUEST
== req_or_rep
) {
320 hms
= Config
.request_header_access
;
321 } else if (ROR_REPLY
== req_or_rep
) {
322 hms
= Config
.reply_header_access
;
324 /* error. But let's call it "request". */
325 hms
= Config
.request_header_access
;
328 /* manglers are not configured for this message kind */
332 const headerMangler
*hm
= hms
->find(*e
);
334 /* mangler or checklist went away. default allow */
335 if (!hm
|| !hm
->access_list
) {
339 ACLFilledChecklist
checklist(hm
->access_list
, request
, NULL
);
341 if (checklist
.fastCheck() == ACCESS_ALLOWED
) {
342 /* aclCheckFast returns true for allow. */
344 } else if (NULL
== hm
->replacement
) {
345 /* It was denied, and we don't have any replacement */
348 /* It was denied, but we have a replacement. Replace the
349 * header on the fly, and return that the new header
352 e
->value
= hm
->replacement
;
359 /** Mangles headers for a list of headers. */
361 httpHdrMangleList(HttpHeader
* l
, HttpRequest
* request
, int req_or_rep
)
364 HttpHeaderPos p
= HttpHeaderInitPos
;
366 int headers_deleted
= 0;
367 while ((e
= l
->getEntry(&p
)))
368 if (0 == httpHdrMangle(e
, request
, req_or_rep
))
369 l
->delAt(p
, headers_deleted
);
376 void header_mangler_clean(headerMangler
&m
)
378 aclDestroyAccessList(&m
.access_list
);
379 safe_free(m
.replacement
);
383 void header_mangler_dump_access(StoreEntry
* entry
, const char *option
,
384 const headerMangler
&m
, const char *name
)
386 if (m
.access_list
!= NULL
) {
387 storeAppendPrintf(entry
, "%s ", option
);
388 dump_acl_access(entry
, name
, m
.access_list
);
393 void header_mangler_dump_replacement(StoreEntry
* entry
, const char *option
,
394 const headerMangler
&m
, const char *name
)
397 storeAppendPrintf(entry
, "%s %s %s\n", option
, name
, m
.replacement
);
400 HeaderManglers::HeaderManglers()
402 memset(known
, 0, sizeof(known
));
403 memset(&all
, 0, sizeof(all
));
406 HeaderManglers::~HeaderManglers()
408 for (int i
= 0; i
< HDR_ENUM_END
; ++i
)
409 header_mangler_clean(known
[i
]);
411 typedef ManglersByName::iterator MBNI
;
412 for (MBNI i
= custom
.begin(); i
!= custom
.end(); ++i
)
413 header_mangler_clean(i
->second
);
415 header_mangler_clean(all
);
419 HeaderManglers::dumpAccess(StoreEntry
* entry
, const char *name
) const
421 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
422 header_mangler_dump_access(entry
, name
, known
[i
],
423 httpHeaderNameById(i
));
426 typedef ManglersByName::const_iterator MBNCI
;
427 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
)
428 header_mangler_dump_access(entry
, name
, i
->second
, i
->first
.c_str());
430 header_mangler_dump_access(entry
, name
, all
, "All");
434 HeaderManglers::dumpReplacement(StoreEntry
* entry
, const char *name
) const
436 for (int i
= 0; i
< HDR_ENUM_END
; ++i
) {
437 header_mangler_dump_replacement(entry
, name
, known
[i
],
438 httpHeaderNameById(i
));
441 typedef ManglersByName::const_iterator MBNCI
;
442 for (MBNCI i
= custom
.begin(); i
!= custom
.end(); ++i
) {
443 header_mangler_dump_replacement(entry
, name
, i
->second
,
447 header_mangler_dump_replacement(entry
, name
, all
, "All");
451 HeaderManglers::track(const char *name
)
453 int id
= httpHeaderIdByNameDef(name
, strlen(name
));
455 if (id
== HDR_BAD_HDR
) { // special keyword or a custom header
456 if (strcmp(name
, "All") == 0)
458 else if (strcmp(name
, "Other") == 0)
462 headerMangler
*m
= NULL
;
463 if (id
== HDR_ENUM_END
) {
465 } else if (id
== HDR_BAD_HDR
) {
468 m
= &known
[id
]; // including HDR_OTHER
476 HeaderManglers::setReplacement(const char *name
, const char *value
)
478 // for backword compatibility, we allow replacements to be configured
479 // for headers w/o access rules, but such replacements are ignored
480 headerMangler
*m
= track(name
);
482 safe_free(m
->replacement
); // overwrite old value if any
483 m
->replacement
= xstrdup(value
);
486 const headerMangler
*
487 HeaderManglers::find(const HttpHeaderEntry
&e
) const
489 // a known header with a configured ACL list
490 if (e
.id
!= HDR_OTHER
&& 0 <= e
.id
&& e
.id
< HDR_ENUM_END
&&
491 known
[e
.id
].access_list
)
495 if (e
.id
== HDR_OTHER
) {
496 // does it have an ACL list configured?
497 // Optimize: use a name type that we do not need to convert to here
498 const ManglersByName::const_iterator i
= custom
.find(e
.name
.termedBuf());
499 if (i
!= custom
.end())
503 // Next-to-last resort: "Other" rules match any custom header
504 if (e
.id
== HDR_OTHER
&& known
[HDR_OTHER
].access_list
)
505 return &known
[HDR_OTHER
];
507 // Last resort: "All" rules match any header
515 httpHdrAdd(HttpHeader
*heads
, HttpRequest
*request
, const AccessLogEntryPointer
&al
, HeaderWithAclList
&headersAdd
)
517 ACLFilledChecklist
checklist(NULL
, request
, NULL
);
519 for (HeaderWithAclList::const_iterator hwa
= headersAdd
.begin(); hwa
!= headersAdd
.end(); ++hwa
) {
520 if (!hwa
->aclList
|| checklist
.fastCheck(hwa
->aclList
) == ACCESS_ALLOWED
) {
521 const char *fieldValue
= NULL
;
526 hwa
->valueFormat
->assemble(mb
, al
, 0);
527 fieldValue
= mb
.content();
530 fieldValue
= hwa
->fieldValue
.c_str();
533 if (!fieldValue
|| fieldValue
[0] == '\0')
536 HttpHeaderEntry
*e
= new HttpHeaderEntry(hwa
->fieldId
, hwa
->fieldName
.c_str(),