// 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 "net/spdy/spdy_http_stream.h" #include "crypto/ec_private_key.h" #include "crypto/ec_signature_creator.h" #include "crypto/signature_creator.h" #include "net/base/asn1_util.h" #include "net/base/default_server_bound_cert_store.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_test_util_spdy2.h" #include "testing/gtest/include/gtest/gtest.h" using namespace net::test_spdy2; namespace net { class SpdyHttpStreamSpdy2Test : public testing::Test { public: OrderedSocketData* data() { return data_.get(); } protected: SpdyHttpStreamSpdy2Test() {} virtual void SetUp() { SpdySession::set_default_protocol(kProtoSPDY2); } virtual void TearDown() { crypto::ECSignatureCreator::SetFactoryForTesting(NULL); MessageLoop::current()->RunAllPending(); } int InitSession(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count, HostPortPair& host_port_pair) { HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); data_.reset(new OrderedSocketData(reads, reads_count, writes, writes_count)); session_deps_.socket_factory->AddSocketDataProvider(data_.get()); http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog()); transport_params_ = new TransportSocketParams(host_port_pair, MEDIUM, false, false, OnHostResolutionCallback()); TestCompletionCallback callback; scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection->Init(host_port_pair.ToString(), transport_params_, MEDIUM, callback.callback(), http_session_->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, callback.WaitForResult()); return session_->InitializeWithSocket(connection.release(), false, OK); } SpdySessionDependencies session_deps_; scoped_ptr<OrderedSocketData> data_; scoped_refptr<HttpNetworkSession> http_session_; scoped_refptr<SpdySession> session_; scoped_refptr<TransportSocketParams> transport_params_; private: SpdyTestStateHelper spdy_state_; }; TEST_F(SpdyHttpStreamSpdy2Test, SendRequest) { scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req.get(), 1), }; scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); MockRead reads[] = { CreateMockRead(*resp, 2), MockRead(SYNCHRONOUS, 0, 3) // EOF }; HostPortPair host_port_pair("www.google.com", 80); HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), host_port_pair)); HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); TestCompletionCallback callback; HttpResponseInfo response; HttpRequestHeaders headers; BoundNetLog net_log; scoped_ptr<SpdyHttpStream> http_stream( new SpdyHttpStream(session_.get(), true)); ASSERT_EQ( OK, http_stream->InitializeStream(&request, net_log, CompletionCallback())); EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, scoped_ptr<UploadDataStream>(), &response, callback.callback())); EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); // This triggers the MockWrite and read 2 callback.WaitForResult(); // This triggers read 3. The empty read causes the session to shut down. data()->CompleteRead(); // Because we abandoned the stream, we don't expect to find a session in the // pool anymore. EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); EXPECT_TRUE(data()->at_read_eof()); EXPECT_TRUE(data()->at_write_eof()); } TEST_F(SpdyHttpStreamSpdy2Test, SendChunkedPost) { UploadDataStream::set_merge_chunks(false); scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true)); MockWrite writes[] = { CreateMockWrite(*req.get(), 1), CreateMockWrite(*chunk1, 2), // POST upload frames CreateMockWrite(*chunk2, 3), }; scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); MockRead reads[] = { CreateMockRead(*resp, 4), CreateMockRead(*chunk1, 5), CreateMockRead(*chunk2, 5), MockRead(SYNCHRONOUS, 0, 6) // EOF }; HostPortPair host_port_pair("www.google.com", 80); HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), host_port_pair)); HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/"); request.upload_data = new UploadData(); request.upload_data->set_is_chunked(true); request.upload_data->AppendChunk(kUploadData, kUploadDataSize, false); request.upload_data->AppendChunk(kUploadData, kUploadDataSize, true); TestCompletionCallback callback; HttpResponseInfo response; HttpRequestHeaders headers; BoundNetLog net_log; SpdyHttpStream http_stream(session_.get(), true); ASSERT_EQ( OK, http_stream.InitializeStream(&request, net_log, CompletionCallback())); scoped_ptr<UploadDataStream> upload_stream( new UploadDataStream(request.upload_data)); ASSERT_EQ(OK, upload_stream->Init()); EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest( headers, upload_stream.Pass(), &response, callback.callback())); EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); // This triggers the MockWrite and read 2 callback.WaitForResult(); // This triggers read 3. The empty read causes the session to shut down. data()->CompleteRead(); MessageLoop::current()->RunAllPending(); // Because we abandoned the stream, we don't expect to find a session in the // pool anymore. EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); EXPECT_TRUE(data()->at_read_eof()); EXPECT_TRUE(data()->at_write_eof()); } // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058 TEST_F(SpdyHttpStreamSpdy2Test, SpdyURLTest) { const char * const full_url = "http://www.google.com/foo?query=what#anchor"; const char * const base_url = "http://www.google.com/foo?query=what"; scoped_ptr<SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req.get(), 1), }; scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); MockRead reads[] = { CreateMockRead(*resp, 2), MockRead(SYNCHRONOUS, 0, 3) // EOF }; HostPortPair host_port_pair("www.google.com", 80); HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), host_port_pair)); HttpRequestInfo request; request.method = "GET"; request.url = GURL(full_url); TestCompletionCallback callback; HttpResponseInfo response; HttpRequestHeaders headers; BoundNetLog net_log; scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); ASSERT_EQ( OK, http_stream->InitializeStream(&request, net_log, CompletionCallback())); EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, scoped_ptr<UploadDataStream>(), &response, callback.callback())); SpdyHeaderBlock* spdy_header = http_stream->stream()->spdy_headers().get(); EXPECT_TRUE(spdy_header != NULL); if (spdy_header->find("url") != spdy_header->end()) EXPECT_EQ("/foo?query=what", spdy_header->find("url")->second); else FAIL() << "No url is set in spdy_header!"; // This triggers the MockWrite and read 2 callback.WaitForResult(); // This triggers read 3. The empty read causes the session to shut down. data()->CompleteRead(); // Because we abandoned the stream, we don't expect to find a session in the // pool anymore. EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); EXPECT_TRUE(data()->at_read_eof()); EXPECT_TRUE(data()->at_write_eof()); } // TODO(willchan): Write a longer test for SpdyStream that exercises all // methods. } // namespace net