]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpHeader.cc
NoNewGlobals for HttpHdrCc:ccLookupTable (#1750)
[thirdparty/squid.git] / src / HttpHeader.cc
index bdf556cb0145aabc706019102f1c4af2c0c6002f..a251328c3dfac126f66c20b082d6def4e7df25b6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 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.
@@ -9,29 +9,34 @@
 /* DEBUG: section 55    HTTP Header */
 
 #include "squid.h"
-#include "base/LookupTable.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 "HttpHdrCc.h"
 #include "HttpHdrContRange.h"
-#include "HttpHdrSc.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 "profiler/Profiler.h"
-#include "rfc1123.h"
+#include "mime_header.h"
+#include "sbuf/StringConvert.h"
 #include "SquidConfig.h"
-#include "SquidString.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.
  * local constants and vars
  */
 
-/* TODO:
- * DONE 1. shift HDR_BAD_HDR to end of enum
- * DONE 2. shift headers data array to http/RegistredHeaders.cc
- * DONE 3. creatign LookupTable object from teh enum and array
- * (with HDR_BAD_HDR as invalid value)
- * DONE 4. replacing httpHeaderIdByName() uses with the lookup table
- * 5. merge HDR_BAD_HDR and HDR_ENUM_END into one thing
- * DONE 6. replacing httpHeaderNameById with direct array lookups
- * 7. being looking at the other arrays removal
- */
-
-
-LookupTable<http_hdr_type, HeaderTableRecord> headerLookupTable(HDR_BAD_HDR, headerTable);
-std::vector<HttpHeaderFieldStat> headerStatsTable(HDR_OTHER+1);
-
-/*
- * headers with field values defined as #(values) in HTTP/1.1
- * Headers that are currently not recognized, are commented out.
- */
-static HttpHeaderMask ListHeadersMask;  /* set run-time using  ListHeadersArr */
-static http_hdr_type ListHeadersArr[] = {
-    HDR_ACCEPT,
-    HDR_ACCEPT_CHARSET,
-    HDR_ACCEPT_ENCODING,
-    HDR_ACCEPT_LANGUAGE,
-    HDR_ACCEPT_RANGES,
-    HDR_ALLOW,
-    HDR_CACHE_CONTROL,
-    HDR_CONTENT_ENCODING,
-    HDR_CONTENT_LANGUAGE,
-    HDR_CONNECTION,
-    HDR_EXPECT,
-    HDR_IF_MATCH,
-    HDR_IF_NONE_MATCH,
-    HDR_KEY,
-    HDR_LINK,
-    HDR_PRAGMA,
-    HDR_PROXY_CONNECTION,
-    HDR_PROXY_SUPPORT,
-    HDR_TRANSFER_ENCODING,
-    HDR_UPGRADE,
-    HDR_VARY,
-    HDR_VIA,
-    HDR_WARNING,
-    HDR_WWW_AUTHENTICATE,
-    HDR_AUTHENTICATION_INFO,
-    HDR_PROXY_AUTHENTICATION_INFO,
-    /* HDR_TE, HDR_TRAILER */
-#if X_ACCELERATOR_VARY
-    HDR_X_ACCELERATOR_VARY,
-#endif
-#if USE_ADAPTATION
-    HDR_X_NEXT_SERVICES,
-#endif
-    HDR_SURROGATE_CAPABILITY,
-    HDR_SURROGATE_CONTROL,
-    HDR_FORWARDED,
-    HDR_X_FORWARDED_FOR
-};
+// statistics counters for headers. clients must not allow Http::HdrType::BAD_HDR to be counted
+std::vector<HttpHeaderFieldStat> headerStatsTable(Http::HdrType::enumEnd_);
 
-/* general-headers */
-static http_hdr_type GeneralHeadersArr[] = {
-    HDR_CACHE_CONTROL,
-    HDR_CONNECTION,
-    HDR_DATE,
-    HDR_FORWARDED,
-    HDR_X_FORWARDED_FOR,
-    HDR_MIME_VERSION,
-    HDR_PRAGMA,
-    HDR_PROXY_CONNECTION,
-    HDR_TRANSFER_ENCODING,
-    HDR_UPGRADE,
-    /* HDR_TRAILER, */
-    HDR_VIA,
-};
-
-/* entity-headers */
-static http_hdr_type EntityHeadersArr[] = {
-    HDR_ALLOW,
-    HDR_CONTENT_BASE,
-    HDR_CONTENT_ENCODING,
-    HDR_CONTENT_LANGUAGE,
-    HDR_CONTENT_LENGTH,
-    HDR_CONTENT_LOCATION,
-    HDR_CONTENT_MD5,
-    HDR_CONTENT_RANGE,
-    HDR_CONTENT_TYPE,
-    HDR_ETAG,
-    HDR_EXPIRES,
-    HDR_LAST_MODIFIED,
-    HDR_LINK,
-    HDR_OTHER
-};
-
-/* request-only headers */
+/* request-only headers. Used for cachemgr */
 static HttpHeaderMask RequestHeadersMask;   /* set run-time using RequestHeaders */
-static http_hdr_type RequestHeadersArr[] = {
-    HDR_ACCEPT,
-    HDR_ACCEPT_CHARSET,
-    HDR_ACCEPT_ENCODING,
-    HDR_ACCEPT_LANGUAGE,
-    HDR_AUTHORIZATION,
-    HDR_EXPECT,
-    HDR_FROM,
-    HDR_HOST,
-    HDR_HTTP2_SETTINGS,
-    HDR_IF_MATCH,
-    HDR_IF_MODIFIED_SINCE,
-    HDR_IF_NONE_MATCH,
-    HDR_IF_RANGE,
-    HDR_IF_UNMODIFIED_SINCE,
-    HDR_MAX_FORWARDS,
-    HDR_ORIGIN,
-    HDR_PROXY_AUTHORIZATION,
-    HDR_RANGE,
-    HDR_REFERER,
-    HDR_REQUEST_RANGE,
-    HDR_TE,
-    HDR_USER_AGENT,
-    HDR_SURROGATE_CAPABILITY
-};
 
-/* reply-only headers */
+/* reply-only headers. Used for cachemgr */
 static HttpHeaderMask ReplyHeadersMask;     /* set run-time using ReplyHeaders */
-static http_hdr_type ReplyHeadersArr[] = {
-    HDR_ACCEPT_ENCODING,
-    HDR_ACCEPT_RANGES,
-    HDR_AGE,
-    HDR_KEY,
-    HDR_LOCATION,
-    HDR_PROXY_AUTHENTICATE,
-    HDR_PUBLIC,
-    HDR_RETRY_AFTER,
-    HDR_SERVER,
-    HDR_SET_COOKIE,
-    HDR_SET_COOKIE2,
-    HDR_VARY,
-    HDR_WARNING,
-    HDR_WWW_AUTHENTICATE,
-    HDR_X_CACHE,
-    HDR_X_CACHE_LOOKUP,
-    HDR_X_REQUEST_URI,
-#if X_ACCELERATOR_VARY
-    HDR_X_ACCELERATOR_VARY,
-#endif
-#if USE_ADAPTATION
-    HDR_X_NEXT_SERVICES,
-#endif
-    HDR_X_SQUID_ERROR,
-    HDR_SURROGATE_CONTROL
-};
-
-/* hop-by-hop headers */
-static HttpHeaderMask HopByHopHeadersMask;
-static http_hdr_type HopByHopHeadersArr[] = {
-    HDR_ALTERNATE_PROTOCOL,
-    HDR_CONNECTION,
-    HDR_HTTP2_SETTINGS,
-    HDR_KEEP_ALIVE,
-    /*HDR_PROXY_AUTHENTICATE, // removal handled specially for peer login */
-    HDR_PROXY_AUTHORIZATION,
-    HDR_TE,
-    HDR_TRAILER,
-    HDR_TRANSFER_ENCODING,
-    HDR_UPGRADE,
-    HDR_PROXY_CONNECTION
-};
 
 /* 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;
 
@@ -251,12 +97,11 @@ static int HeaderEntryParsedCount = 0;
  */
 
 class StoreEntry;
-#define assert_eid(id) assert((id) >= 0 && (id) < HDR_ENUM_END)
-
-static void httpHeaderNoteParsedEntry(http_hdr_type id, String const &value, int error);
 
+// update parse statistics for header id; if error is true also account
+// for errors and write to debug log what happened
+static void httpHeaderNoteParsedEntry(Http::HdrType id, String const &value, bool error);
 static void httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e);
-
 /** store report about current header usage and other stats */
 static void httpHeaderStoreReport(StoreEntry * e);
 
@@ -276,68 +121,43 @@ void
 httpHeaderInitModule(void)
 {
     /* check that we have enough space for masks */
-    assert(8 * sizeof(HttpHeaderMask) >= HDR_ENUM_END);
-
-    // check invariant: for each index in headerTable, (int)headerTable[index] = index
-    for (int i = 0; headerTable[i].name; ++i)
-        assert(headerTable[i].id == i);
-
-    // use headerLookupTable in place of Headers
-
-    /* create masks */
-    httpHeaderMaskInit(&ListHeadersMask, 0);
-    httpHeaderCalcMask(&ListHeadersMask, ListHeadersArr, countof(ListHeadersArr));
-
-    httpHeaderMaskInit(&ReplyHeadersMask, 0);
-    httpHeaderCalcMask(&ReplyHeadersMask, ReplyHeadersArr, countof(ReplyHeadersArr));
-    httpHeaderCalcMask(&ReplyHeadersMask, GeneralHeadersArr, countof(GeneralHeadersArr));
-    httpHeaderCalcMask(&ReplyHeadersMask, EntityHeadersArr, countof(EntityHeadersArr));
-
-    httpHeaderMaskInit(&RequestHeadersMask, 0);
-    httpHeaderCalcMask(&RequestHeadersMask, RequestHeadersArr, countof(RequestHeadersArr));
-    httpHeaderCalcMask(&RequestHeadersMask, GeneralHeadersArr, countof(GeneralHeadersArr));
-    httpHeaderCalcMask(&RequestHeadersMask, EntityHeadersArr, countof(EntityHeadersArr));
-
-    httpHeaderMaskInit(&HopByHopHeadersMask, 0);
-    httpHeaderCalcMask(&HopByHopHeadersMask, HopByHopHeadersArr, countof(HopByHopHeadersArr));
+    assert(8 * sizeof(HttpHeaderMask) >= Http::HdrType::enumEnd_);
+
+    // masks are needed for stats page still
+    for (auto h : WholeEnum<Http::HdrType>()) {
+        if (Http::HeaderLookupTable.lookup(h).request)
+            CBIT_SET(RequestHeadersMask,h);
+        if (Http::HeaderLookupTable.lookup(h).reply)
+            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();
 }
 
-void
-httpHeaderCleanModule(void)
-{
-    httpHdrCcCleanModule();
-    httpHdrScCleanModule();
-}
-
 /*
  * HttpHeader Implementation
  */
 
-HttpHeader::HttpHeader() : owner (hoNone), len (0)
-{
-    httpHeaderMaskInit(&mask, 0);
-}
-
-HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0)
+HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0), conflictingContentLength_(false)
 {
     assert(anOwner > hoNone && anOwner < hoEnd);
     debugs(55, 7, "init-ing hdr: " << this << " owner: " << owner);
+    entries.reserve(32);
     httpHeaderMaskInit(&mask, 0);
 }
 
-HttpHeader::HttpHeader(const HttpHeader &other): owner(other.owner), len(other.len)
+// XXX: Delete as unused, expensive, and violating copy semantics by skipping Warnings
+HttpHeader::HttpHeader(const HttpHeader &other): owner(other.owner), len(other.len), conflictingContentLength_(false)
 {
+    entries.reserve(other.entries.capacity());
     httpHeaderMaskInit(&mask, 0);
-    update(&other, NULL); // will update the mask as well
+    update(&other); // will update the mask as well
 }
 
 HttpHeader::~HttpHeader()
@@ -345,6 +165,7 @@ HttpHeader::~HttpHeader()
     clean();
 }
 
+// XXX: Delete as unused, expensive, and violating assignment semantics by skipping Warnings
 HttpHeader &
 HttpHeader::operator =(const HttpHeader &other)
 {
@@ -352,8 +173,10 @@ HttpHeader::operator =(const HttpHeader &other)
         // we do not really care, but the caller probably does
         assert(owner == other.owner);
         clean();
-        update(&other, NULL); // will update the mask as well
+        update(&other); // will update the mask as well
         len = other.len;
+        conflictingContentLength_ = other.conflictingContentLength_;
+        teUnsupported_ = other.teUnsupported_;
     }
     return *this;
 }
@@ -365,8 +188,6 @@ HttpHeader::clean()
     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
@@ -386,12 +207,11 @@ HttpHeader::clean()
         HttpHeaderStats[owner].busyDestroyedCount += entries.size() > 0;
     } // if (owner <= hoReply)
 
-    for (std::vector<HttpHeaderEntry *>::iterator i = entries.begin(); i != entries.end(); ++i) {
-        HttpHeaderEntry *e = *i;
-        if (e == NULL)
+    for (HttpHeaderEntry *e : entries) {
+        if (e == nullptr)
             continue;
-        if (e->id < 0 || e->id >= HDR_ENUM_END) {
-            debugs(55, DBG_CRITICAL, "BUG: invalid entry (" << e->id << "). Ignored.");
+        if (!Http::any_valid_header(e->id)) {
+            debugs(55, DBG_CRITICAL, "ERROR: Squid BUG: invalid entry (" << e->id << "). Ignored.");
         } else {
             if (owner <= hoReply)
                 HttpHeaderStats[owner].fieldTypeDistr.count(e->id);
@@ -402,83 +222,136 @@ HttpHeader::clean()
     entries.clear();
     httpHeaderMaskInit(&mask, 0);
     len = 0;
-    PROF_stop(HttpHeaderClean);
+    conflictingContentLength_ = false;
+    teUnsupported_ = false;
 }
 
 /* append entries (also see httpHeaderUpdate) */
 void
 HttpHeader::append(const HttpHeader * src)
 {
-    const HttpHeaderEntry *e;
-    HttpHeaderPos pos = HttpHeaderInitPos;
     assert(src);
     assert(src != this);
     debugs(55, 7, "appending hdr: " << this << " += " << src);
 
-    while ((e = src->getEntry(&pos))) {
-        addEntry(e->clone());
+    for (auto e : src->entries) {
+        if (e)
+            addEntry(e->clone());
     }
 }
 
-/* use fresh entries to replace old ones */
-void
-httpHeaderUpdate(HttpHeader * old, const HttpHeader * fresh, const HttpHeaderMask * denied_mask)
+bool
+HttpHeader::needUpdate(HttpHeader const *fresh) const
+{
+    for (const auto e: fresh->entries) {
+        if (!e || skipUpdateHeader(e->id))
+            continue;
+        String value;
+        if (!hasNamed(e->name, &value) ||
+                (value != fresh->getByName(e->name)))
+            return true;
+    }
+    return false;
+}
+
+bool
+HttpHeader::skipUpdateHeader(const Http::HdrType id) const
 {
-    assert (old);
-    old->update (fresh, denied_mask);
+    return
+        // TODO: Consider updating Vary headers after comparing the magnitude of
+        // the required changes (and/or cache losses) with compliance gains.
+        (id == Http::HdrType::VARY);
 }
 
 void
-HttpHeader::update (HttpHeader const *fresh, HttpHeaderMask const *denied_mask)
+HttpHeader::update(HttpHeader const *fresh)
 {
-    const HttpHeaderEntry *e;
-    HttpHeaderPos pos = HttpHeaderInitPos;
     assert(fresh);
     assert(this != fresh);
 
+    const HttpHeaderEntry *e;
+    HttpHeaderPos pos = HttpHeaderInitPos;
+
     while ((e = fresh->getEntry(&pos))) {
-        /* deny bad guys (ok to check for HDR_OTHER) here */
+        /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
 
-        if (denied_mask && CBIT_TEST(*denied_mask, e->id))
+        if (skipUpdateHeader(e->id))
             continue;
 
-        if (e->id != HDR_OTHER)
+        if (e->id != Http::HdrType::OTHER)
             delById(e->id);
         else
-            delByName(e->name.termedBuf());
+            delByName(e->name);
     }
 
     pos = HttpHeaderInitPos;
     while ((e = fresh->getEntry(&pos))) {
-        /* deny bad guys (ok to check for HDR_OTHER) here */
+        /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
 
-        if (denied_mask && CBIT_TEST(*denied_mask, e->id))
+        if (skipUpdateHeader(e->id))
             continue;
 
-        debugs(55, 7, "Updating header '" << headerTable[e->id].name << "' in cached entry");
+        debugs(55, 7, "Updating header '" << Http::HeaderLookupTable.lookup(e->id).name << "' in cached entry");
 
         addEntry(e->clone());
     }
 }
 
-/* just handy in parsing: resets and returns false */
-int
-HttpHeader::reset()
+bool
+HttpHeader::Isolate(const char **parse_start, size_t l, const char **blk_start, const char **blk_end)
 {
-    clean();
-    return 0;
+    /*
+     * parse_start points to the first line of HTTP message *headers*,
+     * not including the request or status lines
+     */
+    const size_t end = headersEnd(*parse_start, l);
+
+    if (end) {
+        *blk_start = *parse_start;
+        *blk_end = *parse_start + end - 1;
+        assert(**blk_end == '\n');
+        // Point blk_end to the first character after the last header field.
+        // In other words, blk_end should point to the CR?LF header terminator.
+        if (end > 1 && *(*blk_end - 1) == '\r')
+            --(*blk_end);
+        *parse_start += end;
+    }
+    return end;
+}
+
+int
+HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz, Http::ContentLengthInterpreter &clen)
+{
+    const char *parse_start = buf;
+    const char *blk_start, *blk_end;
+    hdr_sz = 0;
+
+    if (!Isolate(&parse_start, buf_len, &blk_start, &blk_end)) {
+        // XXX: do not parse non-isolated headers even if the connection is closed.
+        // Treat unterminated headers as "partial headers" framing errors.
+        if (!atEnd)
+            return 0;
+        blk_start = parse_start;
+        blk_end = blk_start + strlen(blk_start);
+    }
+
+    if (parse(blk_start, blk_end - blk_start, clen)) {
+        hdr_sz = parse_start - buf;
+        return 1;
+    }
+    return -1;
 }
 
+// XXX: callers treat this return as boolean.
+// XXX: A better mechanism is needed to signal different types of error.
+//      lexicon, syntax, semantics, validation, access policy - are all (ab)using 'return 0'
 int
-HttpHeader::parse(const char *header_start, size_t hdrLen)
+HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthInterpreter &clen)
 {
     const char *field_ptr = header_start;
     const char *header_end = header_start + hdrLen; // XXX: remove
-    HttpHeaderEntry *e, *e2;
     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;
@@ -487,8 +360,8 @@ HttpHeader::parse(const char *header_start, size_t hdrLen)
     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);
-        return reset();
+        clean();
+        return 0;
     }
 
     /* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
@@ -497,14 +370,17 @@ HttpHeader::parse(const char *header_start, size_t hdrLen)
         const char *field_start = field_ptr;
         const char *field_end;
 
+        const char *hasBareCr = nullptr;
+        size_t lines = 0;
         do {
             const char *this_line = field_ptr;
             field_ptr = (const char *)memchr(field_ptr, '\n', header_end - field_ptr);
+            ++lines;
 
             if (!field_ptr) {
                 // missing <LF>
-                PROF_stop(HttpHeaderParse);
-                return reset();
+                clean();
+                return 0;
             }
 
             field_end = field_ptr;
@@ -524,118 +400,137 @@ HttpHeader::parse(const char *header_start, size_t hdrLen)
                         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);
-                        return reset();
+                        clean();
+                        return 0;
                     }
                 }
             }
 
             /* Barf on stray CR characters */
             if (memchr(this_line, '\r', field_end - this_line)) {
+                hasBareCr = "bare CR";
                 debugs(55, warnOnError, "WARNING: suspicious CR characters in HTTP header {" <<
                        getStringPrefix(field_start, field_end-field_start) << "}");
 
                 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);
-                    return reset();
+                    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);
-                return reset();
+                clean();
+                return 0;
             }
         } while (field_ptr < header_end && (*field_ptr == ' ' || *field_ptr == '\t'));
 
         if (field_start == field_end) {
             if (field_ptr < header_end) {
-                debugs(55, warnOnError, "WARNING: unparseable HTTP header field near {" <<
+                debugs(55, warnOnError, "WARNING: unparsable HTTP header field near {" <<
                        getStringPrefix(field_start, hdrLen-(field_start-header_start)) << "}");
-                PROF_stop(HttpHeaderParse);
-                return reset();
+                clean();
+                return 0;
             }
 
             break;      /* terminating blank line */
         }
 
-        if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) {
-            debugs(55, warnOnError, "WARNING: unparseable HTTP header field {" <<
+        const auto e = HttpHeaderEntry::parse(field_start, field_end, owner);
+        if (!e) {
+            debugs(55, warnOnError, "WARNING: unparsable HTTP header field {" <<
                    getStringPrefix(field_start, field_end-field_start) << "}");
             debugs(55, warnOnError, " in {" << getStringPrefix(header_start, hdrLen) << "}");
 
-            if (Config.onoff.relaxed_header_parser)
-                continue;
-
-            PROF_stop(HttpHeaderParse);
-            return reset();
+            clean();
+            return 0;
         }
 
-        if (e->id == HDR_CONTENT_LENGTH && (e2 = findEntry(e->id)) != NULL) {
-            if (e->value != e2->value) {
-                int64_t l1, l2;
-                debugs(55, warnOnError, "WARNING: found two conflicting content-length headers in {" <<
-                       getStringPrefix(header_start, hdrLen) << "}");
-
-                if (!Config.onoff.relaxed_header_parser) {
-                    delete e;
-                    PROF_stop(HttpHeaderParse);
-                    return reset();
-                }
-
-                if (!httpHeaderParseOffset(e->value.termedBuf(), &l1)) {
-                    debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e->value << "'");
-                    delete e;
-                    continue;
-                } else if (!httpHeaderParseOffset(e2->value.termedBuf(), &l2)) {
-                    debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e2->value << "'");
-                    delById(e2->id);
-                } else if (l1 > l2) {
-                    delById(e2->id);
-                } else {
-                    delete e;
-                    continue;
-                }
-            } else {
-                debugs(55, warnOnError, "NOTICE: found double content-length header");
+        if (lines > 1 || hasBareCr) {
+            const auto framingHeader = (e->id == Http::HdrType::CONTENT_LENGTH || e->id == Http::HdrType::TRANSFER_ENCODING);
+            if (framingHeader) {
+                if (!hasBareCr) // already warned about bare CRs
+                    debugs(55, warnOnError, "WARNING: obs-fold in framing-sensitive " << e->name << ": " << e->value);
                 delete e;
-
-                if (Config.onoff.relaxed_header_parser)
-                    continue;
-
-                PROF_stop(HttpHeaderParse);
-                return reset();
+                clean();
+                return 0;
             }
         }
 
-        if (e->id == HDR_OTHER && stringHasWhitespace(e->name.termedBuf())) {
-            debugs(55, warnOnError, "WARNING: found whitespace in HTTP header name {" <<
-                   getStringPrefix(field_start, field_end-field_start) << "}");
+        if (e->id == Http::HdrType::CONTENT_LENGTH && !clen.checkField(e->value)) {
+            delete e;
 
-            if (!Config.onoff.relaxed_header_parser) {
-                delete e;
-                PROF_stop(HttpHeaderParse);
-                return reset();
-            }
+            if (Config.onoff.relaxed_header_parser)
+                continue; // clen has printed any necessary warnings
+
+            clean();
+            return 0;
         }
 
         addEntry(e);
     }
 
-    if (chunked()) {
+    if (clen.headerWideProblem) {
+        debugs(55, warnOnError, "WARNING: " << clen.headerWideProblem <<
+               " Content-Length field values in" <<
+               Raw("header", header_start, hdrLen));
+    }
+
+    String rawTe;
+    if (clen.prohibitedAndIgnored()) {
+        // prohibitedAndIgnored() includes trailer header blocks
+        // being parsed as a case to forbid/ignore these headers.
+
+        // RFC 7230 section 3.3.2: A server MUST NOT send a Content-Length
+        // header field in any response with a status code of 1xx (Informational)
+        // or 204 (No Content). And RFC 7230 3.3.3#1 tells recipients to ignore
+        // such Content-Lengths.
+        if (delById(Http::HdrType::CONTENT_LENGTH))
+            debugs(55, 3, "Content-Length is " << clen.prohibitedAndIgnored());
+
+        // 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());
+
+    } else if (getByIdIfPresent(Http::HdrType::TRANSFER_ENCODING, &rawTe)) {
         // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
-        delById(HDR_CONTENT_LENGTH);
+        // RFC 7230 section 3.3.3 #3: Transfer-Encoding overwrites Content-Length
+        delById(Http::HdrType::CONTENT_LENGTH);
+        // and clen state becomes irrelevant
+
+        if (rawTe.caseCmp("chunked") == 0) {
+            ; // leave header present for chunked() method
+        } 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.
+            debugs(55, warnOnError, "WARNING: unsupported Transfer-Encoding used by client: " << rawTe);
+            teUnsupported_ = true;
+        }
+
+    } else if (clen.sawBad) {
+        // ensure our callers do not accidentally see bad Content-Length values
+        delById(Http::HdrType::CONTENT_LENGTH);
+        conflictingContentLength_ = true; // TODO: Rename to badContentLength_.
+    } else if (clen.needsSanitizing) {
+        // RFC 7230 section 3.3.2: MUST either reject or ... [sanitize];
+        // ensure our callers see a clean Content-Length value or none at all
+        delById(Http::HdrType::CONTENT_LENGTH);
+        if (clen.sawGood) {
+            putInt64(Http::HdrType::CONTENT_LENGTH, clen.value);
+            debugs(55, 5, "sanitized Content-Length to be " << clen.value);
+        }
     }
 
-    PROF_stop(HttpHeaderParse);
     return 1;           /* even if no fields where found, it is a valid header */
 }
 
@@ -657,13 +552,13 @@ HttpHeader::packInto(Packable * p, bool mask_sensitive_info) const
 
         bool maskThisEntry = false;
         switch (e->id) {
-        case HDR_AUTHORIZATION:
-        case HDR_PROXY_AUTHORIZATION:
+        case Http::HdrType::AUTHORIZATION:
+        case Http::HdrType::PROXY_AUTHORIZATION:
             maskThisEntry = true;
             break;
 
-        case HDR_FTP_ARGUMENTS:
-            if (const HttpHeaderEntry *cmd = findEntry(HDR_FTP_COMMAND))
+        case Http::HdrType::FTP_ARGUMENTS:
+            if (const HttpHeaderEntry *cmd = findEntry(Http::HdrType::FTP_COMMAND))
                 maskThisEntry = (cmd->value == "PASS");
             break;
 
@@ -671,7 +566,7 @@ HttpHeader::packInto(Packable * p, bool mask_sensitive_info) const
             break;
         }
         if (maskThisEntry) {
-            p->append(e->name.rawBuf(), e->name.size());
+            p->append(e->name.rawContent(), e->name.length());
             p->append(": ** NOT DISPLAYED **\r\n", 23);
         } else {
             e->packInto(p);
@@ -695,7 +590,7 @@ HttpHeader::getEntry(HttpHeaderPos * pos) const
             return static_cast<HttpHeaderEntry*>(entries[*pos]);
     }
 
-    return NULL;
+    return nullptr;
 }
 
 /*
@@ -704,70 +599,59 @@ HttpHeader::getEntry(HttpHeaderPos * pos) const
  * "list" headers
  */
 HttpHeaderEntry *
-HttpHeader::findEntry(http_hdr_type id) const
+HttpHeader::findEntry(Http::HdrType id) const
 {
-    HttpHeaderPos pos = HttpHeaderInitPos;
-    HttpHeaderEntry *e;
-    assert_eid(id);
-    assert(!CBIT_TEST(ListHeadersMask, id));
+    assert(any_registered_header(id));
+    assert(!Http::HeaderLookupTable.lookup(id).list);
 
     /* check mask first */
 
     if (!CBIT_TEST(mask, id))
-        return NULL;
+        return nullptr;
 
     /* looks like we must have it, do linear search */
-    while ((e = getEntry(&pos))) {
-        if (e->id == id)
+    for (auto e : entries) {
+        if (e && e->id == id)
             return e;
     }
 
     /* hm.. we thought it was there, but it was not found */
-    assert(0);
-
-    return NULL;        /* not reached */
+    assert(false);
+    return nullptr;        /* not reached */
 }
 
 /*
  * same as httpHeaderFindEntry
  */
 HttpHeaderEntry *
-HttpHeader::findLastEntry(http_hdr_type id) const
+HttpHeader::findLastEntry(Http::HdrType id) const
 {
-    HttpHeaderPos pos = HttpHeaderInitPos;
-    HttpHeaderEntry *e;
-    HttpHeaderEntry *result = NULL;
-    assert_eid(id);
-    assert(!CBIT_TEST(ListHeadersMask, id));
+    assert(any_registered_header(id));
+    assert(!Http::HeaderLookupTable.lookup(id).list);
 
     /* check mask first */
-
     if (!CBIT_TEST(mask, id))
-        return NULL;
+        return nullptr;
 
-    /* looks like we must have it, do linear search */
-    while ((e = getEntry(&pos))) {
-        if (e->id == id)
-            result = e;
+    for (auto e = entries.rbegin(); e != entries.rend(); ++e) {
+        if (*e && (*e)->id == id)
+            return *e;
     }
 
-    assert(result);     /* must be there! */
-    return result;
+    /* hm.. we thought it was there, but it was not found */
+    assert(false);
+    return nullptr; /* not reached */
 }
 
-/*
- * deletes all fields with a given name if any, returns #fields deleted;
- */
 int
-HttpHeader::delByName(const char *name)
+HttpHeader::delByName(const SBuf &name)
 {
     int count = 0;
     HttpHeaderPos pos = HttpHeaderInitPos;
-    HttpHeaderEntry *e;
     httpHeaderMaskInit(&mask, 0);   /* temporal inconsistency */
     debugs(55, 9, "deleting '" << name << "' fields in hdr " << this);
 
-    while ((e = getEntry(&pos))) {
+    while (const HttpHeaderEntry *e = getEntry(&pos)) {
         if (!e->name.caseCmp(name))
             delAt(pos, count);
         else
@@ -779,21 +663,20 @@ HttpHeader::delByName(const char *name)
 
 /* deletes all entries with a given id, returns the #entries deleted */
 int
-HttpHeader::delById(http_hdr_type id)
+HttpHeader::delById(Http::HdrType id)
 {
-    int count = 0;
-    HttpHeaderPos pos = HttpHeaderInitPos;
-    HttpHeaderEntry *e;
     debugs(55, 8, this << " del-by-id " << id);
-    assert_eid(id);
-    assert(id != HDR_OTHER);        /* does not make sense */
+    assert(any_registered_header(id));
 
     if (!CBIT_TEST(mask, id))
         return 0;
 
-    while ((e = getEntry(&pos))) {
+    int count = 0;
+
+    HttpHeaderPos pos = HttpHeaderInitPos;
+    while (HttpHeaderEntry *e = getEntry(&pos)) {
         if (e->id == id)
-            delAt(pos, count);
+            delAt(pos, count); // deletes e
     }
 
     CBIT_CLR(mask, id);
@@ -813,9 +696,9 @@ HttpHeader::delAt(HttpHeaderPos pos, int &headers_deleted)
     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.size() + 2 + e->value.size() + 2;
+    len -= e->name.length() + 2 + e->value.size() + 2;
     assert(len >= 0);
     delete e;
     ++headers_deleted;
@@ -828,9 +711,8 @@ void
 HttpHeader::compact()
 {
     // TODO: optimize removal, or possibly make it so that's not needed.
-    std::vector<HttpHeaderEntry *>::iterator newend;
-    newend = std::remove(entries.begin(), entries.end(), static_cast<HttpHeaderEntry *>(NULL));
-    entries.resize(newend-entries.begin());
+    entries.erase( std::remove(entries.begin(), entries.end(), nullptr),
+                   entries.end());
 }
 
 /*
@@ -841,9 +723,9 @@ HttpHeader::refreshMask()
 {
     httpHeaderMaskInit(&mask, 0);
     debugs(55, 7, "refreshing the mask in hdr " << this);
-    HttpHeaderPos pos = HttpHeaderInitPos;
-    while (HttpHeaderEntry *e = getEntry(&pos)) {
-        CBIT_SET(mask, e->id);
+    for (auto e : entries) {
+        if (e)
+            CBIT_SET(mask, e->id);
     }
 }
 
@@ -854,60 +736,36 @@ void
 HttpHeader::addEntry(HttpHeaderEntry * e)
 {
     assert(e);
-    assert_eid(e->id);
-    assert(e->name.size());
+    assert(any_HdrType_enum_value(e->id));
+    assert(e->name.length());
 
     debugs(55, 7, this << " adding entry: " << e->id << " at " << entries.size());
 
-    if (CBIT_TEST(mask, e->id)) {
-        ++ headerStatsTable[e->id].repCount;
-    } else {
-        CBIT_SET(mask, e->id);
+    if (e->id != Http::HdrType::BAD_HDR) {
+        if (CBIT_TEST(mask, e->id)) {
+            ++ headerStatsTable[e->id].repCount;
+        } else {
+            CBIT_SET(mask, e->id);
+        }
     }
 
     entries.push_back(e);
 
-    /* increment header length, allow for ": " and crlf */
-    len += e->name.size() + 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_eid(e->id);
-
-    debugs(55, 7, this << " adding entry: " << e->id << " at " << entries.size());
-
-    if (CBIT_TEST(mask, e->id)) {
-        ++ headerStatsTable[e->id].repCount; //TODO: use operator[] ?
-    } else {
-        CBIT_SET(mask, e->id);
-    }
-
-    entries.insert(entries.begin(),e);
-
-    /* increment header length, allow for ": " and crlf */
-    len += e->name.size() + 2 + e->value.size() + 2;
+    len += e->length();
 }
 
 bool
-HttpHeader::getList(http_hdr_type id, String *s) const
+HttpHeader::getList(Http::HdrType id, String *s) const
 {
-    HttpHeaderEntry *e;
-    HttpHeaderPos pos = HttpHeaderInitPos;
     debugs(55, 9, this << " joining for id " << id);
     /* only fields from ListHeaders array can be "listed" */
-    assert(CBIT_TEST(ListHeadersMask, id));
+    assert(Http::HeaderLookupTable.lookup(id).list);
 
     if (!CBIT_TEST(mask, id))
         return false;
 
-    while ((e = getEntry(&pos))) {
-        if (e->id == id)
+    for (auto e: entries) {
+        if (e && e->id == id)
             strListAdd(s, e->value.termedBuf(), ',');
     }
 
@@ -918,7 +776,7 @@ HttpHeader::getList(http_hdr_type id, String *s) const
      */
     /* temporary warning: remove it? (Is it useful for diagnostics ?) */
     if (!s->size())
-        debugs(55, 3, "empty list header: " << headerTable[id].name << "(" << id << ")");
+        debugs(55, 3, "empty list header: " << Http::HeaderLookupTable.lookup(id).name << "(" << id << ")");
     else
         debugs(55, 6, this << ": joined for id " << id << ": " << s);
 
@@ -927,13 +785,13 @@ HttpHeader::getList(http_hdr_type id, String *s) const
 
 /* return a list of entries with the same id separated by ',' and ws */
 String
-HttpHeader::getList(http_hdr_type id) const
+HttpHeader::getList(Http::HdrType id) const
 {
     HttpHeaderEntry *e;
     HttpHeaderPos pos = HttpHeaderInitPos;
     debugs(55, 9, this << "joining for id " << id);
     /* only fields from ListHeaders array can be "listed" */
-    assert(CBIT_TEST(ListHeadersMask, id));
+    assert(Http::HeaderLookupTable.lookup(id).list);
 
     if (!CBIT_TEST(mask, id))
         return String();
@@ -952,7 +810,7 @@ HttpHeader::getList(http_hdr_type id) const
      */
     /* temporary warning: remove it? (Is it useful for diagnostics ?) */
     if (!s.size())
-        debugs(55, 3, "empty list header: " << headerTable[id].name << "(" << id << ")");
+        debugs(55, 3, "empty list header: " << Http::HeaderLookupTable.lookup(id).name << "(" << id << ")");
     else
         debugs(55, 6, this << ": joined for id " << id << ": " << s);
 
@@ -961,11 +819,11 @@ HttpHeader::getList(http_hdr_type id) const
 
 /* return a string or list of entries with the same id separated by ',' and ws */
 String
-HttpHeader::getStrOrList(http_hdr_type id) const
+HttpHeader::getStrOrList(Http::HdrType id) const
 {
     HttpHeaderEntry *e;
 
-    if (CBIT_TEST(ListHeadersMask, id))
+    if (Http::HeaderLookupTable.lookup(id).list)
         return getList(id);
 
     if ((e = findEntry(id)))
@@ -982,35 +840,70 @@ HttpHeader::getByName(const char *name) const
 {
     String result;
     // ignore presence: return undefined string if an empty header is present
-    (void)getByNameIfPresent(name, result);
+    (void)hasNamed(name, strlen(name), &result);
     return result;
 }
 
+String
+HttpHeader::getByName(const SBuf &name) const
+{
+    String result;
+    // ignore presence: return undefined string if an empty header is present
+    (void)hasNamed(name, &result);
+    return result;
+}
+
+String
+HttpHeader::getById(Http::HdrType id) const
+{
+    String result;
+    (void)getByIdIfPresent(id, &result);
+    return result;
+}
+
+bool
+HttpHeader::hasNamed(const SBuf &s, String *result) const
+{
+    return hasNamed(s.rawContent(), s.length(), result);
+}
+
+bool
+HttpHeader::getByIdIfPresent(Http::HdrType id, String *result) const
+{
+    if (id == Http::HdrType::BAD_HDR)
+        return false;
+    if (!has(id))
+        return false;
+    if (result)
+        *result = getStrOrList(id);
+    return true;
+}
+
 bool
-HttpHeader::getByNameIfPresent(const char *name, String &result) const
+HttpHeader::hasNamed(const char *name, unsigned int namelen, String *result) const
 {
-    http_hdr_type id;
+    Http::HdrType id;
     HttpHeaderPos pos = HttpHeaderInitPos;
     HttpHeaderEntry *e;
 
     assert(name);
 
     /* First try the quick path */
-    id = httpHeaderIdByNameDef(SBuf(name));
+    id = Http::HeaderLookupTable.lookup(name,namelen).id;
 
-    if (id != -1) {
-        if (!has(id))
-            return false;
-        result = getStrOrList(id);
-        return true;
+    if (id != Http::HdrType::BAD_HDR) {
+        if (getByIdIfPresent(id, result))
+            return true;
     }
 
     /* Sorry, an unknown header name. Do linear search */
     bool found = false;
     while ((e = getEntry(&pos))) {
-        if (e->id == HDR_OTHER && e->name.caseCmp(name) == 0) {
+        if (e->id == Http::HdrType::OTHER && e->name.length() == namelen && e->name.caseCmp(name, namelen) == 0) {
             found = true;
-            strListAdd(&result, e->value.termedBuf(), ',');
+            if (!result)
+                break;
+            strListAdd(result, e->value.termedBuf(), ',');
         }
     }
 
@@ -1020,133 +913,111 @@ HttpHeader::getByNameIfPresent(const char *name, String &result) const
 /*
  * Returns a the value of the specified list member, if any.
  */
-String
+SBuf
 HttpHeader::getByNameListMember(const char *name, const char *member, const char separator) const
 {
-    String header;
-    const char *pos = NULL;
-    const char *item;
-    int ilen;
-    int mlen = strlen(member);
-
     assert(name);
-
-    header = getByName(name);
-
-    String result;
-
-    while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
-        if (strncmp(item, member, mlen) == 0 && item[mlen] == '=') {
-            result.append(item + mlen + 1, ilen - mlen - 1);
-            break;
-        }
-    }
-
-    return result;
+    const auto header = getByName(name);
+    return ::getListMember(header, member, separator);
 }
 
 /*
  * returns a the value of the specified list member, if any.
  */
-String
-HttpHeader::getListMember(http_hdr_type id, const char *member, const char separator) const
+SBuf
+HttpHeader::getListMember(Http::HdrType id, const char *member, const char separator) const
 {
-    String header;
-    const char *pos = NULL;
-    const char *item;
-    int ilen;
-    int mlen = strlen(member);
-
-    assert(id >= 0);
-
-    header = getStrOrList(id);
-    String result;
-
-    while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
-        if (strncmp(item, member, mlen) == 0 && item[mlen] == '=') {
-            result.append(item + mlen + 1, ilen - mlen - 1);
-            break;
-        }
-    }
-
-    header.clean();
-    return result;
+    assert(any_registered_header(id));
+    const auto header = getStrOrList(id);
+    return ::getListMember(header, member, separator);
 }
 
 /* test if a field is present */
 int
-HttpHeader::has(http_hdr_type id) const
+HttpHeader::has(Http::HdrType id) const
 {
-    assert_eid(id);
-    assert(id != HDR_OTHER);
+    assert(any_registered_header(id));
     debugs(55, 9, this << " lookup for " << id);
     return CBIT_TEST(mask, id);
 }
 
 void
-HttpHeader::putInt(http_hdr_type id, int number)
-{
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftInt);  /* must be of an appropriate type */
-    assert(number >= 0);
-    addEntry(new HttpHeaderEntry(id, NULL, xitoa(number)));
+HttpHeader::addVia(const AnyP::ProtocolVersion &ver, const HttpHeader *from)
+{
+    // TODO: do not add Via header for messages where Squid itself
+    // generated the message (i.e., Downloader or ESI) there should be no Via header added at all.
+
+    if (Config.onoff.via) {
+        SBuf buf;
+        // RFC 7230 section 5.7.1.: protocol-name is omitted when
+        // the received protocol is HTTP.
+        if (ver.protocol > AnyP::PROTO_NONE && ver.protocol < AnyP::PROTO_UNKNOWN &&
+                ver.protocol != AnyP::PROTO_HTTP && ver.protocol != AnyP::PROTO_HTTPS)
+            buf.appendf("%s/", AnyP::ProtocolType_str[ver.protocol]);
+        buf.appendf("%d.%d %s", ver.major, ver.minor, ThisCache);
+        const HttpHeader *hdr = from ? from : this;
+        SBuf strVia = StringToSBuf(hdr->getList(Http::HdrType::VIA));
+        if (!strVia.isEmpty())
+            strVia.append(", ", 2);
+        strVia.append(buf);
+        updateOrAddStr(Http::HdrType::VIA, strVia);
+    }
 }
 
 void
-HttpHeader::putInt64(http_hdr_type id, int64_t number)
+HttpHeader::putInt(Http::HdrType id, int number)
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftInt64);    /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt);  /* must be of an appropriate type */
     assert(number >= 0);
-    addEntry(new HttpHeaderEntry(id, NULL, xint64toa(number)));
+    addEntry(new HttpHeaderEntry(id, SBuf(), xitoa(number)));
 }
 
 void
-HttpHeader::putTime(http_hdr_type id, time_t htime)
+HttpHeader::putInt64(Http::HdrType id, int64_t number)
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftDate_1123);    /* must be of an appropriate type */
-    assert(htime >= 0);
-    addEntry(new HttpHeaderEntry(id, NULL, mkrfc1123(htime)));
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt64);    /* must be of an appropriate type */
+    assert(number >= 0);
+    addEntry(new HttpHeaderEntry(id, SBuf(), xint64toa(number)));
 }
 
 void
-HttpHeader::insertTime(http_hdr_type id, time_t htime)
+HttpHeader::putTime(Http::HdrType id, time_t htime)
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftDate_1123);    /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123);    /* must be of an appropriate type */
     assert(htime >= 0);
-    insertEntry(new HttpHeaderEntry(id, NULL, mkrfc1123(htime)));
+    addEntry(new HttpHeaderEntry(id, SBuf(), Time::FormatRfc1123(htime)));
 }
 
 void
-HttpHeader::putStr(http_hdr_type id, const char *str)
+HttpHeader::putStr(Http::HdrType id, const char *str)
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftStr);  /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr);  /* must be of an appropriate type */
     assert(str);
-    addEntry(new HttpHeaderEntry(id, NULL, str));
+    addEntry(new HttpHeaderEntry(id, SBuf(), str));
 }
 
 void
 HttpHeader::putAuth(const char *auth_scheme, const char *realm)
 {
     assert(auth_scheme && realm);
-    httpHeaderPutStrf(this, HDR_WWW_AUTHENTICATE, "%s realm=\"%s\"", auth_scheme, realm);
+    httpHeaderPutStrf(this, Http::HdrType::WWW_AUTHENTICATE, "%s realm=\"%s\"", auth_scheme, realm);
 }
 
 void
-HttpHeader::putCc(const HttpHdrCc cc)
+HttpHeader::putCc(const HttpHdrCc &cc)
 {
-    assert(cc);
     /* remove old directives if any */
-    delById(HDR_CACHE_CONTROL);
+    delById(Http::HdrType::CACHE_CONTROL);
     /* pack into mb */
     MemBuf mb;
     mb.init();
-    cc->packInto(&mb);
+    cc.packInto(&mb);
     /* put */
-    addEntry(new HttpHeaderEntry(HDR_CACHE_CONTROL, NULL, mb.buf));
+    addEntry(new HttpHeaderEntry(Http::HdrType::CACHE_CONTROL, SBuf(), mb.buf));
     /* cleanup */
     mb.clean();
 }
@@ -1156,13 +1027,13 @@ HttpHeader::putContRange(const HttpHdrContRange * cr)
 {
     assert(cr);
     /* remove old directives if any */
-    delById(HDR_CONTENT_RANGE);
+    delById(Http::HdrType::CONTENT_RANGE);
     /* pack into mb */
     MemBuf mb;
     mb.init();
     httpHdrContRangePackInto(cr, &mb);
     /* put */
-    addEntry(new HttpHeaderEntry(HDR_CONTENT_RANGE, NULL, mb.buf));
+    addEntry(new HttpHeaderEntry(Http::HdrType::CONTENT_RANGE, SBuf(), mb.buf));
     /* cleanup */
     mb.clean();
 }
@@ -1172,13 +1043,13 @@ HttpHeader::putRange(const HttpHdrRange * range)
 {
     assert(range);
     /* remove old directives if any */
-    delById(HDR_RANGE);
+    delById(Http::HdrType::RANGE);
     /* pack into mb */
     MemBuf mb;
     mb.init();
     range->packInto(&mb);
     /* put */
-    addEntry(new HttpHeaderEntry(HDR_RANGE, NULL, mb.buf));
+    addEntry(new HttpHeaderEntry(Http::HdrType::RANGE, SBuf(), mb.buf));
     /* cleanup */
     mb.clean();
 }
@@ -1188,39 +1059,68 @@ HttpHeader::putSc(HttpHdrSc *sc)
 {
     assert(sc);
     /* remove old directives if any */
-    delById(HDR_SURROGATE_CONTROL);
+    delById(Http::HdrType::SURROGATE_CONTROL);
     /* pack into mb */
     MemBuf mb;
     mb.init();
     sc->packInto(&mb);
     /* put */
-    addEntry(new HttpHeaderEntry(HDR_SURROGATE_CONTROL, NULL, mb.buf));
+    addEntry(new HttpHeaderEntry(Http::HdrType::SURROGATE_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(HDR_WARNING, buf);
-}
-
 /* add extension header (these fields are not parsed/analyzed/joined, etc.) */
 void
 HttpHeader::putExt(const char *name, const char *value)
 {
     assert(name && value);
     debugs(55, 8, this << " adds ext entry " << name << " : " << value);
-    addEntry(new HttpHeaderEntry(HDR_OTHER, name, 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_hdr_type id) const
+HttpHeader::getInt(Http::HdrType id) const
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftInt);  /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt);  /* must be of an appropriate type */
     HttpHeaderEntry *e;
 
     if ((e = findEntry(id)))
@@ -1230,10 +1130,10 @@ HttpHeader::getInt(http_hdr_type id) const
 }
 
 int64_t
-HttpHeader::getInt64(http_hdr_type id) const
+HttpHeader::getInt64(Http::HdrType id) const
 {
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftInt64);    /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftInt64);    /* must be of an appropriate type */
     HttpHeaderEntry *e;
 
     if ((e = findEntry(id)))
@@ -1243,15 +1143,15 @@ HttpHeader::getInt64(http_hdr_type id) const
 }
 
 time_t
-HttpHeader::getTime(http_hdr_type id) const
+HttpHeader::getTime(Http::HdrType id) const
 {
     HttpHeaderEntry *e;
     time_t value = -1;
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftDate_1123);    /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    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);
     }
 
@@ -1260,51 +1160,50 @@ HttpHeader::getTime(http_hdr_type id) const
 
 /* sync with httpHeaderGetLastStr */
 const char *
-HttpHeader::getStr(http_hdr_type id) const
+HttpHeader::getStr(Http::HdrType id) const
 {
     HttpHeaderEntry *e;
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftStr);  /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr);  /* must be of an appropriate type */
 
     if ((e = findEntry(id))) {
-        httpHeaderNoteParsedEntry(e->id, e->value, 0);  /* no errors are possible */
+        httpHeaderNoteParsedEntry(e->id, e->value, false);  /* no errors are possible */
         return e->value.termedBuf();
     }
 
-    return NULL;
+    return nullptr;
 }
 
 /* unusual */
 const char *
-HttpHeader::getLastStr(http_hdr_type id) const
+HttpHeader::getLastStr(Http::HdrType id) const
 {
     HttpHeaderEntry *e;
-    assert_eid(id);
-    assert(headerTable[id].type == field_type::ftStr);  /* must be of an appropriate type */
+    assert(any_registered_header(id));
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftStr);  /* must be of an appropriate type */
 
     if ((e = findLastEntry(id))) {
-        httpHeaderNoteParsedEntry(e->id, e->value, 0);  /* no errors are possible */
+        httpHeaderNoteParsedEntry(e->id, e->value, false);  /* no errors are possible */
         return e->value.termedBuf();
     }
 
-    return NULL;
+    return nullptr;
 }
 
 HttpHdrCc *
 HttpHeader::getCc() const
 {
-    if (!CBIT_TEST(mask, HDR_CACHE_CONTROL))
-        return NULL;
-    PROF_start(HttpHeader_getCc);
+    if (!CBIT_TEST(mask, Http::HdrType::CACHE_CONTROL))
+        return nullptr;
 
     String s;
-    getList(HDR_CACHE_CONTROL, &s);
+    getList(Http::HdrType::CACHE_CONTROL, &s);
 
     HttpHdrCc *cc=new HttpHdrCc();
 
     if (!cc->parse(s)) {
         delete cc;
-        cc = NULL;
+        cc = nullptr;
     }
 
     ++ HttpHeaderStats[owner].ccParsedCount;
@@ -1312,9 +1211,7 @@ HttpHeader::getCc() const
     if (cc)
         httpHdrCcUpdateStats(cc, &HttpHeaderStats[owner].ccTypeDistr);
 
-    httpHeaderNoteParsedEntry(HDR_CACHE_CONTROL, s, !cc);
-
-    PROF_stop(HttpHeader_getCc);
+    httpHeaderNoteParsedEntry(Http::HdrType::CACHE_CONTROL, s, !cc);
 
     return cc;
 }
@@ -1322,15 +1219,15 @@ HttpHeader::getCc() const
 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;
      * this "if" should work correctly in both cases;
      * hopefully no clients send mismatched headers! */
 
-    if ((e = findEntry(HDR_RANGE)) ||
-            (e = findEntry(HDR_REQUEST_RANGE))) {
+    if ((e = findEntry(Http::HdrType::RANGE)) ||
+            (e = findEntry(Http::HdrType::REQUEST_RANGE))) {
         r = HttpHdrRange::ParseCreate(&e->value);
         httpHeaderNoteParsedEntry(e->id, e->value, !r);
     }
@@ -1341,12 +1238,12 @@ HttpHeader::getRange() const
 HttpHdrSc *
 HttpHeader::getSc() const
 {
-    if (!CBIT_TEST(mask, HDR_SURROGATE_CONTROL))
-        return NULL;
+    if (!CBIT_TEST(mask, Http::HdrType::SURROGATE_CONTROL))
+        return nullptr;
 
     String s;
 
-    (void) getList(HDR_SURROGATE_CONTROL, &s);
+    (void) getList(Http::HdrType::SURROGATE_CONTROL, &s);
 
     HttpHdrSc *sc = httpHdrScParseCreate(s);
 
@@ -1355,7 +1252,7 @@ HttpHeader::getSc() const
     if (sc)
         sc->updateStats(&HttpHeaderStats[owner].scTypeDistr);
 
-    httpHeaderNoteParsedEntry(HDR_SURROGATE_CONTROL, s, !sc);
+    httpHeaderNoteParsedEntry(Http::HdrType::SURROGATE_CONTROL, s, !sc);
 
     return sc;
 }
@@ -1363,10 +1260,10 @@ HttpHeader::getSc() const
 HttpHdrContRange *
 HttpHeader::getContRange() const
 {
-    HttpHdrContRange *cr = NULL;
+    HttpHdrContRange *cr = nullptr;
     HttpHeaderEntry *e;
 
-    if ((e = findEntry(HDR_CONTENT_RANGE))) {
+    if ((e = findEntry(Http::HdrType::CONTENT_RANGE))) {
         cr = httpHdrContRangeParseCreate(e->value.termedBuf());
         httpHeaderNoteParsedEntry(e->id, e->value, !cr);
     }
@@ -1374,51 +1271,54 @@ HttpHeader::getContRange() const
     return cr;
 }
 
-const char *
-HttpHeader::getAuth(http_hdr_type id, const char *auth_scheme) const
+SBuf
+HttpHeader::getAuthToken(Http::HdrType id, const char *auth_scheme) const
 {
     const char *field;
     int l;
     assert(auth_scheme);
     field = getStr(id);
 
+    static const SBuf nil;
     if (!field)         /* no authorization field */
-        return NULL;
+        return nil;
 
     l = strlen(auth_scheme);
 
     if (!l || strncasecmp(field, auth_scheme, l))   /* wrong scheme */
-        return NULL;
+        return nil;
 
     field += l;
 
     if (!xisspace(*field))  /* wrong scheme */
-        return NULL;
+        return nil;
 
     /* skip white space */
     for (; field && xisspace(*field); ++field);
 
     if (!*field)        /* no authorization cookie */
-        return NULL;
+        return nil;
 
-    static char decodedAuthToken[8192];
+    const auto fieldLen = strlen(field);
+    SBuf result;
+    char *decodedAuthToken = result.rawAppendStart(BASE64_DECODE_LENGTH(fieldLen));
     struct base64_decode_ctx ctx;
     base64_decode_init(&ctx);
     size_t decodedLen = 0;
-    if (!base64_decode_update(&ctx, &decodedLen, reinterpret_cast<uint8_t*>(decodedAuthToken), strlen(field), reinterpret_cast<const uint8_t*>(field)) ||
+    if (!base64_decode_update(&ctx, &decodedLen, reinterpret_cast<uint8_t*>(decodedAuthToken), fieldLen, field) ||
             !base64_decode_final(&ctx)) {
-        return NULL;
+        return nil;
     }
-    decodedAuthToken[decodedLen] = '\0';
-    return decodedAuthToken;
+    result.rawAppendFinish(decodedAuthToken, decodedLen);
+    return result;
 }
 
 ETag
-HttpHeader::getETag(http_hdr_type id) const
+HttpHeader::getETag(Http::HdrType id) const
 {
-    ETag etag = {NULL, -1};
+    ETag etag = {nullptr, -1};
     HttpHeaderEntry *e;
-    assert(headerTable[id].type == field_type::ftETag);     /* must be of an appropriate type */
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftETag);     /* must be of an appropriate type */
 
     if ((e = findEntry(id)))
         etagParseInit(&etag, e->value.termedBuf());
@@ -1427,11 +1327,11 @@ HttpHeader::getETag(http_hdr_type id) const
 }
 
 TimeOrTag
-HttpHeader::getTimeOrTag(http_hdr_type id) const
+HttpHeader::getTimeOrTag(Http::HdrType id) const
 {
     TimeOrTag tot;
     HttpHeaderEntry *e;
-    assert(headerTable[id].type == field_type::ftDate_1123_or_ETag);    /* must be of an appropriate type */
+    assert(Http::HeaderLookupTable.lookup(id).type == Http::HdrFieldType::ftDate_1123_or_ETag);    /* must be of an appropriate type */
     memset(&tot, 0, sizeof(tot));
 
     if ((e = findEntry(id))) {
@@ -1439,13 +1339,13 @@ HttpHeader::getTimeOrTag(http_hdr_type id) const
         /* 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;
         }
     }
 
@@ -1457,38 +1357,39 @@ HttpHeader::getTimeOrTag(http_hdr_type id) const
  * HttpHeaderEntry
  */
 
-HttpHeaderEntry::HttpHeaderEntry(http_hdr_type anId, const char *aName, const char *aValue)
+HttpHeaderEntry::HttpHeaderEntry(Http::HdrType anId, const SBuf &aName, const char *aValue)
 {
-    assert_eid(anId);
+    assert(any_HdrType_enum_value(anId));
     id = anId;
 
-    if (id != HDR_OTHER)
-        name = headerTable[id].name;
+    if (id != Http::HdrType::OTHER)
+        name = Http::HeaderLookupTable.lookup(id).name;
     else
         name = aName;
 
     value = aValue;
 
-    ++ headerStatsTable[id].aliveCount;
+    if (id != Http::HdrType::BAD_HDR)
+        ++ headerStatsTable[id].aliveCount;
 
     debugs(55, 9, "created HttpHeaderEntry " << this << ": '" << name << " : " << value );
 }
 
 HttpHeaderEntry::~HttpHeaderEntry()
 {
-    assert_eid(id);
     debugs(55, 9, "destroying entry " << this << ": '" << name << ": " << value << "'");
 
-    assert(headerStatsTable[id].aliveCount); // is this really needed?
-
-    -- headerStatsTable[id].aliveCount;
+    if (id != Http::HdrType::BAD_HDR) {
+        assert(headerStatsTable[id].aliveCount);
+        -- headerStatsTable[id].aliveCount;
+        id = Http::HdrType::BAD_HDR; // it already is BAD_HDR, no sense in resetting it
+    }
 
-    id = HDR_BAD_HDR;
 }
 
 /* parses and inits header entry, returns true/false */
 HttpHeaderEntry *
-HttpHeaderEntry::parse(const char *field_start, const char *field_end)
+HttpHeaderEntry::parse(const char *field_start, const char *field_end, const http_hdr_owner_type msgType)
 {
     /* note: name_start == field_start */
     const char *name_end = (const char *)memchr(field_start, ':', field_end - field_start);
@@ -1501,23 +1402,59 @@ HttpHeaderEntry::parse(const char *field_start, const char *field_end)
     /* 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 */
-        debugs(55, DBG_IMPORTANT, "WARNING: ignoring header name of " << name_len << " bytes");
-        return 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 nullptr;
     }
 
-    if (Config.onoff.relaxed_header_parser && xisspace(field_start[name_len - 1])) {
+    /*
+     * RFC 7230 section 3.2.4:
+     * "No whitespace is allowed between the header field-name and colon.
+     * ...
+     *  A server MUST reject any received request message that contains
+     *  whitespace between a header field-name and colon with a response code
+     *  of 400 (Bad Request).  A proxy MUST remove any such whitespace from a
+     *  response message before forwarding the message downstream."
+     */
+    if (xisspace(field_start[name_len - 1])) {
+
+        if (msgType == hoRequest)
+            return nullptr;
+
+        // for now, also let relaxed parser remove this BWS from any non-HTTP messages
+        const bool stripWhitespace = (msgType == hoReply) ||
+                                     Config.onoff.relaxed_header_parser;
+        if (!stripWhitespace)
+            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)
-            return NULL;
+        if (!name_len) {
+            debugs(55, 2, "found header with only whitespace for name");
+            return nullptr;
+        }
+    }
+
+    /* RFC 7230 section 3.2:
+     *
+     *  header-field   = field-name ":" OWS field-value OWS
+     *  field-name     = token
+     *  token          = 1*TCHAR
+     */
+    for (const char *pos = field_start; pos < (field_start+name_len); ++pos) {
+        if (!CharacterSet::TCHAR[*pos]) {
+            debugs(55, 2, "found header with invalid characters in " <<
+                   Raw("field-name", field_start, min(name_len,100)) << "...");
+            return nullptr;
+        }
     }
 
     /* now we know we can parse it */
@@ -1525,23 +1462,21 @@ HttpHeaderEntry::parse(const char *field_start, const char *field_end)
     debugs(55, 9, "parsing HttpHeaderEntry: near '" <<  getStringPrefix(field_start, field_end-field_start) << "'");
 
     /* is it a "known" field? */
-    http_hdr_type id = headerLookupTable.lookup(SBuf(field_start,name_len));
-    debugs(55, 9, "got hdr id hdr: " << id);
+    Http::HdrType id = Http::HeaderLookupTable.lookup(field_start,name_len).id;
+    debugs(55, 9, "got hdr-id=" << id);
 
-    String name;
+    SBuf theName;
 
     String value;
 
-    if (id < 0)
-        id = HDR_OTHER;
-
-    assert_eid(id);
+    if (id == Http::HdrType::BAD_HDR)
+        id = Http::HdrType::OTHER;
 
     /* set field name */
-    if (id == HDR_OTHER)
-        name.limitInit(field_start, name_len);
+    if (id == Http::HdrType::OTHER)
+        theName.append(field_start, name_len);
     else
-        name = headerTable[id].name;
+        theName = Http::HeaderLookupTable.lookup(id).name;
 
     /* trim field value */
     while (value_start < field_end && xisspace(*value_start))
@@ -1552,35 +1487,32 @@ HttpHeaderEntry::parse(const char *field_start, const char *field_end)
 
     if (field_end - value_start > 65534) {
         /* String must be LESS THAN 64K and it adds a terminating NULL */
-        debugs(55, DBG_IMPORTANT, "WARNING: ignoring '" << name << "' header of " << (field_end - value_start) << " bytes");
-
-        if (id == HDR_OTHER)
-            name.clean();
-
-        return NULL;
+        debugs(55, 2, "WARNING: found '" << theName << "' header of " << (field_end - value_start) << " bytes");
+        return nullptr;
     }
 
     /* set field value */
-    value.limitInit(value_start, field_end - value_start);
+    value.assign(value_start, field_end - value_start);
 
-    ++ headerStatsTable[id].seenCount;
+    if (id != Http::HdrType::BAD_HDR)
+        ++ headerStatsTable[id].seenCount;
 
-    debugs(55, 9, "parsed HttpHeaderEntry: '" << name << ": " << value << "'");
+    debugs(55, 9, "parsed HttpHeaderEntry: '" << theName << ": " << value << "'");
 
-    return new HttpHeaderEntry(id, name.termedBuf(), value.termedBuf());
+    return new HttpHeaderEntry(id, theName, value.termedBuf());
 }
 
 HttpHeaderEntry *
 HttpHeaderEntry::clone() const
 {
-    return new HttpHeaderEntry(id, name.termedBuf(), value.termedBuf());
+    return new HttpHeaderEntry(id, name, value.termedBuf());
 }
 
 void
 HttpHeaderEntry::packInto(Packable * p) const
 {
     assert(p);
-    p->append(name.rawBuf(), name.size());
+    p->append(name.rawContent(), name.length());
     p->append(": ", 2);
     p->append(value.rawBuf(), value.size());
     p->append("\r\n", 2);
@@ -1589,10 +1521,9 @@ HttpHeaderEntry::packInto(Packable * p) const
 int
 HttpHeaderEntry::getInt() const
 {
-    assert_eid (id);
     int val = -1;
     int ok = httpHeaderParseInt(value.termedBuf(), &val);
-    httpHeaderNoteParsedEntry(id, value, !ok);
+    httpHeaderNoteParsedEntry(id, value, ok == 0);
     /* XXX: Should we check ok - ie
      * return ok ? -1 : value;
      */
@@ -1602,24 +1533,22 @@ HttpHeaderEntry::getInt() const
 int64_t
 HttpHeaderEntry::getInt64() const
 {
-    assert_eid (id);
     int64_t val = -1;
-    int ok = httpHeaderParseOffset(value.termedBuf(), &val);
+    const bool ok = httpHeaderParseOffset(value.termedBuf(), &val);
     httpHeaderNoteParsedEntry(id, value, !ok);
-    /* XXX: Should we check ok - ie
-     * return ok ? -1 : value;
-     */
-    return val;
+    return val; // remains -1 if !ok (XXX: bad method API)
 }
 
 static void
-httpHeaderNoteParsedEntry(http_hdr_type id, String const &context, int error)
+httpHeaderNoteParsedEntry(Http::HdrType id, String const &context, bool error)
 {
-    ++ headerStatsTable[id].parsCount;
+    if (id != Http::HdrType::BAD_HDR)
+        ++ headerStatsTable[id].parsCount;
 
     if (error) {
-        ++ headerStatsTable[id].errCount;
-        debugs(55, 2, "cannot parse hdr field: '" << headerTable[id].name << ": " << context << "'");
+        if (id != Http::HdrType::BAD_HDR)
+            ++ headerStatsTable[id].errCount;
+        debugs(55, 2, "cannot parse hdr field: '" << Http::HeaderLookupTable.lookup(id).name << ": " << context << "'");
     }
 }
 
@@ -1629,14 +1558,14 @@ httpHeaderNoteParsedEntry(http_hdr_type id, String const &context, int error)
 
 /* 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 = (int) val;
-    const int valid_id = id >= 0 && id < HDR_ENUM_END;
-    const char *name = valid_id ? headerTable[id].name : "INVALID";
+    const int id = static_cast<int>(val);
+    const bool valid_id = Http::any_valid_header(static_cast<Http::HdrType>(id));
+    const char *name = valid_id ? Http::HeaderLookupTable.lookup(static_cast<Http::HdrType>(id)).name : "INVALID";
     int visible = count > 0;
     /* for entries with zero count, list only those that belong to current type of message */
 
@@ -1660,7 +1589,11 @@ httpHeaderFldsPerHdrDumper(StoreEntry * sentry, int idx, double val, double, int
 static void
 httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e)
 {
-    assert(hs && e);
+    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);
@@ -1681,14 +1614,12 @@ httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e)
                       "id", "#flds", "count", "%total");
     hs->hdrUCountDistr.dump(e, httpHeaderFldsPerHdrDumper);
     storeAppendPrintf(e, "\n");
-    dump_stat = NULL;
+    dump_stat = nullptr;
 }
 
 void
 httpHeaderStoreReport(StoreEntry * e)
 {
-    int i;
-//    http_hdr_type ht;
     assert(e);
 
     HttpHeaderStats[0].parsedCount =
@@ -1700,9 +1631,8 @@ httpHeaderStoreReport(StoreEntry * e)
     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");
@@ -1711,12 +1641,14 @@ httpHeaderStoreReport(StoreEntry * e)
                       "id", "name", "#alive", "%err", "%repeat");
 
     // scan heaaderTable and output
-    for (int j = 0; headerTable[j].name != nullptr; ++j) {
-        auto stats = headerStatsTable[j];
+    for (auto h : WholeEnum<Http::HdrType>()) {
+        auto stats = headerStatsTable[h];
         storeAppendPrintf(e, "%2d\t %-25s\t %5d\t %6.3f\t %6.3f\n",
-            headerTable[j].id, headerTable[j].name, stats.aliveCount,
-            xpercent(stats.errCount, stats.parsCount),
-            xpercent(stats.repCount, stats.seenCount));
+                          Http::HeaderLookupTable.lookup(h).id,
+                          Http::HeaderLookupTable.lookup(h).name,
+                          stats.aliveCount,
+                          xpercent(stats.errCount, stats.parsCount),
+                          xpercent(stats.repCount, stats.seenCount));
     }
 
     storeAppendPrintf(e, "Headers Parsed: %d + %d = %d\n",
@@ -1726,45 +1658,16 @@ httpHeaderStoreReport(StoreEntry * e)
     storeAppendPrintf(e, "Hdr Fields Parsed: %d\n", HeaderEntryParsedCount);
 }
 
-// (ab)used by other modules.
-http_hdr_type
-httpHeaderIdByName(const char *name, size_t name_len, const HttpHeaderFieldInfo * info, int end)
-{
-    if (name_len > 0) {
-        for (int i = 0; i < end; ++i) {
-            if (name_len != info[i].name.size())
-                continue;
-
-            if (!strncasecmp(name, info[i].name.rawBuf(), name_len))
-                return info[i].id;
-        }
-    }
-
-    return HDR_BAD_HDR;
-}
-
-http_hdr_type
-httpHeaderIdByNameDef(const SBuf &name)
-{
-    return headerLookupTable.lookup(name);
-}
-
-http_hdr_type
-httpHeaderIdByNameDef(const char *name, int name_len)
-{
-    return headerLookupTable.lookup(SBuf(name,name_len));
-}
-
 int
-HttpHeader::hasListMember(http_hdr_type id, const char *member, const char separator) const
+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);
 
-    assert(id >= 0);
+    assert(any_registered_header(id));
 
     String header (getStrOrList(id));
 
@@ -1783,7 +1686,7 @@ int
 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);
@@ -1812,8 +1715,8 @@ HttpHeader::removeHopByHopEntries()
     HttpHeaderPos pos = HttpHeaderInitPos;
     int headers_deleted = 0;
     while ((e = getEntry(&pos))) {
-        int id = e->id;
-        if (CBIT_TEST(HopByHopHeadersMask, id)) {
+        Http::HdrType id = e->id;
+        if (Http::HeaderLookupTable.lookup(id).hopbyhop) {
             delAt(pos, headers_deleted);
             CBIT_CLR(mask, id);
         }
@@ -1823,11 +1726,11 @@ HttpHeader::removeHopByHopEntries()
 void
 HttpHeader::removeConnectionHeaderEntries()
 {
-    if (has(HDR_CONNECTION)) {
+    if (has(Http::HdrType::CONNECTION)) {
         /* anything that matches Connection list member will be deleted */
         String strConnection;
 
-        (void) getList(HDR_CONNECTION, &strConnection);
+        (void) getList(Http::HdrType::CONNECTION, &strConnection);
         const HttpHeaderEntry *e;
         HttpHeaderPos pos = HttpHeaderInitPos;
         /*
@@ -1841,7 +1744,7 @@ HttpHeader::removeConnectionHeaderEntries()
 
         int headers_deleted = 0;
         while ((e = getEntry(&pos))) {
-            if (strListIsMember(&strConnection, e->name.termedBuf(), ','))
+            if (strListIsMember(&strConnection, e->name, ','))
                 delAt(pos, headers_deleted);
         }
         if (headers_deleted)