from:

commit fd68382860633aca92065e6c343cfd1b12b126e7
Author: Amos Jeffries <yadij@users.noreply.github.com>
Date:   Sun Aug 16 02:21:22 2020 +0000

    Improve Transfer-Encoding handling (#702)
    
    Reject messages containing Transfer-Encoding header with coding other
    than chunked or identity. Squid does not support other codings.
    
    For simplicity and security sake, also reject messages where
    Transfer-Encoding contains unnecessary complex values that are
    technically equivalent to "chunked" or "identity" (e.g., ",,chunked" or
    "identity, chunked").
    
    RFC 7230 formally deprecated and removed identity coding, but it is
    still used by some agents.


Index: squid-3.5.21/src/HttpHeader.cc
===================================================================
--- squid-3.5.21.orig/src/HttpHeader.cc
+++ squid-3.5.21/src/HttpHeader.cc
@@ -469,6 +469,7 @@ HttpHeader::operator =(const HttpHeader
         update(&other, NULL); // will update the mask as well
         len = other.len;
         conflictingContentLength_ = other.conflictingContentLength_;
+        teUnsupported_ = other.teUnsupported_;
     }
     return *this;
 }
@@ -518,6 +519,7 @@ HttpHeader::clean()
     httpHeaderMaskInit(&mask, 0);
     len = 0;
     conflictingContentLength_ = false;
+    teUnsupported_ = false;
     PROF_stop(HttpHeaderClean);
 }
 
@@ -723,11 +725,22 @@ HttpHeader::parse(const char *header_sta
                Raw("header", header_start, header_end - header_start));
     }
 
-    if (chunked()) {
+    if (has(HDR_TRANSFER_ENCODING)) {
+        const String rawTe = getStrOrList(HDR_TRANSFER_ENCODING);
         // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
         delById(HDR_CONTENT_LENGTH);
         // RFC 7230 section 3.3.3 #4: ignore Content-Length conflicts with Transfer-Encoding
         conflictingContentLength_ = false;
+
+        if (rawTe == "chunked") {
+            ; // leave header present for chunked() method
+        } else if (rawTe == "identity") { // deprecated. no coding
+            delById(HDR_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(HDR_CONTENT_LENGTH);
Index: squid-3.5.21/src/HttpHeader.h
===================================================================
--- squid-3.5.21.orig/src/HttpHeader.h
+++ squid-3.5.21/src/HttpHeader.h
@@ -267,7 +267,13 @@ public:
     int hasListMember(http_hdr_type id, const char *member, const char separator) const;
     int hasByNameListMember(const char *name, const char *member, const char separator) const;
     void removeHopByHopEntries();
-    inline bool chunked() const; ///< whether message uses chunked Transfer-Encoding
+
+    /// whether the message uses chunked Transfer-Encoding
+    /// optimized implementation relies on us rejecting/removing other codings
+    bool chunked() const { return has(HDR_TRANSFER_ENCODING); }
+
+    /// whether message used an unsupported and/or invalid Transfer-Encoding
+    bool unsupportedTe() const { return teUnsupported_; }
 
     /* protected, do not use these, use interface functions instead */
     std::vector<HttpHeaderEntry *> entries;     /**< parsed fields in raw format */
@@ -282,6 +288,9 @@ protected:
 private:
     HttpHeaderEntry *findLastEntry(http_hdr_type id) const;
     bool conflictingContentLength_; ///< found different Content-Length fields
+    /// unsupported encoding, unnecessary syntax characters, and/or
+    /// invalid field-value found in Transfer-Encoding header
+    bool teUnsupported_ = false;
 };
 
 int httpHeaderParseQuotedString(const char *start, const int len, String *val);
@@ -293,13 +302,6 @@ int httpHeaderHasByNameListMember(const
 void httpHeaderUpdate(HttpHeader * old, const HttpHeader * fresh, const HttpHeaderMask * denied_mask);
 void httpHeaderCalcMask(HttpHeaderMask * mask, http_hdr_type http_hdr_type_enums[], size_t count);
 
-inline bool
-HttpHeader::chunked() const
-{
-    return has(HDR_TRANSFER_ENCODING) &&
-           hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',');
-}
-
 void httpHeaderInitModule(void);
 void httpHeaderCleanModule(void);
 
Index: squid-3.5.21/src/client_side.cc
===================================================================
--- squid-3.5.21.orig/src/client_side.cc
+++ squid-3.5.21/src/client_side.cc
@@ -2581,9 +2581,7 @@ clientProcessRequest(ConnStateData *conn
     ClientHttpRequest *http = context->http;
     HttpRequest::Pointer request;
     bool notedUseOfBuffer = false;
-    bool chunked = false;
     bool mustReplyToOptions = false;
-    bool unsupportedTe = false;
     bool expectBody = false;
 
     // temporary hack to avoid splitting this huge function with sensitive code
@@ -2767,13 +2765,7 @@ clientProcessRequest(ConnStateData *conn
     // TODO: this effectively obsoletes a lot of conn->FOO copying. That needs cleaning up later.
     request->clientConnectionManager = conn;
 
-    if (request->header.chunked()) {
-        chunked = true;
-    } else if (request->header.has(HDR_TRANSFER_ENCODING)) {
-        const String te = request->header.getList(HDR_TRANSFER_ENCODING);
-        // HTTP/1.1 requires chunking to be the last encoding if there is one
-        unsupportedTe = te.size() && te != "identity";
-    } // else implied identity coding
+    const auto unsupportedTe = request->header.unsupportedTe();
 
     mustReplyToOptions = (method == Http::METHOD_OPTIONS) &&
                          (request->header.getInt64(HDR_MAX_FORWARDS) == 0);
@@ -2791,6 +2783,7 @@ clientProcessRequest(ConnStateData *conn
         return;
     }
 
+    const auto chunked = request->header.chunked();
     if (!chunked && !clientIsContentLengthValid(request.getRaw())) {
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
Index: squid-3.5.21/src/http.cc
===================================================================
--- squid-3.5.21.orig/src/http.cc
+++ squid-3.5.21/src/http.cc
@@ -1265,6 +1265,9 @@ HttpStateData::continueAfterParsingHeade
             } else if (vrep->header.conflictingContentLength()) {
                 fwd->dontRetry(true);
                 error = ERR_INVALID_RESP;
+            } else if (vrep->header.unsupportedTe()) {
+                fwd->dontRetry(true);
+                error = ERR_INVALID_RESP;
             } else {
                 return true; // done parsing, got reply, and no error
             }
