Index: net/websockets/websocket_job_unittest.cc |
diff --git a/net/websockets/websocket_job_unittest.cc b/net/websockets/websocket_job_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..de96a323f56b8bd69130ea715a1d71b20fdf5aae |
--- /dev/null |
+++ b/net/websockets/websocket_job_unittest.cc |
@@ -0,0 +1,495 @@ |
+// Copyright (c) 2010 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 <string> |
+#include <vector> |
+ |
+#include "base/ref_counted.h" |
+#include "googleurl/src/gurl.h" |
+#include "net/base/cookie_policy.h" |
+#include "net/base/cookie_store.h" |
+#include "net/base/net_errors.h" |
+#include "net/socket_stream/socket_stream.h" |
+#include "net/url_request/url_request_context.h" |
+#include "net/websockets/websocket_job.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/platform_test.h" |
+ |
+namespace net { |
+ |
+class MockSocketStream : public SocketStream { |
+ public: |
+ MockSocketStream(const GURL& url, SocketStream::Delegate* delegate) |
+ : SocketStream(url, delegate) {} |
+ virtual ~MockSocketStream() {} |
+ |
+ virtual void Connect() {} |
+ virtual bool SendData(const char* data, int len) { |
+ sent_data_ += std::string(data, len); |
+ return true; |
+ } |
+ |
+ virtual void Close() {} |
+ virtual void RestartWithAuth( |
+ const std::wstring& username, std::wstring& password) {} |
+ virtual void DetachDelegate() { |
+ delegate_ = NULL; |
+ } |
+ |
+ const std::string& sent_data() const { |
+ return sent_data_; |
+ } |
+ |
+ private: |
+ std::string sent_data_; |
+}; |
+ |
+class MockSocketStreamDelegate : public SocketStream::Delegate { |
+ public: |
+ MockSocketStreamDelegate() |
+ : amount_sent_(0) {} |
+ virtual ~MockSocketStreamDelegate() {} |
+ |
+ virtual void OnConnected(SocketStream* socket, int max_pending_send_allowed) { |
+ } |
+ virtual void OnSentData(SocketStream* socket, int amount_sent) { |
+ amount_sent_ += amount_sent; |
+ } |
+ virtual void OnReceivedData(SocketStream* socket, |
+ const char* data, int len) { |
+ received_data_ += std::string(data, len); |
+ } |
+ virtual void OnClose(SocketStream* socket) { |
+ } |
+ |
+ size_t amount_sent() const { return amount_sent_; } |
+ const std::string& received_data() const { return received_data_; } |
+ |
+ private: |
+ int amount_sent_; |
+ std::string received_data_; |
+}; |
+ |
+class MockCookieStore : public CookieStore { |
+ public: |
+ struct Entry { |
+ GURL url; |
+ std::string cookie_line; |
+ CookieOptions options; |
+ }; |
+ MockCookieStore() {} |
+ |
+ virtual bool SetCookieWithOptions(const GURL& url, |
+ const std::string& cookie_line, |
+ const CookieOptions& options) { |
+ Entry entry; |
+ entry.url = url; |
+ entry.cookie_line = cookie_line; |
+ entry.options = options; |
+ entries_.push_back(entry); |
+ return true; |
+ } |
+ virtual std::string GetCookiesWithOptions(const GURL& url, |
+ const CookieOptions& options) { |
+ std::string result; |
+ for (size_t i = 0; i < entries_.size(); i++) { |
+ Entry &entry = entries_[i]; |
+ if (url == entry.url) { |
+ if (!result.empty()) { |
+ result += "; "; |
+ } |
+ result += entry.cookie_line; |
+ } |
+ } |
+ return result; |
+ } |
+ virtual void DeleteCookie(const GURL& url, |
+ const std::string& cookie_name) {} |
+ virtual CookieMonster* GetCookieMonster() { return NULL; } |
+ |
+ const std::vector<Entry>& entries() const { return entries_; } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<MockCookieStore>; |
+ virtual ~MockCookieStore() {} |
+ |
+ std::vector<Entry> entries_; |
+}; |
+ |
+class MockCookiePolicy : public CookiePolicy, |
+ public base::RefCountedThreadSafe<MockCookiePolicy> { |
+ public: |
+ MockCookiePolicy() : allow_all_cookies_(true), callback_(NULL) {} |
+ |
+ void set_allow_all_cookies(bool allow_all_cookies) { |
+ allow_all_cookies_ = allow_all_cookies; |
+ } |
+ |
+ virtual int CanGetCookies(const GURL& url, |
+ const GURL& first_party_for_cookies, |
+ CompletionCallback* callback) { |
+ DCHECK(!callback_); |
+ callback_ = callback; |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanGetCookies)); |
+ return ERR_IO_PENDING; |
+ } |
+ |
+ virtual int CanSetCookie(const GURL& url, |
+ const GURL& first_party_for_cookies, |
+ const std::string& cookie_line, |
+ CompletionCallback* callback) { |
+ DCHECK(!callback_); |
+ callback_ = callback; |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanSetCookie)); |
+ return ERR_IO_PENDING; |
+ } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<MockCookiePolicy>; |
+ virtual ~MockCookiePolicy() {} |
+ |
+ void OnCanGetCookies() { |
+ CompletionCallback* callback = callback_; |
+ callback_ = NULL; |
+ if (allow_all_cookies_) |
+ callback->Run(OK); |
+ else |
+ callback->Run(ERR_ACCESS_DENIED); |
+ } |
+ void OnCanSetCookie() { |
+ CompletionCallback* callback = callback_; |
+ callback_ = NULL; |
+ if (allow_all_cookies_) |
+ callback->Run(OK); |
+ else |
+ callback->Run(ERR_ACCESS_DENIED); |
+ } |
+ |
+ bool allow_all_cookies_; |
+ CompletionCallback* callback_; |
+}; |
+ |
+class MockURLRequestContext : public URLRequestContext { |
+ public: |
+ MockURLRequestContext(CookieStore* cookie_store, |
+ CookiePolicy* cookie_policy) { |
+ cookie_store_ = cookie_store; |
+ cookie_policy_ = cookie_policy; |
+ } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<MockURLRequestContext>; |
+ virtual ~MockURLRequestContext() {} |
+}; |
+ |
+class WebSocketJobTest : public PlatformTest { |
+ public: |
+ virtual void SetUp() { |
+ cookie_store_ = new MockCookieStore; |
+ cookie_policy_ = new MockCookiePolicy; |
+ context_ = new MockURLRequestContext( |
+ cookie_store_.get(), cookie_policy_.get()); |
+ } |
+ virtual void TearDown() { |
+ cookie_store_ = NULL; |
+ cookie_policy_ = NULL; |
+ context_ = NULL; |
+ websocket_ = NULL; |
+ socket_ = NULL; |
+ } |
+ protected: |
+ void InitWebSocketJob(const GURL& url, MockSocketStreamDelegate* delegate) { |
+ websocket_ = new WebSocketJob(delegate); |
+ socket_ = new MockSocketStream(url, websocket_.get()); |
+ websocket_->InitSocketStream(socket_.get()); |
+ websocket_->state_ = WebSocketJob::CONNECTING; |
+ websocket_->set_context(context_.get()); |
+ } |
+ WebSocketJob::State GetWebSocketJobState() { |
+ return websocket_->state_; |
+ } |
+ void CloseWebSocketJob() { |
+ if (websocket_->socket_) |
+ websocket_->socket_->DetachDelegate(); |
+ websocket_->state_ = WebSocketJob::CLOSED; |
+ websocket_->delegate_ = NULL; |
+ websocket_->socket_ = NULL; |
+ } |
+ |
+ scoped_refptr<MockCookieStore> cookie_store_; |
+ scoped_refptr<MockCookiePolicy> cookie_policy_; |
+ scoped_refptr<MockURLRequestContext> context_; |
+ scoped_refptr<WebSocketJob> websocket_; |
+ scoped_refptr<MockSocketStream> socket_; |
+}; |
+ |
+TEST_F(WebSocketJobTest, SimpleHandshake) { |
+ GURL url("ws://example.com/demo"); |
+ MockSocketStreamDelegate delegate; |
+ InitWebSocketJob(url, &delegate); |
+ |
+ static const char* kHandshakeRequestMessage = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ bool sent = websocket_->SendData(kHandshakeRequestMessage, |
+ strlen(kHandshakeRequestMessage)); |
+ EXPECT_EQ(true, sent); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ websocket_->OnSentData(socket_.get(), strlen(kHandshakeRequestMessage)); |
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
+ |
+ static const char* kHandshakeResponseMessage = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ websocket_->OnReceivedData(socket_.get(), |
+ kHandshakeResponseMessage, |
+ strlen(kHandshakeResponseMessage)); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data()); |
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
+ CloseWebSocketJob(); |
+} |
+ |
+TEST_F(WebSocketJobTest, SlowHandshake) { |
+ GURL url("ws://example.com/demo"); |
+ MockSocketStreamDelegate delegate; |
+ InitWebSocketJob(url, &delegate); |
+ |
+ static const char* kHandshakeRequestMessage = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ std::vector<std::string> lines; |
+ SplitString(kHandshakeRequestMessage, '\n', &lines); |
+ for (size_t i = 0; i < lines.size() - 2; i++) { |
+ std::string line = lines[i] + "\r\n"; |
+ SCOPED_TRACE("Line: " + line); |
+ bool sent = websocket_->SendData(line.c_str(), line.size()); |
+ EXPECT_EQ(true, sent); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_TRUE(socket_->sent_data().empty()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ } |
+ bool sent = websocket_->SendData("\r\n", 2); |
+ EXPECT_EQ(true, sent); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ |
+ for (size_t i = 0; i < lines.size() - 2; i++) { |
+ std::string line = lines[i] + "\r\n"; |
+ SCOPED_TRACE("Line: " + line); |
+ websocket_->OnSentData(socket_.get(), line.size()); |
+ EXPECT_EQ(0U, delegate.amount_sent()); |
+ } |
+ websocket_->OnSentData(socket_.get(), 2); // \r\n |
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ |
+ static const char* kHandshakeResponseMessage = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ lines.clear(); |
+ SplitString(kHandshakeResponseMessage, '\n', &lines); |
+ for (size_t i = 0; i < lines.size() - 2; i++) { |
+ std::string line = lines[i] + "\r\n"; |
+ SCOPED_TRACE("Line: " + line); |
+ websocket_->OnReceivedData(socket_, |
+ line.c_str(), |
+ line.size()); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_TRUE(delegate.received_data().empty()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ } |
+ websocket_->OnReceivedData(socket_.get(), "\r\n", 2); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data()); |
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
+ CloseWebSocketJob(); |
+} |
+ |
+TEST_F(WebSocketJobTest, HandshakeWithCookie) { |
+ GURL url("ws://example.com/demo"); |
+ GURL cookieUrl("http://example.com/demo"); |
+ CookieOptions cookie_options; |
+ cookie_store_->SetCookieWithOptions( |
+ cookieUrl, "CR-test=1", cookie_options); |
+ cookie_options.set_include_httponly(); |
+ cookie_store_->SetCookieWithOptions( |
+ cookieUrl, "CR-test-httponly=1", cookie_options); |
+ |
+ MockSocketStreamDelegate delegate; |
+ InitWebSocketJob(url, &delegate); |
+ |
+ static const char* kHandshakeRequestMessage = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "Cookie: WK-test=1\r\n" |
+ "\r\n"; |
+ |
+ static const char* kHandshakeRequestExpected = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "Cookie: CR-test=1; CR-test-httponly=1\r\n" |
+ "\r\n"; |
+ |
+ bool sent = websocket_->SendData(kHandshakeRequestMessage, |
+ strlen(kHandshakeRequestMessage)); |
+ EXPECT_EQ(true, sent); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected)); |
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
+ |
+ static const char* kHandshakeResponseMessage = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "Set-Cookie: CR-set-test=1\r\n" |
+ "\r\n"; |
+ |
+ static const char* kHandshakeResponseExpected = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ websocket_->OnReceivedData(socket_.get(), |
+ kHandshakeResponseMessage, |
+ strlen(kHandshakeResponseMessage)); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data()); |
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
+ |
+ EXPECT_EQ(3U, cookie_store_->entries().size()); |
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); |
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); |
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); |
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); |
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); |
+ EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); |
+ |
+ CloseWebSocketJob(); |
+} |
+ |
+TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { |
+ GURL url("ws://example.com/demo"); |
+ GURL cookieUrl("http://example.com/demo"); |
+ CookieOptions cookie_options; |
+ cookie_store_->SetCookieWithOptions( |
+ cookieUrl, "CR-test=1", cookie_options); |
+ cookie_options.set_include_httponly(); |
+ cookie_store_->SetCookieWithOptions( |
+ cookieUrl, "CR-test-httponly=1", cookie_options); |
+ cookie_policy_->set_allow_all_cookies(false); |
+ |
+ MockSocketStreamDelegate delegate; |
+ InitWebSocketJob(url, &delegate); |
+ |
+ static const char* kHandshakeRequestMessage = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "Cookie: WK-test=1\r\n" |
+ "\r\n"; |
+ |
+ static const char* kHandshakeRequestExpected = |
+ "GET /demo HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Host: example.com\r\n" |
+ "Origin: http://example.com\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ bool sent = websocket_->SendData(kHandshakeRequestMessage, |
+ strlen(kHandshakeRequestMessage)); |
+ EXPECT_EQ(true, sent); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data()); |
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
+ websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected)); |
+ EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
+ |
+ static const char* kHandshakeResponseMessage = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "Set-Cookie: CR-set-test=1\r\n" |
+ "\r\n"; |
+ |
+ static const char* kHandshakeResponseExpected = |
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "WebSocket-Origin: http://example.com\r\n" |
+ "WebSocket-Location: ws://example.com/demo\r\n" |
+ "WebSocket-Protocol: sample\r\n" |
+ "\r\n"; |
+ |
+ websocket_->OnReceivedData(socket_.get(), |
+ kHandshakeResponseMessage, |
+ strlen(kHandshakeResponseMessage)); |
+ MessageLoop::current()->RunAllPending(); |
+ EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data()); |
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
+ |
+ EXPECT_EQ(2U, cookie_store_->entries().size()); |
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); |
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); |
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); |
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); |
+ |
+ CloseWebSocketJob(); |
+} |
+ |
+} // namespace net |