• 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/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