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