• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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