ported from:

commit 671ba97abe929156dc4c717ee52ad22fba0f7443
Author: Amos Jeffries <yadij@users.noreply.github.com>
Date:   2019-09-11 02:52:52 +0000

    RFC 7230: server MUST reject messages with BWS after field-name (#445)
    
    Obey the RFC requirement to reject HTTP requests with whitespace
    between field-name and the colon delimiter. Rejection is
    critical in the presence of broken HTTP agents that mishandle
    malformed messages.
    
    Also obey requirement to always strip such whitespace from HTTP
    response messages. The relaxed parser is no longer necessary for
    this response change.
    
    For now non-HTTP protocols retain the old behaviour of removal
    only when using the relaxed parser.

Index: squid-3.1.23/src/HttpHeader.cc
===================================================================
--- squid-3.1.23.orig/src/HttpHeader.cc
+++ squid-3.1.23/src/HttpHeader.cc
@@ -584,16 +584,13 @@ HttpHeader::parse(const char *header_sta
             break;		/* terminating blank line */
         }
 
-        if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) {
+        if ((e = HttpHeaderEntry::parse(field_start, field_end, owner)) == NULL) {
             debugs(55, 1, "WARNING: unparseable HTTP header field {" <<
                    getStringPrefix(field_start, field_end) << "}");
             debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
                    " in {" << getStringPrefix(header_start, header_end) << "}");
 
-            if (Config.onoff.relaxed_header_parser)
-                continue;
-            else
-                goto reset;
+            goto reset;
         }
 
         if (e->id == HDR_CONTENT_LENGTH && (e2 = findEntry(e->id)) != NULL) {
@@ -1486,7 +1483,7 @@ HttpHeaderEntry::~HttpHeaderEntry()
 
 /* 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);
@@ -1503,19 +1500,42 @@ HttpHeaderEntry::parse(const char *field
 
     if (name_len > 65534) {
         /* String must be LESS THAN 64K and it adds a terminating NULL */
-        debugs(55, 1, "WARNING: ignoring header name of " << name_len << " bytes");
+        // 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;
     }
 
-    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 NULL;
+
+        // 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 NULL; // 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) << "'");
 
         while (name_len > 0 && xisspace(field_start[name_len - 1]))
             name_len--;
 
-        if (!name_len)
+        if (!name_len) {
+            debugs(55, 2, "found header with only whitespace for name");
             return NULL;
+        }
     }
 
     /* now we know we can parse it */
@@ -1549,11 +1569,7 @@ HttpHeaderEntry::parse(const char *field
 
     if (field_end - value_start > 65534) {
         /* String must be LESS THAN 64K and it adds a terminating NULL */
-        debugs(55, 1, "WARNING: ignoring '" << name << "' header of " << (field_end - value_start) << " bytes");
-
-        if (id == HDR_OTHER)
-            name.clean();
-
+        debugs(55, 2, "WARNING: found '" << name << "' header of " << (field_end - value_start) << " bytes");
         return NULL;
     }
 
Index: squid-3.1.23/src/HttpHeader.h
===================================================================
--- squid-3.1.23.orig/src/HttpHeader.h
+++ squid-3.1.23/src/HttpHeader.h
@@ -188,7 +188,7 @@ class HttpHeaderEntry
 public:
     HttpHeaderEntry(http_hdr_type id, const char *name, const char *value);
     ~HttpHeaderEntry();
-    static HttpHeaderEntry *parse(const char *field_start, const char *field_end);
+    static HttpHeaderEntry *parse(const char *field_start, const char *field_end, const http_hdr_owner_type msgType);
     HttpHeaderEntry *clone() const;
     void packInto(Packer *p) const;
     int getInt() const;
Index: squid-3.1.23/src/Debug.h
===================================================================
--- squid-3.1.23.orig/src/Debug.h
+++ squid-3.1.23/src/Debug.h
@@ -133,4 +133,46 @@ inline std::ostream& operator <<(std::os
     return (os << (int)d);
 }
 
+
+
+/// Prints raw and/or non-terminated data safely, efficiently, and beautifully.
+/// Allows raw data debugging in debugs() statements with low debugging levels
+/// by printing only if higher section debugging levels are configured:
+///   debugs(11, DBG_IMPORTANT, "always printed" << Raw(may be printed...));
+class Raw
+{
+public:
+    Raw(const char *label, const char *data, const size_t size):
+        level(-1), label_(label), data_(data), size_(size) {}
+
+    /// limit data printing to at least the given debugging level
+    Raw &minLevel(const int aLevel) { level = aLevel; return *this; }
+
+    /// If debugging is prohibited by the current debugs() or section level,
+    /// prints nothing. Otherwise, dumps data using one of these formats:
+    ///   " label[size]=data" if label was set and data size is positive
+    ///   " label[0]" if label was set and data size is zero
+    ///   " data" if label was not set and data size is positive
+    ///   "" (i.e., prints nothing) if label was not set and data size is zero
+    std::ostream &print(std::ostream &os) const;
+
+    /// Minimum section debugging level necessary for printing. By default,
+    /// small strings are always printed while large strings are only printed
+    /// if DBG_DATA debugging level is enabled.
+    int level;
+
+private:
+    const char *label_; ///< optional data name or ID; triggers size printing
+    const char *data_; ///< raw data to be printed
+    size_t size_; ///< data length
+};
+
+inline
+std::ostream &operator <<(std::ostream &os, const Raw &raw)
+{
+    return raw.print(os);
+}
+
+
+
 #endif /* SQUID_DEBUG_H */
Index: squid-3.1.23/src/debug.cc
===================================================================
--- squid-3.1.23.orig/src/debug.cc
+++ squid-3.1.23/src/debug.cc
@@ -770,3 +770,27 @@ Debug::xassert(const char *msg, const ch
 }
 
 std::ostringstream (*Debug::CurrentDebug)(NULL);
+
+std::ostream &
+Raw::print(std::ostream &os) const
+{
+    if (label_)
+        os << ' ' << label_ << '[' << size_ << ']';
+
+    if (!size_)
+        return os;
+
+    // finalize debugging level if no level was set explicitly via minLevel()
+//    const int finalLevel = (level >= 0) ? level :
+//                           (size_ > 40 ? DBG_DATA : Debug::SectionLevel());
+//    if (finalLevel <= Debug::SectionLevel()) {
+        os << (label_ ? '=' : ' ');
+        if (data_)
+            os.write(data_, size_);
+        else
+            os << "[null]";
+//    }
+
+    return os;
+}
+
