xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_client_stream.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "quiche/quic/core/http/quic_spdy_client_stream.h"
6 
7 #include <utility>
8 
9 #include "absl/strings/str_cat.h"
10 #include "absl/strings/string_view.h"
11 #include "quiche/quic/core/http/quic_spdy_client_session.h"
12 #include "quiche/quic/core/http/spdy_utils.h"
13 #include "quiche/quic/core/http/web_transport_http3.h"
14 #include "quiche/quic/core/quic_alarm.h"
15 #include "quiche/quic/platform/api/quic_flags.h"
16 #include "quiche/quic/platform/api/quic_logging.h"
17 #include "quiche/common/platform/api/quiche_flag_utils.h"
18 #include "quiche/common/quiche_text_utils.h"
19 #include "quiche/spdy/core/spdy_protocol.h"
20 
21 using spdy::Http2HeaderBlock;
22 
23 namespace quic {
24 
QuicSpdyClientStream(QuicStreamId id,QuicSpdyClientSession * session,StreamType type)25 QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
26                                            QuicSpdyClientSession* session,
27                                            StreamType type)
28     : QuicSpdyStream(id, session, type),
29       content_length_(-1),
30       response_code_(0),
31       header_bytes_read_(0),
32       header_bytes_written_(0),
33       session_(session) {}
34 
QuicSpdyClientStream(PendingStream * pending,QuicSpdyClientSession * session)35 QuicSpdyClientStream::QuicSpdyClientStream(PendingStream* pending,
36                                            QuicSpdyClientSession* session)
37     : QuicSpdyStream(pending, session),
38       content_length_(-1),
39       response_code_(0),
40       header_bytes_read_(0),
41       header_bytes_written_(0),
42       session_(session) {}
43 
44 QuicSpdyClientStream::~QuicSpdyClientStream() = default;
45 
CopyAndValidateHeaders(const QuicHeaderList & header_list,int64_t & content_length,spdy::Http2HeaderBlock & headers)46 bool QuicSpdyClientStream::CopyAndValidateHeaders(
47     const QuicHeaderList& header_list, int64_t& content_length,
48     spdy::Http2HeaderBlock& headers) {
49   return SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
50                                            &headers);
51 }
52 
ParseAndValidateStatusCode()53 bool QuicSpdyClientStream::ParseAndValidateStatusCode() {
54   if (!ParseHeaderStatusCode(response_headers_, &response_code_)) {
55     QUIC_DLOG(ERROR) << "Received invalid response code: "
56                      << response_headers_[":status"].as_string()
57                      << " on stream " << id();
58     Reset(QUIC_BAD_APPLICATION_PAYLOAD);
59     return false;
60   }
61 
62   if (response_code_ == 101) {
63     // 101 "Switching Protocols" is forbidden in HTTP/3 as per the
64     // "HTTP Upgrade" section of draft-ietf-quic-http.
65     QUIC_DLOG(ERROR) << "Received forbidden 101 response code"
66                      << " on stream " << id();
67     Reset(QUIC_BAD_APPLICATION_PAYLOAD);
68     return false;
69   }
70 
71   if (response_code_ >= 100 && response_code_ < 200) {
72     // These are Informational 1xx headers, not the actual response headers.
73     QUIC_DLOG(INFO) << "Received informational response code: "
74                     << response_headers_[":status"].as_string() << " on stream "
75                     << id();
76     set_headers_decompressed(false);
77     preliminary_headers_.push_back(std::move(response_headers_));
78   }
79 
80   return true;
81 }
82 
OnInitialHeadersComplete(bool fin,size_t frame_len,const QuicHeaderList & header_list)83 void QuicSpdyClientStream::OnInitialHeadersComplete(
84     bool fin, size_t frame_len, const QuicHeaderList& header_list) {
85   QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
86   time_to_response_headers_received_ =
87       session()->GetClock()->ApproximateNow() - creation_time();
88   QUICHE_DCHECK(headers_decompressed());
89   header_bytes_read_ += frame_len;
90   if (rst_sent()) {
91     // QuicSpdyStream::OnInitialHeadersComplete already rejected invalid
92     // response header.
93     return;
94   }
95 
96   if (!CopyAndValidateHeaders(header_list, content_length_,
97                               response_headers_)) {
98     QUIC_DLOG(ERROR) << "Failed to parse header list: "
99                      << header_list.DebugString() << " on stream " << id();
100     Reset(QUIC_BAD_APPLICATION_PAYLOAD);
101     return;
102   }
103 
104   if (web_transport() != nullptr) {
105     web_transport()->HeadersReceived(response_headers_);
106     if (!web_transport()->ready()) {
107       // The request was rejected by WebTransport, typically due to not having a
108       // 2xx status.  The reason we're using Reset() here rather than closing
109       // cleanly is that even if the server attempts to send us any form of body
110       // with a 4xx request, we've already set up the capsule parser, and we
111       // don't have any way to process anything from the response body in
112       // question.
113       Reset(QUIC_STREAM_CANCELLED);
114       return;
115     }
116   }
117 
118   if (!ParseAndValidateStatusCode()) {
119     return;
120   }
121 
122   ConsumeHeaderList();
123   QUIC_DVLOG(1) << "headers complete for stream " << id();
124 }
125 
OnTrailingHeadersComplete(bool fin,size_t frame_len,const QuicHeaderList & header_list)126 void QuicSpdyClientStream::OnTrailingHeadersComplete(
127     bool fin, size_t frame_len, const QuicHeaderList& header_list) {
128   QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
129   MarkTrailersConsumed();
130 }
131 
OnBodyAvailable()132 void QuicSpdyClientStream::OnBodyAvailable() {
133   while (HasBytesToRead()) {
134     struct iovec iov;
135     if (GetReadableRegions(&iov, 1) == 0) {
136       // No more data to read.
137       break;
138     }
139     QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
140                   << id();
141     data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
142 
143     if (content_length_ >= 0 &&
144         data_.size() > static_cast<uint64_t>(content_length_)) {
145       QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
146                        << ") with data of size " << data_.size();
147       Reset(QUIC_BAD_APPLICATION_PAYLOAD);
148       return;
149     }
150     MarkConsumed(iov.iov_len);
151   }
152   if (sequencer()->IsClosed()) {
153     OnFinRead();
154   } else {
155     sequencer()->SetUnblocked();
156   }
157 }
158 
SendRequest(Http2HeaderBlock headers,absl::string_view body,bool fin)159 size_t QuicSpdyClientStream::SendRequest(Http2HeaderBlock headers,
160                                          absl::string_view body, bool fin) {
161   QuicConnection::ScopedPacketFlusher flusher(session_->connection());
162   bool send_fin_with_headers = fin && body.empty();
163   size_t bytes_sent = body.size();
164   header_bytes_written_ =
165       WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
166   bytes_sent += header_bytes_written_;
167 
168   if (!body.empty()) {
169     WriteOrBufferBody(body, fin);
170   }
171 
172   return bytes_sent;
173 }
174 
ValidateReceivedHeaders(const QuicHeaderList & header_list)175 bool QuicSpdyClientStream::ValidateReceivedHeaders(
176     const QuicHeaderList& header_list) {
177   if (!QuicSpdyStream::ValidateReceivedHeaders(header_list)) {
178     return false;
179   }
180   // Verify the presence of :status header.
181   bool saw_status = false;
182   for (const std::pair<std::string, std::string>& pair : header_list) {
183     if (pair.first == ":status") {
184       saw_status = true;
185     } else if (absl::StrContains(pair.first, ":")) {
186       set_invalid_request_details(
187           absl::StrCat("Unexpected ':' in header ", pair.first, "."));
188       QUIC_DLOG(ERROR) << invalid_request_details();
189       return false;
190     }
191   }
192   if (!saw_status) {
193     set_invalid_request_details("Missing :status in response header.");
194     QUIC_DLOG(ERROR) << invalid_request_details();
195     return false;
196   }
197   return saw_status;
198 }
199 
OnFinRead()200 void QuicSpdyClientStream::OnFinRead() {
201   time_to_response_complete_ =
202       session()->GetClock()->ApproximateNow() - creation_time();
203   QuicSpdyStream::OnFinRead();
204 }
205 
206 }  // namespace quic
207