OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 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 <string> |
| 6 #include <vector> |
| 7 |
| 8 #include "base/ref_counted.h" |
| 9 #include "googleurl/src/gurl.h" |
| 10 #include "net/base/cookie_policy.h" |
| 11 #include "net/base/cookie_store.h" |
| 12 #include "net/base/net_errors.h" |
| 13 #include "net/socket_stream/socket_stream.h" |
| 14 #include "net/url_request/url_request_context.h" |
| 15 #include "net/websockets/websocket_job.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 #include "testing/gmock/include/gmock/gmock.h" |
| 18 #include "testing/platform_test.h" |
| 19 |
| 20 namespace net { |
| 21 |
| 22 class MockSocketStream : public SocketStream { |
| 23 public: |
| 24 MockSocketStream(const GURL& url, SocketStream::Delegate* delegate) |
| 25 : SocketStream(url, delegate) {} |
| 26 virtual ~MockSocketStream() {} |
| 27 |
| 28 virtual void Connect() {} |
| 29 virtual bool SendData(const char* data, int len) { |
| 30 sent_data_ += std::string(data, len); |
| 31 return true; |
| 32 } |
| 33 |
| 34 virtual void Close() {} |
| 35 virtual void RestartWithAuth( |
| 36 const std::wstring& username, std::wstring& password) {} |
| 37 virtual void DetachDelegate() { |
| 38 delegate_ = NULL; |
| 39 } |
| 40 |
| 41 const std::string& sent_data() const { |
| 42 return sent_data_; |
| 43 } |
| 44 |
| 45 private: |
| 46 std::string sent_data_; |
| 47 }; |
| 48 |
| 49 class MockSocketStreamDelegate : public SocketStream::Delegate { |
| 50 public: |
| 51 MockSocketStreamDelegate() |
| 52 : amount_sent_(0) {} |
| 53 virtual ~MockSocketStreamDelegate() {} |
| 54 |
| 55 virtual void OnConnected(SocketStream* socket, int max_pending_send_allowed) { |
| 56 } |
| 57 virtual void OnSentData(SocketStream* socket, int amount_sent) { |
| 58 amount_sent_ += amount_sent; |
| 59 } |
| 60 virtual void OnReceivedData(SocketStream* socket, |
| 61 const char* data, int len) { |
| 62 received_data_ += std::string(data, len); |
| 63 } |
| 64 virtual void OnClose(SocketStream* socket) { |
| 65 } |
| 66 |
| 67 size_t amount_sent() const { return amount_sent_; } |
| 68 const std::string& received_data() const { return received_data_; } |
| 69 |
| 70 private: |
| 71 int amount_sent_; |
| 72 std::string received_data_; |
| 73 }; |
| 74 |
| 75 class MockCookieStore : public CookieStore { |
| 76 public: |
| 77 struct Entry { |
| 78 GURL url; |
| 79 std::string cookie_line; |
| 80 CookieOptions options; |
| 81 }; |
| 82 MockCookieStore() {} |
| 83 |
| 84 virtual bool SetCookieWithOptions(const GURL& url, |
| 85 const std::string& cookie_line, |
| 86 const CookieOptions& options) { |
| 87 Entry entry; |
| 88 entry.url = url; |
| 89 entry.cookie_line = cookie_line; |
| 90 entry.options = options; |
| 91 entries_.push_back(entry); |
| 92 return true; |
| 93 } |
| 94 virtual std::string GetCookiesWithOptions(const GURL& url, |
| 95 const CookieOptions& options) { |
| 96 std::string result; |
| 97 for (size_t i = 0; i < entries_.size(); i++) { |
| 98 Entry &entry = entries_[i]; |
| 99 if (url == entry.url) { |
| 100 if (!result.empty()) { |
| 101 result += "; "; |
| 102 } |
| 103 result += entry.cookie_line; |
| 104 } |
| 105 } |
| 106 return result; |
| 107 } |
| 108 virtual void DeleteCookie(const GURL& url, |
| 109 const std::string& cookie_name) {} |
| 110 virtual CookieMonster* GetCookieMonster() { return NULL; } |
| 111 |
| 112 const std::vector<Entry>& entries() const { return entries_; } |
| 113 |
| 114 private: |
| 115 friend class base::RefCountedThreadSafe<MockCookieStore>; |
| 116 virtual ~MockCookieStore() {} |
| 117 |
| 118 std::vector<Entry> entries_; |
| 119 }; |
| 120 |
| 121 class MockCookiePolicy : public CookiePolicy, |
| 122 public base::RefCountedThreadSafe<MockCookiePolicy> { |
| 123 public: |
| 124 MockCookiePolicy() : allow_all_cookies_(true), callback_(NULL) {} |
| 125 |
| 126 void set_allow_all_cookies(bool allow_all_cookies) { |
| 127 allow_all_cookies_ = allow_all_cookies; |
| 128 } |
| 129 |
| 130 virtual int CanGetCookies(const GURL& url, |
| 131 const GURL& first_party_for_cookies, |
| 132 CompletionCallback* callback) { |
| 133 DCHECK(!callback_); |
| 134 callback_ = callback; |
| 135 MessageLoop::current()->PostTask( |
| 136 FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanGetCookies)); |
| 137 return ERR_IO_PENDING; |
| 138 } |
| 139 |
| 140 virtual int CanSetCookie(const GURL& url, |
| 141 const GURL& first_party_for_cookies, |
| 142 const std::string& cookie_line, |
| 143 CompletionCallback* callback) { |
| 144 DCHECK(!callback_); |
| 145 callback_ = callback; |
| 146 MessageLoop::current()->PostTask( |
| 147 FROM_HERE, NewRunnableMethod(this, &MockCookiePolicy::OnCanSetCookie)); |
| 148 return ERR_IO_PENDING; |
| 149 } |
| 150 |
| 151 private: |
| 152 friend class base::RefCountedThreadSafe<MockCookiePolicy>; |
| 153 virtual ~MockCookiePolicy() {} |
| 154 |
| 155 void OnCanGetCookies() { |
| 156 CompletionCallback* callback = callback_; |
| 157 callback_ = NULL; |
| 158 if (allow_all_cookies_) |
| 159 callback->Run(OK); |
| 160 else |
| 161 callback->Run(ERR_ACCESS_DENIED); |
| 162 } |
| 163 void OnCanSetCookie() { |
| 164 CompletionCallback* callback = callback_; |
| 165 callback_ = NULL; |
| 166 if (allow_all_cookies_) |
| 167 callback->Run(OK); |
| 168 else |
| 169 callback->Run(ERR_ACCESS_DENIED); |
| 170 } |
| 171 |
| 172 bool allow_all_cookies_; |
| 173 CompletionCallback* callback_; |
| 174 }; |
| 175 |
| 176 class MockURLRequestContext : public URLRequestContext { |
| 177 public: |
| 178 MockURLRequestContext(CookieStore* cookie_store, |
| 179 CookiePolicy* cookie_policy) { |
| 180 cookie_store_ = cookie_store; |
| 181 cookie_policy_ = cookie_policy; |
| 182 } |
| 183 |
| 184 private: |
| 185 friend class base::RefCountedThreadSafe<MockURLRequestContext>; |
| 186 virtual ~MockURLRequestContext() {} |
| 187 }; |
| 188 |
| 189 class WebSocketJobTest : public PlatformTest { |
| 190 public: |
| 191 virtual void SetUp() { |
| 192 cookie_store_ = new MockCookieStore; |
| 193 cookie_policy_ = new MockCookiePolicy; |
| 194 context_ = new MockURLRequestContext( |
| 195 cookie_store_.get(), cookie_policy_.get()); |
| 196 } |
| 197 virtual void TearDown() { |
| 198 cookie_store_ = NULL; |
| 199 cookie_policy_ = NULL; |
| 200 context_ = NULL; |
| 201 websocket_ = NULL; |
| 202 socket_ = NULL; |
| 203 } |
| 204 protected: |
| 205 void InitWebSocketJob(const GURL& url, MockSocketStreamDelegate* delegate) { |
| 206 websocket_ = new WebSocketJob(delegate); |
| 207 socket_ = new MockSocketStream(url, websocket_.get()); |
| 208 websocket_->InitSocketStream(socket_.get()); |
| 209 websocket_->state_ = WebSocketJob::CONNECTING; |
| 210 websocket_->set_context(context_.get()); |
| 211 } |
| 212 WebSocketJob::State GetWebSocketJobState() { |
| 213 return websocket_->state_; |
| 214 } |
| 215 void CloseWebSocketJob() { |
| 216 if (websocket_->socket_) |
| 217 websocket_->socket_->DetachDelegate(); |
| 218 websocket_->state_ = WebSocketJob::CLOSED; |
| 219 websocket_->delegate_ = NULL; |
| 220 websocket_->socket_ = NULL; |
| 221 } |
| 222 |
| 223 scoped_refptr<MockCookieStore> cookie_store_; |
| 224 scoped_refptr<MockCookiePolicy> cookie_policy_; |
| 225 scoped_refptr<MockURLRequestContext> context_; |
| 226 scoped_refptr<WebSocketJob> websocket_; |
| 227 scoped_refptr<MockSocketStream> socket_; |
| 228 }; |
| 229 |
| 230 TEST_F(WebSocketJobTest, SimpleHandshake) { |
| 231 GURL url("ws://example.com/demo"); |
| 232 MockSocketStreamDelegate delegate; |
| 233 InitWebSocketJob(url, &delegate); |
| 234 |
| 235 static const char* kHandshakeRequestMessage = |
| 236 "GET /demo HTTP/1.1\r\n" |
| 237 "Upgrade: WebSocket\r\n" |
| 238 "Connection: Upgrade\r\n" |
| 239 "Host: example.com\r\n" |
| 240 "Origin: http://example.com\r\n" |
| 241 "WebSocket-Protocol: sample\r\n" |
| 242 "\r\n"; |
| 243 |
| 244 bool sent = websocket_->SendData(kHandshakeRequestMessage, |
| 245 strlen(kHandshakeRequestMessage)); |
| 246 EXPECT_EQ(true, sent); |
| 247 MessageLoop::current()->RunAllPending(); |
| 248 EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data()); |
| 249 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 250 websocket_->OnSentData(socket_.get(), strlen(kHandshakeRequestMessage)); |
| 251 EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
| 252 |
| 253 static const char* kHandshakeResponseMessage = |
| 254 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 255 "Upgrade: WebSocket\r\n" |
| 256 "Connection: Upgrade\r\n" |
| 257 "WebSocket-Origin: http://example.com\r\n" |
| 258 "WebSocket-Location: ws://example.com/demo\r\n" |
| 259 "WebSocket-Protocol: sample\r\n" |
| 260 "\r\n"; |
| 261 |
| 262 websocket_->OnReceivedData(socket_.get(), |
| 263 kHandshakeResponseMessage, |
| 264 strlen(kHandshakeResponseMessage)); |
| 265 MessageLoop::current()->RunAllPending(); |
| 266 EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data()); |
| 267 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
| 268 CloseWebSocketJob(); |
| 269 } |
| 270 |
| 271 TEST_F(WebSocketJobTest, SlowHandshake) { |
| 272 GURL url("ws://example.com/demo"); |
| 273 MockSocketStreamDelegate delegate; |
| 274 InitWebSocketJob(url, &delegate); |
| 275 |
| 276 static const char* kHandshakeRequestMessage = |
| 277 "GET /demo HTTP/1.1\r\n" |
| 278 "Upgrade: WebSocket\r\n" |
| 279 "Connection: Upgrade\r\n" |
| 280 "Host: example.com\r\n" |
| 281 "Origin: http://example.com\r\n" |
| 282 "WebSocket-Protocol: sample\r\n" |
| 283 "\r\n"; |
| 284 std::vector<std::string> lines; |
| 285 SplitString(kHandshakeRequestMessage, '\n', &lines); |
| 286 for (size_t i = 0; i < lines.size() - 2; i++) { |
| 287 std::string line = lines[i] + "\r\n"; |
| 288 SCOPED_TRACE("Line: " + line); |
| 289 bool sent = websocket_->SendData(line.c_str(), line.size()); |
| 290 EXPECT_EQ(true, sent); |
| 291 MessageLoop::current()->RunAllPending(); |
| 292 EXPECT_TRUE(socket_->sent_data().empty()); |
| 293 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 294 } |
| 295 bool sent = websocket_->SendData("\r\n", 2); |
| 296 EXPECT_EQ(true, sent); |
| 297 MessageLoop::current()->RunAllPending(); |
| 298 EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data()); |
| 299 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 300 |
| 301 for (size_t i = 0; i < lines.size() - 2; i++) { |
| 302 std::string line = lines[i] + "\r\n"; |
| 303 SCOPED_TRACE("Line: " + line); |
| 304 websocket_->OnSentData(socket_.get(), line.size()); |
| 305 EXPECT_EQ(0U, delegate.amount_sent()); |
| 306 } |
| 307 websocket_->OnSentData(socket_.get(), 2); // \r\n |
| 308 EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
| 309 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 310 |
| 311 static const char* kHandshakeResponseMessage = |
| 312 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 313 "Upgrade: WebSocket\r\n" |
| 314 "Connection: Upgrade\r\n" |
| 315 "WebSocket-Origin: http://example.com\r\n" |
| 316 "WebSocket-Location: ws://example.com/demo\r\n" |
| 317 "WebSocket-Protocol: sample\r\n" |
| 318 "\r\n"; |
| 319 |
| 320 lines.clear(); |
| 321 SplitString(kHandshakeResponseMessage, '\n', &lines); |
| 322 for (size_t i = 0; i < lines.size() - 2; i++) { |
| 323 std::string line = lines[i] + "\r\n"; |
| 324 SCOPED_TRACE("Line: " + line); |
| 325 websocket_->OnReceivedData(socket_, |
| 326 line.c_str(), |
| 327 line.size()); |
| 328 MessageLoop::current()->RunAllPending(); |
| 329 EXPECT_TRUE(delegate.received_data().empty()); |
| 330 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 331 } |
| 332 websocket_->OnReceivedData(socket_.get(), "\r\n", 2); |
| 333 MessageLoop::current()->RunAllPending(); |
| 334 EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data()); |
| 335 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
| 336 CloseWebSocketJob(); |
| 337 } |
| 338 |
| 339 TEST_F(WebSocketJobTest, HandshakeWithCookie) { |
| 340 GURL url("ws://example.com/demo"); |
| 341 GURL cookieUrl("http://example.com/demo"); |
| 342 CookieOptions cookie_options; |
| 343 cookie_store_->SetCookieWithOptions( |
| 344 cookieUrl, "CR-test=1", cookie_options); |
| 345 cookie_options.set_include_httponly(); |
| 346 cookie_store_->SetCookieWithOptions( |
| 347 cookieUrl, "CR-test-httponly=1", cookie_options); |
| 348 |
| 349 MockSocketStreamDelegate delegate; |
| 350 InitWebSocketJob(url, &delegate); |
| 351 |
| 352 static const char* kHandshakeRequestMessage = |
| 353 "GET /demo HTTP/1.1\r\n" |
| 354 "Upgrade: WebSocket\r\n" |
| 355 "Connection: Upgrade\r\n" |
| 356 "Host: example.com\r\n" |
| 357 "Origin: http://example.com\r\n" |
| 358 "WebSocket-Protocol: sample\r\n" |
| 359 "Cookie: WK-test=1\r\n" |
| 360 "\r\n"; |
| 361 |
| 362 static const char* kHandshakeRequestExpected = |
| 363 "GET /demo HTTP/1.1\r\n" |
| 364 "Upgrade: WebSocket\r\n" |
| 365 "Connection: Upgrade\r\n" |
| 366 "Host: example.com\r\n" |
| 367 "Origin: http://example.com\r\n" |
| 368 "WebSocket-Protocol: sample\r\n" |
| 369 "Cookie: CR-test=1; CR-test-httponly=1\r\n" |
| 370 "\r\n"; |
| 371 |
| 372 bool sent = websocket_->SendData(kHandshakeRequestMessage, |
| 373 strlen(kHandshakeRequestMessage)); |
| 374 EXPECT_EQ(true, sent); |
| 375 MessageLoop::current()->RunAllPending(); |
| 376 EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data()); |
| 377 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 378 websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected)); |
| 379 EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
| 380 |
| 381 static const char* kHandshakeResponseMessage = |
| 382 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 383 "Upgrade: WebSocket\r\n" |
| 384 "Connection: Upgrade\r\n" |
| 385 "WebSocket-Origin: http://example.com\r\n" |
| 386 "WebSocket-Location: ws://example.com/demo\r\n" |
| 387 "WebSocket-Protocol: sample\r\n" |
| 388 "Set-Cookie: CR-set-test=1\r\n" |
| 389 "\r\n"; |
| 390 |
| 391 static const char* kHandshakeResponseExpected = |
| 392 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 393 "Upgrade: WebSocket\r\n" |
| 394 "Connection: Upgrade\r\n" |
| 395 "WebSocket-Origin: http://example.com\r\n" |
| 396 "WebSocket-Location: ws://example.com/demo\r\n" |
| 397 "WebSocket-Protocol: sample\r\n" |
| 398 "\r\n"; |
| 399 |
| 400 websocket_->OnReceivedData(socket_.get(), |
| 401 kHandshakeResponseMessage, |
| 402 strlen(kHandshakeResponseMessage)); |
| 403 MessageLoop::current()->RunAllPending(); |
| 404 EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data()); |
| 405 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
| 406 |
| 407 EXPECT_EQ(3U, cookie_store_->entries().size()); |
| 408 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); |
| 409 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); |
| 410 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); |
| 411 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); |
| 412 EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); |
| 413 EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); |
| 414 |
| 415 CloseWebSocketJob(); |
| 416 } |
| 417 |
| 418 TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { |
| 419 GURL url("ws://example.com/demo"); |
| 420 GURL cookieUrl("http://example.com/demo"); |
| 421 CookieOptions cookie_options; |
| 422 cookie_store_->SetCookieWithOptions( |
| 423 cookieUrl, "CR-test=1", cookie_options); |
| 424 cookie_options.set_include_httponly(); |
| 425 cookie_store_->SetCookieWithOptions( |
| 426 cookieUrl, "CR-test-httponly=1", cookie_options); |
| 427 cookie_policy_->set_allow_all_cookies(false); |
| 428 |
| 429 MockSocketStreamDelegate delegate; |
| 430 InitWebSocketJob(url, &delegate); |
| 431 |
| 432 static const char* kHandshakeRequestMessage = |
| 433 "GET /demo HTTP/1.1\r\n" |
| 434 "Upgrade: WebSocket\r\n" |
| 435 "Connection: Upgrade\r\n" |
| 436 "Host: example.com\r\n" |
| 437 "Origin: http://example.com\r\n" |
| 438 "WebSocket-Protocol: sample\r\n" |
| 439 "Cookie: WK-test=1\r\n" |
| 440 "\r\n"; |
| 441 |
| 442 static const char* kHandshakeRequestExpected = |
| 443 "GET /demo HTTP/1.1\r\n" |
| 444 "Upgrade: WebSocket\r\n" |
| 445 "Connection: Upgrade\r\n" |
| 446 "Host: example.com\r\n" |
| 447 "Origin: http://example.com\r\n" |
| 448 "WebSocket-Protocol: sample\r\n" |
| 449 "\r\n"; |
| 450 |
| 451 bool sent = websocket_->SendData(kHandshakeRequestMessage, |
| 452 strlen(kHandshakeRequestMessage)); |
| 453 EXPECT_EQ(true, sent); |
| 454 MessageLoop::current()->RunAllPending(); |
| 455 EXPECT_EQ(kHandshakeRequestExpected, socket_->sent_data()); |
| 456 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); |
| 457 websocket_->OnSentData(socket_, strlen(kHandshakeRequestExpected)); |
| 458 EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent()); |
| 459 |
| 460 static const char* kHandshakeResponseMessage = |
| 461 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 462 "Upgrade: WebSocket\r\n" |
| 463 "Connection: Upgrade\r\n" |
| 464 "WebSocket-Origin: http://example.com\r\n" |
| 465 "WebSocket-Location: ws://example.com/demo\r\n" |
| 466 "WebSocket-Protocol: sample\r\n" |
| 467 "Set-Cookie: CR-set-test=1\r\n" |
| 468 "\r\n"; |
| 469 |
| 470 static const char* kHandshakeResponseExpected = |
| 471 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" |
| 472 "Upgrade: WebSocket\r\n" |
| 473 "Connection: Upgrade\r\n" |
| 474 "WebSocket-Origin: http://example.com\r\n" |
| 475 "WebSocket-Location: ws://example.com/demo\r\n" |
| 476 "WebSocket-Protocol: sample\r\n" |
| 477 "\r\n"; |
| 478 |
| 479 websocket_->OnReceivedData(socket_.get(), |
| 480 kHandshakeResponseMessage, |
| 481 strlen(kHandshakeResponseMessage)); |
| 482 MessageLoop::current()->RunAllPending(); |
| 483 EXPECT_EQ(kHandshakeResponseExpected, delegate.received_data()); |
| 484 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); |
| 485 |
| 486 EXPECT_EQ(2U, cookie_store_->entries().size()); |
| 487 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); |
| 488 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); |
| 489 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); |
| 490 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); |
| 491 |
| 492 CloseWebSocketJob(); |
| 493 } |
| 494 |
| 495 } // namespace net |
OLD | NEW |