Chromium Code Reviews| Index: net/server/web_socket.cc |
| diff --git a/net/server/web_socket.cc b/net/server/web_socket.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5fd1b5b1ed3b85120523f72332e8f5bca2d02727 |
| --- /dev/null |
| +++ b/net/server/web_socket.cc |
| @@ -0,0 +1,365 @@ |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "net/server/web_socket.h" |
| + |
| +#include "base/base64.h" |
| +#include "base/rand_util.h" |
| +#include "base/logging.h" |
| +#include "base/md5.h" |
| +#include "base/sha1.h" |
| +#include "base/string_number_conversions.h" |
| +#include "base/stringprintf.h" |
| +#include "net/server/http_connection.h" |
| +#include "net/server/http_server_request_info.h" |
| + |
| +#if defined(OS_WIN) |
| +#include <winsock2.h> |
| +#else |
| +#include <arpa/inet.h> |
| +#endif |
| + |
| +#include <limits> |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +static uint32 WebSocketKeyFingerprint(const std::string& str) { |
| + std::string result; |
| + const char* pChar = str.c_str(); |
|
eroman
2011/08/02 19:01:33
please change this name to match styleguide.
|
| + int length = str.length(); |
| + int spaces = 0; |
| + for (int i = 0; i < length; ++i) { |
| + if (pChar[i] >= '0' && pChar[i] <= '9') |
| + result.append(&pChar[i], 1); |
| + else if (pChar[i] == ' ') |
| + spaces++; |
| + } |
| + if (spaces == 0) |
| + return 0; |
| + int64 number = 0; |
| + if (!base::StringToInt64(result, &number)) |
| + return 0; |
| + return htonl(static_cast<uint32>(number / spaces)); |
| +} |
| + |
| +class WebSocketHixie76 : public net::WebSocket { |
| + public: |
| + static net::WebSocket* Create(HttpConnection* connection, |
| + const HttpServerRequestInfo& request, |
| + size_t* pos) { |
| + if (connection->recv_data().length() < *pos + kWebSocketHandshakeBodyLen) |
| + return NULL; |
| + return new WebSocketHixie76(connection, request, pos); |
| + } |
| + |
| + virtual void Accept(const HttpServerRequestInfo& request) { |
| + std::string key1 = request.GetHeaderValue("Sec-WebSocket-Key1"); |
| + std::string key2 = request.GetHeaderValue("Sec-WebSocket-Key2"); |
| + |
| + uint32 fp1 = WebSocketKeyFingerprint(key1); |
| + uint32 fp2 = WebSocketKeyFingerprint(key2); |
| + |
| + char data[16]; |
| + memcpy(data, &fp1, 4); |
| + memcpy(data + 4, &fp2, 4); |
| + memcpy(data + 8, &key3_[0], 8); |
| + |
| + base::MD5Digest digest; |
| + base::MD5Sum(data, 16, &digest); |
| + |
| + std::string origin = request.GetHeaderValue("Origin"); |
| + std::string host = request.GetHeaderValue("Host"); |
| + std::string location = "ws://" + host + request.path; |
| + connection_->Send(base::StringPrintf( |
| + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" |
| + "Upgrade: WebSocket\r\n" |
| + "Connection: Upgrade\r\n" |
| + "Sec-WebSocket-Origin: %s\r\n" |
| + "Sec-WebSocket-Location: %s\r\n" |
| + "\r\n", |
| + origin.c_str(), |
| + location.c_str())); |
| + connection_->Send(reinterpret_cast<char*>(digest.a), 16); |
| + } |
| + |
| + virtual ParseResult Read(std::string* message) { |
| + DCHECK(message); |
| + const std::string& data = connection_->recv_data(); |
| + if (data[0]) |
| + return FRAME_ERROR; |
| + |
| + size_t pos = data.find('\377', 1); |
| + if (pos == std::string::npos) |
| + return FRAME_INCOMPLETE; |
| + |
| + std::string buffer(data.begin() + 1, data.begin() + pos); |
| + message->swap(buffer); |
| + connection_->Shift(pos + 1); |
| + |
| + return FRAME_OK; |
| + } |
| + |
| + virtual void Send(const std::string& message) { |
| + char message_start = 0; |
| + char message_end = -1; |
| + connection_->Send(&message_start, 1); |
| + connection_->Send(message); |
| + connection_->Send(&message_end, 1); |
| + } |
| + |
| + private: |
| + static const int kWebSocketHandshakeBodyLen; |
| + |
| + WebSocketHixie76(HttpConnection* connection, |
| + const HttpServerRequestInfo& request, |
| + size_t* pos) : WebSocket(connection) { |
| + std::string key1 = request.GetHeaderValue("Sec-WebSocket-Key1"); |
| + std::string key2 = request.GetHeaderValue("Sec-WebSocket-Key2"); |
| + |
| + if (key1.empty()) { |
| + connection->Send500("Invalid request format. " |
| + "Sec-WebSocket-Key1 is empty or isn't specified."); |
| + return; |
| + } |
| + |
| + if (key2.empty()) { |
| + connection->Send500("Invalid request format. " |
| + "Sec-WebSocket-Key2 is empty or isn't specified."); |
| + return; |
| + } |
| + |
| + key3_ = connection->recv_data().substr( |
| + *pos, |
| + *pos + kWebSocketHandshakeBodyLen); |
| + *pos += kWebSocketHandshakeBodyLen; |
| + } |
| + |
| + std::string key3_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(WebSocketHixie76); |
| +}; |
| + |
| +const int WebSocketHixie76::kWebSocketHandshakeBodyLen = 8; |
| + |
| + |
| +// Constants for hybi-10 frame format. |
| + |
| +typedef int OpCode; |
| + |
| +const OpCode kOpCodeContinuation = 0x0; |
| +const OpCode kOpCodeText = 0x1; |
| +const OpCode kOpCodeBinary = 0x2; |
| +const OpCode kOpCodeClose = 0x8; |
| +const OpCode kOpCodePing = 0x9; |
| +const OpCode kOpCodePong = 0xA; |
| + |
| +const unsigned char kFinalBit = 0x80; |
| +const unsigned char kReserved1Bit = 0x40; |
| +const unsigned char kReserved2Bit = 0x20; |
| +const unsigned char kReserved3Bit = 0x10; |
| +const unsigned char kOpCodeMask = 0xF; |
| +const unsigned char kMaskBit = 0x80; |
| +const unsigned char kPayloadLengthMask = 0x7F; |
| + |
| +const size_t kMaxSingleBytePayloadLength = 125; |
| +const size_t kTwoBytePayloadLengthField = 126; |
| +const size_t kEightBytePayloadLengthField = 127; |
| +const size_t kMaskingKeyWidthInBytes = 4; |
| + |
| +class WebSocketHybi10 : public WebSocket { |
| + public: |
| + static WebSocket* Create(HttpConnection* connection, |
| + const HttpServerRequestInfo& request, |
| + size_t* pos) { |
| + std::string version = request.GetHeaderValue("Sec-WebSocket-Version"); |
| + if (version != "8") |
| + return NULL; |
| + |
| + std::string key = request.GetHeaderValue("Sec-WebSocket-Key"); |
| + if (key.empty()) { |
| + connection->Send500("Invalid request format. " |
| + "Sec-WebSocket-Key is empty or isn't specified."); |
| + return NULL; |
| + } |
| + return new WebSocketHybi10(connection, request, pos); |
| + } |
| + |
| + virtual void Accept(const HttpServerRequestInfo& request) { |
| + static const char* const web_socket_GUID = |
|
eroman
2011/08/02 19:01:33
kWebSocketGuid
|
| + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| + std::string key = request.GetHeaderValue("Sec-WebSocket-Key"); |
| + std::string data = base::StringPrintf("%s%s", key.c_str(), web_socket_GUID); |
| + std::string encoded_hash; |
| + base::Base64Encode(base::SHA1HashString(data), &encoded_hash); |
| + |
| + std::string response = base::StringPrintf( |
| + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" |
| + "Upgrade: WebSocket\r\n" |
| + "Connection: Upgrade\r\n" |
| + "Sec-WebSocket-Accept: %s\r\n" |
| + "\r\n", |
| + encoded_hash.c_str()); |
| + connection_->Send(response); |
| + } |
| + |
| + virtual ParseResult Read(std::string* message) { |
| + size_t data_length = connection_->recv_data().length(); |
| + if (data_length < 2) |
| + return FRAME_INCOMPLETE; |
| + |
| + const char* p = connection_->recv_data().c_str(); |
| + const char* buffer_end = p + data_length; |
| + |
| + unsigned char first_byte = *p++; |
| + unsigned char second_byte = *p++; |
| + |
| + final_ = first_byte & kFinalBit; |
| + reserved1_ = first_byte & kReserved1Bit; |
| + reserved2_ = first_byte & kReserved2Bit; |
| + reserved3_ = first_byte & kReserved3Bit; |
| + op_code_ = first_byte & kOpCodeMask; |
| + masked_ = second_byte & kMaskBit; |
| + |
| + CHECK(op_code_ == kOpCodeText); |
|
eroman
2011/08/02 19:01:33
check_eq
|
| + |
| + uint64 payload_length64 = second_byte & kPayloadLengthMask; |
| + if (payload_length64 > kMaxSingleBytePayloadLength) { |
| + int extended_payload_length_size; |
| + if (payload_length64 == kTwoBytePayloadLengthField) |
| + extended_payload_length_size = 2; |
| + else { |
| + DCHECK(payload_length64 == kEightBytePayloadLengthField); |
| + extended_payload_length_size = 8; |
| + } |
| + if (buffer_end - p < extended_payload_length_size) |
| + return FRAME_INCOMPLETE; |
| + payload_length64 = 0; |
| + for (int i = 0; i < extended_payload_length_size; ++i) { |
| + payload_length64 <<= 8; |
| + payload_length64 |= static_cast<unsigned char>(*p++); |
| + } |
| + } |
| + |
| + static const uint64 max_payload_length = 0x7FFFFFFFFFFFFFFFull; |
| + size_t masking_key_length = masked_ ? kMaskingKeyWidthInBytes : 0; |
| + static size_t max_length = std::numeric_limits<size_t>::max(); |
| + if (payload_length64 > max_payload_length || |
| + payload_length64 + masking_key_length > max_length) { |
| + // WebSocket frame length too large. |
| + return FRAME_ERROR; |
| + } |
| + payload_length_ = static_cast<size_t>(payload_length64); |
| + |
| + size_t total_length = masking_key_length + payload_length_; |
| + if (static_cast<size_t>(buffer_end - p) < total_length) |
| + return FRAME_INCOMPLETE; |
| + |
| + if (masked_) { |
| + message->resize(payload_length_); |
| + const char* masking_key = p; |
| + char* payload = const_cast<char*>(p + kMaskingKeyWidthInBytes); |
| + for (size_t i = 0; i < payload_length_; ++i) // Unmask the payload. |
| + (*message)[i] = payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]; |
|
eroman
2011/08/02 19:01:33
I only did a cursory review of this logic BTW
|
| + } else { |
| + std::string buffer(p, p + payload_length_); |
| + message->swap(buffer); |
| + } |
| + |
| + size_t pos = p + masking_key_length + payload_length_ - |
| + connection_->recv_data().c_str(); |
| + connection_->Shift(pos); |
| + return FRAME_OK; |
| + } |
| + |
| + virtual void Send(const std::string& message) { |
| + std::vector<char> frame; |
| + OpCode op_code = kOpCodeText; |
| + size_t data_length = message.length(); |
| + |
| + frame.push_back(kFinalBit | op_code); |
| + if (data_length <= kMaxSingleBytePayloadLength) |
| + frame.push_back(kMaskBit | data_length); |
| + else if (data_length <= 0xFFFF) { |
| + frame.push_back(kMaskBit | kTwoBytePayloadLengthField); |
| + frame.push_back((data_length & 0xFF00) >> 8); |
| + frame.push_back(data_length & 0xFF); |
| + } else { |
| + frame.push_back(kMaskBit | kEightBytePayloadLengthField); |
| + char extended_payload_length[8]; |
| + size_t remaining = data_length; |
| + // Fill the length into extended_payload_length in the network byte order. |
| + for (int i = 0; i < 8; ++i) { |
| + extended_payload_length[7 - i] = remaining & 0xFF; |
| + remaining >>= 8; |
| + } |
| + frame.insert(frame.end(), |
| + extended_payload_length, |
| + extended_payload_length + 8); |
| + DCHECK(!remaining); |
| + } |
| + |
| + // Mask the frame. |
| + size_t masking_key_start = frame.size(); |
| + // Add placeholder for masking key. Will be overwritten. |
| + frame.resize(frame.size() + kMaskingKeyWidthInBytes); |
| + size_t payload_start = frame.size(); |
| + const char* data = message.c_str(); |
| + frame.insert(frame.end(), data, data + data_length); |
| + |
| + base::RandBytes(&frame[0] + masking_key_start, |
| + kMaskingKeyWidthInBytes); |
| + for (size_t i = 0; i < data_length; ++i) { |
| + frame[payload_start + i] ^= |
| + frame[masking_key_start + i % kMaskingKeyWidthInBytes]; |
| + } |
| + connection_->Send(&frame[0], frame.size()); |
| + } |
| + |
| + private: |
| + WebSocketHybi10(HttpConnection* connection, |
| + const HttpServerRequestInfo& request, |
| + size_t* pos) |
| + : WebSocket(connection), |
| + op_code_(0), |
| + final_(false), |
| + reserved1_(false), |
| + reserved2_(false), |
| + reserved3_(false), |
| + masked_(false), |
| + payload_(0), |
| + payload_length_(0), |
| + frame_end_(0) { |
| + } |
| + |
| + OpCode op_code_; |
| + bool final_; |
| + bool reserved1_; |
| + bool reserved2_; |
| + bool reserved3_; |
| + bool masked_; |
| + const char* payload_; |
| + size_t payload_length_; |
| + const char* frame_end_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(WebSocketHybi10); |
| +}; |
| + |
| +} // anonymous namespace |
| + |
| +WebSocket* WebSocket::CreateWebSocket(HttpConnection* connection, |
| + const HttpServerRequestInfo& request, |
| + size_t* pos) { |
| + WebSocket* socket = WebSocketHybi10::Create(connection, request, pos); |
| + if (socket) |
| + return socket; |
| + |
| + return WebSocketHixie76::Create(connection, request, pos); |
| +} |
| + |
| +WebSocket::WebSocket(HttpConnection* connection) : connection_(connection) { |
| +} |
| + |
| +} // namespace net |