| // Copyright (c) 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 "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h" | |
| #include <list> | |
| #include <memory> | |
| #include <utility> | |
| #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" | |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" | |
| #include "net/third_party/quiche/src/quic/tools/quic_backend_response.h" | |
| #include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h" | |
| #include "net/third_party/quiche/src/quic/tools/quic_simple_server_session.h" | |
| using testing::_; | |
| using testing::AnyNumber; | |
| using testing::InSequence; | |
| using testing::Invoke; | |
| using testing::Return; | |
| using testing::StrictMock; | |
| using testing::ValuesIn; | |
| namespace quic { | |
| namespace test { | |
| const size_t kFakeFrameLen = 60; | |
| const size_t kErrorLength = strlen(QuicSimpleServerStream::kErrorResponseBody); | |
| const size_t kDataFrameHeaderLength = 2; | |
| class TestStream : public QuicSimpleServerStream { | |
| public: | |
| TestStream(QuicStreamId stream_id, | |
| QuicSpdySession* session, | |
| StreamType type, | |
| QuicSimpleServerBackend* quic_simple_server_backend) | |
| : QuicSimpleServerStream(stream_id, | |
| session, | |
| type, | |
| quic_simple_server_backend) {} | |
| ~TestStream() override = default; | |
| MOCK_METHOD1(WriteHeadersMock, void(bool fin)); | |
| size_t WriteHeaders(spdy::SpdyHeaderBlock /*header_block*/, | |
| bool fin, | |
| QuicReferenceCountedPointer<QuicAckListenerInterface> | |
| /*ack_listener*/) override { | |
| WriteHeadersMock(fin); | |
| return 0; | |
| } | |
| // Expose protected QuicSimpleServerStream methods. | |
| void DoSendResponse() { SendResponse(); } | |
| void DoSendErrorResponse() { SendErrorResponse(); } | |
| spdy::SpdyHeaderBlock* mutable_headers() { return &request_headers_; } | |
| void set_body(std::string body) { body_ = std::move(body); } | |
| const std::string& body() const { return body_; } | |
| int content_length() const { return content_length_; } | |
| QuicStringPiece GetHeader(QuicStringPiece key) const { | |
| auto it = request_headers_.find(key); | |
| DCHECK(it != request_headers_.end()); | |
| return it->second; | |
| } | |
| }; | |
| namespace { | |
| class MockQuicSimpleServerSession : public QuicSimpleServerSession { | |
| public: | |
| const size_t kMaxStreamsForTest = 100; | |
| MockQuicSimpleServerSession( | |
| QuicConnection* connection, | |
| MockQuicSessionVisitor* owner, | |
| MockQuicCryptoServerStreamHelper* helper, | |
| QuicCryptoServerConfig* crypto_config, | |
| QuicCompressedCertsCache* compressed_certs_cache, | |
| QuicSimpleServerBackend* quic_simple_server_backend) | |
| : QuicSimpleServerSession(DefaultQuicConfig(), | |
| CurrentSupportedVersions(), | |
| connection, | |
| owner, | |
| helper, | |
| crypto_config, | |
| compressed_certs_cache, | |
| quic_simple_server_backend) { | |
| if (VersionHasIetfQuicFrames(connection->transport_version())) { | |
| QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams( | |
| this, kMaxStreamsForTest); | |
| QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams( | |
| this, kMaxStreamsForTest); | |
| } else { | |
| QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest); | |
| QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest); | |
| } | |
| ON_CALL(*this, WritevData(_, _, _, _, _)) | |
| .WillByDefault(Invoke(MockQuicSession::ConsumeData)); | |
| } | |
| MockQuicSimpleServerSession(const MockQuicSimpleServerSession&) = delete; | |
| MockQuicSimpleServerSession& operator=(const MockQuicSimpleServerSession&) = | |
| delete; | |
| ~MockQuicSimpleServerSession() override = default; | |
| MOCK_METHOD2(OnConnectionClosed, | |
| void(const QuicConnectionCloseFrame& frame, | |
| ConnectionCloseSource source)); | |
| MOCK_METHOD1(CreateIncomingStream, QuicSpdyStream*(QuicStreamId id)); | |
| MOCK_METHOD5(WritevData, | |
| QuicConsumedData(QuicStream* stream, | |
| QuicStreamId id, | |
| size_t write_length, | |
| QuicStreamOffset offset, | |
| StreamSendingState state)); | |
| MOCK_METHOD4(OnStreamHeaderList, | |
| void(QuicStreamId stream_id, | |
| bool fin, | |
| size_t frame_len, | |
| const QuicHeaderList& header_list)); | |
| MOCK_METHOD2(OnStreamHeadersPriority, | |
| void(QuicStreamId stream_id, | |
| const spdy::SpdyStreamPrecedence& precedence)); | |
| MOCK_METHOD3(SendRstStream, | |
| void(QuicStreamId stream_id, | |
| QuicRstStreamErrorCode error, | |
| QuicStreamOffset bytes_written)); | |
| MOCK_METHOD1(OnHeadersHeadOfLineBlocking, void(QuicTime::Delta delta)); | |
| // Matchers cannot be used on non-copyable types like SpdyHeaderBlock. | |
| void PromisePushResources( | |
| const std::string& request_url, | |
| const std::list<QuicBackendResponse::ServerPushInfo>& resources, | |
| QuicStreamId original_stream_id, | |
| const spdy::SpdyStreamPrecedence& original_precedence, | |
| const spdy::SpdyHeaderBlock& original_request_headers) override { | |
| original_request_headers_ = original_request_headers.Clone(); | |
| PromisePushResourcesMock(request_url, resources, original_stream_id, | |
| original_precedence, original_request_headers); | |
| } | |
| MOCK_METHOD5(PromisePushResourcesMock, | |
| void(const std::string&, | |
| const std::list<QuicBackendResponse::ServerPushInfo>&, | |
| QuicStreamId, | |
| const spdy::SpdyStreamPrecedence&, | |
| const spdy::SpdyHeaderBlock&)); | |
| using QuicSession::ActivateStream; | |
| MOCK_METHOD1(OnStopSendingReceived, void(const QuicStopSendingFrame& frame)); | |
| spdy::SpdyHeaderBlock original_request_headers_; | |
| }; | |
| class QuicSimpleServerStreamTest : public QuicTestWithParam<ParsedQuicVersion> { | |
| public: | |
| QuicSimpleServerStreamTest() | |
| : connection_( | |
| new StrictMock<MockQuicConnection>(&helper_, | |
| &alarm_factory_, | |
| Perspective::IS_SERVER, | |
| SupportedVersions(GetParam()))), | |
| crypto_config_(new QuicCryptoServerConfig( | |
| QuicCryptoServerConfig::TESTING, | |
| QuicRandom::GetInstance(), | |
| crypto_test_utils::ProofSourceForTesting(), | |
| KeyExchangeSource::Default())), | |
| compressed_certs_cache_( | |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), | |
| session_(connection_, | |
| &session_owner_, | |
| &session_helper_, | |
| crypto_config_.get(), | |
| &compressed_certs_cache_, | |
| &memory_cache_backend_), | |
| quic_response_(new QuicBackendResponse), | |
| body_("hello world") { | |
| connection_->set_visitor(&session_); | |
| header_list_.OnHeaderBlockStart(); | |
| header_list_.OnHeader(":authority", "www.google.com"); | |
| header_list_.OnHeader(":path", "/"); | |
| header_list_.OnHeader(":method", "POST"); | |
| header_list_.OnHeader(":version", "HTTP/1.1"); | |
| header_list_.OnHeader("content-length", "11"); | |
| header_list_.OnHeaderBlockEnd(128, 128); | |
| // New streams rely on having the peer's flow control receive window | |
| // negotiated in the config. | |
| session_.config()->SetInitialStreamFlowControlWindowToSend( | |
| kInitialStreamFlowControlWindowForTest); | |
| session_.config()->SetInitialSessionFlowControlWindowToSend( | |
| kInitialSessionFlowControlWindowForTest); | |
| session_.Initialize(); | |
| stream_ = new StrictMock<TestStream>( | |
| GetNthClientInitiatedBidirectionalStreamId( | |
| connection_->transport_version(), 0), | |
| &session_, BIDIRECTIONAL, &memory_cache_backend_); | |
| // Register stream_ in dynamic_stream_map_ and pass ownership to session_. | |
| session_.ActivateStream(QuicWrapUnique(stream_)); | |
| QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow( | |
| session_.config(), kMinimumFlowControlSendWindow); | |
| QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams( | |
| session_.config(), 10); | |
| session_.OnConfigNegotiated(); | |
| connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1)); | |
| } | |
| const std::string& StreamBody() { return stream_->body(); } | |
| std::string StreamHeadersValue(const std::string& key) { | |
| return (*stream_->mutable_headers())[key].as_string(); | |
| } | |
| bool HasFrameHeader() const { | |
| return VersionHasDataFrameHeader(connection_->transport_version()); | |
| } | |
| spdy::SpdyHeaderBlock response_headers_; | |
| MockQuicConnectionHelper helper_; | |
| MockAlarmFactory alarm_factory_; | |
| StrictMock<MockQuicConnection>* connection_; | |
| StrictMock<MockQuicSessionVisitor> session_owner_; | |
| StrictMock<MockQuicCryptoServerStreamHelper> session_helper_; | |
| std::unique_ptr<QuicCryptoServerConfig> crypto_config_; | |
| QuicCompressedCertsCache compressed_certs_cache_; | |
| QuicMemoryCacheBackend memory_cache_backend_; | |
| StrictMock<MockQuicSimpleServerSession> session_; | |
| StrictMock<TestStream>* stream_; // Owned by session_. | |
| std::unique_ptr<QuicBackendResponse> quic_response_; | |
| std::string body_; | |
| QuicHeaderList header_list_; | |
| HttpEncoder encoder_; | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(Tests, | |
| QuicSimpleServerStreamTest, | |
| ValuesIn(AllSupportedVersions())); | |
| TEST_P(QuicSimpleServerStreamTest, TestFraming) { | |
| EXPECT_CALL(session_, WritevData(_, _, _, _, _)) | |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); | |
| stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body_.length(), &buffer); | |
| std::string header = std::string(buffer.get(), header_length); | |
| std::string data = HasFrameHeader() ? header + body_ : body_; | |
| stream_->OnStreamFrame( | |
| QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); | |
| EXPECT_EQ("11", StreamHeadersValue("content-length")); | |
| EXPECT_EQ("/", StreamHeadersValue(":path")); | |
| EXPECT_EQ("POST", StreamHeadersValue(":method")); | |
| EXPECT_EQ(body_, StreamBody()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, TestFramingOnePacket) { | |
| EXPECT_CALL(session_, WritevData(_, _, _, _, _)) | |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); | |
| stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body_.length(), &buffer); | |
| std::string header = std::string(buffer.get(), header_length); | |
| std::string data = HasFrameHeader() ? header + body_ : body_; | |
| stream_->OnStreamFrame( | |
| QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); | |
| EXPECT_EQ("11", StreamHeadersValue("content-length")); | |
| EXPECT_EQ("/", StreamHeadersValue(":path")); | |
| EXPECT_EQ("POST", StreamHeadersValue(":method")); | |
| EXPECT_EQ(body_, StreamBody()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendQuicRstStreamNoErrorInStopReading) { | |
| EXPECT_CALL(session_, WritevData(_, _, _, _, _)) | |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); | |
| EXPECT_FALSE(stream_->fin_received()); | |
| EXPECT_FALSE(stream_->rst_received()); | |
| stream_->set_fin_sent(true); | |
| stream_->CloseWriteSide(); | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(1); | |
| stream_->StopReading(); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, TestFramingExtraData) { | |
| InSequence seq; | |
| std::string large_body = "hello world!!!!!!"; | |
| // We'll automatically write out an error (headers + body) | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, kDataFrameHeaderLength, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); | |
| stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body_.length(), &buffer); | |
| std::string header = std::string(buffer.get(), header_length); | |
| std::string data = HasFrameHeader() ? header + body_ : body_; | |
| stream_->OnStreamFrame( | |
| QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); | |
| // Content length is still 11. This will register as an error and we won't | |
| // accept the bytes. | |
| header_length = | |
| encoder_.SerializeDataFrameHeader(large_body.length(), &buffer); | |
| header = std::string(buffer.get(), header_length); | |
| std::string data2 = HasFrameHeader() ? header + large_body : large_body; | |
| stream_->OnStreamFrame( | |
| QuicStreamFrame(stream_->id(), /*fin=*/true, data.size(), data2)); | |
| EXPECT_EQ("11", StreamHeadersValue("content-length")); | |
| EXPECT_EQ("/", StreamHeadersValue(":path")); | |
| EXPECT_EQ("POST", StreamHeadersValue(":method")); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus) { | |
| // Send an illegal response with response status not supported by HTTP/2. | |
| spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); | |
| (*request_headers)[":path"] = "/bar"; | |
| (*request_headers)[":authority"] = "www.google.com"; | |
| (*request_headers)[":version"] = "HTTP/1.1"; | |
| (*request_headers)[":method"] = "GET"; | |
| response_headers_[":version"] = "HTTP/1.1"; | |
| // HTTP/2 only supports integer responsecode, so "200 OK" is illegal. | |
| response_headers_[":status"] = "200 OK"; | |
| response_headers_["content-length"] = "5"; | |
| std::string body = "Yummm"; | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body.length(), &buffer); | |
| memory_cache_backend_.AddResponse("www.google.com", "/bar", | |
| std::move(response_headers_), body); | |
| stream_->set_fin_received(true); | |
| InSequence s; | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); | |
| stream_->DoSendResponse(); | |
| EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus2) { | |
| // Send an illegal response with response status not supported by HTTP/2. | |
| spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); | |
| (*request_headers)[":path"] = "/bar"; | |
| (*request_headers)[":authority"] = "www.google.com"; | |
| (*request_headers)[":version"] = "HTTP/1.1"; | |
| (*request_headers)[":method"] = "GET"; | |
| response_headers_[":version"] = "HTTP/1.1"; | |
| // HTTP/2 only supports 3-digit-integer, so "+200" is illegal. | |
| response_headers_[":status"] = "+200"; | |
| response_headers_["content-length"] = "5"; | |
| std::string body = "Yummm"; | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body.length(), &buffer); | |
| memory_cache_backend_.AddResponse("www.google.com", "/bar", | |
| std::move(response_headers_), body); | |
| stream_->set_fin_received(true); | |
| InSequence s; | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); | |
| stream_->DoSendResponse(); | |
| EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendPushResponseWith404Response) { | |
| // Create a new promised stream with even id(). | |
| auto promised_stream = new StrictMock<TestStream>( | |
| GetNthServerInitiatedUnidirectionalStreamId( | |
| connection_->transport_version(), 3), | |
| &session_, WRITE_UNIDIRECTIONAL, &memory_cache_backend_); | |
| session_.ActivateStream(QuicWrapUnique(promised_stream)); | |
| // Send a push response with response status 404, which will be regarded as | |
| // invalid server push response. | |
| spdy::SpdyHeaderBlock* request_headers = promised_stream->mutable_headers(); | |
| (*request_headers)[":path"] = "/bar"; | |
| (*request_headers)[":authority"] = "www.google.com"; | |
| (*request_headers)[":version"] = "HTTP/1.1"; | |
| (*request_headers)[":method"] = "GET"; | |
| response_headers_[":version"] = "HTTP/1.1"; | |
| response_headers_[":status"] = "404"; | |
| response_headers_["content-length"] = "8"; | |
| std::string body = "NotFound"; | |
| memory_cache_backend_.AddResponse("www.google.com", "/bar", | |
| std::move(response_headers_), body); | |
| InSequence s; | |
| EXPECT_CALL(session_, | |
| SendRstStream(promised_stream->id(), QUIC_STREAM_CANCELLED, 0)); | |
| promised_stream->DoSendResponse(); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) { | |
| // Add a request and response with valid headers. | |
| spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); | |
| (*request_headers)[":path"] = "/bar"; | |
| (*request_headers)[":authority"] = "www.google.com"; | |
| (*request_headers)[":version"] = "HTTP/1.1"; | |
| (*request_headers)[":method"] = "GET"; | |
| response_headers_[":version"] = "HTTP/1.1"; | |
| response_headers_[":status"] = "200"; | |
| response_headers_["content-length"] = "5"; | |
| std::string body = "Yummm"; | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body.length(), &buffer); | |
| memory_cache_backend_.AddResponse("www.google.com", "/bar", | |
| std::move(response_headers_), body); | |
| stream_->set_fin_received(true); | |
| InSequence s; | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, body.length(), _, FIN)); | |
| stream_->DoSendResponse(); | |
| EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, SendResponseWithPushResources) { | |
| // Tests that if a response has push resources to be send, SendResponse() will | |
| // call PromisePushResources() to handle these resources. | |
| // Add a request and response with valid headers into cache. | |
| std::string host = "www.google.com"; | |
| std::string request_path = "/foo"; | |
| std::string body = "Yummm"; | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(body.length(), &buffer); | |
| QuicBackendResponse::ServerPushInfo push_info( | |
| QuicUrl(host, "/bar"), spdy::SpdyHeaderBlock(), | |
| QuicStream::kDefaultPriority, "Push body"); | |
| std::list<QuicBackendResponse::ServerPushInfo> push_resources; | |
| push_resources.push_back(push_info); | |
| memory_cache_backend_.AddSimpleResponseWithServerPushResources( | |
| host, request_path, 200, body, push_resources); | |
| spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); | |
| (*request_headers)[":path"] = request_path; | |
| (*request_headers)[":authority"] = host; | |
| (*request_headers)[":version"] = "HTTP/1.1"; | |
| (*request_headers)[":method"] = "GET"; | |
| stream_->set_fin_received(true); | |
| InSequence s; | |
| EXPECT_CALL(session_, PromisePushResourcesMock( | |
| host + request_path, _, | |
| GetNthClientInitiatedBidirectionalStreamId( | |
| connection_->transport_version(), 0), | |
| _, _)); | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, body.length(), _, FIN)); | |
| stream_->DoSendResponse(); | |
| EXPECT_EQ(*request_headers, session_.original_request_headers_); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) { | |
| // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. | |
| if (GetParam() != AllSupportedVersions()[0]) { | |
| return; | |
| } | |
| // Calling PushResponse() on a client initialted stream is never supposed to | |
| // happen. | |
| EXPECT_QUIC_BUG(stream_->PushResponse(spdy::SpdyHeaderBlock()), | |
| "Client initiated stream" | |
| " shouldn't be used as promised stream."); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, PushResponseOnServerInitiatedStream) { | |
| // Tests that PushResponse() should take the given headers as request headers | |
| // and fetch response from cache, and send it out. | |
| // Create a stream with even stream id and test against this stream. | |
| const QuicStreamId kServerInitiatedStreamId = | |
| GetNthServerInitiatedUnidirectionalStreamId( | |
| connection_->transport_version(), 3); | |
| // Create a server initiated stream and pass it to session_. | |
| auto server_initiated_stream = | |
| new StrictMock<TestStream>(kServerInitiatedStreamId, &session_, | |
| WRITE_UNIDIRECTIONAL, &memory_cache_backend_); | |
| session_.ActivateStream(QuicWrapUnique(server_initiated_stream)); | |
| const std::string kHost = "www.foo.com"; | |
| const std::string kPath = "/bar"; | |
| spdy::SpdyHeaderBlock headers; | |
| headers[":path"] = kPath; | |
| headers[":authority"] = kHost; | |
| headers[":version"] = "HTTP/1.1"; | |
| headers[":method"] = "GET"; | |
| response_headers_[":version"] = "HTTP/1.1"; | |
| response_headers_[":status"] = "200"; | |
| response_headers_["content-length"] = "5"; | |
| const std::string kBody = "Hello"; | |
| std::unique_ptr<char[]> buffer; | |
| QuicByteCount header_length = | |
| encoder_.SerializeDataFrameHeader(kBody.length(), &buffer); | |
| memory_cache_backend_.AddResponse(kHost, kPath, std::move(response_headers_), | |
| kBody); | |
| // Call PushResponse() should trigger stream to fetch response from cache | |
| // and send it back. | |
| InSequence s; | |
| EXPECT_CALL(*server_initiated_stream, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, kServerInitiatedStreamId, header_length, | |
| _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, | |
| WritevData(_, kServerInitiatedStreamId, kBody.size(), _, FIN)); | |
| server_initiated_stream->PushResponse(std::move(headers)); | |
| EXPECT_EQ(kPath, server_initiated_stream->GetHeader(":path")); | |
| EXPECT_EQ("GET", server_initiated_stream->GetHeader(":method")); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, TestSendErrorResponse) { | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); | |
| stream_->set_fin_received(true); | |
| InSequence s; | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| if (HasFrameHeader()) { | |
| EXPECT_CALL(session_, WritevData(_, _, kDataFrameHeaderLength, _, NO_FIN)); | |
| } | |
| EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); | |
| stream_->DoSendErrorResponse(); | |
| EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, InvalidMultipleContentLength) { | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); | |
| spdy::SpdyHeaderBlock request_headers; | |
| // \000 is a way to write the null byte when followed by a literal digit. | |
| header_list_.OnHeader("content-length", QuicStringPiece("11\00012", 5)); | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| EXPECT_CALL(session_, WritevData(_, _, _, _, _)) | |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); | |
| stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_); | |
| EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->reading_stopped()); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, InvalidLeadingNullContentLength) { | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); | |
| spdy::SpdyHeaderBlock request_headers; | |
| // \000 is a way to write the null byte when followed by a literal digit. | |
| header_list_.OnHeader("content-length", QuicStringPiece("\00012", 3)); | |
| EXPECT_CALL(*stream_, WriteHeadersMock(false)); | |
| EXPECT_CALL(session_, WritevData(_, _, _, _, _)) | |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); | |
| stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_); | |
| EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_TRUE(stream_->reading_stopped()); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, ValidMultipleContentLength) { | |
| spdy::SpdyHeaderBlock request_headers; | |
| // \000 is a way to write the null byte when followed by a literal digit. | |
| header_list_.OnHeader("content-length", QuicStringPiece("11\00011", 5)); | |
| stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); | |
| EXPECT_EQ(11, stream_->content_length()); | |
| EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); | |
| EXPECT_FALSE(stream_->reading_stopped()); | |
| EXPECT_FALSE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, | |
| DoNotSendQuicRstStreamNoErrorWithRstReceived) { | |
| InSequence s; | |
| EXPECT_FALSE(stream_->reading_stopped()); | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); | |
| EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _)).Times(1); | |
| QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(), | |
| QUIC_STREAM_CANCELLED, 1234); | |
| stream_->OnStreamReset(rst_frame); | |
| if (VersionHasIetfQuicFrames(connection_->transport_version())) { | |
| // For V99 receiving a RST_STREAM causes a 1-way close; the test requires | |
| // a full close. A CloseWriteSide closes the other half of the stream. | |
| // Everything should then work properly. | |
| stream_->CloseWriteSide(); | |
| } | |
| EXPECT_TRUE(stream_->reading_stopped()); | |
| EXPECT_TRUE(stream_->write_side_closed()); | |
| } | |
| TEST_P(QuicSimpleServerStreamTest, InvalidHeadersWithFin) { | |
| char arr[] = { | |
| 0x3a, 0x68, 0x6f, 0x73, // :hos | |
| 0x74, 0x00, 0x00, 0x00, // t... | |
| 0x00, 0x00, 0x00, 0x00, // .... | |
| 0x07, 0x3a, 0x6d, 0x65, // .:me | |
| 0x74, 0x68, 0x6f, 0x64, // thod | |
| 0x00, 0x00, 0x00, 0x03, // .... | |
| 0x47, 0x45, 0x54, 0x00, // GET. | |
| 0x00, 0x00, 0x05, 0x3a, // ...: | |
| 0x70, 0x61, 0x74, 0x68, // path | |
| 0x00, 0x00, 0x00, 0x04, // .... | |
| 0x2f, 0x66, 0x6f, 0x6f, // /foo | |
| 0x00, 0x00, 0x00, 0x07, // .... | |
| 0x3a, 0x73, 0x63, 0x68, // :sch | |
| 0x65, 0x6d, 0x65, 0x00, // eme. | |
| 0x00, 0x00, 0x00, 0x00, // .... | |
| 0x00, 0x00, 0x08, 0x3a, // ...: | |
| 0x76, 0x65, 0x72, 0x73, // vers | |
| 0x96, 0x6f, 0x6e, 0x00, // <i(69)>on. | |
| 0x00, 0x00, 0x08, 0x48, // ...H | |
| 0x54, 0x54, 0x50, 0x2f, // TTP/ | |
| 0x31, 0x2e, 0x31, // 1.1 | |
| }; | |
| QuicStringPiece data(arr, QUIC_ARRAYSIZE(arr)); | |
| QuicStreamFrame frame(stream_->id(), true, 0, data); | |
| // Verify that we don't crash when we get a invalid headers in stream frame. | |
| stream_->OnStreamFrame(frame); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |