OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "net/websockets/websocket_job.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
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" | |
36 #include "url/gurl.h" | |
37 | |
38 namespace net { | |
39 | |
40 namespace { | |
41 | |
42 class MockSocketStream : public SocketStream { | |
43 public: | |
44 MockSocketStream(const GURL& url, SocketStream::Delegate* delegate, | |
45 URLRequestContext* context, CookieStore* cookie_store) | |
46 : SocketStream(url, delegate, context, cookie_store) {} | |
47 | |
48 void Connect() override {} | |
49 bool SendData(const char* data, int len) override { | |
50 sent_data_ += std::string(data, len); | |
51 return true; | |
52 } | |
53 | |
54 void Close() override {} | |
55 void RestartWithAuth(const AuthCredentials& credentials) override {} | |
56 | |
57 void DetachDelegate() override { delegate_ = NULL; } | |
58 | |
59 const std::string& sent_data() const { | |
60 return sent_data_; | |
61 } | |
62 | |
63 protected: | |
64 ~MockSocketStream() override {} | |
65 | |
66 private: | |
67 std::string sent_data_; | |
68 }; | |
69 | |
70 class MockSocketStreamDelegate : public SocketStream::Delegate { | |
71 public: | |
72 MockSocketStreamDelegate() | |
73 : amount_sent_(0), allow_all_cookies_(true) {} | |
74 void set_allow_all_cookies(bool allow_all_cookies) { | |
75 allow_all_cookies_ = allow_all_cookies; | |
76 } | |
77 ~MockSocketStreamDelegate() override {} | |
78 | |
79 void SetOnStartOpenConnection(const base::Closure& callback) { | |
80 on_start_open_connection_ = callback; | |
81 } | |
82 void SetOnConnected(const base::Closure& callback) { | |
83 on_connected_ = callback; | |
84 } | |
85 void SetOnSentData(const base::Closure& callback) { | |
86 on_sent_data_ = callback; | |
87 } | |
88 void SetOnReceivedData(const base::Closure& callback) { | |
89 on_received_data_ = callback; | |
90 } | |
91 void SetOnClose(const base::Closure& callback) { | |
92 on_close_ = callback; | |
93 } | |
94 | |
95 int OnStartOpenConnection(SocketStream* socket, | |
96 const CompletionCallback& callback) override { | |
97 if (!on_start_open_connection_.is_null()) | |
98 on_start_open_connection_.Run(); | |
99 return OK; | |
100 } | |
101 void OnConnected(SocketStream* socket, | |
102 int max_pending_send_allowed) override { | |
103 if (!on_connected_.is_null()) | |
104 on_connected_.Run(); | |
105 } | |
106 void OnSentData(SocketStream* socket, int amount_sent) override { | |
107 amount_sent_ += amount_sent; | |
108 if (!on_sent_data_.is_null()) | |
109 on_sent_data_.Run(); | |
110 } | |
111 void OnReceivedData(SocketStream* socket, | |
112 const char* data, | |
113 int len) override { | |
114 received_data_ += std::string(data, len); | |
115 if (!on_received_data_.is_null()) | |
116 on_received_data_.Run(); | |
117 } | |
118 void OnClose(SocketStream* socket) override { | |
119 if (!on_close_.is_null()) | |
120 on_close_.Run(); | |
121 } | |
122 bool CanGetCookies(SocketStream* socket, const GURL& url) override { | |
123 return allow_all_cookies_; | |
124 } | |
125 bool CanSetCookie(SocketStream* request, | |
126 const GURL& url, | |
127 const std::string& cookie_line, | |
128 CookieOptions* options) override { | |
129 return allow_all_cookies_; | |
130 } | |
131 | |
132 size_t amount_sent() const { return amount_sent_; } | |
133 const std::string& received_data() const { return received_data_; } | |
134 | |
135 private: | |
136 int amount_sent_; | |
137 bool allow_all_cookies_; | |
138 std::string received_data_; | |
139 base::Closure on_start_open_connection_; | |
140 base::Closure on_connected_; | |
141 base::Closure on_sent_data_; | |
142 base::Closure on_received_data_; | |
143 base::Closure on_close_; | |
144 }; | |
145 | |
146 class MockCookieStore : public CookieStore { | |
147 public: | |
148 struct Entry { | |
149 GURL url; | |
150 std::string cookie_line; | |
151 CookieOptions options; | |
152 }; | |
153 | |
154 MockCookieStore() {} | |
155 | |
156 bool SetCookieWithOptions(const GURL& url, | |
157 const std::string& cookie_line, | |
158 const CookieOptions& options) { | |
159 Entry entry; | |
160 entry.url = url; | |
161 entry.cookie_line = cookie_line; | |
162 entry.options = options; | |
163 entries_.push_back(entry); | |
164 return true; | |
165 } | |
166 | |
167 std::string GetCookiesWithOptions(const GURL& url, | |
168 const CookieOptions& options) { | |
169 std::string result; | |
170 for (size_t i = 0; i < entries_.size(); i++) { | |
171 Entry& entry = entries_[i]; | |
172 if (url == entry.url) { | |
173 if (!result.empty()) { | |
174 result += "; "; | |
175 } | |
176 result += entry.cookie_line; | |
177 } | |
178 } | |
179 return result; | |
180 } | |
181 | |
182 // CookieStore: | |
183 void SetCookieWithOptionsAsync(const GURL& url, | |
184 const std::string& cookie_line, | |
185 const CookieOptions& options, | |
186 const SetCookiesCallback& callback) override { | |
187 bool result = SetCookieWithOptions(url, cookie_line, options); | |
188 if (!callback.is_null()) | |
189 callback.Run(result); | |
190 } | |
191 | |
192 void GetCookiesWithOptionsAsync(const GURL& url, | |
193 const CookieOptions& options, | |
194 const GetCookiesCallback& callback) override { | |
195 if (!callback.is_null()) | |
196 callback.Run(GetCookiesWithOptions(url, options)); | |
197 } | |
198 | |
199 void GetAllCookiesForURLAsync( | |
200 const GURL& url, | |
201 const GetCookieListCallback& callback) override { | |
202 ADD_FAILURE(); | |
203 } | |
204 | |
205 void DeleteCookieAsync(const GURL& url, | |
206 const std::string& cookie_name, | |
207 const base::Closure& callback) override { | |
208 ADD_FAILURE(); | |
209 } | |
210 | |
211 void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin, | |
212 const base::Time& delete_end, | |
213 const DeleteCallback& callback) override { | |
214 ADD_FAILURE(); | |
215 } | |
216 | |
217 void DeleteAllCreatedBetweenForHostAsync( | |
218 const base::Time delete_begin, | |
219 const base::Time delete_end, | |
220 const GURL& url, | |
221 const DeleteCallback& callback) override { | |
222 ADD_FAILURE(); | |
223 } | |
224 | |
225 void DeleteSessionCookiesAsync(const DeleteCallback&) override { | |
226 ADD_FAILURE(); | |
227 } | |
228 | |
229 CookieMonster* GetCookieMonster() override { return NULL; } | |
230 | |
231 scoped_ptr<CookieStore::CookieChangedSubscription> | |
232 AddCallbackForCookie(const GURL& url, const std::string& name, | |
233 const CookieChangedCallback& callback) override { | |
234 ADD_FAILURE(); | |
235 return scoped_ptr<CookieChangedSubscription>(); | |
236 } | |
237 | |
238 const std::vector<Entry>& entries() const { return entries_; } | |
239 | |
240 private: | |
241 friend class base::RefCountedThreadSafe<MockCookieStore>; | |
242 ~MockCookieStore() override {} | |
243 | |
244 std::vector<Entry> entries_; | |
245 }; | |
246 | |
247 class MockSSLConfigService : public SSLConfigService { | |
248 public: | |
249 void GetSSLConfig(SSLConfig* config) override {} | |
250 | |
251 protected: | |
252 ~MockSSLConfigService() override {} | |
253 }; | |
254 | |
255 class MockURLRequestContext : public URLRequestContext { | |
256 public: | |
257 explicit MockURLRequestContext(CookieStore* cookie_store) | |
258 : transport_security_state_() { | |
259 set_cookie_store(cookie_store); | |
260 set_transport_security_state(&transport_security_state_); | |
261 base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000); | |
262 bool include_subdomains = false; | |
263 transport_security_state_.AddHSTS("upgrademe.com", expiry, | |
264 include_subdomains); | |
265 } | |
266 | |
267 ~MockURLRequestContext() override { AssertNoURLRequests(); } | |
268 | |
269 private: | |
270 TransportSecurityState transport_security_state_; | |
271 }; | |
272 | |
273 class MockHttpTransactionFactory : public HttpTransactionFactory { | |
274 public: | |
275 MockHttpTransactionFactory(NextProto next_proto, | |
276 OrderedSocketData* data, | |
277 bool enable_websocket_over_spdy) { | |
278 data_ = 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_->enable_websocket_over_spdy = enable_websocket_over_spdy; | |
283 session_deps_->socket_factory->AddSocketDataProvider(data_); | |
284 http_session_ = | |
285 SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); | |
286 host_port_pair_.set_host("example.com"); | |
287 host_port_pair_.set_port(80); | |
288 spdy_session_key_ = SpdySessionKey(host_port_pair_, | |
289 ProxyServer::Direct(), | |
290 PRIVACY_MODE_DISABLED); | |
291 session_ = CreateInsecureSpdySession( | |
292 http_session_, spdy_session_key_, BoundNetLog()); | |
293 } | |
294 | |
295 int CreateTransaction(RequestPriority priority, | |
296 scoped_ptr<HttpTransaction>* trans) override { | |
297 NOTREACHED(); | |
298 return ERR_UNEXPECTED; | |
299 } | |
300 | |
301 HttpCache* GetCache() override { | |
302 NOTREACHED(); | |
303 return NULL; | |
304 } | |
305 | |
306 HttpNetworkSession* GetSession() override { return http_session_.get(); } | |
307 | |
308 private: | |
309 OrderedSocketData* data_; | |
310 scoped_ptr<SpdySessionDependencies> session_deps_; | |
311 scoped_refptr<HttpNetworkSession> http_session_; | |
312 base::WeakPtr<SpdySession> session_; | |
313 HostPortPair host_port_pair_; | |
314 SpdySessionKey spdy_session_key_; | |
315 }; | |
316 | |
317 class DeletingSocketStreamDelegate : public SocketStream::Delegate { | |
318 public: | |
319 DeletingSocketStreamDelegate() | |
320 : delete_next_(false) {} | |
321 | |
322 // Since this class needs to be able to delete |job_|, it must be the only | |
323 // reference holder (except for temporary references). Provide access to the | |
324 // pointer for tests to use. | |
325 WebSocketJob* job() { return job_.get(); } | |
326 | |
327 void set_job(WebSocketJob* job) { job_ = job; } | |
328 | |
329 // After calling this, the next call to a method on this delegate will delete | |
330 // the WebSocketJob object. | |
331 void set_delete_next(bool delete_next) { delete_next_ = delete_next; } | |
332 | |
333 void DeleteJobMaybe() { | |
334 if (delete_next_) { | |
335 job_->DetachContext(); | |
336 job_->DetachDelegate(); | |
337 job_ = NULL; | |
338 } | |
339 } | |
340 | |
341 // SocketStream::Delegate implementation | |
342 | |
343 // OnStartOpenConnection() is not implemented by SocketStreamDispatcherHost | |
344 | |
345 void OnConnected(SocketStream* socket, | |
346 int max_pending_send_allowed) override { | |
347 DeleteJobMaybe(); | |
348 } | |
349 | |
350 void OnSentData(SocketStream* socket, int amount_sent) override { | |
351 DeleteJobMaybe(); | |
352 } | |
353 | |
354 void OnReceivedData(SocketStream* socket, | |
355 const char* data, | |
356 int len) override { | |
357 DeleteJobMaybe(); | |
358 } | |
359 | |
360 void OnClose(SocketStream* socket) override { DeleteJobMaybe(); } | |
361 | |
362 void OnAuthRequired(SocketStream* socket, | |
363 AuthChallengeInfo* auth_info) override { | |
364 DeleteJobMaybe(); | |
365 } | |
366 | |
367 void OnSSLCertificateError(SocketStream* socket, | |
368 const SSLInfo& ssl_info, | |
369 bool fatal) override { | |
370 DeleteJobMaybe(); | |
371 } | |
372 | |
373 void OnError(const SocketStream* socket, int error) override { | |
374 DeleteJobMaybe(); | |
375 } | |
376 | |
377 // CanGetCookies() and CanSetCookies() do not appear to be able to delete the | |
378 // WebSocketJob object. | |
379 | |
380 private: | |
381 scoped_refptr<WebSocketJob> job_; | |
382 bool delete_next_; | |
383 }; | |
384 | |
385 } // namespace | |
386 | |
387 class WebSocketJobTest : public PlatformTest, | |
388 public ::testing::WithParamInterface<NextProto> { | |
389 public: | |
390 WebSocketJobTest() | |
391 : spdy_util_(GetParam()), | |
392 enable_websocket_over_spdy_(false) {} | |
393 | |
394 void SetUp() override { | |
395 stream_type_ = STREAM_INVALID; | |
396 cookie_store_ = new MockCookieStore; | |
397 context_.reset(new MockURLRequestContext(cookie_store_.get())); | |
398 } | |
399 void TearDown() override { | |
400 cookie_store_ = NULL; | |
401 context_.reset(); | |
402 websocket_ = NULL; | |
403 socket_ = NULL; | |
404 } | |
405 void DoSendRequest() { | |
406 EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie, | |
407 kHandshakeRequestWithoutCookieLength)); | |
408 } | |
409 void DoSendData() { | |
410 if (received_data().size() == kHandshakeResponseWithoutCookieLength) | |
411 websocket_->SendData(kDataHello, kDataHelloLength); | |
412 } | |
413 void DoSync() { | |
414 sync_test_callback_.callback().Run(OK); | |
415 } | |
416 int WaitForResult() { | |
417 return sync_test_callback_.WaitForResult(); | |
418 } | |
419 | |
420 protected: | |
421 enum StreamType { | |
422 STREAM_INVALID, | |
423 STREAM_MOCK_SOCKET, | |
424 STREAM_SOCKET, | |
425 STREAM_SPDY_WEBSOCKET, | |
426 }; | |
427 enum ThrottlingOption { | |
428 THROTTLING_OFF, | |
429 THROTTLING_ON, | |
430 }; | |
431 enum SpdyOption { | |
432 SPDY_OFF, | |
433 SPDY_ON, | |
434 }; | |
435 void InitWebSocketJob(const GURL& url, | |
436 MockSocketStreamDelegate* delegate, | |
437 StreamType stream_type) { | |
438 DCHECK_NE(STREAM_INVALID, stream_type); | |
439 stream_type_ = stream_type; | |
440 websocket_ = new WebSocketJob(delegate); | |
441 | |
442 if (stream_type == STREAM_MOCK_SOCKET) | |
443 socket_ = new MockSocketStream(url, websocket_.get(), context_.get(), | |
444 NULL); | |
445 | |
446 if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) { | |
447 if (stream_type == STREAM_SPDY_WEBSOCKET) { | |
448 http_factory_.reset(new MockHttpTransactionFactory( | |
449 GetParam(), data_.get(), enable_websocket_over_spdy_)); | |
450 context_->set_http_transaction_factory(http_factory_.get()); | |
451 } | |
452 | |
453 ssl_config_service_ = new MockSSLConfigService(); | |
454 context_->set_ssl_config_service(ssl_config_service_.get()); | |
455 proxy_service_.reset(ProxyService::CreateDirect()); | |
456 context_->set_proxy_service(proxy_service_.get()); | |
457 host_resolver_.reset(new MockHostResolver); | |
458 context_->set_host_resolver(host_resolver_.get()); | |
459 | |
460 socket_ = new SocketStream(url, websocket_.get(), context_.get(), NULL); | |
461 socket_factory_.reset(new MockClientSocketFactory); | |
462 DCHECK(data_.get()); | |
463 socket_factory_->AddSocketDataProvider(data_.get()); | |
464 socket_->SetClientSocketFactory(socket_factory_.get()); | |
465 } | |
466 | |
467 websocket_->InitSocketStream(socket_.get()); | |
468 // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create | |
469 // a WebSocketJob purely to block another one in a throttling test, we don't | |
470 // perform a real connect. In that case, the following address is used | |
471 // instead. | |
472 IPAddressNumber ip; | |
473 ParseIPLiteralToNumber("127.0.0.1", &ip); | |
474 websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80); | |
475 } | |
476 void SkipToConnecting() { | |
477 websocket_->state_ = WebSocketJob::CONNECTING; | |
478 ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get())); | |
479 } | |
480 WebSocketJob::State GetWebSocketJobState() { | |
481 return websocket_->state_; | |
482 } | |
483 void CloseWebSocketJob() { | |
484 if (websocket_->socket_.get()) { | |
485 websocket_->socket_->DetachDelegate(); | |
486 WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get()); | |
487 } | |
488 websocket_->state_ = WebSocketJob::CLOSED; | |
489 websocket_->delegate_ = NULL; | |
490 websocket_->socket_ = NULL; | |
491 } | |
492 SocketStream* GetSocket(SocketStreamJob* job) { | |
493 return job->socket_.get(); | |
494 } | |
495 const std::string& sent_data() const { | |
496 DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_); | |
497 MockSocketStream* socket = | |
498 static_cast<MockSocketStream*>(socket_.get()); | |
499 DCHECK(socket); | |
500 return socket->sent_data(); | |
501 } | |
502 const std::string& received_data() const { | |
503 DCHECK_NE(STREAM_INVALID, stream_type_); | |
504 MockSocketStreamDelegate* delegate = | |
505 static_cast<MockSocketStreamDelegate*>(websocket_->delegate_); | |
506 DCHECK(delegate); | |
507 return delegate->received_data(); | |
508 } | |
509 | |
510 void TestSimpleHandshake(); | |
511 void TestSlowHandshake(); | |
512 void TestHandshakeWithCookie(); | |
513 void TestHandshakeWithCookieButNotAllowed(); | |
514 void TestHSTSUpgrade(); | |
515 void TestInvalidSendData(); | |
516 void TestConnectByWebSocket(ThrottlingOption throttling); | |
517 void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling); | |
518 void TestThrottlingLimit(); | |
519 | |
520 SpdyWebSocketTestUtil spdy_util_; | |
521 StreamType stream_type_; | |
522 scoped_refptr<MockCookieStore> cookie_store_; | |
523 scoped_ptr<MockURLRequestContext> context_; | |
524 scoped_refptr<WebSocketJob> websocket_; | |
525 scoped_refptr<SocketStream> socket_; | |
526 scoped_ptr<MockClientSocketFactory> socket_factory_; | |
527 scoped_ptr<OrderedSocketData> data_; | |
528 TestCompletionCallback sync_test_callback_; | |
529 scoped_refptr<MockSSLConfigService> ssl_config_service_; | |
530 scoped_ptr<ProxyService> proxy_service_; | |
531 scoped_ptr<MockHostResolver> host_resolver_; | |
532 scoped_ptr<MockHttpTransactionFactory> http_factory_; | |
533 | |
534 // Must be set before call to enable_websocket_over_spdy, defaults to false. | |
535 bool enable_websocket_over_spdy_; | |
536 | |
537 static const char kHandshakeRequestWithoutCookie[]; | |
538 static const char kHandshakeRequestWithCookie[]; | |
539 static const char kHandshakeRequestWithFilteredCookie[]; | |
540 static const char kHandshakeResponseWithoutCookie[]; | |
541 static const char kHandshakeResponseWithCookie[]; | |
542 static const char kDataHello[]; | |
543 static const char kDataWorld[]; | |
544 static const char* const kHandshakeRequestForSpdy[]; | |
545 static const char* const kHandshakeResponseForSpdy[]; | |
546 static const size_t kHandshakeRequestWithoutCookieLength; | |
547 static const size_t kHandshakeRequestWithCookieLength; | |
548 static const size_t kHandshakeRequestWithFilteredCookieLength; | |
549 static const size_t kHandshakeResponseWithoutCookieLength; | |
550 static const size_t kHandshakeResponseWithCookieLength; | |
551 static const size_t kDataHelloLength; | |
552 static const size_t kDataWorldLength; | |
553 }; | |
554 | |
555 // Tests using this fixture verify that the WebSocketJob can handle being | |
556 // deleted while calling back to the delegate correctly. These tests need to be | |
557 // run under AddressSanitizer or other systems for detecting use-after-free | |
558 // errors in order to find problems. | |
559 class WebSocketJobDeleteTest : public ::testing::Test { | |
560 protected: | |
561 WebSocketJobDeleteTest() | |
562 : delegate_(new DeletingSocketStreamDelegate), | |
563 cookie_store_(new MockCookieStore), | |
564 context_(new MockURLRequestContext(cookie_store_.get())) { | |
565 WebSocketJob* websocket = new WebSocketJob(delegate_.get()); | |
566 delegate_->set_job(websocket); | |
567 | |
568 socket_ = new MockSocketStream( | |
569 GURL("ws://127.0.0.1/"), websocket, context_.get(), NULL); | |
570 | |
571 websocket->InitSocketStream(socket_.get()); | |
572 } | |
573 | |
574 void SetDeleteNext() { return delegate_->set_delete_next(true); } | |
575 WebSocketJob* job() { return delegate_->job(); } | |
576 | |
577 scoped_ptr<DeletingSocketStreamDelegate> delegate_; | |
578 scoped_refptr<MockCookieStore> cookie_store_; | |
579 scoped_ptr<MockURLRequestContext> context_; | |
580 scoped_refptr<SocketStream> socket_; | |
581 }; | |
582 | |
583 const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] = | |
584 "GET /demo HTTP/1.1\r\n" | |
585 "Host: example.com\r\n" | |
586 "Upgrade: WebSocket\r\n" | |
587 "Connection: Upgrade\r\n" | |
588 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
589 "Origin: http://example.com\r\n" | |
590 "Sec-WebSocket-Protocol: sample\r\n" | |
591 "Sec-WebSocket-Version: 13\r\n" | |
592 "\r\n"; | |
593 | |
594 const char WebSocketJobTest::kHandshakeRequestWithCookie[] = | |
595 "GET /demo HTTP/1.1\r\n" | |
596 "Host: example.com\r\n" | |
597 "Upgrade: WebSocket\r\n" | |
598 "Connection: Upgrade\r\n" | |
599 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
600 "Origin: http://example.com\r\n" | |
601 "Sec-WebSocket-Protocol: sample\r\n" | |
602 "Sec-WebSocket-Version: 13\r\n" | |
603 "Cookie: WK-test=1\r\n" | |
604 "\r\n"; | |
605 | |
606 const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] = | |
607 "GET /demo HTTP/1.1\r\n" | |
608 "Host: example.com\r\n" | |
609 "Upgrade: WebSocket\r\n" | |
610 "Connection: Upgrade\r\n" | |
611 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
612 "Origin: http://example.com\r\n" | |
613 "Sec-WebSocket-Protocol: sample\r\n" | |
614 "Sec-WebSocket-Version: 13\r\n" | |
615 "Cookie: CR-test=1; CR-test-httponly=1\r\n" | |
616 "\r\n"; | |
617 | |
618 const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] = | |
619 "HTTP/1.1 101 Switching Protocols\r\n" | |
620 "Upgrade: websocket\r\n" | |
621 "Connection: Upgrade\r\n" | |
622 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
623 "Sec-WebSocket-Protocol: sample\r\n" | |
624 "\r\n"; | |
625 | |
626 const char WebSocketJobTest::kHandshakeResponseWithCookie[] = | |
627 "HTTP/1.1 101 Switching Protocols\r\n" | |
628 "Upgrade: websocket\r\n" | |
629 "Connection: Upgrade\r\n" | |
630 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
631 "Sec-WebSocket-Protocol: sample\r\n" | |
632 "Set-Cookie: CR-set-test=1\r\n" | |
633 "\r\n"; | |
634 | |
635 const char WebSocketJobTest::kDataHello[] = "Hello, "; | |
636 | |
637 const char WebSocketJobTest::kDataWorld[] = "World!\n"; | |
638 | |
639 const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength = | |
640 arraysize(kHandshakeRequestWithoutCookie) - 1; | |
641 const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength = | |
642 arraysize(kHandshakeRequestWithCookie) - 1; | |
643 const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength = | |
644 arraysize(kHandshakeRequestWithFilteredCookie) - 1; | |
645 const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength = | |
646 arraysize(kHandshakeResponseWithoutCookie) - 1; | |
647 const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength = | |
648 arraysize(kHandshakeResponseWithCookie) - 1; | |
649 const size_t WebSocketJobTest::kDataHelloLength = | |
650 arraysize(kDataHello) - 1; | |
651 const size_t WebSocketJobTest::kDataWorldLength = | |
652 arraysize(kDataWorld) - 1; | |
653 | |
654 void WebSocketJobTest::TestSimpleHandshake() { | |
655 GURL url("ws://example.com/demo"); | |
656 MockSocketStreamDelegate delegate; | |
657 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
658 SkipToConnecting(); | |
659 | |
660 DoSendRequest(); | |
661 base::MessageLoop::current()->RunUntilIdle(); | |
662 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); | |
663 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
664 websocket_->OnSentData(socket_.get(), | |
665 kHandshakeRequestWithoutCookieLength); | |
666 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); | |
667 | |
668 websocket_->OnReceivedData(socket_.get(), | |
669 kHandshakeResponseWithoutCookie, | |
670 kHandshakeResponseWithoutCookieLength); | |
671 base::MessageLoop::current()->RunUntilIdle(); | |
672 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); | |
673 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); | |
674 CloseWebSocketJob(); | |
675 } | |
676 | |
677 void WebSocketJobTest::TestSlowHandshake() { | |
678 GURL url("ws://example.com/demo"); | |
679 MockSocketStreamDelegate delegate; | |
680 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
681 SkipToConnecting(); | |
682 | |
683 DoSendRequest(); | |
684 // We assume request is sent in one data chunk (from WebKit) | |
685 // We don't support streaming request. | |
686 base::MessageLoop::current()->RunUntilIdle(); | |
687 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); | |
688 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
689 websocket_->OnSentData(socket_.get(), | |
690 kHandshakeRequestWithoutCookieLength); | |
691 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); | |
692 | |
693 std::vector<std::string> lines; | |
694 base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines); | |
695 for (size_t i = 0; i < lines.size() - 2; i++) { | |
696 std::string line = lines[i] + "\r\n"; | |
697 SCOPED_TRACE("Line: " + line); | |
698 websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size()); | |
699 base::MessageLoop::current()->RunUntilIdle(); | |
700 EXPECT_TRUE(delegate.received_data().empty()); | |
701 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
702 } | |
703 websocket_->OnReceivedData(socket_.get(), "\r\n", 2); | |
704 base::MessageLoop::current()->RunUntilIdle(); | |
705 EXPECT_FALSE(delegate.received_data().empty()); | |
706 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); | |
707 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); | |
708 CloseWebSocketJob(); | |
709 } | |
710 | |
711 INSTANTIATE_TEST_CASE_P( | |
712 NextProto, | |
713 WebSocketJobTest, | |
714 testing::Values(kProtoDeprecatedSPDY2, | |
715 kProtoSPDY3, kProtoSPDY31, kProtoSPDY4)); | |
716 | |
717 TEST_P(WebSocketJobTest, DelayedCookies) { | |
718 enable_websocket_over_spdy_ = true; | |
719 GURL url("ws://example.com/demo"); | |
720 GURL cookieUrl("http://example.com/demo"); | |
721 CookieOptions cookie_options; | |
722 scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster(); | |
723 context_->set_cookie_store(cookie_store.get()); | |
724 cookie_store->SetCookieWithOptionsAsync(cookieUrl, | |
725 "CR-test=1", | |
726 cookie_options, | |
727 CookieMonster::SetCookiesCallback()); | |
728 cookie_options.set_include_httponly(); | |
729 cookie_store->SetCookieWithOptionsAsync( | |
730 cookieUrl, "CR-test-httponly=1", cookie_options, | |
731 CookieMonster::SetCookiesCallback()); | |
732 | |
733 MockSocketStreamDelegate delegate; | |
734 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
735 SkipToConnecting(); | |
736 | |
737 bool sent = websocket_->SendData(kHandshakeRequestWithCookie, | |
738 kHandshakeRequestWithCookieLength); | |
739 EXPECT_TRUE(sent); | |
740 base::MessageLoop::current()->RunUntilIdle(); | |
741 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); | |
742 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
743 websocket_->OnSentData(socket_.get(), | |
744 kHandshakeRequestWithFilteredCookieLength); | |
745 EXPECT_EQ(kHandshakeRequestWithCookieLength, | |
746 delegate.amount_sent()); | |
747 | |
748 websocket_->OnReceivedData(socket_.get(), | |
749 kHandshakeResponseWithCookie, | |
750 kHandshakeResponseWithCookieLength); | |
751 base::MessageLoop::current()->RunUntilIdle(); | |
752 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); | |
753 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); | |
754 | |
755 CloseWebSocketJob(); | |
756 } | |
757 | |
758 void WebSocketJobTest::TestHandshakeWithCookie() { | |
759 GURL url("ws://example.com/demo"); | |
760 GURL cookieUrl("http://example.com/demo"); | |
761 CookieOptions cookie_options; | |
762 cookie_store_->SetCookieWithOptions( | |
763 cookieUrl, "CR-test=1", cookie_options); | |
764 cookie_options.set_include_httponly(); | |
765 cookie_store_->SetCookieWithOptions( | |
766 cookieUrl, "CR-test-httponly=1", cookie_options); | |
767 | |
768 MockSocketStreamDelegate delegate; | |
769 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
770 SkipToConnecting(); | |
771 | |
772 bool sent = websocket_->SendData(kHandshakeRequestWithCookie, | |
773 kHandshakeRequestWithCookieLength); | |
774 EXPECT_TRUE(sent); | |
775 base::MessageLoop::current()->RunUntilIdle(); | |
776 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); | |
777 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
778 websocket_->OnSentData(socket_.get(), | |
779 kHandshakeRequestWithFilteredCookieLength); | |
780 EXPECT_EQ(kHandshakeRequestWithCookieLength, | |
781 delegate.amount_sent()); | |
782 | |
783 websocket_->OnReceivedData(socket_.get(), | |
784 kHandshakeResponseWithCookie, | |
785 kHandshakeResponseWithCookieLength); | |
786 base::MessageLoop::current()->RunUntilIdle(); | |
787 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); | |
788 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); | |
789 | |
790 EXPECT_EQ(3U, cookie_store_->entries().size()); | |
791 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); | |
792 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); | |
793 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); | |
794 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); | |
795 EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); | |
796 EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); | |
797 | |
798 CloseWebSocketJob(); | |
799 } | |
800 | |
801 void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() { | |
802 GURL url("ws://example.com/demo"); | |
803 GURL cookieUrl("http://example.com/demo"); | |
804 CookieOptions cookie_options; | |
805 cookie_store_->SetCookieWithOptions( | |
806 cookieUrl, "CR-test=1", cookie_options); | |
807 cookie_options.set_include_httponly(); | |
808 cookie_store_->SetCookieWithOptions( | |
809 cookieUrl, "CR-test-httponly=1", cookie_options); | |
810 | |
811 MockSocketStreamDelegate delegate; | |
812 delegate.set_allow_all_cookies(false); | |
813 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
814 SkipToConnecting(); | |
815 | |
816 bool sent = websocket_->SendData(kHandshakeRequestWithCookie, | |
817 kHandshakeRequestWithCookieLength); | |
818 EXPECT_TRUE(sent); | |
819 base::MessageLoop::current()->RunUntilIdle(); | |
820 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); | |
821 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
822 websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength); | |
823 EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent()); | |
824 | |
825 websocket_->OnReceivedData(socket_.get(), | |
826 kHandshakeResponseWithCookie, | |
827 kHandshakeResponseWithCookieLength); | |
828 base::MessageLoop::current()->RunUntilIdle(); | |
829 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); | |
830 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); | |
831 | |
832 EXPECT_EQ(2U, cookie_store_->entries().size()); | |
833 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); | |
834 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); | |
835 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); | |
836 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); | |
837 | |
838 CloseWebSocketJob(); | |
839 } | |
840 | |
841 void WebSocketJobTest::TestHSTSUpgrade() { | |
842 GURL url("ws://upgrademe.com/"); | |
843 MockSocketStreamDelegate delegate; | |
844 scoped_refptr<SocketStreamJob> job = | |
845 SocketStreamJob::CreateSocketStreamJob( | |
846 url, &delegate, context_->transport_security_state(), | |
847 context_->ssl_config_service(), NULL, NULL); | |
848 EXPECT_TRUE(GetSocket(job.get())->is_secure()); | |
849 job->DetachDelegate(); | |
850 | |
851 url = GURL("ws://donotupgrademe.com/"); | |
852 job = SocketStreamJob::CreateSocketStreamJob( | |
853 url, &delegate, context_->transport_security_state(), | |
854 context_->ssl_config_service(), NULL, NULL); | |
855 EXPECT_FALSE(GetSocket(job.get())->is_secure()); | |
856 job->DetachDelegate(); | |
857 } | |
858 | |
859 void WebSocketJobTest::TestInvalidSendData() { | |
860 GURL url("ws://example.com/demo"); | |
861 MockSocketStreamDelegate delegate; | |
862 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); | |
863 SkipToConnecting(); | |
864 | |
865 DoSendRequest(); | |
866 // We assume request is sent in one data chunk (from WebKit) | |
867 // We don't support streaming request. | |
868 base::MessageLoop::current()->RunUntilIdle(); | |
869 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); | |
870 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
871 websocket_->OnSentData(socket_.get(), | |
872 kHandshakeRequestWithoutCookieLength); | |
873 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); | |
874 | |
875 // We could not send any data until connection is established. | |
876 bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie, | |
877 kHandshakeRequestWithoutCookieLength); | |
878 EXPECT_FALSE(sent); | |
879 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); | |
880 CloseWebSocketJob(); | |
881 } | |
882 | |
883 // Following tests verify cooperation between WebSocketJob and SocketStream. | |
884 // Other former tests use MockSocketStream as SocketStream, so we could not | |
885 // check SocketStream behavior. | |
886 // OrderedSocketData provide socket level verifiation by checking out-going | |
887 // packets in comparison with the MockWrite array and emulating in-coming | |
888 // packets with MockRead array. | |
889 | |
890 void WebSocketJobTest::TestConnectByWebSocket( | |
891 ThrottlingOption throttling) { | |
892 // This is a test for verifying cooperation between WebSocketJob and | |
893 // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic | |
894 // situation. If |throttling| was |THROTTLING_ON|, throttling limits the | |
895 // latter connection. | |
896 MockWrite writes[] = { | |
897 MockWrite(ASYNC, | |
898 kHandshakeRequestWithoutCookie, | |
899 kHandshakeRequestWithoutCookieLength, | |
900 1), | |
901 MockWrite(ASYNC, | |
902 kDataHello, | |
903 kDataHelloLength, | |
904 3) | |
905 }; | |
906 MockRead reads[] = { | |
907 MockRead(ASYNC, | |
908 kHandshakeResponseWithoutCookie, | |
909 kHandshakeResponseWithoutCookieLength, | |
910 2), | |
911 MockRead(ASYNC, | |
912 kDataWorld, | |
913 kDataWorldLength, | |
914 4), | |
915 MockRead(SYNCHRONOUS, 0, 5) // EOF | |
916 }; | |
917 data_.reset(new OrderedSocketData( | |
918 reads, arraysize(reads), writes, arraysize(writes))); | |
919 | |
920 GURL url("ws://example.com/demo"); | |
921 MockSocketStreamDelegate delegate; | |
922 WebSocketJobTest* test = this; | |
923 if (throttling == THROTTLING_ON) | |
924 delegate.SetOnStartOpenConnection( | |
925 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); | |
926 delegate.SetOnConnected( | |
927 base::Bind(&WebSocketJobTest::DoSendRequest, | |
928 base::Unretained(test))); | |
929 delegate.SetOnReceivedData( | |
930 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); | |
931 delegate.SetOnClose( | |
932 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); | |
933 InitWebSocketJob(url, &delegate, STREAM_SOCKET); | |
934 | |
935 scoped_refptr<WebSocketJob> block_websocket; | |
936 if (throttling == THROTTLING_ON) { | |
937 // Create former WebSocket object which obstructs the latter one. | |
938 block_websocket = new WebSocketJob(NULL); | |
939 block_websocket->addresses_ = AddressList(websocket_->address_list()); | |
940 ASSERT_TRUE( | |
941 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); | |
942 } | |
943 | |
944 websocket_->Connect(); | |
945 | |
946 if (throttling == THROTTLING_ON) { | |
947 EXPECT_EQ(OK, WaitForResult()); | |
948 EXPECT_TRUE(websocket_->IsWaiting()); | |
949 | |
950 // Remove the former WebSocket object from throttling queue to unblock the | |
951 // latter. | |
952 block_websocket->state_ = WebSocketJob::CLOSED; | |
953 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); | |
954 block_websocket = NULL; | |
955 } | |
956 | |
957 EXPECT_EQ(OK, WaitForResult()); | |
958 EXPECT_TRUE(data_->at_read_eof()); | |
959 EXPECT_TRUE(data_->at_write_eof()); | |
960 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); | |
961 } | |
962 | |
963 void WebSocketJobTest::TestConnectBySpdy( | |
964 SpdyOption spdy, ThrottlingOption throttling) { | |
965 // This is a test for verifying cooperation between WebSocketJob and | |
966 // SocketStream in the situation we have SPDY session to the server. If | |
967 // |throttling| was |THROTTLING_ON|, throttling limits the latter connection. | |
968 // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected | |
969 // results depend on its configuration. | |
970 MockWrite writes_websocket[] = { | |
971 MockWrite(ASYNC, | |
972 kHandshakeRequestWithoutCookie, | |
973 kHandshakeRequestWithoutCookieLength, | |
974 1), | |
975 MockWrite(ASYNC, | |
976 kDataHello, | |
977 kDataHelloLength, | |
978 3) | |
979 }; | |
980 MockRead reads_websocket[] = { | |
981 MockRead(ASYNC, | |
982 kHandshakeResponseWithoutCookie, | |
983 kHandshakeResponseWithoutCookieLength, | |
984 2), | |
985 MockRead(ASYNC, | |
986 kDataWorld, | |
987 kDataWorldLength, | |
988 4), | |
989 MockRead(SYNCHRONOUS, 0, 5) // EOF | |
990 }; | |
991 | |
992 scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock()); | |
993 spdy_util_.SetHeader("path", "/demo", request_headers.get()); | |
994 spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get()); | |
995 spdy_util_.SetHeader("scheme", "ws", request_headers.get()); | |
996 spdy_util_.SetHeader("host", "example.com", request_headers.get()); | |
997 spdy_util_.SetHeader("origin", "http://example.com", request_headers.get()); | |
998 spdy_util_.SetHeader("sec-websocket-protocol", "sample", | |
999 request_headers.get()); | |
1000 | |
1001 scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock()); | |
1002 spdy_util_.SetHeader("status", "101 Switching Protocols", | |
1003 response_headers.get()); | |
1004 spdy_util_.SetHeader("sec-websocket-protocol", "sample", | |
1005 response_headers.get()); | |
1006 | |
1007 const SpdyStreamId kStreamId = 1; | |
1008 scoped_ptr<SpdyFrame> request_frame( | |
1009 spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame( | |
1010 request_headers.Pass(), | |
1011 kStreamId, | |
1012 MEDIUM)); | |
1013 scoped_ptr<SpdyFrame> response_frame( | |
1014 spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame( | |
1015 response_headers.Pass(), | |
1016 kStreamId, | |
1017 MEDIUM)); | |
1018 scoped_ptr<SpdyFrame> data_hello_frame( | |
1019 spdy_util_.ConstructSpdyWebSocketDataFrame( | |
1020 kDataHello, | |
1021 kDataHelloLength, | |
1022 kStreamId, | |
1023 false)); | |
1024 scoped_ptr<SpdyFrame> data_world_frame( | |
1025 spdy_util_.ConstructSpdyWebSocketDataFrame( | |
1026 kDataWorld, | |
1027 kDataWorldLength, | |
1028 kStreamId, | |
1029 false)); | |
1030 MockWrite writes_spdy[] = { | |
1031 CreateMockWrite(*request_frame.get(), 1), | |
1032 CreateMockWrite(*data_hello_frame.get(), 3), | |
1033 }; | |
1034 MockRead reads_spdy[] = { | |
1035 CreateMockRead(*response_frame.get(), 2), | |
1036 CreateMockRead(*data_world_frame.get(), 4), | |
1037 MockRead(SYNCHRONOUS, 0, 5) // EOF | |
1038 }; | |
1039 | |
1040 if (spdy == SPDY_ON) | |
1041 data_.reset(new OrderedSocketData( | |
1042 reads_spdy, arraysize(reads_spdy), | |
1043 writes_spdy, arraysize(writes_spdy))); | |
1044 else | |
1045 data_.reset(new OrderedSocketData( | |
1046 reads_websocket, arraysize(reads_websocket), | |
1047 writes_websocket, arraysize(writes_websocket))); | |
1048 | |
1049 GURL url("ws://example.com/demo"); | |
1050 MockSocketStreamDelegate delegate; | |
1051 WebSocketJobTest* test = this; | |
1052 if (throttling == THROTTLING_ON) | |
1053 delegate.SetOnStartOpenConnection( | |
1054 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); | |
1055 delegate.SetOnConnected( | |
1056 base::Bind(&WebSocketJobTest::DoSendRequest, | |
1057 base::Unretained(test))); | |
1058 delegate.SetOnReceivedData( | |
1059 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); | |
1060 delegate.SetOnClose( | |
1061 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); | |
1062 InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET); | |
1063 | |
1064 scoped_refptr<WebSocketJob> block_websocket; | |
1065 if (throttling == THROTTLING_ON) { | |
1066 // Create former WebSocket object which obstructs the latter one. | |
1067 block_websocket = new WebSocketJob(NULL); | |
1068 block_websocket->addresses_ = AddressList(websocket_->address_list()); | |
1069 ASSERT_TRUE( | |
1070 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); | |
1071 } | |
1072 | |
1073 websocket_->Connect(); | |
1074 | |
1075 if (throttling == THROTTLING_ON) { | |
1076 EXPECT_EQ(OK, WaitForResult()); | |
1077 EXPECT_TRUE(websocket_->IsWaiting()); | |
1078 | |
1079 // Remove the former WebSocket object from throttling queue to unblock the | |
1080 // latter. | |
1081 block_websocket->state_ = WebSocketJob::CLOSED; | |
1082 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); | |
1083 block_websocket = NULL; | |
1084 } | |
1085 | |
1086 EXPECT_EQ(OK, WaitForResult()); | |
1087 EXPECT_TRUE(data_->at_read_eof()); | |
1088 EXPECT_TRUE(data_->at_write_eof()); | |
1089 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); | |
1090 } | |
1091 | |
1092 void WebSocketJobTest::TestThrottlingLimit() { | |
1093 std::vector<scoped_refptr<WebSocketJob> > jobs; | |
1094 const int kMaxWebSocketJobsThrottled = 1024; | |
1095 IPAddressNumber ip; | |
1096 ParseIPLiteralToNumber("127.0.0.1", &ip); | |
1097 for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) { | |
1098 scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL); | |
1099 job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80)); | |
1100 if (i >= kMaxWebSocketJobsThrottled) | |
1101 EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job.get())); | |
1102 else | |
1103 EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job.get())); | |
1104 jobs.push_back(job); | |
1105 } | |
1106 | |
1107 // Close the jobs in reverse order. Otherwise, We need to make them prepared | |
1108 // for Wakeup call. | |
1109 for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter = | |
1110 jobs.rbegin(); | |
1111 iter != jobs.rend(); | |
1112 ++iter) { | |
1113 WebSocketJob* job = (*iter).get(); | |
1114 job->state_ = WebSocketJob::CLOSED; | |
1115 WebSocketThrottle::GetInstance()->RemoveFromQueue(job); | |
1116 } | |
1117 } | |
1118 | |
1119 // Execute tests in both spdy-disabled mode and spdy-enabled mode. | |
1120 TEST_P(WebSocketJobTest, SimpleHandshake) { | |
1121 TestSimpleHandshake(); | |
1122 } | |
1123 | |
1124 TEST_P(WebSocketJobTest, SlowHandshake) { | |
1125 TestSlowHandshake(); | |
1126 } | |
1127 | |
1128 TEST_P(WebSocketJobTest, HandshakeWithCookie) { | |
1129 TestHandshakeWithCookie(); | |
1130 } | |
1131 | |
1132 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { | |
1133 TestHandshakeWithCookieButNotAllowed(); | |
1134 } | |
1135 | |
1136 TEST_P(WebSocketJobTest, HSTSUpgrade) { | |
1137 TestHSTSUpgrade(); | |
1138 } | |
1139 | |
1140 TEST_P(WebSocketJobTest, InvalidSendData) { | |
1141 TestInvalidSendData(); | |
1142 } | |
1143 | |
1144 TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) { | |
1145 enable_websocket_over_spdy_ = true; | |
1146 TestSimpleHandshake(); | |
1147 } | |
1148 | |
1149 TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) { | |
1150 enable_websocket_over_spdy_ = true; | |
1151 TestSlowHandshake(); | |
1152 } | |
1153 | |
1154 TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) { | |
1155 enable_websocket_over_spdy_ = true; | |
1156 TestHandshakeWithCookie(); | |
1157 } | |
1158 | |
1159 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) { | |
1160 enable_websocket_over_spdy_ = true; | |
1161 TestHandshakeWithCookieButNotAllowed(); | |
1162 } | |
1163 | |
1164 TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) { | |
1165 enable_websocket_over_spdy_ = true; | |
1166 TestHSTSUpgrade(); | |
1167 } | |
1168 | |
1169 TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) { | |
1170 enable_websocket_over_spdy_ = true; | |
1171 TestInvalidSendData(); | |
1172 } | |
1173 | |
1174 TEST_P(WebSocketJobTest, ConnectByWebSocket) { | |
1175 enable_websocket_over_spdy_ = true; | |
1176 TestConnectByWebSocket(THROTTLING_OFF); | |
1177 } | |
1178 | |
1179 TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) { | |
1180 enable_websocket_over_spdy_ = true; | |
1181 TestConnectByWebSocket(THROTTLING_OFF); | |
1182 } | |
1183 | |
1184 TEST_P(WebSocketJobTest, ConnectBySpdy) { | |
1185 TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF); | |
1186 } | |
1187 | |
1188 TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) { | |
1189 enable_websocket_over_spdy_ = true; | |
1190 TestConnectBySpdy(SPDY_ON, THROTTLING_OFF); | |
1191 } | |
1192 | |
1193 TEST_P(WebSocketJobTest, ThrottlingWebSocket) { | |
1194 TestConnectByWebSocket(THROTTLING_ON); | |
1195 } | |
1196 | |
1197 TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) { | |
1198 TestThrottlingLimit(); | |
1199 } | |
1200 | |
1201 TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) { | |
1202 enable_websocket_over_spdy_ = true; | |
1203 TestConnectByWebSocket(THROTTLING_ON); | |
1204 } | |
1205 | |
1206 TEST_P(WebSocketJobTest, ThrottlingSpdy) { | |
1207 TestConnectBySpdy(SPDY_OFF, THROTTLING_ON); | |
1208 } | |
1209 | |
1210 TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) { | |
1211 enable_websocket_over_spdy_ = true; | |
1212 TestConnectBySpdy(SPDY_ON, THROTTLING_ON); | |
1213 } | |
1214 | |
1215 TEST_F(WebSocketJobDeleteTest, OnClose) { | |
1216 SetDeleteNext(); | |
1217 job()->OnClose(socket_.get()); | |
1218 // OnClose() sets WebSocketJob::_socket to NULL before we can detach it, so | |
1219 // socket_->delegate is still set at this point. Clear it to avoid hitting | |
1220 // DCHECK(!delegate_) in the SocketStream destructor. SocketStream::Finish() | |
1221 // is the only caller of this method in real code, and it also sets delegate_ | |
1222 // to NULL. | |
1223 socket_->DetachDelegate(); | |
1224 EXPECT_FALSE(job()); | |
1225 } | |
1226 | |
1227 TEST_F(WebSocketJobDeleteTest, OnAuthRequired) { | |
1228 SetDeleteNext(); | |
1229 job()->OnAuthRequired(socket_.get(), NULL); | |
1230 EXPECT_FALSE(job()); | |
1231 } | |
1232 | |
1233 TEST_F(WebSocketJobDeleteTest, OnSSLCertificateError) { | |
1234 SSLInfo ssl_info; | |
1235 SetDeleteNext(); | |
1236 job()->OnSSLCertificateError(socket_.get(), ssl_info, true); | |
1237 EXPECT_FALSE(job()); | |
1238 } | |
1239 | |
1240 TEST_F(WebSocketJobDeleteTest, OnError) { | |
1241 SetDeleteNext(); | |
1242 job()->OnError(socket_.get(), ERR_CONNECTION_RESET); | |
1243 EXPECT_FALSE(job()); | |
1244 } | |
1245 | |
1246 TEST_F(WebSocketJobDeleteTest, OnSentSpdyHeaders) { | |
1247 job()->Connect(); | |
1248 SetDeleteNext(); | |
1249 job()->OnSentSpdyHeaders(); | |
1250 EXPECT_FALSE(job()); | |
1251 } | |
1252 | |
1253 TEST_F(WebSocketJobDeleteTest, OnSentHandshakeRequest) { | |
1254 static const char kMinimalRequest[] = | |
1255 "GET /demo HTTP/1.1\r\n" | |
1256 "Host: example.com\r\n" | |
1257 "Upgrade: WebSocket\r\n" | |
1258 "Connection: Upgrade\r\n" | |
1259 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
1260 "Origin: http://example.com\r\n" | |
1261 "Sec-WebSocket-Version: 13\r\n" | |
1262 "\r\n"; | |
1263 const size_t kMinimalRequestSize = arraysize(kMinimalRequest) - 1; | |
1264 job()->Connect(); | |
1265 job()->SendData(kMinimalRequest, kMinimalRequestSize); | |
1266 SetDeleteNext(); | |
1267 job()->OnSentData(socket_.get(), kMinimalRequestSize); | |
1268 EXPECT_FALSE(job()); | |
1269 } | |
1270 | |
1271 TEST_F(WebSocketJobDeleteTest, NotifyHeadersComplete) { | |
1272 static const char kMinimalResponse[] = | |
1273 "HTTP/1.1 101 Switching Protocols\r\n" | |
1274 "Upgrade: websocket\r\n" | |
1275 "Connection: Upgrade\r\n" | |
1276 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
1277 "\r\n"; | |
1278 job()->Connect(); | |
1279 SetDeleteNext(); | |
1280 job()->OnReceivedData( | |
1281 socket_.get(), kMinimalResponse, arraysize(kMinimalResponse) - 1); | |
1282 EXPECT_FALSE(job()); | |
1283 } | |
1284 | |
1285 // TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation. | |
1286 // TODO(toyoshim,yutak): Add tests to verify closing handshake. | |
1287 } // namespace net | |
OLD | NEW |