1c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be 3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file. 4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "net/websockets/websocket_handshake_handler.h" 6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/md5.h" 8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/string_piece.h" 9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/string_util.h" 10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "googleurl/src/gurl.h" 113345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "net/http/http_response_headers.h" 12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "net/http/http_util.h" 13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace { 15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst size_t kRequestKey3Size = 8U; 17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst size_t kResponseKeySize = 16U; 18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid ParseHandshakeHeader( 20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* handshake_message, int len, 21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string* status_line, 22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string* headers) { 23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n"); 24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (i == base::StringPiece::npos) { 25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *status_line = std::string(handshake_message, len); 26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *headers = ""; 27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // |status_line| includes \r\n. 30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *status_line = std::string(handshake_message, i + 2); 31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int header_len = len - (i + 2) - 2; 33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (header_len > 0) { 34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // |handshake_message| includes tailing \r\n\r\n. 35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // |headers| doesn't include 2nd \r\n. 36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *headers = std::string(handshake_message + i + 2, header_len); 37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else { 38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *headers = ""; 39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid FetchHeaders(const std::string& headers, 43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* const headers_to_get[], 44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t headers_to_get_len, 45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::vector<std::string>* values) { 46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch net::HttpUtil::HeadersIterator iter(headers.begin(), headers.end(), "\r\n"); 47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while (iter.GetNext()) { 48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (size_t i = 0; i < headers_to_get_len; i++) { 49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), 50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch headers_to_get[i])) { 51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch values->push_back(iter.values()); 52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool GetHeaderName(std::string::const_iterator line_begin, 58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator line_end, 59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator* name_begin, 60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator* name_end) { 61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator colon = std::find(line_begin, line_end, ':'); 62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (colon == line_end) { 63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *name_begin = line_begin; 66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch *name_end = colon; 67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin)) 68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch net::HttpUtil::TrimLWS(name_begin, name_end); 70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return true; 71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that 74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// is, lines that are not formatted as "<name>: <value>\r\n". 75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstd::string FilterHeaders( 76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const std::string& headers, 77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* const headers_to_remove[], 78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t headers_to_remove_len) { 79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string filtered_headers; 80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch StringTokenizer lines(headers.begin(), headers.end(), "\r\n"); 82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while (lines.GetNext()) { 83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator line_begin = lines.token_begin(); 84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator line_end = lines.token_end(); 85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator name_begin; 86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string::const_iterator name_end; 87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch bool should_remove = false; 88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (GetHeaderName(line_begin, line_end, &name_begin, &name_end)) { 89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (size_t i = 0; i < headers_to_remove_len; ++i) { 90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (LowerCaseEqualsASCII(name_begin, name_end, headers_to_remove[i])) { 91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch should_remove = true; 92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break; 93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!should_remove) { 97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filtered_headers.append(line_begin, line_end); 98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch filtered_headers.append("\r\n"); 99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return filtered_headers; 102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Gets a key number from |key| and appends the number to |challenge|. 105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// The key number (/part_N/) is extracted as step 4.-8. in 106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// 5.2. Sending the server's opening handshake of 107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt 108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid GetKeyNumber(const std::string& key, std::string* challenge) { 109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch uint32 key_number = 0; 110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch uint32 spaces = 0; 111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (size_t i = 0; i < key.size(); ++i) { 112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (isdigit(key[i])) { 113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // key_number should not overflow. (it comes from 114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // WebCore/websockets/WebSocketHandshake.cpp). 115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key_number = key_number * 10 + key[i] - '0'; 116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else if (key[i] == ' ') { 117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ++spaces; 118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // spaces should not be zero in valid handshake request. 121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (spaces == 0) 122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key_number /= spaces; 124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch char part[4]; 126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (int i = 0; i < 4; i++) { 127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch part[3 - i] = key_number & 0xFF; 128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key_number >>= 8; 129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch challenge->append(part, 4); 131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} // anonymous namespace 134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace net { 136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 137c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochWebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler() 138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : original_length_(0), 139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raw_length_(0) {} 140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool WebSocketHandshakeRequestHandler::ParseRequest( 142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* data, int length) { 143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_GT(length, 0); 144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string input(data, length); 145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int input_header_length = 146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HttpUtil::LocateEndOfHeaders(input.data(), input.size(), 0); 147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (input_header_length <= 0 || 148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch input_header_length + kRequestKey3Size > input.size()) 149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ParseHandshakeHeader(input.data(), 152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch input_header_length, 153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch &status_line_, 154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch &headers_); 155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // draft-hixie-thewebsocketprotocol-76 or later will send /key3/ 157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // after handshake request header. 158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Assumes WebKit doesn't send any data after handshake request message 159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // until handshake is finished. 160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Thus, |key3_| is part of handshake message, and not in part 161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // of WebSocket frame stream. 162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kRequestKey3Size, 163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch input.size() - 164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch input_header_length); 165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key3_ = std::string(input.data() + input_header_length, 166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch input.size() - input_header_length); 167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_length_ = input.size(); 168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return true; 169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochsize_t WebSocketHandshakeRequestHandler::original_length() const { 172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return original_length_; 173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid WebSocketHandshakeRequestHandler::AppendHeaderIfMissing( 176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const std::string& name, const std::string& value) { 177dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HttpUtil::AppendHeaderIfMissing(name.c_str(), value, &headers_); 179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid WebSocketHandshakeRequestHandler::RemoveHeaders( 182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* const headers_to_remove[], 183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t headers_to_remove_len) { 184dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 185c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch headers_ = FilterHeaders( 186c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch headers_, headers_to_remove, headers_to_remove_len); 187c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 188c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 189c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochHttpRequestInfo WebSocketHandshakeRequestHandler::GetRequestInfo( 190c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const GURL& url, std::string* challenge) { 191c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HttpRequestInfo request_info; 192c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.url = url; 193c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch base::StringPiece method = status_line_.data(); 194c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t method_end = base::StringPiece( 195c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch status_line_.data(), status_line_.size()).find_first_of(" "); 196c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (method_end != base::StringPiece::npos) 197c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.method = std::string(status_line_.data(), method_end); 198c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 199c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.Clear(); 200c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.AddHeadersFromString(headers_); 201c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 202c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.RemoveHeader("Upgrade"); 203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.RemoveHeader("Connection"); 204c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch challenge->clear(); 206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string key; 207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.GetHeader("Sec-WebSocket-Key1", &key); 208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key1"); 209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GetKeyNumber(key, challenge); 210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key); 212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2"); 213c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GetKeyNumber(key, challenge); 214c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 215c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch challenge->append(key3_); 216c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 217c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return request_info; 218c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 220c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock( 221c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const GURL& url, spdy::SpdyHeaderBlock* headers, std::string* challenge) { 222c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // We don't set "method" and "version". These are fixed value in WebSocket 223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // protocol. 224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (*headers)["url"] = url.spec(); 225c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string key1; 227c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string key2; 228c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch HttpUtil::HeadersIterator iter(headers_.begin(), headers_.end(), "\r\n"); 229c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while (iter.GetNext()) { 230c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), 231c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "connection")) { 232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Ignore "Connection" header. 233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue; 234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), 235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "upgrade")) { 236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Ignore "Upgrade" header. 237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue; 238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), 239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "sec-websocket-key1")) { 240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Use only for generating challenge. 241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key1 = iter.values(); 242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue; 243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), 244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch "sec-websocket-key2")) { 245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Use only for generating challenge. 246c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key2 = iter.values(); 247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch continue; 248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 249c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Others should be sent out to |headers|. 250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string name = StringToLowerASCII(iter.name()); 251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch spdy::SpdyHeaderBlock::iterator found = headers->find(name); 252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (found == headers->end()) { 253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch (*headers)[name] = iter.values(); 254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else { 255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // For now, websocket doesn't use multiple headers, but follows to http. 256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch found->second.append(1, '\0'); // +=() doesn't append 0's 257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch found->second.append(iter.values()); 258c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 259c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 261c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch challenge->clear(); 262c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GetKeyNumber(key1, challenge); 263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GetKeyNumber(key2, challenge); 264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch challenge->append(key3_); 265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return true; 267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstd::string WebSocketHandshakeRequestHandler::GetRawRequest() { 270dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!status_line_.empty()); 271dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kRequestKey3Size, key3_.size()); 273c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string raw_request = status_line_ + headers_ + "\r\n" + key3_; 274c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch raw_length_ = raw_request.size(); 275c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return raw_request; 276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 277c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 278c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochsize_t WebSocketHandshakeRequestHandler::raw_length() const { 279c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_GT(raw_length_, 0); 280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return raw_length_; 281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 283c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochWebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler() 284c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : original_header_length_(0) { 285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 2873f50c38dc070f4bb515c1b64450dae14f316474eKristian MonsenWebSocketHandshakeResponseHandler::~WebSocketHandshakeResponseHandler() {} 2883f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen 289c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochsize_t WebSocketHandshakeResponseHandler::ParseRawResponse( 290c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* data, int length) { 291c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_GT(length, 0); 292c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (HasResponse()) { 293dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!status_line_.empty()); 294dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 295c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kResponseKeySize, key_.size()); 296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return 0; 297c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t old_original_length = original_.size(); 300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_.append(data, length); 302c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // TODO(ukai): fail fast when response gives wrong status code. 303c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_header_length_ = HttpUtil::LocateEndOfHeaders( 304c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_.data(), original_.size(), 0); 305c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!HasResponse()) 306c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return length; 307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ParseHandshakeHeader(original_.data(), 309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_header_length_, 310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch &status_line_, 311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch &headers_); 312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int header_size = status_line_.size() + headers_.size(); 313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_GE(original_header_length_, header_size); 314c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch header_separator_ = std::string(original_.data() + header_size, 315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_header_length_ - header_size); 316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch key_ = std::string(original_.data() + original_header_length_, 317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch kResponseKeySize); 318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return original_header_length_ + kResponseKeySize - old_original_length; 320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 321c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool WebSocketHandshakeResponseHandler::HasResponse() const { 323c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return original_header_length_ > 0 && 324c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch original_header_length_ + kResponseKeySize <= original_.size(); 325c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 326c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 327c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool WebSocketHandshakeResponseHandler::ParseResponseInfo( 328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const HttpResponseInfo& response_info, 329c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const std::string& challenge) { 330c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!response_info.headers.get()) 331c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 332c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string response_message; 334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message = response_info.headers->GetStatusLine(); 335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "\r\n"; 336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "Upgrade: WebSocket\r\n"; 337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "Connection: Upgrade\r\n"; 338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch void* iter = NULL; 339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string name; 340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string value; 341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while (response_info.headers->EnumerateHeaderLines(&iter, &name, &value)) { 342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += name + ": " + value + "\r\n"; 343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "\r\n"; 345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch MD5Digest digest; 347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch MD5Sum(challenge.data(), challenge.size(), &digest); 348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* digest_data = reinterpret_cast<char*>(digest.a); 350c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message.append(digest_data, sizeof(digest.a)); 351c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return ParseRawResponse(response_message.data(), 353c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message.size()) == response_message.size(); 354c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 355c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 356c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock( 357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const spdy::SpdyHeaderBlock& headers, 358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const std::string& challenge) { 359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string response_message; 360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"; 361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "Upgrade: WebSocket\r\n"; 362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "Connection: Upgrade\r\n"; 363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (spdy::SpdyHeaderBlock::const_iterator iter = headers.begin(); 364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch iter != headers.end(); 365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ++iter) { 366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // For each value, if the server sends a NUL-separated list of values, 367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // we separate that back out into individual headers for each value 368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // in the list. 369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const std::string& value = iter->second; 370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t start = 0; 371c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t end = 0; 372c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch do { 373c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch end = value.find('\0', start); 374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string tval; 375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (end != std::string::npos) 376c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch tval = value.substr(start, (end - start)); 377c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch else 378c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch tval = value.substr(start); 379c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += iter->first + ": " + tval + "\r\n"; 380c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch start = end + 1; 381c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } while (end != std::string::npos); 382c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 383c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message += "\r\n"; 384c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 385c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch MD5Digest digest; 386c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch MD5Sum(challenge.data(), challenge.size(), &digest); 387c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 388c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* digest_data = reinterpret_cast<char*>(digest.a); 389c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message.append(digest_data, sizeof(digest.a)); 390c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 391c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return ParseRawResponse(response_message.data(), 392c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch response_message.size()) == response_message.size(); 393c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 394c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 395c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid WebSocketHandshakeResponseHandler::GetHeaders( 396c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* const headers_to_get[], 397c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t headers_to_get_len, 398c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::vector<std::string>* values) { 399c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK(HasResponse()); 400dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!status_line_.empty()); 401dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 402c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kResponseKeySize, key_.size()); 403c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 404c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch FetchHeaders(headers_, headers_to_get, headers_to_get_len, values); 405c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 406c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 407c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid WebSocketHandshakeResponseHandler::RemoveHeaders( 408c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char* const headers_to_remove[], 409c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch size_t headers_to_remove_len) { 410c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK(HasResponse()); 411dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!status_line_.empty()); 412dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!headers_.empty()); 413c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kResponseKeySize, key_.size()); 414c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 415c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len); 416c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 417c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 4183345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickstd::string WebSocketHandshakeResponseHandler::GetRawResponse() const { 4193345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick DCHECK(HasResponse()); 4203345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick return std::string(original_.data(), 4213345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick original_header_length_ + kResponseKeySize); 4223345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 4233345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 424c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstd::string WebSocketHandshakeResponseHandler::GetResponse() { 425c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK(HasResponse()); 426dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DCHECK(!status_line_.empty()); 427c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // headers_ might be empty for wrong response from server. 428c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(kResponseKeySize, key_.size()); 429c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 430c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return status_line_ + headers_ + header_separator_ + key_; 431c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 432c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 433c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} // namespace net 434