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