diff options
author | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-03 08:58:08 +0000 |
---|---|---|
committer | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-03 08:58:08 +0000 |
commit | 817fc50713c37fb0cb41fbc455d83fb40b40da7e (patch) | |
tree | dfde9cc335e1e35bf9c19fc140c696c3d00e3281 /net/websockets | |
parent | 18141b6e06116e417b40e3bfc16a042e8188fb2b (diff) | |
download | chromium_src-817fc50713c37fb0cb41fbc455d83fb40b40da7e.zip chromium_src-817fc50713c37fb0cb41fbc455d83fb40b40da7e.tar.gz chromium_src-817fc50713c37fb0cb41fbc455d83fb40b40da7e.tar.bz2 |
Refactor WebSocket handshake.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/2452001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@48816 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/websockets')
-rw-r--r-- | net/websockets/websocket_handshake_handler.cc | 277 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler.h | 110 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler_unittest.cc | 152 | ||||
-rw-r--r-- | net/websockets/websocket_job.cc | 240 | ||||
-rw-r--r-- | net/websockets/websocket_job.h | 10 |
5 files changed, 595 insertions, 194 deletions
diff --git a/net/websockets/websocket_handshake_handler.cc b/net/websockets/websocket_handshake_handler.cc new file mode 100644 index 0000000..8b1e071 --- /dev/null +++ b/net/websockets/websocket_handshake_handler.cc @@ -0,0 +1,277 @@ +// Copyright (c) 2010 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/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/http/http_util.h" + +namespace { + +const size_t kRequestKey3Size = 8U; +const size_t kResponseKeySize = 16U; + +void ParseHandshakeHeader( + const char* handshake_message, int len, + std::string* status_line, + std::string* headers) { + size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n"); + if (i == base::StringPiece::npos) { + *status_line = std::string(handshake_message, len); + *headers = ""; + return; + } + *status_line = std::string(handshake_message, i + 2); + *headers = std::string(handshake_message + i + 2, len - i - 2); +} + +void FetchHeaders(const std::string& headers, + const char* const headers_to_get[], + size_t headers_to_get_len, + std::vector<std::string>* values) { + net::HttpUtil::HeadersIterator iter(headers.begin(), headers.end(), "\r\n"); + while (iter.GetNext()) { + for (size_t i = 0; i < headers_to_get_len; i++) { + if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), + headers_to_get[i])) { + values->push_back(iter.values()); + } + } + } +} + +bool GetHeaderName(std::string::const_iterator line_begin, + std::string::const_iterator line_end, + std::string::const_iterator* name_begin, + std::string::const_iterator* name_end) { + std::string::const_iterator colon = std::find(line_begin, line_end, ':'); + if (colon == line_end) { + return false; + } + *name_begin = line_begin; + *name_end = colon; + if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin)) + return false; + net::HttpUtil::TrimLWS(name_begin, name_end); + return true; +} + +// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that +// is, lines that are not formatted as "<name>: <value>\r\n". +std::string FilterHeaders( + const std::string& headers, + const char* const headers_to_remove[], + size_t headers_to_remove_len) { + std::string filtered_headers; + + StringTokenizer lines(headers.begin(), headers.end(), "\r\n"); + while (lines.GetNext()) { + std::string::const_iterator line_begin = lines.token_begin(); + std::string::const_iterator line_end = lines.token_end(); + std::string::const_iterator name_begin; + std::string::const_iterator name_end; + bool should_remove = false; + if (GetHeaderName(line_begin, line_end, &name_begin, &name_end)) { + for (size_t i = 0; i < headers_to_remove_len; ++i) { + if (LowerCaseEqualsASCII(name_begin, name_end, headers_to_remove[i])) { + should_remove = true; + break; + } + } + } + if (!should_remove) { + filtered_headers.append(line_begin, line_end); + filtered_headers.append("\r\n"); + } + } + return filtered_headers; +} + +} // anonymous namespace + +namespace net { + +WebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler() + : original_length_(0), + raw_length_(0) {} + +bool WebSocketHandshakeRequestHandler::ParseRequest( + const char* data, int length) { + DCHECK_GT(length, 0); + 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()) + return false; + + ParseHandshakeHeader(input.data(), + input_header_length, + &status_line_, + &headers_); + + // draft-hixie-thewebsocketprotocol-76 or later will send /key3/ + // after handshake request header. + // 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); + key3_ = std::string(input.data() + input_header_length, + input.size() - input_header_length); + original_length_ = input.size(); + return true; +} + +size_t WebSocketHandshakeRequestHandler::original_length() const { + return original_length_; +} + +void WebSocketHandshakeRequestHandler::AppendHeaderIfMissing( + const std::string& name, const std::string& value) { + DCHECK(headers_.size() > 0); + HttpUtil::AppendHeaderIfMissing(name.c_str(), value, &headers_); +} + +void WebSocketHandshakeRequestHandler::RemoveHeaders( + const char* const headers_to_remove[], + size_t headers_to_remove_len) { + DCHECK(headers_.size() > 0); + headers_ = FilterHeaders( + headers_, headers_to_remove, headers_to_remove_len); +} + +const HttpRequestInfo& WebSocketHandshakeRequestHandler::GetRequestInfo( + const GURL& url) { + NOTIMPLEMENTED(); + // TODO(ukai): implement for Spdy support. Rest is incomplete. + request_info_.url = url; + // TODO(ukai): method should be built from |request_status_line_|. + request_info_.method = "GET"; + + request_info_.extra_headers.Clear(); + request_info_.extra_headers.AddHeadersFromString(headers_); + // TODO(ukai): eliminate unnecessary headers, such as Sec-WebSocket-Key1 + // and Sec-WebSocket-Key2. + return request_info_; +} + +std::string WebSocketHandshakeRequestHandler::GetRawRequest() { + DCHECK(status_line_.size() > 0); + DCHECK(headers_.size() > 0); + DCHECK_EQ(kRequestKey3Size, key3_.size()); + std::string raw_request = status_line_ + headers_ + "\r\n" + key3_; + raw_length_ = raw_request.size(); + return raw_request; +} + +size_t WebSocketHandshakeRequestHandler::raw_length() const { + DCHECK_GT(raw_length_, 0); + return raw_length_; +} + +std::string WebSocketHandshakeRequestHandler::GetChallenge() const { + NOTIMPLEMENTED(); + return ""; +} + +WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler() + : original_header_length_(0) { +} + +size_t WebSocketHandshakeResponseHandler::ParseRawResponse( + const char* data, int length) { + DCHECK_GT(length, 0); + if (HasResponse()) { + DCHECK(status_line_.size() > 0); + DCHECK(headers_.size() > 0); + DCHECK_EQ(kResponseKeySize, key_.size()); + return 0; + } + + size_t old_original_length = original_.size(); + + original_.append(data, length); + // TODO(ukai): fail fast when response gives wrong status code. + original_header_length_ = HttpUtil::LocateEndOfHeaders( + original_.data(), original_.size(), 0); + if (!HasResponse()) + return length; + + ParseHandshakeHeader(original_.data(), + original_header_length_, + &status_line_, + &headers_); + key_ = std::string(original_.data() + original_header_length_, + kResponseKeySize); + + return original_header_length_ + kResponseKeySize - old_original_length; +} + +bool WebSocketHandshakeResponseHandler::HasResponse() const { + return original_header_length_ > 0 && + original_header_length_ + kResponseKeySize <= original_.size(); +} + +bool WebSocketHandshakeResponseHandler::ParseResponseInfo( + const HttpResponseInfo& response_info, + WebSocketHandshakeRequestHandler* request_handler) { + NOTIMPLEMENTED(); + // TODO(ukai): implement for Spdy support. Rest is incomplete. + response_info_ = response_info; + + if (!response_info_.headers.get()) + return false; + + std::string response_message; + response_message = response_info_.headers->GetStatusLine(); + response_message += "\r\n"; + void* iter = NULL; + std::string name; + std::string value; + while (response_info_.headers->EnumerateHeaderLines(&iter, &name, &value)) { + response_message += name + ": " + value + "\r\n"; + } + // TODO(ukai): generate response key from request_handler->GetChallenge() + // and add it to |response_message|. + return ParseRawResponse(response_message.data(), + response_message.size()) == response_message.size(); +} + +void WebSocketHandshakeResponseHandler::GetHeaders( + const char* const headers_to_get[], + size_t headers_to_get_len, + std::vector<std::string>* values) { + DCHECK(HasResponse()); + DCHECK(status_line_.size() > 0); + DCHECK(headers_.size() > 0); + DCHECK_EQ(kResponseKeySize, key_.size()); + + FetchHeaders(headers_, headers_to_get, headers_to_get_len, values); +} + +void WebSocketHandshakeResponseHandler::RemoveHeaders( + const char* const headers_to_remove[], + size_t headers_to_remove_len) { + DCHECK(HasResponse()); + DCHECK(status_line_.size() > 0); + DCHECK(headers_.size() > 0); + DCHECK_EQ(kResponseKeySize, key_.size()); + + headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len); +} + +std::string WebSocketHandshakeResponseHandler::GetResponse() { + DCHECK(HasResponse()); + DCHECK(status_line_.size() > 0); + DCHECK(headers_.size() > 0); + DCHECK_EQ(kResponseKeySize, key_.size()); + + return status_line_ + headers_ + "\r\n" + key_; +} + +} // namespace net diff --git a/net/websockets/websocket_handshake_handler.h b/net/websockets/websocket_handshake_handler.h new file mode 100644 index 0000000..8f0cb4b --- /dev/null +++ b/net/websockets/websocket_handshake_handler.h @@ -0,0 +1,110 @@ +// Copyright (c) 2010 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. +// +// WebSocketHandshake*Handler handles WebSocket handshake request message +// from WebKit renderer process, and WebSocket handshake response message +// from WebSocket server. +// It modifies messages for the following reason: +// - 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. + +#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_ +#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" + +namespace net { + +class WebSocketHandshakeRequestHandler { + public: + WebSocketHandshakeRequestHandler(); + ~WebSocketHandshakeRequestHandler() {} + + // Parses WebSocket handshake request from renderer process. + // It assumes a WebSocket handshake request message is given at once, and + // no other data is added to the request message. + bool ParseRequest(const char* data, int length); + + size_t original_length() const; + + // Appends the header value pair for |name| and |value|, if |name| doesn't + // exist. + void AppendHeaderIfMissing(const std::string& name, + const std::string& value); + // Removes the headers that matches (case insensitive). + void RemoveHeaders(const char* const headers_to_remove[], + size_t headers_to_remove_len); + + // Gets request info to open WebSocket connection. + const HttpRequestInfo& GetRequestInfo(const GURL& url); + // Gets WebSocket handshake raw request message to open WebSocket + // connection. + std::string GetRawRequest(); + // Calling raw_length is valid only after GetRawRquest() call. + size_t raw_length() const; + + std::string GetChallenge() const; + + private: + HttpRequestInfo request_info_; + + std::string status_line_; + std::string headers_; + std::string key3_; + int original_length_; + int raw_length_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestHandler); +}; + +class WebSocketHandshakeResponseHandler { + public: + WebSocketHandshakeResponseHandler(); + ~WebSocketHandshakeResponseHandler() {} + + // 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 + // handshake response message, returns zero. In other words, + // [data + returned value, data + length) will be WebSocket frame data + // after handshake response message. + // TODO(ukai): fail fast when response gives wrong status code. + size_t ParseRawResponse(const char* data, int length); + // Returns true if it already parses full handshake response message. + bool HasResponse() const; + // Parses WebSocket handshake response info given as HttpResponseInfo. + bool ParseResponseInfo(const HttpResponseInfo& response_info, + WebSocketHandshakeRequestHandler* request_handler); + + // Gets the headers value. + void GetHeaders(const char* const headers_to_get[], + size_t headers_to_get_len, + std::vector<std::string>* values); + // Removes the headers that matches (case insensitive). + void RemoveHeaders(const char* const headers_to_remove[], + size_t headers_to_remove_len); + + // Gets WebSocket handshake response message sent to renderer process. + std::string GetResponse(); + + private: + HttpResponseInfo response_info_; + std::string original_; + int original_header_length_; + std::string status_line_; + std::string headers_; + std::string key_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeResponseHandler); +}; + +} // namespace net + +#endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_ diff --git a/net/websockets/websocket_handshake_handler_unittest.cc b/net/websockets/websocket_handshake_handler_unittest.cc new file mode 100644 index 0000000..44e1633 --- /dev/null +++ b/net/websockets/websocket_handshake_handler_unittest.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "googleurl/src/gurl.h" +#include "net/websockets/websocket_handshake_handler.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace { + +const char* const kCookieHeaders[] = { + "cookie", "cookie2" +}; + +const char* const kSetCookieHeaders[] = { + "set-cookie", "set-cookie2" +}; + +} + +namespace net { + +TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequest) { + WebSocketHandshakeRequestHandler handler; + + static const char* kHandshakeRequestMessage = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "^n:ds[4U"; + + EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage, + strlen(kHandshakeRequestMessage))); + + handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders)); + + EXPECT_EQ(kHandshakeRequestMessage, handler.GetRawRequest()); +} + +TEST(WebSocketHandshakeRequestHandlerTest, ReplaceRequestCookies) { + WebSocketHandshakeRequestHandler handler; + + static const char* kHandshakeRequestMessage = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "Cookie: WK-websocket-test=1\r\n" + "\r\n" + "^n:ds[4U"; + + EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage, + strlen(kHandshakeRequestMessage))); + + 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" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n" + "\r\n" + "^n:ds[4U"; + + EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest()); +} + +TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponse) { + WebSocketHandshakeResponseHandler handler; + + static const char* kHandshakeResponseMessage = + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n" + "Sec-WebSocket-Location: ws://example.com/demo\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "\r\n" + "8jKS'y:G*Co,Wxa-"; + + EXPECT_TRUE(handler.ParseRawResponse(kHandshakeResponseMessage, + strlen(kHandshakeResponseMessage))); + + handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders)); + + EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse()); +} + +TEST(WebSocketHandshakeResponseHandlerTest, ReplaceResponseCookies) { + WebSocketHandshakeResponseHandler handler; + + static const char* kHandshakeResponseMessage = + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n" + "Sec-WebSocket-Location: ws://example.com/demo\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Set-Cookie: WK-websocket-test-1\r\n" + "Set-Cookie: WK-websocket-test-httponly=1; HttpOnly\r\n" + "\r\n" + "8jKS'y:G*Co,Wxa-"; + + EXPECT_TRUE(handler.ParseRawResponse(kHandshakeResponseMessage, + strlen(kHandshakeResponseMessage))); + 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 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n" + "Sec-WebSocket-Location: ws://example.com/demo\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "\r\n" + "8jKS'y:G*Co,Wxa-"; + + EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse()); +} + +} // namespace net diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc index 063b3ab..0998c98 100644 --- a/net/websockets/websocket_job.cc +++ b/net/websockets/websocket_job.cc @@ -15,13 +15,11 @@ #include "net/http/http_util.h" #include "net/url_request/url_request_context.h" #include "net/websockets/websocket_frame_handler.h" +#include "net/websockets/websocket_handshake_handler.h" #include "net/websockets/websocket_throttle.h" namespace { -const size_t kRequestKey3Size = 8U; -const size_t kResponseKeySize = 16U; - // lower-case header names. const char* const kCookieHeaders[] = { "cookie", "cookie2" @@ -46,83 +44,6 @@ class WebSocketJobInitSingleton { } }; -void ParseHandshakeMessage( - const char* handshake_message, int len, - std::string* status_line, - std::string* header) { - size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n"); - if (i == base::StringPiece::npos) { - *status_line = std::string(handshake_message, len); - *header = ""; - return; - } - *status_line = std::string(handshake_message, i + 2); - *header = std::string(handshake_message + i + 2, len - i - 2); -} - -void FetchResponseCookies( - const char* handshake_message, int len, - std::vector<std::string>* response_cookies) { - std::string handshake_response(handshake_message, len); - net::HttpUtil::HeadersIterator iter(handshake_response.begin(), - handshake_response.end(), "\r\n"); - while (iter.GetNext()) { - for (size_t i = 0; i < arraysize(kSetCookieHeaders); i++) { - if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), - kSetCookieHeaders[i])) { - response_cookies->push_back(iter.values()); - } - } - } -} - -bool GetHeaderName(std::string::const_iterator line_begin, - std::string::const_iterator line_end, - std::string::const_iterator* name_begin, - std::string::const_iterator* name_end) { - std::string::const_iterator colon = std::find(line_begin, line_end, ':'); - if (colon == line_end) { - return false; - } - *name_begin = line_begin; - *name_end = colon; - if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin)) - return false; - net::HttpUtil::TrimLWS(name_begin, name_end); - return true; -} - -// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that -// is, lines that are not formatted as "<name>: <value>\r\n". -std::string FilterHeaders( - const std::string& headers, - const char* const headers_to_remove[], - size_t headers_to_remove_len) { - std::string filtered_headers; - - StringTokenizer lines(headers.begin(), headers.end(), "\r\n"); - while (lines.GetNext()) { - std::string::const_iterator line_begin = lines.token_begin(); - std::string::const_iterator line_end = lines.token_end(); - std::string::const_iterator name_begin; - std::string::const_iterator name_end; - bool should_remove = false; - if (GetHeaderName(line_begin, line_end, &name_begin, &name_end)) { - for (size_t i = 0; i < headers_to_remove_len; ++i) { - if (LowerCaseEqualsASCII(name_begin, name_end, headers_to_remove[i])) { - should_remove = true; - break; - } - } - } - if (!should_remove) { - filtered_headers.append(line_begin, line_end); - filtered_headers.append("\r\n"); - } - } - return filtered_headers; -} - } // anonymous namespace namespace net { @@ -137,8 +58,9 @@ WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate) state_(INITIALIZED), waiting_(false), callback_(NULL), + handshake_request_(new WebSocketHandshakeRequestHandler), + handshake_response_(new WebSocketHandshakeResponseHandler), handshake_request_sent_(0), - handshake_response_header_length_(0), response_cookies_save_index_(0), ALLOW_THIS_IN_INITIALIZER_LIST(can_get_cookies_callback_( this, &WebSocketJob::OnCanGetCookiesCompleted)), @@ -330,22 +252,12 @@ void WebSocketJob::OnError(const SocketStream* socket, int error) { bool WebSocketJob::SendHandshakeRequest(const char* data, int len) { DCHECK_EQ(state_, CONNECTING); - if (!handshake_request_.empty()) { - // if we're already sending handshake message, don't send any more data - // until handshake is completed. + if (!handshake_request_->ParseRequest(data, len)) return false; - } - original_handshake_request_.append(data, len); - original_handshake_request_header_length_ = - HttpUtil::LocateEndOfHeaders(original_handshake_request_.data(), - original_handshake_request_.size(), 0); - if (original_handshake_request_header_length_ > 0 && - original_handshake_request_header_length_ + kRequestKey3Size <= - original_handshake_request_.size()) { - // handshake message is completed. - AddCookieHeaderAndSend(); - } - // Just buffered in original_handshake_request_. + + // handshake message is completed. + AddCookieHeaderAndSend(); + // Just buffered in |handshake_request_|. return true; } @@ -367,19 +279,8 @@ void WebSocketJob::AddCookieHeaderAndSend() { void WebSocketJob::OnCanGetCookiesCompleted(int policy) { if (socket_ && delegate_ && state_ == CONNECTING) { - std::string handshake_request_status_line; - std::string handshake_request_header; - ParseHandshakeMessage(original_handshake_request_.data(), - original_handshake_request_header_length_, - &handshake_request_status_line, - &handshake_request_header); - - // Remove cookie headers. We should not send out malformed headers, so - // use HttpUtil::StripHeaders() instead of FilterHeaders(). - handshake_request_header = HttpUtil::StripHeaders( - handshake_request_header, + handshake_request_->RemoveHeaders( kCookieHeaders, arraysize(kCookieHeaders)); - if (policy == OK) { // Add cookies, including HttpOnly cookies. if (socket_->context()->cookie_store()) { @@ -388,34 +289,15 @@ void WebSocketJob::OnCanGetCookiesCompleted(int policy) { std::string cookie = socket_->context()->cookie_store()->GetCookiesWithOptions( GetURLForCookies(), cookie_options); - if (!cookie.empty()) { - HttpUtil::AppendHeaderIfMissing("Cookie", cookie, - &handshake_request_header); - } + if (!cookie.empty()) + handshake_request_->AppendHeaderIfMissing("Cookie", cookie); } } - // draft-hixie-thewebsocketprotocol-76 or later will send /key3/ - // after handshake request header. - // Assumes WebKit doesn't send any data after handshake request message - // until handshake is finished. - // Thus, additional_data is part of handshake message, and not in part - // of websocket frame stream. - DCHECK_EQ(kRequestKey3Size, - original_handshake_request_.size() - - original_handshake_request_header_length_); - std::string additional_data = - std::string(original_handshake_request_.data() + - original_handshake_request_header_length_, - original_handshake_request_.size() - - original_handshake_request_header_length_); - handshake_request_ = - handshake_request_status_line + handshake_request_header + "\r\n" + - additional_data; - + const std::string& handshake_request = handshake_request_->GetRawRequest(); handshake_request_sent_ = 0; - socket_->SendData(handshake_request_.data(), - handshake_request_.size()); + socket_->SendData(handshake_request.data(), + handshake_request.size()); } Release(); // Balance AddRef taken in AddCookieHeaderAndSend } @@ -424,50 +306,51 @@ void WebSocketJob::OnSentHandshakeRequest( SocketStream* socket, int amount_sent) { DCHECK_EQ(state_, CONNECTING); handshake_request_sent_ += amount_sent; - DCHECK_LE(handshake_request_sent_, handshake_request_.size()); - if (handshake_request_sent_ >= handshake_request_.size()) { + DCHECK_LE(handshake_request_sent_, handshake_request_->raw_length()); + if (handshake_request_sent_ >= handshake_request_->raw_length()) { // handshake request has been sent. // notify original size of handshake request to delegate. if (delegate_) - delegate_->OnSentData(socket, original_handshake_request_.size()); + delegate_->OnSentData( + socket, + handshake_request_->original_length()); + handshake_request_.reset(); } } void WebSocketJob::OnReceivedHandshakeResponse( SocketStream* socket, const char* data, int len) { DCHECK_EQ(state_, CONNECTING); - // Check if response is already full received before appending new data - // to |handshake_response_| - if (handshake_response_header_length_ > 0 && - handshake_response_header_length_ + kResponseKeySize - <= handshake_response_.size()) { - // already started cookies processing. - handshake_response_.append(data, len); + if (handshake_response_->HasResponse()) { + // If we already has handshake response, received data should be frame + // data, not handshake message. + receive_frame_handler_->AppendData(data, len); return; } - handshake_response_.append(data, len); - handshake_response_header_length_ = HttpUtil::LocateEndOfHeaders( - handshake_response_.data(), - handshake_response_.size(), 0); - if (handshake_response_header_length_ > 0 && - handshake_response_header_length_ + kResponseKeySize - <= handshake_response_.size()) { - // handshake message is completed. - SaveCookiesAndNotifyHeaderComplete(); + + size_t response_length = handshake_response_->ParseRawResponse(data, len); + if (!handshake_response_->HasResponse()) { + // not yet. we need more data. + return; } + // handshake message is completed. + if (len - response_length > 0) { + // If we received extra data, it should be frame data. + receive_frame_handler_->AppendData(data + response_length, + len - response_length); + } + SaveCookiesAndNotifyHeaderComplete(); } void WebSocketJob::SaveCookiesAndNotifyHeaderComplete() { // handshake message is completed. - DCHECK(handshake_response_.data()); - DCHECK_GT(handshake_response_header_length_, 0); + DCHECK(handshake_response_->HasResponse()); response_cookies_.clear(); response_cookies_save_index_ = 0; - FetchResponseCookies(handshake_response_.data(), - handshake_response_header_length_, - &response_cookies_); + handshake_response_->GetHeaders( + kSetCookieHeaders, arraysize(kSetCookieHeaders), &response_cookies_); // Now, loop over the response cookies, and attempt to persist each. SaveNextCookie(); @@ -478,41 +361,18 @@ void WebSocketJob::SaveNextCookie() { response_cookies_.clear(); response_cookies_save_index_ = 0; - std::string handshake_response_status_line; - std::string handshake_response_header; - ParseHandshakeMessage(handshake_response_.data(), - handshake_response_header_length_, - &handshake_response_status_line, - &handshake_response_header); // Remove cookie headers, with malformed headers preserved. // Actual handshake should be done in WebKit. - std::string filtered_handshake_response_header = - FilterHeaders(handshake_response_header, - kSetCookieHeaders, arraysize(kSetCookieHeaders)); - std::string response_key = - std::string(handshake_response_.data() + - handshake_response_header_length_, - kResponseKeySize); - std::string received_data = - handshake_response_status_line + - filtered_handshake_response_header + - "\r\n" + - response_key; - if (handshake_response_header_length_ + kResponseKeySize - < handshake_response_.size()) { - receive_frame_handler_->AppendData( - handshake_response_.data() + handshake_response_header_length_ + - kResponseKeySize, - handshake_response_.size() - handshake_response_header_length_ - - kResponseKeySize); - // Don't buffer receiving data for now. - // TODO(ukai): fix performance of WebSocketFrameHandler. - while (receive_frame_handler_->UpdateCurrentBuffer(false) > 0) { - received_data += - std::string(receive_frame_handler_->GetCurrentBuffer()->data(), - receive_frame_handler_->GetCurrentBufferSize()); - receive_frame_handler_->ReleaseCurrentBuffer(); - } + handshake_response_->RemoveHeaders( + kSetCookieHeaders, arraysize(kSetCookieHeaders)); + std::string received_data = handshake_response_->GetResponse(); + // Don't buffer receiving data for now. + // TODO(ukai): fix performance of WebSocketFrameHandler. + while (receive_frame_handler_->UpdateCurrentBuffer(false) > 0) { + received_data += + std::string(receive_frame_handler_->GetCurrentBuffer()->data(), + receive_frame_handler_->GetCurrentBufferSize()); + receive_frame_handler_->ReleaseCurrentBuffer(); } state_ = OPEN; @@ -520,6 +380,8 @@ void WebSocketJob::SaveNextCookie() { delegate_->OnReceivedData( socket_, received_data.data(), received_data.size()); + handshake_response_.reset(); + Singleton<WebSocketThrottle>::get()->RemoveFromQueue(this); Singleton<WebSocketThrottle>::get()->WakeupSocketIfNecessary(); return; diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h index db436be..833726b 100644 --- a/net/websockets/websocket_job.h +++ b/net/websockets/websocket_job.h @@ -18,6 +18,8 @@ namespace net { class DrainableIOBuffer; class WebSocketFrameHandler; +class WebSocketHandshakeRequestHandler; +class WebSocketHandshakeResponseHandler; // WebSocket protocol specific job on SocketStream. // It captures WebSocket handshake message and handles cookie operations. @@ -95,13 +97,11 @@ class WebSocketJob : public SocketStreamJob, public SocketStream::Delegate { AddressList addresses_; CompletionCallback* callback_; // for throttling. - std::string original_handshake_request_; - int original_handshake_request_header_length_; - std::string handshake_request_; + scoped_ptr<WebSocketHandshakeRequestHandler> handshake_request_; + scoped_ptr<WebSocketHandshakeResponseHandler> handshake_response_; + size_t handshake_request_sent_; - std::string handshake_response_; - int handshake_response_header_length_; std::vector<std::string> response_cookies_; size_t response_cookies_save_index_; |