Fix QuicSpdyStream to allow "host" header in response header according to https://github.com/envoyproxy/envoy/issues/9873. Also rename ValidatedRequestHeaders() to ValidatedReceivedHeaders(). The behavioral change is client side only, and does not affect GFE backend. Protected by FLAGS_quic_reloadable_flag_quic_allow_host_header_in_response with default true. PiperOrigin-RevId: 549684429
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.cc b/quiche/quic/core/http/quic_spdy_client_stream.cc index 30ddbe2..e5da71c 100644 --- a/quiche/quic/core/http/quic_spdy_client_stream.cc +++ b/quiche/quic/core/http/quic_spdy_client_stream.cc
@@ -197,9 +197,9 @@ return bytes_sent; } -bool QuicSpdyClientStream::ValidatedRequestHeaders( +bool QuicSpdyClientStream::ValidatedReceivedHeaders( const QuicHeaderList& header_list) { - if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) { + if (!QuicSpdyStream::ValidatedReceivedHeaders(header_list)) { return false; } // Verify the presence of :status header.
diff --git a/quiche/quic/core/http/quic_spdy_client_stream.h b/quiche/quic/core/http/quic_spdy_client_stream.h index 089ad74..3f35d7d 100644 --- a/quiche/quic/core/http/quic_spdy_client_stream.h +++ b/quiche/quic/core/http/quic_spdy_client_stream.h
@@ -82,7 +82,7 @@ using QuicSpdyStream::SetPriority; protected: - bool ValidatedRequestHeaders(const QuicHeaderList& header_list) override; + bool ValidatedReceivedHeaders(const QuicHeaderList& header_list) override; // Called by OnInitialHeadersComplete to set response_header_. Returns false // on error.
diff --git a/quiche/quic/core/http/quic_spdy_client_stream_test.cc b/quiche/quic/core/http/quic_spdy_client_stream_test.cc index 2735b01..7310486 100644 --- a/quiche/quic/core/http/quic_spdy_client_stream_test.cc +++ b/quiche/quic/core/http/quic_spdy_client_stream_test.cc
@@ -160,6 +160,17 @@ EXPECT_EQ(body_, stream_->data()); } +TEST_P(QuicSpdyClientStreamTest, HostAllowedInResponseHeader) { + SetQuicReloadableFlag(quic_act_upon_invalid_header, true); + SetQuicReloadableFlag(quic_allow_host_header_in_response, true); + auto headers = AsHeaderList(std::vector<std::pair<std::string, std::string>>{ + {":status", "200"}, {"host", "example.com"}}); + EXPECT_CALL(*connection_, OnStreamReset(stream_->id(), _)).Times(0u); + stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(), + headers); + EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_STREAM_NO_ERROR)); +} + TEST_P(QuicSpdyClientStreamTest, Test100ContinueBeforeSuccessful) { // First send 100 Continue. headers_[":status"] = "100";
diff --git a/quiche/quic/core/http/quic_spdy_server_stream_base.cc b/quiche/quic/core/http/quic_spdy_server_stream_base.cc index 6176032..18e52a1 100644 --- a/quiche/quic/core/http/quic_spdy_server_stream_base.cc +++ b/quiche/quic/core/http/quic_spdy_server_stream_base.cc
@@ -49,9 +49,9 @@ QuicSpdyStream::StopReading(); } -bool QuicSpdyServerStreamBase::ValidatedRequestHeaders( +bool QuicSpdyServerStreamBase::ValidatedReceivedHeaders( const QuicHeaderList& header_list) { - if (!QuicSpdyStream::ValidatedRequestHeaders(header_list)) { + if (!QuicSpdyStream::ValidatedReceivedHeaders(header_list)) { return false; }
diff --git a/quiche/quic/core/http/quic_spdy_server_stream_base.h b/quiche/quic/core/http/quic_spdy_server_stream_base.h index 24133d5..d547841 100644 --- a/quiche/quic/core/http/quic_spdy_server_stream_base.h +++ b/quiche/quic/core/http/quic_spdy_server_stream_base.h
@@ -23,7 +23,7 @@ void StopReading() override; protected: - bool ValidatedRequestHeaders(const QuicHeaderList& header_list) override; + bool ValidatedReceivedHeaders(const QuicHeaderList& header_list) override; }; } // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc index ba1b6e3..749ac81 100644 --- a/quiche/quic/core/http/quic_spdy_session_test.cc +++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -257,7 +257,8 @@ MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override)); protected: - bool ValidatedRequestHeaders(const QuicHeaderList& /*header_list*/) override { + bool ValidatedReceivedHeaders( + const QuicHeaderList& /*header_list*/) override { return true; } };
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc index 9646d21..28b509a 100644 --- a/quiche/quic/core/http/quic_spdy_stream.cc +++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -621,7 +621,7 @@ } // Validate request headers if it did not exceed size limit. If it did, // OnHeadersTooLarge() should have already handled it previously. - if (!header_too_large && !ValidatedRequestHeaders(header_list)) { + if (!header_too_large && !ValidatedReceivedHeaders(header_list)) { QUIC_CODE_COUNT_N(quic_validate_request_header, 1, 2); QUICHE_DCHECK(!invalid_request_details().empty()) << "ValidatedRequestHeaders() returns false without populating " @@ -1678,7 +1678,7 @@ } } // namespace -bool QuicSpdyStream::ValidatedRequestHeaders( +bool QuicSpdyStream::ValidatedReceivedHeaders( const QuicHeaderList& header_list) { bool force_fail_validation = false; AdjustTestValue("quic::QuicSpdyStream::request_header_validation_adjust", @@ -1689,6 +1689,7 @@ QUIC_DLOG(ERROR) << invalid_request_details_; return false; } + bool is_response = false; for (const std::pair<std::string, std::string>& pair : header_list) { const std::string& name = pair.first; if (std::any_of(name.begin(), name.end(), isInvalidHeaderNameCharacter)) { @@ -1696,6 +1697,16 @@ QUIC_DLOG(ERROR) << invalid_request_details_; return false; } + if (GetQuicReloadableFlag(quic_allow_host_header_in_response)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_allow_host_header_in_response); + if (name == ":status") { + is_response = !pair.second.empty(); + } + if (is_response && name == "host") { + // Host header is allowed in response. + continue; + } + } if (http2::GetInvalidHttp2HeaderSet().contains(name)) { invalid_request_details_ = absl::StrCat(name, " header is not allowed"); QUIC_DLOG(ERROR) << invalid_request_details_;
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h index 66439ae..a43e9fb 100644 --- a/quiche/quic/core/http/quic_spdy_stream.h +++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -365,10 +365,10 @@ void OnWriteSideInDataRecvdState() override; - virtual bool ValidatedRequestHeaders(const QuicHeaderList& header_list); + virtual bool ValidatedReceivedHeaders(const QuicHeaderList& header_list); // TODO(b/202433856) Merge AreHeaderFieldValueValid into - // ValidatedRequestHeaders once all flags guarding the behavior of - // ValidatedRequestHeaders has been rolled out. + // ValidatedReceivedHeaders once all flags guarding the behavior of + // ValidatedReceivedHeaders has been rolled out. virtual bool AreHeaderFieldValuesValid( const QuicHeaderList& header_list) const;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h index 4528846..136d470 100644 --- a/quiche/quic/core/quic_flags_list.h +++ b/quiche/quic/core/quic_flags_list.h
@@ -19,6 +19,8 @@ QUIC_FLAG(quic_reloadable_flag_quic_block_until_settings_received_copt, false) // If trrue, early return before write control frame in OnCanWrite() if the connection is already closed. QUIC_FLAG(quic_reloadable_flag_quic_no_write_control_frame_upon_connection_close, true) +// If true, HTTP/3 client will allow host header in HTTP/3 response. +QUIC_FLAG(quic_reloadable_flag_quic_allow_host_header_in_response, true) // If true, QUIC BBR2 will ignore non-positive RTT samples. QUIC_FLAG(quic_reloadable_flag_quic_bbr2_ignore_bad_rtt_sample, false) // If true, QUIC server will not respond to gQUIC probing packet(PING + PADDING) but treat it as a regular packet.