]>
Commit | Line | Data |
---|---|---|
7faf2bdb | 1 | /* |
77b1029d | 2 | * Copyright (C) 1996-2020 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
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. | |
7faf2bdb | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 66 HTTP Header Tools */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
c0941a6a | 12 | #include "acl/FilledChecklist.h" |
3b07476b | 13 | #include "acl/Gadgets.h" |
81ab22b6 | 14 | #include "base/EnumIterator.h" |
582c2af2 | 15 | #include "client_side.h" |
602d9612 | 16 | #include "client_side_request.h" |
f4698e0b | 17 | #include "comm/Connection.h" |
27bc2077 | 18 | #include "compat/strtoll.h" |
d7f4a0b7 | 19 | #include "ConfigParser.h" |
f4698e0b | 20 | #include "fde.h" |
602d9612 | 21 | #include "globals.h" |
4277583a | 22 | #include "http/RegisteredHeaders.h" |
d3dddfb5 | 23 | #include "http/Stream.h" |
27bc2077 AJ |
24 | #include "HttpHdrContRange.h" |
25 | #include "HttpHeader.h" | |
79cb238d | 26 | #include "HttpHeaderFieldInfo.h" |
3b07476b CT |
27 | #include "HttpHeaderTools.h" |
28 | #include "HttpRequest.h" | |
0eb49b6d | 29 | #include "MemBuf.h" |
4d5904f7 | 30 | #include "SquidConfig.h" |
582c2af2 | 31 | #include "Store.h" |
28204b3b | 32 | #include "StrList.h" |
582c2af2 | 33 | |
cb4f4424 | 34 | #if USE_OPENSSL |
f4698e0b CT |
35 | #include "ssl/support.h" |
36 | #endif | |
582c2af2 | 37 | |
f4698e0b | 38 | #include <algorithm> |
1a30fdf5 | 39 | #include <cerrno> |
f4698e0b | 40 | #include <string> |
7faf2bdb | 41 | |
789217a2 | 42 | static void httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs); |
cde8f31b | 43 | static void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd); |
de336bbe | 44 | |
d8b249ef | 45 | void |
97474590 | 46 | httpHeaderMaskInit(HttpHeaderMask * mask, int value) |
d8b249ef | 47 | { |
97474590 | 48 | memset(mask, value, sizeof(*mask)); |
d8b249ef | 49 | } |
50 | ||
2246b732 | 51 | /* same as httpHeaderPutStr, but formats the string using snprintf first */ |
2246b732 | 52 | void |
789217a2 | 53 | httpHeaderPutStrf(HttpHeader * hdr, Http::HdrType id, const char *fmt,...) |
2246b732 | 54 | { |
9bc73deb | 55 | va_list args; |
56 | va_start(args, fmt); | |
62e76326 | 57 | |
2246b732 | 58 | httpHeaderPutStrvf(hdr, id, fmt, args); |
59 | va_end(args); | |
60 | } | |
61 | ||
62 | /* used by httpHeaderPutStrf */ | |
63 | static void | |
789217a2 | 64 | httpHeaderPutStrvf(HttpHeader * hdr, Http::HdrType id, const char *fmt, va_list vargs) |
2246b732 | 65 | { |
2246b732 | 66 | MemBuf mb; |
2fe7eff9 | 67 | mb.init(); |
8ed3716a | 68 | mb.vappendf(fmt, vargs); |
a9925b40 | 69 | hdr->putStr(id, mb.buf); |
2fe7eff9 | 70 | mb.clean(); |
2246b732 | 71 | } |
72 | ||
9035d1d5 | 73 | /** wrapper arrounf PutContRange */ |
d192d11f | 74 | void |
47f6e231 | 75 | httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len) |
d192d11f | 76 | { |
77 | HttpHdrContRange *cr = httpHdrContRangeCreate(); | |
78 | assert(hdr && ent_len >= 0); | |
79 | httpHdrContRangeSet(cr, spec, ent_len); | |
a9925b40 | 80 | hdr->putContRange(cr); |
3c670b50 | 81 | delete cr; |
d192d11f | 82 | } |
83 | ||
9035d1d5 | 84 | /** |
1c4feb50 AJ |
85 | * \return true if a given directive is found in the Connection header field-value. |
86 | * | |
87 | * \note if no Connection header exists we may check the Proxy-Connection header | |
99edd1c3 | 88 | */ |
1c4feb50 | 89 | bool |
d5f18517 | 90 | httpHeaderHasConnDir(const HttpHeader * hdr, const SBuf &directive) |
99edd1c3 | 91 | { |
30abd221 | 92 | String list; |
1c4feb50 | 93 | |
9bc73deb | 94 | /* what type of header do we have? */ |
1c4feb50 AJ |
95 | if (hdr->getList(Http::HdrType::CONNECTION, &list)) |
96 | return strListIsMember(&list, directive, ',') != 0; | |
62e76326 | 97 | |
95e78500 | 98 | #if USE_HTTP_VIOLATIONS |
1c4feb50 AJ |
99 | if (hdr->getList(Http::HdrType::PROXY_CONNECTION, &list)) |
100 | return strListIsMember(&list, directive, ',') != 0; | |
95e78500 | 101 | #endif |
30abd221 | 102 | |
1c4feb50 AJ |
103 | // else, no connection header for it to exist in |
104 | return false; | |
99edd1c3 | 105 | } |
106 | ||
b50e327b | 107 | /** handy to printf prefixes of potentially very long buffers */ |
7faf2bdb | 108 | const char * |
81858ebc | 109 | getStringPrefix(const char *str, size_t sz) |
7faf2bdb | 110 | { |
d8b249ef | 111 | #define SHORT_PREFIX_SIZE 512 |
7faf2bdb | 112 | LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE); |
81858ebc | 113 | xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz); |
7faf2bdb | 114 | return buf; |
115 | } | |
b5107edb | 116 | |
b50e327b | 117 | /** |
b5107edb | 118 | * parses an int field, complains if soemthing went wrong, returns true on |
119 | * success | |
120 | */ | |
121 | int | |
122 | httpHeaderParseInt(const char *start, int *value) | |
123 | { | |
124 | assert(value); | |
125 | *value = atoi(start); | |
62e76326 | 126 | |
b6a2f15e | 127 | if (!*value && !xisdigit(*start)) { |
bf8fe701 | 128 | debugs(66, 2, "failed to parse an int header field near '" << start << "'"); |
62e76326 | 129 | return 0; |
b5107edb | 130 | } |
62e76326 | 131 | |
b5107edb | 132 | return 1; |
133 | } | |
134 | ||
a1b9ec20 AR |
135 | bool |
136 | httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr) | |
47f6e231 | 137 | { |
a1b9ec20 | 138 | char *end = nullptr; |
66e255bc | 139 | errno = 0; |
a1b9ec20 AR |
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; | |
81ab22b6 | 152 | } |
47f6e231 | 153 | *value = res; |
a1b9ec20 AR |
154 | if (endPtr) |
155 | *endPtr = end; | |
81ab22b6 | 156 | debugs(66, 7, "offset " << start << " parsed as " << res); |
a1b9ec20 | 157 | return true; |
47f6e231 | 158 | } |
159 | ||
b50e327b AJ |
160 | /** |
161 | * Parses a quoted-string field (RFC 2616 section 2.2), complains if | |
43ae1d95 | 162 | * something went wrong, returns non-zero on success. |
34460e19 | 163 | * Un-escapes quoted-pair characters found within the string. |
229af339 | 164 | * start should point at the first double-quote. |
43ae1d95 | 165 | */ |
166 | int | |
34460e19 | 167 | httpHeaderParseQuotedString(const char *start, const int len, String *val) |
43ae1d95 | 168 | { |
169 | const char *end, *pos; | |
30abd221 | 170 | val->clean(); |
9abd1514 | 171 | if (*start != '"') { |
5f5ddef6 | 172 | debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'"); |
6d97f5f1 | 173 | return 0; |
9abd1514 | 174 | } |
43ae1d95 | 175 | pos = start + 1; |
176 | ||
34460e19 | 177 | while (*pos != '"' && len > (pos-start)) { |
5f5ddef6 CT |
178 | |
179 | if (*pos =='\r') { | |
95dc7ff4 | 180 | ++pos; |
5f5ddef6 CT |
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 << "'"); | |
184 | val->clean(); | |
185 | return 0; | |
186 | } | |
187 | } | |
188 | ||
189 | if (*pos == '\n') { | |
95dc7ff4 | 190 | ++pos; |
5f5ddef6 CT |
191 | if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) { |
192 | debugs(66, 2, HERE << "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(" "); | |
95dc7ff4 | 198 | ++pos; |
5f5ddef6 CT |
199 | debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start)); |
200 | continue; | |
201 | } | |
202 | ||
a0133f10 | 203 | bool quoted = (*pos == '\\'); |
5f5ddef6 | 204 | if (quoted) { |
95dc7ff4 | 205 | ++pos; |
5f5ddef6 CT |
206 | if (!*pos || (pos-start) > len) { |
207 | debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'"); | |
208 | val->clean(); | |
209 | return 0; | |
210 | } | |
6d97f5f1 | 211 | } |
34460e19 | 212 | end = pos; |
c4b86597 | 213 | while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F) |
95dc7ff4 | 214 | ++end; |
c4b86597 | 215 | if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) { |
5f5ddef6 | 216 | debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos) |
34460e19 AJ |
217 | << " bytes into '" << start << "'"); |
218 | val->clean(); | |
219 | return 0; | |
220 | } | |
6d97f5f1 A |
221 | val->append(pos, end-pos); |
222 | pos = end; | |
43ae1d95 | 223 | } |
5f5ddef6 CT |
224 | |
225 | if (*pos != '\"') { | |
226 | debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" "); | |
227 | val->clean(); | |
228 | return 0; | |
229 | } | |
9abd1514 | 230 | /* Make sure it's defined even if empty "" */ |
b38b26cb | 231 | if (!val->termedBuf()) |
2fe0439c | 232 | val->assign("", 0); |
9abd1514 | 233 | return 1; |
43ae1d95 | 234 | } |
235 | ||
e7ce227f | 236 | SBuf |
22381a6b AR |
237 | httpHeaderQuoteString(const char *raw) |
238 | { | |
239 | assert(raw); | |
240 | ||
3cc0f4e7 AR |
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 | ||
e7ce227f AR |
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) | |
22381a6b AR |
246 | bool needInnerQuote = false; |
247 | for (const char *s = raw; !needInnerQuote && *s; ++s) | |
248 | needInnerQuote = *s == '"' || *s == '\\'; | |
249 | ||
e7ce227f | 250 | SBuf quotedStr; |
22381a6b AR |
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 | } | |
e7ce227f | 262 | |
22381a6b AR |
263 | quotedStr.append('"'); |
264 | return quotedStr; | |
265 | } | |
266 | ||
b50e327b AJ |
267 | /** |
268 | * Checks the anonymizer (header_access) configuration. | |
4f4611bf | 269 | * |
b50e327b AJ |
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 | |
6bccf575 | 274 | */ |
2d72d4fd | 275 | static int |
82a9ea6c | 276 | httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms, const AccessLogEntryPointer &al) |
6bccf575 | 277 | { |
e9b1e111 | 278 | int retval; |
279 | ||
6bccf575 | 280 | assert(e); |
8c01ada0 | 281 | |
001d55dc | 282 | const headerMangler *hm = hms->find(*e); |
3b07476b | 283 | |
b50e327b | 284 | /* mangler or checklist went away. default allow */ |
af6a12ee | 285 | if (!hm || !hm->access_list) { |
81ab22b6 | 286 | debugs(66, 7, "couldn't find mangler or access list. Allowing"); |
b50e327b AJ |
287 | return 1; |
288 | } | |
289 | ||
c0941a6a | 290 | ACLFilledChecklist checklist(hm->access_list, request, NULL); |
62e76326 | 291 | |
82a9ea6c EB |
292 | checklist.al = al; |
293 | if (al && al->reply) { | |
294 | checklist.reply = al->reply.getRaw(); | |
295 | HTTPMSGLOCK(checklist.reply); | |
296 | } | |
297 | ||
06bf5384 | 298 | if (checklist.fastCheck().allowed()) { |
b50e327b | 299 | /* aclCheckFast returns true for allow. */ |
81ab22b6 | 300 | debugs(66, 7, "checklist for mangler is positive. Mangle"); |
62e76326 | 301 | retval = 1; |
a453662f | 302 | } else if (NULL == hm->replacement) { |
62e76326 | 303 | /* It was denied, and we don't have any replacement */ |
81ab22b6 | 304 | debugs(66, 7, "checklist denied, we have no replacement. Pass"); |
62e76326 | 305 | retval = 0; |
a453662f | 306 | } else { |
62e76326 | 307 | /* It was denied, but we have a replacement. Replace the |
308 | * header on the fly, and return that the new header | |
309 | * is allowed. | |
310 | */ | |
81ab22b6 | 311 | debugs(66, 7, "checklist denied but we have replacement. Replace"); |
62e76326 | 312 | e->value = hm->replacement; |
313 | retval = 1; | |
a453662f | 314 | } |
e9b1e111 | 315 | |
e9b1e111 | 316 | return retval; |
6bccf575 | 317 | } |
318 | ||
b50e327b | 319 | /** Mangles headers for a list of headers. */ |
6bccf575 | 320 | void |
cde8f31b | 321 | httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep) |
6bccf575 | 322 | { |
323 | HttpHeaderEntry *e; | |
324 | HttpHeaderPos p = HttpHeaderInitPos; | |
62e76326 | 325 | |
cde8f31b NH |
326 | /* check with anonymizer tables */ |
327 | HeaderManglers *hms = nullptr; | |
328 | HeaderWithAclList *headersAdd = nullptr; | |
329 | ||
330 | switch (req_or_rep) { | |
b7290021 SM |
331 | case ROR_REQUEST: |
332 | hms = Config.request_header_access; | |
333 | headersAdd = Config.request_header_add; | |
334 | break; | |
335 | case ROR_REPLY: | |
336 | hms = Config.reply_header_access; | |
337 | headersAdd = Config.reply_header_add; | |
338 | break; | |
cde8f31b NH |
339 | } |
340 | ||
341 | if (hms) { | |
342 | int headers_deleted = 0; | |
343 | while ((e = l->getEntry(&p))) { | |
82a9ea6c | 344 | if (httpHdrMangle(e, request, hms, al) == 0) |
cde8f31b NH |
345 | l->delAt(p, headers_deleted); |
346 | } | |
ba9fb01d | 347 | |
cde8f31b NH |
348 | if (headers_deleted) |
349 | l->refreshMask(); | |
350 | } | |
351 | ||
352 | if (headersAdd && !headersAdd->empty()) { | |
353 | httpHdrAdd(l, request, al, *headersAdd); | |
354 | } | |
6bccf575 | 355 | } |
5967c0bf | 356 | |
3b07476b | 357 | static |
001d55dc | 358 | void header_mangler_clean(headerMangler &m) |
3b07476b CT |
359 | { |
360 | aclDestroyAccessList(&m.access_list); | |
361 | safe_free(m.replacement); | |
362 | } | |
363 | ||
364 | static | |
365 | void header_mangler_dump_access(StoreEntry * entry, const char *option, | |
001d55dc | 366 | const headerMangler &m, const char *name) |
3b07476b CT |
367 | { |
368 | if (m.access_list != NULL) { | |
369 | storeAppendPrintf(entry, "%s ", option); | |
370 | dump_acl_access(entry, name, m.access_list); | |
371 | } | |
372 | } | |
373 | ||
374 | static | |
375 | void header_mangler_dump_replacement(StoreEntry * entry, const char *option, | |
001d55dc | 376 | const headerMangler &m, const char *name) |
3b07476b CT |
377 | { |
378 | if (m.replacement) | |
379 | storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement); | |
380 | } | |
381 | ||
382 | HeaderManglers::HeaderManglers() | |
383 | { | |
384 | memset(known, 0, sizeof(known)); | |
385 | memset(&all, 0, sizeof(all)); | |
386 | } | |
387 | ||
388 | HeaderManglers::~HeaderManglers() | |
389 | { | |
81ab22b6 | 390 | for (auto i : WholeEnum<Http::HdrType>()) |
3b07476b CT |
391 | header_mangler_clean(known[i]); |
392 | ||
81ab22b6 FC |
393 | for (auto i : custom) |
394 | header_mangler_clean(i.second); | |
3b07476b CT |
395 | |
396 | header_mangler_clean(all); | |
397 | } | |
398 | ||
399 | void | |
400 | HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const | |
5967c0bf | 401 | { |
81ab22b6 FC |
402 | for (auto id : WholeEnum<Http::HdrType>()) |
403 | header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name); | |
5967c0bf | 404 | |
81ab22b6 FC |
405 | for (auto i : custom) |
406 | header_mangler_dump_access(entry, name, i.second, i.first.c_str()); | |
3b07476b CT |
407 | |
408 | header_mangler_dump_access(entry, name, all, "All"); | |
5967c0bf | 409 | } |
3b07476b CT |
410 | |
411 | void | |
412 | HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const | |
413 | { | |
81ab22b6 FC |
414 | for (auto id : WholeEnum<Http::HdrType>()) { |
415 | header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name); | |
3b07476b CT |
416 | } |
417 | ||
81ab22b6 FC |
418 | for (auto i: custom) { |
419 | header_mangler_dump_replacement(entry, name, i.second, i.first.c_str()); | |
3b07476b CT |
420 | } |
421 | ||
422 | header_mangler_dump_replacement(entry, name, all, "All"); | |
423 | } | |
424 | ||
001d55dc | 425 | headerMangler * |
3b07476b CT |
426 | HeaderManglers::track(const char *name) |
427 | { | |
81ab22b6 FC |
428 | if (strcmp(name, "All") == 0) |
429 | return &all; | |
3b07476b | 430 | |
81ab22b6 | 431 | const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id; |
3b07476b | 432 | |
81ab22b6 FC |
433 | if (id != Http::HdrType::BAD_HDR) |
434 | return &known[id]; | |
435 | ||
436 | if (strcmp(name, "Other") == 0) | |
437 | return &known[Http::HdrType::OTHER]; | |
3b07476b | 438 | |
81ab22b6 | 439 | return &custom[name]; |
3b07476b CT |
440 | } |
441 | ||
442 | void | |
443 | HeaderManglers::setReplacement(const char *name, const char *value) | |
444 | { | |
445 | // for backword compatibility, we allow replacements to be configured | |
446 | // for headers w/o access rules, but such replacements are ignored | |
001d55dc | 447 | headerMangler *m = track(name); |
3b07476b CT |
448 | |
449 | safe_free(m->replacement); // overwrite old value if any | |
450 | m->replacement = xstrdup(value); | |
451 | } | |
452 | ||
001d55dc | 453 | const headerMangler * |
3b07476b CT |
454 | HeaderManglers::find(const HttpHeaderEntry &e) const |
455 | { | |
456 | // a known header with a configured ACL list | |
1da82544 | 457 | if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) && |
d3abcb0d | 458 | known[e.id].access_list) |
3b07476b CT |
459 | return &known[e.id]; |
460 | ||
461 | // a custom header | |
789217a2 | 462 | if (e.id == Http::HdrType::OTHER) { |
3b07476b CT |
463 | // does it have an ACL list configured? |
464 | // Optimize: use a name type that we do not need to convert to here | |
d5f18517 AJ |
465 | SBuf tmp(e.name); // XXX: performance regression. c_str() reallocates |
466 | const ManglersByName::const_iterator i = custom.find(tmp.c_str()); | |
3b07476b CT |
467 | if (i != custom.end()) |
468 | return &i->second; | |
469 | } | |
470 | ||
471 | // Next-to-last resort: "Other" rules match any custom header | |
789217a2 FC |
472 | if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list) |
473 | return &known[Http::HdrType::OTHER]; | |
3b07476b CT |
474 | |
475 | // Last resort: "All" rules match any header | |
476 | if (all.access_list) | |
477 | return &all; | |
478 | ||
479 | return NULL; | |
480 | } | |
481 | ||
f4698e0b | 482 | void |
4bf68cfa | 483 | httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd) |
f4698e0b CT |
484 | { |
485 | ACLFilledChecklist checklist(NULL, request, NULL); | |
486 | ||
b6f3c19a CT |
487 | checklist.al = al; |
488 | if (al && al->reply) { | |
489 | checklist.reply = al->reply.getRaw(); | |
490 | HTTPMSGLOCK(checklist.reply); | |
491 | } | |
492 | ||
f4698e0b | 493 | for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) { |
06bf5384 | 494 | if (!hwa->aclList || checklist.fastCheck(hwa->aclList).allowed()) { |
f4698e0b CT |
495 | const char *fieldValue = NULL; |
496 | MemBuf mb; | |
497 | if (hwa->quoted) { | |
4bf68cfa | 498 | if (al != NULL) { |
f4698e0b | 499 | mb.init(); |
4bf68cfa | 500 | hwa->valueFormat->assemble(mb, al, 0); |
f4698e0b CT |
501 | fieldValue = mb.content(); |
502 | } | |
503 | } else { | |
504 | fieldValue = hwa->fieldValue.c_str(); | |
505 | } | |
506 | ||
507 | if (!fieldValue || fieldValue[0] == '\0') | |
508 | fieldValue = "-"; | |
509 | ||
d5f18517 | 510 | HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, SBuf(hwa->fieldName), fieldValue); |
f4698e0b CT |
511 | heads->addEntry(e); |
512 | } | |
513 | } | |
514 | } | |
f53969cc | 515 |