]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpHeader.cc
Return from handleIMSReply when verdict is decided (#722)
[thirdparty/squid.git] / src / HttpHeader.cc
CommitLineData
cb69b4c7 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
cb69b4c7 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.
cb69b4c7 7 */
8
bbc27441
AJ
9/* DEBUG: section 55 HTTP Header */
10
582c2af2 11#include "squid.h"
d5f18517 12#include "base/CharacterSet.h"
81ab22b6 13#include "base/EnumIterator.h"
25f98340 14#include "base64.h"
67679543 15#include "globals.h"
a1b9ec20 16#include "http/ContentLengthInterpreter.h"
7ebe76de 17#include "HttpHdrCc.h"
582c2af2 18#include "HttpHdrContRange.h"
1da82544 19#include "HttpHdrScTarget.h" // also includes HttpHdrSc.h
8822ebee 20#include "HttpHeader.h"
79cb238d 21#include "HttpHeaderFieldInfo.h"
e1656dc4 22#include "HttpHeaderStat.h"
a5bac1d2 23#include "HttpHeaderTools.h"
0eb49b6d 24#include "MemBuf.h"
8822ebee 25#include "mgr/Registration.h"
69c698a3 26#include "mime_header.h"
582c2af2 27#include "profiler/Profiler.h"
25f98340 28#include "rfc1123.h"
90be6ff5 29#include "sbuf/StringConvert.h"
602d9612 30#include "SquidConfig.h"
00a7574e 31#include "StatHist.h"
8822ebee 32#include "Store.h"
28204b3b 33#include "StrList.h"
81a94152 34#include "TimeOrTag.h"
ed6e9fb9 35#include "util.h"
cb69b4c7 36
42865d25
FC
37#include <algorithm>
38
6fda1515
FC
39/* XXX: the whole set of API managing the entries vector should be rethought
40 * after the parse4r-ng effort is complete.
41 */
42
cb69b4c7 43/*
2ac76861 44 * On naming conventions:
26ac0430
AJ
45 *
46 * HTTP/1.1 defines message-header as
47 *
2ac76861 48 * message-header = field-name ":" [ field-value ] CRLF
49 * field-name = token
50 * field-value = *( field-content | LWS )
26ac0430 51 *
2ac76861 52 * HTTP/1.1 does not give a name name a group of all message-headers in a message.
53 * Squid 1.1 seems to refer to that group _plus_ start-line as "headers".
26ac0430 54 *
2ac76861 55 * HttpHeader is an object that represents all message-headers in a message.
56 * HttpHeader does not manage start-line.
26ac0430 57 *
d8b249ef 58 * HttpHeader is implemented as a collection of header "entries".
59 * An entry is a (field_id, field_name, field_value) triplet.
2ac76861 60 */
cb69b4c7 61
cb69b4c7 62/*
63 * local constants and vars
64 */
65
789217a2 66// statistics counters for headers. clients must not allow Http::HdrType::BAD_HDR to be counted
81ab22b6 67std::vector<HttpHeaderFieldStat> headerStatsTable(Http::HdrType::enumEnd_);
cb69b4c7 68
81ab22b6 69/* request-only headers. Used for cachemgr */
f53969cc 70static HttpHeaderMask RequestHeadersMask; /* set run-time using RequestHeaders */
16d3204a 71
81ab22b6 72/* reply-only headers. Used for cachemgr */
f53969cc 73static HttpHeaderMask ReplyHeadersMask; /* set run-time using ReplyHeaders */
2cdeea82 74
12cf1be2 75/* header accounting */
26735116 76// NP: keep in sync with enum http_hdr_owner_type
26ac0430 77static HttpHeaderStat HttpHeaderStats[] = {
95cc1e3e 78 HttpHeaderStat(/*hoNone*/ "all", NULL),
9f4d4f65 79#if USE_HTCP
95cc1e3e 80 HttpHeaderStat(/*hoHtcpReply*/ "HTCP reply", &ReplyHeadersMask),
9f4d4f65 81#endif
95cc1e3e
FC
82 HttpHeaderStat(/*hoRequest*/ "request", &RequestHeadersMask),
83 HttpHeaderStat(/*hoReply*/ "reply", &ReplyHeadersMask)
26735116
AJ
84#if USE_OPENSSL
85 /* hoErrorDetail */
86#endif
87 /* hoEnd */
26ac0430 88};
9bea1d5b 89static int HttpHeaderStatCount = countof(HttpHeaderStats);
12cf1be2 90
7faf2bdb 91static int HeaderEntryParsedCount = 0;
cb69b4c7 92
12cf1be2 93/*
f734cda2 94 * forward declarations and local routines
12cf1be2 95 */
cb69b4c7 96
f734cda2 97class StoreEntry;
cb69b4c7 98
81ab22b6
FC
99// update parse statistics for header id; if error is true also account
100// for errors and write to debug log what happened
101static void httpHeaderNoteParsedEntry(Http::HdrType id, String const &value, bool error);
73b2aa87 102static void httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e);
f734cda2
FC
103/** store report about current header usage and other stats */
104static void httpHeaderStoreReport(StoreEntry * e);
105
cb69b4c7 106/*
107 * Module initialization routines
108 */
109
6b7d87bb
FC
110static void
111httpHeaderRegisterWithCacheManager(void)
112{
8822ebee 113 Mgr::RegisterAction("http_headers",
d9fc6862
A
114 "HTTP Header Statistics",
115 httpHeaderStoreReport, 0, 1);
6b7d87bb
FC
116}
117
cb69b4c7 118void
9bea1d5b 119httpHeaderInitModule(void)
cb69b4c7 120{
9bea1d5b 121 /* check that we have enough space for masks */
81ab22b6
FC
122 assert(8 * sizeof(HttpHeaderMask) >= Http::HdrType::enumEnd_);
123
124 // masks are needed for stats page still
125 for (auto h : WholeEnum<Http::HdrType>()) {
126 if (Http::HeaderLookupTable.lookup(h).request)
127 CBIT_SET(RequestHeadersMask,h);
128 if (Http::HeaderLookupTable.lookup(h).reply)
129 CBIT_SET(ReplyHeadersMask,h);
130 }
dcf3665b 131
26735116 132 /* header stats initialized by class constructor */
9bea1d5b 133 assert(HttpHeaderStatCount == hoReply + 1);
16d3204a 134
9bea1d5b 135 /* init dependent modules */
136 httpHdrCcInitModule();
43ae1d95 137 httpHdrScInitModule();
ea391f18
FC
138
139 httpHeaderRegisterWithCacheManager();
62ee09ca 140}
43ae1d95 141
cb69b4c7 142/*
143 * HttpHeader Implementation
144 */
145
3e42b356 146HttpHeader::HttpHeader() : owner (hoNone), len (0), conflictingContentLength_(false)
cb69b4c7 147{
c3b51d64 148 entries.reserve(32);
75faaa7a 149 httpHeaderMaskInit(&mask, 0);
150}
151
3e42b356 152HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0), conflictingContentLength_(false)
75faaa7a 153{
02259ff8 154 assert(anOwner > hoNone && anOwner < hoEnd);
efc26e8e 155 debugs(55, 7, "init-ing hdr: " << this << " owner: " << owner);
c3b51d64 156 entries.reserve(32);
75faaa7a 157 httpHeaderMaskInit(&mask, 0);
158}
159
66d51f4f 160// XXX: Delete as unused, expensive, and violating copy semantics by skipping Warnings
3e42b356 161HttpHeader::HttpHeader(const HttpHeader &other): owner(other.owner), len(other.len), conflictingContentLength_(false)
5e5fa5b1 162{
c3b51d64 163 entries.reserve(other.entries.capacity());
5e5fa5b1 164 httpHeaderMaskInit(&mask, 0);
2d4f252d 165 update(&other); // will update the mask as well
5e5fa5b1
AR
166}
167
75faaa7a 168HttpHeader::~HttpHeader()
169{
519e0948 170 clean();
cb69b4c7 171}
172
66d51f4f 173// XXX: Delete as unused, expensive, and violating assignment semantics by skipping Warnings
5e5fa5b1
AR
174HttpHeader &
175HttpHeader::operator =(const HttpHeader &other)
176{
177 if (this != &other) {
178 // we do not really care, but the caller probably does
179 assert(owner == other.owner);
180 clean();
2d4f252d 181 update(&other); // will update the mask as well
5e5fa5b1 182 len = other.len;
3e42b356 183 conflictingContentLength_ = other.conflictingContentLength_;
f6dd87e9 184 teUnsupported_ = other.teUnsupported_;
5e5fa5b1
AR
185 }
186 return *this;
187}
188
cb69b4c7 189void
519e0948 190HttpHeader::clean()
cb69b4c7 191{
9bea1d5b 192
02259ff8 193 assert(owner > hoNone && owner < hoEnd);
efc26e8e 194 debugs(55, 7, "cleaning hdr: " << this << " owner: " << owner);
9bea1d5b 195
3a9cf2d5 196 PROF_start(HttpHeaderClean);
197
0de73a38 198 if (owner <= hoReply) {
c594d76c
A
199 /*
200 * An unfortunate bug. The entries array is initialized
201 * such that count is set to zero. httpHeaderClean() seems to
202 * be called both when 'hdr' is created, and destroyed. Thus,
203 * we accumulate a large number of zero counts for 'hdr' before
204 * it is ever used. Can't think of a good way to fix it, except
205 * adding a state variable that indicates whether or not 'hdr'
206 * has been used. As a hack, just never count zero-sized header
207 * arrays.
208 */
4c9eadc2
FC
209 if (!entries.empty())
210 HttpHeaderStats[owner].hdrUCountDistr.count(entries.size());
51328da8 211
95dc7ff4 212 ++ HttpHeaderStats[owner].destroyedCount;
51328da8 213
4c9eadc2 214 HttpHeaderStats[owner].busyDestroyedCount += entries.size() > 0;
0de73a38 215 } // if (owner <= hoReply)
62e76326 216
81ab22b6 217 for (HttpHeaderEntry *e : entries) {
1da82544 218 if (e == nullptr)
6fda1515 219 continue;
1da82544 220 if (!Http::any_valid_header(e->id)) {
6fda1515 221 debugs(55, DBG_CRITICAL, "BUG: invalid entry (" << e->id << "). Ignored.");
c594d76c
A
222 } else {
223 if (owner <= hoReply)
f30f7998 224 HttpHeaderStats[owner].fieldTypeDistr.count(e->id);
c594d76c 225 delete e;
62e76326 226 }
c594d76c 227 }
6fda1515 228
c33a88ca 229 entries.clear();
519e0948 230 httpHeaderMaskInit(&mask, 0);
5e5fa5b1 231 len = 0;
3e42b356 232 conflictingContentLength_ = false;
f6dd87e9 233 teUnsupported_ = false;
3a9cf2d5 234 PROF_stop(HttpHeaderClean);
cb69b4c7 235}
236
2246b732 237/* append entries (also see httpHeaderUpdate) */
238void
a9925b40 239HttpHeader::append(const HttpHeader * src)
2246b732 240{
a9925b40 241 assert(src);
242 assert(src != this);
efc26e8e 243 debugs(55, 7, "appending hdr: " << this << " += " << src);
9bea1d5b 244
81ab22b6
FC
245 for (auto e : src->entries) {
246 if (e)
247 addEntry(e->clone());
2246b732 248 }
249}
250
1a210de4
EB
251bool
252HttpHeader::needUpdate(HttpHeader const *fresh) const
253{
66d51f4f
AR
254 // our 1xx Warnings must be removed
255 for (const auto e: entries) {
256 // TODO: Move into HttpHeaderEntry::is1xxWarning() before official commit.
257 if (e && e->id == Http::HdrType::WARNING && (e->getInt()/100 == 1))
258 return true;
259 }
260
1a210de4 261 for (const auto e: fresh->entries) {
d747ab54 262 if (!e || skipUpdateHeader(e->id))
1a210de4
EB
263 continue;
264 String value;
d5f18517
AJ
265 if (!hasNamed(e->name, &value) ||
266 (value != fresh->getByName(e->name)))
1a210de4
EB
267 return true;
268 }
269 return false;
270}
271
924f73bc 272void
2d4f252d
EB
273HttpHeader::updateWarnings()
274{
275 int count = 0;
276 HttpHeaderPos pos = HttpHeaderInitPos;
277
278 // RFC 7234, section 4.3.4: delete 1xx warnings and retain 2xx warnings
279 while (HttpHeaderEntry *e = getEntry(&pos)) {
280 if (e->id == Http::HdrType::WARNING && (e->getInt()/100 == 1) )
281 delAt(pos, count);
282 }
283}
284
285bool
286HttpHeader::skipUpdateHeader(const Http::HdrType id) const
287{
66d51f4f
AR
288 return
289 // RFC 7234, section 4.3.4: use header fields other than Warning
290 (id == Http::HdrType::WARNING) ||
291 // TODO: Consider updating Vary headers after comparing the magnitude of
292 // the required changes (and/or cache losses) with compliance gains.
293 (id == Http::HdrType::VARY);
2d4f252d
EB
294}
295
66d51f4f 296void
2d4f252d 297HttpHeader::update(HttpHeader const *fresh)
cb69b4c7 298{
a9925b40 299 assert(fresh);
924f73bc 300 assert(this != fresh);
9bea1d5b 301
2d4f252d
EB
302 updateWarnings();
303
1a210de4
EB
304 const HttpHeaderEntry *e;
305 HttpHeaderPos pos = HttpHeaderInitPos;
306
a9925b40 307 while ((e = fresh->getEntry(&pos))) {
789217a2 308 /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
62e76326 309
2d4f252d 310 if (skipUpdateHeader(e->id))
62e76326 311 continue;
312
789217a2 313 if (e->id != Http::HdrType::OTHER)
ba9fb01d 314 delById(e->id);
315 else
d5f18517 316 delByName(e->name);
9917847f 317 }
318
319 pos = HttpHeaderInitPos;
320 while ((e = fresh->getEntry(&pos))) {
789217a2 321 /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
9917847f 322
2d4f252d 323 if (skipUpdateHeader(e->id))
9917847f 324 continue;
325
81ab22b6 326 debugs(55, 7, "Updating header '" << Http::HeaderLookupTable.lookup(e->id).name << "' in cached entry");
62e76326 327
eede25e7 328 addEntry(e->clone());
cb69b4c7 329 }
cb69b4c7 330}
331
69c698a3
EB
332bool
333HttpHeader::Isolate(const char **parse_start, size_t l, const char **blk_start, const char **blk_end)
334{
335 /*
336 * parse_start points to the first line of HTTP message *headers*,
337 * not including the request or status lines
338 */
339 const size_t end = headersEnd(*parse_start, l);
340
341 if (end) {
342 *blk_start = *parse_start;
343 *blk_end = *parse_start + end - 1;
344 assert(**blk_end == '\n');
345 // Point blk_end to the first character after the last header field.
346 // In other words, blk_end should point to the CR?LF header terminator.
347 if (end > 1 && *(*blk_end - 1) == '\r')
348 --(*blk_end);
349 *parse_start += end;
350 }
351 return end;
352}
353
354int
4f1c93a7 355HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz, Http::ContentLengthInterpreter &clen)
69c698a3
EB
356{
357 const char *parse_start = buf;
358 const char *blk_start, *blk_end;
359 hdr_sz = 0;
360
361 if (!Isolate(&parse_start, buf_len, &blk_start, &blk_end)) {
362 // XXX: do not parse non-isolated headers even if the connection is closed.
363 // Treat unterminated headers as "partial headers" framing errors.
364 if (!atEnd)
365 return 0;
366 blk_start = parse_start;
367 blk_end = blk_start + strlen(blk_start);
368 }
369
4f1c93a7 370 if (parse(blk_start, blk_end - blk_start, clen)) {
69c698a3
EB
371 hdr_sz = parse_start - buf;
372 return 1;
373 }
374 return -1;
375}
376
580448c9
AJ
377// XXX: callers treat this return as boolean.
378// XXX: A better mechanism is needed to signal different types of error.
379// lexicon, syntax, semantics, validation, access policy - are all (ab)using 'return 0'
cb69b4c7 380int
4f1c93a7 381HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthInterpreter &clen)
cb69b4c7 382{
52d3f198 383 const char *field_ptr = header_start;
784619e6 384 const char *header_end = header_start + hdrLen; // XXX: remove
56cd29e6 385 int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2);
9bea1d5b 386
3a9cf2d5 387 PROF_start(HttpHeaderParse);
388
9bea1d5b 389 assert(header_start && header_end);
81858ebc 390 debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, hdrLen));
95dc7ff4 391 ++ HttpHeaderStats[owner].parsedCount;
62e76326 392
1d8a896b 393 char *nulpos;
784619e6 394 if ((nulpos = (char*)memchr(header_start, '\0', hdrLen))) {
e0236918 395 debugs(55, DBG_IMPORTANT, "WARNING: HTTP header contains NULL characters {" <<
81858ebc 396 getStringPrefix(header_start, nulpos-header_start) << "}\nNULL\n{" << getStringPrefix(nulpos+1, hdrLen-(nulpos-header_start)-1));
6d7bc97b 397 PROF_stop(HttpHeaderParse);
81ab22b6
FC
398 clean();
399 return 0;
52d3f198 400 }
401
402 /* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
403 * continuation lines start with a (single) space or tab */
404 while (field_ptr < header_end) {
405 const char *field_start = field_ptr;
62e76326 406 const char *field_end;
62e76326 407
580448c9
AJ
408 const char *hasBareCr = nullptr;
409 size_t lines = 0;
62e76326 410 do {
52d3f198 411 const char *this_line = field_ptr;
412 field_ptr = (const char *)memchr(field_ptr, '\n', header_end - field_ptr);
580448c9 413 ++lines;
52d3f198 414
6d7bc97b
AJ
415 if (!field_ptr) {
416 // missing <LF>
417 PROF_stop(HttpHeaderParse);
81ab22b6
FC
418 clean();
419 return 0;
6d7bc97b 420 }
52d3f198 421
422 field_end = field_ptr;
423
f53969cc 424 ++field_ptr; /* Move to next line */
52d3f198 425
426 if (field_end > this_line && field_end[-1] == '\r') {
f53969cc 427 --field_end; /* Ignore CR LF */
52d3f198 428
e10272fc
AR
429 if (owner == hoRequest && field_end > this_line) {
430 bool cr_only = true;
431 for (const char *p = this_line; p < field_end && cr_only; ++p) {
432 if (*p != '\r')
433 cr_only = false;
434 }
435 if (cr_only) {
6e09ed7b 436 debugs(55, DBG_IMPORTANT, "SECURITY WARNING: Rejecting HTTP request with a CR+ "
e10272fc 437 "header field to prevent request smuggling attacks: {" <<
81858ebc 438 getStringPrefix(header_start, hdrLen) << "}");
6d7bc97b 439 PROF_stop(HttpHeaderParse);
81ab22b6
FC
440 clean();
441 return 0;
e10272fc 442 }
52d3f198 443 }
444 }
445
446 /* Barf on stray CR characters */
447 if (memchr(this_line, '\r', field_end - this_line)) {
580448c9 448 hasBareCr = "bare CR";
6e09ed7b 449 debugs(55, warnOnError, "WARNING: suspicious CR characters in HTTP header {" <<
81858ebc 450 getStringPrefix(field_start, field_end-field_start) << "}");
52d3f198 451
452 if (Config.onoff.relaxed_header_parser) {
f53969cc 453 char *p = (char *) this_line; /* XXX Warning! This destroys original header content and violates specifications somewhat */
52d3f198 454
a38ec4b1
FC
455 while ((p = (char *)memchr(p, '\r', field_end - p)) != NULL) {
456 *p = ' ';
457 ++p;
458 }
6d7bc97b
AJ
459 } else {
460 PROF_stop(HttpHeaderParse);
81ab22b6
FC
461 clean();
462 return 0;
6d7bc97b 463 }
52d3f198 464 }
465
466 if (this_line + 1 == field_end && this_line > field_start) {
6e09ed7b 467 debugs(55, warnOnError, "WARNING: Blank continuation line in HTTP header {" <<
81858ebc 468 getStringPrefix(header_start, hdrLen) << "}");
6d7bc97b 469 PROF_stop(HttpHeaderParse);
81ab22b6
FC
470 clean();
471 return 0;
52d3f198 472 }
473 } while (field_ptr < header_end && (*field_ptr == ' ' || *field_ptr == '\t'));
474
475 if (field_start == field_end) {
476 if (field_ptr < header_end) {
2f8abb64 477 debugs(55, warnOnError, "WARNING: unparsable HTTP header field near {" <<
81858ebc 478 getStringPrefix(field_start, hdrLen-(field_start-header_start)) << "}");
6d7bc97b 479 PROF_stop(HttpHeaderParse);
81ab22b6
FC
480 clean();
481 return 0;
52d3f198 482 }
483
f53969cc 484 break; /* terminating blank line */
52d3f198 485 }
62e76326 486
2358b975
AJ
487 const auto e = HttpHeaderEntry::parse(field_start, field_end, owner);
488 if (!e) {
2f8abb64 489 debugs(55, warnOnError, "WARNING: unparsable HTTP header field {" <<
81858ebc
AJ
490 getStringPrefix(field_start, field_end-field_start) << "}");
491 debugs(55, warnOnError, " in {" << getStringPrefix(header_start, hdrLen) << "}");
52d3f198 492
6d7bc97b 493 PROF_stop(HttpHeaderParse);
81ab22b6
FC
494 clean();
495 return 0;
47ac2ebe 496 }
62e76326 497
580448c9
AJ
498 if (lines > 1 || hasBareCr) {
499 const auto framingHeader = (e->id == Http::HdrType::CONTENT_LENGTH || e->id == Http::HdrType::TRANSFER_ENCODING);
500 if (framingHeader) {
501 if (!hasBareCr) // already warned about bare CRs
502 debugs(55, warnOnError, "WARNING: obs-fold in framing-sensitive " << e->name << ": " << e->value);
503 delete e;
504 PROF_stop(HttpHeaderParse);
505 clean();
506 return 0;
507 }
508 }
509
a1b9ec20
AR
510 if (e->id == Http::HdrType::CONTENT_LENGTH && !clen.checkField(e->value)) {
511 delete e;
b3123159 512
a1b9ec20
AR
513 if (Config.onoff.relaxed_header_parser)
514 continue; // clen has printed any necessary warnings
6e09ed7b 515
a1b9ec20
AR
516 PROF_stop(HttpHeaderParse);
517 clean();
518 return 0;
52d3f198 519 }
62e76326 520
a9925b40 521 addEntry(e);
cb69b4c7 522 }
62e76326 523
a1b9ec20
AR
524 if (clen.headerWideProblem) {
525 debugs(55, warnOnError, "WARNING: " << clen.headerWideProblem <<
526 " Content-Length field values in" <<
527 Raw("header", header_start, hdrLen));
528 }
529
f6dd87e9 530 String rawTe;
4f1c93a7 531 if (clen.prohibitedAndIgnored()) {
f6dd87e9
AJ
532 // prohibitedAndIgnored() includes trailer header blocks
533 // being parsed as a case to forbid/ignore these headers.
534
4f1c93a7
EB
535 // RFC 7230 section 3.3.2: A server MUST NOT send a Content-Length
536 // header field in any response with a status code of 1xx (Informational)
537 // or 204 (No Content). And RFC 7230 3.3.3#1 tells recipients to ignore
538 // such Content-Lengths.
539 if (delById(Http::HdrType::CONTENT_LENGTH))
540 debugs(55, 3, "Content-Length is " << clen.prohibitedAndIgnored());
f6dd87e9
AJ
541
542 // RFC 7230 section 3.3.1 has the same criteria forbid Transfer-Encoding
543 if (delById(Http::HdrType::TRANSFER_ENCODING)) {
544 debugs(55, 3, "Transfer-Encoding is " << clen.prohibitedAndIgnored());
545 teUnsupported_ = true;
546 }
547
548 } else if (getByIdIfPresent(Http::HdrType::TRANSFER_ENCODING, &rawTe)) {
c3d0ba0c 549 // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
a1b9ec20 550 // RFC 7230 section 3.3.3 #3: Transfer-Encoding overwrites Content-Length
789217a2 551 delById(Http::HdrType::CONTENT_LENGTH);
a1b9ec20 552 // and clen state becomes irrelevant
f6dd87e9
AJ
553
554 if (rawTe == "chunked") {
555 ; // leave header present for chunked() method
556 } else if (rawTe == "identity") { // deprecated. no coding
557 delById(Http::HdrType::TRANSFER_ENCODING);
558 } else {
559 // This also rejects multiple encodings until we support them properly.
560 debugs(55, warnOnError, "WARNING: unsupported Transfer-Encoding used by client: " << rawTe);
561 teUnsupported_ = true;
562 }
563
a1b9ec20
AR
564 } else if (clen.sawBad) {
565 // ensure our callers do not accidentally see bad Content-Length values
3e42b356 566 delById(Http::HdrType::CONTENT_LENGTH);
a1b9ec20
AR
567 conflictingContentLength_ = true; // TODO: Rename to badContentLength_.
568 } else if (clen.needsSanitizing) {
569 // RFC 7230 section 3.3.2: MUST either reject or ... [sanitize];
570 // ensure our callers see a clean Content-Length value or none at all
571 delById(Http::HdrType::CONTENT_LENGTH);
572 if (clen.sawGood) {
573 putInt64(Http::HdrType::CONTENT_LENGTH, clen.value);
574 debugs(55, 5, "sanitized Content-Length to be " << clen.value);
575 }
c3d0ba0c
AR
576 }
577
3a9cf2d5 578 PROF_stop(HttpHeaderParse);
f53969cc 579 return 1; /* even if no fields where found, it is a valid header */
cb69b4c7 580}
581
99edd1c3 582/* packs all the entries using supplied packer */
cb69b4c7 583void
17802cf1 584HttpHeader::packInto(Packable * p, bool mask_sensitive_info) const
cb69b4c7 585{
9bea1d5b 586 HttpHeaderPos pos = HttpHeaderInitPos;
587 const HttpHeaderEntry *e;
a9925b40 588 assert(p);
3cc0f4e7
AR
589 debugs(55, 7, this << " into " << p <<
590 (mask_sensitive_info ? " while masking" : ""));
9bea1d5b 591 /* pack all entries one by one */
d8f6c79c
FC
592 while ((e = getEntry(&pos))) {
593 if (!mask_sensitive_info) {
594 e->packInto(p);
595 continue;
596 }
3cc0f4e7
AR
597
598 bool maskThisEntry = false;
d8f6c79c 599 switch (e->id) {
789217a2
FC
600 case Http::HdrType::AUTHORIZATION:
601 case Http::HdrType::PROXY_AUTHORIZATION:
3cc0f4e7 602 maskThisEntry = true;
d3823ea7 603 break;
3cc0f4e7 604
789217a2
FC
605 case Http::HdrType::FTP_ARGUMENTS:
606 if (const HttpHeaderEntry *cmd = findEntry(Http::HdrType::FTP_COMMAND))
3cc0f4e7
AR
607 maskThisEntry = (cmd->value == "PASS");
608 break;
609
d3823ea7 610 default:
d3823ea7 611 break;
d8f6c79c 612 }
3cc0f4e7 613 if (maskThisEntry) {
d5f18517 614 p->append(e->name.rawContent(), e->name.length());
785b508d 615 p->append(": ** NOT DISPLAYED **\r\n", 23);
3cc0f4e7
AR
616 } else {
617 e->packInto(p);
618 }
619
d8f6c79c 620 }
35b88ed2 621 /* Pack in the "special" entries */
622
623 /* Cache-Control */
cb69b4c7 624}
625
626/* returns next valid entry */
99edd1c3 627HttpHeaderEntry *
a9925b40 628HttpHeader::getEntry(HttpHeaderPos * pos) const
cb69b4c7 629{
a9925b40 630 assert(pos);
c0abe2ec 631 assert(*pos >= HttpHeaderInitPos && *pos < static_cast<ssize_t>(entries.size()));
62e76326 632
c0abe2ec 633 for (++(*pos); *pos < static_cast<ssize_t>(entries.size()); ++(*pos)) {
4c9eadc2 634 if (entries[*pos])
c0abe2ec 635 return static_cast<HttpHeaderEntry*>(entries[*pos]);
cb69b4c7 636 }
62e76326 637
9bea1d5b 638 return NULL;
cb69b4c7 639}
640
641/*
26ac0430 642 * returns a pointer to a specified entry if any
d8b249ef 643 * note that we return one entry so it does not make much sense to ask for
644 * "list" headers
cb69b4c7 645 */
de336bbe 646HttpHeaderEntry *
789217a2 647HttpHeader::findEntry(Http::HdrType id) const
cb69b4c7 648{
8cccf1f8 649 assert(any_registered_header(id));
81ab22b6 650 assert(!Http::HeaderLookupTable.lookup(id).list);
9bea1d5b 651
652 /* check mask first */
62e76326 653
a9925b40 654 if (!CBIT_TEST(mask, id))
62e76326 655 return NULL;
656
9bea1d5b 657 /* looks like we must have it, do linear search */
81ab22b6
FC
658 for (auto e : entries) {
659 if (e && e->id == id)
62e76326 660 return e;
cb69b4c7 661 }
62e76326 662
9bea1d5b 663 /* hm.. we thought it was there, but it was not found */
81ab22b6
FC
664 assert(false);
665 return nullptr; /* not reached */
cb69b4c7 666}
667
a622fff0 668/*
669 * same as httpHeaderFindEntry
670 */
a9925b40 671HttpHeaderEntry *
789217a2 672HttpHeader::findLastEntry(Http::HdrType id) const
a622fff0 673{
8cccf1f8 674 assert(any_registered_header(id));
81ab22b6 675 assert(!Http::HeaderLookupTable.lookup(id).list);
9bea1d5b 676
677 /* check mask first */
a9925b40 678 if (!CBIT_TEST(mask, id))
62e76326 679 return NULL;
680
81ab22b6
FC
681 for (auto e = entries.rbegin(); e != entries.rend(); ++e) {
682 if (*e && (*e)->id == id)
683 return *e;
a622fff0 684 }
62e76326 685
81ab22b6
FC
686 /* hm.. we thought it was there, but it was not found */
687 assert(false);
688 return nullptr; /* not reached */
a622fff0 689}
690
2ac76861 691int
d5f18517 692HttpHeader::delByName(const SBuf &name)
cb69b4c7 693{
9bea1d5b 694 int count = 0;
695 HttpHeaderPos pos = HttpHeaderInitPos;
f53969cc 696 httpHeaderMaskInit(&mask, 0); /* temporal inconsistency */
efc26e8e 697 debugs(55, 9, "deleting '" << name << "' fields in hdr " << this);
62e76326 698
d5f18517 699 while (const HttpHeaderEntry *e = getEntry(&pos)) {
30abd221 700 if (!e->name.caseCmp(name))
ba9fb01d 701 delAt(pos, count);
702 else
a9925b40 703 CBIT_SET(mask, e->id);
cb69b4c7 704 }
62e76326 705
9bea1d5b 706 return count;
cb69b4c7 707}
708
2246b732 709/* deletes all entries with a given id, returns the #entries deleted */
710int
789217a2 711HttpHeader::delById(Http::HdrType id)
d8b249ef 712{
efc26e8e 713 debugs(55, 8, this << " del-by-id " << id);
8cccf1f8 714 assert(any_registered_header(id));
62e76326 715
a9925b40 716 if (!CBIT_TEST(mask, id))
62e76326 717 return 0;
718
5134d3a4
AR
719 int count = 0;
720
721 HttpHeaderPos pos = HttpHeaderInitPos;
722 while (HttpHeaderEntry *e = getEntry(&pos)) {
723 if (e->id == id)
724 delAt(pos, count); // deletes e
725 }
62e76326 726
a9925b40 727 CBIT_CLR(mask, id);
9bea1d5b 728 assert(count);
729 return count;
d8b249ef 730}
d8b249ef 731
cb69b4c7 732/*
733 * deletes an entry at pos and leaves a gap; leaving a gap makes it
734 * possible to iterate(search) and delete fields at the same time
ba9fb01d 735 * NOTE: Does not update the header mask. Caller must follow up with
736 * a call to refreshMask() if headers_deleted was incremented.
cb69b4c7 737 */
2246b732 738void
ba9fb01d 739HttpHeader::delAt(HttpHeaderPos pos, int &headers_deleted)
cb69b4c7 740{
9bea1d5b 741 HttpHeaderEntry *e;
c0abe2ec
FC
742 assert(pos >= HttpHeaderInitPos && pos < static_cast<ssize_t>(entries.size()));
743 e = static_cast<HttpHeaderEntry*>(entries[pos]);
4c9eadc2 744 entries[pos] = NULL;
9bea1d5b 745 /* decrement header length, allow for ": " and crlf */
d5f18517 746 len -= e->name.length() + 2 + e->value.size() + 2;
a9925b40 747 assert(len >= 0);
eede25e7 748 delete e;
ba9fb01d 749 ++headers_deleted;
cb69b4c7 750}
751
394499bd 752/*
753 * Compacts the header storage
754 */
755void
756HttpHeader::compact()
757{
6e1410f9 758 // TODO: optimize removal, or possibly make it so that's not needed.
81ab22b6
FC
759 entries.erase( std::remove(entries.begin(), entries.end(), nullptr),
760 entries.end());
394499bd 761}
762
ba9fb01d 763/*
764 * Refreshes the header mask. Required after delAt() calls.
765 */
766void
767HttpHeader::refreshMask()
768{
769 httpHeaderMaskInit(&mask, 0);
770 debugs(55, 7, "refreshing the mask in hdr " << this);
81ab22b6
FC
771 for (auto e : entries) {
772 if (e)
773 CBIT_SET(mask, e->id);
ba9fb01d 774 }
775}
99edd1c3 776
62e76326 777/* appends an entry;
eede25e7 778 * does not call e->clone() so one should not reuse "*e"
cb69b4c7 779 */
99edd1c3 780void
a9925b40 781HttpHeader::addEntry(HttpHeaderEntry * e)
cb69b4c7 782{
a9925b40 783 assert(e);
1da82544 784 assert(any_HdrType_enum_value(e->id));
d5f18517 785 assert(e->name.length());
9bea1d5b 786
4c9eadc2 787 debugs(55, 7, this << " adding entry: " << e->id << " at " << entries.size());
62e76326 788
789217a2 789 if (e->id != Http::HdrType::BAD_HDR) {
73b2aa87
FC
790 if (CBIT_TEST(mask, e->id)) {
791 ++ headerStatsTable[e->id].repCount;
792 } else {
793 CBIT_SET(mask, e->id);
794 }
b88a45ed 795 }
62e76326 796
a9925b40 797 entries.push_back(e);
62e76326 798
9bea1d5b 799 /* increment header length, allow for ": " and crlf */
d5f18517 800 len += e->name.length() + 2 + e->value.size() + 2;
cb69b4c7 801}
802
bbe58ab5 803/* inserts an entry;
eede25e7 804 * does not call e->clone() so one should not reuse "*e"
bbe58ab5 805 */
806void
a9925b40 807HttpHeader::insertEntry(HttpHeaderEntry * e)
bbe58ab5 808{
a9925b40 809 assert(e);
229eb96e 810 assert(any_valid_header(e->id));
bbe58ab5 811
f9514ae8 812 debugs(55, 7, this << " adding entry: " << e->id << " at " << entries.size());
bbe58ab5 813
789217a2 814 // Http::HdrType::BAD_HDR is filtered out by assert_any_valid_header
b88a45ed 815 if (CBIT_TEST(mask, e->id)) {
73b2aa87 816 ++ headerStatsTable[e->id].repCount;
b88a45ed 817 } else {
a9925b40 818 CBIT_SET(mask, e->id);
b88a45ed 819 }
bbe58ab5 820
42865d25 821 entries.insert(entries.begin(),e);
bbe58ab5 822
823 /* increment header length, allow for ": " and crlf */
d5f18517 824 len += e->name.length() + 2 + e->value.size() + 2;
bbe58ab5 825}
826
35b88ed2 827bool
789217a2 828HttpHeader::getList(Http::HdrType id, String *s) const
35b88ed2 829{
efc26e8e 830 debugs(55, 9, this << " joining for id " << id);
35b88ed2 831 /* only fields from ListHeaders array can be "listed" */
81ab22b6 832 assert(Http::HeaderLookupTable.lookup(id).list);
35b88ed2 833
834 if (!CBIT_TEST(mask, id))
835 return false;
836
81ab22b6
FC
837 for (auto e: entries) {
838 if (e && e->id == id)
9d7a89a5 839 strListAdd(s, e->value.termedBuf(), ',');
35b88ed2 840 }
841
842 /*
843 * note: we might get an empty (size==0) string if there was an "empty"
844 * header. This results in an empty length String, which may have a NULL
845 * buffer.
846 */
847 /* temporary warning: remove it? (Is it useful for diagnostics ?) */
848 if (!s->size())
81ab22b6 849 debugs(55, 3, "empty list header: " << Http::HeaderLookupTable.lookup(id).name << "(" << id << ")");
35b88ed2 850 else
851 debugs(55, 6, this << ": joined for id " << id << ": " << s);
852
853 return true;
854}
855
99edd1c3 856/* return a list of entries with the same id separated by ',' and ws */
30abd221 857String
789217a2 858HttpHeader::getList(Http::HdrType id) const
cb69b4c7 859{
9bea1d5b 860 HttpHeaderEntry *e;
861 HttpHeaderPos pos = HttpHeaderInitPos;
efc26e8e 862 debugs(55, 9, this << "joining for id " << id);
9bea1d5b 863 /* only fields from ListHeaders array can be "listed" */
81ab22b6 864 assert(Http::HeaderLookupTable.lookup(id).list);
62e76326 865
a9925b40 866 if (!CBIT_TEST(mask, id))
30abd221 867 return String();
650c4b88 868
30abd221 869 String s;
62e76326 870
a9925b40 871 while ((e = getEntry(&pos))) {
62e76326 872 if (e->id == id)
9d7a89a5 873 strListAdd(&s, e->value.termedBuf(), ',');
d35b9a94 874 }
62e76326 875
9bea1d5b 876 /*
948078e3 877 * note: we might get an empty (size==0) string if there was an "empty"
878 * header. This results in an empty length String, which may have a NULL
879 * buffer.
9bea1d5b 880 */
948078e3 881 /* temporary warning: remove it? (Is it useful for diagnostics ?) */
528b2c61 882 if (!s.size())
81ab22b6 883 debugs(55, 3, "empty list header: " << Http::HeaderLookupTable.lookup(id).name << "(" << id << ")");
948078e3 884 else
885 debugs(55, 6, this << ": joined for id " << id << ": " << s);
62e76326 886
9bea1d5b 887 return s;
cb69b4c7 888}
889
f66a9ef4 890/* return a string or list of entries with the same id separated by ',' and ws */
30abd221 891String
789217a2 892HttpHeader::getStrOrList(Http::HdrType id) const
f66a9ef4 893{
c7327fa0 894 HttpHeaderEntry *e;
9bea1d5b 895
81ab22b6 896 if (Http::HeaderLookupTable.lookup(id).list)
a9925b40 897 return getList(id);
62e76326 898
a9925b40 899 if ((e = findEntry(id)))
62e76326 900 return e->value;
901
30abd221 902 return String();
f66a9ef4 903}
904
905/*
b2c44718 906 * Returns the value of the specified header and/or an undefined String.
f66a9ef4 907 */
30abd221 908String
a9925b40 909HttpHeader::getByName(const char *name) const
81ab22b6
FC
910{
911 String result;
912 // ignore presence: return undefined string if an empty header is present
f29d429e 913 (void)hasNamed(name, strlen(name), &result);
81ab22b6
FC
914 return result;
915}
916
917String
918HttpHeader::getByName(const SBuf &name) const
b2c44718
AR
919{
920 String result;
921 // ignore presence: return undefined string if an empty header is present
f29d429e 922 (void)hasNamed(name, &result);
b2c44718
AR
923 return result;
924}
925
81ab22b6
FC
926String
927HttpHeader::getById(Http::HdrType id) const
928{
929 String result;
f29d429e 930 (void)getByIdIfPresent(id, &result);
81ab22b6
FC
931 return result;
932}
933
934bool
f29d429e 935HttpHeader::hasNamed(const SBuf &s, String *result) const
81ab22b6 936{
f29d429e 937 return hasNamed(s.rawContent(), s.length(), result);
81ab22b6
FC
938}
939
940bool
f29d429e 941HttpHeader::getByIdIfPresent(Http::HdrType id, String *result) const
81ab22b6
FC
942{
943 if (id == Http::HdrType::BAD_HDR)
944 return false;
945 if (!has(id))
946 return false;
f29d429e
EB
947 if (result)
948 *result = getStrOrList(id);
81ab22b6
FC
949 return true;
950}
951
b2c44718 952bool
d5f18517 953HttpHeader::hasNamed(const char *name, unsigned int namelen, String *result) const
f66a9ef4 954{
789217a2 955 Http::HdrType id;
9bea1d5b 956 HttpHeaderPos pos = HttpHeaderInitPos;
957 HttpHeaderEntry *e;
9bea1d5b 958
9bea1d5b 959 assert(name);
960
961 /* First try the quick path */
81ab22b6 962 id = Http::HeaderLookupTable.lookup(name,namelen).id;
62e76326 963
789217a2 964 if (id != Http::HdrType::BAD_HDR) {
81ab22b6
FC
965 if (getByIdIfPresent(id, result))
966 return true;
b2c44718 967 }
650c4b88 968
9bea1d5b 969 /* Sorry, an unknown header name. Do linear search */
b2c44718 970 bool found = false;
a9925b40 971 while ((e = getEntry(&pos))) {
d5f18517 972 if (e->id == Http::HdrType::OTHER && e->name.length() == namelen && e->name.caseCmp(name, namelen) == 0) {
b2c44718 973 found = true;
f29d429e
EB
974 if (!result)
975 break;
976 strListAdd(result, e->value.termedBuf(), ',');
62e76326 977 }
f66a9ef4 978 }
62e76326 979
b2c44718 980 return found;
f66a9ef4 981}
cb69b4c7 982
14b463aa 983/*
372fdfbf 984 * Returns a the value of the specified list member, if any.
14b463aa 985 */
36c774f7 986SBuf
a9925b40 987HttpHeader::getByNameListMember(const char *name, const char *member, const char separator) const
14b463aa 988{
14b463aa 989 assert(name);
36c774f7
EB
990 const auto header = getByName(name);
991 return ::getListMember(header, member, separator);
14b463aa 992}
993
994/*
995 * returns a the value of the specified list member, if any.
996 */
36c774f7 997SBuf
789217a2 998HttpHeader::getListMember(Http::HdrType id, const char *member, const char separator) const
14b463aa 999{
8cccf1f8 1000 assert(any_registered_header(id));
36c774f7
EB
1001 const auto header = getStrOrList(id);
1002 return ::getListMember(header, member, separator);
14b463aa 1003}
1004
cb69b4c7 1005/* test if a field is present */
2ac76861 1006int
789217a2 1007HttpHeader::has(Http::HdrType id) const
cb69b4c7 1008{
8cccf1f8 1009 assert(any_registered_header(id));
efc26e8e 1010 debugs(55, 9, this << " lookup for " << id);
a9925b40 1011 return CBIT_TEST(mask, id);
cb69b4c7 1012}
1013
90be6ff5
EB
1014void
1015HttpHeader::addVia(const AnyP::ProtocolVersion &ver, const HttpHeader *from)
1016{
1017 // TODO: do not add Via header for messages where Squid itself
1018 // generated the message (i.e., Downloader or ESI) there should be no Via header added at all.
1019
1020 if (Config.onoff.via) {
1021 SBuf buf;
1022 // RFC 7230 section 5.7.1.: protocol-name is omitted when
1023 // the received protocol is HTTP.
1024 if (ver.protocol > AnyP::PROTO_NONE && ver.protocol < AnyP::PROTO_UNKNOWN &&
1025 ver.protocol != AnyP::PROTO_HTTP && ver.protocol != AnyP::PROTO_HTTPS)
1026 buf.appendf("%s/", AnyP::ProtocolType_str[ver.protocol]);
1027 buf.appendf("%d.%d %s", ver.major, ver.minor, ThisCache);
1028 const HttpHeader *hdr = from ? from : this;
1029 SBuf strVia = StringToSBuf(hdr->getList(Http::HdrType::VIA));
1030 if (!strVia.isEmpty())
1031 strVia.append(", ", 2);
1032 strVia.append(buf);
1033 // XXX: putStr() still suffers from String size limits
1034 Must(strVia.length() < String::SizeMaxXXX());
1035 delById(Http::HdrType::VIA);
1036 putStr(Http::HdrType::VIA, strVia.c_str());
1037 }
1038}
1039
cb69b4c7 1040void
789217a2 1041HttpHeader::putInt(Http::HdrType id, int number)
cb69b4c7 1042{
8cccf1f8 1043 assert(any_registered_header(id));
81ab22b6 1044 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt); /* must be of an appropriate type */
9bea1d5b 1045 assert(number >= 0);
d5f18517 1046 addEntry(new HttpHeaderEntry(id, SBuf(), xitoa(number)));
cb69b4c7 1047}
1048
47f6e231 1049void
789217a2 1050HttpHeader::putInt64(Http::HdrType id, int64_t number)
47f6e231 1051{
8cccf1f8 1052 assert(any_registered_header(id));
81ab22b6 1053 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt64); /* must be of an appropriate type */
47f6e231 1054 assert(number >= 0);
d5f18517 1055 addEntry(new HttpHeaderEntry(id, SBuf(), xint64toa(number)));
47f6e231 1056}
1057
cb69b4c7 1058void
789217a2 1059HttpHeader::putTime(Http::HdrType id, time_t htime)
cb69b4c7 1060{
8cccf1f8 1061 assert(any_registered_header(id));
81ab22b6 1062 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123); /* must be of an appropriate type */
a1d6870f 1063 assert(htime >= 0);
d5f18517 1064 addEntry(new HttpHeaderEntry(id, SBuf(), mkrfc1123(htime)));
cb69b4c7 1065}
2ac76861 1066
cb69b4c7 1067void
789217a2 1068HttpHeader::putStr(Http::HdrType id, const char *str)
cb69b4c7 1069{
8cccf1f8 1070 assert(any_registered_header(id));
81ab22b6 1071 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr); /* must be of an appropriate type */
9bea1d5b 1072 assert(str);
d5f18517 1073 addEntry(new HttpHeaderEntry(id, SBuf(), str));
cb69b4c7 1074}
1075
63259c34 1076void
a9925b40 1077HttpHeader::putAuth(const char *auth_scheme, const char *realm)
63259c34 1078{
a9925b40 1079 assert(auth_scheme && realm);
789217a2 1080 httpHeaderPutStrf(this, Http::HdrType::WWW_AUTHENTICATE, "%s realm=\"%s\"", auth_scheme, realm);
63259c34 1081}
1082
99edd1c3 1083void
a9925b40 1084HttpHeader::putCc(const HttpHdrCc * cc)
99edd1c3 1085{
a9925b40 1086 assert(cc);
9bea1d5b 1087 /* remove old directives if any */
789217a2 1088 delById(Http::HdrType::CACHE_CONTROL);
9bea1d5b 1089 /* pack into mb */
10201568 1090 MemBuf mb;
2fe7eff9 1091 mb.init();
10201568 1092 cc->packInto(&mb);
9bea1d5b 1093 /* put */
d5f18517 1094 addEntry(new HttpHeaderEntry(Http::HdrType::CACHE_CONTROL, SBuf(), mb.buf));
9bea1d5b 1095 /* cleanup */
2fe7eff9 1096 mb.clean();
99edd1c3 1097}
1098
d192d11f 1099void
a9925b40 1100HttpHeader::putContRange(const HttpHdrContRange * cr)
d192d11f 1101{
a9925b40 1102 assert(cr);
9bea1d5b 1103 /* remove old directives if any */
789217a2 1104 delById(Http::HdrType::CONTENT_RANGE);
9bea1d5b 1105 /* pack into mb */
10201568 1106 MemBuf mb;
2fe7eff9 1107 mb.init();
10201568 1108 httpHdrContRangePackInto(cr, &mb);
9bea1d5b 1109 /* put */
d5f18517 1110 addEntry(new HttpHeaderEntry(Http::HdrType::CONTENT_RANGE, SBuf(), mb.buf));
9bea1d5b 1111 /* cleanup */
2fe7eff9 1112 mb.clean();
d192d11f 1113}
1114
1115void
a9925b40 1116HttpHeader::putRange(const HttpHdrRange * range)
d192d11f 1117{
a9925b40 1118 assert(range);
9bea1d5b 1119 /* remove old directives if any */
789217a2 1120 delById(Http::HdrType::RANGE);
9bea1d5b 1121 /* pack into mb */
10201568 1122 MemBuf mb;
2fe7eff9 1123 mb.init();
10201568 1124 range->packInto(&mb);
9bea1d5b 1125 /* put */
d5f18517 1126 addEntry(new HttpHeaderEntry(Http::HdrType::RANGE, SBuf(), mb.buf));
9bea1d5b 1127 /* cleanup */
2fe7eff9 1128 mb.clean();
d192d11f 1129}
1130
43ae1d95 1131void
a9925b40 1132HttpHeader::putSc(HttpHdrSc *sc)
43ae1d95 1133{
a9925b40 1134 assert(sc);
43ae1d95 1135 /* remove old directives if any */
789217a2 1136 delById(Http::HdrType::SURROGATE_CONTROL);
43ae1d95 1137 /* pack into mb */
10201568 1138 MemBuf mb;
2fe7eff9 1139 mb.init();
10201568 1140 sc->packInto(&mb);
43ae1d95 1141 /* put */
d5f18517 1142 addEntry(new HttpHeaderEntry(Http::HdrType::SURROGATE_CONTROL, SBuf(), mb.buf));
43ae1d95 1143 /* cleanup */
2fe7eff9 1144 mb.clean();
43ae1d95 1145}
1146
bcfba8bd
AR
1147void
1148HttpHeader::putWarning(const int code, const char *const text)
1149{
1150 char buf[512];
1151 snprintf(buf, sizeof(buf), "%i %s \"%s\"", code, visible_appname_string, text);
789217a2 1152 putStr(Http::HdrType::WARNING, buf);
bcfba8bd
AR
1153}
1154
cb69b4c7 1155/* add extension header (these fields are not parsed/analyzed/joined, etc.) */
1156void
a9925b40 1157HttpHeader::putExt(const char *name, const char *value)
cb69b4c7 1158{
9bea1d5b 1159 assert(name && value);
efc26e8e 1160 debugs(55, 8, this << " adds ext entry " << name << " : " << value);
d5f18517 1161 addEntry(new HttpHeaderEntry(Http::HdrType::OTHER, SBuf(name), value));
528b2c61 1162}
1163
1164int
789217a2 1165HttpHeader::getInt(Http::HdrType id) const
528b2c61 1166{
8cccf1f8 1167 assert(any_registered_header(id));
81ab22b6 1168 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt); /* must be of an appropriate type */
528b2c61 1169 HttpHeaderEntry *e;
62e76326 1170
a9925b40 1171 if ((e = findEntry(id)))
eede25e7 1172 return e->getInt();
62e76326 1173
528b2c61 1174 return -1;
7c525cc2 1175}
1176
47f6e231 1177int64_t
789217a2 1178HttpHeader::getInt64(Http::HdrType id) const
47f6e231 1179{
8cccf1f8 1180 assert(any_registered_header(id));
81ab22b6 1181 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt64); /* must be of an appropriate type */
47f6e231 1182 HttpHeaderEntry *e;
1183
1184 if ((e = findEntry(id)))
1185 return e->getInt64();
1186
1187 return -1;
1188}
1189
de336bbe 1190time_t
789217a2 1191HttpHeader::getTime(Http::HdrType id) const
cb69b4c7 1192{
9bea1d5b 1193 HttpHeaderEntry *e;
1194 time_t value = -1;
8cccf1f8 1195 assert(any_registered_header(id));
81ab22b6 1196 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123); /* must be of an appropriate type */
62e76326 1197
a9925b40 1198 if ((e = findEntry(id))) {
9d7a89a5 1199 value = parse_rfc1123(e->value.termedBuf());
62e76326 1200 httpHeaderNoteParsedEntry(e->id, e->value, value < 0);
d8b249ef 1201 }
62e76326 1202
9bea1d5b 1203 return value;
cb69b4c7 1204}
1205
99edd1c3 1206/* sync with httpHeaderGetLastStr */
de336bbe 1207const char *
789217a2 1208HttpHeader::getStr(Http::HdrType id) const
cb69b4c7 1209{
9bea1d5b 1210 HttpHeaderEntry *e;
8cccf1f8 1211 assert(any_registered_header(id));
81ab22b6 1212 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr); /* must be of an appropriate type */
62e76326 1213
a9925b40 1214 if ((e = findEntry(id))) {
81ab22b6 1215 httpHeaderNoteParsedEntry(e->id, e->value, false); /* no errors are possible */
9d7a89a5 1216 return e->value.termedBuf();
d8b249ef 1217 }
62e76326 1218
9bea1d5b 1219 return NULL;
cb69b4c7 1220}
1221
a622fff0 1222/* unusual */
1223const char *
789217a2 1224HttpHeader::getLastStr(Http::HdrType id) const
a622fff0 1225{
9bea1d5b 1226 HttpHeaderEntry *e;
8cccf1f8 1227 assert(any_registered_header(id));
81ab22b6 1228 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr); /* must be of an appropriate type */
62e76326 1229
a9925b40 1230 if ((e = findLastEntry(id))) {
81ab22b6 1231 httpHeaderNoteParsedEntry(e->id, e->value, false); /* no errors are possible */
9d7a89a5 1232 return e->value.termedBuf();
a622fff0 1233 }
62e76326 1234
9bea1d5b 1235 return NULL;
a622fff0 1236}
1237
7faf2bdb 1238HttpHdrCc *
a9925b40 1239HttpHeader::getCc() const
cb69b4c7 1240{
789217a2 1241 if (!CBIT_TEST(mask, Http::HdrType::CACHE_CONTROL))
62e76326 1242 return NULL;
35b88ed2 1243 PROF_start(HttpHeader_getCc);
62e76326 1244
c57b8f05 1245 String s;
789217a2 1246 getList(Http::HdrType::CACHE_CONTROL, &s);
62e76326 1247
c57b8f05 1248 HttpHdrCc *cc=new HttpHdrCc();
7ebe76de
FC
1249
1250 if (!cc->parse(s)) {
cf66f10a
FC
1251 delete cc;
1252 cc = NULL;
1253 }
62e76326 1254
95dc7ff4 1255 ++ HttpHeaderStats[owner].ccParsedCount;
62e76326 1256
9bea1d5b 1257 if (cc)
a9925b40 1258 httpHdrCcUpdateStats(cc, &HttpHeaderStats[owner].ccTypeDistr);
62e76326 1259
789217a2 1260 httpHeaderNoteParsedEntry(Http::HdrType::CACHE_CONTROL, s, !cc);
62e76326 1261
35b88ed2 1262 PROF_stop(HttpHeader_getCc);
62e76326 1263
9bea1d5b 1264 return cc;
cb69b4c7 1265}
1266
02922e76 1267HttpHdrRange *
a9925b40 1268HttpHeader::getRange() const
02922e76 1269{
9bea1d5b 1270 HttpHdrRange *r = NULL;
1271 HttpHeaderEntry *e;
1272 /* some clients will send "Request-Range" _and_ *matching* "Range"
1273 * who knows, some clients might send Request-Range only;
1274 * this "if" should work correctly in both cases;
1275 * hopefully no clients send mismatched headers! */
62e76326 1276
789217a2
FC
1277 if ((e = findEntry(Http::HdrType::RANGE)) ||
1278 (e = findEntry(Http::HdrType::REQUEST_RANGE))) {
62e76326 1279 r = HttpHdrRange::ParseCreate(&e->value);
1280 httpHeaderNoteParsedEntry(e->id, e->value, !r);
d192d11f 1281 }
62e76326 1282
9bea1d5b 1283 return r;
02922e76 1284}
1285
43ae1d95 1286HttpHdrSc *
a9925b40 1287HttpHeader::getSc() const
43ae1d95 1288{
789217a2 1289 if (!CBIT_TEST(mask, Http::HdrType::SURROGATE_CONTROL))
43ae1d95 1290 return NULL;
1291
30abd221 1292 String s;
26ac0430 1293
789217a2 1294 (void) getList(Http::HdrType::SURROGATE_CONTROL, &s);
43ae1d95 1295
45a58345 1296 HttpHdrSc *sc = httpHdrScParseCreate(s);
43ae1d95 1297
95dc7ff4 1298 ++ HttpHeaderStats[owner].ccParsedCount;
43ae1d95 1299
1300 if (sc)
45a58345 1301 sc->updateStats(&HttpHeaderStats[owner].scTypeDistr);
43ae1d95 1302
789217a2 1303 httpHeaderNoteParsedEntry(Http::HdrType::SURROGATE_CONTROL, s, !sc);
43ae1d95 1304
1305 return sc;
1306}
1307
d76fcfa7 1308HttpHdrContRange *
a9925b40 1309HttpHeader::getContRange() const
d76fcfa7 1310{
9bea1d5b 1311 HttpHdrContRange *cr = NULL;
1312 HttpHeaderEntry *e;
62e76326 1313
789217a2 1314 if ((e = findEntry(Http::HdrType::CONTENT_RANGE))) {
9d7a89a5 1315 cr = httpHdrContRangeParseCreate(e->value.termedBuf());
62e76326 1316 httpHeaderNoteParsedEntry(e->id, e->value, !cr);
d8b249ef 1317 }
62e76326 1318
9bea1d5b 1319 return cr;
cb69b4c7 1320}
1321
2582f64a
AJ
1322SBuf
1323HttpHeader::getAuthToken(Http::HdrType id, const char *auth_scheme) const
cb69b4c7 1324{
9bea1d5b 1325 const char *field;
1326 int l;
a9925b40 1327 assert(auth_scheme);
1328 field = getStr(id);
62e76326 1329
2582f64a 1330 static const SBuf nil;
f53969cc 1331 if (!field) /* no authorization field */
2582f64a 1332 return nil;
62e76326 1333
9bea1d5b 1334 l = strlen(auth_scheme);
62e76326 1335
f53969cc 1336 if (!l || strncasecmp(field, auth_scheme, l)) /* wrong scheme */
2582f64a 1337 return nil;
62e76326 1338
9bea1d5b 1339 field += l;
62e76326 1340
f53969cc 1341 if (!xisspace(*field)) /* wrong scheme */
2582f64a 1342 return nil;
62e76326 1343
9bea1d5b 1344 /* skip white space */
95dc7ff4 1345 for (; field && xisspace(*field); ++field);
62e76326 1346
f53969cc 1347 if (!*field) /* no authorization cookie */
2582f64a 1348 return nil;
62e76326 1349
2582f64a
AJ
1350 const auto fieldLen = strlen(field);
1351 SBuf result;
1352 char *decodedAuthToken = result.rawAppendStart(BASE64_DECODE_LENGTH(fieldLen));
aadbbd7d
AJ
1353 struct base64_decode_ctx ctx;
1354 base64_decode_init(&ctx);
1355 size_t decodedLen = 0;
2582f64a 1356 if (!base64_decode_update(&ctx, &decodedLen, reinterpret_cast<uint8_t*>(decodedAuthToken), fieldLen, field) ||
aadbbd7d 1357 !base64_decode_final(&ctx)) {
2582f64a 1358 return nil;
aadbbd7d 1359 }
2582f64a
AJ
1360 result.rawAppendFinish(decodedAuthToken, decodedLen);
1361 return result;
cb69b4c7 1362}
1363
a9771e51 1364ETag
789217a2 1365HttpHeader::getETag(Http::HdrType id) const
a9771e51 1366{
26ac0430 1367 ETag etag = {NULL, -1};
9bea1d5b 1368 HttpHeaderEntry *e;
81ab22b6 1369 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftETag); /* must be of an appropriate type */
62e76326 1370
a9925b40 1371 if ((e = findEntry(id)))
9d7a89a5 1372 etagParseInit(&etag, e->value.termedBuf());
62e76326 1373
9bea1d5b 1374 return etag;
a9771e51 1375}
1376
1377TimeOrTag
789217a2 1378HttpHeader::getTimeOrTag(Http::HdrType id) const
a9771e51 1379{
9bea1d5b 1380 TimeOrTag tot;
1381 HttpHeaderEntry *e;
81ab22b6 1382 assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123_or_ETag); /* must be of an appropriate type */
9bea1d5b 1383 memset(&tot, 0, sizeof(tot));
62e76326 1384
a9925b40 1385 if ((e = findEntry(id))) {
9d7a89a5 1386 const char *str = e->value.termedBuf();
62e76326 1387 /* try as an ETag */
1388
1389 if (etagParseInit(&tot.tag, str)) {
1390 tot.valid = tot.tag.str != NULL;
1391 tot.time = -1;
1392 } else {
1393 /* or maybe it is time? */
1394 tot.time = parse_rfc1123(str);
1395 tot.valid = tot.time >= 0;
1396 tot.tag.str = NULL;
1397 }
a9771e51 1398 }
62e76326 1399
f53969cc 1400 assert(tot.time < 0 || !tot.tag.str); /* paranoid */
9bea1d5b 1401 return tot;
a9771e51 1402}
1403
cb69b4c7 1404/*
1405 * HttpHeaderEntry
1406 */
1407
d5f18517 1408HttpHeaderEntry::HttpHeaderEntry(Http::HdrType anId, const SBuf &aName, const char *aValue)
cb69b4c7 1409{
1da82544 1410 assert(any_HdrType_enum_value(anId));
eede25e7 1411 id = anId;
62e76326 1412
789217a2 1413 if (id != Http::HdrType::OTHER)
81ab22b6 1414 name = Http::HeaderLookupTable.lookup(id).name;
9bea1d5b 1415 else
eede25e7 1416 name = aName;
62e76326 1417
eede25e7 1418 value = aValue;
62e76326 1419
1da82544 1420 if (id != Http::HdrType::BAD_HDR)
73b2aa87 1421 ++ headerStatsTable[id].aliveCount;
62e76326 1422
9d7a89a5 1423 debugs(55, 9, "created HttpHeaderEntry " << this << ": '" << name << " : " << value );
eede25e7 1424}
62e76326 1425
eede25e7 1426HttpHeaderEntry::~HttpHeaderEntry()
2ac76861 1427{
9d7a89a5 1428 debugs(55, 9, "destroying entry " << this << ": '" << name << ": " << value << "'");
62e76326 1429
1da82544
FC
1430 if (id != Http::HdrType::BAD_HDR) {
1431 assert(headerStatsTable[id].aliveCount);
1432 -- headerStatsTable[id].aliveCount;
1433 id = Http::HdrType::BAD_HDR; // it already is BAD_HDR, no sense in resetting it
1434 }
62e76326 1435
cb69b4c7 1436}
1437
eede25e7 1438/* parses and inits header entry, returns true/false */
cdce6c61 1439HttpHeaderEntry *
2358b975 1440HttpHeaderEntry::parse(const char *field_start, const char *field_end, const http_hdr_owner_type msgType)
cb69b4c7 1441{
9bea1d5b 1442 /* note: name_start == field_start */
52d3f198 1443 const char *name_end = (const char *)memchr(field_start, ':', field_end - field_start);
26ac0430 1444 int name_len = name_end ? name_end - field_start :0;
f53969cc 1445 const char *value_start = field_start + name_len + 1; /* skip ':' */
9bea1d5b 1446 /* note: value_end == field_end */
1447
95dc7ff4 1448 ++ HeaderEntryParsedCount;
9bea1d5b 1449
1450 /* do we have a valid field name within this field? */
62e76326 1451
9bea1d5b 1452 if (!name_len || name_end > field_end)
cdce6c61 1453 return NULL;
62e76326 1454
67746eea 1455 if (name_len > 65534) {
1456 /* String must be LESS THAN 64K and it adds a terminating NULL */
2358b975
AJ
1457 // TODO: update this to show proper name_len in Raw markup, but not print all that
1458 debugs(55, 2, "ignoring huge header field (" << Raw("field_start", field_start, 100) << "...)");
cdce6c61 1459 return NULL;
25acfb53 1460 }
62e76326 1461
2358b975
AJ
1462 /*
1463 * RFC 7230 section 3.2.4:
1464 * "No whitespace is allowed between the header field-name and colon.
1465 * ...
1466 * A server MUST reject any received request message that contains
1467 * whitespace between a header field-name and colon with a response code
1468 * of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1469 * response message before forwarding the message downstream."
1470 */
1471 if (xisspace(field_start[name_len - 1])) {
1472
1473 if (msgType == hoRequest)
1474 return nullptr;
1475
1476 // for now, also let relaxed parser remove this BWS from any non-HTTP messages
1477 const bool stripWhitespace = (msgType == hoReply) ||
1478 Config.onoff.relaxed_header_parser;
1479 if (!stripWhitespace)
1480 return nullptr; // reject if we cannot strip
1481
bf8fe701 1482 debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
81858ebc 1483 "NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end-field_start) << "'");
52d3f198 1484
1485 while (name_len > 0 && xisspace(field_start[name_len - 1]))
5e263176 1486 --name_len;
52d3f198 1487
2358b975
AJ
1488 if (!name_len) {
1489 debugs(55, 2, "found header with only whitespace for name");
cdce6c61 1490 return NULL;
2358b975 1491 }
52d3f198 1492 }
1493
b453677b
AJ
1494 /* RFC 7230 section 3.2:
1495 *
1496 * header-field = field-name ":" OWS field-value OWS
1497 * field-name = token
1498 * token = 1*TCHAR
1499 */
1500 for (const char *pos = field_start; pos < (field_start+name_len); ++pos) {
1501 if (!CharacterSet::TCHAR[*pos]) {
1502 debugs(55, 2, "found header with invalid characters in " <<
1503 Raw("field-name", field_start, min(name_len,100)) << "...");
1504 return nullptr;
1505 }
1506 }
1507
9bea1d5b 1508 /* now we know we can parse it */
62e76326 1509
81858ebc 1510 debugs(55, 9, "parsing HttpHeaderEntry: near '" << getStringPrefix(field_start, field_end-field_start) << "'");
62e76326 1511
9bea1d5b 1512 /* is it a "known" field? */
81ab22b6 1513 Http::HdrType id = Http::HeaderLookupTable.lookup(field_start,name_len).id;
92991271 1514 debugs(55, 9, "got hdr-id=" << id);
cdce6c61 1515
d5f18517 1516 SBuf theName;
cdce6c61 1517
30abd221 1518 String value;
62e76326 1519
789217a2
FC
1520 if (id == Http::HdrType::BAD_HDR)
1521 id = Http::HdrType::OTHER;
62e76326 1522
9bea1d5b 1523 /* set field name */
789217a2 1524 if (id == Http::HdrType::OTHER)
d5f18517 1525 theName.append(field_start, name_len);
9bea1d5b 1526 else
d5f18517 1527 theName = Http::HeaderLookupTable.lookup(id).name;
62e76326 1528
9bea1d5b 1529 /* trim field value */
1530 while (value_start < field_end && xisspace(*value_start))
95dc7ff4 1531 ++value_start;
62e76326 1532
52d3f198 1533 while (value_start < field_end && xisspace(field_end[-1]))
5e263176 1534 --field_end;
52d3f198 1535
67746eea 1536 if (field_end - value_start > 65534) {
1537 /* String must be LESS THAN 64K and it adds a terminating NULL */
2358b975 1538 debugs(55, 2, "WARNING: found '" << theName << "' header of " << (field_end - value_start) << " bytes");
cdce6c61 1539 return NULL;
25acfb53 1540 }
62e76326 1541
9bea1d5b 1542 /* set field value */
2fe0439c 1543 value.assign(value_start, field_end - value_start);
62e76326 1544
789217a2 1545 if (id != Http::HdrType::BAD_HDR)
73b2aa87 1546 ++ headerStatsTable[id].seenCount;
62e76326 1547
d5f18517 1548 debugs(55, 9, "parsed HttpHeaderEntry: '" << theName << ": " << value << "'");
62e76326 1549
d5f18517 1550 return new HttpHeaderEntry(id, theName, value.termedBuf());
cb69b4c7 1551}
1552
99edd1c3 1553HttpHeaderEntry *
eede25e7 1554HttpHeaderEntry::clone() const
cb69b4c7 1555{
d5f18517 1556 return new HttpHeaderEntry(id, name, value.termedBuf());
cb69b4c7 1557}
1558
de336bbe 1559void
17802cf1 1560HttpHeaderEntry::packInto(Packable * p) const
cb69b4c7 1561{
eede25e7 1562 assert(p);
d5f18517 1563 p->append(name.rawContent(), name.length());
785b508d
AJ
1564 p->append(": ", 2);
1565 p->append(value.rawBuf(), value.size());
1566 p->append("\r\n", 2);
cb69b4c7 1567}
1568
eede25e7 1569int
1570HttpHeaderEntry::getInt() const
1571{
eede25e7 1572 int val = -1;
9d7a89a5 1573 int ok = httpHeaderParseInt(value.termedBuf(), &val);
81ab22b6 1574 httpHeaderNoteParsedEntry(id, value, ok == 0);
eede25e7 1575 /* XXX: Should we check ok - ie
1576 * return ok ? -1 : value;
1577 */
1578 return val;
1579}
1580
47f6e231 1581int64_t
1582HttpHeaderEntry::getInt64() const
1583{
47f6e231 1584 int64_t val = -1;
a1b9ec20 1585 const bool ok = httpHeaderParseOffset(value.termedBuf(), &val);
a7fea967 1586 httpHeaderNoteParsedEntry(id, value, !ok);
a1b9ec20 1587 return val; // remains -1 if !ok (XXX: bad method API)
47f6e231 1588}
1589
cb69b4c7 1590static void
81ab22b6 1591httpHeaderNoteParsedEntry(Http::HdrType id, String const &context, bool error)
cb69b4c7 1592{
789217a2 1593 if (id != Http::HdrType::BAD_HDR)
73b2aa87 1594 ++ headerStatsTable[id].parsCount;
62e76326 1595
9bea1d5b 1596 if (error) {
789217a2 1597 if (id != Http::HdrType::BAD_HDR)
73b2aa87 1598 ++ headerStatsTable[id].errCount;
81ab22b6 1599 debugs(55, 2, "cannot parse hdr field: '" << Http::HeaderLookupTable.lookup(id).name << ": " << context << "'");
cb69b4c7 1600 }
cb69b4c7 1601}
1602
cb69b4c7 1603/*
12cf1be2 1604 * Reports
cb69b4c7 1605 */
cb69b4c7 1606
fcd2d3ef 1607/* tmp variable used to pass stat info to dumpers */
f53969cc 1608extern const HttpHeaderStat *dump_stat; /* argh! */
fcd2d3ef 1609const HttpHeaderStat *dump_stat = NULL;
1610
59a09b98 1611void
ced8def3 1612httpHeaderFieldStatDumper(StoreEntry * sentry, int, double val, double, int count)
cb69b4c7 1613{
92991271 1614 const int id = static_cast<int>(val);
1da82544 1615 const bool valid_id = Http::any_valid_header(static_cast<Http::HdrType>(id));
81ab22b6 1616 const char *name = valid_id ? Http::HeaderLookupTable.lookup(static_cast<Http::HdrType>(id)).name : "INVALID";
9bea1d5b 1617 int visible = count > 0;
1618 /* for entries with zero count, list only those that belong to current type of message */
62e76326 1619
9bea1d5b 1620 if (!visible && valid_id && dump_stat->owner_mask)
62e76326 1621 visible = CBIT_TEST(*dump_stat->owner_mask, id);
1622
9bea1d5b 1623 if (visible)
62e76326 1624 storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
1625 id, name, count, xdiv(count, dump_stat->busyDestroyedCount));
cb69b4c7 1626}
1627
12cf1be2 1628static void
ced8def3 1629httpHeaderFldsPerHdrDumper(StoreEntry * sentry, int idx, double val, double, int count)
cb69b4c7 1630{
9bea1d5b 1631 if (count)
62e76326 1632 storeAppendPrintf(sentry, "%2d\t %5d\t %5d\t %6.2f\n",
1633 idx, (int) val, count,
1634 xpercent(count, dump_stat->destroyedCount));
cb69b4c7 1635}
1636
cb69b4c7 1637static void
9bea1d5b 1638httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e)
12cf1be2 1639{
73b2aa87
FC
1640 assert(hs);
1641 assert(e);
9bea1d5b 1642
1643 dump_stat = hs;
1644 storeAppendPrintf(e, "\nHeader Stats: %s\n", hs->label);
1645 storeAppendPrintf(e, "\nField type distribution\n");
1646 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
62e76326 1647 "id", "name", "count", "#/header");
96886986 1648 hs->fieldTypeDistr.dump(e, httpHeaderFieldStatDumper);
9bea1d5b 1649 storeAppendPrintf(e, "\nCache-control directives distribution\n");
1650 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
62e76326 1651 "id", "name", "count", "#/cc_field");
96886986 1652 hs->ccTypeDistr.dump(e, httpHdrCcStatDumper);
43ae1d95 1653 storeAppendPrintf(e, "\nSurrogate-control directives distribution\n");
1654 storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
1655 "id", "name", "count", "#/sc_field");
96886986 1656 hs->scTypeDistr.dump(e, httpHdrScStatDumper);
9bea1d5b 1657 storeAppendPrintf(e, "\nNumber of fields per header distribution\n");
1658 storeAppendPrintf(e, "%2s\t %-5s\t %5s\t %6s\n",
62e76326 1659 "id", "#flds", "count", "%total");
96886986 1660 hs->hdrUCountDistr.dump(e, httpHeaderFldsPerHdrDumper);
41b43e70 1661 storeAppendPrintf(e, "\n");
9bea1d5b 1662 dump_stat = NULL;
cb69b4c7 1663}
1664
12cf1be2 1665void
9bea1d5b 1666httpHeaderStoreReport(StoreEntry * e)
cb69b4c7 1667{
9bea1d5b 1668 int i;
9bea1d5b 1669 assert(e);
1670
1671 HttpHeaderStats[0].parsedCount =
62e76326 1672 HttpHeaderStats[hoRequest].parsedCount + HttpHeaderStats[hoReply].parsedCount;
9bea1d5b 1673 HttpHeaderStats[0].ccParsedCount =
62e76326 1674 HttpHeaderStats[hoRequest].ccParsedCount + HttpHeaderStats[hoReply].ccParsedCount;
9bea1d5b 1675 HttpHeaderStats[0].destroyedCount =
62e76326 1676 HttpHeaderStats[hoRequest].destroyedCount + HttpHeaderStats[hoReply].destroyedCount;
9bea1d5b 1677 HttpHeaderStats[0].busyDestroyedCount =
62e76326 1678 HttpHeaderStats[hoRequest].busyDestroyedCount + HttpHeaderStats[hoReply].busyDestroyedCount;
9bea1d5b 1679
95dc7ff4 1680 for (i = 1; i < HttpHeaderStatCount; ++i) {
62e76326 1681 httpHeaderStatDump(HttpHeaderStats + i, e);
12cf1be2 1682 }
62e76326 1683
9bea1d5b 1684 /* field stats for all messages */
1685 storeAppendPrintf(e, "\nHttp Fields Stats (replies and requests)\n");
62e76326 1686
077fe581 1687 storeAppendPrintf(e, "%2s\t %-25s\t %5s\t %6s\t %6s\n",
62e76326 1688 "id", "name", "#alive", "%err", "%repeat");
1689
fda6d769 1690 // scan heaaderTable and output
81ab22b6
FC
1691 for (auto h : WholeEnum<Http::HdrType>()) {
1692 auto stats = headerStatsTable[h];
fda6d769 1693 storeAppendPrintf(e, "%2d\t %-25s\t %5d\t %6.3f\t %6.3f\n",
81ab22b6
FC
1694 Http::HeaderLookupTable.lookup(h).id,
1695 Http::HeaderLookupTable.lookup(h).name,
92991271 1696 stats.aliveCount,
f7ad4af5
FC
1697 xpercent(stats.errCount, stats.parsCount),
1698 xpercent(stats.repCount, stats.seenCount));
fda6d769 1699 }
62e76326 1700
9bea1d5b 1701 storeAppendPrintf(e, "Headers Parsed: %d + %d = %d\n",
62e76326 1702 HttpHeaderStats[hoRequest].parsedCount,
1703 HttpHeaderStats[hoReply].parsedCount,
1704 HttpHeaderStats[0].parsedCount);
9bea1d5b 1705 storeAppendPrintf(e, "Hdr Fields Parsed: %d\n", HeaderEntryParsedCount);
cb69b4c7 1706}
97474590 1707
924f73bc 1708int
789217a2 1709HttpHeader::hasListMember(Http::HdrType id, const char *member, const char separator) const
924f73bc 1710{
1711 int result = 0;
1712 const char *pos = NULL;
1713 const char *item;
1714 int ilen;
1715 int mlen = strlen(member);
1716
8cccf1f8 1717 assert(any_registered_header(id));
924f73bc 1718
30abd221 1719 String header (getStrOrList(id));
924f73bc 1720
9d0cdbb9 1721 while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
380e7faf 1722 if (strncasecmp(item, member, mlen) == 0
9d0cdbb9 1723 && (item[mlen] == '=' || item[mlen] == separator || item[mlen] == ';' || item[mlen] == '\0')) {
1724 result = 1;
1725 break;
1726 }
1727 }
1728
1729 return result;
1730}
1731
1732int
a9925b40 1733HttpHeader::hasByNameListMember(const char *name, const char *member, const char separator) const
9d0cdbb9 1734{
1735 int result = 0;
1736 const char *pos = NULL;
1737 const char *item;
1738 int ilen;
1739 int mlen = strlen(member);
1740
9d0cdbb9 1741 assert(name);
1742
30abd221 1743 String header (getByName(name));
9d0cdbb9 1744
924f73bc 1745 while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
380e7faf 1746 if (strncasecmp(item, member, mlen) == 0
924f73bc 1747 && (item[mlen] == '=' || item[mlen] == separator || item[mlen] == ';' || item[mlen] == '\0')) {
1748 result = 1;
1749 break;
1750 }
1751 }
1752
1753 return result;
1754}
1755
2cdeea82 1756void
1757HttpHeader::removeHopByHopEntries()
1758{
1759 removeConnectionHeaderEntries();
26ac0430 1760
dcf3665b
AJ
1761 const HttpHeaderEntry *e;
1762 HttpHeaderPos pos = HttpHeaderInitPos;
1763 int headers_deleted = 0;
1764 while ((e = getEntry(&pos))) {
789217a2 1765 Http::HdrType id = e->id;
81ab22b6 1766 if (Http::HeaderLookupTable.lookup(id).hopbyhop) {
26ac0430
AJ
1767 delAt(pos, headers_deleted);
1768 CBIT_CLR(mask, id);
1769 }
dcf3665b 1770 }
2cdeea82 1771}
1772
924f73bc 1773void
1774HttpHeader::removeConnectionHeaderEntries()
1775{
789217a2 1776 if (has(Http::HdrType::CONNECTION)) {
924f73bc 1777 /* anything that matches Connection list member will be deleted */
30abd221 1778 String strConnection;
26ac0430 1779
789217a2 1780 (void) getList(Http::HdrType::CONNECTION, &strConnection);
924f73bc 1781 const HttpHeaderEntry *e;
1782 HttpHeaderPos pos = HttpHeaderInitPos;
1783 /*
1784 * think: on-average-best nesting of the two loops (hdrEntry
1785 * and strListItem) @?@
1786 */
1787 /*
1788 * maybe we should delete standard stuff ("keep-alive","close")
1789 * from strConnection first?
1790 */
1791
ba9fb01d 1792 int headers_deleted = 0;
a9925b40 1793 while ((e = getEntry(&pos))) {
d5f18517 1794 if (strListIsMember(&strConnection, e->name, ','))
ba9fb01d 1795 delAt(pos, headers_deleted);
924f73bc 1796 }
ba9fb01d 1797 if (headers_deleted)
1798 refreshMask();
924f73bc 1799 }
1800}
f53969cc 1801