Chromium Code Reviews| 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..deb8395e96b92d1e7883c1ba0044ec36c3275915 100644 |
| --- a/net/websockets/websocket_handshake_handler.cc |
| +++ b/net/websockets/websocket_handshake_handler.cc |
| @@ -1,10 +1,12 @@ |
| -// 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_piece.h" |
| #include "base/string_util.h" |
| #include "googleurl/src/gurl.h" |
| @@ -15,6 +17,7 @@ namespace { |
| const size_t kRequestKey3Size = 8U; |
| const size_t kResponseKeySize = 16U; |
| +const char* const kWebSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| void ParseHandshakeHeader( |
| const char* handshake_message, int len, |
| @@ -130,13 +133,23 @@ void GetKeyNumber(const std::string& key, std::string* challenge) { |
| challenge->append(part, 4); |
| } |
| +// Hybi-04 and later drafts require clients to send Sec-WebSocket-Key header, |
| +// thus we can detect request format by looking at the presence of this header. |
| +bool IsHybi04HandshakeRequest(const std::string& request_headers) { |
| + std::vector<std::string> values; |
| + const char* const headers_to_get[1] = { "sec-websocket-key" }; |
| + FetchHeaders(request_headers, headers_to_get, 1, &values); |
| + return !values.empty(); |
| +} |
| + |
| } // anonymous namespace |
| namespace net { |
| WebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler() |
| : original_length_(0), |
| - raw_length_(0) {} |
| + raw_length_(0), |
| + is_hybi04_handshake_(false) {} |
| bool WebSocketHandshakeRequestHandler::ParseRequest( |
| const char* data, int length) { |
| @@ -144,8 +157,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,17 +165,28 @@ 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. |
| + if (IsHybi04HandshakeRequest(headers_)) { |
| + key3_ = ""; |
| + is_hybi04_handshake_ = true; |
| + 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); |
| + is_hybi04_handshake_ = false; |
| original_length_ = input.size(); |
| return true; |
| } |
| @@ -202,17 +225,24 @@ 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); |
| + if (is_hybi04_handshake_) { |
| + std::string key; |
| + request_info.extra_headers.GetHeader("Sec-WebSocket-Key", &key); |
| + request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key"); |
| + *challenge = key; |
| + } else { |
| + 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); |
| + request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key); |
| + request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2"); |
| + GetKeyNumber(key, challenge); |
| - challenge->append(key3_); |
| + challenge->append(key3_); |
| + } |
| return request_info; |
| } |
| @@ -223,8 +253,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 +268,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 +294,17 @@ bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock( |
| } |
| } |
| - challenge->clear(); |
| - GetKeyNumber(key1, challenge); |
| - GetKeyNumber(key2, challenge); |
| - challenge->append(key3_); |
| + if (is_hybi04_handshake_) { |
| + DCHECK(old_key1.empty()); |
| + DCHECK(old_key2.empty()); |
| + *challenge = new_key; |
| + } else { |
| + DCHECK(new_key.empty()); |
| + challenge->clear(); |
| + GetKeyNumber(old_key1, challenge); |
| + GetKeyNumber(old_key2, challenge); |
| + challenge->append(key3_); |
| + } |
| return true; |
| } |
| @@ -269,7 +312,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 +324,33 @@ size_t WebSocketHandshakeRequestHandler::raw_length() const { |
| return raw_length_; |
| } |
| +bool WebSocketHandshakeRequestHandler::is_hybi04_handshake() const { |
| + return is_hybi04_handshake_; |
| +} |
| + |
| WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler() |
| - : original_header_length_(0) { |
| + : original_header_length_(0), |
| + expect_hybi04_handshake_(false) { |
| } |
| WebSocketHandshakeResponseHandler::~WebSocketHandshakeResponseHandler() {} |
| +bool WebSocketHandshakeResponseHandler::expect_hybi04_handshake() const { |
| + return expect_hybi04_handshake_; |
| +} |
| + |
| +void WebSocketHandshakeResponseHandler::set_expect_hybi04_handshake( |
| + bool expect_hybi04_handshake) { |
| + expect_hybi04_handshake_ = expect_hybi04_handshake; |
| +} |
| + |
| 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 +372,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 +390,19 @@ bool WebSocketHandshakeResponseHandler::ParseResponseInfo( |
| std::string response_message; |
| response_message = response_info.headers->GetStatusLine(); |
| response_message += "\r\n"; |
| - response_message += "Upgrade: WebSocket\r\n"; |
| + if (expect_hybi04_handshake_) |
| + response_message += "Upgrade: websocket\r\n"; |
| + else |
| + response_message += "Upgrade: WebSocket\r\n"; |
| response_message += "Connection: Upgrade\r\n"; |
| + |
| + if (expect_hybi04_handshake_) { |
| + std::string hash = base::SHA1HashString(challenge + kWebSocketGuid); |
| + std::string websocket_accept; |
| + DCHECK(base::Base64Encode(hash, &websocket_accept)); |
|
Yuta Kitamura
2011/04/13 09:57:52
Call to Base64Encode should not be inside DCHECK.
Yuta Kitamura
2011/04/13 11:23:50
Done.
|
| + response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n"; |
| + } |
| + |
| void* iter = NULL; |
| std::string name; |
| std::string value; |
| @@ -343,11 +411,13 @@ bool WebSocketHandshakeResponseHandler::ParseResponseInfo( |
| } |
| response_message += "\r\n"; |
| - MD5Digest digest; |
| - MD5Sum(challenge.data(), challenge.size(), &digest); |
| + if (!expect_hybi04_handshake_) { |
| + 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 +427,22 @@ 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 (expect_hybi04_handshake_) { |
| + 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 (expect_hybi04_handshake_) { |
| + std::string hash = base::SHA1HashString(challenge + kWebSocketGuid); |
| + std::string websocket_accept; |
| + DCHECK(base::Base64Encode(hash, &websocket_accept)); |
|
Yuta Kitamura
2011/04/13 11:23:50
Fixed here, too.
|
| + response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n"; |
| + } |
| + |
| for (spdy::SpdyHeaderBlock::const_iterator iter = headers.begin(); |
| iter != headers.end(); |
| ++iter) { |
| @@ -382,11 +465,13 @@ bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock( |
| } |
| response_message += "\r\n"; |
| - MD5Digest digest; |
| - MD5Sum(challenge.data(), challenge.size(), &digest); |
| + if (!expect_hybi04_handshake_) { |
| + 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 +484,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 +495,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 +503,20 @@ 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 { |
| + return expect_hybi04_handshake_ ? 0 : kResponseKeySize; |
| +} |
| + |
| } // namespace net |