diff options
Diffstat (limited to 'net/http/http_network_transaction_unittest.cc')
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 313 |
1 files changed, 311 insertions, 2 deletions
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 83fe640..71e775c 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -14,6 +14,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/json/json_writer.h" +#include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/run_loop.h" @@ -27,6 +28,7 @@ #include "net/base/elements_upload_data_stream.h" #include "net/base/load_timing_info.h" #include "net/base/load_timing_info_test_util.h" +#include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/base/net_log_unittest.h" #include "net/base/request_priority.h" @@ -41,12 +43,15 @@ #include "net/http/http_auth_handler_digest.h" #include "net/http/http_auth_handler_mock.h" #include "net/http/http_auth_handler_ntlm.h" +#include "net/http/http_basic_state.h" #include "net/http/http_basic_stream.h" #include "net/http/http_network_session.h" #include "net/http/http_network_session_peer.h" +#include "net/http/http_request_headers.h" #include "net/http/http_server_properties_impl.h" #include "net/http/http_stream.h" #include "net/http/http_stream_factory.h" +#include "net/http/http_stream_parser.h" #include "net/http/http_transaction_test_util.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_info.h" @@ -210,6 +215,14 @@ void TestLoadTimingNotReusedWithPac(const net::LoadTimingInfo& load_timing_info, EXPECT_TRUE(load_timing_info.receive_headers_end.is_null()); } +void AddWebSocketHeaders(net::HttpRequestHeaders* headers) { + headers->SetHeader("Connection", "Upgrade"); + headers->SetHeader("Upgrade", "websocket"); + headers->SetHeader("Origin", "http://www.google.com"); + headers->SetHeader("Sec-WebSocket-Version", "13"); + headers->SetHeader("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); +} + } // namespace namespace net { @@ -12470,6 +12483,118 @@ class FakeStreamFactory : public HttpStreamFactory { DISALLOW_COPY_AND_ASSIGN(FakeStreamFactory); }; +// TODO(ricea): Maybe unify this with the one in +// url_request_http_job_unittest.cc ? +class FakeWebSocketBasicHandshakeStream : public WebSocketHandshakeStreamBase { + public: + FakeWebSocketBasicHandshakeStream(scoped_ptr<ClientSocketHandle> connection, + bool using_proxy) + : state_(connection.release(), using_proxy) {} + + // Fake implementation of HttpStreamBase methods. + // This ends up being quite "real" because this object has to really send data + // on the mock socket. It might be easier to use the real implementation, but + // the fact that the WebSocket code is not compiled on iOS makes that + // difficult. + int InitializeStream(const HttpRequestInfo* request_info, + RequestPriority priority, + const BoundNetLog& net_log, + const CompletionCallback& callback) override { + state_.Initialize(request_info, priority, net_log, callback); + return OK; + } + + int SendRequest(const HttpRequestHeaders& request_headers, + HttpResponseInfo* response, + const CompletionCallback& callback) override { + return parser()->SendRequest(state_.GenerateRequestLine(), request_headers, + response, callback); + } + + int ReadResponseHeaders(const CompletionCallback& callback) override { + return parser()->ReadResponseHeaders(callback); + } + + int ReadResponseBody(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) override { + NOTREACHED(); + return ERR_IO_PENDING; + } + + void Close(bool not_reusable) override { + if (parser()) + parser()->Close(true); + } + + bool IsResponseBodyComplete() const override { + NOTREACHED(); + return false; + } + + bool CanFindEndOfResponse() const override { + return parser()->CanFindEndOfResponse(); + } + + bool IsConnectionReused() const override { + NOTREACHED(); + return false; + } + void SetConnectionReused() override { NOTREACHED(); } + + bool IsConnectionReusable() const override { + NOTREACHED(); + return false; + } + + int64 GetTotalReceivedBytes() const override { + NOTREACHED(); + return 0; + } + + bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override { + NOTREACHED(); + return false; + } + + void GetSSLInfo(SSLInfo* ssl_info) override { NOTREACHED(); } + + void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override { + NOTREACHED(); + } + + bool IsSpdyHttpStream() const override { + NOTREACHED(); + return false; + } + + void Drain(HttpNetworkSession* session) override { NOTREACHED(); } + + void SetPriority(RequestPriority priority) override { NOTREACHED(); } + + UploadProgress GetUploadProgress() const override { + NOTREACHED(); + return UploadProgress(); + } + + HttpStream* RenewStreamForAuth() override { + NOTREACHED(); + return nullptr; + } + + // Fake implementation of WebSocketHandshakeStreamBase method(s) + scoped_ptr<WebSocketStream> Upgrade() override { + NOTREACHED(); + return scoped_ptr<WebSocketStream>(); + } + + private: + HttpStreamParser* parser() const { return state_.parser(); } + HttpBasicState state_; + + DISALLOW_COPY_AND_ASSIGN(FakeWebSocketBasicHandshakeStream); +}; + // TODO(yhirano): Split this class out into a net/websockets file, if it is // worth doing. class FakeWebSocketStreamCreateHelper : @@ -12478,8 +12603,8 @@ class FakeWebSocketStreamCreateHelper : WebSocketHandshakeStreamBase* CreateBasicStream( scoped_ptr<ClientSocketHandle> connection, bool using_proxy) override { - NOTREACHED(); - return NULL; + return new FakeWebSocketBasicHandshakeStream(connection.Pass(), + using_proxy); } WebSocketHandshakeStreamBase* CreateSpdyStream( @@ -13253,4 +13378,188 @@ TEST_P(HttpNetworkTransactionTest, PostIgnoresPartial400HeadersAfterReset) { EXPECT_TRUE(response == NULL); } +// Verify that proxy headers are not sent to the destination server when +// establishing a tunnel for a secure WebSocket connection. +TEST_P(HttpNetworkTransactionTest, ProxyHeadersNotSentOverWssTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("wss://www.google.com/"); + AddWebSocketHeaders(&request.extra_headers); + + // Configure against proxy server "myproxy:70". + session_deps_.proxy_service.reset( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + + // Since a proxy is configured, try to establish a tunnel. + MockWrite data_writes[] = { + MockWrite( + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite( + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + + MockWrite( + "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Origin: http://www.google.com\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 101 Switching Protocols\r\n"), + MockRead("Upgrade: websocket\r\n"), + MockRead("Connection: Upgrade\r\n"), + MockRead("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get())); + FakeWebSocketStreamCreateHelper websocket_stream_create_helper; + trans->SetWebSocketHandshakeStreamCreateHelper( + &websocket_stream_create_helper); + + { + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + } + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + ASSERT_TRUE(response->headers.get()); + EXPECT_EQ(407, response->headers->response_code()); + + { + TestCompletionCallback callback; + + int rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), + callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + } + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + ASSERT_TRUE(response->headers.get()); + + EXPECT_EQ(101, response->headers->response_code()); + + trans.reset(); + session->CloseAllConnections(); +} + +// Verify that proxy headers are not sent to the destination server when +// establishing a tunnel for an insecure WebSocket connection. +// This requires the authentication info to be injected into the auth cache +// due to crbug.com/395064 +// TODO(ricea): Change to use a 407 response once issue 395064 is fixed. +TEST_P(HttpNetworkTransactionTest, ProxyHeadersNotSentOverWsTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("ws://www.google.com/"); + AddWebSocketHeaders(&request.extra_headers); + + // Configure against proxy server "myproxy:70". + session_deps_.proxy_service.reset( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + + MockWrite data_writes[] = { + // Try to establish a tunnel for the WebSocket connection, with + // credentials. Because WebSockets have a separate set of socket pools, + // they cannot and will not use the same TCP/IP connection as the + // preflight HTTP request. + MockWrite( + "CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + + MockWrite( + "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Origin: http://www.google.com\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n"), + }; + + MockRead data_reads[] = { + // HTTP CONNECT with credentials. + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + // WebSocket connection established inside tunnel. + MockRead("HTTP/1.1 101 Switching Protocols\r\n"), + MockRead("Upgrade: websocket\r\n"), + MockRead("Connection: Upgrade\r\n"), + MockRead("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + session->http_auth_cache()->Add( + GURL("http://myproxy:70/"), "MyRealm1", HttpAuth::AUTH_SCHEME_BASIC, + "Basic realm=MyRealm1", AuthCredentials(kFoo, kBar), "/"); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get())); + FakeWebSocketStreamCreateHelper websocket_stream_create_helper; + trans->SetWebSocketHandshakeStreamCreateHelper( + &websocket_stream_create_helper); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + ASSERT_TRUE(response->headers.get()); + + EXPECT_EQ(101, response->headers->response_code()); + + trans.reset(); + session->CloseAllConnections(); +} + } // namespace net |