| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "base/string_split.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "net/websockets/websocket_handshake.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/platform_test.h" |
| |
| namespace net { |
| |
| class WebSocketHandshakeTest : public testing::Test { |
| public: |
| static void SetUpParameter(WebSocketHandshake* handshake, |
| uint32 number_1, uint32 number_2, |
| const std::string& key_1, const std::string& key_2, |
| const std::string& key_3) { |
| WebSocketHandshake::Parameter* parameter = |
| new WebSocketHandshake::Parameter; |
| parameter->number_1_ = number_1; |
| parameter->number_2_ = number_2; |
| parameter->key_1_ = key_1; |
| parameter->key_2_ = key_2; |
| parameter->key_3_ = key_3; |
| handshake->parameter_.reset(parameter); |
| } |
| |
| static void ExpectHeaderEquals(const std::string& expected, |
| const std::string& actual) { |
| std::vector<std::string> expected_lines; |
| Tokenize(expected, "\r\n", &expected_lines); |
| std::vector<std::string> actual_lines; |
| Tokenize(actual, "\r\n", &actual_lines); |
| // Request lines. |
| EXPECT_EQ(expected_lines[0], actual_lines[0]); |
| |
| std::vector<std::string> expected_headers; |
| for (size_t i = 1; i < expected_lines.size(); i++) { |
| // Finish at first CRLF CRLF. Note that /key_3/ might include CRLF. |
| if (expected_lines[i] == "") |
| break; |
| expected_headers.push_back(expected_lines[i]); |
| } |
| sort(expected_headers.begin(), expected_headers.end()); |
| |
| std::vector<std::string> actual_headers; |
| for (size_t i = 1; i < actual_lines.size(); i++) { |
| // Finish at first CRLF CRLF. Note that /key_3/ might include CRLF. |
| if (actual_lines[i] == "") |
| break; |
| actual_headers.push_back(actual_lines[i]); |
| } |
| sort(actual_headers.begin(), actual_headers.end()); |
| |
| EXPECT_EQ(expected_headers.size(), actual_headers.size()) |
| << "expected:" << expected |
| << "\nactual:" << actual; |
| for (size_t i = 0; i < expected_headers.size(); i++) { |
| EXPECT_EQ(expected_headers[i], actual_headers[i]); |
| } |
| } |
| |
| static void ExpectHandshakeMessageEquals(const std::string& expected, |
| const std::string& actual) { |
| // Headers. |
| ExpectHeaderEquals(expected, actual); |
| // Compare tailing \r\n\r\n<key3> (4 + 8 bytes). |
| ASSERT_GT(expected.size(), 12U); |
| const char* expected_key3 = expected.data() + expected.size() - 12; |
| EXPECT_GT(actual.size(), 12U); |
| if (actual.size() <= 12U) |
| return; |
| const char* actual_key3 = actual.data() + actual.size() - 12; |
| EXPECT_TRUE(memcmp(expected_key3, actual_key3, 12) == 0) |
| << "expected_key3:" << DumpKey(expected_key3, 12) |
| << ", actual_key3:" << DumpKey(actual_key3, 12); |
| } |
| |
| static std::string DumpKey(const char* buf, int len) { |
| std::string s; |
| for (int i = 0; i < len; i++) { |
| if (isprint(buf[i])) |
| s += base::StringPrintf("%c", buf[i]); |
| else |
| s += base::StringPrintf("\\x%02x", buf[i]); |
| } |
| return s; |
| } |
| |
| static std::string GetResourceName(WebSocketHandshake* handshake) { |
| return handshake->GetResourceName(); |
| } |
| static std::string GetHostFieldValue(WebSocketHandshake* handshake) { |
| return handshake->GetHostFieldValue(); |
| } |
| static std::string GetOriginFieldValue(WebSocketHandshake* handshake) { |
| return handshake->GetOriginFieldValue(); |
| } |
| }; |
| |
| |
| TEST_F(WebSocketHandshakeTest, Connect) { |
| const std::string kExpectedClientHandshakeMessage = |
| "GET /demo HTTP/1.1\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: Upgrade\r\n" |
| "Host: example.com\r\n" |
| "Origin: http://example.com\r\n" |
| "Sec-WebSocket-Protocol: sample\r\n" |
| "Sec-WebSocket-Key1: 388P O503D&ul7 {K%gX( %7 15\r\n" |
| "Sec-WebSocket-Key2: 1 N ?|k UT0or 3o 4 I97N 5-S3O 31\r\n" |
| "\r\n" |
| "\x47\x30\x22\x2D\x5A\x3F\x47\x58"; |
| |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com/demo"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| SetUpParameter(handshake.get(), 777007543U, 114997259U, |
| "388P O503D&ul7 {K%gX( %7 15", |
| "1 N ?|k UT0or 3o 4 I97N 5-S3O 31", |
| std::string("\x47\x30\x22\x2D\x5A\x3F\x47\x58", 8)); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| ExpectHandshakeMessageEquals( |
| kExpectedClientHandshakeMessage, |
| handshake->CreateClientHandshakeMessage()); |
| |
| const char kResponse[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: Upgrade\r\n" |
| "Sec-WebSocket-Origin: http://example.com\r\n" |
| "Sec-WebSocket-Location: ws://example.com/demo\r\n" |
| "Sec-WebSocket-Protocol: sample\r\n" |
| "\r\n" |
| "\x30\x73\x74\x33\x52\x6C\x26\x71\x2D\x32\x5A\x55\x5E\x77\x65\x75"; |
| std::vector<std::string> response_lines; |
| base::SplitStringDontTrim(kResponse, '\n', &response_lines); |
| |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| // too short |
| EXPECT_EQ(-1, handshake->ReadServerHandshake(kResponse, 16)); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| |
| // only status line |
| std::string response = response_lines[0]; |
| EXPECT_EQ(-1, handshake->ReadServerHandshake( |
| response.data(), response.size())); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| // by upgrade header |
| response += response_lines[1]; |
| EXPECT_EQ(-1, handshake->ReadServerHandshake( |
| response.data(), response.size())); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| // by connection header |
| response += response_lines[2]; |
| EXPECT_EQ(-1, handshake->ReadServerHandshake( |
| response.data(), response.size())); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| |
| response += response_lines[3]; // Sec-WebSocket-Origin |
| response += response_lines[4]; // Sec-WebSocket-Location |
| response += response_lines[5]; // Sec-WebSocket-Protocol |
| EXPECT_EQ(-1, handshake->ReadServerHandshake( |
| response.data(), response.size())); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| |
| response += response_lines[6]; // \r\n |
| EXPECT_EQ(-1, handshake->ReadServerHandshake( |
| response.data(), response.size())); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| |
| int handshake_length = sizeof(kResponse) - 1; // -1 for terminating \0 |
| EXPECT_EQ(handshake_length, handshake->ReadServerHandshake( |
| kResponse, handshake_length)); // -1 for terminating \0 |
| EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode()); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, ServerSentData) { |
| const std::string kExpectedClientHandshakeMessage = |
| "GET /demo HTTP/1.1\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: Upgrade\r\n" |
| "Host: example.com\r\n" |
| "Origin: http://example.com\r\n" |
| "Sec-WebSocket-Protocol: sample\r\n" |
| "Sec-WebSocket-Key1: 388P O503D&ul7 {K%gX( %7 15\r\n" |
| "Sec-WebSocket-Key2: 1 N ?|k UT0or 3o 4 I97N 5-S3O 31\r\n" |
| "\r\n" |
| "\x47\x30\x22\x2D\x5A\x3F\x47\x58"; |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com/demo"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| SetUpParameter(handshake.get(), 777007543U, 114997259U, |
| "388P O503D&ul7 {K%gX( %7 15", |
| "1 N ?|k UT0or 3o 4 I97N 5-S3O 31", |
| std::string("\x47\x30\x22\x2D\x5A\x3F\x47\x58", 8)); |
| EXPECT_EQ(WebSocketHandshake::MODE_INCOMPLETE, handshake->mode()); |
| ExpectHandshakeMessageEquals( |
| kExpectedClientHandshakeMessage, |
| handshake->CreateClientHandshakeMessage()); |
| |
| const char kResponse[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: Upgrade\r\n" |
| "Sec-WebSocket-Origin: http://example.com\r\n" |
| "Sec-WebSocket-Location: ws://example.com/demo\r\n" |
| "Sec-WebSocket-Protocol: sample\r\n" |
| "\r\n" |
| "\x30\x73\x74\x33\x52\x6C\x26\x71\x2D\x32\x5A\x55\x5E\x77\x65\x75" |
| "\0Hello\xff"; |
| |
| int handshake_length = strlen(kResponse); // key3 doesn't contain \0. |
| EXPECT_EQ(handshake_length, handshake->ReadServerHandshake( |
| kResponse, sizeof(kResponse) - 1)); // -1 for terminating \0 |
| EXPECT_EQ(WebSocketHandshake::MODE_CONNECTED, handshake->mode()); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, is_secure_false) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com/demo"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| EXPECT_FALSE(handshake->is_secure()); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, is_secure_true) { |
| // wss:// is secure. |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("wss://example.com/demo"), |
| "http://example.com", |
| "wss://example.com/demo", |
| "sample")); |
| EXPECT_TRUE(handshake->is_secure()); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_ResourceName) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com/Test?q=xxx&p=%20"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| // Path and query should be preserved as-is. |
| EXPECT_EQ("/Test?q=xxx&p=%20", GetResourceName(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_Host) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://Example.Com/demo"), |
| "http://Example.Com", |
| "ws://Example.Com/demo", |
| "sample")); |
| // Host should be lowercased |
| EXPECT_EQ("example.com", GetHostFieldValue(handshake.get())); |
| EXPECT_EQ("http://example.com", GetOriginFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_TrimPort80) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com:80/demo"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| // :80 should be trimmed as it's the default port for ws://. |
| EXPECT_EQ("example.com", GetHostFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_TrimPort443) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("wss://example.com:443/demo"), |
| "http://example.com", |
| "wss://example.com/demo", |
| "sample")); |
| // :443 should be trimmed as it's the default port for wss://. |
| EXPECT_EQ("example.com", GetHostFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, |
| CreateClientHandshakeMessage_NonDefaultPortForWs) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com:8080/demo"), |
| "http://example.com", |
| "wss://example.com/demo", |
| "sample")); |
| // :8080 should be preserved as it's not the default port for ws://. |
| EXPECT_EQ("example.com:8080", GetHostFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, |
| CreateClientHandshakeMessage_NonDefaultPortForWss) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("wss://example.com:4443/demo"), |
| "http://example.com", |
| "wss://example.com/demo", |
| "sample")); |
| // :4443 should be preserved as it's not the default port for wss://. |
| EXPECT_EQ("example.com:4443", GetHostFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_WsBut443) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("ws://example.com:443/demo"), |
| "http://example.com", |
| "ws://example.com/demo", |
| "sample")); |
| // :443 should be preserved as it's not the default port for ws://. |
| EXPECT_EQ("example.com:443", GetHostFieldValue(handshake.get())); |
| } |
| |
| TEST_F(WebSocketHandshakeTest, CreateClientHandshakeMessage_WssBut80) { |
| scoped_ptr<WebSocketHandshake> handshake( |
| new WebSocketHandshake(GURL("wss://example.com:80/demo"), |
| "http://example.com", |
| "wss://example.com/demo", |
| "sample")); |
| // :80 should be preserved as it's not the default port for wss://. |
| EXPECT_EQ("example.com:80", GetHostFieldValue(handshake.get())); |
| } |
| |
| } // namespace net |