When the GFE issues a 413 error due to excessively long request headers, also send a Clear-Site-Data: "cookies" response header, but only if the portion of the cookie header we have is greater than a certain length. For full context, see b/181849166. It ended up being a bit unfortunately gnarly to get access to the incomplete request headers in the HEADERS_TOO_LONG cases, requiring changes to the base BalsaFrame, HTTP/2, and QUIC, but I think the resulting state is not too unreasonable. That said, I'm open to suggestions of a better approach. Protected by gfe2_restart_flag_clear_long_cookies_on_413. PiperOrigin-RevId: 547269861
diff --git a/quiche/balsa/balsa_frame.cc b/quiche/balsa/balsa_frame.cc index 8dd4e97..6990e60 100644 --- a/quiche/balsa/balsa_frame.cc +++ b/quiche/balsa/balsa_frame.cc
@@ -843,7 +843,7 @@ // the max_header_length_ (for example after processing the first line) // we handle it gracefully. if (headers_->GetReadableBytesFromHeaderStream() > max_header_length_) { - HandleError(BalsaFrameEnums::HEADERS_TOO_LONG); + HandleHeadersTooLongError(); return message_current - original_message_start; } @@ -975,7 +975,7 @@ // READING_HEADER_AND_FIRSTLINE) in which case we directly declare an error. if (header_length > max_header_length_ || (header_length == max_header_length_ && size > 0)) { - HandleError(BalsaFrameEnums::HEADERS_TOO_LONG); + HandleHeadersTooLongError(); return current - input; } const size_t bytes_to_process = @@ -991,7 +991,7 @@ const size_t header_length_after = headers_->GetReadableBytesFromHeaderStream(); if (header_length_after >= max_header_length_) { - HandleError(BalsaFrameEnums::HEADERS_TOO_LONG); + HandleHeadersTooLongError(); } } return current - input; @@ -1339,6 +1339,29 @@ } } +void BalsaFrame::HandleHeadersTooLongError() { + if (parse_truncated_headers_even_when_headers_too_long_) { + const size_t len = headers_->GetReadableBytesFromHeaderStream(); + const char* stream_begin = headers_->OriginalHeaderStreamBegin(); + + if (last_slash_n_idx_ < len && stream_begin[last_slash_n_idx_] != '\r') { + // We write an end to the truncated line, and a blank line to end the + // headers, to end up with something that will parse. + static const absl::string_view kTwoLineEnds = "\r\n\r\n"; + headers_->WriteFromFramer(kTwoLineEnds.data(), kTwoLineEnds.size()); + + // This is the last, truncated line. + lines_.push_back(std::make_pair(last_slash_n_idx_, len + 2)); + // A blank line to end the headers. + lines_.push_back(std::make_pair(len + 2, len + 4)); + } + + ProcessHeaderLines(lines_, /*is_trailer=*/false, headers_); + } + + HandleError(BalsaFrameEnums::HEADERS_TOO_LONG); +} + const int32_t BalsaFrame::kValidTerm1; const int32_t BalsaFrame::kValidTerm1Mask; const int32_t BalsaFrame::kValidTerm2;
diff --git a/quiche/balsa/balsa_frame.h b/quiche/balsa/balsa_frame.h index 74e61d3..4711112 100644 --- a/quiche/balsa/balsa_frame.h +++ b/quiche/balsa/balsa_frame.h
@@ -201,6 +201,13 @@ use_interim_headers_callback_ = set; } + // If enabled, parse the available portion of headers even on a + // HEADERS_TOO_LONG error, so that that portion of headers is available to the + // error handler. Generally results in the last header being truncated. + void set_parse_truncated_headers_even_when_headers_too_long(bool set) { + parse_truncated_headers_even_when_headers_too_long_ = set; + } + protected: inline BalsaHeadersEnums::ContentLengthStatus ProcessContentLengthLine( size_t line_idx, size_t* length); @@ -271,6 +278,8 @@ void HandleError(BalsaFrameEnums::ErrorCode error_code); void HandleWarning(BalsaFrameEnums::ErrorCode error_code); + void HandleHeadersTooLongError(); + bool last_char_was_slash_r_; bool saw_non_newline_char_; bool start_was_space_; @@ -310,6 +319,9 @@ // TODO(b/68801833): Default-enable and then deprecate this field, along with // set_continue_headers(). bool use_interim_headers_callback_; + + // This is not reset in Reset(). + bool parse_truncated_headers_even_when_headers_too_long_ = false; }; } // namespace quiche
diff --git a/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc b/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc index 41d64e7..f2588fd 100644 --- a/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc +++ b/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc
@@ -46,10 +46,8 @@ : uncompressed_header_bytes_without_overhead_; if (uncompressed_header_bytes > max_header_list_size_) { header_list_size_limit_exceeded_ = true; - quic_header_list_.Clear(); - } else { - quic_header_list_.OnHeader(name, value); } + quic_header_list_.OnHeader(name, value); } void QpackDecodedHeadersAccumulator::OnDecodingCompleted() {