diff options
Diffstat (limited to 'net/spdy/spdy_http_stream_spdy3_unittest.cc')
-rw-r--r-- | net/spdy/spdy_http_stream_spdy3_unittest.cc | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/net/spdy/spdy_http_stream_spdy3_unittest.cc b/net/spdy/spdy_http_stream_spdy3_unittest.cc new file mode 100644 index 0000000..79390df --- /dev/null +++ b/net/spdy/spdy_http_stream_spdy3_unittest.cc @@ -0,0 +1,471 @@ +// 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_origin_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_spdy3.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy3; + +namespace net { + +class SpdyHttpStreamSpdy3Test : public testing::Test { + public: + OrderedSocketData* data() { return data_.get(); } + protected: + SpdyHttpStreamSpdy3Test() {} + + void EnableCompression(bool enabled) { + spdy::SpdyFramer::set_enable_compression_default(enabled); + } + + 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); + 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(), + BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + return session_->InitializeWithSocket(connection.release(), false, OK); + } + + void TestSendCredentials( + OriginBoundCertService* obc_service, + const std::string& cert, + const std::string& proof, + SSLClientCertType type); + + SpdySessionDependencies session_deps_; + scoped_ptr<OrderedSocketData> data_; + scoped_refptr<HttpNetworkSession> http_session_; + scoped_refptr<SpdySession> session_; + scoped_refptr<TransportSocketParams> transport_params_; +}; + +TEST_F(SpdyHttpStreamSpdy3Test, SendRequest) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<spdy::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, NULL, &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(SpdyHttpStreamSpdy3Test, SendChunkedPost) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + UploadDataStream::set_merge_chunks(false); + + scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + CreateMockWrite(*chunk1, 2), // POST upload frames + CreateMockWrite(*chunk2, 3), + }; + scoped_ptr<spdy::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())); + + // http_stream.SendRequest() will take ownership of upload_stream. + 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, &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(SpdyHttpStreamSpdy3Test, SpdyURLTest) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + + 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<spdy::SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<spdy::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, NULL, &response, + callback.callback())); + + spdy::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()); +} + +namespace { + +void GetECOriginBoundCertAndProof(const std::string& origin, + OriginBoundCertService* obc_service, + std::string* cert, + std::string* proof) { + TestCompletionCallback callback; + std::vector<uint8> requested_cert_types; + requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN); + SSLClientCertType cert_type; + std::string key; + OriginBoundCertService::RequestHandle request_handle; + int rv = obc_service->GetOriginBoundCert(origin, requested_cert_types, + &cert_type, &key, cert, + callback.callback(), + &request_handle); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type); + + unsigned char secret[32]; + memset(secret, 'A', arraysize(secret)); + + // Convert the key string into a vector<unit8> + std::vector<uint8> key_data(key.begin(), key.end()); + + base::StringPiece spki_piece; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(*cert, &spki_piece)); + std::vector<uint8> spki(spki_piece.data(), + spki_piece.data() + spki_piece.size()); + + std::vector<uint8> proof_data; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + OriginBoundCertService::kEPKIPassword, key_data, spki)); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + creator->Sign(secret, arraysize(secret), &proof_data); + proof->assign(proof_data.begin(), proof_data.end()); +} + +} // namespace + +// TODO(rch): When openssl supports origin bound certifictes, this +// guard can be removed +#if !defined(USE_OPENSSL) +// Test that if we request a resource for a new origin on a session that +// used origin bound certificates, that we send a CREDENTIAL frame for +// the new origin before we send the new request. +void SpdyHttpStreamSpdy3Test::TestSendCredentials( + OriginBoundCertService* obc_service, + const std::string& cert, + const std::string& proof, + SSLClientCertType type) { + EnableCompression(false); + + spdy::SpdyCredential cred; + cred.slot = 1; + cred.proof = proof; + cred.certs.push_back(cert); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> credential(ConstructSpdyCredential(cred)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet("http://www.gmail.com", + false, 3, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*credential.get(), 2), + CreateMockWrite(*req2.get(), 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*resp2, 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + + DeterministicMockClientSocketFactory* socket_factory = + session_deps_.deterministic_socket_factory.get(); + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + socket_factory->AddSocketDataProvider(data.get()); + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.origin_bound_cert_type = type; + ssl.origin_bound_cert_service = obc_service; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + socket_factory->AddSSLSocketDataProvider(&ssl); + http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + &session_deps_); + session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog()); + transport_params_ = new TransportSocketParams(host_port_pair, + MEDIUM, false, false); + TestCompletionCallback callback; + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + SSLConfig ssl_config; + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> ssl_params( + new SSLSocketParams(transport_params_, + socks_params, + http_proxy_params, + ProxyServer::SCHEME_DIRECT, + host_port_pair, + ssl_config, + 0, + false, + false)); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), + ssl_params, + MEDIUM, + callback.callback(), + http_session_->GetSSLSocketPool(), + BoundNetLog())); + callback.WaitForResult(); + EXPECT_EQ(OK, + session_->InitializeWithSocket(connection.release(), true, OK)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + 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_FALSE(session_->NeedsCredentials(host_port_pair)); + HostPortPair new_host_port_pair("www.gmail.com", 80); + EXPECT_TRUE(session_->NeedsCredentials(new_host_port_pair)); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, NULL, &response, + callback.callback())); + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); + + data->RunFor(2); + callback.WaitForResult(); + + // Start up second request for resource on a new origin. + scoped_ptr<SpdyHttpStream> http_stream2( + new SpdyHttpStream(session_.get(), true)); + request.url = GURL("http://www.gmail.com/"); + ASSERT_EQ( + OK, + http_stream2->InitializeStream(&request, net_log, CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, NULL, &response, + callback.callback())); + data->RunFor(2); + callback.WaitForResult(); + + EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders( + callback.callback())); + data->RunFor(1); + EXPECT_EQ(OK, callback.WaitForResult()); + ASSERT_TRUE(response.headers.get() != NULL); + ASSERT_EQ(200, response.headers->response_code()); +} + +class MockECSignatureCreator : public crypto::ECSignatureCreator { + public: + explicit MockECSignatureCreator(crypto::ECPrivateKey* key) : key_(key) {} + + virtual bool Sign(const uint8* data, + int data_len, + std::vector<uint8>* signature) OVERRIDE { + std::vector<uint8> private_key_value; + key_->ExportValue(&private_key_value); + std::string head = "fakesignature"; + std::string tail = "/fakesignature"; + + signature->clear(); + signature->insert(signature->end(), head.begin(), head.end()); + signature->insert(signature->end(), private_key_value.begin(), + private_key_value.end()); + signature->insert(signature->end(), '-'); + signature->insert(signature->end(), data, data + data_len); + signature->insert(signature->end(), tail.begin(), tail.end()); + return true; + } + + private: + crypto::ECPrivateKey* key_; + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator); +}; + +class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory { + public: + MockECSignatureCreatorFactory() {} + virtual ~MockECSignatureCreatorFactory() {} + + virtual crypto::ECSignatureCreator* Create( + crypto::ECPrivateKey* key) OVERRIDE { + return new MockECSignatureCreator(key); + } + private: + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory); +}; + +TEST_F(SpdyHttpStreamSpdy3Test, SendCredentialsEC) { + scoped_ptr<crypto::ECSignatureCreatorFactory> ec_signature_creator_factory( + new MockECSignatureCreatorFactory()); + crypto::ECSignatureCreator::SetFactoryForTesting( + ec_signature_creator_factory.get()); + + scoped_ptr<OriginBoundCertService> obc_service( + new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL))); + std::string cert; + std::string proof; + GetECOriginBoundCertAndProof("http://www.gmail.com/", obc_service.get(), + &cert, &proof); + + TestSendCredentials(obc_service.get(), cert, proof, CLIENT_CERT_ECDSA_SIGN); +} + +#endif // !defined(USE_OPENSSL) + +// TODO(willchan): Write a longer test for SpdyStream that exercises all +// methods. + +} // namespace net |