Add quiche::HttpValidationPolicy::validate_transfer_encoding. This should always be true for GFE2 so that behavior is not changed. However, Envoy has its own Transfer-Encoding validation (currently in source/common/http/http1/codec_impl.cc, which will be removed in favor of UHV), so it is desirable to skip validation in Balsa when used in Envoy. Adding Content-Length to test cases to avoid MAYBE_BODY_BUT_NO_CONTENT_LENGTH warning. PiperOrigin-RevId: 529654658
diff --git a/quiche/balsa/balsa_frame.cc b/quiche/balsa/balsa_frame.cc index 592a4db..f066c18 100644 --- a/quiche/balsa/balsa_frame.cc +++ b/quiche/balsa/balsa_frame.cc
@@ -529,7 +529,9 @@ return; } - HandleError(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING); + if (http_validation_policy().validate_transfer_encoding) { + HandleError(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING); + } } bool BalsaFrame::CheckHeaderLinesForInvalidChars(const Lines& lines, @@ -636,7 +638,8 @@ continue; } if (absl::EqualsIgnoreCase(key, kTransferEncoding)) { - if (transfer_encoding_idx != 0) { + if (http_validation_policy().validate_transfer_encoding && + transfer_encoding_idx != 0) { HandleError(BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS); return; } @@ -645,7 +648,8 @@ } if (!is_trailer) { - if (http_validation_policy() + if (http_validation_policy().validate_transfer_encoding && + http_validation_policy() .disallow_transfer_encoding_with_content_length && content_length_idx != 0 && transfer_encoding_idx != 0) { HandleError(BalsaFrameEnums::BOTH_TRANSFER_ENCODING_AND_CONTENT_LENGTH);
diff --git a/quiche/balsa/balsa_frame.h b/quiche/balsa/balsa_frame.h index f3f75e8..c6ec6cb 100644 --- a/quiche/balsa/balsa_frame.h +++ b/quiche/balsa/balsa_frame.h
@@ -141,10 +141,10 @@ return invalid_chars_level_ == InvalidCharsLevel::kError; } - void set_http_validation_policy(const quiche::HttpValidationPolicy& policy) { + void set_http_validation_policy(const HttpValidationPolicy& policy) { http_validation_policy_ = policy; } - const quiche::HttpValidationPolicy& http_validation_policy() const { + const HttpValidationPolicy& http_validation_policy() const { return http_validation_policy_; } @@ -309,7 +309,7 @@ // in Reset(). InvalidCharsLevel invalid_chars_level_; // This is not reset in Reset(). - quiche::HttpValidationPolicy http_validation_policy_; + HttpValidationPolicy http_validation_policy_; // This is not reset in Reset(). // TODO(b/68801833): Default-enable and then deprecate this field, along with
diff --git a/quiche/balsa/balsa_frame_test.cc b/quiche/balsa/balsa_frame_test.cc index 83619d4..ce6af47 100644 --- a/quiche/balsa/balsa_frame_test.cc +++ b/quiche/balsa/balsa_frame_test.cc
@@ -2952,6 +2952,7 @@ "HTTP/1.1 200 OK\r\n" "transfer-encoding: chunked\r\n" "transfer-encoding: identity\r\n" + "content-length: 3\r\n" "\r\n"; balsa_frame_.set_is_request(false); balsa_frame_.ProcessInput(header.data(), header.size()); @@ -2960,10 +2961,29 @@ balsa_frame_.ErrorCode()); } +TEST_F(HTTPBalsaFrameTest, AcceptTwoTransferEncodingHeaders) { + HttpValidationPolicy http_validation_policy; + http_validation_policy.validate_transfer_encoding = false; + balsa_frame_.set_http_validation_policy(http_validation_policy); + + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "transfer-encoding: identity\r\n" + "content-length: 3\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); +} + TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingTokensIsAnError) { std::string header = "HTTP/1.1 200 OK\r\n" "transfer-encoding: chunked, identity\r\n" + "content-length: 3\r\n" "\r\n"; balsa_frame_.set_is_request(false); balsa_frame_.ProcessInput(header.data(), header.size()); @@ -2972,10 +2992,28 @@ balsa_frame_.ErrorCode()); } +TEST_F(HTTPBalsaFrameTest, AcceptTwoTransferEncodingTokens) { + HttpValidationPolicy http_validation_policy; + http_validation_policy.validate_transfer_encoding = false; + balsa_frame_.set_http_validation_policy(http_validation_policy); + + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked, identity\r\n" + "content-length: 3\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); +} + TEST_F(HTTPBalsaFrameTest, UnknownTransferEncodingTokenIsAnError) { std::string header = "HTTP/1.1 200 OK\r\n" "transfer-encoding: chunked-identity\r\n" + "content-length: 3\r\n" "\r\n"; balsa_frame_.set_is_request(false); balsa_frame_.ProcessInput(header.data(), header.size()); @@ -2984,6 +3022,23 @@ balsa_frame_.ErrorCode()); } +TEST_F(HTTPBalsaFrameTest, AcceptUnknownTransferEncodingToken) { + HttpValidationPolicy http_validation_policy; + http_validation_policy.validate_transfer_encoding = false; + balsa_frame_.set_http_validation_policy(http_validation_policy); + + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked-identity\r\n" + "content-length: 3\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); +} + class DetachOnDoneFramer : public NoOpBalsaVisitor { public: DetachOnDoneFramer() {
diff --git a/quiche/balsa/http_validation_policy.h b/quiche/balsa/http_validation_policy.h index 725f48e..67ad534 100644 --- a/quiche/balsa/http_validation_policy.h +++ b/quiche/balsa/http_validation_policy.h
@@ -29,6 +29,14 @@ // https://tools.ietf.org/html/rfc7230#section-3.3.2 disallows // Transfer-Encoding and Content-Length header fields together. bool disallow_transfer_encoding_with_content_length = false; + + // If true, signal an error if Transfer-Encoding has a value other than + // "chunked" or "identity", or if there are multiple Transfer-Encoding field + // lines. If false, ignore inconsistencies with Transfer-Encoding field lines, + // also force `disallow_transfer_encoding_with_content_length` to false, but + // still make an effort to determine whether chunked transfer encoding is + // indicated. + bool validate_transfer_encoding = true; }; } // namespace quiche