Fix `BalsaFrame` token corruption when sanitizing first line with multiple spaces. **Key Changes:** - Fixes a bug in `BalsaFrame::ParseHTTPFirstLine` where token extraction arithmetic applied indices from a new, sanitized storage block to the pointer of the original, un-sanitized raw buffer block. This yielded corrupted parser tokens (e.g., getting `" "` instead of `"/"` for a URL in `GET / HTTP/1.1`). - Passes `begin` by reference `(char*&)` to update it to the new buffer allocation, ensuring indices remain perfectly tied to their active memory block container. - Updates `balsa_frame_test.cc` to explicitly verify visitor callbacks (`OnRequestFirstLineInput` / `OnResponseFirstLineInput`) receive clean, uncorrupted component data. - Adds `SanitizeFirstlineSpaces_Request_Served` and `SanitizeFirstlineSpaces_Response_Served` to Envoy integration tests to verify the hook works seamlessly at the gateway level. Protected by sanitize_firstline_spaces (The flag is not in production yet). PiperOrigin-RevId: 966603762
diff --git a/quiche/balsa/balsa_frame.cc b/quiche/balsa/balsa_frame.cc index 60a3e36..747711b 100644 --- a/quiche/balsa/balsa_frame.cc +++ b/quiche/balsa/balsa_frame.cc
@@ -161,7 +161,7 @@ // ProcessFirstLine(begin, end, is_request, &headers, &error_code); // -bool ParseHTTPFirstLine(char* begin, char* end, bool is_request, +bool ParseHTTPFirstLine(char*& begin, char* end, bool is_request, BalsaHeaders* headers, BalsaFrameEnums::ErrorCode* error_code, FirstLineValidationOption whitespace_option, @@ -266,6 +266,8 @@ : BalsaFrameEnums::MULTIPLE_SPACES_IN_STATUS_LINE; return false; } + QUICHE_DCHECK(multiple_spaces_option == + FirstLineValidationOption::SANITIZE); const absl::string_view part1( begin + headers->non_whitespace_1_idx_, headers->whitespace_2_idx_ - headers->non_whitespace_1_idx_); @@ -278,6 +280,7 @@ QUICHE_CODE_COUNT(sanitize_firstline_spaces_sanitized); headers->SetRequestFirstlineFromStringPieces(part1, part2, part3); + begin = headers->BeginningOfFirstLine(); } return true;
diff --git a/quiche/balsa/balsa_frame_test.cc b/quiche/balsa/balsa_frame_test.cc index 5799eef..962ec61 100644 --- a/quiche/balsa/balsa_frame_test.cc +++ b/quiche/balsa/balsa_frame_test.cc
@@ -2414,6 +2414,8 @@ balsa_frame_.set_http_validation_policy(http_validation_policy); EXPECT_CALL(visitor_mock_, ProcessHeaders(_)); EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); const std::string message1 = "GET / HTTP/1.1\r\n" "Host: 1.1.1.1\r\n" @@ -2473,6 +2475,8 @@ balsa_frame_.set_is_request(false); EXPECT_CALL(visitor_mock_, ProcessHeaders(_)); EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput("HTTP/1.1 200 OK", + "HTTP/1.1", "200", "OK")); const std::string message1 = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n"
diff --git a/quiche/balsa/balsa_headers.h b/quiche/balsa/balsa_headers.h index 9f63838..b4664ca 100644 --- a/quiche/balsa/balsa_headers.h +++ b/quiche/balsa/balsa_headers.h
@@ -1062,7 +1062,7 @@ friend class test::BalsaHeadersTestPeer; friend bool ParseHTTPFirstLine( - char* begin, char* end, bool is_request, BalsaHeaders* headers, + char*& begin, char* end, bool is_request, BalsaHeaders* headers, BalsaFrameEnums::ErrorCode* error_code, HttpValidationPolicy::FirstLineValidationOption whitespace_option, HttpValidationPolicy::FirstLineValidationOption multiple_spaces_option,