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 const bool skip_return_on_null_visitor =
134 GetQuicReloadableFlag(quic_skip_return_on_null_visitor);
135 if (skip_return_on_null_visitor) {
136 QUICHE_RELOADABLE_FLAG_COUNT(quic_skip_return_on_null_visitor);
137 }
138 if (visitor() == nullptr) {
139 QUICHE_CODE_COUNT(quic_spdy_client_stream_visitor_null_on_body_available);
140 if (!skip_return_on_null_visitor) {
141 return;
142 }
143 }
144
145 while (HasBytesToRead()) {
146 struct iovec iov;
147 if (GetReadableRegions(&iov, 1) == 0) {
148 // No more data to read.
149 break;
150 }
151 QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
152 << id();
153 data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
154
155 if (content_length_ >= 0 &&
156 data_.size() > static_cast<uint64_t>(content_length_)) {
157 QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
158 << ") with data of size " << data_.size();
159 Reset(QUIC_BAD_APPLICATION_PAYLOAD);
160 return;
161 }
162 MarkConsumed(iov.iov_len);
163 }
164 if (sequencer()->IsClosed()) {
165 OnFinRead();
166 } else {
167 sequencer()->SetUnblocked();
168 }
169 }
170
SendRequest(Http2HeaderBlock headers,absl::string_view body,bool fin)171 size_t QuicSpdyClientStream::SendRequest(Http2HeaderBlock headers,
172 absl::string_view body, bool fin) {
173 QuicConnection::ScopedPacketFlusher flusher(session_->connection());
174 bool send_fin_with_headers = fin && body.empty();
175 size_t bytes_sent = body.size();
176 header_bytes_written_ =
177 WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
178 bytes_sent += header_bytes_written_;
179
180 if (!body.empty()) {
181 WriteOrBufferBody(body, fin);
182 }
183
184 return bytes_sent;
185 }
186
ValidateReceivedHeaders(const QuicHeaderList & header_list)187 bool QuicSpdyClientStream::ValidateReceivedHeaders(
188 const QuicHeaderList& header_list) {
189 if (!QuicSpdyStream::ValidateReceivedHeaders(header_list)) {
190 return false;
191 }
192 // Verify the presence of :status header.
193 bool saw_status = false;
194 for (const std::pair<std::string, std::string>& pair : header_list) {
195 if (pair.first == ":status") {
196 saw_status = true;
197 } else if (absl::StrContains(pair.first, ":")) {
198 set_invalid_request_details(
199 absl::StrCat("Unexpected ':' in header ", pair.first, "."));
200 QUIC_DLOG(ERROR) << invalid_request_details();
201 return false;
202 }
203 }
204 if (!saw_status) {
205 set_invalid_request_details("Missing :status in response header.");
206 QUIC_DLOG(ERROR) << invalid_request_details();
207 return false;
208 }
209 return saw_status;
210 }
211
OnFinRead()212 void QuicSpdyClientStream::OnFinRead() {
213 time_to_response_complete_ =
214 session()->GetClock()->ApproximateNow() - creation_time();
215 QuicSpdyStream::OnFinRead();
216 }
217
218 } // namespace quic
219