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