Add Balsa tests documenting \r detection behavior.

PiperOrigin-RevId: 644119129
diff --git a/quiche/balsa/balsa_frame_test.cc b/quiche/balsa/balsa_frame_test.cc
index e495bc0..75d041f 100644
--- a/quiche/balsa/balsa_frame_test.cc
+++ b/quiche/balsa/balsa_frame_test.cc
@@ -52,9 +52,7 @@
     "This is the seed for Pseudo-random number"
     " generator used when generating random messages for unittests");
 
-namespace quiche {
-
-namespace test {
+namespace quiche::test {
 
 // This random engine from the standard library supports initialization with a
 // seed, which is helpful for reproducing any unit test failures that are due to
@@ -860,6 +858,46 @@
   EXPECT_EQ(framer.get_invalid_chars().at('\r'), 2);
 }
 
+// Test that lone '\r' detection works correctly in the firstline
+// even if it is the last character of fractional input.
+TEST(HTTPBalsaFrame, CarriageReturnIllegalInFirstLineOnInputBoundary) {
+  HttpValidationPolicy policy{.disallow_lone_cr_in_request_headers = true};
+  BalsaHeaders balsa_headers;
+  BalsaFrame framer;
+  framer.set_is_request(true);
+  framer.set_balsa_headers(&balsa_headers);
+  framer.set_http_validation_policy(policy);
+  framer.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError);
+  constexpr absl::string_view message1("GET / \r");
+  constexpr absl::string_view message2("HTTP/1.1\r\n\r\n");
+  EXPECT_EQ(message1.size(),
+            framer.ProcessInput(message1.data(), message1.size()));
+  EXPECT_EQ(message2.size(),
+            framer.ProcessInput(message2.data(), message2.size()));
+  EXPECT_EQ(framer.ErrorCode(), BalsaFrameEnums::INVALID_HEADER_CHARACTER);
+  EXPECT_EQ(framer.get_invalid_chars().at('\r'), 1);
+}
+
+// Test that lone '\r' detection works correctly in header values
+// even if it is the last character of fractional input.
+TEST(HTTPBalsaFrame, CarriageReturnIllegalInHeaderValueOnInputBoundary) {
+  HttpValidationPolicy policy{.disallow_lone_cr_in_request_headers = true};
+  BalsaHeaders balsa_headers;
+  BalsaFrame framer;
+  framer.set_is_request(true);
+  framer.set_balsa_headers(&balsa_headers);
+  framer.set_http_validation_policy(policy);
+  framer.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError);
+  constexpr absl::string_view message1("GET / HTTP/1.1\r\nfoo: b\r");
+  constexpr absl::string_view message2("ar\r\n\r\n");
+  EXPECT_EQ(message1.size(),
+            framer.ProcessInput(message1.data(), message1.size()));
+  EXPECT_EQ(message2.size(),
+            framer.ProcessInput(message2.data(), message2.size()));
+  EXPECT_EQ(framer.ErrorCode(), BalsaFrameEnums::INVALID_HEADER_CHARACTER);
+  EXPECT_EQ(framer.get_invalid_chars().at('\r'), 1);
+}
+
 TEST(HTTPBalsaFrame, CarriageReturnIllegalInHeaderKey) {
   BalsaHeaders balsa_headers;
   BalsaFrame framer;
@@ -1690,6 +1728,24 @@
                             message.size());
 }
 
+// TODO(b/347710034): Fix behavior.
+TEST_F(HTTPBalsaFrameTest,
+       ChunkExtensionLoneCarriageReturnDetectionAtBoundary) {
+  balsa_frame_.set_http_validation_policy(
+      HttpValidationPolicy{.disallow_lone_cr_in_chunk_extension = true});
+  EXPECT_CALL(visitor_mock_, ProcessHeaders(_));
+  EXPECT_CALL(visitor_mock_, HeaderDone());
+  constexpr absl::string_view headers(
+      "POST / HTTP/1.1\r\n"
+      "transfer-encoding: chunked\r\n\r\n");
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+
+  constexpr absl::string_view body1("3\r");
+  ASSERT_EQ(1, balsa_frame_.ProcessInput(body1.data(), body1.size()));
+  EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_EXTENSION, balsa_frame_.ErrorCode());
+}
+
 TEST_F(HTTPBalsaFrameTest,
        VisitorInvokedProperlyForRequestWithTransferEncoding) {
   std::string message_headers =
@@ -4295,7 +4351,4 @@
 }
 
 }  // namespace
-
-}  // namespace test
-
-}  // namespace quiche
+}  // namespace quiche::test