diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/server | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/net/server')
-rw-r--r-- | chromium/net/server/http_connection.cc | 51 | ||||
-rw-r--r-- | chromium/net/server/http_connection.h | 53 | ||||
-rw-r--r-- | chromium/net/server/http_server.cc | 330 | ||||
-rw-r--r-- | chromium/net/server/http_server.h | 103 | ||||
-rw-r--r-- | chromium/net/server/http_server_request_info.cc | 25 | ||||
-rw-r--r-- | chromium/net/server/http_server_request_info.h | 43 | ||||
-rw-r--r-- | chromium/net/server/http_server_response_info.cc | 67 | ||||
-rw-r--r-- | chromium/net/server/http_server_response_info.h | 46 | ||||
-rw-r--r-- | chromium/net/server/http_server_response_info_unittest.cc | 51 | ||||
-rw-r--r-- | chromium/net/server/http_server_unittest.cc | 319 | ||||
-rw-r--r-- | chromium/net/server/web_socket.cc | 406 | ||||
-rw-r--r-- | chromium/net/server/web_socket.h | 53 |
12 files changed, 1547 insertions, 0 deletions
diff --git a/chromium/net/server/http_connection.cc b/chromium/net/server/http_connection.cc new file mode 100644 index 00000000000..d964cb0738b --- /dev/null +++ b/chromium/net/server/http_connection.cc @@ -0,0 +1,51 @@ +// 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/server/http_connection.h" + +#include "net/server/http_server.h" +#include "net/server/http_server_response_info.h" +#include "net/server/web_socket.h" +#include "net/socket/stream_listen_socket.h" + +namespace net { + +int HttpConnection::last_id_ = 0; + +void HttpConnection::Send(const std::string& data) { + if (!socket_.get()) + return; + socket_->Send(data); +} + +void HttpConnection::Send(const char* bytes, int len) { + if (!socket_.get()) + return; + socket_->Send(bytes, len); +} + +void HttpConnection::Send(const HttpServerResponseInfo& response) { + Send(response.Serialize()); +} + +HttpConnection::HttpConnection(HttpServer* server, StreamListenSocket* sock) + : server_(server), + socket_(sock) { + id_ = last_id_++; +} + +HttpConnection::~HttpConnection() { + DetachSocket(); + server_->delegate_->OnClose(id_); +} + +void HttpConnection::DetachSocket() { + socket_ = NULL; +} + +void HttpConnection::Shift(int num_bytes) { + recv_data_ = recv_data_.substr(num_bytes); +} + +} // namespace net diff --git a/chromium/net/server/http_connection.h b/chromium/net/server/http_connection.h new file mode 100644 index 00000000000..b0e37663d4f --- /dev/null +++ b/chromium/net/server/http_connection.h @@ -0,0 +1,53 @@ +// 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_SERVER_HTTP_CONNECTION_H_ +#define NET_SERVER_HTTP_CONNECTION_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/http/http_status_code.h" + +namespace net { + +class HttpServer; +class HttpServerResponseInfo; +class StreamListenSocket; +class WebSocket; + +class HttpConnection { + public: + ~HttpConnection(); + + void Send(const std::string& data); + void Send(const char* bytes, int len); + void Send(const HttpServerResponseInfo& response); + + void Shift(int num_bytes); + + const std::string& recv_data() const { return recv_data_; } + int id() const { return id_; } + + private: + friend class HttpServer; + static int last_id_; + + HttpConnection(HttpServer* server, StreamListenSocket* sock); + + void DetachSocket(); + + HttpServer* server_; + scoped_refptr<StreamListenSocket> socket_; + scoped_ptr<WebSocket> web_socket_; + std::string recv_data_; + int id_; + DISALLOW_COPY_AND_ASSIGN(HttpConnection); +}; + +} // namespace net + +#endif // NET_SERVER_HTTP_CONNECTION_H_ diff --git a/chromium/net/server/http_server.cc b/chromium/net/server/http_server.cc new file mode 100644 index 00000000000..373025c4aa6 --- /dev/null +++ b/chromium/net/server/http_server.cc @@ -0,0 +1,330 @@ +// 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/server/http_server.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/sys_byteorder.h" +#include "build/build_config.h" +#include "net/base/net_errors.h" +#include "net/server/http_connection.h" +#include "net/server/http_server_request_info.h" +#include "net/server/http_server_response_info.h" +#include "net/server/web_socket.h" +#include "net/socket/tcp_listen_socket.h" + +namespace net { + +HttpServer::HttpServer(const StreamListenSocketFactory& factory, + HttpServer::Delegate* delegate) + : delegate_(delegate), + server_(factory.CreateAndListen(this)) { +} + +void HttpServer::AcceptWebSocket( + int connection_id, + const HttpServerRequestInfo& request) { + HttpConnection* connection = FindConnection(connection_id); + if (connection == NULL) + return; + + DCHECK(connection->web_socket_.get()); + connection->web_socket_->Accept(request); +} + +void HttpServer::SendOverWebSocket(int connection_id, + const std::string& data) { + HttpConnection* connection = FindConnection(connection_id); + if (connection == NULL) + return; + DCHECK(connection->web_socket_.get()); + connection->web_socket_->Send(data); +} + +void HttpServer::SendResponse(int connection_id, + const HttpServerResponseInfo& response) { + HttpConnection* connection = FindConnection(connection_id); + if (connection == NULL) + return; + connection->Send(response); +} + +void HttpServer::Send(int connection_id, + HttpStatusCode status_code, + const std::string& data, + const std::string& content_type) { + HttpServerResponseInfo response(status_code); + response.SetBody(data, content_type); + SendResponse(connection_id, response); +} + +void HttpServer::Send200(int connection_id, + const std::string& data, + const std::string& content_type) { + Send(connection_id, HTTP_OK, data, content_type); +} + +void HttpServer::Send404(int connection_id) { + SendResponse(connection_id, HttpServerResponseInfo::CreateFor404()); +} + +void HttpServer::Send500(int connection_id, const std::string& message) { + SendResponse(connection_id, HttpServerResponseInfo::CreateFor500(message)); +} + +void HttpServer::Close(int connection_id) { + HttpConnection* connection = FindConnection(connection_id); + if (connection == NULL) + return; + + // Initiating close from server-side does not lead to the DidClose call. + // Do it manually here. + DidClose(connection->socket_.get()); +} + +int HttpServer::GetLocalAddress(IPEndPoint* address) { + if (!server_) + return ERR_SOCKET_NOT_CONNECTED; + return server_->GetLocalAddress(address); +} + +void HttpServer::DidAccept(StreamListenSocket* server, + StreamListenSocket* socket) { + HttpConnection* connection = new HttpConnection(this, socket); + id_to_connection_[connection->id()] = connection; + socket_to_connection_[socket] = connection; +} + +void HttpServer::DidRead(StreamListenSocket* socket, + const char* data, + int len) { + HttpConnection* connection = FindConnection(socket); + DCHECK(connection != NULL); + if (connection == NULL) + return; + + connection->recv_data_.append(data, len); + while (connection->recv_data_.length()) { + if (connection->web_socket_.get()) { + std::string message; + WebSocket::ParseResult result = connection->web_socket_->Read(&message); + if (result == WebSocket::FRAME_INCOMPLETE) + break; + + if (result == WebSocket::FRAME_CLOSE || + result == WebSocket::FRAME_ERROR) { + Close(connection->id()); + break; + } + delegate_->OnWebSocketMessage(connection->id(), message); + continue; + } + + HttpServerRequestInfo request; + size_t pos = 0; + if (!ParseHeaders(connection, &request, &pos)) + break; + + std::string connection_header = request.GetHeaderValue("connection"); + if (connection_header == "Upgrade") { + connection->web_socket_.reset(WebSocket::CreateWebSocket(connection, + request, + &pos)); + + if (!connection->web_socket_.get()) // Not enough data was received. + break; + delegate_->OnWebSocketRequest(connection->id(), request); + connection->Shift(pos); + continue; + } + + const char kContentLength[] = "content-length"; + if (request.headers.count(kContentLength)) { + size_t content_length = 0; + const size_t kMaxBodySize = 100 << 20; + if (!base::StringToSizeT(request.GetHeaderValue(kContentLength), + &content_length) || + content_length > kMaxBodySize) { + connection->Send(HttpServerResponseInfo::CreateFor500( + "request content-length too big or unknown: " + + request.GetHeaderValue(kContentLength))); + DidClose(socket); + break; + } + + if (connection->recv_data_.length() - pos < content_length) + break; // Not enough data was received yet. + request.data = connection->recv_data_.substr(pos, content_length); + pos += content_length; + } + + delegate_->OnHttpRequest(connection->id(), request); + connection->Shift(pos); + } +} + +void HttpServer::DidClose(StreamListenSocket* socket) { + HttpConnection* connection = FindConnection(socket); + DCHECK(connection != NULL); + id_to_connection_.erase(connection->id()); + socket_to_connection_.erase(connection->socket_.get()); + delete connection; +} + +HttpServer::~HttpServer() { + STLDeleteContainerPairSecondPointers( + id_to_connection_.begin(), id_to_connection_.end()); + server_ = NULL; +} + +// +// HTTP Request Parser +// This HTTP request parser uses a simple state machine to quickly parse +// through the headers. The parser is not 100% complete, as it is designed +// for use in this simple test driver. +// +// Known issues: +// - does not handle whitespace on first HTTP line correctly. Expects +// a single space between the method/url and url/protocol. + +// Input character types. +enum header_parse_inputs { + INPUT_SPACE, + INPUT_CR, + INPUT_LF, + INPUT_COLON, + INPUT_DEFAULT, + MAX_INPUTS, +}; + +// Parser states. +enum header_parse_states { + ST_METHOD, // Receiving the method + ST_URL, // Receiving the URL + ST_PROTO, // Receiving the protocol + ST_HEADER, // Starting a Request Header + ST_NAME, // Receiving a request header name + ST_SEPARATOR, // Receiving the separator between header name and value + ST_VALUE, // Receiving a request header value + ST_DONE, // Parsing is complete and successful + ST_ERR, // Parsing encountered invalid syntax. + MAX_STATES +}; + +// State transition table +int parser_state[MAX_STATES][MAX_INPUTS] = { +/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_METHOD }, +/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_URL }, +/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_PROTO }, +/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR }, +/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_VALUE, ST_NAME }, +/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_VALUE, ST_ERR }, +/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_VALUE }, +/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE }, +/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR } +}; + +// Convert an input character to the parser's input token. +int charToInput(char ch) { + switch(ch) { + case ' ': + return INPUT_SPACE; + case '\r': + return INPUT_CR; + case '\n': + return INPUT_LF; + case ':': + return INPUT_COLON; + } + return INPUT_DEFAULT; +} + +bool HttpServer::ParseHeaders(HttpConnection* connection, + HttpServerRequestInfo* info, + size_t* ppos) { + size_t& pos = *ppos; + size_t data_len = connection->recv_data_.length(); + int state = ST_METHOD; + std::string buffer; + std::string header_name; + std::string header_value; + while (pos < data_len) { + char ch = connection->recv_data_[pos++]; + int input = charToInput(ch); + int next_state = parser_state[state][input]; + + bool transition = (next_state != state); + if (transition) { + // Do any actions based on state transitions. + switch (state) { + case ST_METHOD: + info->method = buffer; + buffer.clear(); + break; + case ST_URL: + info->path = buffer; + buffer.clear(); + break; + case ST_PROTO: + // TODO(mbelshe): Deal better with parsing protocol. + DCHECK(buffer == "HTTP/1.1"); + buffer.clear(); + break; + case ST_NAME: + header_name = StringToLowerASCII(buffer); + buffer.clear(); + break; + case ST_VALUE: + TrimWhitespaceASCII(buffer, TRIM_LEADING, &header_value); + // TODO(mbelshe): Deal better with duplicate headers + DCHECK(info->headers.find(header_name) == info->headers.end()); + info->headers[header_name] = header_value; + buffer.clear(); + break; + case ST_SEPARATOR: + break; + } + state = next_state; + } else { + // Do any actions based on current state + switch (state) { + case ST_METHOD: + case ST_URL: + case ST_PROTO: + case ST_VALUE: + case ST_NAME: + buffer.append(&ch, 1); + break; + case ST_DONE: + DCHECK(input == INPUT_LF); + return true; + case ST_ERR: + return false; + } + } + } + // No more characters, but we haven't finished parsing yet. + return false; +} + +HttpConnection* HttpServer::FindConnection(int connection_id) { + IdToConnectionMap::iterator it = id_to_connection_.find(connection_id); + if (it == id_to_connection_.end()) + return NULL; + return it->second; +} + +HttpConnection* HttpServer::FindConnection(StreamListenSocket* socket) { + SocketToConnectionMap::iterator it = socket_to_connection_.find(socket); + if (it == socket_to_connection_.end()) + return NULL; + return it->second; +} + +} // namespace net diff --git a/chromium/net/server/http_server.h b/chromium/net/server/http_server.h new file mode 100644 index 00000000000..f4345752e2d --- /dev/null +++ b/chromium/net/server/http_server.h @@ -0,0 +1,103 @@ +// 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_SERVER_HTTP_SERVER_H_ +#define NET_SERVER_HTTP_SERVER_H_ + +#include <list> +#include <map> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "net/http/http_status_code.h" +#include "net/socket/stream_listen_socket.h" + +namespace net { + +class HttpConnection; +class HttpServerRequestInfo; +class HttpServerResponseInfo; +class IPEndPoint; +class WebSocket; + +class HttpServer : public StreamListenSocket::Delegate, + public base::RefCountedThreadSafe<HttpServer> { + public: + class Delegate { + public: + virtual void OnHttpRequest(int connection_id, + const HttpServerRequestInfo& info) = 0; + + virtual void OnWebSocketRequest(int connection_id, + const HttpServerRequestInfo& info) = 0; + + virtual void OnWebSocketMessage(int connection_id, + const std::string& data) = 0; + + virtual void OnClose(int connection_id) = 0; + + protected: + virtual ~Delegate() {} + }; + + HttpServer(const StreamListenSocketFactory& socket_factory, + HttpServer::Delegate* delegate); + + void AcceptWebSocket(int connection_id, + const HttpServerRequestInfo& request); + void SendOverWebSocket(int connection_id, const std::string& data); + void SendResponse(int connection_id, const HttpServerResponseInfo& response); + void Send(int connection_id, + HttpStatusCode status_code, + const std::string& data, + const std::string& mime_type); + void Send200(int connection_id, + const std::string& data, + const std::string& mime_type); + void Send404(int connection_id); + void Send500(int connection_id, const std::string& message); + + void Close(int connection_id); + + // Copies the local address to |address|. Returns a network error code. + int GetLocalAddress(IPEndPoint* address); + + // ListenSocketDelegate + virtual void DidAccept(StreamListenSocket* server, + StreamListenSocket* socket) OVERRIDE; + virtual void DidRead(StreamListenSocket* socket, + const char* data, + int len) OVERRIDE; + virtual void DidClose(StreamListenSocket* socket) OVERRIDE; + + protected: + virtual ~HttpServer(); + + private: + friend class base::RefCountedThreadSafe<HttpServer>; + friend class HttpConnection; + + // Expects the raw data to be stored in recv_data_. If parsing is successful, + // will remove the data parsed from recv_data_, leaving only the unused + // recv data. + bool ParseHeaders(HttpConnection* connection, + HttpServerRequestInfo* info, + size_t* pos); + + HttpConnection* FindConnection(int connection_id); + HttpConnection* FindConnection(StreamListenSocket* socket); + + HttpServer::Delegate* delegate_; + scoped_refptr<StreamListenSocket> server_; + typedef std::map<int, HttpConnection*> IdToConnectionMap; + IdToConnectionMap id_to_connection_; + typedef std::map<StreamListenSocket*, HttpConnection*> SocketToConnectionMap; + SocketToConnectionMap socket_to_connection_; + + DISALLOW_COPY_AND_ASSIGN(HttpServer); +}; + +} // namespace net + +#endif // NET_SERVER_HTTP_SERVER_H_ diff --git a/chromium/net/server/http_server_request_info.cc b/chromium/net/server/http_server_request_info.cc new file mode 100644 index 00000000000..67965f29aa3 --- /dev/null +++ b/chromium/net/server/http_server_request_info.cc @@ -0,0 +1,25 @@ +// 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/http_server_request_info.h" + +#include "base/strings/string_util.h" + +namespace net { + +HttpServerRequestInfo::HttpServerRequestInfo() {} + +HttpServerRequestInfo::~HttpServerRequestInfo() {} + +std::string HttpServerRequestInfo::GetHeaderValue( + const std::string& header_name) const { + DCHECK_EQ(StringToLowerASCII(header_name), header_name); + HttpServerRequestInfo::HeadersMap::const_iterator it = + headers.find(header_name); + if (it != headers.end()) + return it->second; + return std::string(); +} + +} // namespace net diff --git a/chromium/net/server/http_server_request_info.h b/chromium/net/server/http_server_request_info.h new file mode 100644 index 00000000000..62824187901 --- /dev/null +++ b/chromium/net/server/http_server_request_info.h @@ -0,0 +1,43 @@ +// 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_SERVER_HTTP_SERVER_REQUEST_INFO_H_ +#define NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_ + +#include <map> +#include <string> + +namespace net { + +// Meta information about an HTTP request. +// This is geared toward servers in that it keeps a map of the headers and +// values rather than just a list of header strings (which net::HttpRequestInfo +// does). +class HttpServerRequestInfo { + public: + HttpServerRequestInfo(); + ~HttpServerRequestInfo(); + + // Returns header value for given header name. |header_name| should be + // lower case. + std::string GetHeaderValue(const std::string& header_name) const; + + // Request method. + std::string method; + + // Request line. + std::string path; + + // Request data. + std::string data; + + // A map of the names -> values for HTTP headers. These should always + // contain lower case field names. + typedef std::map<std::string, std::string> HeadersMap; + mutable HeadersMap headers; +}; + +} // namespace net + +#endif // NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_ diff --git a/chromium/net/server/http_server_response_info.cc b/chromium/net/server/http_server_response_info.cc new file mode 100644 index 00000000000..e4c6043aa8d --- /dev/null +++ b/chromium/net/server/http_server_response_info.cc @@ -0,0 +1,67 @@ +// 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/server/http_server_response_info.h" + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "net/http/http_request_headers.h" + +namespace net { + +HttpServerResponseInfo::HttpServerResponseInfo() : status_code_(HTTP_OK) {} + +HttpServerResponseInfo::HttpServerResponseInfo(HttpStatusCode status_code) + : status_code_(status_code) {} + +HttpServerResponseInfo::~HttpServerResponseInfo() {} + +// static +HttpServerResponseInfo HttpServerResponseInfo::CreateFor404() { + HttpServerResponseInfo response(HTTP_NOT_FOUND); + response.SetBody(std::string(), "text/html"); + return response; +} + +// static +HttpServerResponseInfo HttpServerResponseInfo::CreateFor500( + const std::string& body) { + HttpServerResponseInfo response(HTTP_INTERNAL_SERVER_ERROR); + response.SetBody(body, "text/html"); + return response; +} + +void HttpServerResponseInfo::AddHeader(const std::string& name, + const std::string& value) { + headers_.push_back(std::make_pair(name, value)); +} + +void HttpServerResponseInfo::SetBody(const std::string& body, + const std::string& content_type) { + DCHECK(body_.empty()); + body_ = body; + AddHeader(HttpRequestHeaders::kContentLength, + base::StringPrintf("%" PRIuS, body.length())); + AddHeader(HttpRequestHeaders::kContentType, content_type); +} + +std::string HttpServerResponseInfo::Serialize() const { + std::string response = base::StringPrintf( + "HTTP/1.1 %d %s\r\n", status_code_, GetHttpReasonPhrase(status_code_)); + Headers::const_iterator header; + for (header = headers_.begin(); header != headers_.end(); ++header) + response += header->first + ":" + header->second + "\r\n"; + + return response + "\r\n" + body_; +} + +HttpStatusCode HttpServerResponseInfo::status_code() const { + return status_code_; +} + +const std::string& HttpServerResponseInfo::body() const { + return body_; +} + +} // namespace net diff --git a/chromium/net/server/http_server_response_info.h b/chromium/net/server/http_server_response_info.h new file mode 100644 index 00000000000..d6cedaa8446 --- /dev/null +++ b/chromium/net/server/http_server_response_info.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef NET_SERVER_HTTP_SERVER_RESPONSE_INFO_H_ +#define NET_SERVER_HTTP_SERVER_RESPONSE_INFO_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "net/http/http_status_code.h" + +namespace net { + +class HttpServerResponseInfo { + public: + // Creates a 200 OK HttpServerResponseInfo. + HttpServerResponseInfo(); + explicit HttpServerResponseInfo(HttpStatusCode status_code); + ~HttpServerResponseInfo(); + + static HttpServerResponseInfo CreateFor404(); + static HttpServerResponseInfo CreateFor500(const std::string& body); + + void AddHeader(const std::string& name, const std::string& value); + + // This also adds an appropriate Content-Length header. + void SetBody(const std::string& body, const std::string& content_type); + + std::string Serialize() const; + + HttpStatusCode status_code() const; + const std::string& body() const; + + private: + typedef std::vector<std::pair<std::string, std::string> > Headers; + + HttpStatusCode status_code_; + Headers headers_; + std::string body_; +}; + +} // namespace net + +#endif // NET_SERVER_HTTP_SERVER_RESPONSE_INFO_H_ diff --git a/chromium/net/server/http_server_response_info_unittest.cc b/chromium/net/server/http_server_response_info_unittest.cc new file mode 100644 index 00000000000..f7b8e251d34 --- /dev/null +++ b/chromium/net/server/http_server_response_info_unittest.cc @@ -0,0 +1,51 @@ +// 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/http/http_status_code.h" +#include "net/server/http_server_response_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(HttpServerResponseInfoTest, StatusLine) { + HttpServerResponseInfo response; + ASSERT_EQ(HTTP_OK, response.status_code()); + ASSERT_EQ("HTTP/1.1 200 OK\r\n\r\n", response.Serialize()); +} + +TEST(HttpServerResponseInfoTest, Headers) { + HttpServerResponseInfo response; + response.AddHeader("A", "1"); + response.AddHeader("A", "2"); + ASSERT_EQ("HTTP/1.1 200 OK\r\nA:1\r\nA:2\r\n\r\n", response.Serialize()); +} + +TEST(HttpServerResponseInfoTest, Body) { + HttpServerResponseInfo response; + ASSERT_EQ(std::string(), response.body()); + response.SetBody("body", "type"); + ASSERT_EQ("body", response.body()); + ASSERT_EQ( + "HTTP/1.1 200 OK\r\nContent-Length:4\r\nContent-Type:type\r\n\r\nbody", + response.Serialize()); +} + +TEST(HttpServerResponseInfoTest, CreateFor404) { + HttpServerResponseInfo response = HttpServerResponseInfo::CreateFor404(); + ASSERT_EQ( + "HTTP/1.1 404 Not Found\r\n" + "Content-Length:0\r\nContent-Type:text/html\r\n\r\n", + response.Serialize()); +} + +TEST(HttpServerResponseInfoTest, CreateFor500) { + HttpServerResponseInfo response = + HttpServerResponseInfo::CreateFor500("mess"); + ASSERT_EQ( + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Length:4\r\nContent-Type:text/html\r\n\r\nmess", + response.Serialize()); +} + +} // namespace net diff --git a/chromium/net/server/http_server_unittest.cc b/chromium/net/server/http_server_unittest.cc new file mode 100644 index 00000000000..48a2ce7571f --- /dev/null +++ b/chromium/net/server/http_server_unittest.cc @@ -0,0 +1,319 @@ +// 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 <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/format_macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/time/time.h" +#include "net/base/address_list.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/server/http_server.h" +#include "net/server/http_server_request_info.h" +#include "net/socket/tcp_client_socket.h" +#include "net/socket/tcp_listen_socket.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +void SetTimedOutAndQuitLoop(const base::WeakPtr<bool> timed_out, + const base::Closure& quit_loop_func) { + if (timed_out) { + *timed_out = true; + quit_loop_func.Run(); + } +} + +bool RunLoopWithTimeout(base::RunLoop* run_loop) { + bool timed_out = false; + base::WeakPtrFactory<bool> timed_out_weak_factory(&timed_out); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&SetTimedOutAndQuitLoop, + timed_out_weak_factory.GetWeakPtr(), + run_loop->QuitClosure()), + base::TimeDelta::FromSeconds(1)); + run_loop->Run(); + return !timed_out; +} + +class TestHttpClient { + public: + TestHttpClient() : connect_result_(OK) {} + + int ConnectAndWait(const IPEndPoint& address) { + AddressList addresses(address); + NetLog::Source source; + socket_.reset(new TCPClientSocket(addresses, NULL, source)); + + base::RunLoop run_loop; + connect_result_ = socket_->Connect(base::Bind(&TestHttpClient::OnConnect, + base::Unretained(this), + run_loop.QuitClosure())); + if (connect_result_ != OK && connect_result_ != ERR_IO_PENDING) + return connect_result_; + + if (!RunLoopWithTimeout(&run_loop)) + return ERR_TIMED_OUT; + return connect_result_; + } + + void Send(const std::string& data) { + write_buffer_ = + new DrainableIOBuffer(new StringIOBuffer(data), data.length()); + Write(); + } + + private: + void OnConnect(const base::Closure& quit_loop, int result) { + connect_result_ = result; + quit_loop.Run(); + } + + void Write() { + int result = socket_->Write( + write_buffer_.get(), + write_buffer_->BytesRemaining(), + base::Bind(&TestHttpClient::OnWrite, base::Unretained(this))); + if (result != ERR_IO_PENDING) + OnWrite(result); + } + + void OnWrite(int result) { + ASSERT_GT(result, 0); + write_buffer_->DidConsume(result); + if (write_buffer_->BytesRemaining()) + Write(); + } + + scoped_refptr<DrainableIOBuffer> write_buffer_; + scoped_ptr<TCPClientSocket> socket_; + int connect_result_; +}; + +} // namespace + +class HttpServerTest : public testing::Test, + public HttpServer::Delegate { + public: + HttpServerTest() : quit_after_request_count_(0) {} + + virtual void SetUp() OVERRIDE { + TCPListenSocketFactory socket_factory("127.0.0.1", 0); + server_ = new HttpServer(socket_factory, this); + ASSERT_EQ(OK, server_->GetLocalAddress(&server_address_)); + } + + virtual void OnHttpRequest(int connection_id, + const HttpServerRequestInfo& info) OVERRIDE { + requests_.push_back(info); + if (requests_.size() == quit_after_request_count_) + run_loop_quit_func_.Run(); + } + + virtual void OnWebSocketRequest(int connection_id, + const HttpServerRequestInfo& info) OVERRIDE { + NOTREACHED(); + } + + virtual void OnWebSocketMessage(int connection_id, + const std::string& data) OVERRIDE { + NOTREACHED(); + } + + virtual void OnClose(int connection_id) OVERRIDE {} + + bool RunUntilRequestsReceived(size_t count) { + quit_after_request_count_ = count; + if (requests_.size() == count) + return true; + + base::RunLoop run_loop; + run_loop_quit_func_ = run_loop.QuitClosure(); + bool success = RunLoopWithTimeout(&run_loop); + run_loop_quit_func_.Reset(); + return success; + } + + protected: + scoped_refptr<HttpServer> server_; + IPEndPoint server_address_; + base::Closure run_loop_quit_func_; + std::vector<HttpServerRequestInfo> requests_; + + private: + size_t quit_after_request_count_; +}; + +TEST_F(HttpServerTest, Request) { + TestHttpClient client; + ASSERT_EQ(OK, client.ConnectAndWait(server_address_)); + client.Send("GET /test HTTP/1.1\r\n\r\n"); + ASSERT_TRUE(RunUntilRequestsReceived(1)); + ASSERT_EQ("GET", requests_[0].method); + ASSERT_EQ("/test", requests_[0].path); + ASSERT_EQ("", requests_[0].data); + ASSERT_EQ(0u, requests_[0].headers.size()); +} + +TEST_F(HttpServerTest, RequestWithHeaders) { + TestHttpClient client; + ASSERT_EQ(OK, client.ConnectAndWait(server_address_)); + const char* kHeaders[][3] = { + {"Header", ": ", "1"}, + {"HeaderWithNoWhitespace", ":", "1"}, + {"HeaderWithWhitespace", " : \t ", "1 1 1 \t "}, + {"HeaderWithColon", ": ", "1:1"}, + {"EmptyHeader", ":", ""}, + {"EmptyHeaderWithWhitespace", ": \t ", ""}, + {"HeaderWithNonASCII", ": ", "\u00f7"}, + }; + std::string headers; + for (size_t i = 0; i < arraysize(kHeaders); ++i) { + headers += + std::string(kHeaders[i][0]) + kHeaders[i][1] + kHeaders[i][2] + "\r\n"; + } + + client.Send("GET /test HTTP/1.1\r\n" + headers + "\r\n"); + ASSERT_TRUE(RunUntilRequestsReceived(1)); + ASSERT_EQ("", requests_[0].data); + + for (size_t i = 0; i < arraysize(kHeaders); ++i) { + std::string field = StringToLowerASCII(std::string(kHeaders[i][0])); + std::string value = kHeaders[i][2]; + ASSERT_EQ(1u, requests_[0].headers.count(field)) << field; + ASSERT_EQ(value, requests_[0].headers[field]) << kHeaders[i][0]; + } +} + +TEST_F(HttpServerTest, RequestWithBody) { + TestHttpClient client; + ASSERT_EQ(OK, client.ConnectAndWait(server_address_)); + std::string body = "a" + std::string(1 << 10, 'b') + "c"; + client.Send(base::StringPrintf( + "GET /test HTTP/1.1\r\n" + "SomeHeader: 1\r\n" + "Content-Length: %" PRIuS "\r\n\r\n%s", + body.length(), + body.c_str())); + ASSERT_TRUE(RunUntilRequestsReceived(1)); + ASSERT_EQ(2u, requests_[0].headers.size()); + ASSERT_EQ(body.length(), requests_[0].data.length()); + ASSERT_EQ('a', body[0]); + ASSERT_EQ('c', *body.rbegin()); +} + +TEST_F(HttpServerTest, RequestWithTooLargeBody) { + class TestURLFetcherDelegate : public URLFetcherDelegate { + public: + TestURLFetcherDelegate(const base::Closure& quit_loop_func) + : quit_loop_func_(quit_loop_func) {} + virtual ~TestURLFetcherDelegate() {} + + virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE { + EXPECT_EQ(HTTP_INTERNAL_SERVER_ERROR, source->GetResponseCode()); + quit_loop_func_.Run(); + } + + private: + base::Closure quit_loop_func_; + }; + + base::RunLoop run_loop; + TestURLFetcherDelegate delegate(run_loop.QuitClosure()); + + scoped_refptr<URLRequestContextGetter> request_context_getter( + new TestURLRequestContextGetter(base::MessageLoopProxy::current())); + scoped_ptr<URLFetcher> fetcher( + URLFetcher::Create(GURL(base::StringPrintf("http://127.0.0.1:%d/test", + server_address_.port())), + URLFetcher::GET, + &delegate)); + fetcher->SetRequestContext(request_context_getter.get()); + fetcher->AddExtraRequestHeader( + base::StringPrintf("content-length:%d", 1 << 30)); + fetcher->Start(); + + ASSERT_TRUE(RunLoopWithTimeout(&run_loop)); + ASSERT_EQ(0u, requests_.size()); +} + +namespace { + +class MockStreamListenSocket : public StreamListenSocket { + public: + MockStreamListenSocket(StreamListenSocket::Delegate* delegate) + : StreamListenSocket(kInvalidSocket, delegate) {} + + virtual void Accept() OVERRIDE { NOTREACHED(); } + + private: + virtual ~MockStreamListenSocket() {} +}; + +} // namespace + +TEST_F(HttpServerTest, RequestWithBodySplitAcrossPackets) { + scoped_refptr<StreamListenSocket> socket( + new MockStreamListenSocket(server_.get())); + server_->DidAccept(NULL, socket.get()); + std::string body("body"); + std::string request = base::StringPrintf( + "GET /test HTTP/1.1\r\n" + "SomeHeader: 1\r\n" + "Content-Length: %" PRIuS "\r\n\r\n%s", + body.length(), + body.c_str()); + server_->DidRead(socket.get(), request.c_str(), request.length() - 2); + ASSERT_EQ(0u, requests_.size()); + server_->DidRead(socket.get(), request.c_str() + request.length() - 2, 2); + ASSERT_EQ(1u, requests_.size()); + ASSERT_EQ(body, requests_[0].data); +} + +TEST_F(HttpServerTest, MultipleRequestsOnSameConnection) { + // The idea behind this test is that requests with or without bodies should + // not break parsing of the next request. + TestHttpClient client; + ASSERT_EQ(OK, client.ConnectAndWait(server_address_)); + std::string body = "body"; + client.Send(base::StringPrintf( + "GET /test HTTP/1.1\r\n" + "Content-Length: %" PRIuS "\r\n\r\n%s", + body.length(), + body.c_str())); + ASSERT_TRUE(RunUntilRequestsReceived(1)); + ASSERT_EQ(body, requests_[0].data); + + client.Send("GET /test2 HTTP/1.1\r\n\r\n"); + ASSERT_TRUE(RunUntilRequestsReceived(2)); + ASSERT_EQ("/test2", requests_[1].path); + + client.Send("GET /test3 HTTP/1.1\r\n\r\n"); + ASSERT_TRUE(RunUntilRequestsReceived(3)); + ASSERT_EQ("/test3", requests_[2].path); +} + +} // namespace net diff --git a/chromium/net/server/web_socket.cc b/chromium/net/server/web_socket.cc new file mode 100644 index 00000000000..f06b425fee0 --- /dev/null +++ b/chromium/net/server/web_socket.cc @@ -0,0 +1,406 @@ +// 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/server/web_socket.h" + +#include <limits> + +#include "base/base64.h" +#include "base/rand_util.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/sys_byteorder.h" +#include "net/server/http_connection.h" +#include "net/server/http_server_request_info.h" +#include "net/server/http_server_response_info.h" + +namespace net { + +namespace { + +static uint32 WebSocketKeyFingerprint(const std::string& str) { + std::string result; + const char* p_char = str.c_str(); + int length = str.length(); + int spaces = 0; + for (int i = 0; i < length; ++i) { + if (p_char[i] >= '0' && p_char[i] <= '9') + result.append(&p_char[i], 1); + else if (p_char[i] == ' ') + spaces++; + } + if (spaces == 0) + return 0; + int64 number = 0; + if (!base::StringToInt64(result, &number)) + return 0; + return base::HostToNet32(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) OVERRIDE { + 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) OVERRIDE { + 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) OVERRIDE { + 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->Send(HttpServerResponseInfo::CreateFor500( + "Invalid request format. Sec-WebSocket-Key1 is empty or isn't " + "specified.")); + return; + } + + if (key2.empty()) { + connection->Send(HttpServerResponseInfo::CreateFor500( + "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 WebSocketHybi17 : 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" && version != "13") + return NULL; + + std::string key = request.GetHeaderValue("sec-websocket-key"); + if (key.empty()) { + connection->Send(HttpServerResponseInfo::CreateFor500( + "Invalid request format. Sec-WebSocket-Key is empty or isn't " + "specified.")); + return NULL; + } + return new WebSocketHybi17(connection, request, pos); + } + + virtual void Accept(const HttpServerRequestInfo& request) OVERRIDE { + static const char* const kWebSocketGuid = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::string key = request.GetHeaderValue("sec-websocket-key"); + std::string data = base::StringPrintf("%s%s", key.c_str(), kWebSocketGuid); + 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) OVERRIDE { + const std::string& frame = connection_->recv_data(); + int bytes_consumed = 0; + + ParseResult result = + WebSocket::DecodeFrameHybi17(frame, true, &bytes_consumed, message); + if (result == FRAME_OK) + connection_->Shift(bytes_consumed); + if (result == FRAME_CLOSE) + closed_ = true; + return result; + } + + virtual void Send(const std::string& message) OVERRIDE { + if (closed_) + return; + std::string data = WebSocket::EncodeFrameHybi17(message, 0); + connection_->Send(data); + } + + private: + WebSocketHybi17(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), + closed_(false) { + } + + OpCode op_code_; + bool final_; + bool reserved1_; + bool reserved2_; + bool reserved3_; + bool masked_; + const char* payload_; + size_t payload_length_; + const char* frame_end_; + bool closed_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketHybi17); +}; + +} // anonymous namespace + +WebSocket* WebSocket::CreateWebSocket(HttpConnection* connection, + const HttpServerRequestInfo& request, + size_t* pos) { + WebSocket* socket = WebSocketHybi17::Create(connection, request, pos); + if (socket) + return socket; + + return WebSocketHixie76::Create(connection, request, pos); +} + +// static +WebSocket::ParseResult WebSocket::DecodeFrameHybi17(const std::string& frame, + bool client_frame, + int* bytes_consumed, + std::string* output) { + size_t data_length = frame.length(); + if (data_length < 2) + return FRAME_INCOMPLETE; + + const char* buffer_begin = const_cast<char*>(frame.data()); + const char* p = buffer_begin; + const char* buffer_end = p + data_length; + + unsigned char first_byte = *p++; + unsigned char second_byte = *p++; + + bool final = (first_byte & kFinalBit) != 0; + bool reserved1 = (first_byte & kReserved1Bit) != 0; + bool reserved2 = (first_byte & kReserved2Bit) != 0; + bool reserved3 = (first_byte & kReserved3Bit) != 0; + int op_code = first_byte & kOpCodeMask; + bool masked = (second_byte & kMaskBit) != 0; + if (!final || reserved1 || reserved2 || reserved3) + return FRAME_ERROR; // Extensions and not supported. + + bool closed = false; + switch (op_code) { + case kOpCodeClose: + closed = true; + break; + case kOpCodeText: + break; + case kOpCodeBinary: // We don't support binary frames yet. + case kOpCodeContinuation: // We don't support binary frames yet. + case kOpCodePing: // We don't support binary frames yet. + case kOpCodePong: // We don't support binary frames yet. + default: + return FRAME_ERROR; + } + + if (client_frame && !masked) // In Hybi-17 spec client MUST mask his frame. + return FRAME_ERROR; + + 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++); + } + } + + size_t actual_masking_key_length = masked ? kMaskingKeyWidthInBytes : 0; + static const uint64 max_payload_length = 0x7FFFFFFFFFFFFFFFull; + static size_t max_length = std::numeric_limits<size_t>::max(); + if (payload_length64 > max_payload_length || + payload_length64 + actual_masking_key_length > max_length) { + // WebSocket frame length too large. + return FRAME_ERROR; + } + size_t payload_length = static_cast<size_t>(payload_length64); + + size_t total_length = actual_masking_key_length + payload_length; + if (static_cast<size_t>(buffer_end - p) < total_length) + return FRAME_INCOMPLETE; + + if (masked) { + output->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. + (*output)[i] = payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]; + } else { + std::string buffer(p, p + payload_length); + output->swap(buffer); + } + + size_t pos = p + actual_masking_key_length + payload_length - buffer_begin; + *bytes_consumed = pos; + return closed ? FRAME_CLOSE : FRAME_OK; +} + +// static +std::string WebSocket::EncodeFrameHybi17(const std::string& message, + int masking_key) { + std::vector<char> frame; + OpCode op_code = kOpCodeText; + size_t data_length = message.length(); + + frame.push_back(kFinalBit | op_code); + char mask_key_bit = masking_key != 0 ? kMaskBit : 0; + if (data_length <= kMaxSingleBytePayloadLength) + frame.push_back(data_length | mask_key_bit); + else if (data_length <= 0xFFFF) { + frame.push_back(kTwoBytePayloadLengthField | mask_key_bit); + frame.push_back((data_length & 0xFF00) >> 8); + frame.push_back(data_length & 0xFF); + } else { + frame.push_back(kEightBytePayloadLengthField | mask_key_bit); + 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); + } + + const char* data = const_cast<char*>(message.data()); + if (masking_key != 0) { + const char* mask_bytes = reinterpret_cast<char*>(&masking_key); + frame.insert(frame.end(), mask_bytes, mask_bytes + 4); + for (size_t i = 0; i < data_length; ++i) // Mask the payload. + frame.push_back(data[i] ^ mask_bytes[i % kMaskingKeyWidthInBytes]); + } else { + frame.insert(frame.end(), data, data + data_length); + } + return std::string(&frame[0], frame.size()); +} + +WebSocket::WebSocket(HttpConnection* connection) : connection_(connection) { +} + +} // namespace net diff --git a/chromium/net/server/web_socket.h b/chromium/net/server/web_socket.h new file mode 100644 index 00000000000..49ced84ee6d --- /dev/null +++ b/chromium/net/server/web_socket.h @@ -0,0 +1,53 @@ +// 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_SERVER_WEB_SOCKET_H_ +#define NET_SERVER_WEB_SOCKET_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace net { + +class HttpConnection; +class HttpServerRequestInfo; + +class WebSocket { + public: + enum ParseResult { + FRAME_OK, + FRAME_INCOMPLETE, + FRAME_CLOSE, + FRAME_ERROR + }; + + static WebSocket* CreateWebSocket(HttpConnection* connection, + const HttpServerRequestInfo& request, + size_t* pos); + + static ParseResult DecodeFrameHybi17(const std::string& frame, + bool client_frame, + int* bytes_consumed, + std::string* output); + + static std::string EncodeFrameHybi17(const std::string& data, + int masking_key); + + virtual void Accept(const HttpServerRequestInfo& request) = 0; + virtual ParseResult Read(std::string* message) = 0; + virtual void Send(const std::string& message) = 0; + virtual ~WebSocket() {} + + protected: + explicit WebSocket(HttpConnection* connection); + HttpConnection* connection_; + + private: + DISALLOW_COPY_AND_ASSIGN(WebSocket); +}; + +} // namespace net + +#endif // NET_SERVER_WEB_SOCKET_H_ |