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/string_view.h"
10 #include "quiche/quic/core/http/quic_client_promised_info.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_logging.h"
16 #include "quiche/common/quiche_text_utils.h"
17 #include "quiche/spdy/core/spdy_protocol.h"
18
19 using spdy::Http2HeaderBlock;
20
21 namespace quic {
22
QuicSpdyClientStream(QuicStreamId id,QuicSpdyClientSession * session,StreamType type)23 QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
24 QuicSpdyClientSession* session,
25 StreamType type)
26 : QuicSpdyStream(id, session, type),
27 content_length_(-1),
28 response_code_(0),
29 header_bytes_read_(0),
30 header_bytes_written_(0),
31 session_(session),
32 has_preliminary_headers_(false) {}
33
QuicSpdyClientStream(PendingStream * pending,QuicSpdyClientSession * session)34 QuicSpdyClientStream::QuicSpdyClientStream(PendingStream* pending,
35 QuicSpdyClientSession* session)
36 : QuicSpdyStream(pending, session),
37 content_length_(-1),
38 response_code_(0),
39 header_bytes_read_(0),
40 header_bytes_written_(0),
41 session_(session),
42 has_preliminary_headers_(false) {}
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 if (response_code_ == 100 && !has_preliminary_headers_) {
78 // This is 100 Continue, save it to enable "Expect: 100-continue".
79 has_preliminary_headers_ = true;
80 preliminary_headers_ = std::move(response_headers_);
81 } else {
82 response_headers_.clear();
83 }
84 }
85
86 return true;
87 }
88
OnInitialHeadersComplete(bool fin,size_t frame_len,const QuicHeaderList & header_list)89 void QuicSpdyClientStream::OnInitialHeadersComplete(
90 bool fin, size_t frame_len, const QuicHeaderList& header_list) {
91 QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
92
93 QUICHE_DCHECK(headers_decompressed());
94 header_bytes_read_ += frame_len;
95 if (rst_sent()) {
96 // QuicSpdyStream::OnInitialHeadersComplete already rejected invalid
97 // response header.
98 return;
99 }
100
101 if (!CopyAndValidateHeaders(header_list, content_length_,
102 response_headers_)) {
103 QUIC_DLOG(ERROR) << "Failed to parse header list: "
104 << header_list.DebugString() << " on stream " << id();
105 Reset(QUIC_BAD_APPLICATION_PAYLOAD);
106 return;
107 }
108
109 if (web_transport() != nullptr) {
110 web_transport()->HeadersReceived(response_headers_);
111 if (!web_transport()->ready()) {
112 // The request was rejected by WebTransport, typically due to not having a
113 // 2xx status. The reason we're using Reset() here rather than closing
114 // cleanly is that even if the server attempts to send us any form of body
115 // with a 4xx request, we've already set up the capsule parser, and we
116 // don't have any way to process anything from the response body in
117 // question.
118 Reset(QUIC_STREAM_CANCELLED);
119 return;
120 }
121 }
122
123 if (!ParseAndValidateStatusCode()) {
124 return;
125 }
126
127 ConsumeHeaderList();
128 QUIC_DVLOG(1) << "headers complete for stream " << id();
129
130 session_->OnInitialHeadersComplete(id(), response_headers_);
131 }
132
OnTrailingHeadersComplete(bool fin,size_t frame_len,const QuicHeaderList & header_list)133 void QuicSpdyClientStream::OnTrailingHeadersComplete(
134 bool fin, size_t frame_len, const QuicHeaderList& header_list) {
135 QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
136 MarkTrailersConsumed();
137 }
138
OnPromiseHeaderList(QuicStreamId promised_id,size_t frame_len,const QuicHeaderList & header_list)139 void QuicSpdyClientStream::OnPromiseHeaderList(
140 QuicStreamId promised_id, size_t frame_len,
141 const QuicHeaderList& header_list) {
142 header_bytes_read_ += frame_len;
143 int64_t content_length = -1;
144 Http2HeaderBlock promise_headers;
145 if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
146 &promise_headers)) {
147 QUIC_DLOG(ERROR) << "Failed to parse promise headers: "
148 << header_list.DebugString();
149 Reset(QUIC_BAD_APPLICATION_PAYLOAD);
150 return;
151 }
152
153 session_->HandlePromised(id(), promised_id, promise_headers);
154 if (visitor() != nullptr) {
155 visitor()->OnPromiseHeadersComplete(promised_id, frame_len);
156 }
157 }
158
OnBodyAvailable()159 void QuicSpdyClientStream::OnBodyAvailable() {
160 // For push streams, visitor will not be set until the rendezvous
161 // between server promise and client request is complete.
162 if (visitor() == nullptr) return;
163
164 while (HasBytesToRead()) {
165 struct iovec iov;
166 if (GetReadableRegions(&iov, 1) == 0) {
167 // No more data to read.
168 break;
169 }
170 QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
171 << id();
172 data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
173
174 if (content_length_ >= 0 &&
175 data_.size() > static_cast<uint64_t>(content_length_)) {
176 QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
177 << ") with data of size " << data_.size();
178 Reset(QUIC_BAD_APPLICATION_PAYLOAD);
179 return;
180 }
181 MarkConsumed(iov.iov_len);
182 }
183 if (sequencer()->IsClosed()) {
184 OnFinRead();
185 } else {
186 sequencer()->SetUnblocked();
187 }
188 }
189
SendRequest(Http2HeaderBlock headers,absl::string_view body,bool fin)190 size_t QuicSpdyClientStream::SendRequest(Http2HeaderBlock headers,
191 absl::string_view body, bool fin) {
192 QuicConnection::ScopedPacketFlusher flusher(session_->connection());
193 bool send_fin_with_headers = fin && body.empty();
194 size_t bytes_sent = body.size();
195 header_bytes_written_ =
196 WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
197 bytes_sent += header_bytes_written_;
198
199 if (!body.empty()) {
200 WriteOrBufferBody(body, fin);
201 }
202
203 return bytes_sent;
204 }
205
AreHeadersValid(const QuicHeaderList & header_list) const206 bool QuicSpdyClientStream::AreHeadersValid(
207 const QuicHeaderList& header_list) const {
208 if (!GetQuicReloadableFlag(quic_verify_request_headers_2)) {
209 return true;
210 }
211 if (!QuicSpdyStream::AreHeadersValid(header_list)) {
212 return false;
213 }
214 // Verify the presence of :status header.
215 bool saw_status = false;
216 for (const std::pair<std::string, std::string>& pair : header_list) {
217 if (pair.first == ":status") {
218 saw_status = true;
219 } else if (absl::StrContains(pair.first, ":")) {
220 QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
221 return false;
222 }
223 }
224 return saw_status;
225 }
226
227 } // namespace quic
228