diff options
author | yutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-06 10:14:28 +0000 |
---|---|---|
committer | yutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-06 10:14:28 +0000 |
commit | c6718cc47e54d8fc3299e866d6cf2b0f3d5ea31d (patch) | |
tree | f9408ef9ed8f293808c26f0f9a42e065763942be /net/websockets | |
parent | 5eaeb80c0d3fb5407a4e19b4d09680f09079943a (diff) | |
download | chromium_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.cc | 223 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler.h | 39 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler_unittest.cc | 346 | ||||
-rw-r--r-- | net/websockets/websocket_job.cc | 2 |
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; |