diff options
author | ricea <ricea@chromium.org> | 2014-11-08 06:21:56 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-11-08 14:22:14 +0000 |
commit | 5563c9df40ff8738c49cb1b86633c0ca91ac07c2 (patch) | |
tree | ef37780813f290e1904c70a4e689a06782b8a6c3 /net/websockets | |
parent | 626256ab20378c1aba947414a84ab3868d2efad9 (diff) | |
download | chromium_src-5563c9df40ff8738c49cb1b86633c0ca91ac07c2.zip chromium_src-5563c9df40ff8738c49cb1b86633c0ca91ac07c2.tar.gz chromium_src-5563c9df40ff8738c49cb1b86633c0ca91ac07c2.tar.bz2 |
Delete the old WebSocket implementation from net/
The removal of NetworkDelegate::OnBeforeSocketStreamConnect() is in http://crrev.com/652363005 so not too many reviewers will need to review this more-complex CL.
BUG=423201
TEST=net_unittests, browser_tests
Review URL: https://codereview.chromium.org/679273005
Cr-Commit-Position: refs/heads/master@{#303387}
Diffstat (limited to 'net/websockets')
-rw-r--r-- | net/websockets/PRESUBMIT.py | 57 | ||||
-rw-r--r-- | net/websockets/README | 91 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler.cc | 477 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler.h | 111 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler_spdy_test.cc | 187 | ||||
-rw-r--r-- | net/websockets/websocket_handshake_handler_test.cc | 230 | ||||
-rw-r--r-- | net/websockets/websocket_job.cc | 693 | ||||
-rw-r--r-- | net/websockets/websocket_job.h | 154 | ||||
-rw-r--r-- | net/websockets/websocket_job_test.cc | 1287 | ||||
-rw-r--r-- | net/websockets/websocket_net_log_params.cc | 49 | ||||
-rw-r--r-- | net/websockets/websocket_net_log_params.h | 21 | ||||
-rw-r--r-- | net/websockets/websocket_net_log_params_test.cc | 50 | ||||
-rw-r--r-- | net/websockets/websocket_throttle.cc | 144 | ||||
-rw-r--r-- | net/websockets/websocket_throttle.h | 75 | ||||
-rw-r--r-- | net/websockets/websocket_throttle_test.cc | 359 |
15 files changed, 7 insertions, 3978 deletions
diff --git a/net/websockets/PRESUBMIT.py b/net/websockets/PRESUBMIT.py deleted file mode 100644 index 1da441c..0000000 --- a/net/websockets/PRESUBMIT.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2013 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. - -"""Chromium presubmit script for src/net/websockets. - -See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts -for more details on the presubmit API built into gcl. -""" - - -# TODO(ricea): Remove this once the old implementation has been removed and the -# list of files in the README file is no longer needed. -def _CheckReadMeComplete(input_api, output_api): - """Verifies that any new files have been added to the README file. - - Checks that if any source files were added in this CL, that they were - also added to the README file. We do not warn about pre-existing - errors, as that would be annoying. - - Args: - input_api: The InputApi object provided by the presubmit framework. - output_api: The OutputApi object provided by the framework. - - Returns: - A list of zero or more PresubmitPromptWarning objects. - """ - # None passed to AffectedSourceFiles means "use the default filter", which - # does what we want, ie. returns files in the CL with filenames that look like - # source code. - added_source_filenames = set(input_api.basename(af.LocalPath()) - for af in input_api.AffectedSourceFiles(None) - if af.Action().startswith('A')) - if not added_source_filenames: - return [] - readme = input_api.AffectedSourceFiles( - lambda af: af.LocalPath().endswith('/README')) - if not readme: - return [output_api.PresubmitPromptWarning( - 'One or more files were added to net/websockets without being added\n' - 'to net/websockets/README.\n', added_source_filenames)] - readme_added_filenames = set(line.strip() for line in readme[0].NewContents() - if line.strip() in added_source_filenames) - if readme_added_filenames < added_source_filenames: - return [output_api.PresubmitPromptWarning( - 'One or more files added to net/websockets but not found in the README ' - 'file.\n', added_source_filenames - readme_added_filenames)] - else: - return [] - - -def CheckChangeOnUpload(input_api, output_api): - return _CheckReadMeComplete(input_api, output_api) - - -def CheckChangeOnCommit(input_api, output_api): - return _CheckReadMeComplete(input_api, output_api) diff --git a/net/websockets/README b/net/websockets/README deleted file mode 100644 index efe7a59..0000000 --- a/net/websockets/README +++ /dev/null @@ -1,91 +0,0 @@ -This directory contains files related to Chromium's WebSocket -implementation. See http://www.websocket.org/ for an explanation of WebSockets. - -As of April 2013, the contents of this directory are in a transitional state, -and contain parts of two different WebSocket implementations. - -The following files are part of the legacy implementation. The legacy -implementation performs WebSocket framing within Blink and presents a -low-level socket-like interface to the renderer process. It is described in the -design doc at -https://docs.google.com/a/google.com/document/d/1_R6YjCIrm4kikJ3YeapcOU2Keqr3lVUPd-OeaIJ93qQ/preview - -websocket_handshake_handler_test.cc -websocket_handshake_handler_spdy_test.cc -websocket_job.cc -websocket_job.h -websocket_job_test.cc -websocket_net_log_params.cc -websocket_net_log_params.h -websocket_net_log_params_test.cc -websocket_throttle.cc -websocket_throttle.h -websocket_throttle_test.cc - -The following files are part of the new implementation. The new implementation -performs framing and implements protocol semantics in the browser process, and -presents a high-level interface to the renderer process similar to a -multiplexing proxy. This is the default implementation from M38. - -websocket_basic_handshake_stream.cc -websocket_basic_handshake_stream.h -websocket_basic_stream.cc -websocket_basic_stream.h -websocket_basic_stream_test.cc -websocket_channel.cc -websocket_channel.h -websocket_channel_test.cc -websocket_deflate_predictor.h -websocket_deflate_predictor_impl.cc -websocket_deflate_predictor_impl.h -websocket_deflate_predictor_impl_test.cc -websocket_deflate_stream.cc -websocket_deflate_stream.h -websocket_deflate_stream_test.cc -websocket_deflater.cc -websocket_deflater.h -websocket_deflater_test.cc -websocket_errors.cc -websocket_errors.h -websocket_errors_test.cc -websocket_event_interface.h -websocket_extension.cc -websocket_extension.h -websocket_extension_parser.cc -websocket_extension_parser.h -websocket_extension_parser_test.cc -websocket_frame.cc -websocket_frame.h -websocket_frame_parser.cc -websocket_frame_parser.h -websocket_frame_parser_test.cc -websocket_frame_test.cc -websocket_frame_perftest.cc -websocket_handshake_stream_base.h -websocket_handshake_stream_create_helper.cc -websocket_handshake_stream_create_helper.h -websocket_handshake_stream_create_helper_test.cc -websocket_handshake_request_info.cc -websocket_handshake_request_info.h -websocket_handshake_response_info.cc -websocket_handshake_response_info.h -websocket_inflater.cc -websocket_inflater.h -websocket_inflater_test.cc -websocket_mux.h -websocket_stream.cc -websocket_stream.h -websocket_stream_test.cc -websocket_test_util.cc -websocket_test_util.h - -These files are shared between the old and new implementations. - -websocket_handshake_constants.cc -websocket_handshake_constants.h -websocket_handshake_handler.cc -websocket_handshake_handler.h - -A pre-submit check helps us keep this README file up-to-date: - -PRESUBMIT.py diff --git a/net/websockets/websocket_handshake_handler.cc b/net/websockets/websocket_handshake_handler.cc index 6324710..6bcc230 100644 --- a/net/websockets/websocket_handshake_handler.cc +++ b/net/websockets/websocket_handshake_handler.cc @@ -4,346 +4,12 @@ #include "net/websockets/websocket_handshake_handler.h" -#include <limits> - #include "base/base64.h" +#include "base/logging.h" #include "base/sha1.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" -#include "base/strings/string_tokenizer.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "net/http/http_request_headers.h" -#include "net/http/http_response_headers.h" -#include "net/http/http_util.h" #include "net/websockets/websocket_handshake_constants.h" -#include "url/gurl.h" namespace net { -namespace { - -const int kVersionHeaderValueForRFC6455 = 13; - -// Splits |handshake_message| into Status-Line or Request-Line (including CRLF) -// and headers (excluding 2nd CRLF of double CRLFs at the end of a handshake -// response). -void ParseHandshakeHeader( - const char* handshake_message, int len, - std::string* request_line, - std::string* headers) { - size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n"); - if (i == base::StringPiece::npos) { - *request_line = std::string(handshake_message, len); - *headers = ""; - return; - } - // |request_line| includes \r\n. - *request_line = std::string(handshake_message, i + 2); - - int header_len = len - (i + 2) - 2; - if (header_len > 0) { - // |handshake_message| includes trailing \r\n\r\n. - // |headers| doesn't include 2nd \r\n. - *headers = std::string(handshake_message + i + 2, header_len); - } else { - *headers = ""; - } -} - -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; - - base::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; -} - -bool CheckVersionInRequest(const std::string& request_headers) { - std::vector<std::string> values; - const char* const headers_to_get[1] = { - websockets::kSecWebSocketVersionLowercase}; - FetchHeaders(request_headers, headers_to_get, 1, &values); - DCHECK_LE(values.size(), 1U); - if (values.empty()) - return false; - - int version; - bool conversion_success = base::StringToInt(values[0], &version); - if (!conversion_success) - return false; - - return version == kVersionHeaderValueForRFC6455; -} - -// Append a header to a string. Equivalent to -// response_message += header + ": " + value + "\r\n" -// but avoids unnecessary allocations and copies. -void AppendHeader(const base::StringPiece& header, - const base::StringPiece& value, - std::string* response_message) { - static const char kColonSpace[] = ": "; - const size_t kColonSpaceSize = sizeof(kColonSpace) - 1; - static const char kCrNl[] = "\r\n"; - const size_t kCrNlSize = sizeof(kCrNl) - 1; - - size_t extra_size = - header.size() + kColonSpaceSize + value.size() + kCrNlSize; - response_message->reserve(response_message->size() + extra_size); - response_message->append(header.begin(), header.end()); - response_message->append(kColonSpace, kColonSpace + kColonSpaceSize); - response_message->append(value.begin(), value.end()); - response_message->append(kCrNl, kCrNl + kCrNlSize); -} - -} // namespace - -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) - return false; - - ParseHandshakeHeader(input.data(), - input_header_length, - &request_line_, - &headers_); - - if (!CheckVersionInRequest(headers_)) { - NOTREACHED(); - return false; - } - - original_length_ = input_header_length; - return true; -} - -size_t WebSocketHandshakeRequestHandler::original_length() const { - return original_length_; -} - -void WebSocketHandshakeRequestHandler::AppendHeaderIfMissing( - const std::string& name, const std::string& value) { - DCHECK(!headers_.empty()); - HttpUtil::AppendHeaderIfMissing(name.c_str(), value, &headers_); -} - -void WebSocketHandshakeRequestHandler::RemoveHeaders( - const char* const headers_to_remove[], - size_t headers_to_remove_len) { - DCHECK(!headers_.empty()); - headers_ = FilterHeaders( - headers_, headers_to_remove, headers_to_remove_len); -} - -HttpRequestInfo WebSocketHandshakeRequestHandler::GetRequestInfo( - const GURL& url, std::string* challenge) { - HttpRequestInfo request_info; - request_info.url = url; - size_t method_end = base::StringPiece(request_line_).find_first_of(" "); - if (method_end != base::StringPiece::npos) - request_info.method = std::string(request_line_.data(), method_end); - - request_info.extra_headers.Clear(); - request_info.extra_headers.AddHeadersFromString(headers_); - - request_info.extra_headers.RemoveHeader(websockets::kUpgrade); - request_info.extra_headers.RemoveHeader(HttpRequestHeaders::kConnection); - - std::string key; - bool header_present = request_info.extra_headers.GetHeader( - websockets::kSecWebSocketKey, &key); - DCHECK(header_present); - request_info.extra_headers.RemoveHeader(websockets::kSecWebSocketKey); - *challenge = key; - return request_info; -} - -bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock( - const GURL& url, - SpdyHeaderBlock* headers, - std::string* challenge, - int spdy_protocol_version) { - // Construct opening handshake request headers as a SPDY header block. - // For details, see WebSocket Layering over SPDY/3 Draft 8. - if (spdy_protocol_version <= 2) { - (*headers)["path"] = url.path(); - (*headers)["version"] = "WebSocket/13"; - (*headers)["scheme"] = url.scheme(); - } else { - (*headers)[":path"] = url.path(); - (*headers)[":version"] = "WebSocket/13"; - (*headers)[":scheme"] = url.scheme(); - } - - HttpUtil::HeadersIterator iter(headers_.begin(), headers_.end(), "\r\n"); - while (iter.GetNext()) { - if (LowerCaseEqualsASCII(iter.name_begin(), - iter.name_end(), - websockets::kUpgradeLowercase) || - LowerCaseEqualsASCII( - iter.name_begin(), iter.name_end(), "connection") || - LowerCaseEqualsASCII(iter.name_begin(), - iter.name_end(), - websockets::kSecWebSocketVersionLowercase)) { - // These headers must be ignored. - continue; - } else if (LowerCaseEqualsASCII(iter.name_begin(), - iter.name_end(), - websockets::kSecWebSocketKeyLowercase)) { - *challenge = iter.values(); - // Sec-WebSocket-Key is not sent to the server. - continue; - } else if (LowerCaseEqualsASCII( - iter.name_begin(), iter.name_end(), "host") || - LowerCaseEqualsASCII( - iter.name_begin(), iter.name_end(), "origin") || - LowerCaseEqualsASCII( - iter.name_begin(), - iter.name_end(), - websockets::kSecWebSocketProtocolLowercase) || - LowerCaseEqualsASCII( - iter.name_begin(), - iter.name_end(), - websockets::kSecWebSocketExtensionsLowercase)) { - // TODO(toyoshim): Some WebSocket extensions may not be compatible with - // SPDY. We should omit them from a Sec-WebSocket-Extension header. - std::string name; - if (spdy_protocol_version <= 2) - name = base::StringToLowerASCII(iter.name()); - else - name = ":" + base::StringToLowerASCII(iter.name()); - (*headers)[name] = iter.values(); - continue; - } - // Others should be sent out to |headers|. - std::string name = base::StringToLowerASCII(iter.name()); - SpdyHeaderBlock::iterator found = headers->find(name); - if (found == headers->end()) { - (*headers)[name] = iter.values(); - } else { - // For now, websocket doesn't use multiple headers, but follows to http. - found->second.append(1, '\0'); // +=() doesn't append 0's - found->second.append(iter.values()); - } - } - - return true; -} - -std::string WebSocketHandshakeRequestHandler::GetRawRequest() { - DCHECK(!request_line_.empty()); - DCHECK(!headers_.empty()); - - std::string raw_request = request_line_ + headers_ + "\r\n"; - raw_length_ = raw_request.size(); - return raw_request; -} - -size_t WebSocketHandshakeRequestHandler::raw_length() const { - DCHECK_GT(raw_length_, 0); - return raw_length_; -} - -WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler() - : original_header_length_(0) {} - -WebSocketHandshakeResponseHandler::~WebSocketHandshakeResponseHandler() {} - -size_t WebSocketHandshakeResponseHandler::ParseRawResponse( - const char* data, int length) { - DCHECK_GT(length, 0); - if (HasResponse()) { - DCHECK(!status_line_.empty()); - // headers_ might be empty for wrong response from server. - - 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_); - int header_size = status_line_.size() + headers_.size(); - DCHECK_GE(original_header_length_, header_size); - header_separator_ = std::string(original_.data() + header_size, - original_header_length_ - header_size); - return original_header_length_ - old_original_length; -} - -bool WebSocketHandshakeResponseHandler::HasResponse() const { - return original_header_length_ > 0 && - static_cast<size_t>(original_header_length_) <= original_.size(); -} void ComputeSecWebSocketAccept(const std::string& key, std::string* accept) { @@ -354,145 +20,4 @@ void ComputeSecWebSocketAccept(const std::string& key, base::Base64Encode(hash, accept); } -bool WebSocketHandshakeResponseHandler::ParseResponseInfo( - const HttpResponseInfo& response_info, - const std::string& challenge) { - if (!response_info.headers.get()) - return false; - - // TODO(ricea): Eliminate all the reallocations and string copies. - std::string response_message; - response_message = response_info.headers->GetStatusLine(); - response_message += "\r\n"; - - AppendHeader(websockets::kUpgrade, - websockets::kWebSocketLowercase, - &response_message); - - AppendHeader( - HttpRequestHeaders::kConnection, websockets::kUpgrade, &response_message); - - std::string websocket_accept; - ComputeSecWebSocketAccept(challenge, &websocket_accept); - AppendHeader( - websockets::kSecWebSocketAccept, websocket_accept, &response_message); - - void* iter = NULL; - std::string name; - std::string value; - while (response_info.headers->EnumerateHeaderLines(&iter, &name, &value)) { - AppendHeader(name, value, &response_message); - } - response_message += "\r\n"; - - return ParseRawResponse(response_message.data(), - response_message.size()) == response_message.size(); -} - -bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock( - const SpdyHeaderBlock& headers, - const std::string& challenge, - int spdy_protocol_version) { - SpdyHeaderBlock::const_iterator status; - if (spdy_protocol_version <= 2) - status = headers.find("status"); - else - status = headers.find(":status"); - if (status == headers.end()) - return false; - - std::string hash = - base::SHA1HashString(challenge + websockets::kWebSocketGuid); - std::string websocket_accept; - base::Base64Encode(hash, &websocket_accept); - - std::string response_message = base::StringPrintf( - "%s %s\r\n", websockets::kHttpProtocolVersion, status->second.c_str()); - - AppendHeader( - websockets::kUpgrade, websockets::kWebSocketLowercase, &response_message); - AppendHeader( - HttpRequestHeaders::kConnection, websockets::kUpgrade, &response_message); - AppendHeader( - websockets::kSecWebSocketAccept, websocket_accept, &response_message); - - for (SpdyHeaderBlock::const_iterator iter = headers.begin(); - iter != headers.end(); - ++iter) { - // For each value, if the server sends a NUL-separated list of values, - // we separate that back out into individual headers for each value - // in the list. - if ((spdy_protocol_version <= 2 && - LowerCaseEqualsASCII(iter->first, "status")) || - (spdy_protocol_version >= 3 && - LowerCaseEqualsASCII(iter->first, ":status"))) { - // The status value is already handled as the first line of - // |response_message|. Just skip here. - continue; - } - const std::string& value = iter->second; - size_t start = 0; - size_t end = 0; - do { - end = value.find('\0', start); - std::string tval; - if (end != std::string::npos) - tval = value.substr(start, (end - start)); - else - tval = value.substr(start); - if (spdy_protocol_version >= 3 && - (LowerCaseEqualsASCII(iter->first, - websockets::kSecWebSocketProtocolSpdy3) || - LowerCaseEqualsASCII(iter->first, - websockets::kSecWebSocketExtensionsSpdy3))) - AppendHeader(iter->first.substr(1), tval, &response_message); - else - AppendHeader(iter->first, tval, &response_message); - start = end + 1; - } while (end != std::string::npos); - } - response_message += "\r\n"; - - 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_.empty()); - // headers_ might be empty for wrong response from server. - if (headers_.empty()) - return; - - 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_.empty()); - // headers_ might be empty for wrong response from server. - if (headers_.empty()) - return; - - headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len); -} - -std::string WebSocketHandshakeResponseHandler::GetRawResponse() const { - DCHECK(HasResponse()); - return original_.substr(0, original_header_length_); -} - -std::string WebSocketHandshakeResponseHandler::GetResponse() { - DCHECK(HasResponse()); - DCHECK(!status_line_.empty()); - // headers_ might be empty for wrong response from server. - - return status_line_ + headers_ + header_separator_; -} - } // namespace net diff --git a/net/websockets/websocket_handshake_handler.h b/net/websockets/websocket_handshake_handler.h index 73af66d..c65e6d9 100644 --- a/net/websockets/websocket_handshake_handler.h +++ b/net/websockets/websocket_handshake_handler.h @@ -1,124 +1,19 @@ // Copyright (c) 2012 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 "net/base/net_export.h" -#include "net/http/http_request_info.h" -#include "net/http/http_response_info.h" -#include "net/spdy/spdy_header_block.h" namespace net { +// Given a WebSocket handshake challenge, compute the correct response. +// TODO(ricea): There should probably be a test for this. void ComputeSecWebSocketAccept(const std::string& key, std::string* accept); -class NET_EXPORT_PRIVATE 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 and fills challenge data in - // |challenge|. - HttpRequestInfo GetRequestInfo(const GURL& url, std::string* challenge); - // Gets request as SpdyHeaderBlock. - // Also, fills challenge data in |challenge|. - bool GetRequestHeaderBlock(const GURL& url, - SpdyHeaderBlock* headers, - std::string* challenge, - int spdy_protocol_version); - // Gets WebSocket handshake raw request message to open WebSocket - // connection. - std::string GetRawRequest(); - // Calling raw_length is valid only after GetRawRequest() call. - size_t raw_length() const; - - private: - std::string request_line_; - std::string headers_; - int original_length_; - int raw_length_; - - DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestHandler); -}; - -class NET_EXPORT_PRIVATE WebSocketHandshakeResponseHandler { - public: - WebSocketHandshakeResponseHandler(); - ~WebSocketHandshakeResponseHandler(); - - // Parses WebSocket handshake response from WebSocket server. - // Returns number of bytes in |data| used for WebSocket handshake response - // message. 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, - const std::string& challenge); - // Parses WebSocket handshake response as SpdyHeaderBlock. - bool ParseResponseHeaderBlock(const SpdyHeaderBlock& headers, - const std::string& challenge, - int spdy_protocol_version); - - // 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 raw WebSocket handshake response received from WebSocket server. - std::string GetRawResponse() const; - - // Gets WebSocket handshake response message sent to renderer process. - std::string GetResponse(); - - private: - // Original bytes input by using ParseRawResponse(). - std::string original_; - // Number of bytes actually used for the handshake response in |original_|. - int original_header_length_; - - std::string status_line_; - std::string headers_; - std::string header_separator_; - - DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeResponseHandler); -}; - } // namespace net #endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_ diff --git a/net/websockets/websocket_handshake_handler_spdy_test.cc b/net/websockets/websocket_handshake_handler_spdy_test.cc deleted file mode 100644 index 064bdcf..0000000 --- a/net/websockets/websocket_handshake_handler_spdy_test.cc +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2013 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 <string> - -#include "net/socket/next_proto.h" -#include "net/spdy/spdy_header_block.h" -#include "net/spdy/spdy_websocket_test_util.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -namespace net { - -namespace { - -class WebSocketHandshakeHandlerSpdyTest - : public ::testing::Test, - public ::testing::WithParamInterface<NextProto> { - protected: - WebSocketHandshakeHandlerSpdyTest() : spdy_util_(GetParam()) {} - - SpdyWebSocketTestUtil spdy_util_; -}; - -INSTANTIATE_TEST_CASE_P( - NextProto, - WebSocketHandshakeHandlerSpdyTest, - testing::Values(kProtoDeprecatedSPDY2, - kProtoSPDY3, kProtoSPDY31, kProtoSPDY4)); - -TEST_P(WebSocketHandshakeHandlerSpdyTest, RequestResponse) { - 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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Sec-WebSocket-Extensions: foo\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"; - - EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage, - strlen(kHandshakeRequestMessage))); - - GURL url("ws://example.com/demo"); - std::string challenge; - SpdyHeaderBlock headers; - ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, - &headers, - &challenge, - spdy_util_.spdy_version())); - - EXPECT_EQ(url.path(), spdy_util_.GetHeader(headers, "path")); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "upgrade").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Upgrade").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "connection").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Connection").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Key").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-websocket-key").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Version").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-webSocket-version").empty()); - EXPECT_EQ("example.com", spdy_util_.GetHeader(headers, "host")); - EXPECT_EQ("http://example.com", spdy_util_.GetHeader(headers, "origin")); - EXPECT_EQ("sample", spdy_util_.GetHeader(headers, "sec-websocket-protocol")); - EXPECT_EQ("foo", spdy_util_.GetHeader(headers, "sec-websocket-extensions")); - EXPECT_EQ("ws", spdy_util_.GetHeader(headers, "scheme")); - EXPECT_EQ("WebSocket/13", spdy_util_.GetHeader(headers, "version")); - - static const char expected_challenge[] = "dGhlIHNhbXBsZSBub25jZQ=="; - - EXPECT_EQ(expected_challenge, challenge); - - headers.clear(); - - spdy_util_.SetHeader("status", "101 Switching Protocols", &headers); - spdy_util_.SetHeader("sec-websocket-protocol", "sample", &headers); - spdy_util_.SetHeader("sec-websocket-extensions", "foo", &headers); - - WebSocketHandshakeResponseHandler response_handler; - EXPECT_TRUE(response_handler.ParseResponseHeaderBlock( - headers, challenge, spdy_util_.spdy_version())); - 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-extensions: foo\r\n" - "sec-websocket-protocol: sample\r\n" - "\r\n"; - - EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse()); -} - -TEST_P(WebSocketHandshakeHandlerSpdyTest, RequestResponseWithCookies) { - 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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Sec-WebSocket-Extensions: foo\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n" - "\r\n"; - - EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage, - strlen(kHandshakeRequestMessage))); - - GURL url("ws://example.com/demo"); - std::string challenge; - SpdyHeaderBlock headers; - ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url, - &headers, - &challenge, - spdy_util_.spdy_version())); - - EXPECT_EQ(url.path(), spdy_util_.GetHeader(headers, "path")); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "upgrade").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Upgrade").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "connection").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Connection").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Key").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-websocket-key").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Version").empty()); - EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-webSocket-version").empty()); - EXPECT_EQ("example.com", spdy_util_.GetHeader(headers, "host")); - EXPECT_EQ("http://example.com", spdy_util_.GetHeader(headers, "origin")); - EXPECT_EQ("sample", spdy_util_.GetHeader(headers, "sec-websocket-protocol")); - EXPECT_EQ("foo", spdy_util_.GetHeader(headers, "sec-websocket-extensions")); - EXPECT_EQ("ws", spdy_util_.GetHeader(headers, "scheme")); - EXPECT_EQ("WebSocket/13", spdy_util_.GetHeader(headers, "version")); - EXPECT_EQ("WK-websocket-test=1; WK-websocket-test-httponly=1", - headers["cookie"]); - - const char expected_challenge[] = "dGhlIHNhbXBsZSBub25jZQ=="; - - EXPECT_EQ(expected_challenge, challenge); - - headers.clear(); - - spdy_util_.SetHeader("status", "101 Switching Protocols", &headers); - spdy_util_.SetHeader("sec-websocket-protocol", "sample", &headers); - spdy_util_.SetHeader("sec-websocket-extensions", "foo", &headers); - 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; - EXPECT_TRUE(response_handler.ParseResponseHeaderBlock( - headers, challenge, spdy_util_.spdy_version())); - 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-extensions: foo\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 - -} // namespace net diff --git a/net/websockets/websocket_handshake_handler_test.cc b/net/websockets/websocket_handshake_handler_test.cc index e59a982..8eff571 100644 --- a/net/websockets/websocket_handshake_handler_test.cc +++ b/net/websockets/websocket_handshake_handler_test.cc @@ -4,238 +4,14 @@ #include "net/websockets/websocket_handshake_handler.h" -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "net/http/http_response_headers.h" -#include "net/http/http_util.h" -#include "url/gurl.h" - #include "testing/gtest/include/gtest/gtest.h" -namespace { - -const char* const kCookieHeaders[] = { - "cookie", "cookie2" -}; - -const char* const kSetCookieHeaders[] = { - "set-cookie", "set-cookie2" -}; - -} // namespace - namespace net { -TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequest) { - 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: 13\r\n" - "\r\n"; - - 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" - "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: 13\r\n" - "Cookie: WK-websocket-test=1\r\n" - "\r\n"; - - 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" - "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: 13\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; - - 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; - - 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; - - static const char kBadMessage[] = "\n\n\r\net-Location: w"; - EXPECT_EQ(2U, handler.ParseRawResponse(kBadMessage, strlen(kBadMessage))); - EXPECT_TRUE(handler.HasResponse()); - EXPECT_EQ("\n\n", handler.GetResponse()); -} - -TEST(WebSocketHandshakeResponseHandlerTest, BadResponse2) { - WebSocketHandshakeResponseHandler handler; - - static const char kBadMessage[] = "\n\r\n\r\net-Location: w"; - EXPECT_EQ(3U, handler.ParseRawResponse(kBadMessage, strlen(kBadMessage))); - EXPECT_TRUE(handler.HasResponse()); - EXPECT_EQ("\n\r\n", handler.GetResponse()); -} - -TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) { - 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: 13\r\n" - "\r\n"; - - EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage, - strlen(kHandshakeRequestMessage))); - - 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; - - EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge)); - EXPECT_TRUE(response_handler.HasResponse()); +namespace { - 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"; +// TODO(ricea): Put a test for ComputeSecWebSocketAccept() here. - EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse()); -} +} // namespace } // namespace net diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc deleted file mode 100644 index eb653fa..0000000 --- a/net/websockets/websocket_job.cc +++ /dev/null @@ -1,693 +0,0 @@ -// Copyright (c) 2012 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_job.h" - -#include <algorithm> - -#include "base/bind.h" -#include "base/lazy_instance.h" -#include "net/base/io_buffer.h" -#include "net/base/net_errors.h" -#include "net/base/net_log.h" -#include "net/cookies/cookie_store.h" -#include "net/http/http_network_session.h" -#include "net/http/http_transaction_factory.h" -#include "net/http/http_util.h" -#include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_session_pool.h" -#include "net/url_request/url_request_context.h" -#include "net/websockets/websocket_handshake_handler.h" -#include "net/websockets/websocket_net_log_params.h" -#include "net/websockets/websocket_throttle.h" -#include "url/gurl.h" - -static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes. - -namespace { - -// lower-case header names. -const char* const kCookieHeaders[] = { - "cookie", "cookie2" -}; -const char* const kSetCookieHeaders[] = { - "set-cookie", "set-cookie2" -}; - -net::SocketStreamJob* WebSocketJobFactory( - const GURL& url, net::SocketStream::Delegate* delegate, - net::URLRequestContext* context, net::CookieStore* cookie_store) { - net::WebSocketJob* job = new net::WebSocketJob(delegate); - job->InitSocketStream(new net::SocketStream(url, job, context, cookie_store)); - return job; -} - -class WebSocketJobInitSingleton { - private: - friend struct base::DefaultLazyInstanceTraits<WebSocketJobInitSingleton>; - WebSocketJobInitSingleton() { - net::SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory); - net::SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory); - } -}; - -static base::LazyInstance<WebSocketJobInitSingleton> g_websocket_job_init = - LAZY_INSTANCE_INITIALIZER; - -} // anonymous namespace - -namespace net { - -// static -void WebSocketJob::EnsureInit() { - g_websocket_job_init.Get(); -} - -WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate) - : delegate_(delegate), - state_(INITIALIZED), - waiting_(false), - handshake_request_(new WebSocketHandshakeRequestHandler), - handshake_response_(new WebSocketHandshakeResponseHandler), - started_to_send_handshake_request_(false), - handshake_request_sent_(0), - response_cookies_save_index_(0), - spdy_protocol_version_(0), - save_next_cookie_running_(false), - callback_pending_(false), - weak_ptr_factory_(this), - weak_ptr_factory_for_send_pending_(this) { -} - -WebSocketJob::~WebSocketJob() { - DCHECK_EQ(CLOSED, state_); - DCHECK(!delegate_); - DCHECK(!socket_.get()); -} - -void WebSocketJob::Connect() { - DCHECK(socket_.get()); - DCHECK_EQ(state_, INITIALIZED); - state_ = CONNECTING; - socket_->Connect(); -} - -bool WebSocketJob::SendData(const char* data, int len) { - switch (state_) { - case INITIALIZED: - return false; - - case CONNECTING: - return SendHandshakeRequest(data, len); - - case OPEN: - { - scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(len); - memcpy(buffer->data(), data, len); - if (current_send_buffer_.get() || !send_buffer_queue_.empty()) { - send_buffer_queue_.push_back(buffer); - return true; - } - current_send_buffer_ = new DrainableIOBuffer(buffer.get(), len); - return SendDataInternal(current_send_buffer_->data(), - current_send_buffer_->BytesRemaining()); - } - - case CLOSING: - case CLOSED: - return false; - } - return false; -} - -void WebSocketJob::Close() { - if (state_ == CLOSED) - return; - - state_ = CLOSING; - if (current_send_buffer_.get()) { - // Will close in SendPending. - return; - } - state_ = CLOSED; - CloseInternal(); -} - -void WebSocketJob::RestartWithAuth(const AuthCredentials& credentials) { - state_ = CONNECTING; - socket_->RestartWithAuth(credentials); -} - -void WebSocketJob::DetachDelegate() { - state_ = CLOSED; - WebSocketThrottle::GetInstance()->RemoveFromQueue(this); - - scoped_refptr<WebSocketJob> protect(this); - weak_ptr_factory_.InvalidateWeakPtrs(); - weak_ptr_factory_for_send_pending_.InvalidateWeakPtrs(); - - delegate_ = NULL; - if (socket_.get()) - socket_->DetachDelegate(); - socket_ = NULL; - if (!callback_.is_null()) { - waiting_ = false; - callback_.Reset(); - Release(); // Balanced with OnStartOpenConnection(). - } -} - -int WebSocketJob::OnStartOpenConnection( - SocketStream* socket, const CompletionCallback& callback) { - DCHECK(callback_.is_null()); - state_ = CONNECTING; - - addresses_ = socket->address_list(); - if (!WebSocketThrottle::GetInstance()->PutInQueue(this)) { - return ERR_WS_THROTTLE_QUEUE_TOO_LARGE; - } - - if (delegate_) { - int result = delegate_->OnStartOpenConnection(socket, callback); - DCHECK_EQ(OK, result); - } - if (waiting_) { - // PutInQueue() may set |waiting_| true for throttling. In this case, - // Wakeup() will be called later. - callback_ = callback; - AddRef(); // Balanced when callback_ is cleared. - return ERR_IO_PENDING; - } - return TrySpdyStream(); -} - -void WebSocketJob::OnConnected( - SocketStream* socket, int max_pending_send_allowed) { - if (state_ == CLOSED) - return; - DCHECK_EQ(CONNECTING, state_); - if (delegate_) - delegate_->OnConnected(socket, max_pending_send_allowed); -} - -void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) { - DCHECK_NE(INITIALIZED, state_); - DCHECK_GT(amount_sent, 0); - if (state_ == CLOSED) - return; - if (state_ == CONNECTING) { - OnSentHandshakeRequest(socket, amount_sent); - return; - } - if (delegate_) { - DCHECK(state_ == OPEN || state_ == CLOSING); - if (!current_send_buffer_.get()) { - VLOG(1) - << "OnSentData current_send_buffer=NULL amount_sent=" << amount_sent; - return; - } - current_send_buffer_->DidConsume(amount_sent); - if (current_send_buffer_->BytesRemaining() > 0) - return; - - // We need to report amount_sent of original buffer size, instead of - // amount sent to |socket|. - amount_sent = current_send_buffer_->size(); - DCHECK_GT(amount_sent, 0); - current_send_buffer_ = NULL; - if (!weak_ptr_factory_for_send_pending_.HasWeakPtrs()) { - base::MessageLoopForIO::current()->PostTask( - FROM_HERE, - base::Bind(&WebSocketJob::SendPending, - weak_ptr_factory_for_send_pending_.GetWeakPtr())); - } - delegate_->OnSentData(socket, amount_sent); - } -} - -void WebSocketJob::OnReceivedData( - SocketStream* socket, const char* data, int len) { - DCHECK_NE(INITIALIZED, state_); - if (state_ == CLOSED) - return; - if (state_ == CONNECTING) { - OnReceivedHandshakeResponse(socket, data, len); - return; - } - DCHECK(state_ == OPEN || state_ == CLOSING); - if (delegate_ && len > 0) - delegate_->OnReceivedData(socket, data, len); -} - -void WebSocketJob::OnClose(SocketStream* socket) { - state_ = CLOSED; - WebSocketThrottle::GetInstance()->RemoveFromQueue(this); - - scoped_refptr<WebSocketJob> protect(this); - weak_ptr_factory_.InvalidateWeakPtrs(); - - SocketStream::Delegate* delegate = delegate_; - delegate_ = NULL; - socket_ = NULL; - if (!callback_.is_null()) { - waiting_ = false; - callback_.Reset(); - Release(); // Balanced with OnStartOpenConnection(). - } - if (delegate) - delegate->OnClose(socket); -} - -void WebSocketJob::OnAuthRequired( - SocketStream* socket, AuthChallengeInfo* auth_info) { - if (delegate_) - delegate_->OnAuthRequired(socket, auth_info); -} - -void WebSocketJob::OnSSLCertificateError( - SocketStream* socket, const SSLInfo& ssl_info, bool fatal) { - if (delegate_) - delegate_->OnSSLCertificateError(socket, ssl_info, fatal); -} - -void WebSocketJob::OnError(const SocketStream* socket, int error) { - if (delegate_ && error != ERR_PROTOCOL_SWITCHED) - delegate_->OnError(socket, error); -} - -void WebSocketJob::OnCreatedSpdyStream(int result) { - DCHECK(spdy_websocket_stream_.get()); - DCHECK(socket_.get()); - DCHECK_NE(ERR_IO_PENDING, result); - - if (state_ == CLOSED) { - result = ERR_ABORTED; - } else if (result == OK) { - state_ = CONNECTING; - result = ERR_PROTOCOL_SWITCHED; - } else { - spdy_websocket_stream_.reset(); - } - - CompleteIO(result); -} - -void WebSocketJob::OnSentSpdyHeaders() { - DCHECK_NE(INITIALIZED, state_); - if (state_ != CONNECTING) - return; - size_t original_length = handshake_request_->original_length(); - handshake_request_.reset(); - if (delegate_) - delegate_->OnSentData(socket_.get(), original_length); -} - -void WebSocketJob::OnSpdyResponseHeadersUpdated( - const SpdyHeaderBlock& response_headers) { - DCHECK_NE(INITIALIZED, state_); - if (state_ != CONNECTING) - return; - // TODO(toyoshim): Fallback to non-spdy connection? - handshake_response_->ParseResponseHeaderBlock(response_headers, - challenge_, - spdy_protocol_version_); - - SaveCookiesAndNotifyHeadersComplete(); -} - -void WebSocketJob::OnSentSpdyData(size_t bytes_sent) { - DCHECK_NE(INITIALIZED, state_); - DCHECK_NE(CONNECTING, state_); - if (state_ == CLOSED) - return; - if (!spdy_websocket_stream_.get()) - return; - OnSentData(socket_.get(), static_cast<int>(bytes_sent)); -} - -void WebSocketJob::OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) { - DCHECK_NE(INITIALIZED, state_); - DCHECK_NE(CONNECTING, state_); - if (state_ == CLOSED) - return; - if (!spdy_websocket_stream_.get()) - return; - if (buffer) { - OnReceivedData( - socket_.get(), buffer->GetRemainingData(), buffer->GetRemainingSize()); - } else { - OnReceivedData(socket_.get(), NULL, 0); - } -} - -void WebSocketJob::OnCloseSpdyStream() { - spdy_websocket_stream_.reset(); - OnClose(socket_.get()); -} - -bool WebSocketJob::SendHandshakeRequest(const char* data, int len) { - DCHECK_EQ(state_, CONNECTING); - if (started_to_send_handshake_request_) - return false; - if (!handshake_request_->ParseRequest(data, len)) - return false; - - AddCookieHeaderAndSend(); - return true; -} - -void WebSocketJob::AddCookieHeaderAndSend() { - bool allow = true; - if (delegate_ && !delegate_->CanGetCookies(socket_.get(), GetURLForCookies())) - allow = false; - - if (socket_.get() && delegate_ && state_ == CONNECTING) { - handshake_request_->RemoveHeaders(kCookieHeaders, - arraysize(kCookieHeaders)); - if (allow && socket_->cookie_store()) { - // Add cookies, including HttpOnly cookies. - CookieOptions cookie_options; - cookie_options.set_include_httponly(); - socket_->cookie_store()->GetCookiesWithOptionsAsync( - GetURLForCookies(), cookie_options, - base::Bind(&WebSocketJob::LoadCookieCallback, - weak_ptr_factory_.GetWeakPtr())); - } else { - DoSendData(); - } - } -} - -void WebSocketJob::LoadCookieCallback(const std::string& cookie) { - if (!cookie.empty()) - // TODO(tyoshino): Sending cookie means that connection doesn't need - // PRIVACY_MODE_ENABLED as cookies may be server-bound and channel id - // wouldn't negatively affect privacy anyway. Need to restart connection - // or refactor to determine cookie status prior to connecting. - handshake_request_->AppendHeaderIfMissing("Cookie", cookie); - DoSendData(); -} - -void WebSocketJob::DoSendData() { - if (spdy_websocket_stream_.get()) { - scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock); - handshake_request_->GetRequestHeaderBlock( - socket_->url(), headers.get(), &challenge_, spdy_protocol_version_); - spdy_websocket_stream_->SendRequest(headers.Pass()); - } else { - const std::string& handshake_request = - handshake_request_->GetRawRequest(); - handshake_request_sent_ = 0; - socket_->net_log()->AddEvent( - NetLog::TYPE_WEB_SOCKET_SEND_REQUEST_HEADERS, - base::Bind(&NetLogWebSocketHandshakeCallback, &handshake_request)); - socket_->SendData(handshake_request.data(), - handshake_request.size()); - } - // Just buffered in |handshake_request_|. - started_to_send_handshake_request_ = true; -} - -void WebSocketJob::OnSentHandshakeRequest( - SocketStream* socket, int amount_sent) { - DCHECK_EQ(state_, CONNECTING); - handshake_request_sent_ += amount_sent; - 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. - // Reset the handshake_request_ first in case this object is deleted by the - // delegate. - size_t original_length = handshake_request_->original_length(); - handshake_request_.reset(); - if (delegate_) - delegate_->OnSentData(socket, original_length); - } -} - -void WebSocketJob::OnReceivedHandshakeResponse( - SocketStream* socket, const char* data, int len) { - DCHECK_EQ(state_, CONNECTING); - if (handshake_response_->HasResponse()) { - // If we already has handshake response, received data should be frame - // data, not handshake message. - received_data_after_handshake_.insert( - received_data_after_handshake_.end(), data, data + len); - return; - } - - size_t response_length = handshake_response_->ParseRawResponse(data, len); - if (!handshake_response_->HasResponse()) { - // not yet. we need more data. - return; - } - // handshake message is completed. - std::string raw_response = handshake_response_->GetRawResponse(); - socket_->net_log()->AddEvent( - NetLog::TYPE_WEB_SOCKET_READ_RESPONSE_HEADERS, - base::Bind(&NetLogWebSocketHandshakeCallback, &raw_response)); - if (len - response_length > 0) { - // If we received extra data, it should be frame data. - DCHECK(received_data_after_handshake_.empty()); - received_data_after_handshake_.assign(data + response_length, data + len); - } - SaveCookiesAndNotifyHeadersComplete(); -} - -void WebSocketJob::SaveCookiesAndNotifyHeadersComplete() { - // handshake message is completed. - DCHECK(handshake_response_->HasResponse()); - - // Extract cookies from the handshake response into a temporary vector. - response_cookies_.clear(); - response_cookies_save_index_ = 0; - - handshake_response_->GetHeaders( - kSetCookieHeaders, arraysize(kSetCookieHeaders), &response_cookies_); - - // Now, loop over the response cookies, and attempt to persist each. - SaveNextCookie(); -} - -void WebSocketJob::NotifyHeadersComplete() { - // Remove cookie headers, with malformed headers preserved. - // Actual handshake should be done in Blink. - handshake_response_->RemoveHeaders( - kSetCookieHeaders, arraysize(kSetCookieHeaders)); - std::string handshake_response = handshake_response_->GetResponse(); - handshake_response_.reset(); - std::vector<char> received_data(handshake_response.begin(), - handshake_response.end()); - received_data.insert(received_data.end(), - received_data_after_handshake_.begin(), - received_data_after_handshake_.end()); - received_data_after_handshake_.clear(); - - state_ = OPEN; - - DCHECK(!received_data.empty()); - if (delegate_) - delegate_->OnReceivedData( - socket_.get(), &received_data.front(), received_data.size()); - - WebSocketThrottle::GetInstance()->RemoveFromQueue(this); -} - -void WebSocketJob::SaveNextCookie() { - if (!socket_.get() || !delegate_ || state_ != CONNECTING) - return; - - callback_pending_ = false; - save_next_cookie_running_ = true; - - if (socket_->cookie_store()) { - GURL url_for_cookies = GetURLForCookies(); - - CookieOptions options; - options.set_include_httponly(); - - // Loop as long as SetCookieWithOptionsAsync completes synchronously. Since - // CookieMonster's asynchronous operation APIs queue the callback to run it - // on the thread where the API was called, there won't be race. I.e. unless - // the callback is run synchronously, it won't be run in parallel with this - // method. - while (!callback_pending_ && - response_cookies_save_index_ < response_cookies_.size()) { - std::string cookie = response_cookies_[response_cookies_save_index_]; - response_cookies_save_index_++; - - if (!delegate_->CanSetCookie( - socket_.get(), url_for_cookies, cookie, &options)) - continue; - - callback_pending_ = true; - socket_->cookie_store()->SetCookieWithOptionsAsync( - url_for_cookies, cookie, options, - base::Bind(&WebSocketJob::OnCookieSaved, - weak_ptr_factory_.GetWeakPtr())); - } - } - - save_next_cookie_running_ = false; - - if (callback_pending_) - return; - - response_cookies_.clear(); - response_cookies_save_index_ = 0; - - NotifyHeadersComplete(); -} - -void WebSocketJob::OnCookieSaved(bool cookie_status) { - // Tell the caller of SetCookieWithOptionsAsync() that this completion - // callback is invoked. - // - If the caller checks callback_pending earlier than this callback, the - // caller exits to let this method continue iteration. - // - Otherwise, the caller continues iteration. - callback_pending_ = false; - - // Resume SaveNextCookie if the caller of SetCookieWithOptionsAsync() exited - // the loop. Otherwise, return. - if (save_next_cookie_running_) - return; - - SaveNextCookie(); -} - -GURL WebSocketJob::GetURLForCookies() const { - GURL url = socket_->url(); - std::string scheme = socket_->is_secure() ? "https" : "http"; - url::Replacements<char> replacements; - replacements.SetScheme(scheme.c_str(), url::Component(0, scheme.length())); - return url.ReplaceComponents(replacements); -} - -const AddressList& WebSocketJob::address_list() const { - return addresses_; -} - -int WebSocketJob::TrySpdyStream() { - if (!socket_.get()) - return ERR_FAILED; - - // Check if we have a SPDY session available. - HttpTransactionFactory* factory = - socket_->context()->http_transaction_factory(); - if (!factory) - return OK; - scoped_refptr<HttpNetworkSession> session = factory->GetSession(); - if (!session.get() || !session->params().enable_websocket_over_spdy) - return OK; - SpdySessionPool* spdy_pool = session->spdy_session_pool(); - PrivacyMode privacy_mode = socket_->privacy_mode(); - const SpdySessionKey key(HostPortPair::FromURL(socket_->url()), - socket_->proxy_server(), privacy_mode); - // Forbid wss downgrade to SPDY without SSL. - // TODO(toyoshim): Does it realize the same policy with HTTP? - base::WeakPtr<SpdySession> spdy_session = - spdy_pool->FindAvailableSession(key, *socket_->net_log()); - if (!spdy_session) - return OK; - - SSLInfo ssl_info; - bool was_npn_negotiated; - NextProto protocol_negotiated = kProtoUnknown; - bool use_ssl = spdy_session->GetSSLInfo( - &ssl_info, &was_npn_negotiated, &protocol_negotiated); - if (socket_->is_secure() && !use_ssl) - return OK; - - // Create SpdyWebSocketStream. - spdy_protocol_version_ = spdy_session->GetProtocolVersion(); - spdy_websocket_stream_.reset(new SpdyWebSocketStream(spdy_session, this)); - - int result = spdy_websocket_stream_->InitializeStream( - socket_->url(), MEDIUM, *socket_->net_log()); - if (result == OK) { - OnConnected(socket_.get(), kMaxPendingSendAllowed); - return ERR_PROTOCOL_SWITCHED; - } - if (result != ERR_IO_PENDING) { - spdy_websocket_stream_.reset(); - return OK; - } - - return ERR_IO_PENDING; -} - -void WebSocketJob::SetWaiting() { - waiting_ = true; -} - -bool WebSocketJob::IsWaiting() const { - return waiting_; -} - -void WebSocketJob::Wakeup() { - if (!waiting_) - return; - waiting_ = false; - DCHECK(!callback_.is_null()); - base::MessageLoopForIO::current()->PostTask( - FROM_HERE, - base::Bind(&WebSocketJob::RetryPendingIO, - weak_ptr_factory_.GetWeakPtr())); -} - -void WebSocketJob::RetryPendingIO() { - int result = TrySpdyStream(); - - // In the case of ERR_IO_PENDING, CompleteIO() will be called from - // OnCreatedSpdyStream(). - if (result != ERR_IO_PENDING) - CompleteIO(result); -} - -void WebSocketJob::CompleteIO(int result) { - // |callback_| may be null if OnClose() or DetachDelegate() was called. - if (!callback_.is_null()) { - CompletionCallback callback = callback_; - callback_.Reset(); - callback.Run(result); - Release(); // Balanced with OnStartOpenConnection(). - } -} - -bool WebSocketJob::SendDataInternal(const char* data, int length) { - if (spdy_websocket_stream_.get()) - return ERR_IO_PENDING == spdy_websocket_stream_->SendData(data, length); - if (socket_.get()) - return socket_->SendData(data, length); - return false; -} - -void WebSocketJob::CloseInternal() { - if (spdy_websocket_stream_.get()) - spdy_websocket_stream_->Close(); - if (socket_.get()) - socket_->Close(); -} - -void WebSocketJob::SendPending() { - if (current_send_buffer_.get()) - return; - - // Current buffer has been sent. Try next if any. - if (send_buffer_queue_.empty()) { - // No more data to send. - if (state_ == CLOSING) - CloseInternal(); - return; - } - - scoped_refptr<IOBufferWithSize> next_buffer = send_buffer_queue_.front(); - send_buffer_queue_.pop_front(); - current_send_buffer_ = - new DrainableIOBuffer(next_buffer.get(), next_buffer->size()); - SendDataInternal(current_send_buffer_->data(), - current_send_buffer_->BytesRemaining()); -} - -} // namespace net diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h deleted file mode 100644 index bad06cf..0000000 --- a/net/websockets/websocket_job.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) 2012 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. - -#ifndef NET_WEBSOCKETS_WEBSOCKET_JOB_H_ -#define NET_WEBSOCKETS_WEBSOCKET_JOB_H_ - -#include <deque> -#include <string> -#include <vector> - -#include "base/memory/weak_ptr.h" -#include "net/base/address_list.h" -#include "net/base/completion_callback.h" -#include "net/socket_stream/socket_stream_job.h" -#include "net/spdy/spdy_header_block.h" -#include "net/spdy/spdy_websocket_stream.h" - -class GURL; - -namespace net { - -class DrainableIOBuffer; -class SSLInfo; -class WebSocketHandshakeRequestHandler; -class WebSocketHandshakeResponseHandler; - -// WebSocket protocol specific job on SocketStream. -// It captures WebSocket handshake message and handles cookie operations. -// Chrome security policy doesn't allow renderer process (except dev tools) -// see HttpOnly cookies, so it injects cookie header in handshake request and -// strips set-cookie headers in handshake response. -// TODO(ukai): refactor websocket.cc to use this. -class NET_EXPORT WebSocketJob - : public SocketStreamJob, - public SocketStream::Delegate, - public SpdyWebSocketStream::Delegate { - public: - // This is state of WebSocket, not SocketStream. - enum State { - INITIALIZED = -1, - CONNECTING = 0, - OPEN = 1, - CLOSING = 2, - CLOSED = 3, - }; - - explicit WebSocketJob(SocketStream::Delegate* delegate); - - static void EnsureInit(); - - State state() const { return state_; } - void Connect() override; - bool SendData(const char* data, int len) override; - void Close() override; - void RestartWithAuth(const AuthCredentials& credentials) override; - void DetachDelegate() override; - - // SocketStream::Delegate methods. - int OnStartOpenConnection(SocketStream* socket, - const CompletionCallback& callback) override; - void OnConnected(SocketStream* socket, int max_pending_send_allowed) override; - void OnSentData(SocketStream* socket, int amount_sent) override; - void OnReceivedData(SocketStream* socket, const char* data, int len) override; - void OnClose(SocketStream* socket) override; - void OnAuthRequired(SocketStream* socket, - AuthChallengeInfo* auth_info) override; - void OnSSLCertificateError(SocketStream* socket, - const SSLInfo& ssl_info, - bool fatal) override; - void OnError(const SocketStream* socket, int error) override; - - // SpdyWebSocketStream::Delegate methods. - void OnCreatedSpdyStream(int status) override; - void OnSentSpdyHeaders() override; - void OnSpdyResponseHeadersUpdated( - const SpdyHeaderBlock& response_headers) override; - void OnSentSpdyData(size_t bytes_sent) override; - void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) override; - void OnCloseSpdyStream() override; - - private: - friend class WebSocketThrottle; - friend class WebSocketJobTest; - ~WebSocketJob() override; - - bool SendHandshakeRequest(const char* data, int len); - void AddCookieHeaderAndSend(); - void LoadCookieCallback(const std::string& cookie); - - void OnSentHandshakeRequest(SocketStream* socket, int amount_sent); - // Parses received data into handshake_response_. When finished receiving the - // response, calls SaveCookiesAndNotifyHeadersComplete(). - void OnReceivedHandshakeResponse( - SocketStream* socket, const char* data, int len); - // Saves received cookies to the cookie store, and then notifies the - // delegate_ of completion of handshake. - void SaveCookiesAndNotifyHeadersComplete(); - void SaveNextCookie(); - void OnCookieSaved(bool cookie_status); - // Clears variables for handling cookies, rebuilds handshake string excluding - // cookies, and then pass the handshake string to delegate_. - void NotifyHeadersComplete(); - void DoSendData(); - - GURL GetURLForCookies() const; - - const AddressList& address_list() const; - int TrySpdyStream(); - void SetWaiting(); - bool IsWaiting() const; - void Wakeup(); - void RetryPendingIO(); - void CompleteIO(int result); - - bool SendDataInternal(const char* data, int length); - void CloseInternal(); - void SendPending(); - - SocketStream::Delegate* delegate_; - State state_; - bool waiting_; - AddressList addresses_; - CompletionCallback callback_; // for throttling. - - scoped_ptr<WebSocketHandshakeRequestHandler> handshake_request_; - scoped_ptr<WebSocketHandshakeResponseHandler> handshake_response_; - - bool started_to_send_handshake_request_; - size_t handshake_request_sent_; - - std::vector<std::string> response_cookies_; - size_t response_cookies_save_index_; - - std::deque<scoped_refptr<IOBufferWithSize> > send_buffer_queue_; - scoped_refptr<DrainableIOBuffer> current_send_buffer_; - std::vector<char> received_data_after_handshake_; - - int spdy_protocol_version_; - scoped_ptr<SpdyWebSocketStream> spdy_websocket_stream_; - std::string challenge_; - - bool save_next_cookie_running_; - bool callback_pending_; - - base::WeakPtrFactory<WebSocketJob> weak_ptr_factory_; - base::WeakPtrFactory<WebSocketJob> weak_ptr_factory_for_send_pending_; - - DISALLOW_COPY_AND_ASSIGN(WebSocketJob); -}; - -} // namespace - -#endif // NET_WEBSOCKETS_WEBSOCKET_JOB_H_ diff --git a/net/websockets/websocket_job_test.cc b/net/websockets/websocket_job_test.cc deleted file mode 100644 index c84af8f..0000000 --- a/net/websockets/websocket_job_test.cc +++ /dev/null @@ -1,1287 +0,0 @@ -// Copyright 2013 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_job.h" - -#include <string> -#include <vector> - -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/callback.h" -#include "base/memory/ref_counted.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "net/base/completion_callback.h" -#include "net/base/net_errors.h" -#include "net/base/test_completion_callback.h" -#include "net/cookies/cookie_store.h" -#include "net/cookies/cookie_store_test_helpers.h" -#include "net/dns/mock_host_resolver.h" -#include "net/http/http_transaction_factory.h" -#include "net/http/transport_security_state.h" -#include "net/proxy/proxy_service.h" -#include "net/socket/next_proto.h" -#include "net/socket/socket_test_util.h" -#include "net/socket_stream/socket_stream.h" -#include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_websocket_test_util.h" -#include "net/ssl/ssl_config_service.h" -#include "net/url_request/url_request_context.h" -#include "net/websockets/websocket_throttle.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/platform_test.h" -#include "url/gurl.h" - -namespace net { - -namespace { - -class MockSocketStream : public SocketStream { - public: - MockSocketStream(const GURL& url, SocketStream::Delegate* delegate, - URLRequestContext* context, CookieStore* cookie_store) - : SocketStream(url, delegate, context, cookie_store) {} - - void Connect() override {} - bool SendData(const char* data, int len) override { - sent_data_ += std::string(data, len); - return true; - } - - void Close() override {} - void RestartWithAuth(const AuthCredentials& credentials) override {} - - void DetachDelegate() override { delegate_ = NULL; } - - const std::string& sent_data() const { - return sent_data_; - } - - protected: - ~MockSocketStream() override {} - - private: - std::string sent_data_; -}; - -class MockSocketStreamDelegate : public SocketStream::Delegate { - public: - MockSocketStreamDelegate() - : amount_sent_(0), allow_all_cookies_(true) {} - void set_allow_all_cookies(bool allow_all_cookies) { - allow_all_cookies_ = allow_all_cookies; - } - ~MockSocketStreamDelegate() override {} - - void SetOnStartOpenConnection(const base::Closure& callback) { - on_start_open_connection_ = callback; - } - void SetOnConnected(const base::Closure& callback) { - on_connected_ = callback; - } - void SetOnSentData(const base::Closure& callback) { - on_sent_data_ = callback; - } - void SetOnReceivedData(const base::Closure& callback) { - on_received_data_ = callback; - } - void SetOnClose(const base::Closure& callback) { - on_close_ = callback; - } - - int OnStartOpenConnection(SocketStream* socket, - const CompletionCallback& callback) override { - if (!on_start_open_connection_.is_null()) - on_start_open_connection_.Run(); - return OK; - } - void OnConnected(SocketStream* socket, - int max_pending_send_allowed) override { - if (!on_connected_.is_null()) - on_connected_.Run(); - } - void OnSentData(SocketStream* socket, int amount_sent) override { - amount_sent_ += amount_sent; - if (!on_sent_data_.is_null()) - on_sent_data_.Run(); - } - void OnReceivedData(SocketStream* socket, - const char* data, - int len) override { - received_data_ += std::string(data, len); - if (!on_received_data_.is_null()) - on_received_data_.Run(); - } - void OnClose(SocketStream* socket) override { - if (!on_close_.is_null()) - on_close_.Run(); - } - bool CanGetCookies(SocketStream* socket, const GURL& url) override { - return allow_all_cookies_; - } - bool CanSetCookie(SocketStream* request, - const GURL& url, - const std::string& cookie_line, - CookieOptions* options) override { - return allow_all_cookies_; - } - - size_t amount_sent() const { return amount_sent_; } - const std::string& received_data() const { return received_data_; } - - private: - int amount_sent_; - bool allow_all_cookies_; - std::string received_data_; - base::Closure on_start_open_connection_; - base::Closure on_connected_; - base::Closure on_sent_data_; - base::Closure on_received_data_; - base::Closure on_close_; -}; - -class MockCookieStore : public CookieStore { - public: - struct Entry { - GURL url; - std::string cookie_line; - CookieOptions options; - }; - - MockCookieStore() {} - - bool SetCookieWithOptions(const GURL& url, - const std::string& cookie_line, - const CookieOptions& options) { - Entry entry; - entry.url = url; - entry.cookie_line = cookie_line; - entry.options = options; - entries_.push_back(entry); - return true; - } - - std::string GetCookiesWithOptions(const GURL& url, - const CookieOptions& options) { - std::string result; - for (size_t i = 0; i < entries_.size(); i++) { - Entry& entry = entries_[i]; - if (url == entry.url) { - if (!result.empty()) { - result += "; "; - } - result += entry.cookie_line; - } - } - return result; - } - - // CookieStore: - void SetCookieWithOptionsAsync(const GURL& url, - const std::string& cookie_line, - const CookieOptions& options, - const SetCookiesCallback& callback) override { - bool result = SetCookieWithOptions(url, cookie_line, options); - if (!callback.is_null()) - callback.Run(result); - } - - void GetCookiesWithOptionsAsync(const GURL& url, - const CookieOptions& options, - const GetCookiesCallback& callback) override { - if (!callback.is_null()) - callback.Run(GetCookiesWithOptions(url, options)); - } - - void GetAllCookiesForURLAsync( - const GURL& url, - const GetCookieListCallback& callback) override { - ADD_FAILURE(); - } - - void DeleteCookieAsync(const GURL& url, - const std::string& cookie_name, - const base::Closure& callback) override { - ADD_FAILURE(); - } - - void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin, - const base::Time& delete_end, - const DeleteCallback& callback) override { - ADD_FAILURE(); - } - - void DeleteAllCreatedBetweenForHostAsync( - const base::Time delete_begin, - const base::Time delete_end, - const GURL& url, - const DeleteCallback& callback) override { - ADD_FAILURE(); - } - - void DeleteSessionCookiesAsync(const DeleteCallback&) override { - ADD_FAILURE(); - } - - CookieMonster* GetCookieMonster() override { return NULL; } - - scoped_ptr<CookieStore::CookieChangedSubscription> - AddCallbackForCookie(const GURL& url, const std::string& name, - const CookieChangedCallback& callback) override { - ADD_FAILURE(); - return scoped_ptr<CookieChangedSubscription>(); - } - - const std::vector<Entry>& entries() const { return entries_; } - - private: - friend class base::RefCountedThreadSafe<MockCookieStore>; - ~MockCookieStore() override {} - - std::vector<Entry> entries_; -}; - -class MockSSLConfigService : public SSLConfigService { - public: - void GetSSLConfig(SSLConfig* config) override {} - - protected: - ~MockSSLConfigService() override {} -}; - -class MockURLRequestContext : public URLRequestContext { - public: - explicit MockURLRequestContext(CookieStore* cookie_store) - : transport_security_state_() { - set_cookie_store(cookie_store); - set_transport_security_state(&transport_security_state_); - base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000); - bool include_subdomains = false; - transport_security_state_.AddHSTS("upgrademe.com", expiry, - include_subdomains); - } - - ~MockURLRequestContext() override { AssertNoURLRequests(); } - - private: - TransportSecurityState transport_security_state_; -}; - -class MockHttpTransactionFactory : public HttpTransactionFactory { - public: - MockHttpTransactionFactory(NextProto next_proto, - OrderedSocketData* data, - bool enable_websocket_over_spdy) { - data_ = data; - MockConnect connect_data(SYNCHRONOUS, OK); - data_->set_connect_data(connect_data); - session_deps_.reset(new SpdySessionDependencies(next_proto)); - session_deps_->enable_websocket_over_spdy = enable_websocket_over_spdy; - session_deps_->socket_factory->AddSocketDataProvider(data_); - http_session_ = - SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); - host_port_pair_.set_host("example.com"); - host_port_pair_.set_port(80); - spdy_session_key_ = SpdySessionKey(host_port_pair_, - ProxyServer::Direct(), - PRIVACY_MODE_DISABLED); - session_ = CreateInsecureSpdySession( - http_session_, spdy_session_key_, BoundNetLog()); - } - - int CreateTransaction(RequestPriority priority, - scoped_ptr<HttpTransaction>* trans) override { - NOTREACHED(); - return ERR_UNEXPECTED; - } - - HttpCache* GetCache() override { - NOTREACHED(); - return NULL; - } - - HttpNetworkSession* GetSession() override { return http_session_.get(); } - - private: - OrderedSocketData* data_; - scoped_ptr<SpdySessionDependencies> session_deps_; - scoped_refptr<HttpNetworkSession> http_session_; - base::WeakPtr<SpdySession> session_; - HostPortPair host_port_pair_; - SpdySessionKey spdy_session_key_; -}; - -class DeletingSocketStreamDelegate : public SocketStream::Delegate { - public: - DeletingSocketStreamDelegate() - : delete_next_(false) {} - - // Since this class needs to be able to delete |job_|, it must be the only - // reference holder (except for temporary references). Provide access to the - // pointer for tests to use. - WebSocketJob* job() { return job_.get(); } - - void set_job(WebSocketJob* job) { job_ = job; } - - // After calling this, the next call to a method on this delegate will delete - // the WebSocketJob object. - void set_delete_next(bool delete_next) { delete_next_ = delete_next; } - - void DeleteJobMaybe() { - if (delete_next_) { - job_->DetachContext(); - job_->DetachDelegate(); - job_ = NULL; - } - } - - // SocketStream::Delegate implementation - - // OnStartOpenConnection() is not implemented by SocketStreamDispatcherHost - - void OnConnected(SocketStream* socket, - int max_pending_send_allowed) override { - DeleteJobMaybe(); - } - - void OnSentData(SocketStream* socket, int amount_sent) override { - DeleteJobMaybe(); - } - - void OnReceivedData(SocketStream* socket, - const char* data, - int len) override { - DeleteJobMaybe(); - } - - void OnClose(SocketStream* socket) override { DeleteJobMaybe(); } - - void OnAuthRequired(SocketStream* socket, - AuthChallengeInfo* auth_info) override { - DeleteJobMaybe(); - } - - void OnSSLCertificateError(SocketStream* socket, - const SSLInfo& ssl_info, - bool fatal) override { - DeleteJobMaybe(); - } - - void OnError(const SocketStream* socket, int error) override { - DeleteJobMaybe(); - } - - // CanGetCookies() and CanSetCookies() do not appear to be able to delete the - // WebSocketJob object. - - private: - scoped_refptr<WebSocketJob> job_; - bool delete_next_; -}; - -} // namespace - -class WebSocketJobTest : public PlatformTest, - public ::testing::WithParamInterface<NextProto> { - public: - WebSocketJobTest() - : spdy_util_(GetParam()), - enable_websocket_over_spdy_(false) {} - - void SetUp() override { - stream_type_ = STREAM_INVALID; - cookie_store_ = new MockCookieStore; - context_.reset(new MockURLRequestContext(cookie_store_.get())); - } - void TearDown() override { - cookie_store_ = NULL; - context_.reset(); - websocket_ = NULL; - socket_ = NULL; - } - void DoSendRequest() { - EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie, - kHandshakeRequestWithoutCookieLength)); - } - void DoSendData() { - if (received_data().size() == kHandshakeResponseWithoutCookieLength) - websocket_->SendData(kDataHello, kDataHelloLength); - } - void DoSync() { - sync_test_callback_.callback().Run(OK); - } - int WaitForResult() { - return sync_test_callback_.WaitForResult(); - } - - protected: - enum StreamType { - STREAM_INVALID, - STREAM_MOCK_SOCKET, - STREAM_SOCKET, - STREAM_SPDY_WEBSOCKET, - }; - enum ThrottlingOption { - THROTTLING_OFF, - THROTTLING_ON, - }; - enum SpdyOption { - SPDY_OFF, - SPDY_ON, - }; - void InitWebSocketJob(const GURL& url, - MockSocketStreamDelegate* delegate, - StreamType stream_type) { - DCHECK_NE(STREAM_INVALID, stream_type); - stream_type_ = stream_type; - websocket_ = new WebSocketJob(delegate); - - if (stream_type == STREAM_MOCK_SOCKET) - socket_ = new MockSocketStream(url, websocket_.get(), context_.get(), - NULL); - - if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) { - if (stream_type == STREAM_SPDY_WEBSOCKET) { - http_factory_.reset(new MockHttpTransactionFactory( - GetParam(), data_.get(), enable_websocket_over_spdy_)); - context_->set_http_transaction_factory(http_factory_.get()); - } - - ssl_config_service_ = new MockSSLConfigService(); - context_->set_ssl_config_service(ssl_config_service_.get()); - proxy_service_.reset(ProxyService::CreateDirect()); - context_->set_proxy_service(proxy_service_.get()); - host_resolver_.reset(new MockHostResolver); - context_->set_host_resolver(host_resolver_.get()); - - socket_ = new SocketStream(url, websocket_.get(), context_.get(), NULL); - socket_factory_.reset(new MockClientSocketFactory); - DCHECK(data_.get()); - socket_factory_->AddSocketDataProvider(data_.get()); - socket_->SetClientSocketFactory(socket_factory_.get()); - } - - websocket_->InitSocketStream(socket_.get()); - // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create - // a WebSocketJob purely to block another one in a throttling test, we don't - // perform a real connect. In that case, the following address is used - // instead. - IPAddressNumber ip; - ParseIPLiteralToNumber("127.0.0.1", &ip); - websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80); - } - void SkipToConnecting() { - websocket_->state_ = WebSocketJob::CONNECTING; - ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get())); - } - WebSocketJob::State GetWebSocketJobState() { - return websocket_->state_; - } - void CloseWebSocketJob() { - if (websocket_->socket_.get()) { - websocket_->socket_->DetachDelegate(); - WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get()); - } - websocket_->state_ = WebSocketJob::CLOSED; - websocket_->delegate_ = NULL; - websocket_->socket_ = NULL; - } - SocketStream* GetSocket(SocketStreamJob* job) { - return job->socket_.get(); - } - const std::string& sent_data() const { - DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_); - MockSocketStream* socket = - static_cast<MockSocketStream*>(socket_.get()); - DCHECK(socket); - return socket->sent_data(); - } - const std::string& received_data() const { - DCHECK_NE(STREAM_INVALID, stream_type_); - MockSocketStreamDelegate* delegate = - static_cast<MockSocketStreamDelegate*>(websocket_->delegate_); - DCHECK(delegate); - return delegate->received_data(); - } - - void TestSimpleHandshake(); - void TestSlowHandshake(); - void TestHandshakeWithCookie(); - void TestHandshakeWithCookieButNotAllowed(); - void TestHSTSUpgrade(); - void TestInvalidSendData(); - void TestConnectByWebSocket(ThrottlingOption throttling); - void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling); - void TestThrottlingLimit(); - - SpdyWebSocketTestUtil spdy_util_; - StreamType stream_type_; - scoped_refptr<MockCookieStore> cookie_store_; - scoped_ptr<MockURLRequestContext> context_; - scoped_refptr<WebSocketJob> websocket_; - scoped_refptr<SocketStream> socket_; - scoped_ptr<MockClientSocketFactory> socket_factory_; - scoped_ptr<OrderedSocketData> data_; - TestCompletionCallback sync_test_callback_; - scoped_refptr<MockSSLConfigService> ssl_config_service_; - scoped_ptr<ProxyService> proxy_service_; - scoped_ptr<MockHostResolver> host_resolver_; - scoped_ptr<MockHttpTransactionFactory> http_factory_; - - // Must be set before call to enable_websocket_over_spdy, defaults to false. - bool enable_websocket_over_spdy_; - - static const char kHandshakeRequestWithoutCookie[]; - static const char kHandshakeRequestWithCookie[]; - static const char kHandshakeRequestWithFilteredCookie[]; - static const char kHandshakeResponseWithoutCookie[]; - static const char kHandshakeResponseWithCookie[]; - static const char kDataHello[]; - static const char kDataWorld[]; - static const char* const kHandshakeRequestForSpdy[]; - static const char* const kHandshakeResponseForSpdy[]; - static const size_t kHandshakeRequestWithoutCookieLength; - static const size_t kHandshakeRequestWithCookieLength; - static const size_t kHandshakeRequestWithFilteredCookieLength; - static const size_t kHandshakeResponseWithoutCookieLength; - static const size_t kHandshakeResponseWithCookieLength; - static const size_t kDataHelloLength; - static const size_t kDataWorldLength; -}; - -// Tests using this fixture verify that the WebSocketJob can handle being -// deleted while calling back to the delegate correctly. These tests need to be -// run under AddressSanitizer or other systems for detecting use-after-free -// errors in order to find problems. -class WebSocketJobDeleteTest : public ::testing::Test { - protected: - WebSocketJobDeleteTest() - : delegate_(new DeletingSocketStreamDelegate), - cookie_store_(new MockCookieStore), - context_(new MockURLRequestContext(cookie_store_.get())) { - WebSocketJob* websocket = new WebSocketJob(delegate_.get()); - delegate_->set_job(websocket); - - socket_ = new MockSocketStream( - GURL("ws://127.0.0.1/"), websocket, context_.get(), NULL); - - websocket->InitSocketStream(socket_.get()); - } - - void SetDeleteNext() { return delegate_->set_delete_next(true); } - WebSocketJob* job() { return delegate_->job(); } - - scoped_ptr<DeletingSocketStreamDelegate> delegate_; - scoped_refptr<MockCookieStore> cookie_store_; - scoped_ptr<MockURLRequestContext> context_; - scoped_refptr<SocketStream> socket_; -}; - -const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] = - "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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"; - -const char WebSocketJobTest::kHandshakeRequestWithCookie[] = - "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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Cookie: WK-test=1\r\n" - "\r\n"; - -const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] = - "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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Cookie: CR-test=1; CR-test-httponly=1\r\n" - "\r\n"; - -const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] = - "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"; - -const char WebSocketJobTest::kHandshakeResponseWithCookie[] = - "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: CR-set-test=1\r\n" - "\r\n"; - -const char WebSocketJobTest::kDataHello[] = "Hello, "; - -const char WebSocketJobTest::kDataWorld[] = "World!\n"; - -const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength = - arraysize(kHandshakeRequestWithoutCookie) - 1; -const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength = - arraysize(kHandshakeRequestWithCookie) - 1; -const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength = - arraysize(kHandshakeRequestWithFilteredCookie) - 1; -const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength = - arraysize(kHandshakeResponseWithoutCookie) - 1; -const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength = - arraysize(kHandshakeResponseWithCookie) - 1; -const size_t WebSocketJobTest::kDataHelloLength = - arraysize(kDataHello) - 1; -const size_t WebSocketJobTest::kDataWorldLength = - arraysize(kDataWorld) - 1; - -void WebSocketJobTest::TestSimpleHandshake() { - GURL url("ws://example.com/demo"); - MockSocketStreamDelegate delegate; - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - DoSendRequest(); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), - kHandshakeRequestWithoutCookieLength); - EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); - - websocket_->OnReceivedData(socket_.get(), - kHandshakeResponseWithoutCookie, - kHandshakeResponseWithoutCookieLength); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); - EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); - CloseWebSocketJob(); -} - -void WebSocketJobTest::TestSlowHandshake() { - GURL url("ws://example.com/demo"); - MockSocketStreamDelegate delegate; - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - DoSendRequest(); - // We assume request is sent in one data chunk (from WebKit) - // We don't support streaming request. - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), - kHandshakeRequestWithoutCookieLength); - EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); - - std::vector<std::string> lines; - base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines); - for (size_t i = 0; i < lines.size() - 2; i++) { - std::string line = lines[i] + "\r\n"; - SCOPED_TRACE("Line: " + line); - websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size()); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_TRUE(delegate.received_data().empty()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - } - websocket_->OnReceivedData(socket_.get(), "\r\n", 2); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_FALSE(delegate.received_data().empty()); - EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); - EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); - CloseWebSocketJob(); -} - -INSTANTIATE_TEST_CASE_P( - NextProto, - WebSocketJobTest, - testing::Values(kProtoDeprecatedSPDY2, - kProtoSPDY3, kProtoSPDY31, kProtoSPDY4)); - -TEST_P(WebSocketJobTest, DelayedCookies) { - enable_websocket_over_spdy_ = true; - GURL url("ws://example.com/demo"); - GURL cookieUrl("http://example.com/demo"); - CookieOptions cookie_options; - scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster(); - context_->set_cookie_store(cookie_store.get()); - cookie_store->SetCookieWithOptionsAsync(cookieUrl, - "CR-test=1", - cookie_options, - CookieMonster::SetCookiesCallback()); - cookie_options.set_include_httponly(); - cookie_store->SetCookieWithOptionsAsync( - cookieUrl, "CR-test-httponly=1", cookie_options, - CookieMonster::SetCookiesCallback()); - - MockSocketStreamDelegate delegate; - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - bool sent = websocket_->SendData(kHandshakeRequestWithCookie, - kHandshakeRequestWithCookieLength); - EXPECT_TRUE(sent); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), - kHandshakeRequestWithFilteredCookieLength); - EXPECT_EQ(kHandshakeRequestWithCookieLength, - delegate.amount_sent()); - - websocket_->OnReceivedData(socket_.get(), - kHandshakeResponseWithCookie, - kHandshakeResponseWithCookieLength); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); - EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); - - CloseWebSocketJob(); -} - -void WebSocketJobTest::TestHandshakeWithCookie() { - GURL url("ws://example.com/demo"); - GURL cookieUrl("http://example.com/demo"); - CookieOptions cookie_options; - cookie_store_->SetCookieWithOptions( - cookieUrl, "CR-test=1", cookie_options); - cookie_options.set_include_httponly(); - cookie_store_->SetCookieWithOptions( - cookieUrl, "CR-test-httponly=1", cookie_options); - - MockSocketStreamDelegate delegate; - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - bool sent = websocket_->SendData(kHandshakeRequestWithCookie, - kHandshakeRequestWithCookieLength); - EXPECT_TRUE(sent); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), - kHandshakeRequestWithFilteredCookieLength); - EXPECT_EQ(kHandshakeRequestWithCookieLength, - delegate.amount_sent()); - - websocket_->OnReceivedData(socket_.get(), - kHandshakeResponseWithCookie, - kHandshakeResponseWithCookieLength); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); - EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); - - EXPECT_EQ(3U, cookie_store_->entries().size()); - EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); - EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); - EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); - EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); - EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); - EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); - - CloseWebSocketJob(); -} - -void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() { - GURL url("ws://example.com/demo"); - GURL cookieUrl("http://example.com/demo"); - CookieOptions cookie_options; - cookie_store_->SetCookieWithOptions( - cookieUrl, "CR-test=1", cookie_options); - cookie_options.set_include_httponly(); - cookie_store_->SetCookieWithOptions( - cookieUrl, "CR-test-httponly=1", cookie_options); - - MockSocketStreamDelegate delegate; - delegate.set_allow_all_cookies(false); - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - bool sent = websocket_->SendData(kHandshakeRequestWithCookie, - kHandshakeRequestWithCookieLength); - EXPECT_TRUE(sent); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength); - EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent()); - - websocket_->OnReceivedData(socket_.get(), - kHandshakeResponseWithCookie, - kHandshakeResponseWithCookieLength); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); - EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); - - EXPECT_EQ(2U, cookie_store_->entries().size()); - EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); - EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); - EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); - EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); - - CloseWebSocketJob(); -} - -void WebSocketJobTest::TestHSTSUpgrade() { - GURL url("ws://upgrademe.com/"); - MockSocketStreamDelegate delegate; - scoped_refptr<SocketStreamJob> job = - SocketStreamJob::CreateSocketStreamJob( - url, &delegate, context_->transport_security_state(), - context_->ssl_config_service(), NULL, NULL); - EXPECT_TRUE(GetSocket(job.get())->is_secure()); - job->DetachDelegate(); - - url = GURL("ws://donotupgrademe.com/"); - job = SocketStreamJob::CreateSocketStreamJob( - url, &delegate, context_->transport_security_state(), - context_->ssl_config_service(), NULL, NULL); - EXPECT_FALSE(GetSocket(job.get())->is_secure()); - job->DetachDelegate(); -} - -void WebSocketJobTest::TestInvalidSendData() { - GURL url("ws://example.com/demo"); - MockSocketStreamDelegate delegate; - InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); - SkipToConnecting(); - - DoSendRequest(); - // We assume request is sent in one data chunk (from WebKit) - // We don't support streaming request. - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - websocket_->OnSentData(socket_.get(), - kHandshakeRequestWithoutCookieLength); - EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); - - // We could not send any data until connection is established. - bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie, - kHandshakeRequestWithoutCookieLength); - EXPECT_FALSE(sent); - EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); - CloseWebSocketJob(); -} - -// Following tests verify cooperation between WebSocketJob and SocketStream. -// Other former tests use MockSocketStream as SocketStream, so we could not -// check SocketStream behavior. -// OrderedSocketData provide socket level verifiation by checking out-going -// packets in comparison with the MockWrite array and emulating in-coming -// packets with MockRead array. - -void WebSocketJobTest::TestConnectByWebSocket( - ThrottlingOption throttling) { - // This is a test for verifying cooperation between WebSocketJob and - // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic - // situation. If |throttling| was |THROTTLING_ON|, throttling limits the - // latter connection. - MockWrite writes[] = { - MockWrite(ASYNC, - kHandshakeRequestWithoutCookie, - kHandshakeRequestWithoutCookieLength, - 1), - MockWrite(ASYNC, - kDataHello, - kDataHelloLength, - 3) - }; - MockRead reads[] = { - MockRead(ASYNC, - kHandshakeResponseWithoutCookie, - kHandshakeResponseWithoutCookieLength, - 2), - MockRead(ASYNC, - kDataWorld, - kDataWorldLength, - 4), - MockRead(SYNCHRONOUS, 0, 5) // EOF - }; - data_.reset(new OrderedSocketData( - reads, arraysize(reads), writes, arraysize(writes))); - - GURL url("ws://example.com/demo"); - MockSocketStreamDelegate delegate; - WebSocketJobTest* test = this; - if (throttling == THROTTLING_ON) - delegate.SetOnStartOpenConnection( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); - delegate.SetOnConnected( - base::Bind(&WebSocketJobTest::DoSendRequest, - base::Unretained(test))); - delegate.SetOnReceivedData( - base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); - delegate.SetOnClose( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); - InitWebSocketJob(url, &delegate, STREAM_SOCKET); - - scoped_refptr<WebSocketJob> block_websocket; - if (throttling == THROTTLING_ON) { - // Create former WebSocket object which obstructs the latter one. - block_websocket = new WebSocketJob(NULL); - block_websocket->addresses_ = AddressList(websocket_->address_list()); - ASSERT_TRUE( - WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); - } - - websocket_->Connect(); - - if (throttling == THROTTLING_ON) { - EXPECT_EQ(OK, WaitForResult()); - EXPECT_TRUE(websocket_->IsWaiting()); - - // Remove the former WebSocket object from throttling queue to unblock the - // latter. - block_websocket->state_ = WebSocketJob::CLOSED; - WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); - block_websocket = NULL; - } - - EXPECT_EQ(OK, WaitForResult()); - EXPECT_TRUE(data_->at_read_eof()); - EXPECT_TRUE(data_->at_write_eof()); - EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); -} - -void WebSocketJobTest::TestConnectBySpdy( - SpdyOption spdy, ThrottlingOption throttling) { - // This is a test for verifying cooperation between WebSocketJob and - // SocketStream in the situation we have SPDY session to the server. If - // |throttling| was |THROTTLING_ON|, throttling limits the latter connection. - // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected - // results depend on its configuration. - MockWrite writes_websocket[] = { - MockWrite(ASYNC, - kHandshakeRequestWithoutCookie, - kHandshakeRequestWithoutCookieLength, - 1), - MockWrite(ASYNC, - kDataHello, - kDataHelloLength, - 3) - }; - MockRead reads_websocket[] = { - MockRead(ASYNC, - kHandshakeResponseWithoutCookie, - kHandshakeResponseWithoutCookieLength, - 2), - MockRead(ASYNC, - kDataWorld, - kDataWorldLength, - 4), - MockRead(SYNCHRONOUS, 0, 5) // EOF - }; - - scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock()); - spdy_util_.SetHeader("path", "/demo", request_headers.get()); - spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get()); - spdy_util_.SetHeader("scheme", "ws", request_headers.get()); - spdy_util_.SetHeader("host", "example.com", request_headers.get()); - spdy_util_.SetHeader("origin", "http://example.com", request_headers.get()); - spdy_util_.SetHeader("sec-websocket-protocol", "sample", - request_headers.get()); - - scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock()); - spdy_util_.SetHeader("status", "101 Switching Protocols", - response_headers.get()); - spdy_util_.SetHeader("sec-websocket-protocol", "sample", - response_headers.get()); - - const SpdyStreamId kStreamId = 1; - scoped_ptr<SpdyFrame> request_frame( - spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame( - request_headers.Pass(), - kStreamId, - MEDIUM)); - scoped_ptr<SpdyFrame> response_frame( - spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame( - response_headers.Pass(), - kStreamId, - MEDIUM)); - scoped_ptr<SpdyFrame> data_hello_frame( - spdy_util_.ConstructSpdyWebSocketDataFrame( - kDataHello, - kDataHelloLength, - kStreamId, - false)); - scoped_ptr<SpdyFrame> data_world_frame( - spdy_util_.ConstructSpdyWebSocketDataFrame( - kDataWorld, - kDataWorldLength, - kStreamId, - false)); - MockWrite writes_spdy[] = { - CreateMockWrite(*request_frame.get(), 1), - CreateMockWrite(*data_hello_frame.get(), 3), - }; - MockRead reads_spdy[] = { - CreateMockRead(*response_frame.get(), 2), - CreateMockRead(*data_world_frame.get(), 4), - MockRead(SYNCHRONOUS, 0, 5) // EOF - }; - - if (spdy == SPDY_ON) - data_.reset(new OrderedSocketData( - reads_spdy, arraysize(reads_spdy), - writes_spdy, arraysize(writes_spdy))); - else - data_.reset(new OrderedSocketData( - reads_websocket, arraysize(reads_websocket), - writes_websocket, arraysize(writes_websocket))); - - GURL url("ws://example.com/demo"); - MockSocketStreamDelegate delegate; - WebSocketJobTest* test = this; - if (throttling == THROTTLING_ON) - delegate.SetOnStartOpenConnection( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); - delegate.SetOnConnected( - base::Bind(&WebSocketJobTest::DoSendRequest, - base::Unretained(test))); - delegate.SetOnReceivedData( - base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); - delegate.SetOnClose( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); - InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET); - - scoped_refptr<WebSocketJob> block_websocket; - if (throttling == THROTTLING_ON) { - // Create former WebSocket object which obstructs the latter one. - block_websocket = new WebSocketJob(NULL); - block_websocket->addresses_ = AddressList(websocket_->address_list()); - ASSERT_TRUE( - WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); - } - - websocket_->Connect(); - - if (throttling == THROTTLING_ON) { - EXPECT_EQ(OK, WaitForResult()); - EXPECT_TRUE(websocket_->IsWaiting()); - - // Remove the former WebSocket object from throttling queue to unblock the - // latter. - block_websocket->state_ = WebSocketJob::CLOSED; - WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); - block_websocket = NULL; - } - - EXPECT_EQ(OK, WaitForResult()); - EXPECT_TRUE(data_->at_read_eof()); - EXPECT_TRUE(data_->at_write_eof()); - EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); -} - -void WebSocketJobTest::TestThrottlingLimit() { - std::vector<scoped_refptr<WebSocketJob> > jobs; - const int kMaxWebSocketJobsThrottled = 1024; - IPAddressNumber ip; - ParseIPLiteralToNumber("127.0.0.1", &ip); - for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) { - scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL); - job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80)); - if (i >= kMaxWebSocketJobsThrottled) - EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job.get())); - else - EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job.get())); - jobs.push_back(job); - } - - // Close the jobs in reverse order. Otherwise, We need to make them prepared - // for Wakeup call. - for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter = - jobs.rbegin(); - iter != jobs.rend(); - ++iter) { - WebSocketJob* job = (*iter).get(); - job->state_ = WebSocketJob::CLOSED; - WebSocketThrottle::GetInstance()->RemoveFromQueue(job); - } -} - -// Execute tests in both spdy-disabled mode and spdy-enabled mode. -TEST_P(WebSocketJobTest, SimpleHandshake) { - TestSimpleHandshake(); -} - -TEST_P(WebSocketJobTest, SlowHandshake) { - TestSlowHandshake(); -} - -TEST_P(WebSocketJobTest, HandshakeWithCookie) { - TestHandshakeWithCookie(); -} - -TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { - TestHandshakeWithCookieButNotAllowed(); -} - -TEST_P(WebSocketJobTest, HSTSUpgrade) { - TestHSTSUpgrade(); -} - -TEST_P(WebSocketJobTest, InvalidSendData) { - TestInvalidSendData(); -} - -TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestSimpleHandshake(); -} - -TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestSlowHandshake(); -} - -TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestHandshakeWithCookie(); -} - -TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestHandshakeWithCookieButNotAllowed(); -} - -TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestHSTSUpgrade(); -} - -TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestInvalidSendData(); -} - -TEST_P(WebSocketJobTest, ConnectByWebSocket) { - enable_websocket_over_spdy_ = true; - TestConnectByWebSocket(THROTTLING_OFF); -} - -TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestConnectByWebSocket(THROTTLING_OFF); -} - -TEST_P(WebSocketJobTest, ConnectBySpdy) { - TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF); -} - -TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestConnectBySpdy(SPDY_ON, THROTTLING_OFF); -} - -TEST_P(WebSocketJobTest, ThrottlingWebSocket) { - TestConnectByWebSocket(THROTTLING_ON); -} - -TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) { - TestThrottlingLimit(); -} - -TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestConnectByWebSocket(THROTTLING_ON); -} - -TEST_P(WebSocketJobTest, ThrottlingSpdy) { - TestConnectBySpdy(SPDY_OFF, THROTTLING_ON); -} - -TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) { - enable_websocket_over_spdy_ = true; - TestConnectBySpdy(SPDY_ON, THROTTLING_ON); -} - -TEST_F(WebSocketJobDeleteTest, OnClose) { - SetDeleteNext(); - job()->OnClose(socket_.get()); - // OnClose() sets WebSocketJob::_socket to NULL before we can detach it, so - // socket_->delegate is still set at this point. Clear it to avoid hitting - // DCHECK(!delegate_) in the SocketStream destructor. SocketStream::Finish() - // is the only caller of this method in real code, and it also sets delegate_ - // to NULL. - socket_->DetachDelegate(); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, OnAuthRequired) { - SetDeleteNext(); - job()->OnAuthRequired(socket_.get(), NULL); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, OnSSLCertificateError) { - SSLInfo ssl_info; - SetDeleteNext(); - job()->OnSSLCertificateError(socket_.get(), ssl_info, true); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, OnError) { - SetDeleteNext(); - job()->OnError(socket_.get(), ERR_CONNECTION_RESET); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, OnSentSpdyHeaders) { - job()->Connect(); - SetDeleteNext(); - job()->OnSentSpdyHeaders(); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, OnSentHandshakeRequest) { - static const char kMinimalRequest[] = - "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" - "Origin: http://example.com\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"; - const size_t kMinimalRequestSize = arraysize(kMinimalRequest) - 1; - job()->Connect(); - job()->SendData(kMinimalRequest, kMinimalRequestSize); - SetDeleteNext(); - job()->OnSentData(socket_.get(), kMinimalRequestSize); - EXPECT_FALSE(job()); -} - -TEST_F(WebSocketJobDeleteTest, NotifyHeadersComplete) { - static const char kMinimalResponse[] = - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" - "\r\n"; - job()->Connect(); - SetDeleteNext(); - job()->OnReceivedData( - socket_.get(), kMinimalResponse, arraysize(kMinimalResponse) - 1); - EXPECT_FALSE(job()); -} - -// TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation. -// TODO(toyoshim,yutak): Add tests to verify closing handshake. -} // namespace net diff --git a/net/websockets/websocket_net_log_params.cc b/net/websockets/websocket_net_log_params.cc deleted file mode 100644 index dd9bdde..0000000 --- a/net/websockets/websocket_net_log_params.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2012 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_net_log_params.h" - -#include "base/strings/stringprintf.h" -#include "base/values.h" - -namespace net { - -base::Value* NetLogWebSocketHandshakeCallback( - const std::string* headers, - NetLog::LogLevel /* log_level */) { - base::DictionaryValue* dict = new base::DictionaryValue(); - base::ListValue* header_list = new base::ListValue(); - - size_t last = 0; - size_t headers_size = headers->size(); - size_t pos = 0; - while (pos <= headers_size) { - if (pos == headers_size || - ((*headers)[pos] == '\r' && - pos + 1 < headers_size && (*headers)[pos + 1] == '\n')) { - std::string entry = headers->substr(last, pos - last); - pos += 2; - last = pos; - - header_list->Append(new base::StringValue(entry)); - - if (entry.empty()) { - // Dump WebSocket key3. - std::string key; - for (; pos < headers_size; ++pos) { - key += base::StringPrintf("\\x%02x", (*headers)[pos] & 0xff); - } - header_list->Append(new base::StringValue(key)); - break; - } - } else { - ++pos; - } - } - - dict->Set("headers", header_list); - return dict; -} - -} // namespace net diff --git a/net/websockets/websocket_net_log_params.h b/net/websockets/websocket_net_log_params.h deleted file mode 100644 index 45dabb0..0000000 --- a/net/websockets/websocket_net_log_params.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2012 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. - -#ifndef NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_ -#define NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_ - -#include <string> - -#include "net/base/net_export.h" -#include "net/base/net_log.h" - -namespace net { - -NET_EXPORT_PRIVATE base::Value* NetLogWebSocketHandshakeCallback( - const std::string* headers, - NetLog::LogLevel /* log_level */); - -} // namespace net - -#endif // NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_ diff --git a/net/websockets/websocket_net_log_params_test.cc b/net/websockets/websocket_net_log_params_test.cc deleted file mode 100644 index d6d2a0d..0000000 --- a/net/websockets/websocket_net_log_params_test.cc +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013 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_net_log_params.h" - -#include <string> - -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" -#include "base/values.h" -#include "testing/gtest/include/gtest/gtest.h" - -TEST(NetLogWebSocketHandshakeParameterTest, ToValue) { - base::ListValue* list = new base::ListValue(); - list->Append(new base::StringValue("GET /demo HTTP/1.1")); - list->Append(new base::StringValue("Host: example.com")); - list->Append(new base::StringValue("Connection: Upgrade")); - list->Append(new base::StringValue("Sec-WebSocket-Key2: 12998 5 Y3 1 .P00")); - list->Append(new base::StringValue("Sec-WebSocket-Protocol: sample")); - list->Append(new base::StringValue("Upgrade: WebSocket")); - list->Append(new base::StringValue( - "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5")); - list->Append(new base::StringValue("Origin: http://example.com")); - list->Append(new base::StringValue(std::string())); - list->Append(new base::StringValue( - "\\x00\\x01\\x0a\\x0d\\xff\\xfe\\x0d\\x0a")); - - base::DictionaryValue expected; - expected.Set("headers", list); - - const std::string key("\x00\x01\x0a\x0d\xff\xfe\x0d\x0a", 8); - const std::string testInput = - "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" + - key; - - scoped_ptr<base::Value> actual( - net::NetLogWebSocketHandshakeCallback(&testInput, - net::NetLog::LOG_ALL)); - - EXPECT_TRUE(expected.Equals(actual.get())); -} diff --git a/net/websockets/websocket_throttle.cc b/net/websockets/websocket_throttle.cc deleted file mode 100644 index 59e73fd..0000000 --- a/net/websockets/websocket_throttle.cc +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2012 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_throttle.h" - -#include <algorithm> -#include <set> -#include <string> -#include <utility> - -#include "base/memory/singleton.h" -#include "base/message_loop/message_loop.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "net/base/io_buffer.h" -#include "net/socket_stream/socket_stream.h" -#include "net/websockets/websocket_job.h" - -namespace net { - -namespace { - -const size_t kMaxWebSocketJobsThrottled = 1024; - -} // namespace - -WebSocketThrottle::WebSocketThrottle() { -} - -WebSocketThrottle::~WebSocketThrottle() { - DCHECK(queue_.empty()); - DCHECK(addr_map_.empty()); -} - -// static -WebSocketThrottle* WebSocketThrottle::GetInstance() { - return Singleton<WebSocketThrottle>::get(); -} - -bool WebSocketThrottle::PutInQueue(WebSocketJob* job) { - if (queue_.size() >= kMaxWebSocketJobsThrottled) - return false; - - queue_.push_back(job); - const AddressList& address_list = job->address_list(); - std::set<IPEndPoint> address_set; - for (AddressList::const_iterator addr_iter = address_list.begin(); - addr_iter != address_list.end(); - ++addr_iter) { - const IPEndPoint& address = *addr_iter; - // If |address| is already processed, don't do it again. - if (!address_set.insert(address).second) - continue; - - ConnectingAddressMap::iterator iter = addr_map_.find(address); - if (iter == addr_map_.end()) { - ConnectingAddressMap::iterator new_queue = - addr_map_.insert(make_pair(address, ConnectingQueue())).first; - new_queue->second.push_back(job); - } else { - DCHECK(!iter->second.empty()); - iter->second.push_back(job); - job->SetWaiting(); - DVLOG(1) << "Waiting on " << address.ToString(); - } - } - - return true; -} - -void WebSocketThrottle::RemoveFromQueue(WebSocketJob* job) { - ConnectingQueue::iterator queue_iter = - std::find(queue_.begin(), queue_.end(), job); - if (queue_iter == queue_.end()) - return; - queue_.erase(queue_iter); - - std::set<WebSocketJob*> wakeup_candidates; - - const AddressList& resolved_address_list = job->address_list(); - std::set<IPEndPoint> address_set; - for (AddressList::const_iterator addr_iter = resolved_address_list.begin(); - addr_iter != resolved_address_list.end(); - ++addr_iter) { - const IPEndPoint& address = *addr_iter; - // If |address| is already processed, don't do it again. - if (!address_set.insert(address).second) - continue; - - ConnectingAddressMap::iterator map_iter = addr_map_.find(address); - DCHECK(map_iter != addr_map_.end()); - - ConnectingQueue& per_address_queue = map_iter->second; - DCHECK(!per_address_queue.empty()); - // Job may not be front of the queue if the socket is closed while waiting. - ConnectingQueue::iterator per_address_queue_iter = - std::find(per_address_queue.begin(), per_address_queue.end(), job); - bool was_front = false; - if (per_address_queue_iter != per_address_queue.end()) { - was_front = (per_address_queue_iter == per_address_queue.begin()); - per_address_queue.erase(per_address_queue_iter); - } - if (per_address_queue.empty()) { - addr_map_.erase(map_iter); - } else if (was_front) { - // The new front is a wake-up candidate. - wakeup_candidates.insert(per_address_queue.front()); - } - } - - WakeupSocketIfNecessary(wakeup_candidates); -} - -void WebSocketThrottle::WakeupSocketIfNecessary( - const std::set<WebSocketJob*>& wakeup_candidates) { - for (std::set<WebSocketJob*>::const_iterator iter = wakeup_candidates.begin(); - iter != wakeup_candidates.end(); - ++iter) { - WebSocketJob* job = *iter; - if (!job->IsWaiting()) - continue; - - bool should_wakeup = true; - const AddressList& resolved_address_list = job->address_list(); - for (AddressList::const_iterator addr_iter = resolved_address_list.begin(); - addr_iter != resolved_address_list.end(); - ++addr_iter) { - const IPEndPoint& address = *addr_iter; - ConnectingAddressMap::iterator map_iter = addr_map_.find(address); - DCHECK(map_iter != addr_map_.end()); - const ConnectingQueue& per_address_queue = map_iter->second; - if (job != per_address_queue.front()) { - should_wakeup = false; - break; - } - } - if (should_wakeup) - job->Wakeup(); - } -} - -} // namespace net diff --git a/net/websockets/websocket_throttle.h b/net/websockets/websocket_throttle.h deleted file mode 100644 index 0b7a39f..0000000 --- a/net/websockets/websocket_throttle.h +++ /dev/null @@ -1,75 +0,0 @@ -// 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. - -#ifndef NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_ -#define NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_ - -#include <deque> -#include <map> -#include <set> -#include <string> - -#include "net/base/ip_endpoint.h" -#include "net/base/net_export.h" - -template <typename T> struct DefaultSingletonTraits; - -namespace net { - -class SocketStream; -class WebSocketJob; - -// SocketStreamThrottle for WebSocket protocol. -// Implements the client-side requirements in the spec. -// http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol -// 4.1 Handshake -// 1. If the user agent already has a Web Socket connection to the -// remote host (IP address) identified by /host/, even if known by -// another name, wait until that connection has been established or -// for that connection to have failed. -class NET_EXPORT_PRIVATE WebSocketThrottle { - public: - // Returns the singleton instance. - static WebSocketThrottle* GetInstance(); - - // Puts |job| in |queue_| and queues for the destination addresses - // of |job|. - // If other job is using the same destination address, set |job| waiting. - // - // Returns true if successful. If the number of pending jobs will exceed - // the limit, does nothing and returns false. - bool PutInQueue(WebSocketJob* job); - - // Removes |job| from |queue_| and queues for the destination addresses - // of |job|, and then wakes up jobs that can now resume establishing a - // connection. - void RemoveFromQueue(WebSocketJob* job); - - private: - typedef std::deque<WebSocketJob*> ConnectingQueue; - typedef std::map<IPEndPoint, ConnectingQueue> ConnectingAddressMap; - - WebSocketThrottle(); - ~WebSocketThrottle(); - friend struct DefaultSingletonTraits<WebSocketThrottle>; - - // Examines if any of the given jobs can resume establishing a connection. If - // for all per-address queues for each resolved addresses - // (job->address_list()) of a job, the job is at the front of the queues, the - // job can resume establishing a connection, so wakes up the job. - void WakeupSocketIfNecessary( - const std::set<WebSocketJob*>& wakeup_candidates); - - // Key: string of host's address. Value: queue of sockets for the address. - ConnectingAddressMap addr_map_; - - // Queue of sockets for websockets in opening state. - ConnectingQueue queue_; - - DISALLOW_COPY_AND_ASSIGN(WebSocketThrottle); -}; - -} // namespace net - -#endif // NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_ diff --git a/net/websockets/websocket_throttle_test.cc b/net/websockets/websocket_throttle_test.cc deleted file mode 100644 index 4e27c83..0000000 --- a/net/websockets/websocket_throttle_test.cc +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2013 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_throttle.h" - -#include <string> - -#include "base/message_loop/message_loop.h" -#include "net/base/address_list.h" -#include "net/base/test_completion_callback.h" -#include "net/socket_stream/socket_stream.h" -#include "net/url_request/url_request_test_util.h" -#include "net/websockets/websocket_job.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/platform_test.h" -#include "url/gurl.h" - -namespace net { - -namespace { - -class DummySocketStreamDelegate : public SocketStream::Delegate { - public: - DummySocketStreamDelegate() {} - ~DummySocketStreamDelegate() override {} - void OnConnected(SocketStream* socket, - int max_pending_send_allowed) override {} - void OnSentData(SocketStream* socket, int amount_sent) override {} - void OnReceivedData(SocketStream* socket, - const char* data, - int len) override {} - void OnClose(SocketStream* socket) override {} -}; - -class WebSocketThrottleTestContext : public TestURLRequestContext { - public: - explicit WebSocketThrottleTestContext(bool enable_websocket_over_spdy) - : TestURLRequestContext(true) { - HttpNetworkSession::Params params; - params.enable_websocket_over_spdy = enable_websocket_over_spdy; - Init(); - } -}; - -} // namespace - -class WebSocketThrottleTest : public PlatformTest { - protected: - static IPEndPoint MakeAddr(int a1, int a2, int a3, int a4) { - IPAddressNumber ip; - ip.push_back(a1); - ip.push_back(a2); - ip.push_back(a3); - ip.push_back(a4); - return IPEndPoint(ip, 0); - } - - static void MockSocketStreamConnect( - SocketStream* socket, const AddressList& list) { - socket->set_addresses(list); - // TODO(toyoshim): We should introduce additional tests on cases via proxy. - socket->proxy_info_.UseDirect(); - // In SocketStream::Connect(), it adds reference to socket, which is - // balanced with SocketStream::Finish() that is finally called from - // SocketStream::Close() or SocketStream::DetachDelegate(), when - // next_state_ is not STATE_NONE. - // If next_state_ is STATE_NONE, SocketStream::Close() or - // SocketStream::DetachDelegate() won't call SocketStream::Finish(), - // so Release() won't be called. Thus, we don't need socket->AddRef() - // here. - DCHECK_EQ(socket->next_state_, SocketStream::STATE_NONE); - } -}; - -TEST_F(WebSocketThrottleTest, Throttle) { - // TODO(toyoshim): We need to consider both spdy-enabled and spdy-disabled - // configuration. - WebSocketThrottleTestContext context(true); - DummySocketStreamDelegate delegate; - - // For host1: 1.2.3.4, 1.2.3.5, 1.2.3.6 - AddressList addr; - addr.push_back(MakeAddr(1, 2, 3, 4)); - addr.push_back(MakeAddr(1, 2, 3, 5)); - addr.push_back(MakeAddr(1, 2, 3, 6)); - scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s1( - new SocketStream(GURL("ws://host1/"), w1.get(), &context, NULL)); - w1->InitSocketStream(s1.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr); - - DVLOG(1) << "socket1"; - TestCompletionCallback callback_s1; - // Trying to open connection to host1 will start without wait. - EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback())); - - // Now connecting to host1, so waiting queue looks like - // Address | head -> tail - // 1.2.3.4 | w1 - // 1.2.3.5 | w1 - // 1.2.3.6 | w1 - - // For host2: 1.2.3.4 - addr.clear(); - addr.push_back(MakeAddr(1, 2, 3, 4)); - scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s2( - new SocketStream(GURL("ws://host2/"), w2.get(), &context, NULL)); - w2->InitSocketStream(s2.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s2.get(), addr); - - DVLOG(1) << "socket2"; - TestCompletionCallback callback_s2; - // Trying to open connection to host2 will wait for w1. - EXPECT_EQ(ERR_IO_PENDING, - w2->OnStartOpenConnection(s2.get(), callback_s2.callback())); - // Now waiting queue looks like - // Address | head -> tail - // 1.2.3.4 | w1 w2 - // 1.2.3.5 | w1 - // 1.2.3.6 | w1 - - // For host3: 1.2.3.5 - addr.clear(); - addr.push_back(MakeAddr(1, 2, 3, 5)); - scoped_refptr<WebSocketJob> w3(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s3( - new SocketStream(GURL("ws://host3/"), w3.get(), &context, NULL)); - w3->InitSocketStream(s3.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s3.get(), addr); - - DVLOG(1) << "socket3"; - TestCompletionCallback callback_s3; - // Trying to open connection to host3 will wait for w1. - EXPECT_EQ(ERR_IO_PENDING, - w3->OnStartOpenConnection(s3.get(), callback_s3.callback())); - // Address | head -> tail - // 1.2.3.4 | w1 w2 - // 1.2.3.5 | w1 w3 - // 1.2.3.6 | w1 - - // For host4: 1.2.3.4, 1.2.3.6 - addr.clear(); - addr.push_back(MakeAddr(1, 2, 3, 4)); - addr.push_back(MakeAddr(1, 2, 3, 6)); - scoped_refptr<WebSocketJob> w4(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s4( - new SocketStream(GURL("ws://host4/"), w4.get(), &context, NULL)); - w4->InitSocketStream(s4.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s4.get(), addr); - - DVLOG(1) << "socket4"; - TestCompletionCallback callback_s4; - // Trying to open connection to host4 will wait for w1, w2. - EXPECT_EQ(ERR_IO_PENDING, - w4->OnStartOpenConnection(s4.get(), callback_s4.callback())); - // Address | head -> tail - // 1.2.3.4 | w1 w2 w4 - // 1.2.3.5 | w1 w3 - // 1.2.3.6 | w1 w4 - - // For host5: 1.2.3.6 - addr.clear(); - addr.push_back(MakeAddr(1, 2, 3, 6)); - scoped_refptr<WebSocketJob> w5(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s5( - new SocketStream(GURL("ws://host5/"), w5.get(), &context, NULL)); - w5->InitSocketStream(s5.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s5.get(), addr); - - DVLOG(1) << "socket5"; - TestCompletionCallback callback_s5; - // Trying to open connection to host5 will wait for w1, w4 - EXPECT_EQ(ERR_IO_PENDING, - w5->OnStartOpenConnection(s5.get(), callback_s5.callback())); - // Address | head -> tail - // 1.2.3.4 | w1 w2 w4 - // 1.2.3.5 | w1 w3 - // 1.2.3.6 | w1 w4 w5 - - // For host6: 1.2.3.6 - addr.clear(); - addr.push_back(MakeAddr(1, 2, 3, 6)); - scoped_refptr<WebSocketJob> w6(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s6( - new SocketStream(GURL("ws://host6/"), w6.get(), &context, NULL)); - w6->InitSocketStream(s6.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s6.get(), addr); - - DVLOG(1) << "socket6"; - TestCompletionCallback callback_s6; - // Trying to open connection to host6 will wait for w1, w4, w5 - EXPECT_EQ(ERR_IO_PENDING, - w6->OnStartOpenConnection(s6.get(), callback_s6.callback())); - // Address | head -> tail - // 1.2.3.4 | w1 w2 w4 - // 1.2.3.5 | w1 w3 - // 1.2.3.6 | w1 w4 w5 w6 - - // Receive partial response on w1, still connecting. - DVLOG(1) << "socket1 1"; - static const char kHeader[] = "HTTP/1.1 101 WebSocket Protocol\r\n"; - w1->OnReceivedData(s1.get(), kHeader, sizeof(kHeader) - 1); - EXPECT_FALSE(callback_s2.have_result()); - EXPECT_FALSE(callback_s3.have_result()); - EXPECT_FALSE(callback_s4.have_result()); - EXPECT_FALSE(callback_s5.have_result()); - EXPECT_FALSE(callback_s6.have_result()); - - // Receive rest of handshake response on w1. - DVLOG(1) << "socket1 2"; - static const char kHeader2[] = - "Upgrade: WebSocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Origin: http://www.google.com\r\n" - "Sec-WebSocket-Location: ws://websocket.chromium.org\r\n" - "\r\n" - "8jKS'y:G*Co,Wxa-"; - w1->OnReceivedData(s1.get(), kHeader2, sizeof(kHeader2) - 1); - base::MessageLoopForIO::current()->RunUntilIdle(); - // Now, w1 is open. - EXPECT_EQ(WebSocketJob::OPEN, w1->state()); - // So, w2 and w3 can start connecting. w4 needs to wait w2 (1.2.3.4) - EXPECT_TRUE(callback_s2.have_result()); - EXPECT_TRUE(callback_s3.have_result()); - EXPECT_FALSE(callback_s4.have_result()); - // Address | head -> tail - // 1.2.3.4 | w2 w4 - // 1.2.3.5 | w3 - // 1.2.3.6 | w4 w5 w6 - - // Closing s1 doesn't change waiting queue. - DVLOG(1) << "socket1 close"; - w1->OnClose(s1.get()); - base::MessageLoopForIO::current()->RunUntilIdle(); - EXPECT_FALSE(callback_s4.have_result()); - s1->DetachDelegate(); - // Address | head -> tail - // 1.2.3.4 | w2 w4 - // 1.2.3.5 | w3 - // 1.2.3.6 | w4 w5 w6 - - // w5 can close while waiting in queue. - DVLOG(1) << "socket5 close"; - // w5 close() closes SocketStream that change state to STATE_CLOSE, calls - // DoLoop(), so OnClose() callback will be called. - w5->OnClose(s5.get()); - base::MessageLoopForIO::current()->RunUntilIdle(); - EXPECT_FALSE(callback_s4.have_result()); - // Address | head -> tail - // 1.2.3.4 | w2 w4 - // 1.2.3.5 | w3 - // 1.2.3.6 | w4 w6 - s5->DetachDelegate(); - - // w6 close abnormally (e.g. renderer finishes) while waiting in queue. - DVLOG(1) << "socket6 close abnormally"; - w6->DetachDelegate(); - base::MessageLoopForIO::current()->RunUntilIdle(); - EXPECT_FALSE(callback_s4.have_result()); - // Address | head -> tail - // 1.2.3.4 | w2 w4 - // 1.2.3.5 | w3 - // 1.2.3.6 | w4 - - // Closing s2 kicks w4 to start connecting. - DVLOG(1) << "socket2 close"; - w2->OnClose(s2.get()); - base::MessageLoopForIO::current()->RunUntilIdle(); - EXPECT_TRUE(callback_s4.have_result()); - // Address | head -> tail - // 1.2.3.4 | w4 - // 1.2.3.5 | w3 - // 1.2.3.6 | w4 - s2->DetachDelegate(); - - DVLOG(1) << "socket3 close"; - w3->OnClose(s3.get()); - base::MessageLoopForIO::current()->RunUntilIdle(); - s3->DetachDelegate(); - w4->OnClose(s4.get()); - s4->DetachDelegate(); - DVLOG(1) << "Done"; - base::MessageLoopForIO::current()->RunUntilIdle(); -} - -TEST_F(WebSocketThrottleTest, NoThrottleForDuplicateAddress) { - WebSocketThrottleTestContext context(true); - DummySocketStreamDelegate delegate; - - // For localhost: 127.0.0.1, 127.0.0.1 - AddressList addr; - addr.push_back(MakeAddr(127, 0, 0, 1)); - addr.push_back(MakeAddr(127, 0, 0, 1)); - scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s1( - new SocketStream(GURL("ws://localhost/"), w1.get(), &context, NULL)); - w1->InitSocketStream(s1.get()); - WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr); - - DVLOG(1) << "socket1"; - TestCompletionCallback callback_s1; - // Trying to open connection to localhost will start without wait. - EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback())); - - DVLOG(1) << "socket1 close"; - w1->OnClose(s1.get()); - s1->DetachDelegate(); - DVLOG(1) << "Done"; - base::MessageLoopForIO::current()->RunUntilIdle(); -} - -// A connection should not be blocked by another connection to the same IP -// with a different port. -TEST_F(WebSocketThrottleTest, NoThrottleForDistinctPort) { - WebSocketThrottleTestContext context(false); - DummySocketStreamDelegate delegate; - IPAddressNumber localhost; - ParseIPLiteralToNumber("127.0.0.1", &localhost); - - // socket1: 127.0.0.1:80 - scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s1( - new SocketStream(GURL("ws://localhost:80/"), w1.get(), &context, NULL)); - w1->InitSocketStream(s1.get()); - MockSocketStreamConnect(s1.get(), - AddressList::CreateFromIPAddress(localhost, 80)); - - DVLOG(1) << "connecting socket1"; - TestCompletionCallback callback_s1; - // Trying to open connection to localhost:80 will start without waiting. - EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback())); - - // socket2: 127.0.0.1:81 - scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate)); - scoped_refptr<SocketStream> s2( - new SocketStream(GURL("ws://localhost:81/"), w2.get(), &context, NULL)); - w2->InitSocketStream(s2.get()); - MockSocketStreamConnect(s2.get(), - AddressList::CreateFromIPAddress(localhost, 81)); - - DVLOG(1) << "connecting socket2"; - TestCompletionCallback callback_s2; - // Trying to open connection to localhost:81 will start without waiting. - EXPECT_EQ(OK, w2->OnStartOpenConnection(s2.get(), callback_s2.callback())); - - DVLOG(1) << "closing socket1"; - w1->OnClose(s1.get()); - s1->DetachDelegate(); - - DVLOG(1) << "closing socket2"; - w2->OnClose(s2.get()); - s2->DetachDelegate(); - DVLOG(1) << "Done"; - base::MessageLoopForIO::current()->RunUntilIdle(); -} - -} // namespace net |