| // Copyright 2013 The Chromium Authors. All rights reserved. | |
| /* Modifications: Copyright 2017 The Cobalt Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "cobalt/websocket/web_socket_handshake_helper.h" | |
| #include <set> | |
| #include <string> | |
| #include "base/logging.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_piece.h" | |
| #include "base/strings/string_util.h" | |
| #include "net/http/http_request_headers.h" | |
| #include "net/http/http_status_code.h" | |
| #include "net/websockets/websocket_extension.h" | |
| #include "net/websockets/websocket_extension_parser.h" | |
| #include "net/websockets/websocket_frame.h" | |
| #include "net/websockets/websocket_handshake_challenge.h" | |
| #include "net/websockets/websocket_handshake_constants.h" | |
| #include "starboard/system.h" | |
| namespace { | |
| // Following enum and anonymous functions are adapted from Chromium net source, | |
| // commit id: 7321c9e7ee80ef15b65c2f39646a5a2d22a9c950 in | |
| // src/net/websockets/websocket_basic_handshake_stream.cc. | |
| enum GetHeaderResult { | |
| GET_HEADER_OK, | |
| GET_HEADER_MISSING, | |
| GET_HEADER_MULTIPLE, | |
| }; | |
| GetHeaderResult GetSingleHeaderValue(const net::HttpResponseHeaders* headers, | |
| const base::StringPiece& name, | |
| std::string* value) { | |
| size_t iter = 0; | |
| size_t num_values = 0; | |
| std::string temp_value; | |
| std::string name_string = name.as_string(); | |
| while (headers->EnumerateHeader(&iter, name_string, &temp_value)) { | |
| if (++num_values > 1) return GET_HEADER_MULTIPLE; | |
| *value = temp_value; | |
| } | |
| return num_values > 0 ? GET_HEADER_OK : GET_HEADER_MISSING; | |
| } | |
| std::string MissingHeaderMessage(const std::string& header_name) { | |
| return std::string("'") + header_name + "' header is missing"; | |
| } | |
| std::string MultipleHeaderValuesMessage(const std::string& header_name) { | |
| return std::string("'") + header_name + | |
| "' header must not appear more than once in a response"; | |
| } | |
| bool ValidateHeaderHasSingleValue(GetHeaderResult result, | |
| const std::string& header_name, | |
| std::string* failure_message) { | |
| if (result == GET_HEADER_MISSING) { | |
| *failure_message = MissingHeaderMessage(header_name); | |
| return false; | |
| } | |
| if (result == GET_HEADER_MULTIPLE) { | |
| *failure_message = MultipleHeaderValuesMessage(header_name); | |
| return false; | |
| } | |
| DCHECK_EQ(result, GET_HEADER_OK); | |
| return true; | |
| } | |
| bool ValidateUpgrade(const net::HttpResponseHeaders* headers, | |
| std::string* failure_message) { | |
| std::string value; | |
| GetHeaderResult result = | |
| GetSingleHeaderValue(headers, net::websockets::kUpgrade, &value); | |
| if (!ValidateHeaderHasSingleValue(result, net::websockets::kUpgrade, | |
| failure_message)) { | |
| return false; | |
| } | |
| if (!base::LowerCaseEqualsASCII(value, | |
| net::websockets::kWebSocketLowercase)) { | |
| *failure_message = "'Upgrade' header value is not 'WebSocket': " + value; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ValidateSecWebSocketAccept(const net::HttpResponseHeaders* headers, | |
| const std::string& expected, | |
| std::string* failure_message) { | |
| std::string actual; | |
| GetHeaderResult result = GetSingleHeaderValue( | |
| headers, net::websockets::kSecWebSocketAccept, &actual); | |
| if (!ValidateHeaderHasSingleValue( | |
| result, net::websockets::kSecWebSocketAccept, failure_message)) { | |
| return false; | |
| } | |
| if (expected != actual) { | |
| *failure_message = "Incorrect 'Sec-WebSocket-Accept' header value"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ValidateConnection(const net::HttpResponseHeaders* headers, | |
| std::string* failure_message) { | |
| // Connection header is permitted to contain other tokens. | |
| if (!headers->HasHeader(net::HttpRequestHeaders::kConnection)) { | |
| *failure_message = | |
| MissingHeaderMessage(net::HttpRequestHeaders::kConnection); | |
| return false; | |
| } | |
| if (!headers->HasHeaderValue(net::HttpRequestHeaders::kConnection, | |
| net::websockets::kUpgrade)) { | |
| *failure_message = "'Connection' header value must contain 'Upgrade'"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ValidateSubProtocol( | |
| const net::HttpResponseHeaders* headers, | |
| const std::vector<std::string>& requested_sub_protocols, | |
| std::string* sub_protocol, std::string* failure_message) { | |
| size_t iter = 0; | |
| std::string value; | |
| std::set<std::string> requested_set(requested_sub_protocols.begin(), | |
| requested_sub_protocols.end()); | |
| int count = 0; | |
| bool has_multiple_protocols = false; | |
| bool has_invalid_protocol = false; | |
| while (!has_invalid_protocol || !has_multiple_protocols) { | |
| std::string temp_value; | |
| if (!headers->EnumerateHeader(&iter, net::websockets::kSecWebSocketProtocol, | |
| &temp_value)) | |
| break; | |
| value = temp_value; | |
| if (requested_set.count(value) == 0) has_invalid_protocol = true; | |
| if (++count > 1) has_multiple_protocols = true; | |
| } | |
| if (has_multiple_protocols) { | |
| *failure_message = | |
| MultipleHeaderValuesMessage(net::websockets::kSecWebSocketProtocol); | |
| return false; | |
| } else if (count > 0 && requested_sub_protocols.size() == 0) { | |
| *failure_message = std::string( | |
| "Response must not include 'Sec-WebSocket-Protocol' " | |
| "header if not present in request: ") + | |
| value; | |
| return false; | |
| } else if (has_invalid_protocol) { | |
| *failure_message = "'Sec-WebSocket-Protocol' header value '" + value + | |
| "' in response does not match any of sent values"; | |
| return false; | |
| } else if (requested_sub_protocols.size() > 0 && count == 0) { | |
| *failure_message = | |
| "Sent non-empty 'Sec-WebSocket-Protocol' header " | |
| "but no response was received"; | |
| return false; | |
| } | |
| *sub_protocol = value; | |
| return true; | |
| } | |
| bool ValidateExtensions(const net::HttpResponseHeaders* headers, | |
| std::string* failure_message) { | |
| size_t iter = 0; | |
| std::string header_value; | |
| while (headers->EnumerateHeader( | |
| &iter, net::websockets::kSecWebSocketExtensions, &header_value)) { | |
| net::WebSocketExtensionParser parser; | |
| if (!parser.Parse(header_value)) { | |
| *failure_message = | |
| "'Sec-WebSocket-Extensions' header value is " | |
| "rejected by the parser: " + | |
| header_value; | |
| return false; | |
| } | |
| const std::vector<net::WebSocketExtension>& extensions = | |
| parser.extensions(); | |
| if (extensions.empty() == false) { | |
| *failure_message = "Cobalt does not support any websocket extensions"; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| cobalt::websocket::SecWebSocketKey GenerateRandomSecWebSocketKey() { | |
| using cobalt::websocket::SecWebSocketKey; | |
| SecWebSocketKey::SecWebSocketKeyBytes random_data; | |
| SbSystemGetRandomData(&random_data, | |
| sizeof(SecWebSocketKey::SecWebSocketKeyBytes)); | |
| cobalt::websocket::SecWebSocketKey key(random_data); | |
| return key; | |
| } | |
| } // namespace | |
| namespace cobalt { | |
| namespace websocket { | |
| void WebSocketHandshakeHelper::GenerateHandshakeRequest( | |
| const GURL& connect_url, const std::string& origin, | |
| const std::vector<std::string>& desired_sub_protocols, | |
| std::string* handshake_request) { | |
| DCHECK(handshake_request); | |
| GenerateSecWebSocketKey(); | |
| int effective_port = connect_url.IntPort(); | |
| std::string host_header(connect_url.host()); | |
| if (effective_port != url::PORT_UNSPECIFIED) { | |
| host_header += ":" + connect_url.port(); | |
| } | |
| std::string& header_string(*handshake_request); | |
| header_string.clear(); | |
| header_string.reserve(256); // This avoids reallocations for most cases. | |
| // Note: Concatenating string literals and std::string objects are separated | |
| // to avoid creating unnecessary std::string objects. | |
| header_string += "GET "; | |
| header_string += connect_url.path(); | |
| if (connect_url.has_query()) { | |
| header_string += "?"; | |
| header_string += connect_url.query(); | |
| } | |
| header_string += " HTTP/1.1\r\n"; | |
| header_string += "Host:"; | |
| header_string += host_header; | |
| header_string += "\r\n"; | |
| header_string += | |
| "Connection:Upgrade\r\n" | |
| "Pragma:no-cache\r\n" | |
| "Cache-Control:no-cache\r\n" | |
| "Upgrade:websocket\r\n" | |
| "Sec-WebSocket-Extensions:\r\n" | |
| "Sec-WebSocket-Version:13\r\n"; | |
| header_string += "Origin:"; | |
| header_string += origin; | |
| header_string += "\r\n"; | |
| header_string += "Sec-WebSocket-Key:"; | |
| header_string += sec_websocket_key_.GetKeyEncodedInBase64(); | |
| header_string += "\r\n"; | |
| header_string += "User-Agent:"; | |
| header_string += user_agent_; | |
| header_string += "\r\n"; | |
| if (!desired_sub_protocols.empty()) { | |
| header_string += "Sec-WebSocket-Protocol:"; | |
| header_string += base::JoinString(desired_sub_protocols, ","); | |
| header_string += "\r\n"; | |
| } | |
| header_string += "\r\n"; | |
| requested_sub_protocols_ = desired_sub_protocols; | |
| const std::string& sec_websocket_key_base64( | |
| sec_websocket_key_.GetKeyEncodedInBase64()); | |
| handshake_challenge_response_ = | |
| net::ComputeSecWebSocketAccept(sec_websocket_key_base64); | |
| } | |
| WebSocketHandshakeHelper::WebSocketHandshakeHelper( | |
| const base::StringPiece user_agent) | |
| : sec_websocket_key_generator_function_(&GenerateRandomSecWebSocketKey), | |
| user_agent_(user_agent.data(), user_agent.size()) {} | |
| WebSocketHandshakeHelper::WebSocketHandshakeHelper( | |
| const base::StringPiece user_agent, | |
| SecWebSocketKeyGeneratorFunction sec_websocket_key_generator_function) | |
| : sec_websocket_key_generator_function_( | |
| sec_websocket_key_generator_function), | |
| user_agent_(user_agent.data(), user_agent.size()) {} | |
| bool WebSocketHandshakeHelper::IsResponseValid( | |
| const net::HttpResponseHeaders& headers, std::string* failure_message) { | |
| DCHECK(failure_message); | |
| int response_code = headers.response_code(); | |
| // Check response code first. | |
| if (response_code != net::HTTP_SWITCHING_PROTOCOLS) { | |
| *failure_message = | |
| "Invalid response code " + base::IntToString(response_code); | |
| return false; | |
| } | |
| if (!ValidateUpgrade(&headers, failure_message)) { | |
| return false; | |
| } | |
| if (!ValidateSecWebSocketAccept(&headers, handshake_challenge_response_, | |
| failure_message)) { | |
| return false; | |
| } | |
| if (!ValidateConnection(&headers, failure_message)) { | |
| return false; | |
| } | |
| if (!ValidateSubProtocol(&headers, requested_sub_protocols_, | |
| &selected_subprotocol_, failure_message)) { | |
| return false; | |
| } | |
| // Cobalt does not support extensions, so we just make sure that none are | |
| // being selected. | |
| if (!ValidateExtensions(&headers, failure_message)) { | |
| return false; | |
| } | |
| failure_message->clear(); | |
| return true; | |
| } | |
| void WebSocketHandshakeHelper::GenerateSecWebSocketKey() { | |
| sec_websocket_key_ = sec_websocket_key_generator_function_(); | |
| } | |
| } // namespace websocket | |
| } // namespace cobalt |