Home | History | Annotate | Download | only in websockets
      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_stream.h"
      6 
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/run_loop.h"
     11 #include "net/base/net_errors.h"
     12 #include "net/socket/client_socket_handle.h"
     13 #include "net/socket/socket_test_util.h"
     14 #include "net/url_request/url_request_test_util.h"
     15 #include "net/websockets/websocket_basic_handshake_stream.h"
     16 #include "net/websockets/websocket_handshake_stream_create_helper.h"
     17 #include "net/websockets/websocket_test_util.h"
     18 #include "testing/gtest/include/gtest/gtest.h"
     19 #include "url/gurl.h"
     20 
     21 namespace net {
     22 namespace {
     23 
     24 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
     25 // deterministic key to use in the WebSocket handshake.
     26 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
     27     : public WebSocketHandshakeStreamCreateHelper {
     28  public:
     29   DeterministicKeyWebSocketHandshakeStreamCreateHelper(
     30       const std::vector<std::string>& requested_subprotocols)
     31       : WebSocketHandshakeStreamCreateHelper(requested_subprotocols) {}
     32 
     33   virtual WebSocketHandshakeStreamBase* CreateBasicStream(
     34       scoped_ptr<ClientSocketHandle> connection,
     35       bool using_proxy) OVERRIDE {
     36     WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection.Pass(),
     37                                                             using_proxy);
     38     // This will break in an obvious way if the type created by
     39     // CreateBasicStream() changes.
     40     static_cast<WebSocketBasicHandshakeStream*>(stream())
     41         ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
     42     return stream();
     43   }
     44 };
     45 
     46 class WebSocketStreamCreateTest : public ::testing::Test {
     47  protected:
     48   WebSocketStreamCreateTest() : websocket_error_(0) {}
     49 
     50   void CreateAndConnectCustomResponse(
     51       const std::string& socket_url,
     52       const std::string& socket_path,
     53       const std::vector<std::string>& sub_protocols,
     54       const std::string& origin,
     55       const std::string& extra_request_headers,
     56       const std::string& response_body) {
     57     url_request_context_host_.SetExpectations(
     58         WebSocketStandardRequest(socket_path, origin, extra_request_headers),
     59         response_body);
     60     CreateAndConnectStream(socket_url, sub_protocols, origin);
     61   }
     62 
     63   // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or
     64   // errors like "Unable to perform synchronous IO while stopped" will occur.
     65   void CreateAndConnectStandard(const std::string& socket_url,
     66                                 const std::string& socket_path,
     67                                 const std::vector<std::string>& sub_protocols,
     68                                 const std::string& origin,
     69                                 const std::string& extra_request_headers,
     70                                 const std::string& extra_response_headers) {
     71     CreateAndConnectCustomResponse(
     72         socket_url,
     73         socket_path,
     74         sub_protocols,
     75         origin,
     76         extra_request_headers,
     77         WebSocketStandardResponse(extra_response_headers));
     78   }
     79 
     80   void CreateAndConnectRawExpectations(
     81       const std::string& socket_url,
     82       const std::vector<std::string>& sub_protocols,
     83       const std::string& origin,
     84       scoped_ptr<DeterministicSocketData> socket_data) {
     85     url_request_context_host_.SetRawExpectations(socket_data.Pass());
     86     CreateAndConnectStream(socket_url, sub_protocols, origin);
     87   }
     88 
     89   // A wrapper for CreateAndConnectStreamForTesting that knows about our default
     90   // parameters.
     91   void CreateAndConnectStream(const std::string& socket_url,
     92                               const std::vector<std::string>& sub_protocols,
     93                               const std::string& origin) {
     94     stream_request_ = ::net::CreateAndConnectStreamForTesting(
     95         GURL(socket_url),
     96         scoped_ptr<WebSocketHandshakeStreamCreateHelper>(
     97             new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
     98                 sub_protocols)),
     99         GURL(origin),
    100         url_request_context_host_.GetURLRequestContext(),
    101         BoundNetLog(),
    102         scoped_ptr<WebSocketStream::ConnectDelegate>(
    103             new TestConnectDelegate(this)));
    104   }
    105 
    106   static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
    107 
    108   // A simple function to make the tests more readable. Creates an empty vector.
    109   static std::vector<std::string> NoSubProtocols() {
    110     return std::vector<std::string>();
    111   }
    112 
    113   uint16 error() const { return websocket_error_; }
    114 
    115   class TestConnectDelegate : public WebSocketStream::ConnectDelegate {
    116    public:
    117     TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {}
    118 
    119     virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
    120       stream.swap(owner_->stream_);
    121     }
    122 
    123     virtual void OnFailure(uint16 websocket_error) OVERRIDE {
    124       owner_->websocket_error_ = websocket_error;
    125     }
    126 
    127    private:
    128     WebSocketStreamCreateTest* owner_;
    129   };
    130 
    131   WebSocketTestURLRequestContextHost url_request_context_host_;
    132   scoped_ptr<WebSocketStreamRequest> stream_request_;
    133   // Only set if the connection succeeded.
    134   scoped_ptr<WebSocketStream> stream_;
    135   // Only set if the connection failed. 0 otherwise.
    136   uint16 websocket_error_;
    137 };
    138 
    139 // Confirm that the basic case works as expected.
    140 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
    141   CreateAndConnectStandard(
    142       "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
    143   RunUntilIdle();
    144   EXPECT_TRUE(stream_);
    145 }
    146 
    147 // Confirm that the stream isn't established until the message loop runs.
    148 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) {
    149   CreateAndConnectStandard(
    150       "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
    151   EXPECT_FALSE(stream_);
    152 }
    153 
    154 // Check the path is used.
    155 TEST_F(WebSocketStreamCreateTest, PathIsUsed) {
    156   CreateAndConnectStandard("ws://localhost/testing_path",
    157                            "/testing_path",
    158                            NoSubProtocols(),
    159                            "http://localhost/",
    160                            "",
    161                            "");
    162   RunUntilIdle();
    163   EXPECT_TRUE(stream_);
    164 }
    165 
    166 // Check that the origin is used.
    167 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) {
    168   CreateAndConnectStandard("ws://localhost/testing_path",
    169                            "/testing_path",
    170                            NoSubProtocols(),
    171                            "http://google.com/",
    172                            "",
    173                            "");
    174   RunUntilIdle();
    175   EXPECT_TRUE(stream_);
    176 }
    177 
    178 // Check that sub-protocols are sent and parsed.
    179 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) {
    180   std::vector<std::string> sub_protocols;
    181   sub_protocols.push_back("chatv11.chromium.org");
    182   sub_protocols.push_back("chatv20.chromium.org");
    183   CreateAndConnectStandard("ws://localhost/testing_path",
    184                            "/testing_path",
    185                            sub_protocols,
    186                            "http://google.com/",
    187                            "Sec-WebSocket-Protocol: chatv11.chromium.org, "
    188                            "chatv20.chromium.org\r\n",
    189                            "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
    190   RunUntilIdle();
    191   EXPECT_TRUE(stream_);
    192   EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
    193 }
    194 
    195 // Unsolicited sub-protocols are rejected.
    196 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) {
    197   CreateAndConnectStandard("ws://localhost/testing_path",
    198                            "/testing_path",
    199                            NoSubProtocols(),
    200                            "http://google.com/",
    201                            "",
    202                            "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
    203   RunUntilIdle();
    204   EXPECT_FALSE(stream_);
    205   EXPECT_EQ(1006, error());
    206 }
    207 
    208 // Missing sub-protocol response is rejected.
    209 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) {
    210   CreateAndConnectStandard("ws://localhost/testing_path",
    211                            "/testing_path",
    212                            std::vector<std::string>(1, "chat.example.com"),
    213                            "http://localhost/",
    214                            "Sec-WebSocket-Protocol: chat.example.com\r\n",
    215                            "");
    216   RunUntilIdle();
    217   EXPECT_FALSE(stream_);
    218   EXPECT_EQ(1006, error());
    219 }
    220 
    221 // Only one sub-protocol can be accepted.
    222 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) {
    223   std::vector<std::string> sub_protocols;
    224   sub_protocols.push_back("chatv11.chromium.org");
    225   sub_protocols.push_back("chatv20.chromium.org");
    226   CreateAndConnectStandard("ws://localhost/testing_path",
    227                            "/testing_path",
    228                            sub_protocols,
    229                            "http://google.com/",
    230                            "Sec-WebSocket-Protocol: chatv11.chromium.org, "
    231                            "chatv20.chromium.org\r\n",
    232                            "Sec-WebSocket-Protocol: chatv11.chromium.org, "
    233                            "chatv20.chromium.org\r\n");
    234   RunUntilIdle();
    235   EXPECT_FALSE(stream_);
    236   EXPECT_EQ(1006, error());
    237 }
    238 
    239 // Unknown extension in the response is rejected
    240 TEST_F(WebSocketStreamCreateTest, UnknownExtension) {
    241   CreateAndConnectStandard("ws://localhost/testing_path",
    242                            "/testing_path",
    243                            NoSubProtocols(),
    244                            "http://localhost/",
    245                            "",
    246                            "Sec-WebSocket-Extensions: x-unknown-extension\r\n");
    247   RunUntilIdle();
    248   EXPECT_FALSE(stream_);
    249   EXPECT_EQ(1006, error());
    250 }
    251 
    252 // Additional Sec-WebSocket-Accept headers should be rejected.
    253 TEST_F(WebSocketStreamCreateTest, DoubleAccept) {
    254   CreateAndConnectStandard(
    255       "ws://localhost/",
    256       "/",
    257       NoSubProtocols(),
    258       "http://localhost/",
    259       "",
    260       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
    261   RunUntilIdle();
    262   EXPECT_FALSE(stream_);
    263   EXPECT_EQ(1006, error());
    264 }
    265 
    266 // Response code 200 must be rejected.
    267 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) {
    268   static const char kInvalidStatusCodeResponse[] =
    269       "HTTP/1.1 200 OK\r\n"
    270       "Upgrade: websocket\r\n"
    271       "Connection: Upgrade\r\n"
    272       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
    273       "\r\n";
    274   CreateAndConnectCustomResponse("ws://localhost/",
    275                                  "/",
    276                                  NoSubProtocols(),
    277                                  "http://localhost/",
    278                                  "",
    279                                  kInvalidStatusCodeResponse);
    280   RunUntilIdle();
    281   EXPECT_EQ(1006, error());
    282 }
    283 
    284 // Redirects are not followed (according to the WHATWG WebSocket API, which
    285 // overrides RFC6455 for browser applications).
    286 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) {
    287   static const char kRedirectResponse[] =
    288       "HTTP/1.1 302 Moved Temporarily\r\n"
    289       "Content-Type: text/html\r\n"
    290       "Content-Length: 34\r\n"
    291       "Connection: keep-alive\r\n"
    292       "Location: ws://localhost/other\r\n"
    293       "\r\n"
    294       "<title>Moved</title><h1>Moved</h1>";
    295   CreateAndConnectCustomResponse("ws://localhost/",
    296                                  "/",
    297                                  NoSubProtocols(),
    298                                  "http://localhost/",
    299                                  "",
    300                                  kRedirectResponse);
    301   RunUntilIdle();
    302   EXPECT_EQ(1006, error());
    303 }
    304 
    305 // Malformed responses should be rejected. HttpStreamParser will accept just
    306 // about any garbage in the middle of the headers. To make it give up, the junk
    307 // has to be at the start of the response. Even then, it just gets treated as an
    308 // HTTP/0.9 response.
    309 TEST_F(WebSocketStreamCreateTest, MalformedResponse) {
    310   static const char kMalformedResponse[] =
    311       "220 mx.google.com ESMTP\r\n"
    312       "HTTP/1.1 101 OK\r\n"
    313       "Upgrade: websocket\r\n"
    314       "Connection: Upgrade\r\n"
    315       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
    316       "\r\n";
    317   CreateAndConnectCustomResponse("ws://localhost/",
    318                                  "/",
    319                                  NoSubProtocols(),
    320                                  "http://localhost/",
    321                                  "",
    322                                  kMalformedResponse);
    323   RunUntilIdle();
    324   EXPECT_EQ(1006, error());
    325 }
    326 
    327 // Upgrade header must be present.
    328 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) {
    329   static const char kMissingUpgradeResponse[] =
    330       "HTTP/1.1 101 Switching Protocols\r\n"
    331       "Connection: Upgrade\r\n"
    332       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
    333       "\r\n";
    334   CreateAndConnectCustomResponse("ws://localhost/",
    335                                  "/",
    336                                  NoSubProtocols(),
    337                                  "http://localhost/",
    338                                  "",
    339                                  kMissingUpgradeResponse);
    340   RunUntilIdle();
    341   EXPECT_EQ(1006, error());
    342 }
    343 
    344 // There must only be one upgrade header.
    345 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
    346   CreateAndConnectStandard(
    347       "ws://localhost/",
    348       "/",
    349       NoSubProtocols(),
    350       "http://localhost/",
    351       "", "Upgrade: HTTP/2.0\r\n");
    352   RunUntilIdle();
    353   EXPECT_EQ(1006, error());
    354 }
    355 
    356 // Connection header must be present.
    357 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) {
    358   static const char kMissingConnectionResponse[] =
    359       "HTTP/1.1 101 Switching Protocols\r\n"
    360       "Upgrade: websocket\r\n"
    361       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
    362       "\r\n";
    363   CreateAndConnectCustomResponse("ws://localhost/",
    364                                  "/",
    365                                  NoSubProtocols(),
    366                                  "http://localhost/",
    367                                  "",
    368                                  kMissingConnectionResponse);
    369   RunUntilIdle();
    370   EXPECT_EQ(1006, error());
    371 }
    372 
    373 // Connection header is permitted to contain other tokens.
    374 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
    375   static const char kAdditionalConnectionTokenResponse[] =
    376       "HTTP/1.1 101 Switching Protocols\r\n"
    377       "Upgrade: websocket\r\n"
    378       "Connection: Upgrade, Keep-Alive\r\n"
    379       "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
    380       "\r\n";
    381   CreateAndConnectCustomResponse("ws://localhost/",
    382                                  "/",
    383                                  NoSubProtocols(),
    384                                  "http://localhost/",
    385                                  "",
    386                                  kAdditionalConnectionTokenResponse);
    387   RunUntilIdle();
    388   EXPECT_TRUE(stream_);
    389 }
    390 
    391 // Sec-WebSocket-Accept header must be present.
    392 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
    393   static const char kMissingAcceptResponse[] =
    394       "HTTP/1.1 101 Switching Protocols\r\n"
    395       "Upgrade: websocket\r\n"
    396       "Connection: Upgrade\r\n"
    397       "\r\n";
    398   CreateAndConnectCustomResponse("ws://localhost/",
    399                                  "/",
    400                                  NoSubProtocols(),
    401                                  "http://localhost/",
    402                                  "",
    403                                  kMissingAcceptResponse);
    404   RunUntilIdle();
    405   EXPECT_EQ(1006, error());
    406 }
    407 
    408 // Sec-WebSocket-Accept header must match the key that was sent.
    409 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
    410   static const char kIncorrectAcceptResponse[] =
    411       "HTTP/1.1 101 Switching Protocols\r\n"
    412       "Upgrade: websocket\r\n"
    413       "Connection: Upgrade\r\n"
    414       "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
    415       "\r\n";
    416   CreateAndConnectCustomResponse("ws://localhost/",
    417                                  "/",
    418                                  NoSubProtocols(),
    419                                  "http://localhost/",
    420                                  "",
    421                                  kIncorrectAcceptResponse);
    422   RunUntilIdle();
    423   EXPECT_EQ(1006, error());
    424 }
    425 
    426 // Cancellation works.
    427 TEST_F(WebSocketStreamCreateTest, Cancellation) {
    428   CreateAndConnectStandard(
    429       "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
    430   stream_request_.reset();
    431   RunUntilIdle();
    432   EXPECT_FALSE(stream_);
    433 }
    434 
    435 // Connect failure must look just like negotiation failure.
    436 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) {
    437   scoped_ptr<DeterministicSocketData> socket_data(
    438       new DeterministicSocketData(NULL, 0, NULL, 0));
    439   socket_data->set_connect_data(
    440       MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
    441   CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
    442                                   "http://localhost/", socket_data.Pass());
    443   RunUntilIdle();
    444   EXPECT_EQ(1006, error());
    445 }
    446 
    447 // Connect timeout must look just like any other failure.
    448 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
    449   scoped_ptr<DeterministicSocketData> socket_data(
    450       new DeterministicSocketData(NULL, 0, NULL, 0));
    451   socket_data->set_connect_data(
    452       MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
    453   CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
    454                                   "http://localhost/", socket_data.Pass());
    455   RunUntilIdle();
    456   EXPECT_EQ(1006, error());
    457 }
    458 
    459 // Cancellation during connect works.
    460 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) {
    461   scoped_ptr<DeterministicSocketData> socket_data(
    462       new DeterministicSocketData(NULL, 0, NULL, 0));
    463   socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
    464   CreateAndConnectRawExpectations("ws://localhost/",
    465                                   NoSubProtocols(),
    466                                   "http://localhost/",
    467                                   socket_data.Pass());
    468   stream_request_.reset();
    469   RunUntilIdle();
    470   EXPECT_FALSE(stream_);
    471 }
    472 
    473 // Cancellation during write of the request headers works.
    474 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) {
    475   // We seem to need at least two operations in order to use SetStop().
    476   MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"),
    477                         MockWrite(ASYNC, 1, "1.1\r\n")};
    478   // We keep a copy of the pointer so that we can call RunFor() on it later.
    479   DeterministicSocketData* socket_data(
    480       new DeterministicSocketData(NULL, 0, writes, arraysize(writes)));
    481   socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
    482   socket_data->SetStop(1);
    483   CreateAndConnectRawExpectations("ws://localhost/",
    484                                   NoSubProtocols(),
    485                                   "http://localhost/",
    486                                   make_scoped_ptr(socket_data));
    487   socket_data->Run();
    488   stream_request_.reset();
    489   RunUntilIdle();
    490   EXPECT_FALSE(stream_);
    491 }
    492 
    493 // Cancellation during read of the response headers works.
    494 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) {
    495   std::string request = WebSocketStandardRequest("/", "http://localhost/", "");
    496   MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
    497   MockRead reads[] = {
    498     MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
    499   };
    500   DeterministicSocketData* socket_data(new DeterministicSocketData(
    501       reads, arraysize(reads), writes, arraysize(writes)));
    502   socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
    503   socket_data->SetStop(1);
    504   CreateAndConnectRawExpectations("ws://localhost/",
    505                                   NoSubProtocols(),
    506                                   "http://localhost/",
    507                                   make_scoped_ptr(socket_data));
    508   socket_data->Run();
    509   stream_request_.reset();
    510   RunUntilIdle();
    511   EXPECT_FALSE(stream_);
    512 }
    513 
    514 }  // namespace
    515 }  // namespace net
    516