| 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 |