]>
Commit | Line | Data |
---|---|---|
7faf2bdb | 1 | /* |
ef57eb7b | 2 | * Copyright (C) 1996-2016 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); |
d192d11f | 81 | httpHdrContRangeDestroy(cr); |
82 | } | |
83 | ||
9035d1d5 | 84 | /** |
9bc73deb | 85 | * return true if a given directive is found in at least one of |
789217a2 FC |
86 | * the "connection" header-fields note: if Http::HdrType::PROXY_CONNECTION is |
87 | * present we ignore Http::HdrType::CONNECTION. | |
99edd1c3 | 88 | */ |
89 | int | |
5999b776 | 90 | httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive) |
99edd1c3 | 91 | { |
30abd221 | 92 | String list; |
9bc73deb | 93 | int res; |
94 | /* what type of header do we have? */ | |
62e76326 | 95 | |
95e78500 | 96 | #if USE_HTTP_VIOLATIONS |
789217a2 FC |
97 | if (hdr->has(Http::HdrType::PROXY_CONNECTION)) |
98 | list = hdr->getList(Http::HdrType::PROXY_CONNECTION); | |
95e78500 AJ |
99 | else |
100 | #endif | |
789217a2 FC |
101 | if (hdr->has(Http::HdrType::CONNECTION)) |
102 | list = hdr->getList(Http::HdrType::CONNECTION); | |
2e881a6f A |
103 | else |
104 | return 0; | |
9bc73deb | 105 | |
9bc73deb | 106 | res = strListIsMember(&list, directive, ','); |
62e76326 | 107 | |
30abd221 | 108 | list.clean(); |
109 | ||
9bc73deb | 110 | return res; |
99edd1c3 | 111 | } |
112 | ||
b50e327b | 113 | /** handy to printf prefixes of potentially very long buffers */ |
7faf2bdb | 114 | const char * |
81858ebc | 115 | getStringPrefix(const char *str, size_t sz) |
7faf2bdb | 116 | { |
d8b249ef | 117 | #define SHORT_PREFIX_SIZE 512 |
7faf2bdb | 118 | LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE); |
81858ebc | 119 | xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz); |
7faf2bdb | 120 | return buf; |
121 | } | |
b5107edb | 122 | |
b50e327b | 123 | /** |
b5107edb | 124 | * parses an int field, complains if soemthing went wrong, returns true on |
125 | * success | |
126 | */ | |
127 | int | |
128 | httpHeaderParseInt(const char *start, int *value) | |
129 | { | |
130 | assert(value); | |
131 | *value = atoi(start); | |
62e76326 | 132 | |
b6a2f15e | 133 | if (!*value && !xisdigit(*start)) { |
bf8fe701 | 134 | debugs(66, 2, "failed to parse an int header field near '" << start << "'"); |
62e76326 | 135 | return 0; |
b5107edb | 136 | } |
62e76326 | 137 | |
b5107edb | 138 | return 1; |
139 | } | |
140 | ||
47f6e231 | 141 | int |
142 | httpHeaderParseOffset(const char *start, int64_t * value) | |
143 | { | |
66e255bc | 144 | errno = 0; |
47f6e231 | 145 | int64_t res = strtoll(start, NULL, 10); |
81ab22b6 FC |
146 | if (!res && EINVAL == errno) { /* maybe not portable? */ |
147 | debugs(66, 7, "failed to parse offset in " << start); | |
26ac0430 | 148 | return 0; |
81ab22b6 | 149 | } |
47f6e231 | 150 | *value = res; |
81ab22b6 | 151 | debugs(66, 7, "offset " << start << " parsed as " << res); |
47f6e231 | 152 | return 1; |
153 | } | |
154 | ||
b50e327b AJ |
155 | /** |
156 | * Parses a quoted-string field (RFC 2616 section 2.2), complains if | |
43ae1d95 | 157 | * something went wrong, returns non-zero on success. |
34460e19 | 158 | * Un-escapes quoted-pair characters found within the string. |
229af339 | 159 | * start should point at the first double-quote. |
43ae1d95 | 160 | */ |
161 | int | |
34460e19 | 162 | httpHeaderParseQuotedString(const char *start, const int len, String *val) |
43ae1d95 | 163 | { |
164 | const char *end, *pos; | |
30abd221 | 165 | val->clean(); |
9abd1514 | 166 | if (*start != '"') { |
5f5ddef6 | 167 | debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'"); |
6d97f5f1 | 168 | return 0; |
9abd1514 | 169 | } |
43ae1d95 | 170 | pos = start + 1; |
171 | ||
34460e19 | 172 | while (*pos != '"' && len > (pos-start)) { |
5f5ddef6 CT |
173 | |
174 | if (*pos =='\r') { | |
95dc7ff4 | 175 | ++pos; |
5f5ddef6 CT |
176 | if ((pos-start) > len || *pos != '\n') { |
177 | debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos) | |
178 | << " bytes into '" << start << "'"); | |
179 | val->clean(); | |
180 | return 0; | |
181 | } | |
182 | } | |
183 | ||
184 | if (*pos == '\n') { | |
95dc7ff4 | 185 | ++pos; |
5f5ddef6 CT |
186 | if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) { |
187 | debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'"); | |
188 | val->clean(); | |
189 | return 0; | |
190 | } | |
191 | // TODO: replace the entire LWS with a space | |
192 | val->append(" "); | |
95dc7ff4 | 193 | ++pos; |
5f5ddef6 CT |
194 | debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start)); |
195 | continue; | |
196 | } | |
197 | ||
a0133f10 | 198 | bool quoted = (*pos == '\\'); |
5f5ddef6 | 199 | if (quoted) { |
95dc7ff4 | 200 | ++pos; |
5f5ddef6 CT |
201 | if (!*pos || (pos-start) > len) { |
202 | debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'"); | |
203 | val->clean(); | |
204 | return 0; | |
205 | } | |
6d97f5f1 | 206 | } |
34460e19 | 207 | end = pos; |
c4b86597 | 208 | while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F) |
95dc7ff4 | 209 | ++end; |
c4b86597 | 210 | if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) { |
5f5ddef6 | 211 | debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos) |
34460e19 AJ |
212 | << " bytes into '" << start << "'"); |
213 | val->clean(); | |
214 | return 0; | |
215 | } | |
6d97f5f1 A |
216 | val->append(pos, end-pos); |
217 | pos = end; | |
43ae1d95 | 218 | } |
5f5ddef6 CT |
219 | |
220 | if (*pos != '\"') { | |
221 | debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" "); | |
222 | val->clean(); | |
223 | return 0; | |
224 | } | |
9abd1514 | 225 | /* Make sure it's defined even if empty "" */ |
b38b26cb | 226 | if (!val->termedBuf()) |
6d97f5f1 | 227 | val->limitInit("", 0); |
9abd1514 | 228 | return 1; |
43ae1d95 | 229 | } |
230 | ||
e7ce227f | 231 | SBuf |
22381a6b AR |
232 | httpHeaderQuoteString(const char *raw) |
233 | { | |
234 | assert(raw); | |
235 | ||
3cc0f4e7 AR |
236 | // TODO: Optimize by appending a sequence of characters instead of a char. |
237 | // This optimization may be easier with Tokenizer after raw becomes SBuf. | |
238 | ||
e7ce227f AR |
239 | // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a |
240 | // quoted-string except where necessary" (i.e., DQUOTE and backslash) | |
22381a6b AR |
241 | bool needInnerQuote = false; |
242 | for (const char *s = raw; !needInnerQuote && *s; ++s) | |
243 | needInnerQuote = *s == '"' || *s == '\\'; | |
244 | ||
e7ce227f | 245 | SBuf quotedStr; |
22381a6b AR |
246 | quotedStr.append('"'); |
247 | ||
248 | if (needInnerQuote) { | |
249 | for (const char *s = raw; *s; ++s) { | |
250 | if (*s == '"' || *s == '\\') | |
251 | quotedStr.append('\\'); | |
252 | quotedStr.append(*s); | |
253 | } | |
254 | } else { | |
255 | quotedStr.append(raw); | |
256 | } | |
e7ce227f | 257 | |
22381a6b AR |
258 | quotedStr.append('"'); |
259 | return quotedStr; | |
260 | } | |
261 | ||
b50e327b AJ |
262 | /** |
263 | * Checks the anonymizer (header_access) configuration. | |
4f4611bf | 264 | * |
b50e327b AJ |
265 | * \retval 0 Header is explicitly blocked for removal |
266 | * \retval 1 Header is explicitly allowed | |
267 | * \retval 1 Header has been replaced, the current version can be used. | |
268 | * \retval 1 Header has no access controls to test | |
6bccf575 | 269 | */ |
2d72d4fd | 270 | static int |
cde8f31b | 271 | httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms) |
6bccf575 | 272 | { |
e9b1e111 | 273 | int retval; |
274 | ||
6bccf575 | 275 | assert(e); |
8c01ada0 | 276 | |
001d55dc | 277 | const headerMangler *hm = hms->find(*e); |
3b07476b | 278 | |
b50e327b | 279 | /* mangler or checklist went away. default allow */ |
af6a12ee | 280 | if (!hm || !hm->access_list) { |
81ab22b6 | 281 | debugs(66, 7, "couldn't find mangler or access list. Allowing"); |
b50e327b AJ |
282 | return 1; |
283 | } | |
284 | ||
c0941a6a | 285 | ACLFilledChecklist checklist(hm->access_list, request, NULL); |
62e76326 | 286 | |
2efeb0b7 | 287 | if (checklist.fastCheck() == ACCESS_ALLOWED) { |
b50e327b | 288 | /* aclCheckFast returns true for allow. */ |
81ab22b6 | 289 | debugs(66, 7, "checklist for mangler is positive. Mangle"); |
62e76326 | 290 | retval = 1; |
a453662f | 291 | } else if (NULL == hm->replacement) { |
62e76326 | 292 | /* It was denied, and we don't have any replacement */ |
81ab22b6 | 293 | debugs(66, 7, "checklist denied, we have no replacement. Pass"); |
62e76326 | 294 | retval = 0; |
a453662f | 295 | } else { |
62e76326 | 296 | /* It was denied, but we have a replacement. Replace the |
297 | * header on the fly, and return that the new header | |
298 | * is allowed. | |
299 | */ | |
81ab22b6 | 300 | debugs(66, 7, "checklist denied but we have replacement. Replace"); |
62e76326 | 301 | e->value = hm->replacement; |
302 | retval = 1; | |
a453662f | 303 | } |
e9b1e111 | 304 | |
e9b1e111 | 305 | return retval; |
6bccf575 | 306 | } |
307 | ||
b50e327b | 308 | /** Mangles headers for a list of headers. */ |
6bccf575 | 309 | void |
cde8f31b | 310 | httpHdrMangleList(HttpHeader *l, HttpRequest *request, const AccessLogEntryPointer &al, req_or_rep_t req_or_rep) |
6bccf575 | 311 | { |
312 | HttpHeaderEntry *e; | |
313 | HttpHeaderPos p = HttpHeaderInitPos; | |
62e76326 | 314 | |
cde8f31b NH |
315 | /* check with anonymizer tables */ |
316 | HeaderManglers *hms = nullptr; | |
317 | HeaderWithAclList *headersAdd = nullptr; | |
318 | ||
319 | switch (req_or_rep) { | |
b7290021 SM |
320 | case ROR_REQUEST: |
321 | hms = Config.request_header_access; | |
322 | headersAdd = Config.request_header_add; | |
323 | break; | |
324 | case ROR_REPLY: | |
325 | hms = Config.reply_header_access; | |
326 | headersAdd = Config.reply_header_add; | |
327 | break; | |
cde8f31b NH |
328 | } |
329 | ||
330 | if (hms) { | |
331 | int headers_deleted = 0; | |
332 | while ((e = l->getEntry(&p))) { | |
333 | if (0 == httpHdrMangle(e, request, hms)) | |
334 | l->delAt(p, headers_deleted); | |
335 | } | |
ba9fb01d | 336 | |
cde8f31b NH |
337 | if (headers_deleted) |
338 | l->refreshMask(); | |
339 | } | |
340 | ||
341 | if (headersAdd && !headersAdd->empty()) { | |
342 | httpHdrAdd(l, request, al, *headersAdd); | |
343 | } | |
6bccf575 | 344 | } |
5967c0bf | 345 | |
3b07476b | 346 | static |
001d55dc | 347 | void header_mangler_clean(headerMangler &m) |
3b07476b CT |
348 | { |
349 | aclDestroyAccessList(&m.access_list); | |
350 | safe_free(m.replacement); | |
351 | } | |
352 | ||
353 | static | |
354 | void header_mangler_dump_access(StoreEntry * entry, const char *option, | |
001d55dc | 355 | const headerMangler &m, const char *name) |
3b07476b CT |
356 | { |
357 | if (m.access_list != NULL) { | |
358 | storeAppendPrintf(entry, "%s ", option); | |
359 | dump_acl_access(entry, name, m.access_list); | |
360 | } | |
361 | } | |
362 | ||
363 | static | |
364 | void header_mangler_dump_replacement(StoreEntry * entry, const char *option, | |
001d55dc | 365 | const headerMangler &m, const char *name) |
3b07476b CT |
366 | { |
367 | if (m.replacement) | |
368 | storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement); | |
369 | } | |
370 | ||
371 | HeaderManglers::HeaderManglers() | |
372 | { | |
373 | memset(known, 0, sizeof(known)); | |
374 | memset(&all, 0, sizeof(all)); | |
375 | } | |
376 | ||
377 | HeaderManglers::~HeaderManglers() | |
378 | { | |
81ab22b6 | 379 | for (auto i : WholeEnum<Http::HdrType>()) |
3b07476b CT |
380 | header_mangler_clean(known[i]); |
381 | ||
81ab22b6 FC |
382 | for (auto i : custom) |
383 | header_mangler_clean(i.second); | |
3b07476b CT |
384 | |
385 | header_mangler_clean(all); | |
386 | } | |
387 | ||
388 | void | |
389 | HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const | |
5967c0bf | 390 | { |
81ab22b6 FC |
391 | for (auto id : WholeEnum<Http::HdrType>()) |
392 | header_mangler_dump_access(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name); | |
5967c0bf | 393 | |
81ab22b6 FC |
394 | for (auto i : custom) |
395 | header_mangler_dump_access(entry, name, i.second, i.first.c_str()); | |
3b07476b CT |
396 | |
397 | header_mangler_dump_access(entry, name, all, "All"); | |
5967c0bf | 398 | } |
3b07476b CT |
399 | |
400 | void | |
401 | HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const | |
402 | { | |
81ab22b6 FC |
403 | for (auto id : WholeEnum<Http::HdrType>()) { |
404 | header_mangler_dump_replacement(entry, name, known[id], Http::HeaderLookupTable.lookup(id).name); | |
3b07476b CT |
405 | } |
406 | ||
81ab22b6 FC |
407 | for (auto i: custom) { |
408 | header_mangler_dump_replacement(entry, name, i.second, i.first.c_str()); | |
3b07476b CT |
409 | } |
410 | ||
411 | header_mangler_dump_replacement(entry, name, all, "All"); | |
412 | } | |
413 | ||
001d55dc | 414 | headerMangler * |
3b07476b CT |
415 | HeaderManglers::track(const char *name) |
416 | { | |
81ab22b6 FC |
417 | if (strcmp(name, "All") == 0) |
418 | return &all; | |
3b07476b | 419 | |
81ab22b6 | 420 | const Http::HdrType id = Http::HeaderLookupTable.lookup(SBuf(name)).id; |
3b07476b | 421 | |
81ab22b6 FC |
422 | if (id != Http::HdrType::BAD_HDR) |
423 | return &known[id]; | |
424 | ||
425 | if (strcmp(name, "Other") == 0) | |
426 | return &known[Http::HdrType::OTHER]; | |
3b07476b | 427 | |
81ab22b6 | 428 | return &custom[name]; |
3b07476b CT |
429 | } |
430 | ||
431 | void | |
432 | HeaderManglers::setReplacement(const char *name, const char *value) | |
433 | { | |
434 | // for backword compatibility, we allow replacements to be configured | |
435 | // for headers w/o access rules, but such replacements are ignored | |
001d55dc | 436 | headerMangler *m = track(name); |
3b07476b CT |
437 | |
438 | safe_free(m->replacement); // overwrite old value if any | |
439 | m->replacement = xstrdup(value); | |
440 | } | |
441 | ||
001d55dc | 442 | const headerMangler * |
3b07476b CT |
443 | HeaderManglers::find(const HttpHeaderEntry &e) const |
444 | { | |
445 | // a known header with a configured ACL list | |
1da82544 | 446 | if (e.id != Http::HdrType::OTHER && Http::any_HdrType_enum_value(e.id) && |
d3abcb0d | 447 | known[e.id].access_list) |
3b07476b CT |
448 | return &known[e.id]; |
449 | ||
450 | // a custom header | |
789217a2 | 451 | if (e.id == Http::HdrType::OTHER) { |
3b07476b CT |
452 | // does it have an ACL list configured? |
453 | // Optimize: use a name type that we do not need to convert to here | |
454 | const ManglersByName::const_iterator i = custom.find(e.name.termedBuf()); | |
455 | if (i != custom.end()) | |
456 | return &i->second; | |
457 | } | |
458 | ||
459 | // Next-to-last resort: "Other" rules match any custom header | |
789217a2 FC |
460 | if (e.id == Http::HdrType::OTHER && known[Http::HdrType::OTHER].access_list) |
461 | return &known[Http::HdrType::OTHER]; | |
3b07476b CT |
462 | |
463 | // Last resort: "All" rules match any header | |
464 | if (all.access_list) | |
465 | return &all; | |
466 | ||
467 | return NULL; | |
468 | } | |
469 | ||
f4698e0b | 470 | void |
4bf68cfa | 471 | httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headersAdd) |
f4698e0b CT |
472 | { |
473 | ACLFilledChecklist checklist(NULL, request, NULL); | |
474 | ||
475 | for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) { | |
476 | if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) { | |
477 | const char *fieldValue = NULL; | |
478 | MemBuf mb; | |
479 | if (hwa->quoted) { | |
4bf68cfa | 480 | if (al != NULL) { |
f4698e0b | 481 | mb.init(); |
4bf68cfa | 482 | hwa->valueFormat->assemble(mb, al, 0); |
f4698e0b CT |
483 | fieldValue = mb.content(); |
484 | } | |
485 | } else { | |
486 | fieldValue = hwa->fieldValue.c_str(); | |
487 | } | |
488 | ||
489 | if (!fieldValue || fieldValue[0] == '\0') | |
490 | fieldValue = "-"; | |
491 | ||
999aa5d2 A |
492 | HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(), |
493 | fieldValue); | |
f4698e0b CT |
494 | heads->addEntry(e); |
495 | } | |
496 | } | |
497 | } | |
f53969cc | 498 |