blob: 1661c94bdfd5b022c55dc6f411b221238e789a70 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/core/http/quic_spdy_client_stream.h"
#include <utility>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/http/quic_client_promised_info.h"
#include "quiche/quic/core/http/quic_spdy_client_session.h"
#include "quiche/quic/core/http/spdy_utils.h"
#include "quiche/quic/core/http/web_transport_http3.h"
#include "quiche/quic/core/quic_alarm.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/common/quiche_text_utils.h"
#include "quiche/spdy/core/spdy_protocol.h"
using spdy::Http2HeaderBlock;
namespace quic {
QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
QuicSpdyClientSession* session,
StreamType type)
: QuicSpdyStream(id, session, type),
content_length_(-1),
response_code_(0),
header_bytes_read_(0),
header_bytes_written_(0),
session_(session),
has_preliminary_headers_(false) {}
QuicSpdyClientStream::QuicSpdyClientStream(PendingStream* pending,
QuicSpdyClientSession* session)
: QuicSpdyStream(pending, session),
content_length_(-1),
response_code_(0),
header_bytes_read_(0),
header_bytes_written_(0),
session_(session),
has_preliminary_headers_(false) {}
QuicSpdyClientStream::~QuicSpdyClientStream() = default;
bool QuicSpdyClientStream::CopyAndValidateHeaders(
const QuicHeaderList& header_list, int64_t& content_length,
spdy::Http2HeaderBlock& headers) {
return SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
&headers);
}
bool QuicSpdyClientStream::ParseAndValidateStatusCode() {
if (!ParseHeaderStatusCode(response_headers_, &response_code_)) {
QUIC_DLOG(ERROR) << "Received invalid response code: "
<< response_headers_[":status"].as_string()
<< " on stream " << id();
Reset(QUIC_BAD_APPLICATION_PAYLOAD);
return false;
}
if (response_code_ == 101) {
// 101 "Switching Protocols" is forbidden in HTTP/3 as per the
// "HTTP Upgrade" section of draft-ietf-quic-http.
QUIC_DLOG(ERROR) << "Received forbidden 101 response code"
<< " on stream " << id();
Reset(QUIC_BAD_APPLICATION_PAYLOAD);
return false;
}
if (response_code_ >= 100 && response_code_ < 200) {
// These are Informational 1xx headers, not the actual response headers.
QUIC_DLOG(INFO) << "Received informational response code: "
<< response_headers_[":status"].as_string() << " on stream "
<< id();
set_headers_decompressed(false);
if (response_code_ == 100 && !has_preliminary_headers_) {
// This is 100 Continue, save it to enable "Expect: 100-continue".
has_preliminary_headers_ = true;
preliminary_headers_ = std::move(response_headers_);
} else {
response_headers_.clear();
}
}
return true;
}
void QuicSpdyClientStream::OnInitialHeadersComplete(
bool fin, size_t frame_len, const QuicHeaderList& header_list) {
QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
QUICHE_DCHECK(headers_decompressed());
header_bytes_read_ += frame_len;
if (rst_sent()) {
// QuicSpdyStream::OnInitialHeadersComplete already rejected invalid
// response header.
return;
}
if (!CopyAndValidateHeaders(header_list, content_length_,
response_headers_)) {
QUIC_DLOG(ERROR) << "Failed to parse header list: "
<< header_list.DebugString() << " on stream " << id();
Reset(QUIC_BAD_APPLICATION_PAYLOAD);
return;
}
if (web_transport() != nullptr) {
web_transport()->HeadersReceived(response_headers_);
if (!web_transport()->ready()) {
// The request was rejected by WebTransport, typically due to not having a
// 2xx status. The reason we're using Reset() here rather than closing
// cleanly is that even if the server attempts to send us any form of body
// with a 4xx request, we've already set up the capsule parser, and we
// don't have any way to process anything from the response body in
// question.
Reset(QUIC_STREAM_CANCELLED);
return;
}
}
if (!ParseAndValidateStatusCode()) {
return;
}
ConsumeHeaderList();
QUIC_DVLOG(1) << "headers complete for stream " << id();
session_->OnInitialHeadersComplete(id(), response_headers_);
}
void QuicSpdyClientStream::OnTrailingHeadersComplete(
bool fin, size_t frame_len, const QuicHeaderList& header_list) {
QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
MarkTrailersConsumed();
}
void QuicSpdyClientStream::OnPromiseHeaderList(
QuicStreamId promised_id, size_t frame_len,
const QuicHeaderList& header_list) {
header_bytes_read_ += frame_len;
int64_t content_length = -1;
Http2HeaderBlock promise_headers;
if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
&promise_headers)) {
QUIC_DLOG(ERROR) << "Failed to parse promise headers: "
<< header_list.DebugString();
Reset(QUIC_BAD_APPLICATION_PAYLOAD);
return;
}
session_->HandlePromised(id(), promised_id, promise_headers);
if (visitor() != nullptr) {
visitor()->OnPromiseHeadersComplete(promised_id, frame_len);
}
}
void QuicSpdyClientStream::OnBodyAvailable() {
// For push streams, visitor will not be set until the rendezvous
// between server promise and client request is complete.
if (visitor() == nullptr) return;
while (HasBytesToRead()) {
struct iovec iov;
if (GetReadableRegions(&iov, 1) == 0) {
// No more data to read.
break;
}
QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
<< id();
data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
if (content_length_ >= 0 &&
data_.size() > static_cast<uint64_t>(content_length_)) {
QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
<< ") with data of size " << data_.size();
Reset(QUIC_BAD_APPLICATION_PAYLOAD);
return;
}
MarkConsumed(iov.iov_len);
}
if (sequencer()->IsClosed()) {
OnFinRead();
} else {
sequencer()->SetUnblocked();
}
}
size_t QuicSpdyClientStream::SendRequest(Http2HeaderBlock headers,
absl::string_view body, bool fin) {
QuicConnection::ScopedPacketFlusher flusher(session_->connection());
bool send_fin_with_headers = fin && body.empty();
size_t bytes_sent = body.size();
header_bytes_written_ =
WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
bytes_sent += header_bytes_written_;
if (!body.empty()) {
WriteOrBufferBody(body, fin);
}
return bytes_sent;
}
bool QuicSpdyClientStream::AreHeadersValid(
const QuicHeaderList& header_list) const {
if (!GetQuicReloadableFlag(quic_verify_request_headers_2)) {
return true;
}
if (!QuicSpdyStream::AreHeadersValid(header_list)) {
return false;
}
// Verify the presence of :status header.
bool saw_status = false;
for (const std::pair<std::string, std::string>& pair : header_list) {
if (pair.first == ":status") {
saw_status = true;
} else if (absl::StrContains(pair.first, ":")) {
QUIC_DLOG(ERROR) << "Unexpected ':' in header " << pair.first << ".";
return false;
}
}
return saw_status;
}
} // namespace quic