/*
- * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
/* DEBUG: section 55 HTTP Header */
#include "squid.h"
+#include "base/Assure.h"
#include "base/CharacterSet.h"
#include "base/EnumIterator.h"
+#include "base/Raw.h"
#include "base64.h"
#include "globals.h"
#include "http/ContentLengthInterpreter.h"
#include "HttpHdrContRange.h"
#include "HttpHdrScTarget.h" // also includes HttpHdrSc.h
#include "HttpHeader.h"
-#include "HttpHeaderFieldInfo.h"
+#include "HttpHeaderFieldStat.h"
#include "HttpHeaderStat.h"
#include "HttpHeaderTools.h"
#include "MemBuf.h"
#include "mgr/Registration.h"
#include "mime_header.h"
-#include "profiler/Profiler.h"
-#include "rfc1123.h"
#include "sbuf/StringConvert.h"
#include "SquidConfig.h"
#include "StatHist.h"
#include "Store.h"
#include "StrList.h"
+#include "time/gadgets.h"
#include "TimeOrTag.h"
#include "util.h"
#include <algorithm>
+#include <array>
/* XXX: the whole set of API managing the entries vector should be rethought
* after the parse4r-ng effort is complete.
/* header accounting */
// NP: keep in sync with enum http_hdr_owner_type
-static HttpHeaderStat HttpHeaderStats[] = {
- HttpHeaderStat(/*hoNone*/ "all", NULL),
+static std::array<HttpHeaderStat, hoEnd> HttpHeaderStats = {{
+ HttpHeaderStat(/*hoNone*/ "all", nullptr),
#if USE_HTCP
- HttpHeaderStat(/*hoHtcpReply*/ "HTCP reply", &ReplyHeadersMask),
+ HttpHeaderStat(/*hoHtcpReply*/ "HTCP reply", &ReplyHeadersMask),
#endif
- HttpHeaderStat(/*hoRequest*/ "request", &RequestHeadersMask),
- HttpHeaderStat(/*hoReply*/ "reply", &ReplyHeadersMask)
+ HttpHeaderStat(/*hoRequest*/ "request", &RequestHeadersMask),
+ HttpHeaderStat(/*hoReply*/ "reply", &ReplyHeadersMask)
#if USE_OPENSSL
- /* hoErrorDetail */
+ , HttpHeaderStat(/*hoErrorDetail*/ "error detail templates", nullptr)
#endif
- /* hoEnd */
+ /* hoEnd */
+ }
};
-static int HttpHeaderStatCount = countof(HttpHeaderStats);
static int HeaderEntryParsedCount = 0;
CBIT_SET(ReplyHeadersMask,h);
}
- /* header stats initialized by class constructor */
- assert(HttpHeaderStatCount == hoReply + 1);
+ assert(HttpHeaderStats[0].label && "httpHeaderInitModule() called via main()");
+ assert(HttpHeaderStats[hoEnd-1].label && "HttpHeaderStats created with all elements");
/* init dependent modules */
- httpHdrCcInitModule();
httpHdrScInitModule();
httpHeaderRegisterWithCacheManager();
* HttpHeader Implementation
*/
-HttpHeader::HttpHeader() : owner (hoNone), len (0), conflictingContentLength_(false)
-{
- entries.reserve(32);
- httpHeaderMaskInit(&mask, 0);
-}
-
HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0), conflictingContentLength_(false)
{
assert(anOwner > hoNone && anOwner < hoEnd);
assert(owner > hoNone && owner < hoEnd);
debugs(55, 7, "cleaning hdr: " << this << " owner: " << owner);
- PROF_start(HttpHeaderClean);
-
if (owner <= hoReply) {
/*
* An unfortunate bug. The entries array is initialized
if (e == nullptr)
continue;
if (!Http::any_valid_header(e->id)) {
- debugs(55, DBG_CRITICAL, "BUG: invalid entry (" << e->id << "). Ignored.");
+ debugs(55, DBG_CRITICAL, "ERROR: Squid BUG: invalid entry (" << e->id << "). Ignored.");
} else {
if (owner <= hoReply)
HttpHeaderStats[owner].fieldTypeDistr.count(e->id);
len = 0;
conflictingContentLength_ = false;
teUnsupported_ = false;
- PROF_stop(HttpHeaderClean);
}
/* append entries (also see httpHeaderUpdate) */
bool
HttpHeader::needUpdate(HttpHeader const *fresh) const
{
- // our 1xx Warnings must be removed
- for (const auto e: entries) {
- // TODO: Move into HttpHeaderEntry::is1xxWarning() before official commit.
- if (e && e->id == Http::HdrType::WARNING && (e->getInt()/100 == 1))
- return true;
- }
-
for (const auto e: fresh->entries) {
if (!e || skipUpdateHeader(e->id))
continue;
return false;
}
-void
-HttpHeader::updateWarnings()
-{
- int count = 0;
- HttpHeaderPos pos = HttpHeaderInitPos;
-
- // RFC 7234, section 4.3.4: delete 1xx warnings and retain 2xx warnings
- while (HttpHeaderEntry *e = getEntry(&pos)) {
- if (e->id == Http::HdrType::WARNING && (e->getInt()/100 == 1) )
- delAt(pos, count);
- }
-}
-
bool
HttpHeader::skipUpdateHeader(const Http::HdrType id) const
{
return
- // RFC 7234, section 4.3.4: use header fields other than Warning
- (id == Http::HdrType::WARNING) ||
// TODO: Consider updating Vary headers after comparing the magnitude of
// the required changes (and/or cache losses) with compliance gains.
(id == Http::HdrType::VARY);
assert(fresh);
assert(this != fresh);
- updateWarnings();
-
const HttpHeaderEntry *e;
HttpHeaderPos pos = HttpHeaderInitPos;
const char *header_end = header_start + hdrLen; // XXX: remove
int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2);
- PROF_start(HttpHeaderParse);
-
assert(header_start && header_end);
debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, hdrLen));
++ HttpHeaderStats[owner].parsedCount;
if ((nulpos = (char*)memchr(header_start, '\0', hdrLen))) {
debugs(55, DBG_IMPORTANT, "WARNING: HTTP header contains NULL characters {" <<
getStringPrefix(header_start, nulpos-header_start) << "}\nNULL\n{" << getStringPrefix(nulpos+1, hdrLen-(nulpos-header_start)-1));
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (!field_ptr) {
// missing <LF>
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
debugs(55, DBG_IMPORTANT, "SECURITY WARNING: Rejecting HTTP request with a CR+ "
"header field to prevent request smuggling attacks: {" <<
getStringPrefix(header_start, hdrLen) << "}");
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (Config.onoff.relaxed_header_parser) {
char *p = (char *) this_line; /* XXX Warning! This destroys original header content and violates specifications somewhat */
- while ((p = (char *)memchr(p, '\r', field_end - p)) != NULL) {
+ while ((p = (char *)memchr(p, '\r', field_end - p)) != nullptr) {
*p = ' ';
++p;
}
} else {
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (this_line + 1 == field_end && this_line > field_start) {
debugs(55, warnOnError, "WARNING: Blank continuation line in HTTP header {" <<
getStringPrefix(header_start, hdrLen) << "}");
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (field_ptr < header_end) {
debugs(55, warnOnError, "WARNING: unparsable HTTP header field near {" <<
getStringPrefix(field_start, hdrLen-(field_start-header_start)) << "}");
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
getStringPrefix(field_start, field_end-field_start) << "}");
debugs(55, warnOnError, " in {" << getStringPrefix(header_start, hdrLen) << "}");
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (!hasBareCr) // already warned about bare CRs
debugs(55, warnOnError, "WARNING: obs-fold in framing-sensitive " << e->name << ": " << e->value);
delete e;
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (Config.onoff.relaxed_header_parser)
continue; // clen has printed any necessary warnings
- PROF_stop(HttpHeaderParse);
clean();
return 0;
}
if (delById(Http::HdrType::CONTENT_LENGTH))
debugs(55, 3, "Content-Length is " << clen.prohibitedAndIgnored());
- // RFC 7230 section 3.3.1 has the same criteria forbid Transfer-Encoding
- if (delById(Http::HdrType::TRANSFER_ENCODING)) {
+ // The same RFC 7230 3.3.3#1-based logic applies to Transfer-Encoding
+ // banned by RFC 7230 section 3.3.1.
+ if (delById(Http::HdrType::TRANSFER_ENCODING))
debugs(55, 3, "Transfer-Encoding is " << clen.prohibitedAndIgnored());
- teUnsupported_ = true;
- }
} else if (getByIdIfPresent(Http::HdrType::TRANSFER_ENCODING, &rawTe)) {
// RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
delById(Http::HdrType::CONTENT_LENGTH);
// and clen state becomes irrelevant
- if (rawTe == "chunked") {
+ if (rawTe.caseCmp("chunked") == 0) {
; // leave header present for chunked() method
- } else if (rawTe == "identity") { // deprecated. no coding
+ } else if (rawTe.caseCmp("identity") == 0) { // deprecated. no coding
delById(Http::HdrType::TRANSFER_ENCODING);
} else {
// This also rejects multiple encodings until we support them properly.
}
}
- PROF_stop(HttpHeaderParse);
return 1; /* even if no fields where found, it is a valid header */
}
return static_cast<HttpHeaderEntry*>(entries[*pos]);
}
- return NULL;
+ return nullptr;
}
/*
/* check mask first */
if (!CBIT_TEST(mask, id))
- return NULL;
+ return nullptr;
/* looks like we must have it, do linear search */
for (auto e : entries) {
/* check mask first */
if (!CBIT_TEST(mask, id))
- return NULL;
+ return nullptr;
for (auto e = entries.rbegin(); e != entries.rend(); ++e) {
if (*e && (*e)->id == id)
HttpHeaderEntry *e;
assert(pos >= HttpHeaderInitPos && pos < static_cast<ssize_t>(entries.size()));
e = static_cast<HttpHeaderEntry*>(entries[pos]);
- entries[pos] = NULL;
+ entries[pos] = nullptr;
/* decrement header length, allow for ": " and crlf */
len -= e->name.length() + 2 + e->value.size() + 2;
assert(len >= 0);
entries.push_back(e);
- /* increment header length, allow for ": " and crlf */
- len += e->name.length() + 2 + e->value.size() + 2;
-}
-
-/* inserts an entry;
- * does not call e->clone() so one should not reuse "*e"
- */
-void
-HttpHeader::insertEntry(HttpHeaderEntry * e)
-{
- assert(e);
- assert(any_valid_header(e->id));
-
- debugs(55, 7, this << " adding entry: " << e->id << " at " << entries.size());
-
- // Http::HdrType::BAD_HDR is filtered out by assert_any_valid_header
- if (CBIT_TEST(mask, e->id)) {
- ++ headerStatsTable[e->id].repCount;
- } else {
- CBIT_SET(mask, e->id);
- }
-
- entries.insert(entries.begin(),e);
-
- /* increment header length, allow for ": " and crlf */
- len += e->name.length() + 2 + e->value.size() + 2;
+ len += e->length();
}
bool
if (!strVia.isEmpty())
strVia.append(", ", 2);
strVia.append(buf);
- // XXX: putStr() still suffers from String size limits
- Must(strVia.length() < String::SizeMaxXXX());
- delById(Http::HdrType::VIA);
- putStr(Http::HdrType::VIA, strVia.c_str());
+ updateOrAddStr(Http::HdrType::VIA, strVia);
}
}
assert(any_registered_header(id));
assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123); /* must be of an appropriate type */
assert(htime >= 0);
- addEntry(new HttpHeaderEntry(id, SBuf(), mkrfc1123(htime)));
+ addEntry(new HttpHeaderEntry(id, SBuf(), Time::FormatRfc1123(htime)));
}
void
}
void
-HttpHeader::putCc(const HttpHdrCc * cc)
+HttpHeader::putCc(const HttpHdrCc &cc)
{
- assert(cc);
/* remove old directives if any */
delById(Http::HdrType::CACHE_CONTROL);
/* pack into mb */
MemBuf mb;
mb.init();
- cc->packInto(&mb);
+ cc.packInto(&mb);
/* put */
addEntry(new HttpHeaderEntry(Http::HdrType::CACHE_CONTROL, SBuf(), mb.buf));
/* cleanup */
mb.clean();
}
-void
-HttpHeader::putWarning(const int code, const char *const text)
-{
- char buf[512];
- snprintf(buf, sizeof(buf), "%i %s \"%s\"", code, visible_appname_string, text);
- putStr(Http::HdrType::WARNING, buf);
-}
-
/* add extension header (these fields are not parsed/analyzed/joined, etc.) */
void
HttpHeader::putExt(const char *name, const char *value)
addEntry(new HttpHeaderEntry(Http::HdrType::OTHER, SBuf(name), value));
}
+void
+HttpHeader::updateOrAddStr(const Http::HdrType id, const SBuf &newValue)
+{
+ assert(any_registered_header(id));
+ assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr);
+
+ // XXX: HttpHeaderEntry::value suffers from String size limits
+ Assure(newValue.length() < String::SizeMaxXXX());
+
+ if (!CBIT_TEST(mask, id)) {
+ auto newValueCopy = newValue; // until HttpHeaderEntry::value becomes SBuf
+ addEntry(new HttpHeaderEntry(id, SBuf(), newValueCopy.c_str()));
+ return;
+ }
+
+ auto foundSameName = false;
+ for (auto &e: entries) {
+ if (!e || e->id != id)
+ continue;
+
+ if (foundSameName) {
+ // get rid of this repeated same-name entry
+ delete e;
+ e = nullptr;
+ continue;
+ }
+
+ if (newValue.cmp(e->value.termedBuf()) != 0)
+ e->value.assign(newValue.rawContent(), newValue.plength());
+
+ foundSameName = true;
+ // continue to delete any repeated same-name entries
+ }
+ assert(foundSameName);
+ debugs(55, 5, "synced: " << Http::HeaderLookupTable.lookup(id).name << ": " << newValue);
+}
+
int
HttpHeader::getInt(Http::HdrType id) const
{
assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123); /* must be of an appropriate type */
if ((e = findEntry(id))) {
- value = parse_rfc1123(e->value.termedBuf());
+ value = Time::ParseRfc1123(e->value.termedBuf());
httpHeaderNoteParsedEntry(e->id, e->value, value < 0);
}
return e->value.termedBuf();
}
- return NULL;
+ return nullptr;
}
/* unusual */
return e->value.termedBuf();
}
- return NULL;
+ return nullptr;
}
HttpHdrCc *
HttpHeader::getCc() const
{
if (!CBIT_TEST(mask, Http::HdrType::CACHE_CONTROL))
- return NULL;
- PROF_start(HttpHeader_getCc);
+ return nullptr;
String s;
getList(Http::HdrType::CACHE_CONTROL, &s);
if (!cc->parse(s)) {
delete cc;
- cc = NULL;
+ cc = nullptr;
}
++ HttpHeaderStats[owner].ccParsedCount;
httpHeaderNoteParsedEntry(Http::HdrType::CACHE_CONTROL, s, !cc);
- PROF_stop(HttpHeader_getCc);
-
return cc;
}
HttpHdrRange *
HttpHeader::getRange() const
{
- HttpHdrRange *r = NULL;
+ HttpHdrRange *r = nullptr;
HttpHeaderEntry *e;
/* some clients will send "Request-Range" _and_ *matching* "Range"
* who knows, some clients might send Request-Range only;
HttpHeader::getSc() const
{
if (!CBIT_TEST(mask, Http::HdrType::SURROGATE_CONTROL))
- return NULL;
+ return nullptr;
String s;
HttpHdrContRange *
HttpHeader::getContRange() const
{
- HttpHdrContRange *cr = NULL;
+ HttpHdrContRange *cr = nullptr;
HttpHeaderEntry *e;
if ((e = findEntry(Http::HdrType::CONTENT_RANGE))) {
ETag
HttpHeader::getETag(Http::HdrType id) const
{
- ETag etag = {NULL, -1};
+ ETag etag = {nullptr, -1};
HttpHeaderEntry *e;
assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftETag); /* must be of an appropriate type */
/* try as an ETag */
if (etagParseInit(&tot.tag, str)) {
- tot.valid = tot.tag.str != NULL;
+ tot.valid = tot.tag.str != nullptr;
tot.time = -1;
} else {
/* or maybe it is time? */
- tot.time = parse_rfc1123(str);
+ tot.time = Time::ParseRfc1123(str);
tot.valid = tot.time >= 0;
- tot.tag.str = NULL;
+ tot.tag.str = nullptr;
}
}
/* do we have a valid field name within this field? */
if (!name_len || name_end > field_end)
- return NULL;
+ return nullptr;
if (name_len > 65534) {
/* String must be LESS THAN 64K and it adds a terminating NULL */
// TODO: update this to show proper name_len in Raw markup, but not print all that
debugs(55, 2, "ignoring huge header field (" << Raw("field_start", field_start, 100) << "...)");
- return NULL;
+ return nullptr;
}
/*
return nullptr; // reject if we cannot strip
debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
- "NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end-field_start) << "'");
+ "WARNING: Whitespace after header name in '" << getStringPrefix(field_start, field_end-field_start) << "'");
while (name_len > 0 && xisspace(field_start[name_len - 1]))
--name_len;
if (!name_len) {
debugs(55, 2, "found header with only whitespace for name");
- return NULL;
+ return nullptr;
}
}
if (field_end - value_start > 65534) {
/* String must be LESS THAN 64K and it adds a terminating NULL */
debugs(55, 2, "WARNING: found '" << theName << "' header of " << (field_end - value_start) << " bytes");
- return NULL;
+ return nullptr;
}
/* set field value */
/* tmp variable used to pass stat info to dumpers */
extern const HttpHeaderStat *dump_stat; /* argh! */
-const HttpHeaderStat *dump_stat = NULL;
+const HttpHeaderStat *dump_stat = nullptr;
-void
+static void
httpHeaderFieldStatDumper(StoreEntry * sentry, int, double val, double, int count)
{
const int id = static_cast<int>(val);
assert(hs);
assert(e);
+ if (!hs->owner_mask)
+ return; // these HttpHeaderStat objects were not meant to be dumped here
+
dump_stat = hs;
storeAppendPrintf(e, "\nHeader Stats: %s\n", hs->label);
storeAppendPrintf(e, "\nField type distribution\n");
"id", "#flds", "count", "%total");
hs->hdrUCountDistr.dump(e, httpHeaderFldsPerHdrDumper);
storeAppendPrintf(e, "\n");
- dump_stat = NULL;
+ dump_stat = nullptr;
}
void
httpHeaderStoreReport(StoreEntry * e)
{
- int i;
assert(e);
HttpHeaderStats[0].parsedCount =
HttpHeaderStats[0].busyDestroyedCount =
HttpHeaderStats[hoRequest].busyDestroyedCount + HttpHeaderStats[hoReply].busyDestroyedCount;
- for (i = 1; i < HttpHeaderStatCount; ++i) {
- httpHeaderStatDump(HttpHeaderStats + i, e);
- }
+ for (const auto &stats: HttpHeaderStats)
+ httpHeaderStatDump(&stats, e);
/* field stats for all messages */
storeAppendPrintf(e, "\nHttp Fields Stats (replies and requests)\n");
HttpHeader::hasListMember(Http::HdrType id, const char *member, const char separator) const
{
int result = 0;
- const char *pos = NULL;
+ const char *pos = nullptr;
const char *item;
int ilen;
int mlen = strlen(member);
HttpHeader::hasByNameListMember(const char *name, const char *member, const char separator) const
{
int result = 0;
- const char *pos = NULL;
+ const char *pos = nullptr;
const char *item;
int ilen;
int mlen = strlen(member);