1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/websockets/websocket_job.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/callback.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "net/base/completion_callback.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/test_completion_callback.h"
19 #include "net/cookies/cookie_store.h"
20 #include "net/cookies/cookie_store_test_helpers.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/http/http_transaction_factory.h"
23 #include "net/http/transport_security_state.h"
24 #include "net/proxy/proxy_service.h"
25 #include "net/socket/next_proto.h"
26 #include "net/socket/socket_test_util.h"
27 #include "net/socket_stream/socket_stream.h"
28 #include "net/spdy/spdy_session.h"
29 #include "net/spdy/spdy_websocket_test_util.h"
30 #include "net/ssl/ssl_config_service.h"
31 #include "net/url_request/url_request_context.h"
32 #include "net/websockets/websocket_throttle.h"
33 #include "testing/gmock/include/gmock/gmock.h"
34 #include "testing/gtest/include/gtest/gtest.h"
35 #include "testing/platform_test.h"
42 class MockSocketStream : public SocketStream {
44 MockSocketStream(const GURL& url, SocketStream::Delegate* delegate,
45 URLRequestContext* context, CookieStore* cookie_store)
46 : SocketStream(url, delegate, context, cookie_store) {}
48 virtual void Connect() OVERRIDE {}
49 virtual bool SendData(const char* data, int len) OVERRIDE {
50 sent_data_ += std::string(data, len);
54 virtual void Close() OVERRIDE {}
55 virtual void RestartWithAuth(
56 const AuthCredentials& credentials) OVERRIDE {
59 virtual void DetachDelegate() OVERRIDE {
63 const std::string& sent_data() const {
68 virtual ~MockSocketStream() {}
71 std::string sent_data_;
74 class MockSocketStreamDelegate : public SocketStream::Delegate {
76 MockSocketStreamDelegate()
77 : amount_sent_(0), allow_all_cookies_(true) {}
78 void set_allow_all_cookies(bool allow_all_cookies) {
79 allow_all_cookies_ = allow_all_cookies;
81 virtual ~MockSocketStreamDelegate() {}
83 void SetOnStartOpenConnection(const base::Closure& callback) {
84 on_start_open_connection_ = callback;
86 void SetOnConnected(const base::Closure& callback) {
87 on_connected_ = callback;
89 void SetOnSentData(const base::Closure& callback) {
90 on_sent_data_ = callback;
92 void SetOnReceivedData(const base::Closure& callback) {
93 on_received_data_ = callback;
95 void SetOnClose(const base::Closure& callback) {
99 virtual int OnStartOpenConnection(
100 SocketStream* socket,
101 const CompletionCallback& callback) OVERRIDE {
102 if (!on_start_open_connection_.is_null())
103 on_start_open_connection_.Run();
106 virtual void OnConnected(SocketStream* socket,
107 int max_pending_send_allowed) OVERRIDE {
108 if (!on_connected_.is_null())
111 virtual void OnSentData(SocketStream* socket,
112 int amount_sent) OVERRIDE {
113 amount_sent_ += amount_sent;
114 if (!on_sent_data_.is_null())
117 virtual void OnReceivedData(SocketStream* socket,
118 const char* data, int len) OVERRIDE {
119 received_data_ += std::string(data, len);
120 if (!on_received_data_.is_null())
121 on_received_data_.Run();
123 virtual void OnClose(SocketStream* socket) OVERRIDE {
124 if (!on_close_.is_null())
127 virtual bool CanGetCookies(SocketStream* socket,
128 const GURL& url) OVERRIDE {
129 return allow_all_cookies_;
131 virtual bool CanSetCookie(SocketStream* request,
133 const std::string& cookie_line,
134 CookieOptions* options) OVERRIDE {
135 return allow_all_cookies_;
138 size_t amount_sent() const { return amount_sent_; }
139 const std::string& received_data() const { return received_data_; }
143 bool allow_all_cookies_;
144 std::string received_data_;
145 base::Closure on_start_open_connection_;
146 base::Closure on_connected_;
147 base::Closure on_sent_data_;
148 base::Closure on_received_data_;
149 base::Closure on_close_;
152 class MockCookieStore : public CookieStore {
156 std::string cookie_line;
157 CookieOptions options;
162 bool SetCookieWithOptions(const GURL& url,
163 const std::string& cookie_line,
164 const CookieOptions& options) {
167 entry.cookie_line = cookie_line;
168 entry.options = options;
169 entries_.push_back(entry);
173 std::string GetCookiesWithOptions(const GURL& url,
174 const CookieOptions& options) {
176 for (size_t i = 0; i < entries_.size(); i++) {
177 Entry& entry = entries_[i];
178 if (url == entry.url) {
179 if (!result.empty()) {
182 result += entry.cookie_line;
189 virtual void SetCookieWithOptionsAsync(
191 const std::string& cookie_line,
192 const CookieOptions& options,
193 const SetCookiesCallback& callback) OVERRIDE {
194 bool result = SetCookieWithOptions(url, cookie_line, options);
195 if (!callback.is_null())
196 callback.Run(result);
199 virtual void GetCookiesWithOptionsAsync(
201 const CookieOptions& options,
202 const GetCookiesCallback& callback) OVERRIDE {
203 if (!callback.is_null())
204 callback.Run(GetCookiesWithOptions(url, options));
207 virtual void GetAllCookiesForURLAsync(
209 const GetCookieListCallback& callback) OVERRIDE {
213 virtual void DeleteCookieAsync(const GURL& url,
214 const std::string& cookie_name,
215 const base::Closure& callback) OVERRIDE {
219 virtual void DeleteAllCreatedBetweenAsync(
220 const base::Time& delete_begin,
221 const base::Time& delete_end,
222 const DeleteCallback& callback) OVERRIDE {
226 virtual void DeleteAllCreatedBetweenForHostAsync(
227 const base::Time delete_begin,
228 const base::Time delete_end,
230 const DeleteCallback& callback) OVERRIDE {
234 virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE {
238 virtual CookieMonster* GetCookieMonster() OVERRIDE { return NULL; }
240 const std::vector<Entry>& entries() const { return entries_; }
243 friend class base::RefCountedThreadSafe<MockCookieStore>;
244 virtual ~MockCookieStore() {}
246 std::vector<Entry> entries_;
249 class MockSSLConfigService : public SSLConfigService {
251 virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {}
254 virtual ~MockSSLConfigService() {}
257 class MockURLRequestContext : public URLRequestContext {
259 explicit MockURLRequestContext(CookieStore* cookie_store)
260 : transport_security_state_() {
261 set_cookie_store(cookie_store);
262 set_transport_security_state(&transport_security_state_);
263 base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
264 bool include_subdomains = false;
265 transport_security_state_.AddHSTS("upgrademe.com", expiry,
269 virtual ~MockURLRequestContext() {}
272 TransportSecurityState transport_security_state_;
275 class MockHttpTransactionFactory : public HttpTransactionFactory {
277 MockHttpTransactionFactory(NextProto next_proto, OrderedSocketData* data) {
279 MockConnect connect_data(SYNCHRONOUS, OK);
280 data_->set_connect_data(connect_data);
281 session_deps_.reset(new SpdySessionDependencies(next_proto));
282 session_deps_->socket_factory->AddSocketDataProvider(data_);
284 SpdySessionDependencies::SpdyCreateSession(session_deps_.get());
285 host_port_pair_.set_host("example.com");
286 host_port_pair_.set_port(80);
287 spdy_session_key_ = SpdySessionKey(host_port_pair_,
288 ProxyServer::Direct(),
289 kPrivacyModeDisabled);
290 session_ = CreateInsecureSpdySession(
291 http_session_, spdy_session_key_, BoundNetLog());
294 virtual int CreateTransaction(
295 RequestPriority priority,
296 scoped_ptr<HttpTransaction>* trans) OVERRIDE {
298 return ERR_UNEXPECTED;
301 virtual HttpCache* GetCache() OVERRIDE {
306 virtual HttpNetworkSession* GetSession() OVERRIDE {
307 return http_session_.get();
311 OrderedSocketData* data_;
312 scoped_ptr<SpdySessionDependencies> session_deps_;
313 scoped_refptr<HttpNetworkSession> http_session_;
314 base::WeakPtr<SpdySession> session_;
315 HostPortPair host_port_pair_;
316 SpdySessionKey spdy_session_key_;
321 class WebSocketJobTest : public PlatformTest,
322 public ::testing::WithParamInterface<NextProto> {
324 WebSocketJobTest() : spdy_util_(GetParam()) {}
326 virtual void SetUp() OVERRIDE {
327 stream_type_ = STREAM_INVALID;
328 cookie_store_ = new MockCookieStore;
329 context_.reset(new MockURLRequestContext(cookie_store_.get()));
331 virtual void TearDown() OVERRIDE {
332 cookie_store_ = NULL;
337 void DoSendRequest() {
338 EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie,
339 kHandshakeRequestWithoutCookieLength));
342 if (received_data().size() == kHandshakeResponseWithoutCookieLength)
343 websocket_->SendData(kDataHello, kDataHelloLength);
346 sync_test_callback_.callback().Run(OK);
348 int WaitForResult() {
349 return sync_test_callback_.WaitForResult();
356 STREAM_SPDY_WEBSOCKET,
358 enum ThrottlingOption {
366 void InitWebSocketJob(const GURL& url,
367 MockSocketStreamDelegate* delegate,
368 StreamType stream_type) {
369 DCHECK_NE(STREAM_INVALID, stream_type);
370 stream_type_ = stream_type;
371 websocket_ = new WebSocketJob(delegate);
373 if (stream_type == STREAM_MOCK_SOCKET)
374 socket_ = new MockSocketStream(url, websocket_.get(), context_.get(),
377 if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) {
378 if (stream_type == STREAM_SPDY_WEBSOCKET) {
380 new MockHttpTransactionFactory(GetParam(), data_.get()));
381 context_->set_http_transaction_factory(http_factory_.get());
384 ssl_config_service_ = new MockSSLConfigService();
385 context_->set_ssl_config_service(ssl_config_service_.get());
386 proxy_service_.reset(ProxyService::CreateDirect());
387 context_->set_proxy_service(proxy_service_.get());
388 host_resolver_.reset(new MockHostResolver);
389 context_->set_host_resolver(host_resolver_.get());
391 socket_ = new SocketStream(url, websocket_.get(), context_.get(), NULL);
392 socket_factory_.reset(new MockClientSocketFactory);
394 socket_factory_->AddSocketDataProvider(data_.get());
395 socket_->SetClientSocketFactory(socket_factory_.get());
398 websocket_->InitSocketStream(socket_.get());
399 // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create
400 // a WebSocketJob purely to block another one in a throttling test, we don't
401 // perform a real connect. In that case, the following address is used
404 ParseIPLiteralToNumber("127.0.0.1", &ip);
405 websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80);
407 void SkipToConnecting() {
408 websocket_->state_ = WebSocketJob::CONNECTING;
409 ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get()));
411 WebSocketJob::State GetWebSocketJobState() {
412 return websocket_->state_;
414 void CloseWebSocketJob() {
415 if (websocket_->socket_.get()) {
416 websocket_->socket_->DetachDelegate();
417 WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get());
419 websocket_->state_ = WebSocketJob::CLOSED;
420 websocket_->delegate_ = NULL;
421 websocket_->socket_ = NULL;
423 SocketStream* GetSocket(SocketStreamJob* job) {
424 return job->socket_.get();
426 const std::string& sent_data() const {
427 DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_);
428 MockSocketStream* socket =
429 static_cast<MockSocketStream*>(socket_.get());
431 return socket->sent_data();
433 const std::string& received_data() const {
434 DCHECK_NE(STREAM_INVALID, stream_type_);
435 MockSocketStreamDelegate* delegate =
436 static_cast<MockSocketStreamDelegate*>(websocket_->delegate_);
438 return delegate->received_data();
441 void TestSimpleHandshake();
442 void TestSlowHandshake();
443 void TestHandshakeWithCookie();
444 void TestHandshakeWithCookieButNotAllowed();
445 void TestHSTSUpgrade();
446 void TestInvalidSendData();
447 void TestConnectByWebSocket(ThrottlingOption throttling);
448 void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling);
449 void TestThrottlingLimit();
451 SpdyWebSocketTestUtil spdy_util_;
452 StreamType stream_type_;
453 scoped_refptr<MockCookieStore> cookie_store_;
454 scoped_ptr<MockURLRequestContext> context_;
455 scoped_refptr<WebSocketJob> websocket_;
456 scoped_refptr<SocketStream> socket_;
457 scoped_ptr<MockClientSocketFactory> socket_factory_;
458 scoped_ptr<OrderedSocketData> data_;
459 TestCompletionCallback sync_test_callback_;
460 scoped_refptr<MockSSLConfigService> ssl_config_service_;
461 scoped_ptr<ProxyService> proxy_service_;
462 scoped_ptr<MockHostResolver> host_resolver_;
463 scoped_ptr<MockHttpTransactionFactory> http_factory_;
465 static const char kHandshakeRequestWithoutCookie[];
466 static const char kHandshakeRequestWithCookie[];
467 static const char kHandshakeRequestWithFilteredCookie[];
468 static const char kHandshakeResponseWithoutCookie[];
469 static const char kHandshakeResponseWithCookie[];
470 static const char kDataHello[];
471 static const char kDataWorld[];
472 static const char* const kHandshakeRequestForSpdy[];
473 static const char* const kHandshakeResponseForSpdy[];
474 static const size_t kHandshakeRequestWithoutCookieLength;
475 static const size_t kHandshakeRequestWithCookieLength;
476 static const size_t kHandshakeRequestWithFilteredCookieLength;
477 static const size_t kHandshakeResponseWithoutCookieLength;
478 static const size_t kHandshakeResponseWithCookieLength;
479 static const size_t kDataHelloLength;
480 static const size_t kDataWorldLength;
483 const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] =
484 "GET /demo HTTP/1.1\r\n"
485 "Host: example.com\r\n"
486 "Upgrade: WebSocket\r\n"
487 "Connection: Upgrade\r\n"
488 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
489 "Origin: http://example.com\r\n"
490 "Sec-WebSocket-Protocol: sample\r\n"
491 "Sec-WebSocket-Version: 13\r\n"
494 const char WebSocketJobTest::kHandshakeRequestWithCookie[] =
495 "GET /demo HTTP/1.1\r\n"
496 "Host: example.com\r\n"
497 "Upgrade: WebSocket\r\n"
498 "Connection: Upgrade\r\n"
499 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
500 "Origin: http://example.com\r\n"
501 "Sec-WebSocket-Protocol: sample\r\n"
502 "Sec-WebSocket-Version: 13\r\n"
503 "Cookie: WK-test=1\r\n"
506 const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] =
507 "GET /demo HTTP/1.1\r\n"
508 "Host: example.com\r\n"
509 "Upgrade: WebSocket\r\n"
510 "Connection: Upgrade\r\n"
511 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
512 "Origin: http://example.com\r\n"
513 "Sec-WebSocket-Protocol: sample\r\n"
514 "Sec-WebSocket-Version: 13\r\n"
515 "Cookie: CR-test=1; CR-test-httponly=1\r\n"
518 const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] =
519 "HTTP/1.1 101 Switching Protocols\r\n"
520 "Upgrade: websocket\r\n"
521 "Connection: Upgrade\r\n"
522 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
523 "Sec-WebSocket-Protocol: sample\r\n"
526 const char WebSocketJobTest::kHandshakeResponseWithCookie[] =
527 "HTTP/1.1 101 Switching Protocols\r\n"
528 "Upgrade: websocket\r\n"
529 "Connection: Upgrade\r\n"
530 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
531 "Sec-WebSocket-Protocol: sample\r\n"
532 "Set-Cookie: CR-set-test=1\r\n"
535 const char WebSocketJobTest::kDataHello[] = "Hello, ";
537 const char WebSocketJobTest::kDataWorld[] = "World!\n";
539 const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength =
540 arraysize(kHandshakeRequestWithoutCookie) - 1;
541 const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength =
542 arraysize(kHandshakeRequestWithCookie) - 1;
543 const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength =
544 arraysize(kHandshakeRequestWithFilteredCookie) - 1;
545 const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength =
546 arraysize(kHandshakeResponseWithoutCookie) - 1;
547 const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength =
548 arraysize(kHandshakeResponseWithCookie) - 1;
549 const size_t WebSocketJobTest::kDataHelloLength =
550 arraysize(kDataHello) - 1;
551 const size_t WebSocketJobTest::kDataWorldLength =
552 arraysize(kDataWorld) - 1;
554 void WebSocketJobTest::TestSimpleHandshake() {
555 GURL url("ws://example.com/demo");
556 MockSocketStreamDelegate delegate;
557 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
561 base::MessageLoop::current()->RunUntilIdle();
562 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
563 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
564 websocket_->OnSentData(socket_.get(),
565 kHandshakeRequestWithoutCookieLength);
566 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
568 websocket_->OnReceivedData(socket_.get(),
569 kHandshakeResponseWithoutCookie,
570 kHandshakeResponseWithoutCookieLength);
571 base::MessageLoop::current()->RunUntilIdle();
572 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
573 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
577 void WebSocketJobTest::TestSlowHandshake() {
578 GURL url("ws://example.com/demo");
579 MockSocketStreamDelegate delegate;
580 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
584 // We assume request is sent in one data chunk (from WebKit)
585 // We don't support streaming request.
586 base::MessageLoop::current()->RunUntilIdle();
587 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
588 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
589 websocket_->OnSentData(socket_.get(),
590 kHandshakeRequestWithoutCookieLength);
591 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
593 std::vector<std::string> lines;
594 base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines);
595 for (size_t i = 0; i < lines.size() - 2; i++) {
596 std::string line = lines[i] + "\r\n";
597 SCOPED_TRACE("Line: " + line);
598 websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size());
599 base::MessageLoop::current()->RunUntilIdle();
600 EXPECT_TRUE(delegate.received_data().empty());
601 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
603 websocket_->OnReceivedData(socket_.get(), "\r\n", 2);
604 base::MessageLoop::current()->RunUntilIdle();
605 EXPECT_FALSE(delegate.received_data().empty());
606 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
607 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
611 INSTANTIATE_TEST_CASE_P(
614 testing::Values(kProtoDeprecatedSPDY2,
615 kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
616 kProtoHTTP2Draft04));
618 TEST_P(WebSocketJobTest, DelayedCookies) {
619 WebSocketJob::set_websocket_over_spdy_enabled(true);
620 GURL url("ws://example.com/demo");
621 GURL cookieUrl("http://example.com/demo");
622 CookieOptions cookie_options;
623 scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster();
624 context_->set_cookie_store(cookie_store.get());
625 cookie_store->SetCookieWithOptionsAsync(cookieUrl,
628 CookieMonster::SetCookiesCallback());
629 cookie_options.set_include_httponly();
630 cookie_store->SetCookieWithOptionsAsync(
631 cookieUrl, "CR-test-httponly=1", cookie_options,
632 CookieMonster::SetCookiesCallback());
634 MockSocketStreamDelegate delegate;
635 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
638 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
639 kHandshakeRequestWithCookieLength);
641 base::MessageLoop::current()->RunUntilIdle();
642 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
643 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
644 websocket_->OnSentData(socket_.get(),
645 kHandshakeRequestWithFilteredCookieLength);
646 EXPECT_EQ(kHandshakeRequestWithCookieLength,
647 delegate.amount_sent());
649 websocket_->OnReceivedData(socket_.get(),
650 kHandshakeResponseWithCookie,
651 kHandshakeResponseWithCookieLength);
652 base::MessageLoop::current()->RunUntilIdle();
653 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
654 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
659 void WebSocketJobTest::TestHandshakeWithCookie() {
660 GURL url("ws://example.com/demo");
661 GURL cookieUrl("http://example.com/demo");
662 CookieOptions cookie_options;
663 cookie_store_->SetCookieWithOptions(
664 cookieUrl, "CR-test=1", cookie_options);
665 cookie_options.set_include_httponly();
666 cookie_store_->SetCookieWithOptions(
667 cookieUrl, "CR-test-httponly=1", cookie_options);
669 MockSocketStreamDelegate delegate;
670 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
673 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
674 kHandshakeRequestWithCookieLength);
676 base::MessageLoop::current()->RunUntilIdle();
677 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
678 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
679 websocket_->OnSentData(socket_.get(),
680 kHandshakeRequestWithFilteredCookieLength);
681 EXPECT_EQ(kHandshakeRequestWithCookieLength,
682 delegate.amount_sent());
684 websocket_->OnReceivedData(socket_.get(),
685 kHandshakeResponseWithCookie,
686 kHandshakeResponseWithCookieLength);
687 base::MessageLoop::current()->RunUntilIdle();
688 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
689 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
691 EXPECT_EQ(3U, cookie_store_->entries().size());
692 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
693 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
694 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
695 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
696 EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url);
697 EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line);
702 void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() {
703 GURL url("ws://example.com/demo");
704 GURL cookieUrl("http://example.com/demo");
705 CookieOptions cookie_options;
706 cookie_store_->SetCookieWithOptions(
707 cookieUrl, "CR-test=1", cookie_options);
708 cookie_options.set_include_httponly();
709 cookie_store_->SetCookieWithOptions(
710 cookieUrl, "CR-test-httponly=1", cookie_options);
712 MockSocketStreamDelegate delegate;
713 delegate.set_allow_all_cookies(false);
714 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
717 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
718 kHandshakeRequestWithCookieLength);
720 base::MessageLoop::current()->RunUntilIdle();
721 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
722 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
723 websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength);
724 EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent());
726 websocket_->OnReceivedData(socket_.get(),
727 kHandshakeResponseWithCookie,
728 kHandshakeResponseWithCookieLength);
729 base::MessageLoop::current()->RunUntilIdle();
730 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
731 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
733 EXPECT_EQ(2U, cookie_store_->entries().size());
734 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
735 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
736 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
737 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
742 void WebSocketJobTest::TestHSTSUpgrade() {
743 GURL url("ws://upgrademe.com/");
744 MockSocketStreamDelegate delegate;
745 scoped_refptr<SocketStreamJob> job =
746 SocketStreamJob::CreateSocketStreamJob(
747 url, &delegate, context_->transport_security_state(),
748 context_->ssl_config_service(), NULL, NULL);
749 EXPECT_TRUE(GetSocket(job.get())->is_secure());
750 job->DetachDelegate();
752 url = GURL("ws://donotupgrademe.com/");
753 job = SocketStreamJob::CreateSocketStreamJob(
754 url, &delegate, context_->transport_security_state(),
755 context_->ssl_config_service(), NULL, NULL);
756 EXPECT_FALSE(GetSocket(job.get())->is_secure());
757 job->DetachDelegate();
760 void WebSocketJobTest::TestInvalidSendData() {
761 GURL url("ws://example.com/demo");
762 MockSocketStreamDelegate delegate;
763 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
767 // We assume request is sent in one data chunk (from WebKit)
768 // We don't support streaming request.
769 base::MessageLoop::current()->RunUntilIdle();
770 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
771 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
772 websocket_->OnSentData(socket_.get(),
773 kHandshakeRequestWithoutCookieLength);
774 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
776 // We could not send any data until connection is established.
777 bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie,
778 kHandshakeRequestWithoutCookieLength);
780 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
784 // Following tests verify cooperation between WebSocketJob and SocketStream.
785 // Other former tests use MockSocketStream as SocketStream, so we could not
786 // check SocketStream behavior.
787 // OrderedSocketData provide socket level verifiation by checking out-going
788 // packets in comparison with the MockWrite array and emulating in-coming
789 // packets with MockRead array.
791 void WebSocketJobTest::TestConnectByWebSocket(
792 ThrottlingOption throttling) {
793 // This is a test for verifying cooperation between WebSocketJob and
794 // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic
795 // situation. If |throttling| was |THROTTLING_ON|, throttling limits the
796 // latter connection.
797 MockWrite writes[] = {
799 kHandshakeRequestWithoutCookie,
800 kHandshakeRequestWithoutCookieLength,
809 kHandshakeResponseWithoutCookie,
810 kHandshakeResponseWithoutCookieLength,
816 MockRead(SYNCHRONOUS, 0, 5) // EOF
818 data_.reset(new OrderedSocketData(
819 reads, arraysize(reads), writes, arraysize(writes)));
821 GURL url("ws://example.com/demo");
822 MockSocketStreamDelegate delegate;
823 WebSocketJobTest* test = this;
824 if (throttling == THROTTLING_ON)
825 delegate.SetOnStartOpenConnection(
826 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
827 delegate.SetOnConnected(
828 base::Bind(&WebSocketJobTest::DoSendRequest,
829 base::Unretained(test)));
830 delegate.SetOnReceivedData(
831 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
833 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
834 InitWebSocketJob(url, &delegate, STREAM_SOCKET);
836 scoped_refptr<WebSocketJob> block_websocket;
837 if (throttling == THROTTLING_ON) {
838 // Create former WebSocket object which obstructs the latter one.
839 block_websocket = new WebSocketJob(NULL);
840 block_websocket->addresses_ = AddressList(websocket_->address_list());
842 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
845 websocket_->Connect();
847 if (throttling == THROTTLING_ON) {
848 EXPECT_EQ(OK, WaitForResult());
849 EXPECT_TRUE(websocket_->IsWaiting());
851 // Remove the former WebSocket object from throttling queue to unblock the
853 block_websocket->state_ = WebSocketJob::CLOSED;
854 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
855 block_websocket = NULL;
858 EXPECT_EQ(OK, WaitForResult());
859 EXPECT_TRUE(data_->at_read_eof());
860 EXPECT_TRUE(data_->at_write_eof());
861 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
864 void WebSocketJobTest::TestConnectBySpdy(
865 SpdyOption spdy, ThrottlingOption throttling) {
866 // This is a test for verifying cooperation between WebSocketJob and
867 // SocketStream in the situation we have SPDY session to the server. If
868 // |throttling| was |THROTTLING_ON|, throttling limits the latter connection.
869 // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected
870 // results depend on its configuration.
871 MockWrite writes_websocket[] = {
873 kHandshakeRequestWithoutCookie,
874 kHandshakeRequestWithoutCookieLength,
881 MockRead reads_websocket[] = {
883 kHandshakeResponseWithoutCookie,
884 kHandshakeResponseWithoutCookieLength,
890 MockRead(SYNCHRONOUS, 0, 5) // EOF
893 scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock());
894 spdy_util_.SetHeader("path", "/demo", request_headers.get());
895 spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get());
896 spdy_util_.SetHeader("scheme", "ws", request_headers.get());
897 spdy_util_.SetHeader("host", "example.com", request_headers.get());
898 spdy_util_.SetHeader("origin", "http://example.com", request_headers.get());
899 spdy_util_.SetHeader("sec-websocket-protocol", "sample",
900 request_headers.get());
902 scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock());
903 spdy_util_.SetHeader("status", "101 Switching Protocols",
904 response_headers.get());
905 spdy_util_.SetHeader("sec-websocket-protocol", "sample",
906 response_headers.get());
908 const SpdyStreamId kStreamId = 1;
909 scoped_ptr<SpdyFrame> request_frame(
910 spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame(
911 request_headers.Pass(),
914 scoped_ptr<SpdyFrame> response_frame(
915 spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame(
916 response_headers.Pass(),
919 scoped_ptr<SpdyFrame> data_hello_frame(
920 spdy_util_.ConstructSpdyWebSocketDataFrame(
925 scoped_ptr<SpdyFrame> data_world_frame(
926 spdy_util_.ConstructSpdyWebSocketDataFrame(
931 MockWrite writes_spdy[] = {
932 CreateMockWrite(*request_frame.get(), 1),
933 CreateMockWrite(*data_hello_frame.get(), 3),
935 MockRead reads_spdy[] = {
936 CreateMockRead(*response_frame.get(), 2),
937 CreateMockRead(*data_world_frame.get(), 4),
938 MockRead(SYNCHRONOUS, 0, 5) // EOF
942 data_.reset(new OrderedSocketData(
943 reads_spdy, arraysize(reads_spdy),
944 writes_spdy, arraysize(writes_spdy)));
946 data_.reset(new OrderedSocketData(
947 reads_websocket, arraysize(reads_websocket),
948 writes_websocket, arraysize(writes_websocket)));
950 GURL url("ws://example.com/demo");
951 MockSocketStreamDelegate delegate;
952 WebSocketJobTest* test = this;
953 if (throttling == THROTTLING_ON)
954 delegate.SetOnStartOpenConnection(
955 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
956 delegate.SetOnConnected(
957 base::Bind(&WebSocketJobTest::DoSendRequest,
958 base::Unretained(test)));
959 delegate.SetOnReceivedData(
960 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
962 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
963 InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET);
965 scoped_refptr<WebSocketJob> block_websocket;
966 if (throttling == THROTTLING_ON) {
967 // Create former WebSocket object which obstructs the latter one.
968 block_websocket = new WebSocketJob(NULL);
969 block_websocket->addresses_ = AddressList(websocket_->address_list());
971 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
974 websocket_->Connect();
976 if (throttling == THROTTLING_ON) {
977 EXPECT_EQ(OK, WaitForResult());
978 EXPECT_TRUE(websocket_->IsWaiting());
980 // Remove the former WebSocket object from throttling queue to unblock the
982 block_websocket->state_ = WebSocketJob::CLOSED;
983 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
984 block_websocket = NULL;
987 EXPECT_EQ(OK, WaitForResult());
988 EXPECT_TRUE(data_->at_read_eof());
989 EXPECT_TRUE(data_->at_write_eof());
990 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
993 void WebSocketJobTest::TestThrottlingLimit() {
994 std::vector<scoped_refptr<WebSocketJob> > jobs;
995 const int kMaxWebSocketJobsThrottled = 1024;
997 ParseIPLiteralToNumber("127.0.0.1", &ip);
998 for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) {
999 scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL);
1000 job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80));
1001 if (i >= kMaxWebSocketJobsThrottled)
1002 EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job));
1004 EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job));
1005 jobs.push_back(job);
1008 // Close the jobs in reverse order. Otherwise, We need to make them prepared
1010 for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter =
1012 iter != jobs.rend();
1014 WebSocketJob* job = (*iter).get();
1015 job->state_ = WebSocketJob::CLOSED;
1016 WebSocketThrottle::GetInstance()->RemoveFromQueue(job);
1020 // Execute tests in both spdy-disabled mode and spdy-enabled mode.
1021 TEST_P(WebSocketJobTest, SimpleHandshake) {
1022 WebSocketJob::set_websocket_over_spdy_enabled(false);
1023 TestSimpleHandshake();
1026 TEST_P(WebSocketJobTest, SlowHandshake) {
1027 WebSocketJob::set_websocket_over_spdy_enabled(false);
1028 TestSlowHandshake();
1031 TEST_P(WebSocketJobTest, HandshakeWithCookie) {
1032 WebSocketJob::set_websocket_over_spdy_enabled(false);
1033 TestHandshakeWithCookie();
1036 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
1037 WebSocketJob::set_websocket_over_spdy_enabled(false);
1038 TestHandshakeWithCookieButNotAllowed();
1041 TEST_P(WebSocketJobTest, HSTSUpgrade) {
1042 WebSocketJob::set_websocket_over_spdy_enabled(false);
1046 TEST_P(WebSocketJobTest, InvalidSendData) {
1047 WebSocketJob::set_websocket_over_spdy_enabled(false);
1048 TestInvalidSendData();
1051 TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) {
1052 WebSocketJob::set_websocket_over_spdy_enabled(true);
1053 TestSimpleHandshake();
1056 TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) {
1057 WebSocketJob::set_websocket_over_spdy_enabled(true);
1058 TestSlowHandshake();
1061 TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) {
1062 WebSocketJob::set_websocket_over_spdy_enabled(true);
1063 TestHandshakeWithCookie();
1066 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) {
1067 WebSocketJob::set_websocket_over_spdy_enabled(true);
1068 TestHandshakeWithCookieButNotAllowed();
1071 TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) {
1072 WebSocketJob::set_websocket_over_spdy_enabled(true);
1076 TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) {
1077 WebSocketJob::set_websocket_over_spdy_enabled(true);
1078 TestInvalidSendData();
1081 TEST_P(WebSocketJobTest, ConnectByWebSocket) {
1082 WebSocketJob::set_websocket_over_spdy_enabled(false);
1083 TestConnectByWebSocket(THROTTLING_OFF);
1086 TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) {
1087 WebSocketJob::set_websocket_over_spdy_enabled(true);
1088 TestConnectByWebSocket(THROTTLING_OFF);
1091 TEST_P(WebSocketJobTest, ConnectBySpdy) {
1092 WebSocketJob::set_websocket_over_spdy_enabled(false);
1093 TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF);
1096 TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) {
1097 WebSocketJob::set_websocket_over_spdy_enabled(true);
1098 TestConnectBySpdy(SPDY_ON, THROTTLING_OFF);
1101 TEST_P(WebSocketJobTest, ThrottlingWebSocket) {
1102 WebSocketJob::set_websocket_over_spdy_enabled(false);
1103 TestConnectByWebSocket(THROTTLING_ON);
1106 TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) {
1107 TestThrottlingLimit();
1110 TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) {
1111 WebSocketJob::set_websocket_over_spdy_enabled(true);
1112 TestConnectByWebSocket(THROTTLING_ON);
1115 TEST_P(WebSocketJobTest, ThrottlingSpdy) {
1116 WebSocketJob::set_websocket_over_spdy_enabled(false);
1117 TestConnectBySpdy(SPDY_OFF, THROTTLING_ON);
1120 TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) {
1121 WebSocketJob::set_websocket_over_spdy_enabled(true);
1122 TestConnectBySpdy(SPDY_ON, THROTTLING_ON);
1125 // TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation.
1126 // TODO(toyoshim,yutak): Add tests to verify closing handshake.