Index: net/http/http_network_transaction_unittest.cc |
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc |
index 136791ec5bea3af66d2574b228272ccf6e8becb0..a85e6ea9e9d44841946df4ff17345c44c3e3abe1 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 { |
@@ -564,19 +577,18 @@ CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool( |
template <> |
CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool( |
- HostResolver* host_resolver, |
+ HostResolver* /* host_resolver */, |
CertVerifier* /* cert_verifier */) |
- : HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL) { |
+ : HttpProxyClientSocketPool(0, 0, NULL, NULL, NULL, NULL) { |
} |
template <> |
CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( |
- HostResolver* host_resolver, |
+ HostResolver* /* host_resolver */, |
CertVerifier* cert_verifier) |
: SSLClientSocketPool(0, |
0, |
NULL, |
- host_resolver, |
cert_verifier, |
NULL, |
NULL, |
@@ -1842,8 +1854,8 @@ TEST_P(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) { |
"HTTP/1.1 301 Moved Permanently", |
}; |
- COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines), |
- forgot_to_update_kStatusLines); |
+ static_assert(kNumUnreadBodies == arraysize(kStatusLines), |
+ "forgot to update kStatusLines"); |
for (int i = 0; i < kNumUnreadBodies; ++i) |
EXPECT_EQ(kStatusLines[i], response_lines[i]); |
@@ -12471,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 : |
@@ -12479,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( |
@@ -13254,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 |