blob: d5b41374ebefe24040d83b58ecdafa8515c85e03 [file] [log] [blame]
// Copyright 2013 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 <memory>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "quiche/quic/core/crypto/null_encrypter.h"
#include "quiche/quic/core/http/quic_spdy_client_session.h"
#include "quiche/quic/core/http/spdy_utils.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/crypto_test_utils.h"
#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/common/simple_buffer_allocator.h"
using spdy::Http2HeaderBlock;
using testing::_;
using testing::StrictMock;
namespace quic {
namespace test {
namespace {
class MockQuicSpdyClientSession : public QuicSpdyClientSession {
public:
explicit MockQuicSpdyClientSession(
const ParsedQuicVersionVector& supported_versions,
QuicConnection* connection,
QuicClientPushPromiseIndex* push_promise_index)
: QuicSpdyClientSession(DefaultQuicConfig(), supported_versions,
connection,
QuicServerId("example.com", 443, false),
&crypto_config_, push_promise_index),
crypto_config_(crypto_test_utils::ProofVerifierForTesting()) {}
MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
delete;
~MockQuicSpdyClientSession() override = default;
MOCK_METHOD(bool, WriteControlFrame,
(const QuicFrame& frame, TransmissionType type), (override));
using QuicSession::ActivateStream;
private:
QuicCryptoClientConfig crypto_config_;
};
class QuicSpdyClientStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
public:
class StreamVisitor;
QuicSpdyClientStreamTest()
: connection_(new StrictMock<MockQuicConnection>(
&helper_, &alarm_factory_, Perspective::IS_CLIENT,
SupportedVersions(GetParam()))),
session_(connection_->supported_versions(), connection_,
&push_promise_index_),
body_("hello world") {
session_.Initialize();
connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
connection_->SetEncrypter(
ENCRYPTION_FORWARD_SECURE,
std::make_unique<NullEncrypter>(connection_->perspective()));
headers_[":status"] = "200";
headers_["content-length"] = "11";
auto stream = std::make_unique<QuicSpdyClientStream>(
GetNthClientInitiatedBidirectionalStreamId(
connection_->transport_version(), 0),
&session_, BIDIRECTIONAL);
stream_ = stream.get();
session_.ActivateStream(std::move(stream));
stream_visitor_ = std::make_unique<StreamVisitor>();
stream_->set_visitor(stream_visitor_.get());
}
class StreamVisitor : public QuicSpdyClientStream::Visitor {
void OnClose(QuicSpdyStream* stream) override {
QUIC_DVLOG(1) << "stream " << stream->id();
}
};
MockQuicConnectionHelper helper_;
MockAlarmFactory alarm_factory_;
StrictMock<MockQuicConnection>* connection_;
QuicClientPushPromiseIndex push_promise_index_;
MockQuicSpdyClientSession session_;
QuicSpdyClientStream* stream_;
std::unique_ptr<StreamVisitor> stream_visitor_;
Http2HeaderBlock headers_;
std::string body_;
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdyClientStreamTest,
::testing::ValuesIn(AllSupportedVersions()),
::testing::PrintToStringParamName());
TEST_P(QuicSpdyClientStreamTest, TestReceivingIllegalResponseStatusCode) {
headers_[":status"] = "200 ok";
EXPECT_CALL(session_, WriteControlFrame(_, _));
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_THAT(stream_->stream_error(),
IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
}
TEST_P(QuicSpdyClientStreamTest, InvalidResponseHeader) {
SetQuicReloadableFlag(quic_verify_request_headers_2, true);
SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
auto headers = AsHeaderList(std::vector<std::pair<std::string, std::string>>{
{":status", "200"}, {":path", "/foo"}});
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_THAT(stream_->stream_error(),
IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
}
TEST_P(QuicSpdyClientStreamTest, MissingStatusCode) {
SetQuicReloadableFlag(quic_verify_request_headers_2, true);
SetQuicReloadableFlag(quic_act_upon_invalid_header, true);
auto headers = AsHeaderList(
std::vector<std::pair<std::string, std::string>>{{"key", "value"}});
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_THAT(stream_->stream_error(),
IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
}
TEST_P(QuicSpdyClientStreamTest, TestFraming) {
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
body_.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), body_)
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
}
TEST_P(QuicSpdyClientStreamTest, Test100ContinueBeforeSuccessful) {
// First send 100 Continue.
headers_[":status"] = "100";
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
EXPECT_EQ(0u, stream_->response_headers().size());
EXPECT_EQ(100, stream_->response_code());
EXPECT_EQ("", stream_->data());
// Then send 200 OK.
headers_[":status"] = "200";
headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
body_.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), body_)
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
// Make sure the 200 response got parsed correctly.
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
// Make sure the 100 response is still available.
EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
}
TEST_P(QuicSpdyClientStreamTest, TestUnknownInformationalBeforeSuccessful) {
// First send 199, an unknown Informational (1XX).
headers_[":status"] = "199";
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_EQ(0u, stream_->response_headers().size());
EXPECT_EQ(199, stream_->response_code());
EXPECT_EQ("", stream_->data());
// Then send 200 OK.
headers_[":status"] = "200";
headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
body_.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), body_)
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
// Make sure the 200 response got parsed correctly.
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
}
TEST_P(QuicSpdyClientStreamTest, TestReceiving101) {
// 101 "Switching Protocols" is forbidden in HTTP/3 as per the
// "HTTP Upgrade" section of draft-ietf-quic-http.
headers_[":status"] = "101";
EXPECT_CALL(session_, WriteControlFrame(_, _));
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_THAT(stream_->stream_error(),
IsStreamError(QUIC_BAD_APPLICATION_PAYLOAD));
}
TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
body_.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), body_)
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
}
TEST_P(QuicSpdyClientStreamTest,
QUIC_TEST_DISABLED_IN_CHROME(TestFramingExtraData)) {
std::string large_body = "hello world!!!!!!";
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
// The headers should parse successfully.
EXPECT_THAT(stream_->stream_error(), IsQuicStreamNoError());
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
large_body.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), large_body)
: large_body;
EXPECT_CALL(session_, WriteControlFrame(_, _));
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
}
// Test that receiving trailing headers (on the headers stream), containing a
// final offset, results in the stream being closed at that byte offset.
TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) {
// There is no kFinalOffsetHeaderKey if trailers are sent on the
// request/response stream.
if (VersionUsesHttp3(connection_->transport_version())) {
return;
}
// Send headers as usual.
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
// Send trailers before sending the body. Even though a FIN has been received
// the stream should not be closed, as it does not yet have all the data bytes
// promised by the final offset field.
Http2HeaderBlock trailer_block;
trailer_block["trailer key"] = "trailer value";
trailer_block[kFinalOffsetHeaderKey] = absl::StrCat(body_.size());
auto trailers = AsHeaderList(trailer_block);
stream_->OnStreamHeaderList(true, trailers.uncompressed_header_bytes(),
trailers);
// Now send the body, which should close the stream as the FIN has been
// received, as well as all data.
quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
body_.length(), quiche::SimpleBufferAllocator::Get());
std::string data = VersionUsesHttp3(connection_->transport_version())
? absl::StrCat(header.AsStringView(), body_)
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_TRUE(stream_->reading_stopped());
}
} // namespace
} // namespace test
} // namespace quic