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