| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/websockets/websocket_stream.h" | 5 #include "net/websockets/websocket_stream.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/run_loop.h" | 10 #include "base/run_loop.h" |
| (...skipping 27 matching lines...) Expand all Loading... |
| 38 // This will break in an obvious way if the type created by | 38 // This will break in an obvious way if the type created by |
| 39 // CreateBasicStream() changes. | 39 // CreateBasicStream() changes. |
| 40 static_cast<WebSocketBasicHandshakeStream*>(stream()) | 40 static_cast<WebSocketBasicHandshakeStream*>(stream()) |
| 41 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ=="); | 41 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ=="); |
| 42 return stream(); | 42 return stream(); |
| 43 } | 43 } |
| 44 }; | 44 }; |
| 45 | 45 |
| 46 class WebSocketStreamCreateTest : public ::testing::Test { | 46 class WebSocketStreamCreateTest : public ::testing::Test { |
| 47 protected: | 47 protected: |
| 48 WebSocketStreamCreateTest() : websocket_error_(0) {} | 48 WebSocketStreamCreateTest(): has_failed_(false) {} |
| 49 | 49 |
| 50 void CreateAndConnectCustomResponse( | 50 void CreateAndConnectCustomResponse( |
| 51 const std::string& socket_url, | 51 const std::string& socket_url, |
| 52 const std::string& socket_path, | 52 const std::string& socket_path, |
| 53 const std::vector<std::string>& sub_protocols, | 53 const std::vector<std::string>& sub_protocols, |
| 54 const std::string& origin, | 54 const std::string& origin, |
| 55 const std::string& extra_request_headers, | 55 const std::string& extra_request_headers, |
| 56 const std::string& response_body) { | 56 const std::string& response_body) { |
| 57 url_request_context_host_.SetExpectations( | 57 url_request_context_host_.SetExpectations( |
| 58 WebSocketStandardRequest(socket_path, origin, extra_request_headers), | 58 WebSocketStandardRequest(socket_path, origin, extra_request_headers), |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 103 new TestConnectDelegate(this))); | 103 new TestConnectDelegate(this))); |
| 104 } | 104 } |
| 105 | 105 |
| 106 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } | 106 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } |
| 107 | 107 |
| 108 // A simple function to make the tests more readable. Creates an empty vector. | 108 // A simple function to make the tests more readable. Creates an empty vector. |
| 109 static std::vector<std::string> NoSubProtocols() { | 109 static std::vector<std::string> NoSubProtocols() { |
| 110 return std::vector<std::string>(); | 110 return std::vector<std::string>(); |
| 111 } | 111 } |
| 112 | 112 |
| 113 uint16 error() const { return websocket_error_; } | 113 const std::string& failure_message() const { return failure_message_; } |
| 114 bool has_failed() const { return has_failed_; } |
| 114 | 115 |
| 115 class TestConnectDelegate : public WebSocketStream::ConnectDelegate { | 116 class TestConnectDelegate : public WebSocketStream::ConnectDelegate { |
| 116 public: | 117 public: |
| 117 TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {} | 118 explicit TestConnectDelegate(WebSocketStreamCreateTest* owner) |
| 119 : owner_(owner) {} |
| 118 | 120 |
| 119 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE { | 121 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE { |
| 120 stream.swap(owner_->stream_); | 122 stream.swap(owner_->stream_); |
| 121 } | 123 } |
| 122 | 124 |
| 123 virtual void OnFailure(uint16 websocket_error) OVERRIDE { | 125 virtual void OnFailure(const std::string& message) OVERRIDE { |
| 124 owner_->websocket_error_ = websocket_error; | 126 owner_->has_failed_ = true; |
| 127 owner_->failure_message_ = message; |
| 125 } | 128 } |
| 126 | 129 |
| 127 private: | 130 private: |
| 128 WebSocketStreamCreateTest* owner_; | 131 WebSocketStreamCreateTest* owner_; |
| 129 }; | 132 }; |
| 130 | 133 |
| 131 WebSocketTestURLRequestContextHost url_request_context_host_; | 134 WebSocketTestURLRequestContextHost url_request_context_host_; |
| 132 scoped_ptr<WebSocketStreamRequest> stream_request_; | 135 scoped_ptr<WebSocketStreamRequest> stream_request_; |
| 133 // Only set if the connection succeeded. | 136 // Only set if the connection succeeded. |
| 134 scoped_ptr<WebSocketStream> stream_; | 137 scoped_ptr<WebSocketStream> stream_; |
| 135 // Only set if the connection failed. 0 otherwise. | 138 // Only set if the connection failed. |
| 136 uint16 websocket_error_; | 139 std::string failure_message_; |
| 140 bool has_failed_; |
| 137 }; | 141 }; |
| 138 | 142 |
| 139 // Confirm that the basic case works as expected. | 143 // Confirm that the basic case works as expected. |
| 140 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { | 144 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { |
| 141 CreateAndConnectStandard( | 145 CreateAndConnectStandard( |
| 142 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); | 146 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); |
| 143 RunUntilIdle(); | 147 RunUntilIdle(); |
| 148 EXPECT_FALSE(has_failed()); |
| 144 EXPECT_TRUE(stream_); | 149 EXPECT_TRUE(stream_); |
| 145 } | 150 } |
| 146 | 151 |
| 147 // Confirm that the stream isn't established until the message loop runs. | 152 // Confirm that the stream isn't established until the message loop runs. |
| 148 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { | 153 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { |
| 149 CreateAndConnectStandard( | 154 CreateAndConnectStandard( |
| 150 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); | 155 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); |
| 156 EXPECT_FALSE(has_failed()); |
| 151 EXPECT_FALSE(stream_); | 157 EXPECT_FALSE(stream_); |
| 152 } | 158 } |
| 153 | 159 |
| 154 // Check the path is used. | 160 // Check the path is used. |
| 155 TEST_F(WebSocketStreamCreateTest, PathIsUsed) { | 161 TEST_F(WebSocketStreamCreateTest, PathIsUsed) { |
| 156 CreateAndConnectStandard("ws://localhost/testing_path", | 162 CreateAndConnectStandard("ws://localhost/testing_path", |
| 157 "/testing_path", | 163 "/testing_path", |
| 158 NoSubProtocols(), | 164 NoSubProtocols(), |
| 159 "http://localhost/", | 165 "http://localhost/", |
| 160 "", | 166 "", |
| 161 ""); | 167 ""); |
| 162 RunUntilIdle(); | 168 RunUntilIdle(); |
| 169 EXPECT_FALSE(has_failed()); |
| 163 EXPECT_TRUE(stream_); | 170 EXPECT_TRUE(stream_); |
| 164 } | 171 } |
| 165 | 172 |
| 166 // Check that the origin is used. | 173 // Check that the origin is used. |
| 167 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { | 174 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { |
| 168 CreateAndConnectStandard("ws://localhost/testing_path", | 175 CreateAndConnectStandard("ws://localhost/testing_path", |
| 169 "/testing_path", | 176 "/testing_path", |
| 170 NoSubProtocols(), | 177 NoSubProtocols(), |
| 171 "http://google.com/", | 178 "http://google.com/", |
| 172 "", | 179 "", |
| 173 ""); | 180 ""); |
| 174 RunUntilIdle(); | 181 RunUntilIdle(); |
| 182 EXPECT_FALSE(has_failed()); |
| 175 EXPECT_TRUE(stream_); | 183 EXPECT_TRUE(stream_); |
| 176 } | 184 } |
| 177 | 185 |
| 178 // Check that sub-protocols are sent and parsed. | 186 // Check that sub-protocols are sent and parsed. |
| 179 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { | 187 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { |
| 180 std::vector<std::string> sub_protocols; | 188 std::vector<std::string> sub_protocols; |
| 181 sub_protocols.push_back("chatv11.chromium.org"); | 189 sub_protocols.push_back("chatv11.chromium.org"); |
| 182 sub_protocols.push_back("chatv20.chromium.org"); | 190 sub_protocols.push_back("chatv20.chromium.org"); |
| 183 CreateAndConnectStandard("ws://localhost/testing_path", | 191 CreateAndConnectStandard("ws://localhost/testing_path", |
| 184 "/testing_path", | 192 "/testing_path", |
| 185 sub_protocols, | 193 sub_protocols, |
| 186 "http://google.com/", | 194 "http://google.com/", |
| 187 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | 195 "Sec-WebSocket-Protocol: chatv11.chromium.org, " |
| 188 "chatv20.chromium.org\r\n", | 196 "chatv20.chromium.org\r\n", |
| 189 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | 197 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); |
| 190 RunUntilIdle(); | 198 RunUntilIdle(); |
| 191 EXPECT_TRUE(stream_); | 199 EXPECT_TRUE(stream_); |
| 200 EXPECT_FALSE(has_failed()); |
| 192 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); | 201 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); |
| 193 } | 202 } |
| 194 | 203 |
| 195 // Unsolicited sub-protocols are rejected. | 204 // Unsolicited sub-protocols are rejected. |
| 196 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { | 205 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { |
| 197 CreateAndConnectStandard("ws://localhost/testing_path", | 206 CreateAndConnectStandard("ws://localhost/testing_path", |
| 198 "/testing_path", | 207 "/testing_path", |
| 199 NoSubProtocols(), | 208 NoSubProtocols(), |
| 200 "http://google.com/", | 209 "http://google.com/", |
| 201 "", | 210 "", |
| 202 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | 211 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); |
| 203 RunUntilIdle(); | 212 RunUntilIdle(); |
| 204 EXPECT_FALSE(stream_); | 213 EXPECT_FALSE(stream_); |
| 205 EXPECT_EQ(1006, error()); | 214 EXPECT_TRUE(has_failed()); |
| 215 EXPECT_EQ("Error during WebSocket handshake: " |
| 216 "Response must not include 'Sec-WebSocket-Protocol' header " |
| 217 "if not present in request: chatv20.chromium.org", |
| 218 failure_message()); |
| 206 } | 219 } |
| 207 | 220 |
| 208 // Missing sub-protocol response is rejected. | 221 // Missing sub-protocol response is rejected. |
| 209 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) { | 222 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) { |
| 223 std::vector<std::string> sub_protocols; |
| 224 sub_protocols.push_back("chat.example.com"); |
| 210 CreateAndConnectStandard("ws://localhost/testing_path", | 225 CreateAndConnectStandard("ws://localhost/testing_path", |
| 211 "/testing_path", | 226 "/testing_path", |
| 212 std::vector<std::string>(1, "chat.example.com"), | 227 sub_protocols, |
| 213 "http://localhost/", | 228 "http://localhost/", |
| 214 "Sec-WebSocket-Protocol: chat.example.com\r\n", | 229 "Sec-WebSocket-Protocol: chat.example.com\r\n", |
| 215 ""); | 230 ""); |
| 216 RunUntilIdle(); | 231 RunUntilIdle(); |
| 217 EXPECT_FALSE(stream_); | 232 EXPECT_FALSE(stream_); |
| 218 EXPECT_EQ(1006, error()); | 233 EXPECT_TRUE(has_failed()); |
| 234 EXPECT_EQ("Error during WebSocket handshake: " |
| 235 "Sent non-empty 'Sec-WebSocket-Protocol' header " |
| 236 "but no response was received", |
| 237 failure_message()); |
| 219 } | 238 } |
| 220 | 239 |
| 221 // Only one sub-protocol can be accepted. | 240 // Only one sub-protocol can be accepted. |
| 222 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { | 241 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { |
| 223 std::vector<std::string> sub_protocols; | 242 std::vector<std::string> sub_protocols; |
| 224 sub_protocols.push_back("chatv11.chromium.org"); | 243 sub_protocols.push_back("chatv11.chromium.org"); |
| 225 sub_protocols.push_back("chatv20.chromium.org"); | 244 sub_protocols.push_back("chatv20.chromium.org"); |
| 226 CreateAndConnectStandard("ws://localhost/testing_path", | 245 CreateAndConnectStandard("ws://localhost/testing_path", |
| 227 "/testing_path", | 246 "/testing_path", |
| 228 sub_protocols, | 247 sub_protocols, |
| 229 "http://google.com/", | 248 "http://google.com/", |
| 230 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | 249 "Sec-WebSocket-Protocol: chatv11.chromium.org, " |
| 231 "chatv20.chromium.org\r\n", | 250 "chatv20.chromium.org\r\n", |
| 232 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | 251 "Sec-WebSocket-Protocol: chatv11.chromium.org, " |
| 233 "chatv20.chromium.org\r\n"); | 252 "chatv20.chromium.org\r\n"); |
| 234 RunUntilIdle(); | 253 RunUntilIdle(); |
| 235 EXPECT_FALSE(stream_); | 254 EXPECT_FALSE(stream_); |
| 236 EXPECT_EQ(1006, error()); | 255 EXPECT_TRUE(has_failed()); |
| 256 EXPECT_EQ("Error during WebSocket handshake: " |
| 257 "'Sec-WebSocket-Protocol' header must not appear " |
| 258 "more than once in a response", |
| 259 failure_message()); |
| 260 } |
| 261 |
| 262 // Unmatched sub-protocol should be rejected. |
| 263 TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) { |
| 264 std::vector<std::string> sub_protocols; |
| 265 sub_protocols.push_back("chatv11.chromium.org"); |
| 266 sub_protocols.push_back("chatv20.chromium.org"); |
| 267 CreateAndConnectStandard("ws://localhost/testing_path", |
| 268 "/testing_path", |
| 269 sub_protocols, |
| 270 "http://google.com/", |
| 271 "Sec-WebSocket-Protocol: chatv11.chromium.org, " |
| 272 "chatv20.chromium.org\r\n", |
| 273 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); |
| 274 RunUntilIdle(); |
| 275 EXPECT_FALSE(stream_); |
| 276 EXPECT_TRUE(has_failed()); |
| 277 EXPECT_EQ("Error during WebSocket handshake: " |
| 278 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " |
| 279 "in response does not match any of sent values", |
| 280 failure_message()); |
| 237 } | 281 } |
| 238 | 282 |
| 239 // Unknown extension in the response is rejected | 283 // Unknown extension in the response is rejected |
| 240 TEST_F(WebSocketStreamCreateTest, UnknownExtension) { | 284 TEST_F(WebSocketStreamCreateTest, UnknownExtension) { |
| 241 CreateAndConnectStandard("ws://localhost/testing_path", | 285 CreateAndConnectStandard("ws://localhost/testing_path", |
| 242 "/testing_path", | 286 "/testing_path", |
| 243 NoSubProtocols(), | 287 NoSubProtocols(), |
| 244 "http://localhost/", | 288 "http://localhost/", |
| 245 "", | 289 "", |
| 246 "Sec-WebSocket-Extensions: x-unknown-extension\r\n"); | 290 "Sec-WebSocket-Extensions: x-unknown-extension\r\n"); |
| 247 RunUntilIdle(); | 291 RunUntilIdle(); |
| 248 EXPECT_FALSE(stream_); | 292 EXPECT_FALSE(stream_); |
| 249 EXPECT_EQ(1006, error()); | 293 EXPECT_TRUE(has_failed()); |
| 294 EXPECT_EQ("Error during WebSocket handshake: " |
| 295 "Found an unsupported extension 'x-unknown-extension' " |
| 296 "in 'Sec-WebSocket-Extensions' header", |
| 297 failure_message()); |
| 250 } | 298 } |
| 251 | 299 |
| 252 // Additional Sec-WebSocket-Accept headers should be rejected. | 300 // Additional Sec-WebSocket-Accept headers should be rejected. |
| 253 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { | 301 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { |
| 254 CreateAndConnectStandard( | 302 CreateAndConnectStandard( |
| 255 "ws://localhost/", | 303 "ws://localhost/", |
| 256 "/", | 304 "/", |
| 257 NoSubProtocols(), | 305 NoSubProtocols(), |
| 258 "http://localhost/", | 306 "http://localhost/", |
| 259 "", | 307 "", |
| 260 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); | 308 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); |
| 261 RunUntilIdle(); | 309 RunUntilIdle(); |
| 262 EXPECT_FALSE(stream_); | 310 EXPECT_FALSE(stream_); |
| 263 EXPECT_EQ(1006, error()); | 311 EXPECT_TRUE(has_failed()); |
| 312 EXPECT_EQ("Error during WebSocket handshake: " |
| 313 "'Sec-WebSocket-Accept' header must not appear " |
| 314 "more than once in a response", |
| 315 failure_message()); |
| 264 } | 316 } |
| 265 | 317 |
| 266 // Response code 200 must be rejected. | 318 // Response code 200 must be rejected. |
| 267 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { | 319 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { |
| 268 static const char kInvalidStatusCodeResponse[] = | 320 static const char kInvalidStatusCodeResponse[] = |
| 269 "HTTP/1.1 200 OK\r\n" | 321 "HTTP/1.1 200 OK\r\n" |
| 270 "Upgrade: websocket\r\n" | 322 "Upgrade: websocket\r\n" |
| 271 "Connection: Upgrade\r\n" | 323 "Connection: Upgrade\r\n" |
| 272 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 324 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 273 "\r\n"; | 325 "\r\n"; |
| 274 CreateAndConnectCustomResponse("ws://localhost/", | 326 CreateAndConnectCustomResponse("ws://localhost/", |
| 275 "/", | 327 "/", |
| 276 NoSubProtocols(), | 328 NoSubProtocols(), |
| 277 "http://localhost/", | 329 "http://localhost/", |
| 278 "", | 330 "", |
| 279 kInvalidStatusCodeResponse); | 331 kInvalidStatusCodeResponse); |
| 280 RunUntilIdle(); | 332 RunUntilIdle(); |
| 281 EXPECT_EQ(1006, error()); | 333 EXPECT_TRUE(has_failed()); |
| 334 EXPECT_EQ("Unexpected status code: 200", failure_message()); |
| 282 } | 335 } |
| 283 | 336 |
| 284 // Redirects are not followed (according to the WHATWG WebSocket API, which | 337 // Redirects are not followed (according to the WHATWG WebSocket API, which |
| 285 // overrides RFC6455 for browser applications). | 338 // overrides RFC6455 for browser applications). |
| 286 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { | 339 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { |
| 287 static const char kRedirectResponse[] = | 340 static const char kRedirectResponse[] = |
| 288 "HTTP/1.1 302 Moved Temporarily\r\n" | 341 "HTTP/1.1 302 Moved Temporarily\r\n" |
| 289 "Content-Type: text/html\r\n" | 342 "Content-Type: text/html\r\n" |
| 290 "Content-Length: 34\r\n" | 343 "Content-Length: 34\r\n" |
| 291 "Connection: keep-alive\r\n" | 344 "Connection: keep-alive\r\n" |
| 292 "Location: ws://localhost/other\r\n" | 345 "Location: ws://localhost/other\r\n" |
| 293 "\r\n" | 346 "\r\n" |
| 294 "<title>Moved</title><h1>Moved</h1>"; | 347 "<title>Moved</title><h1>Moved</h1>"; |
| 295 CreateAndConnectCustomResponse("ws://localhost/", | 348 CreateAndConnectCustomResponse("ws://localhost/", |
| 296 "/", | 349 "/", |
| 297 NoSubProtocols(), | 350 NoSubProtocols(), |
| 298 "http://localhost/", | 351 "http://localhost/", |
| 299 "", | 352 "", |
| 300 kRedirectResponse); | 353 kRedirectResponse); |
| 301 RunUntilIdle(); | 354 RunUntilIdle(); |
| 302 EXPECT_EQ(1006, error()); | 355 EXPECT_TRUE(has_failed()); |
| 356 EXPECT_EQ("Unexpected status code: 302", failure_message()); |
| 303 } | 357 } |
| 304 | 358 |
| 305 // Malformed responses should be rejected. HttpStreamParser will accept just | 359 // 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 | 360 // 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 | 361 // has to be at the start of the response. Even then, it just gets treated as an |
| 308 // HTTP/0.9 response. | 362 // HTTP/0.9 response. |
| 309 TEST_F(WebSocketStreamCreateTest, MalformedResponse) { | 363 TEST_F(WebSocketStreamCreateTest, MalformedResponse) { |
| 310 static const char kMalformedResponse[] = | 364 static const char kMalformedResponse[] = |
| 311 "220 mx.google.com ESMTP\r\n" | 365 "220 mx.google.com ESMTP\r\n" |
| 312 "HTTP/1.1 101 OK\r\n" | 366 "HTTP/1.1 101 OK\r\n" |
| 313 "Upgrade: websocket\r\n" | 367 "Upgrade: websocket\r\n" |
| 314 "Connection: Upgrade\r\n" | 368 "Connection: Upgrade\r\n" |
| 315 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 369 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 316 "\r\n"; | 370 "\r\n"; |
| 317 CreateAndConnectCustomResponse("ws://localhost/", | 371 CreateAndConnectCustomResponse("ws://localhost/", |
| 318 "/", | 372 "/", |
| 319 NoSubProtocols(), | 373 NoSubProtocols(), |
| 320 "http://localhost/", | 374 "http://localhost/", |
| 321 "", | 375 "", |
| 322 kMalformedResponse); | 376 kMalformedResponse); |
| 323 RunUntilIdle(); | 377 RunUntilIdle(); |
| 324 EXPECT_EQ(1006, error()); | 378 EXPECT_TRUE(has_failed()); |
| 379 EXPECT_EQ("Unexpected status code: 200", failure_message()); |
| 325 } | 380 } |
| 326 | 381 |
| 327 // Upgrade header must be present. | 382 // Upgrade header must be present. |
| 328 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { | 383 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { |
| 329 static const char kMissingUpgradeResponse[] = | 384 static const char kMissingUpgradeResponse[] = |
| 330 "HTTP/1.1 101 Switching Protocols\r\n" | 385 "HTTP/1.1 101 Switching Protocols\r\n" |
| 331 "Connection: Upgrade\r\n" | 386 "Connection: Upgrade\r\n" |
| 332 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 387 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 333 "\r\n"; | 388 "\r\n"; |
| 334 CreateAndConnectCustomResponse("ws://localhost/", | 389 CreateAndConnectCustomResponse("ws://localhost/", |
| 335 "/", | 390 "/", |
| 336 NoSubProtocols(), | 391 NoSubProtocols(), |
| 337 "http://localhost/", | 392 "http://localhost/", |
| 338 "", | 393 "", |
| 339 kMissingUpgradeResponse); | 394 kMissingUpgradeResponse); |
| 340 RunUntilIdle(); | 395 RunUntilIdle(); |
| 341 EXPECT_EQ(1006, error()); | 396 EXPECT_TRUE(has_failed()); |
| 397 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", |
| 398 failure_message()); |
| 342 } | 399 } |
| 343 | 400 |
| 344 // There must only be one upgrade header. | 401 // There must only be one upgrade header. |
| 345 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { | 402 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { |
| 346 CreateAndConnectStandard( | 403 CreateAndConnectStandard( |
| 347 "ws://localhost/", | 404 "ws://localhost/", |
| 348 "/", | 405 "/", |
| 349 NoSubProtocols(), | 406 NoSubProtocols(), |
| 350 "http://localhost/", | 407 "http://localhost/", |
| 351 "", "Upgrade: HTTP/2.0\r\n"); | 408 "", "Upgrade: HTTP/2.0\r\n"); |
| 352 RunUntilIdle(); | 409 RunUntilIdle(); |
| 353 EXPECT_EQ(1006, error()); | 410 EXPECT_TRUE(has_failed()); |
| 411 EXPECT_EQ("Error during WebSocket handshake: " |
| 412 "'Upgrade' header must not appear more than once in a response", |
| 413 failure_message()); |
| 414 } |
| 415 |
| 416 // There must only be one correct upgrade header. |
| 417 TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) { |
| 418 static const char kMissingUpgradeResponse[] = |
| 419 "HTTP/1.1 101 Switching Protocols\r\n" |
| 420 "Connection: Upgrade\r\n" |
| 421 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 422 "Upgrade: hogefuga\r\n" |
| 423 "\r\n"; |
| 424 CreateAndConnectCustomResponse("ws://localhost/", |
| 425 "/", |
| 426 NoSubProtocols(), |
| 427 "http://localhost/", |
| 428 "", |
| 429 kMissingUpgradeResponse); |
| 430 RunUntilIdle(); |
| 431 EXPECT_TRUE(has_failed()); |
| 432 EXPECT_EQ("Error during WebSocket handshake: " |
| 433 "'Upgrade' header value is not 'WebSocket': hogefuga", |
| 434 failure_message()); |
| 354 } | 435 } |
| 355 | 436 |
| 356 // Connection header must be present. | 437 // Connection header must be present. |
| 357 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { | 438 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { |
| 358 static const char kMissingConnectionResponse[] = | 439 static const char kMissingConnectionResponse[] = |
| 359 "HTTP/1.1 101 Switching Protocols\r\n" | 440 "HTTP/1.1 101 Switching Protocols\r\n" |
| 360 "Upgrade: websocket\r\n" | 441 "Upgrade: websocket\r\n" |
| 361 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 442 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 362 "\r\n"; | 443 "\r\n"; |
| 363 CreateAndConnectCustomResponse("ws://localhost/", | 444 CreateAndConnectCustomResponse("ws://localhost/", |
| 364 "/", | 445 "/", |
| 365 NoSubProtocols(), | 446 NoSubProtocols(), |
| 366 "http://localhost/", | 447 "http://localhost/", |
| 367 "", | 448 "", |
| 368 kMissingConnectionResponse); | 449 kMissingConnectionResponse); |
| 369 RunUntilIdle(); | 450 RunUntilIdle(); |
| 370 EXPECT_EQ(1006, error()); | 451 EXPECT_TRUE(has_failed()); |
| 452 EXPECT_EQ("Error during WebSocket handshake: " |
| 453 "'Connection' header is missing", |
| 454 failure_message()); |
| 455 } |
| 456 |
| 457 // Connection header must contain "Upgrade". |
| 458 TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) { |
| 459 static const char kMissingConnectionResponse[] = |
| 460 "HTTP/1.1 101 Switching Protocols\r\n" |
| 461 "Upgrade: websocket\r\n" |
| 462 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 463 "Connection: hogefuga\r\n" |
| 464 "\r\n"; |
| 465 CreateAndConnectCustomResponse("ws://localhost/", |
| 466 "/", |
| 467 NoSubProtocols(), |
| 468 "http://localhost/", |
| 469 "", |
| 470 kMissingConnectionResponse); |
| 471 RunUntilIdle(); |
| 472 EXPECT_TRUE(has_failed()); |
| 473 EXPECT_EQ("Error during WebSocket handshake: " |
| 474 "'Connection' header value must contain 'Upgrade'", |
| 475 failure_message()); |
| 371 } | 476 } |
| 372 | 477 |
| 373 // Connection header is permitted to contain other tokens. | 478 // Connection header is permitted to contain other tokens. |
| 374 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { | 479 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { |
| 375 static const char kAdditionalConnectionTokenResponse[] = | 480 static const char kAdditionalConnectionTokenResponse[] = |
| 376 "HTTP/1.1 101 Switching Protocols\r\n" | 481 "HTTP/1.1 101 Switching Protocols\r\n" |
| 377 "Upgrade: websocket\r\n" | 482 "Upgrade: websocket\r\n" |
| 378 "Connection: Upgrade, Keep-Alive\r\n" | 483 "Connection: Upgrade, Keep-Alive\r\n" |
| 379 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 484 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" |
| 380 "\r\n"; | 485 "\r\n"; |
| 381 CreateAndConnectCustomResponse("ws://localhost/", | 486 CreateAndConnectCustomResponse("ws://localhost/", |
| 382 "/", | 487 "/", |
| 383 NoSubProtocols(), | 488 NoSubProtocols(), |
| 384 "http://localhost/", | 489 "http://localhost/", |
| 385 "", | 490 "", |
| 386 kAdditionalConnectionTokenResponse); | 491 kAdditionalConnectionTokenResponse); |
| 387 RunUntilIdle(); | 492 RunUntilIdle(); |
| 493 EXPECT_FALSE(has_failed()); |
| 388 EXPECT_TRUE(stream_); | 494 EXPECT_TRUE(stream_); |
| 389 } | 495 } |
| 390 | 496 |
| 391 // Sec-WebSocket-Accept header must be present. | 497 // Sec-WebSocket-Accept header must be present. |
| 392 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { | 498 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { |
| 393 static const char kMissingAcceptResponse[] = | 499 static const char kMissingAcceptResponse[] = |
| 394 "HTTP/1.1 101 Switching Protocols\r\n" | 500 "HTTP/1.1 101 Switching Protocols\r\n" |
| 395 "Upgrade: websocket\r\n" | 501 "Upgrade: websocket\r\n" |
| 396 "Connection: Upgrade\r\n" | 502 "Connection: Upgrade\r\n" |
| 397 "\r\n"; | 503 "\r\n"; |
| 398 CreateAndConnectCustomResponse("ws://localhost/", | 504 CreateAndConnectCustomResponse("ws://localhost/", |
| 399 "/", | 505 "/", |
| 400 NoSubProtocols(), | 506 NoSubProtocols(), |
| 401 "http://localhost/", | 507 "http://localhost/", |
| 402 "", | 508 "", |
| 403 kMissingAcceptResponse); | 509 kMissingAcceptResponse); |
| 404 RunUntilIdle(); | 510 RunUntilIdle(); |
| 405 EXPECT_EQ(1006, error()); | 511 EXPECT_TRUE(has_failed()); |
| 512 EXPECT_EQ("Error during WebSocket handshake: " |
| 513 "'Sec-WebSocket-Accept' header is missing", |
| 514 failure_message()); |
| 406 } | 515 } |
| 407 | 516 |
| 408 // Sec-WebSocket-Accept header must match the key that was sent. | 517 // Sec-WebSocket-Accept header must match the key that was sent. |
| 409 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { | 518 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { |
| 410 static const char kIncorrectAcceptResponse[] = | 519 static const char kIncorrectAcceptResponse[] = |
| 411 "HTTP/1.1 101 Switching Protocols\r\n" | 520 "HTTP/1.1 101 Switching Protocols\r\n" |
| 412 "Upgrade: websocket\r\n" | 521 "Upgrade: websocket\r\n" |
| 413 "Connection: Upgrade\r\n" | 522 "Connection: Upgrade\r\n" |
| 414 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" | 523 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" |
| 415 "\r\n"; | 524 "\r\n"; |
| 416 CreateAndConnectCustomResponse("ws://localhost/", | 525 CreateAndConnectCustomResponse("ws://localhost/", |
| 417 "/", | 526 "/", |
| 418 NoSubProtocols(), | 527 NoSubProtocols(), |
| 419 "http://localhost/", | 528 "http://localhost/", |
| 420 "", | 529 "", |
| 421 kIncorrectAcceptResponse); | 530 kIncorrectAcceptResponse); |
| 422 RunUntilIdle(); | 531 RunUntilIdle(); |
| 423 EXPECT_EQ(1006, error()); | 532 EXPECT_TRUE(has_failed()); |
| 533 EXPECT_EQ("Error during WebSocket handshake: " |
| 534 "Incorrect 'Sec-WebSocket-Accept' header value", |
| 535 failure_message()); |
| 424 } | 536 } |
| 425 | 537 |
| 426 // Cancellation works. | 538 // Cancellation works. |
| 427 TEST_F(WebSocketStreamCreateTest, Cancellation) { | 539 TEST_F(WebSocketStreamCreateTest, Cancellation) { |
| 428 CreateAndConnectStandard( | 540 CreateAndConnectStandard( |
| 429 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); | 541 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); |
| 430 stream_request_.reset(); | 542 stream_request_.reset(); |
| 431 RunUntilIdle(); | 543 RunUntilIdle(); |
| 544 EXPECT_FALSE(has_failed()); |
| 432 EXPECT_FALSE(stream_); | 545 EXPECT_FALSE(stream_); |
| 433 } | 546 } |
| 434 | 547 |
| 435 // Connect failure must look just like negotiation failure. | 548 // Connect failure must look just like negotiation failure. |
| 436 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { | 549 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { |
| 437 scoped_ptr<DeterministicSocketData> socket_data( | 550 scoped_ptr<DeterministicSocketData> socket_data( |
| 438 new DeterministicSocketData(NULL, 0, NULL, 0)); | 551 new DeterministicSocketData(NULL, 0, NULL, 0)); |
| 439 socket_data->set_connect_data( | 552 socket_data->set_connect_data( |
| 440 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | 553 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); |
| 441 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | 554 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), |
| 442 "http://localhost/", socket_data.Pass()); | 555 "http://localhost/", socket_data.Pass()); |
| 443 RunUntilIdle(); | 556 RunUntilIdle(); |
| 444 EXPECT_EQ(1006, error()); | 557 EXPECT_TRUE(has_failed()); |
| 558 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", |
| 559 failure_message()); |
| 445 } | 560 } |
| 446 | 561 |
| 447 // Connect timeout must look just like any other failure. | 562 // Connect timeout must look just like any other failure. |
| 448 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { | 563 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { |
| 449 scoped_ptr<DeterministicSocketData> socket_data( | 564 scoped_ptr<DeterministicSocketData> socket_data( |
| 450 new DeterministicSocketData(NULL, 0, NULL, 0)); | 565 new DeterministicSocketData(NULL, 0, NULL, 0)); |
| 451 socket_data->set_connect_data( | 566 socket_data->set_connect_data( |
| 452 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); | 567 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); |
| 453 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | 568 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), |
| 454 "http://localhost/", socket_data.Pass()); | 569 "http://localhost/", socket_data.Pass()); |
| 455 RunUntilIdle(); | 570 RunUntilIdle(); |
| 456 EXPECT_EQ(1006, error()); | 571 EXPECT_TRUE(has_failed()); |
| 572 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", |
| 573 failure_message()); |
| 457 } | 574 } |
| 458 | 575 |
| 459 // Cancellation during connect works. | 576 // Cancellation during connect works. |
| 460 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { | 577 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { |
| 461 scoped_ptr<DeterministicSocketData> socket_data( | 578 scoped_ptr<DeterministicSocketData> socket_data( |
| 462 new DeterministicSocketData(NULL, 0, NULL, 0)); | 579 new DeterministicSocketData(NULL, 0, NULL, 0)); |
| 463 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | 580 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); |
| 464 CreateAndConnectRawExpectations("ws://localhost/", | 581 CreateAndConnectRawExpectations("ws://localhost/", |
| 465 NoSubProtocols(), | 582 NoSubProtocols(), |
| 466 "http://localhost/", | 583 "http://localhost/", |
| 467 socket_data.Pass()); | 584 socket_data.Pass()); |
| 468 stream_request_.reset(); | 585 stream_request_.reset(); |
| 469 RunUntilIdle(); | 586 RunUntilIdle(); |
| 587 EXPECT_FALSE(has_failed()); |
| 470 EXPECT_FALSE(stream_); | 588 EXPECT_FALSE(stream_); |
| 471 } | 589 } |
| 472 | 590 |
| 473 // Cancellation during write of the request headers works. | 591 // Cancellation during write of the request headers works. |
| 474 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { | 592 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { |
| 475 // We seem to need at least two operations in order to use SetStop(). | 593 // We seem to need at least two operations in order to use SetStop(). |
| 476 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"), | 594 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"), |
| 477 MockWrite(ASYNC, 1, "1.1\r\n")}; | 595 MockWrite(ASYNC, 1, "1.1\r\n")}; |
| 478 // We keep a copy of the pointer so that we can call RunFor() on it later. | 596 // We keep a copy of the pointer so that we can call RunFor() on it later. |
| 479 DeterministicSocketData* socket_data( | 597 DeterministicSocketData* socket_data( |
| 480 new DeterministicSocketData(NULL, 0, writes, arraysize(writes))); | 598 new DeterministicSocketData(NULL, 0, writes, arraysize(writes))); |
| 481 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 599 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| 482 socket_data->SetStop(1); | 600 socket_data->SetStop(1); |
| 483 CreateAndConnectRawExpectations("ws://localhost/", | 601 CreateAndConnectRawExpectations("ws://localhost/", |
| 484 NoSubProtocols(), | 602 NoSubProtocols(), |
| 485 "http://localhost/", | 603 "http://localhost/", |
| 486 make_scoped_ptr(socket_data)); | 604 make_scoped_ptr(socket_data)); |
| 487 socket_data->Run(); | 605 socket_data->Run(); |
| 488 stream_request_.reset(); | 606 stream_request_.reset(); |
| 489 RunUntilIdle(); | 607 RunUntilIdle(); |
| 608 EXPECT_FALSE(has_failed()); |
| 490 EXPECT_FALSE(stream_); | 609 EXPECT_FALSE(stream_); |
| 491 } | 610 } |
| 492 | 611 |
| 493 // Cancellation during read of the response headers works. | 612 // Cancellation during read of the response headers works. |
| 494 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { | 613 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { |
| 495 std::string request = WebSocketStandardRequest("/", "http://localhost/", ""); | 614 std::string request = WebSocketStandardRequest("/", "http://localhost/", ""); |
| 496 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; | 615 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; |
| 497 MockRead reads[] = { | 616 MockRead reads[] = { |
| 498 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), | 617 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), |
| 499 }; | 618 }; |
| 500 DeterministicSocketData* socket_data(new DeterministicSocketData( | 619 DeterministicSocketData* socket_data(new DeterministicSocketData( |
| 501 reads, arraysize(reads), writes, arraysize(writes))); | 620 reads, arraysize(reads), writes, arraysize(writes))); |
| 502 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 621 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| 503 socket_data->SetStop(1); | 622 socket_data->SetStop(1); |
| 504 CreateAndConnectRawExpectations("ws://localhost/", | 623 CreateAndConnectRawExpectations("ws://localhost/", |
| 505 NoSubProtocols(), | 624 NoSubProtocols(), |
| 506 "http://localhost/", | 625 "http://localhost/", |
| 507 make_scoped_ptr(socket_data)); | 626 make_scoped_ptr(socket_data)); |
| 508 socket_data->Run(); | 627 socket_data->Run(); |
| 509 stream_request_.reset(); | 628 stream_request_.reset(); |
| 510 RunUntilIdle(); | 629 RunUntilIdle(); |
| 630 EXPECT_FALSE(has_failed()); |
| 511 EXPECT_FALSE(stream_); | 631 EXPECT_FALSE(stream_); |
| 512 } | 632 } |
| 513 | 633 |
| 514 } // namespace | 634 } // namespace |
| 515 } // namespace net | 635 } // namespace net |
| OLD | NEW |