Provide child/frame IDs for WebSocket handshake request
AndroidCookiePolicy needs the child ID and the frame ID of a WebSocket
connection to determine if it allows the connection to attach
third-party cookies. This CL provide the additional information to the
WebSocket handshake net::URLRequest.
BUG=634311
Review-Url: https://codereview.chromium.org/2397393002
Cr-Commit-Position: refs/heads/master@{#427109}
diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc
index 1dc3ba9..f14d09f6 100644
--- a/android_webview/browser/aw_cookie_access_policy.cc
+++ b/android_webview/browser/aw_cookie_access_policy.cc
@@ -10,11 +10,13 @@
#include "base/logging.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
+#include "content/public/browser/websocket_handshake_request_info.h"
#include "net/base/net_errors.h"
using base::AutoLock;
using content::BrowserThread;
using content::ResourceRequestInfo;
+using content::WebSocketHandshakeRequestInfo;
using net::StaticCookiePolicy;
namespace android_webview {
@@ -57,12 +59,21 @@
bool AwCookieAccessPolicy::GetShouldAcceptThirdPartyCookies(
const net::URLRequest& request) {
+ int child_id = 0;
+ int frame_id = 0;
const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(&request);
- if (!info) {
- return false;
+ if (info) {
+ child_id = info->GetChildID();
+ frame_id = info->GetRenderFrameID();
+ } else {
+ const WebSocketHandshakeRequestInfo* websocket_info =
+ WebSocketHandshakeRequestInfo::ForRequest(&request);
+ if (!websocket_info)
+ return false;
+ child_id = websocket_info->GetChildId();
+ frame_id = websocket_info->GetRenderFrameId();
}
- return GetShouldAcceptThirdPartyCookies(info->GetChildID(),
- info->GetRenderFrameID());
+ return GetShouldAcceptThirdPartyCookies(child_id, frame_id);
}
bool AwCookieAccessPolicy::OnCanGetCookies(const net::URLRequest& request,
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index c96fdeb..08c6e3e 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -17,6 +17,8 @@
import org.chromium.android_webview.test.util.CookieUtils;
import org.chromium.android_webview.test.util.JSUtils;
import org.chromium.base.test.util.Feature;
+import org.chromium.content.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.WebContents;
import org.chromium.net.test.util.TestWebServer;
import java.util.ArrayList;
@@ -401,6 +403,51 @@
}
}
+ private String thirdPartyCookieForWebSocket(boolean acceptCookie) throws Throwable {
+ TestWebServer webServer = TestWebServer.start();
+ try {
+ // Turn global allow on.
+ mCookieManager.setAcceptCookie(true);
+ assertTrue(mCookieManager.acceptCookie());
+
+ // Sets the per-WebView value.
+ mAwContents.getSettings().setAcceptThirdPartyCookies(acceptCookie);
+ assertEquals(acceptCookie, mAwContents.getSettings().getAcceptThirdPartyCookies());
+
+ // |cookieUrl| is a third-party url that sets a cookie on response.
+ String cookieUrl = toThirdPartyUrl(
+ makeCookieWebSocketUrl(webServer, "/cookie_1", "test1", "value1"));
+ // This html file includes a script establishing a WebSocket connection to |cookieUrl|.
+ String url = makeWebSocketScriptUrl(webServer, "/content_1.html", cookieUrl);
+ loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+ final String connecting = "0"; // WebSocket.CONNECTING
+ final String closed = "3"; // WebSocket.CLOSED
+ String readyState = connecting;
+ WebContents webContents = mAwContents.getWebContents();
+ while (!readyState.equals(closed)) {
+ readyState = JavaScriptUtils.executeJavaScriptAndWaitForResult(
+ webContents, "ws.readyState");
+ }
+ assertEquals("true",
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, "hasOpened"));
+ return mCookieManager.getCookie(cookieUrl);
+ } finally {
+ webServer.shutdown();
+ }
+ }
+
+ @MediumTest
+ @Feature({"AndroidWebView", "Privacy"})
+ public void testThirdPartyCookieForWebSocketDisabledCase() throws Throwable {
+ assertNull(thirdPartyCookieForWebSocket(false));
+ }
+
+ @MediumTest
+ @Feature({"AndroidWebView", "Privacy"})
+ public void testThirdPartyCookieForWebSocketEnabledCase() throws Throwable {
+ assertEquals("test1=value1", thirdPartyCookieForWebSocket(true));
+ }
+
/**
* Creates a response on the TestWebServer which attempts to set a cookie when fetched.
* @param webServer the webServer on which to create the response
@@ -418,6 +465,22 @@
}
/**
+ * Creates a response on the TestWebServer which attempts to set a cookie when establishing a
+ * WebSocket connection.
+ * @param webServer the webServer on which to create the response
+ * @param path the path component of the url (e.g "/cookie_test.html")
+ * @param key the key of the cookie
+ * @param value the value of the cookie
+ * @return the url which gets the response
+ */
+ private String makeCookieWebSocketUrl(
+ TestWebServer webServer, String path, String key, String value) {
+ List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>();
+ responseHeaders.add(Pair.create("Set-Cookie", key + "=" + value + "; path=" + path));
+ return webServer.setResponseForWebSocket(path, responseHeaders);
+ }
+
+ /**
* Creates a response on the TestWebServer which contains a script tag with an external src.
* @param webServer the webServer on which to create the response
* @param path the path component of the url (e.g "/my_thing_with_script.html")
@@ -430,6 +493,24 @@
return webServer.setResponse(path, responseStr, null);
}
+ /**
+ * Creates a response on the TestWebServer which contains a script establishing a WebSocket
+ * connection.
+ * @param webServer the webServer on which to create the response
+ * @param path the path component of the url (e.g "/my_thing_with_script.html")
+ * @param url the url which which should appear as the src of the script tag.
+ * @return the url which gets the response
+ */
+ private String makeWebSocketScriptUrl(TestWebServer webServer, String path, String url) {
+ String responseStr = "<html><head><title>Content!</title></head>"
+ + "<body><script>\n"
+ + "let ws = new WebSocket('" + url.replaceAll("^http", "ws") + "');\n"
+ + "let hasOpened = false;\n"
+ + "ws.onopen = () => hasOpened = true;\n"
+ + "</script></body></html>";
+ return webServer.setResponse(path, responseStr, null);
+ }
+
@MediumTest
@Feature({"AndroidWebView", "Privacy"})
public void testThirdPartyJavascriptCookie() throws Throwable {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 819f89f..9365a8de 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1349,6 +1349,8 @@
"web_contents/web_drag_source_mac.mm",
"web_contents/web_drag_utils_win.cc",
"web_contents/web_drag_utils_win.h",
+ "websockets/websocket_handshake_request_info_impl.cc",
+ "websockets/websocket_handshake_request_info_impl.h",
"websockets/websocket_impl.cc",
"websockets/websocket_impl.h",
"websockets/websocket_manager.cc",
diff --git a/content/browser/websockets/websocket_handshake_request_info_impl.cc b/content/browser/websockets/websocket_handshake_request_info_impl.cc
new file mode 100644
index 0000000..14bb5cb1
--- /dev/null
+++ b/content/browser/websockets/websocket_handshake_request_info_impl.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 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 "content/browser/websockets/websocket_handshake_request_info_impl.h"
+
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+namespace {
+
+constexpr int g_tag = 0;
+
+} // namesapce
+
+WebSocketHandshakeRequestInfoImpl::WebSocketHandshakeRequestInfoImpl(
+ int child_id,
+ int render_frame_id)
+ : child_id_(child_id), render_frame_id_(render_frame_id) {}
+
+WebSocketHandshakeRequestInfoImpl::~WebSocketHandshakeRequestInfoImpl() {}
+
+void WebSocketHandshakeRequestInfoImpl::CreateInfoAndAssociateWithRequest(
+ int child_id,
+ int render_frame_id,
+ net::URLRequest* request) {
+ request->SetUserData(
+ &g_tag, new WebSocketHandshakeRequestInfoImpl(child_id, render_frame_id));
+}
+
+int WebSocketHandshakeRequestInfoImpl::GetChildId() const {
+ return child_id_;
+}
+
+int WebSocketHandshakeRequestInfoImpl::GetRenderFrameId() const {
+ return render_frame_id_;
+}
+
+const WebSocketHandshakeRequestInfo* WebSocketHandshakeRequestInfo::ForRequest(
+ const net::URLRequest* request) {
+ return static_cast<WebSocketHandshakeRequestInfoImpl*>(
+ request->GetUserData(&g_tag));
+}
+
+} // namespace content
diff --git a/content/browser/websockets/websocket_handshake_request_info_impl.h b/content/browser/websockets/websocket_handshake_request_info_impl.h
new file mode 100644
index 0000000..4e768b1
--- /dev/null
+++ b/content/browser/websockets/websocket_handshake_request_info_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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.
+
+#ifndef CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
+#define CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
+
+#include "content/public/browser/websocket_handshake_request_info.h"
+
+#include "base/macros.h"
+#include "base/supports_user_data.h"
+
+namespace content {
+
+class WebSocketHandshakeRequestInfoImpl final
+ : public WebSocketHandshakeRequestInfo,
+ public base::SupportsUserData::Data {
+ public:
+ static void CreateInfoAndAssociateWithRequest(int child_id,
+ int render_frame_id,
+ net::URLRequest* request);
+
+ int GetChildId() const override;
+ int GetRenderFrameId() const override;
+
+ private:
+ WebSocketHandshakeRequestInfoImpl(int child_id, int render_frame_id);
+ ~WebSocketHandshakeRequestInfoImpl() override;
+
+ const int child_id_;
+ const int render_frame_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestInfoImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
diff --git a/content/browser/websockets/websocket_impl.cc b/content/browser/websockets/websocket_impl.cc
index 6ea179d..7019bc5 100644
--- a/content/browser/websockets/websocket_impl.cc
+++ b/content/browser/websockets/websocket_impl.cc
@@ -22,6 +22,7 @@
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/ssl/ssl_error_handler.h"
#include "content/browser/ssl/ssl_manager.h"
+#include "content/browser/websockets/websocket_handshake_request_info_impl.h"
#include "content/public/browser/storage_partition.h"
#include "ipc/ipc_message.h"
#include "net/base/io_buffer.h"
@@ -90,6 +91,7 @@
// net::WebSocketEventInterface implementation
+ void OnCreateURLRequest(net::URLRequest* url_request) override;
ChannelState OnAddChannelResponse(const std::string& selected_subprotocol,
const std::string& extensions) override;
ChannelState OnDataFrame(bool fin,
@@ -151,6 +153,12 @@
<< reinterpret_cast<void*>(this);
}
+void WebSocketImpl::WebSocketEventHandler::OnCreateURLRequest(
+ net::URLRequest* url_request) {
+ WebSocketHandshakeRequestInfoImpl::CreateInfoAndAssociateWithRequest(
+ impl_->child_id_, impl_->frame_id_, url_request);
+}
+
ChannelState WebSocketImpl::WebSocketEventHandler::OnAddChannelResponse(
const std::string& selected_protocol,
const std::string& extensions) {
@@ -354,15 +362,16 @@
callbacks_->ContinueSSLRequest();
}
-WebSocketImpl::WebSocketImpl(
- Delegate* delegate,
- blink::mojom::WebSocketRequest request,
- int frame_id,
- base::TimeDelta delay)
+WebSocketImpl::WebSocketImpl(Delegate* delegate,
+ blink::mojom::WebSocketRequest request,
+ int child_id,
+ int frame_id,
+ base::TimeDelta delay)
: delegate_(delegate),
binding_(this, std::move(request)),
delay_(delay),
pending_flow_control_quota_(0),
+ child_id_(child_id),
frame_id_(frame_id),
handshake_succeeded_(false),
weak_ptr_factory_(this) {
diff --git a/content/browser/websockets/websocket_impl.h b/content/browser/websockets/websocket_impl.h
index fb64613..c6da1316 100644
--- a/content/browser/websockets/websocket_impl.h
+++ b/content/browser/websockets/websocket_impl.h
@@ -46,6 +46,7 @@
WebSocketImpl(Delegate* delegate,
blink::mojom::WebSocketRequest request,
+ int child_id,
int frame_id,
base::TimeDelta delay);
~WebSocketImpl() override;
@@ -96,6 +97,7 @@
// Zero indicates there is no pending SendFlowControl().
int64_t pending_flow_control_quota_;
+ int child_id_;
int frame_id_;
// handshake_succeeded_ is set and used by WebSocketManager to manage
diff --git a/content/browser/websockets/websocket_manager.cc b/content/browser/websockets/websocket_manager.cc
index bd0e372..6984564f 100644
--- a/content/browser/websockets/websocket_manager.cc
+++ b/content/browser/websockets/websocket_manager.cc
@@ -118,8 +118,8 @@
// Keep all WebSocketImpls alive until either the client drops its
// connection (see OnLostConnectionToClient) or we need to shutdown.
- impls_.insert(CreateWebSocketImpl(this, std::move(request), frame_id,
- CalculateDelay()));
+ impls_.insert(CreateWebSocketImpl(this, std::move(request), process_id_,
+ frame_id, CalculateDelay()));
++num_pending_connections_;
if (!throttling_period_timer_.IsRunning()) {
@@ -161,9 +161,11 @@
WebSocketImpl* WebSocketManager::CreateWebSocketImpl(
WebSocketImpl::Delegate* delegate,
blink::mojom::WebSocketRequest request,
+ int child_id,
int frame_id,
base::TimeDelta delay) {
- return new WebSocketImpl(delegate, std::move(request), frame_id, delay);
+ return new WebSocketImpl(delegate, std::move(request), child_id, frame_id,
+ delay);
}
int WebSocketManager::GetClientProcessId() {
diff --git a/content/browser/websockets/websocket_manager.h b/content/browser/websockets/websocket_manager.h
index bee498a..20132ded 100644
--- a/content/browser/websockets/websocket_manager.h
+++ b/content/browser/websockets/websocket_manager.h
@@ -45,6 +45,7 @@
virtual WebSocketImpl* CreateWebSocketImpl(
WebSocketImpl::Delegate* delegate,
blink::mojom::WebSocketRequest request,
+ int child_id,
int frame_id,
base::TimeDelta delay);
diff --git a/content/browser/websockets/websocket_manager_unittest.cc b/content/browser/websockets/websocket_manager_unittest.cc
index c2a90b80..22cb7c4d 100644
--- a/content/browser/websockets/websocket_manager_unittest.cc
+++ b/content/browser/websockets/websocket_manager_unittest.cc
@@ -23,9 +23,14 @@
public:
TestWebSocketImpl(Delegate* delegate,
blink::mojom::WebSocketRequest request,
+ int process_id,
int frame_id,
base::TimeDelta delay)
- : WebSocketImpl(delegate, std::move(request), frame_id, delay) {}
+ : WebSocketImpl(delegate,
+ std::move(request),
+ process_id,
+ frame_id,
+ delay) {}
base::TimeDelta delay() const { return delay_; }
@@ -61,10 +66,11 @@
private:
WebSocketImpl* CreateWebSocketImpl(WebSocketImpl::Delegate* delegate,
blink::mojom::WebSocketRequest request,
+ int process_id,
int frame_id,
base::TimeDelta delay) override {
- TestWebSocketImpl* impl =
- new TestWebSocketImpl(delegate, std::move(request), frame_id, delay);
+ TestWebSocketImpl* impl = new TestWebSocketImpl(
+ delegate, std::move(request), process_id, frame_id, delay);
// We keep a vector of sockets here to track their creation order.
sockets_.push_back(impl);
return impl;
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 3cf7963..143e737 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -273,6 +273,7 @@
"web_ui_controller_factory.h",
"web_ui_data_source.h",
"web_ui_message_handler.h",
+ "websocket_handshake_request_info.h",
"worker_service.h",
"worker_service_observer.h",
"zoom_level_delegate.h",
diff --git a/content/public/browser/websocket_handshake_request_info.h b/content/public/browser/websocket_handshake_request_info.h
new file mode 100644
index 0000000..def71272
--- /dev/null
+++ b/content/public/browser/websocket_handshake_request_info.h
@@ -0,0 +1,34 @@
+// Copyright 2016 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.
+
+#ifndef CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
+#define CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
+
+#include "content/common/content_export.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace content {
+
+// WebSocketHandshakeRequestInfo provides additional information attached to
+// a net::URLRequest.
+class WebSocketHandshakeRequestInfo {
+ public:
+ virtual ~WebSocketHandshakeRequestInfo() {}
+ // Returns the ID of the child process where the WebSocket connection lives.
+ virtual int GetChildId() const = 0;
+ // Returns the ID of the renderer frame where the WebSocket connection lives.
+ virtual int GetRenderFrameId() const = 0;
+
+ // Returns the WebSocketHandshakeRequestInfo instance attached to the given
+ // URLRequest. Returns nullptr when no instance is attached.
+ CONTENT_EXPORT static const WebSocketHandshakeRequestInfo* ForRequest(
+ const net::URLRequest* request);
+};
+
+} // namespace content
+
+#endif // CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
diff --git a/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java b/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
index 9023991..5ca9b52 100644
--- a/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
+++ b/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
@@ -9,6 +9,7 @@
import android.util.Log;
import android.util.Pair;
+import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -33,8 +34,10 @@
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
@@ -80,13 +83,15 @@
final Runnable mResponseAction;
final boolean mIsNotFound;
final boolean mIsNoContent;
+ final boolean mForWebSocket;
Response(byte[] responseData, List<Pair<String, String>> responseHeaders,
- boolean isRedirect, boolean isNotFound, boolean isNoContent,
+ boolean isRedirect, boolean isNotFound, boolean isNoContent, boolean forWebSocket,
Runnable responseAction) {
mIsRedirect = isRedirect;
mIsNotFound = isNotFound;
mIsNoContent = isNoContent;
+ mForWebSocket = forWebSocket;
mResponseData = responseData;
mResponseHeaders = responseHeaders == null
? new ArrayList<Pair<String, String>>() : responseHeaders;
@@ -195,6 +200,7 @@
private static final int RESPONSE_STATUS_MOVED_TEMPORARILY = 1;
private static final int RESPONSE_STATUS_NOT_FOUND = 2;
private static final int RESPONSE_STATUS_NO_CONTENT = 3;
+ private static final int RESPONSE_STATUS_FOR_WEBSOCKET = 4;
private String setResponseInternal(
String requestPath, byte[] responseData,
@@ -203,11 +209,12 @@
final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY);
final boolean isNotFound = (status == RESPONSE_STATUS_NOT_FOUND);
final boolean isNoContent = (status == RESPONSE_STATUS_NO_CONTENT);
+ final boolean forWebSocket = (status == RESPONSE_STATUS_FOR_WEBSOCKET);
synchronized (mLock) {
- mResponseMap.put(requestPath, new Response(
- responseData, responseHeaders, isRedirect, isNotFound, isNoContent,
- responseAction));
+ mResponseMap.put(
+ requestPath, new Response(responseData, responseHeaders, isRedirect, isNotFound,
+ isNoContent, forWebSocket, responseAction));
mResponseCountMap.put(requestPath, Integer.valueOf(0));
mLastRequestMap.put(requestPath, null);
}
@@ -344,6 +351,28 @@
}
/**
+ * Sets a response to a WebSocket handshake request.
+ *
+ * @param requestPath The path to respond to.
+ * @param responseHeaders Any additional headers that should be returned along with the
+ * response (null is acceptable).
+ * @return The full URL including the path that should be requested to get the expected
+ * response.
+ */
+ public String setResponseForWebSocket(
+ String requestPath, List<Pair<String, String>> responseHeaders) {
+ if (responseHeaders == null) {
+ responseHeaders = new ArrayList<Pair<String, String>>();
+ } else {
+ responseHeaders = new ArrayList<Pair<String, String>>(responseHeaders);
+ }
+ responseHeaders.add(Pair.create("Connection", "Upgrade"));
+ responseHeaders.add(Pair.create("Upgrade", "websocket"));
+ return setResponseInternal(
+ requestPath, "".getBytes(), responseHeaders, null, RESPONSE_STATUS_FOR_WEBSOCKET);
+ }
+
+ /**
* Get the number of requests was made at this path since it was last set.
*/
public int getRequestCount(String requestPath) {
@@ -481,6 +510,23 @@
httpResponse.addHeader(header.first, header.second);
}
servedResponseFor(path, request);
+ } else if (response.mForWebSocket) {
+ Header[] keys = request.getHeaders("Sec-WebSocket-Key");
+ try {
+ if (keys.length == 1) {
+ final String key = keys[0].getValue();
+ httpResponse = createResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
+ for (Pair<String, String> header : response.mResponseHeaders) {
+ httpResponse.addHeader(header.first, header.second);
+ }
+ httpResponse.addHeader("Sec-WebSocket-Accept", computeWebSocketAccept(key));
+ } else {
+ httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
+ }
+ servedResponseFor(path, request);
} else {
if (response.mResponseAction != null) response.mResponseAction.run();
@@ -552,6 +598,20 @@
return entity;
}
+ /**
+ * Return a response for WebSocket handshake challenge.
+ */
+ private static String computeWebSocketAccept(String keyString) throws NoSuchAlgorithmException {
+ byte[] key = keyString.getBytes(Charset.forName("US-ASCII"));
+ byte[] guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(Charset.forName("US-ASCII"));
+
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ md.update(key);
+ md.update(guid);
+ byte[] output = md.digest();
+ return Base64.encodeToString(output, Base64.DEFAULT);
+ }
+
private static class ServerThread extends Thread {
private TestWebServer mServer;
private ServerSocket mSocket;
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
index 6cbd8b59..f5d639f 100644
--- a/net/websockets/websocket_channel.cc
+++ b/net/websockets/websocket_channel.cc
@@ -175,6 +175,10 @@
public:
explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
+ void OnCreateRequest(net::URLRequest* request) override {
+ creator_->OnCreateURLRequest(request);
+ }
+
void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {
creator_->OnConnectSuccess(std::move(stream));
// |this| may have been deleted.
@@ -603,6 +607,10 @@
SetState(CONNECTING);
}
+void WebSocketChannel::OnCreateURLRequest(URLRequest* request) {
+ event_interface_->OnCreateURLRequest(request);
+}
+
void WebSocketChannel::OnConnectSuccess(
std::unique_ptr<WebSocketStream> stream) {
DCHECK(stream);
diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h
index 43089791..2f68b14 100644
--- a/net/websockets/websocket_channel.h
+++ b/net/websockets/websocket_channel.h
@@ -33,6 +33,7 @@
class NetLogWithSource;
class IOBuffer;
+class URLRequest;
class URLRequestContext;
struct WebSocketHandshakeRequestInfo;
struct WebSocketHandshakeResponseInfo;
@@ -226,6 +227,9 @@
const std::string& additional_headers,
const WebSocketStreamRequestCreationCallback& callback);
+ // Called when a URLRequest is created for handshaking.
+ void OnCreateURLRequest(URLRequest* request);
+
// Success callback from WebSocketStream::CreateAndConnectStream(). Reports
// success to the event interface. May delete |this|.
void OnConnectSuccess(std::unique_ptr<WebSocketStream> stream);
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index 10ea232e..d2c477a 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -167,6 +167,7 @@
std::vector<char>(data, data + buffer_size));
}
+ MOCK_METHOD1(OnCreateURLRequest, void(URLRequest*));
MOCK_METHOD2(OnAddChannelResponse,
ChannelState(const std::string&,
const std::string&)); // NOLINT
@@ -211,6 +212,7 @@
// This fake EventInterface is for tests which need a WebSocketEventInterface
// implementation but are not verifying how it is used.
class FakeWebSocketEventInterface : public WebSocketEventInterface {
+ void OnCreateURLRequest(URLRequest* request) override {}
ChannelState OnAddChannelResponse(const std::string& selected_protocol,
const std::string& extensions) override {
return CHANNEL_ALIVE;
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 31b557a..ba2a657 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -37,6 +37,8 @@
namespace net {
+class URLRequest;
+
namespace {
static const char kEchoServer[] = "echo-with-no-extension";
@@ -66,6 +68,8 @@
std::string extensions() const;
// Implementation of WebSocketEventInterface.
+ void OnCreateURLRequest(URLRequest* request) override {}
+
ChannelState OnAddChannelResponse(const std::string& selected_subprotocol,
const std::string& extensions) override;
diff --git a/net/websockets/websocket_event_interface.h b/net/websockets/websocket_event_interface.h
index ac29659..db24207f 100644
--- a/net/websockets/websocket_event_interface.h
+++ b/net/websockets/websocket_event_interface.h
@@ -22,6 +22,7 @@
class IOBuffer;
class SSLInfo;
+class URLRequest;
struct WebSocketHandshakeRequestInfo;
struct WebSocketHandshakeResponseInfo;
@@ -41,6 +42,9 @@
virtual ~WebSocketEventInterface() {}
+ // Called when a URLRequest is created for handshaking.
+ virtual void OnCreateURLRequest(URLRequest* request) = 0;
+
// Called in response to an AddChannelRequest. This means that a response has
// been received from the remote server.
virtual ChannelState OnAddChannelResponse(
diff --git a/net/websockets/websocket_handshake_stream_create_helper_test.cc b/net/websockets/websocket_handshake_stream_create_helper_test.cc
index 6b43527ff..f4d7c26 100644
--- a/net/websockets/websocket_handshake_stream_create_helper_test.cc
+++ b/net/websockets/websocket_handshake_stream_create_helper_test.cc
@@ -65,6 +65,7 @@
public:
~TestConnectDelegate() override {}
+ void OnCreateRequest(URLRequest* request) override {}
void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {}
void OnFailure(const std::string& failure_message) override {}
void OnStartOpeningHandshake(
diff --git a/net/websockets/websocket_stream.cc b/net/websockets/websocket_stream.cc
index ef62c24..8cca365 100644
--- a/net/websockets/websocket_stream.cc
+++ b/net/websockets/websocket_stream.cc
@@ -116,6 +116,7 @@
WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
handshake_stream_create_helper_);
url_request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE);
+ connect_delegate_->OnCreateRequest(url_request_.get());
}
// Destroying this object destroys the URLRequest, which cancels the request
diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h
index 389e092..e960f3d 100644
--- a/net/websockets/websocket_stream.h
+++ b/net/websockets/websocket_stream.h
@@ -32,6 +32,7 @@
namespace net {
class NetLogWithSource;
+class URLRequest;
class URLRequestContext;
struct WebSocketFrame;
class WebSocketHandshakeStreamBase;
@@ -71,6 +72,9 @@
class NET_EXPORT_PRIVATE ConnectDelegate {
public:
virtual ~ConnectDelegate();
+ // Called when the URLRequest is created.
+ virtual void OnCreateRequest(URLRequest* url_request) = 0;
+
// Called on successful connection. The parameter is an object derived from
// WebSocketStream.
virtual void OnSuccess(std::unique_ptr<WebSocketStream> stream) = 0;
diff --git a/net/websockets/websocket_stream_create_test_base.cc b/net/websockets/websocket_stream_create_test_base.cc
index 6ca5d70..d3b1668 100644
--- a/net/websockets/websocket_stream_create_test_base.cc
+++ b/net/websockets/websocket_stream_create_test_base.cc
@@ -50,6 +50,10 @@
const base::Closure& done_callback)
: owner_(owner), done_callback_(done_callback) {}
+ void OnCreateRequest(URLRequest* request) override {
+ owner_->url_request_ = request;
+ }
+
void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {
stream.swap(owner_->stream_);
done_callback_.Run();
@@ -92,8 +96,7 @@
};
WebSocketStreamCreateTestBase::WebSocketStreamCreateTestBase()
- : has_failed_(false), ssl_fatal_(false) {
-}
+ : has_failed_(false), ssl_fatal_(false), url_request_(nullptr) {}
WebSocketStreamCreateTestBase::~WebSocketStreamCreateTestBase() {
}
diff --git a/net/websockets/websocket_stream_create_test_base.h b/net/websockets/websocket_stream_create_test_base.h
index be7912f..5805b50 100644
--- a/net/websockets/websocket_stream_create_test_base.h
+++ b/net/websockets/websocket_stream_create_test_base.h
@@ -24,6 +24,7 @@
class HttpRequestHeaders;
class HttpResponseHeaders;
+class URLRequest;
class WebSocketStream;
class WebSocketStreamRequest;
struct WebSocketHandshakeRequestInfo;
@@ -75,6 +76,7 @@
SSLInfo ssl_info_;
bool ssl_fatal_;
std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_data_;
+ URLRequest* url_request_;
// This temporarily sets WebSocketEndpointLockManager unlock delay to zero
// during tests.
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc
index fcafedc..091c28d 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -325,11 +325,13 @@
// Confirm that the basic case works as expected.
TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
+ EXPECT_FALSE(url_request_);
CreateAndConnectStandard("ws://localhost/", "localhost", "/",
NoSubProtocols(), LocalhostOrigin(), LocalhostUrl(),
"", "", "");
EXPECT_FALSE(request_info_);
EXPECT_FALSE(response_info_);
+ EXPECT_TRUE(url_request_);
WaitUntilConnectDone();
EXPECT_FALSE(has_failed());
EXPECT_TRUE(stream_);