summaryrefslogtreecommitdiffstats
path: root/net/websockets
diff options
context:
space:
mode:
authoryutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-06 10:14:28 +0000
committeryutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-06 10:14:28 +0000
commitc6718cc47e54d8fc3299e866d6cf2b0f3d5ea31d (patch)
treef9408ef9ed8f293808c26f0f9a42e065763942be /net/websockets
parent5eaeb80c0d3fb5407a4e19b4d09680f09079943a (diff)
downloadchromium_src-c6718cc47e54d8fc3299e866d6cf2b0f3d5ea31d.zip
chromium_src-c6718cc47e54d8fc3299e866d6cf2b0f3d5ea31d.tar.gz
chromium_src-c6718cc47e54d8fc3299e866d6cf2b0f3d5ea31d.tar.bz2
Accept new WebSocket handshake format (hybi-04 and later).
To update WebSocket protocol implementation in WebKit, WebSocketRequestHandshakeHandler and WebSocketResponseHandler need to be able to understand both old handshake (hybi-03 and prior, including hixie-76) and new handshake (hybi-04 and later). BUG=64470 TEST=net_unittests --gtest_filter="WebSocketHandshake*HandlerTest.*" Review URL: http://codereview.chromium.org/6823075 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@84427 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/websockets')
-rw-r--r--net/websockets/websocket_handshake_handler.cc223
-rw-r--r--net/websockets/websocket_handshake_handler.h39
-rw-r--r--net/websockets/websocket_handshake_handler_unittest.cc346
-rw-r--r--net/websockets/websocket_job.cc2
4 files changed, 553 insertions, 57 deletions
diff --git a/net/websockets/websocket_handshake_handler.cc b/net/websockets/websocket_handshake_handler.cc
index 68b0445..2e62a18 100644
--- a/net/websockets/websocket_handshake_handler.cc
+++ b/net/websockets/websocket_handshake_handler.cc
@@ -1,10 +1,13 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// 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 "net/websockets/websocket_handshake_handler.h"
+#include "base/base64.h"
#include "base/md5.h"
+#include "base/sha1.h"
+#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "base/string_util.h"
#include "googleurl/src/gurl.h"
@@ -16,6 +19,13 @@ namespace {
const size_t kRequestKey3Size = 8U;
const size_t kResponseKeySize = 16U;
+// First version that introduced new WebSocket handshake which does not
+// require sending "key3" or "response key" data after headers.
+const int kMinVersionOfHybiNewHandshake = 4;
+
+// Used when we calculate the value of Sec-WebSocket-Accept.
+const char* const kWebSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
void ParseHandshakeHeader(
const char* handshake_message, int len,
std::string* status_line,
@@ -130,13 +140,29 @@ void GetKeyNumber(const std::string& key, std::string* challenge) {
challenge->append(part, 4);
}
+int GetVersionFromRequest(const std::string& request_headers) {
+ std::vector<std::string> values;
+ const char* const headers_to_get[2] = { "sec-websocket-version",
+ "sec-websocket-draft" };
+ FetchHeaders(request_headers, headers_to_get, 2, &values);
+ DCHECK_LE(values.size(), 1U);
+ if (values.empty())
+ return 0;
+ int version;
+ bool conversion_success = base::StringToInt(values[0], &version);
+ DCHECK(conversion_success);
+ DCHECK_GE(version, 1);
+ return version;
+}
+
} // anonymous namespace
namespace net {
WebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler()
: original_length_(0),
- raw_length_(0) {}
+ raw_length_(0),
+ protocol_version_(-1) {}
bool WebSocketHandshakeRequestHandler::ParseRequest(
const char* data, int length) {
@@ -144,8 +170,7 @@ bool WebSocketHandshakeRequestHandler::ParseRequest(
std::string input(data, length);
int input_header_length =
HttpUtil::LocateEndOfHeaders(input.data(), input.size(), 0);
- if (input_header_length <= 0 ||
- input_header_length + kRequestKey3Size > input.size())
+ if (input_header_length <= 0)
return false;
ParseHandshakeHeader(input.data(),
@@ -153,15 +178,26 @@ bool WebSocketHandshakeRequestHandler::ParseRequest(
&status_line_,
&headers_);
- // draft-hixie-thewebsocketprotocol-76 or later will send /key3/
- // after handshake request header.
+ // WebSocket protocol drafts hixie-76 (hybi-00), hybi-01, 02 and 03 require
+ // the clients to send key3 after the handshake request header fields.
+ // Hybi-04 and later drafts, on the other hand, no longer have key3
+ // in the handshake format.
+ protocol_version_ = GetVersionFromRequest(headers_);
+ DCHECK_GE(protocol_version_, 0);
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ key3_ = "";
+ original_length_ = input_header_length;
+ return true;
+ }
+
+ if (input_header_length + kRequestKey3Size > input.size())
+ return false;
+
// Assumes WebKit doesn't send any data after handshake request message
// until handshake is finished.
// Thus, |key3_| is part of handshake message, and not in part
// of WebSocket frame stream.
- DCHECK_EQ(kRequestKey3Size,
- input.size() -
- input_header_length);
+ DCHECK_EQ(kRequestKey3Size, input.size() - input_header_length);
key3_ = std::string(input.data() + input_header_length,
input.size() - input_header_length);
original_length_ = input.size();
@@ -202,17 +238,30 @@ HttpRequestInfo WebSocketHandshakeRequestHandler::GetRequestInfo(
request_info.extra_headers.RemoveHeader("Upgrade");
request_info.extra_headers.RemoveHeader("Connection");
- challenge->clear();
- std::string key;
- request_info.extra_headers.GetHeader("Sec-WebSocket-Key1", &key);
- request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key1");
- GetKeyNumber(key, challenge);
-
- request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key);
- request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2");
- GetKeyNumber(key, challenge);
-
- challenge->append(key3_);
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ std::string key;
+ bool header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key");
+ *challenge = key;
+ } else {
+ challenge->clear();
+ std::string key;
+ bool header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key1", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key1");
+ GetKeyNumber(key, challenge);
+
+ header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2");
+ GetKeyNumber(key, challenge);
+
+ challenge->append(key3_);
+ }
return request_info;
}
@@ -223,8 +272,9 @@ bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
// protocol.
(*headers)["url"] = url.spec();
- std::string key1;
- std::string key2;
+ std::string new_key; // For protocols hybi-04 and newer.
+ std::string old_key1; // For protocols hybi-03 and older.
+ std::string old_key2; // Ditto.
HttpUtil::HeadersIterator iter(headers_.begin(), headers_.end(), "\r\n");
while (iter.GetNext()) {
if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
@@ -237,13 +287,18 @@ bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
continue;
} else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
"sec-websocket-key1")) {
- // Use only for generating challenge.
- key1 = iter.values();
+ // Only used for generating challenge.
+ old_key1 = iter.values();
continue;
} else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
"sec-websocket-key2")) {
- // Use only for generating challenge.
- key2 = iter.values();
+ // Only used for generating challenge.
+ old_key2 = iter.values();
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ "sec-websocket-key")) {
+ // Only used for generating challenge.
+ new_key = iter.values();
continue;
}
// Others should be sent out to |headers|.
@@ -258,10 +313,20 @@ bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
}
}
- challenge->clear();
- GetKeyNumber(key1, challenge);
- GetKeyNumber(key2, challenge);
- challenge->append(key3_);
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ DVLOG_IF(1, !old_key1.empty())
+ << "Server sent unexpected Sec-WebSocket-Key1 header.";
+ DVLOG_IF(1, !old_key2.empty())
+ << "Server sent unexpected Sec-WebSocket-Key2 header.";
+ *challenge = new_key;
+ } else {
+ DVLOG_IF(1, !new_key.empty())
+ << "Server sent unexpected Sec-WebSocket-Key header.";
+ challenge->clear();
+ GetKeyNumber(old_key1, challenge);
+ GetKeyNumber(old_key2, challenge);
+ challenge->append(key3_);
+ }
return true;
}
@@ -269,7 +334,8 @@ bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
std::string WebSocketHandshakeRequestHandler::GetRawRequest() {
DCHECK(!status_line_.empty());
DCHECK(!headers_.empty());
- DCHECK_EQ(kRequestKey3Size, key3_.size());
+ // The following works on both hybi-04 and older handshake,
+ // because |key3_| is guaranteed to be empty if the handshake was hybi-04's.
std::string raw_request = status_line_ + headers_ + "\r\n" + key3_;
raw_length_ = raw_request.size();
return raw_request;
@@ -280,19 +346,35 @@ size_t WebSocketHandshakeRequestHandler::raw_length() const {
return raw_length_;
}
-WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler()
- : original_header_length_(0) {
+int WebSocketHandshakeRequestHandler::protocol_version() const {
+ DCHECK_GE(protocol_version_, 0);
+ return protocol_version_;
}
+WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler()
+ : original_header_length_(0),
+ protocol_version_(0) {}
+
WebSocketHandshakeResponseHandler::~WebSocketHandshakeResponseHandler() {}
+int WebSocketHandshakeResponseHandler::protocol_version() const {
+ DCHECK_GE(protocol_version_, 0);
+ return protocol_version_;
+}
+
+void WebSocketHandshakeResponseHandler::set_protocol_version(
+ int protocol_version) {
+ DCHECK_GE(protocol_version, 0);
+ protocol_version_ = protocol_version;
+}
+
size_t WebSocketHandshakeResponseHandler::ParseRawResponse(
const char* data, int length) {
DCHECK_GT(length, 0);
if (HasResponse()) {
DCHECK(!status_line_.empty());
DCHECK(!headers_.empty());
- DCHECK_EQ(kResponseKeySize, key_.size());
+ DCHECK_EQ(GetResponseKeySize(), key_.size());
return 0;
}
@@ -314,14 +396,13 @@ size_t WebSocketHandshakeResponseHandler::ParseRawResponse(
header_separator_ = std::string(original_.data() + header_size,
original_header_length_ - header_size);
key_ = std::string(original_.data() + original_header_length_,
- kResponseKeySize);
-
- return original_header_length_ + kResponseKeySize - old_original_length;
+ GetResponseKeySize());
+ return original_header_length_ + GetResponseKeySize() - old_original_length;
}
bool WebSocketHandshakeResponseHandler::HasResponse() const {
return original_header_length_ > 0 &&
- original_header_length_ + kResponseKeySize <= original_.size();
+ original_header_length_ + GetResponseKeySize() <= original_.size();
}
bool WebSocketHandshakeResponseHandler::ParseResponseInfo(
@@ -333,8 +414,20 @@ bool WebSocketHandshakeResponseHandler::ParseResponseInfo(
std::string response_message;
response_message = response_info.headers->GetStatusLine();
response_message += "\r\n";
- response_message += "Upgrade: WebSocket\r\n";
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake)
+ response_message += "Upgrade: websocket\r\n";
+ else
+ response_message += "Upgrade: WebSocket\r\n";
response_message += "Connection: Upgrade\r\n";
+
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ std::string hash = base::SHA1HashString(challenge + kWebSocketGuid);
+ std::string websocket_accept;
+ bool encode_success = base::Base64Encode(hash, &websocket_accept);
+ DCHECK(encode_success);
+ response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n";
+ }
+
void* iter = NULL;
std::string name;
std::string value;
@@ -343,11 +436,13 @@ bool WebSocketHandshakeResponseHandler::ParseResponseInfo(
}
response_message += "\r\n";
- MD5Digest digest;
- MD5Sum(challenge.data(), challenge.size(), &digest);
+ if (protocol_version_ < kMinVersionOfHybiNewHandshake) {
+ MD5Digest digest;
+ MD5Sum(challenge.data(), challenge.size(), &digest);
- const char* digest_data = reinterpret_cast<char*>(digest.a);
- response_message.append(digest_data, sizeof(digest.a));
+ const char* digest_data = reinterpret_cast<char*>(digest.a);
+ response_message.append(digest_data, sizeof(digest.a));
+ }
return ParseRawResponse(response_message.data(),
response_message.size()) == response_message.size();
@@ -357,9 +452,23 @@ bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock(
const spdy::SpdyHeaderBlock& headers,
const std::string& challenge) {
std::string response_message;
- response_message = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
- response_message += "Upgrade: WebSocket\r\n";
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ response_message = "HTTP/1.1 101 Switching Protocols\r\n";
+ response_message += "Upgrade: websocket\r\n";
+ } else {
+ response_message = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
+ response_message += "Upgrade: WebSocket\r\n";
+ }
response_message += "Connection: Upgrade\r\n";
+
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ std::string hash = base::SHA1HashString(challenge + kWebSocketGuid);
+ std::string websocket_accept;
+ bool encode_success = base::Base64Encode(hash, &websocket_accept);
+ DCHECK(encode_success);
+ response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n";
+ }
+
for (spdy::SpdyHeaderBlock::const_iterator iter = headers.begin();
iter != headers.end();
++iter) {
@@ -382,11 +491,13 @@ bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock(
}
response_message += "\r\n";
- MD5Digest digest;
- MD5Sum(challenge.data(), challenge.size(), &digest);
+ if (protocol_version_ < kMinVersionOfHybiNewHandshake) {
+ MD5Digest digest;
+ MD5Sum(challenge.data(), challenge.size(), &digest);
- const char* digest_data = reinterpret_cast<char*>(digest.a);
- response_message.append(digest_data, sizeof(digest.a));
+ const char* digest_data = reinterpret_cast<char*>(digest.a);
+ response_message.append(digest_data, sizeof(digest.a));
+ }
return ParseRawResponse(response_message.data(),
response_message.size()) == response_message.size();
@@ -399,7 +510,7 @@ void WebSocketHandshakeResponseHandler::GetHeaders(
DCHECK(HasResponse());
DCHECK(!status_line_.empty());
DCHECK(!headers_.empty());
- DCHECK_EQ(kResponseKeySize, key_.size());
+ DCHECK_EQ(GetResponseKeySize(), key_.size());
FetchHeaders(headers_, headers_to_get, headers_to_get_len, values);
}
@@ -410,7 +521,7 @@ void WebSocketHandshakeResponseHandler::RemoveHeaders(
DCHECK(HasResponse());
DCHECK(!status_line_.empty());
DCHECK(!headers_.empty());
- DCHECK_EQ(kResponseKeySize, key_.size());
+ DCHECK_EQ(GetResponseKeySize(), key_.size());
headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len);
}
@@ -418,16 +529,22 @@ void WebSocketHandshakeResponseHandler::RemoveHeaders(
std::string WebSocketHandshakeResponseHandler::GetRawResponse() const {
DCHECK(HasResponse());
return std::string(original_.data(),
- original_header_length_ + kResponseKeySize);
+ original_header_length_ + GetResponseKeySize());
}
std::string WebSocketHandshakeResponseHandler::GetResponse() {
DCHECK(HasResponse());
DCHECK(!status_line_.empty());
// headers_ might be empty for wrong response from server.
- DCHECK_EQ(kResponseKeySize, key_.size());
+ DCHECK_EQ(GetResponseKeySize(), key_.size());
return status_line_ + headers_ + header_separator_ + key_;
}
+size_t WebSocketHandshakeResponseHandler::GetResponseKeySize() const {
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake)
+ return 0;
+ return kResponseKeySize;
+}
+
} // namespace net
diff --git a/net/websockets/websocket_handshake_handler.h b/net/websockets/websocket_handshake_handler.h
index 855d12f..b96ee87 100644
--- a/net/websockets/websocket_handshake_handler.h
+++ b/net/websockets/websocket_handshake_handler.h
@@ -9,6 +9,21 @@
// - We don't trust WebKit renderer process, so we'll not expose HttpOnly
// cookies to the renderer process, so handles HttpOnly cookies in
// browser process.
+//
+// The classes below support two styles of handshake: handshake based
+// on hixie-76 draft and one based on hybi-04 draft. The critical difference
+// between these two is how they pass challenge and response values. Hixie-76
+// based handshake appends a few bytes of binary data after header fields of
+// handshake request and response. These data are called "key3" (for request)
+// or "response key" (for response). On the other hand, handshake based on
+// hybi-04 and later drafts put challenge and response values into handshake
+// header fields, thus we do not need to send or receive extra bytes after
+// handshake headers.
+//
+// While we are working on updating WebSocket implementation in WebKit to
+// conform to the latest procotol draft, we need to accept both styles of
+// handshake. After we land the protocol changes in WebKit, we will be able to
+// drop codes handling old-style handshake.
#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
@@ -45,10 +60,12 @@ class WebSocketHandshakeRequestHandler {
size_t headers_to_remove_len);
// Gets request info to open WebSocket connection.
- // Also, fill challange data in |challenge|.
+ // Fills challange data (concatenation of key1, 2 and 3 for hybi-03 and
+ // earlier, or Sec-WebSocket-Key header value for hybi-04 and later)
+ // in |challenge|.
HttpRequestInfo GetRequestInfo(const GURL& url, std::string* challenge);
// Gets request as SpdyHeaderBlock.
- // Also, fill challenge data in |challenge|.
+ // Also, fills challenge data in |challenge|.
bool GetRequestHeaderBlock(const GURL& url,
spdy::SpdyHeaderBlock* headers,
std::string* challenge);
@@ -58,12 +75,19 @@ class WebSocketHandshakeRequestHandler {
// Calling raw_length is valid only after GetRawRquest() call.
size_t raw_length() const;
+ // Returns the value of Sec-WebSocket-Version or Sec-WebSocket-Draft header
+ // (the latter is an old name of the former). Returns 0 if both headers were
+ // absent, which means the handshake was based on hybi-00 (= hixie-76).
+ // Should only be called after the handshake has been parsed.
+ int protocol_version() const;
+
private:
std::string status_line_;
std::string headers_;
std::string key3_;
int original_length_;
int raw_length_;
+ int protocol_version_; // "-1" means we haven't parsed the handshake yet.
DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestHandler);
};
@@ -73,6 +97,11 @@ class WebSocketHandshakeResponseHandler {
WebSocketHandshakeResponseHandler();
~WebSocketHandshakeResponseHandler();
+ // Set WebSocket protocol version before parsing the response.
+ // Default is 0 (hybi-00, which is same as hixie-76).
+ int protocol_version() const;
+ void set_protocol_version(int protocol_version);
+
// Parses WebSocket handshake response from WebSocket server.
// Returns number of bytes in |data| used for WebSocket handshake response
// message, including response key. If it already got whole WebSocket
@@ -105,12 +134,18 @@ class WebSocketHandshakeResponseHandler {
std::string GetResponse();
private:
+ // Returns the length of response key. This function will return 0
+ // if the specified WebSocket protocol version does not require
+ // sending response key.
+ size_t GetResponseKeySize() const;
+
std::string original_;
int original_header_length_;
std::string status_line_;
std::string headers_;
std::string header_separator_;
std::string key_;
+ int protocol_version_;
DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeResponseHandler);
};
diff --git a/net/websockets/websocket_handshake_handler_unittest.cc b/net/websockets/websocket_handshake_handler_unittest.cc
index fafd77e..50199d9 100644
--- a/net/websockets/websocket_handshake_handler_unittest.cc
+++ b/net/websockets/websocket_handshake_handler_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// 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.
@@ -47,6 +47,30 @@ TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequest) {
EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeRequestMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequestHybi06Handshake) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, handler.protocol_version());
handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
@@ -71,6 +95,7 @@ TEST(WebSocketHandshakeRequestHandlerTest, ReplaceRequestCookies) {
EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, handler.protocol_version());
handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
@@ -94,8 +119,50 @@ TEST(WebSocketHandshakeRequestHandlerTest, ReplaceRequestCookies) {
EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest());
}
+TEST(WebSocketHandshakeRequestHandlerTest,
+ ReplaceRequestCookiesHybi06Handshake) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "Cookie: WK-websocket-test=1\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ handler.AppendHeaderIfMissing("Cookie",
+ "WK-websocket-test=1; "
+ "WK-websocket-test-httponly=1");
+
+ static const char* kHandshakeRequestExpectedMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest());
+}
+
TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponse) {
WebSocketHandshakeResponseHandler handler;
+ EXPECT_EQ(0, handler.protocol_version());
static const char* kHandshakeResponseMessage =
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
@@ -117,8 +184,32 @@ TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponse) {
EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse());
}
+TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponseHybi06Handshake) {
+ WebSocketHandshakeResponseHandler handler;
+ handler.set_protocol_version(6);
+ EXPECT_EQ(6, handler.protocol_version());
+
+ static const char* kHandshakeResponseMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse());
+}
+
TEST(WebSocketHandshakeResponseHandlerTest, ReplaceResponseCookies) {
WebSocketHandshakeResponseHandler handler;
+ EXPECT_EQ(0, handler.protocol_version());
static const char* kHandshakeResponseMessage =
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
@@ -156,6 +247,44 @@ TEST(WebSocketHandshakeResponseHandlerTest, ReplaceResponseCookies) {
EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse());
}
+TEST(WebSocketHandshakeResponseHandlerTest,
+ ReplaceResponseCookiesHybi06Handshake) {
+ WebSocketHandshakeResponseHandler handler;
+ handler.set_protocol_version(6);
+ EXPECT_EQ(6, handler.protocol_version());
+
+ static const char* kHandshakeResponseMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: WK-websocket-test-1\r\n"
+ "Set-Cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ std::vector<std::string> cookies;
+ handler.GetHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders), &cookies);
+ ASSERT_EQ(2U, cookies.size());
+ EXPECT_EQ("WK-websocket-test-1", cookies[0]);
+ EXPECT_EQ("WK-websocket-test-httponly=1; HttpOnly", cookies[1]);
+ handler.RemoveHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders));
+
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse());
+}
+
TEST(WebSocketHandshakeResponseHandlerTest, BadResponse) {
WebSocketHandshakeResponseHandler handler;
@@ -193,6 +322,7 @@ TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, request_handler.protocol_version());
GURL url("ws://example.com/demo");
std::string challenge;
@@ -214,7 +344,7 @@ TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
&value));
EXPECT_EQ("sample", value);
- const char expected_challenge[] = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
+ const char* expected_challenge = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
EXPECT_EQ(expected_challenge, challenge);
@@ -242,6 +372,7 @@ TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
"sample"));
WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_EQ(0, response_handler.protocol_version());
EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge));
EXPECT_TRUE(response_handler.HasResponse());
@@ -258,6 +389,82 @@ TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
}
+TEST(WebSocketHandshakeHandlerTest, HttpRequestResponseHybi06Handshake) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ const HttpRequestInfo& request_info =
+ request_handler.GetRequestInfo(url, &challenge);
+
+ EXPECT_EQ(url, request_info.url);
+ EXPECT_EQ("GET", request_info.method);
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Upgrade"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Connection"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key"));
+ std::string value;
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Host", &value));
+ EXPECT_EQ("example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Origin",
+ &value));
+ EXPECT_EQ("http://example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Protocol",
+ &value));
+ EXPECT_EQ("sample", value);
+
+ EXPECT_EQ("dGhlIHNhbXBsZSBub25jZQ==", challenge);
+
+ static const char* kHandshakeResponseHeader =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n";
+
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(kHandshakeResponseHeader,
+ strlen(kHandshakeResponseHeader));
+ HttpResponseInfo response_info;
+ response_info.headers = new HttpResponseHeaders(raw_headers);
+
+ EXPECT_TRUE(StartsWithASCII(response_info.headers->GetStatusLine(),
+ "HTTP/1.1 101 ", false));
+ EXPECT_FALSE(response_info.headers->HasHeader("Upgrade"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Connection"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Sec-WebSocket-Accept"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Protocol",
+ "sample"));
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(request_handler.protocol_version());
+ EXPECT_EQ(6, response_handler.protocol_version());
+
+ EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponse) {
WebSocketHandshakeRequestHandler request_handler;
@@ -277,6 +484,7 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponse) {
EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, request_handler.protocol_version());
GURL url("ws://example.com/demo");
std::string challenge;
@@ -310,6 +518,7 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponse) {
headers["sec-websocket-protocol"] = "sample";
WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_EQ(0, response_handler.protocol_version());
EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
EXPECT_TRUE(response_handler.HasResponse());
@@ -327,6 +536,68 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponse) {
EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
}
+TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseHybi06Handshake) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "X-bogus-header: X\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "X-bogus-header: Y\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ spdy::SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, &headers, &challenge));
+
+ EXPECT_EQ(url.spec(), headers["url"]);
+ EXPECT_TRUE(headers.find("upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("Upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("connection") == headers.end());
+ EXPECT_TRUE(headers.find("Connection") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key") == headers.end());
+ EXPECT_EQ("example.com", headers["host"]);
+ EXPECT_EQ("http://example.com", headers["sec-websocket-origin"]);
+ EXPECT_EQ("sample", headers["sec-websocket-protocol"]);
+ const char bogus_header[] = "X\0Y";
+ std::string bogus_header_str(bogus_header, sizeof(bogus_header) - 1);
+ EXPECT_EQ(bogus_header_str, headers["x-bogus-header"]);
+
+ EXPECT_EQ("dGhlIHNhbXBsZSBub25jZQ==", challenge);
+
+ headers.clear();
+
+ headers["sec-websocket-protocol"] = "sample";
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(request_handler.protocol_version());
+ EXPECT_EQ(6, response_handler.protocol_version());
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseWithCookies) {
WebSocketHandshakeRequestHandler request_handler;
@@ -347,6 +618,7 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseWithCookies) {
EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, request_handler.protocol_version());
GURL url("ws://example.com/demo");
std::string challenge;
@@ -383,6 +655,7 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseWithCookies) {
headers["set-cookie"] = cookie;
WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_EQ(0, response_handler.protocol_version());
EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
EXPECT_TRUE(response_handler.HasResponse());
@@ -402,4 +675,73 @@ TEST(WebSocketHandshakeHandlerTest, SpdyRequestResponseWithCookies) {
EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
}
+TEST(WebSocketHandshakeHandlerTest,
+ SpdyRequestResponseWithCookiesHybi06Handshake) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ // Note that websocket won't use multiple headers in request now.
+ static const char* kHandshakeRequestMessage =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ spdy::SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, &headers, &challenge));
+
+ EXPECT_EQ(url.spec(), headers["url"]);
+ EXPECT_TRUE(headers.find("upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("Upgrade") == headers.end());
+ EXPECT_TRUE(headers.find("connection") == headers.end());
+ EXPECT_TRUE(headers.find("Connection") == headers.end());
+ EXPECT_TRUE(headers.find("Sec-WebSocket-Key") == headers.end());
+ EXPECT_TRUE(headers.find("sec-websocket-key") == headers.end());
+ EXPECT_EQ("example.com", headers["host"]);
+ EXPECT_EQ("http://example.com", headers["sec-websocket-origin"]);
+ EXPECT_EQ("sample", headers["sec-websocket-protocol"]);
+ EXPECT_EQ("WK-websocket-test=1; WK-websocket-test-httponly=1",
+ headers["cookie"]);
+
+ EXPECT_EQ("dGhlIHNhbXBsZSBub25jZQ==", challenge);
+
+ headers.clear();
+
+ headers["sec-websocket-protocol"] = "sample";
+ std::string cookie = "WK-websocket-test=1";
+ cookie.append(1, '\0');
+ cookie += "WK-websocket-test-httponly=1; HttpOnly";
+ headers["set-cookie"] = cookie;
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(request_handler.protocol_version());
+ EXPECT_EQ(6, response_handler.protocol_version());
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(headers, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char* kHandshakeResponseExpectedMessage =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "set-cookie: WK-websocket-test=1\r\n"
+ "set-cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
} // namespace net
diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc
index 7a5e030..f90d55f 100644
--- a/net/websockets/websocket_job.cc
+++ b/net/websockets/websocket_job.cc
@@ -271,6 +271,8 @@ bool WebSocketJob::SendHandshakeRequest(const char* data, int len) {
return false;
// handshake message is completed.
+ handshake_response_->set_protocol_version(
+ handshake_request_->protocol_version());
AddCookieHeaderAndSend();
// Just buffered in |handshake_request_|.
return true;