| Index: net/websockets/websocket_handshake_handler.cc
|
| diff --git a/net/websockets/websocket_handshake_handler.cc b/net/websockets/websocket_handshake_handler.cc
|
| index 68b0445fed1299f546a45fe84ebd0c5acd5c125e..2e62a180f1f366a1ccde832ac2ed3e9f4e79c454 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
|
|
|