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