diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index ce55a6f..6ce06f2 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -470,6 +470,7 @@ HttpHeader::operator =(const HttpHeader &other) update(&other, NULL); // will update the mask as well len = other.len; conflictingContentLength_ = other.conflictingContentLength_; + teUnsupported_ = other.teUnsupported_; } return *this; } @@ -519,6 +520,7 @@ HttpHeader::clean() httpHeaderMaskInit(&mask, 0); len = 0; conflictingContentLength_ = false; + teUnsupported_ = false; PROF_stop(HttpHeaderClean); } @@ -717,12 +719,24 @@ HttpHeader::parse(const char *header_start, const char *header_end) Raw("header", header_start, (size_t)(header_end - header_start))); } - if (chunked()) { + + + String rawTe; + if (getByIdIfPresent(HDR_TRANSFER_ENCODING, &rawTe)) { // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding // RFC 7230 section 3.3.3 #3: Transfer-Encoding overwrites Content-Length delById(HDR_CONTENT_LENGTH); - // and clen state becomes irrelevant + + 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); @@ -1084,6 +1098,18 @@ HttpHeader::getStrOrList(http_hdr_type id) const return String(); } +bool +HttpHeader::getByIdIfPresent(http_hdr_type id, String *result) const +{ + if (id == HDR_BAD_HDR) + return false; + if (!has(id)) + return false; + if (result) + *result = getStrOrList(id); + return true; +} + /* * Returns the value of the specified header and/or an undefined String. */ diff --git a/src/HttpHeader.h b/src/HttpHeader.h index 836a26f..c49b105 100644 --- a/src/HttpHeader.h +++ b/src/HttpHeader.h @@ -239,6 +239,9 @@ public: bool getByNameIfPresent(const char *name, String &value) const; String getByNameListMember(const char *name, const char *member, const char separator) const; String getListMember(http_hdr_type id, const char *member, const char separator) const; + /// returns true iff a [possibly empty] field identified by id is there + /// when returning true, also sets the `result` parameter (if it is not nil) + bool getByIdIfPresent(http_hdr_type id, String *result) const; int has(http_hdr_type id) const; void putInt(http_hdr_type id, int number); void putInt64(http_hdr_type id, int64_t number); @@ -267,7 +270,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 entries; /**< parsed fields in raw format */ @@ -282,6 +291,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 +305,6 @@ int httpHeaderHasByNameListMember(const HttpHeader * hdr, const char *name, cons 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); diff --git a/src/client_side.cc b/src/client_side.cc index 261abdf..6858eb4 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2581,9 +2581,7 @@ clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *c 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, HttpParser *hp, ClientSocketContext *c // 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, HttpParser *hp, ClientSocketContext *c return; } + const auto chunked = request->header.chunked(); if (!chunked && !clientIsContentLengthValid(request.getRaw())) { clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); diff --git a/src/http.cc b/src/http.cc index 08531dc..f0fe648 100644 --- a/src/http.cc +++ b/src/http.cc @@ -1296,6 +1296,9 @@ HttpStateData::continueAfterParsingHeader() } 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 }