diff options
Diffstat (limited to 'websocketpp/processors')
-rw-r--r-- | websocketpp/processors/base.hpp | 299 | ||||
-rw-r--r-- | websocketpp/processors/hybi00.hpp | 462 | ||||
-rw-r--r-- | websocketpp/processors/hybi07.hpp | 78 | ||||
-rw-r--r-- | websocketpp/processors/hybi08.hpp | 83 | ||||
-rw-r--r-- | websocketpp/processors/hybi13.hpp | 1056 | ||||
-rw-r--r-- | websocketpp/processors/processor.hpp | 407 |
6 files changed, 2385 insertions, 0 deletions
diff --git a/websocketpp/processors/base.hpp b/websocketpp/processors/base.hpp new file mode 100644 index 00000000..ddb8b81a --- /dev/null +++ b/websocketpp/processors/base.hpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2014, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_BASE_HPP +#define WEBSOCKETPP_PROCESSOR_BASE_HPP + +#include <websocketpp/close.hpp> +#include <websocketpp/utilities.hpp> +#include <websocketpp/uri.hpp> + +#include <websocketpp/common/cpp11.hpp> +#include <websocketpp/common/system_error.hpp> + +#include <string> + +namespace websocketpp { +namespace processor { + +/// Constants related to processing WebSocket connections +namespace constants { + +static char const upgrade_token[] = "websocket"; +static char const connection_token[] = "upgrade"; +static char const handshake_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +} // namespace constants + + +/// Processor class related error codes +namespace error_cat { +enum value { + BAD_REQUEST = 0, // Error was the result of improperly formatted user input + INTERNAL_ERROR = 1, // Error was a logic error internal to WebSocket++ + PROTOCOL_VIOLATION = 2, + MESSAGE_TOO_BIG = 3, + PAYLOAD_VIOLATION = 4 // Error was due to receiving invalid payload data +}; +} // namespace error_cat + +/// Error code category and codes used by all processor types +namespace error { +enum processor_errors { + /// Catch-all error for processor policy errors that don't fit in other + /// categories + general = 1, + + /// Error was the result of improperly formatted user input + bad_request, + + /// Processor encountered a protocol violation in an incoming message + protocol_violation, + + /// Processor encountered a message that was too large + message_too_big, + + /// Processor encountered invalid payload data. + invalid_payload, + + /// The processor method was called with invalid arguments + invalid_arguments, + + /// Opcode was invalid for requested operation + invalid_opcode, + + /// Control frame too large + control_too_big, + + /// Illegal use of reserved bit + invalid_rsv_bit, + + /// Fragmented control message + fragmented_control, + + /// Continuation without message + invalid_continuation, + + /// Clients may not send unmasked frames + masking_required, + + /// Servers may not send masked frames + masking_forbidden, + + /// Payload length not minimally encoded + non_minimal_encoding, + + /// Not supported on 32 bit systems + requires_64bit, + + /// Invalid UTF-8 encoding + invalid_utf8, + + /// Operation required not implemented functionality + not_implemented, + + /// Invalid HTTP method + invalid_http_method, + + /// Invalid HTTP version + invalid_http_version, + + /// Invalid HTTP status + invalid_http_status, + + /// Missing Required Header + missing_required_header, + + /// Embedded SHA-1 library error + sha1_library, + + /// No support for this feature in this protocol version. + no_protocol_support, + + /// Reserved close code used + reserved_close_code, + + /// Invalid close code used + invalid_close_code, + + /// Using a reason requires a close code + reason_requires_code, + + /// Error parsing subprotocols + subprotocol_parse_error, + + /// Error parsing extensions + extension_parse_error, + + /// Extension related operation was ignored because extensions are disabled + extensions_disabled, + + /// Short Ke3 read. Hybi00 requires a third key to be read from the 8 bytes + /// after the handshake. Less than 8 bytes were read. + short_key3 +}; + +/// Category for processor errors +class processor_category : public lib::error_category { +public: + processor_category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.processor"; + } + + std::string message(int value) const { + switch(value) { + case error::general: + return "Generic processor error"; + case error::bad_request: + return "invalid user input"; + case error::protocol_violation: + return "Generic protocol violation"; + case error::message_too_big: + return "A message was too large"; + case error::invalid_payload: + return "A payload contained invalid data"; + case error::invalid_arguments: + return "invalid function arguments"; + case error::invalid_opcode: + return "invalid opcode"; + case error::control_too_big: + return "Control messages are limited to fewer than 125 characters"; + case error::invalid_rsv_bit: + return "Invalid use of reserved bits"; + case error::fragmented_control: + return "Control messages cannot be fragmented"; + case error::invalid_continuation: + return "Invalid message continuation"; + case error::masking_required: + return "Clients may not send unmasked frames"; + case error::masking_forbidden: + return "Servers may not send masked frames"; + case error::non_minimal_encoding: + return "Payload length was not minimally encoded"; + case error::requires_64bit: + return "64 bit frames are not supported on 32 bit systems"; + case error::invalid_utf8: + return "Invalid UTF8 encoding"; + case error::not_implemented: + return "Operation required not implemented functionality"; + case error::invalid_http_method: + return "Invalid HTTP method."; + case error::invalid_http_version: + return "Invalid HTTP version."; + case error::invalid_http_status: + return "Invalid HTTP status."; + case error::missing_required_header: + return "A required HTTP header is missing"; + case error::sha1_library: + return "SHA-1 library error"; + case error::no_protocol_support: + return "The WebSocket protocol version in use does not support this feature"; + case error::reserved_close_code: + return "Reserved close code used"; + case error::invalid_close_code: + return "Invalid close code used"; + case error::reason_requires_code: + return "Using a close reason requires a valid close code"; + case error::subprotocol_parse_error: + return "Error parsing subprotocol header"; + case error::extension_parse_error: + return "Error parsing extension header"; + case error::extensions_disabled: + return "Extensions are disabled"; + case error::short_key3: + return "Short Hybi00 Key 3 read"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the processor error category +inline lib::error_category const & get_processor_category() { + static processor_category instance; + return instance; +} + +/// Create an error code with the given value and the processor category +inline lib::error_code make_error_code(error::processor_errors e) { + return lib::error_code(static_cast<int>(e), get_processor_category()); +} + +/// Converts a processor error_code into a websocket close code +/** + * Looks up the appropriate WebSocket close code that should be sent after an + * error of this sort occurred. + * + * If the error is not in the processor category close::status::blank is + * returned. + * + * If the error isn't normally associated with reasons to close a connection + * (such as errors intended to be used internally or delivered to client + * applications, ex: invalid arguments) then + * close::status::internal_endpoint_error is returned. + */ +inline close::status::value to_ws(lib::error_code ec) { + if (ec.category() != get_processor_category()) { + return close::status::blank; + } + + switch (ec.value()) { + case error::protocol_violation: + case error::control_too_big: + case error::invalid_opcode: + case error::invalid_rsv_bit: + case error::fragmented_control: + case error::invalid_continuation: + case error::masking_required: + case error::masking_forbidden: + case error::reserved_close_code: + case error::invalid_close_code: + return close::status::protocol_error; + case error::invalid_payload: + case error::invalid_utf8: + return close::status::invalid_payload; + case error::message_too_big: + return close::status::message_too_big; + default: + return close::status::internal_endpoint_error; + } +} + +} // namespace error +} // namespace processor +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum<websocketpp::processor::error::processor_errors> +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif //WEBSOCKETPP_PROCESSOR_BASE_HPP diff --git a/websocketpp/processors/hybi00.hpp b/websocketpp/processors/hybi00.hpp new file mode 100644 index 00000000..95ad9dfa --- /dev/null +++ b/websocketpp/processors/hybi00.hpp @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2014, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_HYBI00_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI00_HPP + +#include <websocketpp/frame.hpp> +#include <websocketpp/http/constants.hpp> + +#include <websocketpp/utf8_validator.hpp> +#include <websocketpp/common/network.hpp> +#include <websocketpp/common/md5.hpp> +#include <websocketpp/common/platforms.hpp> + +#include <websocketpp/processors/processor.hpp> + +#include <algorithm> +#include <cstdlib> +#include <string> +#include <vector> + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 00 +/** + * There are many differences between Hybi 00 and Hybi 13 + */ +template <typename config> +class hybi00 : public processor<config> { +public: + typedef processor<config> base; + + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + + typedef typename config::message_type message_type; + typedef typename message_type::ptr message_ptr; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + + explicit hybi00(bool secure, bool p_is_server, msg_manager_ptr manager) + : processor<config>(secure, p_is_server) + , msg_hdr(0x00) + , msg_ftr(0xff) + , m_state(HEADER) + , m_msg_manager(manager) {} + + int get_version() const { + return 0; + } + + lib::error_code validate_handshake(request_type const & r) const { + if (r.get_method() != "GET") { + return make_error_code(error::invalid_http_method); + } + + if (r.get_version() != "HTTP/1.1") { + return make_error_code(error::invalid_http_version); + } + + // required headers + // Host is required by HTTP/1.1 + // Connection is required by is_websocket_handshake + // Upgrade is required by is_websocket_handshake + if (r.get_header("Sec-WebSocket-Key1").empty() || + r.get_header("Sec-WebSocket-Key2").empty() || + r.get_header("Sec-WebSocket-Key3").empty()) + { + return make_error_code(error::missing_required_header); + } + + return lib::error_code(); + } + + lib::error_code process_handshake(request_type const & req, + std::string const & subprotocol, response_type & res) const + { + char key_final[16]; + + // copy key1 into final key + decode_client_key(req.get_header("Sec-WebSocket-Key1"), &key_final[0]); + + // copy key2 into final key + decode_client_key(req.get_header("Sec-WebSocket-Key2"), &key_final[4]); + + // copy key3 into final key + // key3 should be exactly 8 bytes. If it is more it will be truncated + // if it is less the final key will almost certainly be wrong. + // TODO: decide if it is best to silently fail here or produce some sort + // of warning or exception. + std::string const & key3 = req.get_header("Sec-WebSocket-Key3"); + std::copy(key3.c_str(), + key3.c_str()+(std::min)(static_cast<size_t>(8), key3.size()), + &key_final[8]); + + res.append_header( + "Sec-WebSocket-Key3", + md5::md5_hash_string(std::string(key_final,16)) + ); + + res.append_header("Upgrade","WebSocket"); + res.append_header("Connection","Upgrade"); + + // Echo back client's origin unless our local application set a + // more restrictive one. + if (res.get_header("Sec-WebSocket-Origin").empty()) { + res.append_header("Sec-WebSocket-Origin",req.get_header("Origin")); + } + + // Echo back the client's request host unless our local application + // set a different one. + if (res.get_header("Sec-WebSocket-Location").empty()) { + uri_ptr uri = get_uri(req); + res.append_header("Sec-WebSocket-Location",uri->str()); + } + + if (!subprotocol.empty()) { + res.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + + return lib::error_code(); + } + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 00 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector<std::string> const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + /// Validate the server's response to an outgoing handshake request + /** + * The Hybi 00 processor only implements incoming connections so this will + * always return an error. + * + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + lib::error_code validate_server_handshake_response(request_type const &, + response_type &) const + { + return error::make_error_code(error::no_protocol_support); + } + + std::string get_raw(response_type const & res) const { + response_type temp = res; + temp.remove_header("Sec-WebSocket-Key3"); + return temp.raw() + res.get_header("Sec-WebSocket-Key3"); + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Origin"); + } + + /// Extracts requested subprotocols from a handshake request + /** + * hybi00 does support subprotocols + * https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-1.9 + * + * @param [in] req The request to extract from + * @param [out] subprotocol_list A reference to a vector of strings to store + * the results in. + */ + lib::error_code extract_subprotocols(request_type const & req, + std::vector<std::string> & subprotocol_list) + { + if (!req.get_header("Sec-WebSocket-Protocol").empty()) { + http::parameter_list p; + + if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { + http::parameter_list::const_iterator it; + + for (it = p.begin(); it != p.end(); ++it) { + subprotocol_list.push_back(it->first); + } + } else { + return error::make_error_code(error::subprotocol_parse_error); + } + } + return lib::error_code(); + } + + uri_ptr get_uri(request_type const & request) const { + std::string h = request.get_header("Host"); + + size_t last_colon = h.rfind(":"); + size_t last_sbrace = h.rfind("]"); + + // no : = hostname with no port + // last : before ] = ipv6 literal with no port + // : with no ] = hostname with port + // : after ] = ipv6 literal with port + + if (last_colon == std::string::npos || + (last_sbrace != std::string::npos && last_sbrace > last_colon)) + { + return lib::make_shared<uri>(base::m_secure, h, request.get_uri()); + } else { + return lib::make_shared<uri>(base::m_secure, + h.substr(0,last_colon), + h.substr(last_colon+1), + request.get_uri()); + } + + // TODO: check if get_uri is a full uri + } + + /// Get hybi00 handshake key3 + /** + * @todo This doesn't appear to be used anymore. It might be able to be + * removed + */ + std::string get_key3() const { + return ""; + } + + /// Process new websocket connection bytes + size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) { + // if in state header we are expecting a 0x00 byte, if we don't get one + // it is a fatal error + size_t p = 0; // bytes processed + size_t l = 0; + + ec = lib::error_code(); + + while (p < len) { + if (m_state == HEADER) { + if (buf[p] == msg_hdr) { + p++; + m_msg_ptr = m_msg_manager->get_message(frame::opcode::text,1); + + if (!m_msg_ptr) { + ec = make_error_code(websocketpp::error::no_incoming_buffers); + m_state = FATAL_ERROR; + } else { + m_state = PAYLOAD; + } + } else { + ec = make_error_code(error::protocol_violation); + m_state = FATAL_ERROR; + } + } else if (m_state == PAYLOAD) { + uint8_t *it = std::find(buf+p,buf+len,msg_ftr); + + // 0 1 2 3 4 5 + // 0x00 0x23 0x23 0x23 0xff 0xXX + + // Copy payload bytes into message + l = static_cast<size_t>(it-(buf+p)); + m_msg_ptr->append_payload(buf+p,l); + p += l; + + if (it != buf+len) { + // message is done, copy it and the trailing + p++; + // TODO: validation + m_state = READY; + } + } else { + // TODO + break; + } + } + // If we get one, we create a new message and move to application state + + // if in state application we are copying bytes into the output message + // and validating them for UTF8 until we hit a 0xff byte. Once we hit + // 0x00, the message is complete and is dispatched. Then we go back to + // header state. + + //ec = make_error_code(error::not_implemented); + return p; + } + + bool ready() const { + return (m_state == READY); + } + + bool get_error() const { + return false; + } + + message_ptr get_message() { + message_ptr ret = m_msg_ptr; + m_msg_ptr = message_ptr(); + m_state = HEADER; + return ret; + } + + /// Prepare a message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) + { + if (!in || !out) { + return make_error_code(error::invalid_arguments); + } + + // TODO: check if the message is prepared already + + // validate opcode + if (in->get_opcode() != frame::opcode::text) { + return make_error_code(error::invalid_opcode); + } + + std::string& i = in->get_raw_payload(); + //std::string& o = out->get_raw_payload(); + + // validate payload utf8 + if (!utf8_validator::validate(i)) { + return make_error_code(error::invalid_payload); + } + + // generate header + out->set_header(std::string(reinterpret_cast<char const *>(&msg_hdr),1)); + + // process payload + out->set_payload(i); + out->append_payload(std::string(reinterpret_cast<char const *>(&msg_ftr),1)); + + // hybi00 doesn't support compression + // hybi00 doesn't have masking + + out->set_prepared(true); + + return lib::error_code(); + } + + /// Prepare a ping frame + /** + * Hybi 00 doesn't support pings so this will always return an error + * + * @param in The string to use for the ping payload + * @param out The message buffer to prepare the ping in. + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_ping(std::string const &, message_ptr) const + { + return lib::error_code(error::no_protocol_support); + } + + /// Prepare a pong frame + /** + * Hybi 00 doesn't support pongs so this will always return an error + * + * @param in The string to use for the pong payload + * @param out The message buffer to prepare the pong in. + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_pong(std::string const &, message_ptr) const + { + return lib::error_code(error::no_protocol_support); + } + + /// Prepare a close frame + /** + * Hybi 00 doesn't support the close code or reason so these parameters are + * ignored. + * + * @param code The close code to send + * @param reason The reason string to send + * @param out The message buffer to prepare the fame in + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_close(close::status::value, std::string const &, + message_ptr out) const + { + if (!out) { + return lib::error_code(error::invalid_arguments); + } + + std::string val; + val.append(1,'\xff'); + val.append(1,'\x00'); + out->set_payload(val); + out->set_prepared(true); + + return lib::error_code(); + } +private: + void decode_client_key(std::string const & key, char * result) const { + unsigned int spaces = 0; + std::string digits; + uint32_t num; + + // key2 + for (size_t i = 0; i < key.size(); i++) { + if (key[i] == ' ') { + spaces++; + } else if (key[i] >= '0' && key[i] <= '9') { + digits += key[i]; + } + } + + num = static_cast<uint32_t>(strtoul(digits.c_str(), NULL, 10)); + if (spaces > 0 && num > 0) { + num = htonl(num/spaces); + std::copy(reinterpret_cast<char*>(&num), + reinterpret_cast<char*>(&num)+4, + result); + } else { + std::fill(result,result+4,0); + } + } + + enum state { + HEADER = 0, + PAYLOAD = 1, + READY = 2, + FATAL_ERROR = 3 + }; + + uint8_t const msg_hdr; + uint8_t const msg_ftr; + + state m_state; + + msg_manager_ptr m_msg_manager; + message_ptr m_msg_ptr; + utf8_validator::validator m_validator; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI00_HPP diff --git a/websocketpp/processors/hybi07.hpp b/websocketpp/processors/hybi07.hpp new file mode 100644 index 00000000..14b67c21 --- /dev/null +++ b/websocketpp/processors/hybi07.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_HYBI07_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI07_HPP + +#include <websocketpp/processors/hybi08.hpp> + +#include <string> +#include <vector> + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 07 +/** + * The primary difference between 07 and 08 is a version number. + */ +template <typename config> +class hybi07 : public hybi08<config> { +public: + typedef typename config::request_type request_type; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + explicit hybi07(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi08<config>(secure, p_is_server, manager, rng) {} + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 07 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector<std::string> const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + int get_version() const { + return 7; + } +private: +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI07_HPP diff --git a/websocketpp/processors/hybi08.hpp b/websocketpp/processors/hybi08.hpp new file mode 100644 index 00000000..15f6e658 --- /dev/null +++ b/websocketpp/processors/hybi08.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_HYBI08_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI08_HPP + +#include <websocketpp/processors/hybi13.hpp> + +#include <string> +#include <vector> + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 08 +/** + * The primary difference between 08 and 13 is a different origin header name + */ +template <typename config> +class hybi08 : public hybi13<config> { +public: + typedef hybi08<config> type; + typedef typename config::request_type request_type; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + explicit hybi08(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi13<config>(secure, p_is_server, manager, rng) {} + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 08 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector<std::string> const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + int get_version() const { + return 8; + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Sec-WebSocket-Origin"); + } +private: +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI08_HPP diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp new file mode 100644 index 00000000..79486654 --- /dev/null +++ b/websocketpp/processors/hybi13.hpp @@ -0,0 +1,1056 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_HYBI13_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI13_HPP + +#include <websocketpp/processors/processor.hpp> + +#include <websocketpp/frame.hpp> +#include <websocketpp/http/constants.hpp> + +#include <websocketpp/utf8_validator.hpp> +#include <websocketpp/sha1/sha1.hpp> +#include <websocketpp/base64/base64.hpp> + +#include <websocketpp/common/network.hpp> +#include <websocketpp/common/platforms.hpp> + +#include <algorithm> +#include <cassert> +#include <string> +#include <vector> +#include <utility> + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi version 13 (RFC6455) +template <typename config> +class hybi13 : public processor<config> { +public: + typedef processor<config> base; + + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + + typedef typename config::message_type message_type; + typedef typename message_type::ptr message_ptr; + + typedef typename config::con_msg_manager_type msg_manager_type; + typedef typename msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + typedef typename config::permessage_deflate_type permessage_deflate_type; + + typedef std::pair<lib::error_code,std::string> err_str_pair; + + explicit hybi13(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : processor<config>(secure, p_is_server) + , m_msg_manager(manager) + , m_rng(rng) + { + reset_headers(); + } + + int get_version() const { + return 13; + } + + bool has_permessage_deflate() const { + return m_permessage_deflate.is_implemented(); + } + + err_str_pair negotiate_extensions(request_type const & request) { + return negotiate_extensions_helper(request); + } + + err_str_pair negotiate_extensions(response_type const & response) { + return negotiate_extensions_helper(response); + } + + /// Extension negotiation helper function + /** + * This exists mostly because the code for requests and responses is + * identical and I can't have virtual template methods. + */ + template <typename header_type> + err_str_pair negotiate_extensions_helper(header_type const & header) { + err_str_pair ret; + + // Respect blanket disabling of all extensions and don't even parse + // the extension header + if (!config::enable_extensions) { + ret.first = make_error_code(error::extensions_disabled); + return ret; + } + + http::parameter_list p; + + bool error = header.get_header_as_plist("Sec-WebSocket-Extensions",p); + + if (error) { + ret.first = make_error_code(error::extension_parse_error); + return ret; + } + + // If there are no extensions parsed then we are done! + if (p.size() == 0) { + return ret; + } + + http::parameter_list::const_iterator it; + + if (m_permessage_deflate.is_implemented()) { + err_str_pair neg_ret; + for (it = p.begin(); it != p.end(); ++it) { + // look through each extension, if the key is permessage-deflate + if (it->first == "permessage-deflate") { + // if we have already successfully negotiated this extension + // then skip any other requests to negotiate the same one + // with different parameters + if (m_permessage_deflate.is_enabled()) { + continue; + } + + + neg_ret = m_permessage_deflate.negotiate(it->second); + + if (neg_ret.first) { + // Figure out if this is an error that should halt all + // extension negotiations or simply cause negotiation of + // this specific extension to fail. + //std::cout << "permessage-compress negotiation failed: " + // << neg_ret.first.message() << std::endl; + } else { + // Note: this list will need commas if WebSocket++ ever + // supports more than one extension + ret.second += neg_ret.second; + m_permessage_deflate.init(base::m_server); + continue; + } + } + } + } + + return ret; + } + + lib::error_code validate_handshake(request_type const & r) const { + if (r.get_method() != "GET") { + return make_error_code(error::invalid_http_method); + } + + if (r.get_version() != "HTTP/1.1") { + return make_error_code(error::invalid_http_version); + } + + // required headers + // Host is required by HTTP/1.1 + // Connection is required by is_websocket_handshake + // Upgrade is required by is_websocket_handshake + if (r.get_header("Sec-WebSocket-Key").empty()) { + return make_error_code(error::missing_required_header); + } + + return lib::error_code(); + } + + /* TODO: the 'subprotocol' parameter may need to be expanded into a more + * generic struct if other user input parameters to the processed handshake + * are found. + */ + lib::error_code process_handshake(request_type const & request, + std::string const & subprotocol, response_type & response) const + { + std::string server_key = request.get_header("Sec-WebSocket-Key"); + + lib::error_code ec = process_handshake_key(server_key); + + if (ec) { + return ec; + } + + response.replace_header("Sec-WebSocket-Accept",server_key); + response.append_header("Upgrade",constants::upgrade_token); + response.append_header("Connection",constants::connection_token); + + if (!subprotocol.empty()) { + response.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + + return lib::error_code(); + } + + /// Fill in a set of request headers for a client connection request + /** + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type & req, uri_ptr + uri, std::vector<std::string> const & subprotocols) const + { + req.set_method("GET"); + req.set_uri(uri->get_resource()); + req.set_version("HTTP/1.1"); + + req.append_header("Upgrade","websocket"); + req.append_header("Connection","Upgrade"); + req.replace_header("Sec-WebSocket-Version","13"); + req.replace_header("Host",uri->get_host_port()); + + if (!subprotocols.empty()) { + std::ostringstream result; + std::vector<std::string>::const_iterator it = subprotocols.begin(); + result << *it++; + while (it != subprotocols.end()) { + result << ", " << *it++; + } + + req.replace_header("Sec-WebSocket-Protocol",result.str()); + } + + // Generate handshake key + frame::uint32_converter conv; + unsigned char raw_key[16]; + + for (int i = 0; i < 4; i++) { + conv.i = m_rng(); + std::copy(conv.c,conv.c+4,&raw_key[i*4]); + } + + req.replace_header("Sec-WebSocket-Key",base64_encode(raw_key, 16)); + + if (m_permessage_deflate.is_implemented()) { + std::string offer = m_permessage_deflate.generate_offer(); + if (!offer.empty()) { + req.replace_header("Sec-WebSocket-Extensions",offer); + } + } + + return lib::error_code(); + } + + /// Validate the server's response to an outgoing handshake request + /** + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + lib::error_code validate_server_handshake_response(request_type const & req, + response_type& res) const + { + // A valid response has an HTTP 101 switching protocols code + if (res.get_status_code() != http::status_code::switching_protocols) { + return error::make_error_code(error::invalid_http_status); + } + + // And the upgrade token in an upgrade header + std::string const & upgrade_header = res.get_header("Upgrade"); + if (utility::ci_find_substr(upgrade_header, constants::upgrade_token, + sizeof(constants::upgrade_token)-1) == upgrade_header.end()) + { + return error::make_error_code(error::missing_required_header); + } + + // And the websocket token in the connection header + std::string const & con_header = res.get_header("Connection"); + if (utility::ci_find_substr(con_header, constants::connection_token, + sizeof(constants::connection_token)-1) == con_header.end()) + { + return error::make_error_code(error::missing_required_header); + } + + // And has a valid Sec-WebSocket-Accept value + std::string key = req.get_header("Sec-WebSocket-Key"); + lib::error_code ec = process_handshake_key(key); + + if (ec || key != res.get_header("Sec-WebSocket-Accept")) { + return error::make_error_code(error::missing_required_header); + } + + // check extensions + + return lib::error_code(); + } + + std::string get_raw(response_type const & res) const { + return res.raw(); + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Origin"); + } + + lib::error_code extract_subprotocols(request_type const & req, + std::vector<std::string> & subprotocol_list) + { + if (!req.get_header("Sec-WebSocket-Protocol").empty()) { + http::parameter_list p; + + if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { + http::parameter_list::const_iterator it; + + for (it = p.begin(); it != p.end(); ++it) { + subprotocol_list.push_back(it->first); + } + } else { + return error::make_error_code(error::subprotocol_parse_error); + } + } + return lib::error_code(); + } + + uri_ptr get_uri(request_type const & request) const { + return get_uri_from_host(request,(base::m_secure ? "wss" : "ws")); + } + + /// Process new websocket connection bytes + /** + * + * Hybi 13 data streams represent a series of variable length frames. Each + * frame is made up of a series of fixed length fields. The lengths of later + * fields are contained in earlier fields. The first field length is fixed + * by the spec. + * + * This processor represents a state machine that keeps track of what field + * is presently being read and how many more bytes are needed to complete it + * + * + * + * + * Read two header bytes + * Extract full frame length. + * Read extra header bytes + * Validate frame header (including extension validate) + * Read extension data into extension message state object + * Read payload data into payload + * + * @param buf Input buffer + * + * @param len Length of input buffer + * + * @return Number of bytes processed or zero on error + */ + size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) { + size_t p = 0; + + ec = lib::error_code(); + + //std::cout << "consume: " << utility::to_hex(buf,len) << std::endl; + + // Loop while we don't have a message ready and we still have bytes + // left to process. + while (m_state != READY && m_state != FATAL_ERROR && + (p < len || m_bytes_needed == 0)) + { + if (m_state == HEADER_BASIC) { + p += this->copy_basic_header_bytes(buf+p,len-p); + + if (m_bytes_needed > 0) { + continue; + } + + ec = this->validate_incoming_basic_header( + m_basic_header, base::m_server, !m_data_msg.msg_ptr + ); + if (ec) {break;} + + // extract full header size and adjust consume state accordingly + m_state = HEADER_EXTENDED; + m_cursor = 0; + m_bytes_needed = frame::get_header_len(m_basic_header) - + frame::BASIC_HEADER_LENGTH; + } else if (m_state == HEADER_EXTENDED) { + p += this->copy_extended_header_bytes(buf+p,len-p); + + if (m_bytes_needed > 0) { + continue; + } + + ec = validate_incoming_extended_header(m_basic_header,m_extended_header); + if (ec){break;} + + m_state = APPLICATION; + m_bytes_needed = static_cast<size_t>(get_payload_size(m_basic_header,m_extended_header)); + + // check if this frame is the start of a new message and set up + // the appropriate message metadata. + frame::opcode::value op = frame::get_opcode(m_basic_header); + + // TODO: get_message failure conditions + + if (frame::opcode::is_control(op)) { + m_control_msg = msg_metadata( + m_msg_manager->get_message(op,m_bytes_needed), + frame::get_masking_key(m_basic_header,m_extended_header) + ); + + m_current_msg = &m_control_msg; + } else { + if (!m_data_msg.msg_ptr) { + if (m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + + m_data_msg = msg_metadata( + m_msg_manager->get_message(op,m_bytes_needed), + frame::get_masking_key(m_basic_header,m_extended_header) + ); + + if (m_permessage_deflate.is_enabled()) { + m_data_msg.msg_ptr->set_compressed(frame::get_rsv1(m_basic_header)); + } + } else { + // Fetch the underlying payload buffer from the data message we + // are writing into. + std::string & out = m_data_msg.msg_ptr->get_raw_payload(); + + if (out.size() + m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + + // Each frame starts a new masking key. All other state + // remains between frames. + m_data_msg.prepared_key = prepare_masking_key( + frame::get_masking_key( + m_basic_header, + m_extended_header + ) + ); + + out.reserve(out.size() + m_bytes_needed); + } + m_current_msg = &m_data_msg; + } + } else if (m_state == EXTENSION) { + m_state = APPLICATION; + } else if (m_state == APPLICATION) { + size_t bytes_to_process = (std::min)(m_bytes_needed,len-p); + + if (bytes_to_process > 0) { + p += this->process_payload_bytes(buf+p,bytes_to_process,ec); + + if (ec) {break;} + } + + if (m_bytes_needed > 0) { + continue; + } + + // If this was the last frame in the message set the ready flag. + // Otherwise, reset processor state to read additional frames. + if (frame::get_fin(m_basic_header)) { + ec = finalize_message(); + if (ec) { + break; + } + } else { + this->reset_headers(); + } + } else { + // shouldn't be here + ec = make_error_code(error::general); + return 0; + } + } + + return p; + } + + /// Perform any finalization actions on an incoming message + /** + * Called after the full message is received. Provides the opportunity for + * extensions to complete any data post processing as well as final UTF8 + * validation checks for text messages. + * + * @return A code indicating errors, if any + */ + lib::error_code finalize_message() { + std::string & out = m_current_msg->msg_ptr->get_raw_payload(); + + // if the frame is compressed, append the compression + // trailer and flush the compression buffer. + if (m_permessage_deflate.is_enabled() + && m_current_msg->msg_ptr->get_compressed()) + { + uint8_t trailer[4] = {0x00, 0x00, 0xff, 0xff}; + + // Decompress current buffer into the message buffer + lib::error_code ec; + ec = m_permessage_deflate.decompress(trailer,4,out); + if (ec) { + return ec; + } + } + + // ensure that text messages end on a valid UTF8 code point + if (frame::get_opcode(m_basic_header) == frame::opcode::TEXT) { + if (!m_current_msg->validator.complete()) { + return make_error_code(error::invalid_utf8); + } + } + + m_state = READY; + + return lib::error_code(); + } + + void reset_headers() { + m_state = HEADER_BASIC; + m_bytes_needed = frame::BASIC_HEADER_LENGTH; + + m_basic_header.b0 = 0x00; + m_basic_header.b1 = 0x00; + + std::fill_n( + m_extended_header.bytes, + frame::MAX_EXTENDED_HEADER_LENGTH, + 0x00 + ); + } + + /// Test whether or not the processor has a message ready + bool ready() const { + return (m_state == READY); + } + + message_ptr get_message() { + if (!ready()) { + return message_ptr(); + } + message_ptr ret = m_current_msg->msg_ptr; + m_current_msg->msg_ptr.reset(); + + if (frame::opcode::is_control(ret->get_opcode())) { + m_control_msg.msg_ptr.reset(); + } else { + m_data_msg.msg_ptr.reset(); + } + + this->reset_headers(); + + return ret; + } + + /// Test whether or not the processor is in a fatal error state. + bool get_error() const { + return m_state == FATAL_ERROR; + } + + size_t get_bytes_needed() const { + return m_bytes_needed; + } + + /// Prepare a user data message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + * + * TODO: tests + * + * @param in An unprepared message to prepare + * @param out A message to be overwritten with the prepared message + * @return error code + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) + { + if (!in || !out) { + return make_error_code(error::invalid_arguments); + } + + frame::opcode::value op = in->get_opcode(); + + // validate opcode: only regular data frames + if (frame::opcode::is_control(op)) { + return make_error_code(error::invalid_opcode); + } + + std::string& i = in->get_raw_payload(); + std::string& o = out->get_raw_payload(); + + // validate payload utf8 + if (op == frame::opcode::TEXT && !utf8_validator::validate(i)) { + return make_error_code(error::invalid_payload); + } + + frame::masking_key_type key; + bool masked = !base::m_server; + bool compressed = m_permessage_deflate.is_enabled() + && in->get_compressed(); + bool fin = in->get_fin(); + + if (masked) { + // Generate masking key. + key.i = m_rng(); + } else { + key.i = 0; + } + + // prepare payload + if (compressed) { + // compress and store in o after header. + m_permessage_deflate.compress(i,o); + + if (o.size() < 4) { + return make_error_code(error::general); + } + + // Strip trailing 4 0x00 0x00 0xff 0xff bytes before writing to the + // wire + o.resize(o.size()-4); + + // mask in place if necessary + if (masked) { + this->masked_copy(o,o,key); + } + } else { + // no compression, just copy data into the output buffer + o.resize(i.size()); + + // if we are masked, have the masking function write to the output + // buffer directly to avoid another copy. If not masked, copy + // directly without masking. + if (masked) { + this->masked_copy(i,o,key); + } else { + std::copy(i.begin(),i.end(),o.begin()); + } + } + + // generate header + frame::basic_header h(op,o.size(),fin,masked,compressed); + + if (masked) { + frame::extended_header e(o.size(),key.i); + out->set_header(frame::prepare_header(h,e)); + } else { + frame::extended_header e(o.size()); + out->set_header(frame::prepare_header(h,e)); + } + + out->set_prepared(true); + out->set_opcode(op); + + return lib::error_code(); + } + + /// Get URI + lib::error_code prepare_ping(std::string const & in, message_ptr out) const { + return this->prepare_control(frame::opcode::PING,in,out); + } + + lib::error_code prepare_pong(std::string const & in, message_ptr out) const { + return this->prepare_control(frame::opcode::PONG,in,out); + } + + virtual lib::error_code prepare_close(close::status::value code, + std::string const & reason, message_ptr out) const + { + if (close::status::reserved(code)) { + return make_error_code(error::reserved_close_code); + } + + if (close::status::invalid(code) && code != close::status::no_status) { + return make_error_code(error::invalid_close_code); + } + + if (code == close::status::no_status && reason.size() > 0) { + return make_error_code(error::reason_requires_code); + } + + if (reason.size() > frame:: limits::payload_size_basic-2) { + return make_error_code(error::control_too_big); + } + + std::string payload; + + if (code != close::status::no_status) { + close::code_converter val; + val.i = htons(code); + + payload.resize(reason.size()+2); + + payload[0] = val.c[0]; + payload[1] = val.c[1]; + + std::copy(reason.begin(),reason.end(),payload.begin()+2); + } + + return this->prepare_control(frame::opcode::CLOSE,payload,out); + } +protected: + /// Convert a client handshake key into a server response key in place + lib::error_code process_handshake_key(std::string & key) const { + key.append(constants::handshake_guid); + + unsigned char message_digest[20]; + sha1::calc(key.c_str(),key.length(),message_digest); + key = base64_encode(message_digest,20); + + return lib::error_code(); + } + + /// Reads bytes from buf into m_basic_header + size_t copy_basic_header_bytes(uint8_t const * buf, size_t len) { + if (len == 0 || m_bytes_needed == 0) { + return 0; + } + + if (len > 1) { + // have at least two bytes + if (m_bytes_needed == 2) { + m_basic_header.b0 = buf[0]; + m_basic_header.b1 = buf[1]; + m_bytes_needed -= 2; + return 2; + } else { + m_basic_header.b1 = buf[0]; + m_bytes_needed--; + return 1; + } + } else { + // have exactly one byte + if (m_bytes_needed == 2) { + m_basic_header.b0 = buf[0]; + m_bytes_needed--; + return 1; + } else { + m_basic_header.b1 = buf[0]; + m_bytes_needed--; + return 1; + } + } + } + + /// Reads bytes from buf into m_extended_header + size_t copy_extended_header_bytes(uint8_t const * buf, size_t len) { + size_t bytes_to_read = (std::min)(m_bytes_needed,len); + + std::copy(buf,buf+bytes_to_read,m_extended_header.bytes+m_cursor); + m_cursor += bytes_to_read; + m_bytes_needed -= bytes_to_read; + + return bytes_to_read; + } + + /// Reads bytes from buf into message payload + /** + * This function performs unmasking and uncompression, validates the + * decoded bytes, and writes them to the appropriate message buffer. + * + * This member function will use the input buffer as stratch space for its + * work. The raw input bytes will not be preserved. This applies only to the + * bytes actually needed. At most min(m_bytes_needed,len) will be processed. + * + * @param buf Input/working buffer + * @param len Length of buf + * @return Number of bytes processed or zero in case of an error + */ + size_t process_payload_bytes(uint8_t * buf, size_t len, lib::error_code& ec) + { + // unmask if masked + if (frame::get_masked(m_basic_header)) { + m_current_msg->prepared_key = frame::byte_mask_circ( + buf, len, m_current_msg->prepared_key); + // TODO: SIMD masking + } + + std::string & out = m_current_msg->msg_ptr->get_raw_payload(); + size_t offset = out.size(); + + // decompress message if needed. + if (m_permessage_deflate.is_enabled() + && m_current_msg->msg_ptr->get_compressed()) + { + // Decompress current buffer into the message buffer + ec = m_permessage_deflate.decompress(buf,len,out); + if (ec) { + return 0; + } + } else { + // No compression, straight copy + out.append(reinterpret_cast<char *>(buf),len); + } + + // validate unmasked, decompressed values + if (m_current_msg->msg_ptr->get_opcode() == frame::opcode::TEXT) { + if (!m_current_msg->validator.decode(out.begin()+offset,out.end())) { + ec = make_error_code(error::invalid_utf8); + return 0; + } + } + + m_bytes_needed -= len; + + return len; + } + + /// Validate an incoming basic header + /** + * Validates an incoming hybi13 basic header. + * + * @param h The basic header to validate + * @param is_server Whether or not the endpoint that received this frame + * is a server. + * @param new_msg Whether or not this is the first frame of the message + * @return 0 on success or a non-zero error code on failure + */ + lib::error_code validate_incoming_basic_header(frame::basic_header const & h, + bool is_server, bool new_msg) const + { + frame::opcode::value op = frame::get_opcode(h); + + // Check control frame size limit + if (frame::opcode::is_control(op) && + frame::get_basic_size(h) > frame::limits::payload_size_basic) + { + return make_error_code(error::control_too_big); + } + + // Check that RSV bits are clear + // The only RSV bits allowed are rsv1 if the permessage_compress + // extension is enabled for this connection and the message is not + // a control message. + // + // TODO: unit tests for this + if (frame::get_rsv1(h) && (!m_permessage_deflate.is_enabled() + || frame::opcode::is_control(op))) + { + return make_error_code(error::invalid_rsv_bit); + } + + if (frame::get_rsv2(h) || frame::get_rsv3(h)) { + return make_error_code(error::invalid_rsv_bit); + } + + // Check for reserved opcodes + if (frame::opcode::reserved(op)) { + return make_error_code(error::invalid_opcode); + } + + // Check for invalid opcodes + // TODO: unit tests for this? + if (frame::opcode::invalid(op)) { + return make_error_code(error::invalid_opcode); + } + + // Check for fragmented control message + if (frame::opcode::is_control(op) && !frame::get_fin(h)) { + return make_error_code(error::fragmented_control); + } + + // Check for continuation without an active message + if (new_msg && op == frame::opcode::CONTINUATION) { + return make_error_code(error::invalid_continuation); + } + + // Check for new data frame when expecting continuation + if (!new_msg && !frame::opcode::is_control(op) && + op != frame::opcode::CONTINUATION) + { + return make_error_code(error::invalid_continuation); + } + + // Servers should reject any unmasked frames from clients. + // Clients should reject any masked frames from servers. + if (is_server && !frame::get_masked(h)) { + return make_error_code(error::masking_required); + } else if (!is_server && frame::get_masked(h)) { + return make_error_code(error::masking_forbidden); + } + + return lib::error_code(); + } + + /// Validate an incoming extended header + /** + * Validates an incoming hybi13 full header. + * + * @todo unit test for the >32 bit frames on 32 bit systems case + * + * @param h The basic header to validate + * @param e The extended header to validate + * @return An error_code, non-zero values indicate why the validation + * failed + */ + lib::error_code validate_incoming_extended_header(frame::basic_header h, + frame::extended_header e) const + { + uint8_t basic_size = frame::get_basic_size(h); + uint64_t payload_size = frame::get_payload_size(h,e); + + // Check for non-minimally encoded payloads + if (basic_size == frame::payload_size_code_16bit && + payload_size <= frame::limits::payload_size_basic) + { + return make_error_code(error::non_minimal_encoding); + } + + if (basic_size == frame::payload_size_code_64bit && + payload_size <= frame::limits::payload_size_extended) + { + return make_error_code(error::non_minimal_encoding); + } + + // Check for >32bit frames on 32 bit systems + if (sizeof(size_t) == 4 && (payload_size >> 32)) { + return make_error_code(error::requires_64bit); + } + + return lib::error_code(); + } + + /// Copy and mask/unmask in one operation + /** + * Reads input from one string and writes unmasked output to another. + * + * @param [in] i The input string. + * @param [out] o The output string. + * @param [in] key The masking key to use for masking/unmasking + */ + void masked_copy (std::string const & i, std::string & o, + frame::masking_key_type key) const + { + frame::byte_mask(i.begin(),i.end(),o.begin(),key); + // TODO: SIMD masking + } + + /// Generic prepare control frame with opcode and payload. + /** + * Internal control frame building method. Handles validation, masking, etc + * + * @param op The control opcode to use + * @param payload The payload to use + * @param out The message buffer to store the prepared frame in + * @return Status code, zero on success, non-zero on error + */ + lib::error_code prepare_control(frame::opcode::value op, + std::string const & payload, message_ptr out) const + { + if (!out) { + return make_error_code(error::invalid_arguments); + } + + if (!frame::opcode::is_control(op)) { + return make_error_code(error::invalid_opcode); + } + + if (payload.size() > frame::limits::payload_size_basic) { + return make_error_code(error::control_too_big); + } + + frame::masking_key_type key; + bool masked = !base::m_server; + + frame::basic_header h(op,payload.size(),true,masked); + + std::string & o = out->get_raw_payload(); + o.resize(payload.size()); + + if (masked) { + // Generate masking key. + key.i = m_rng(); + + frame::extended_header e(payload.size(),key.i); + out->set_header(frame::prepare_header(h,e)); + this->masked_copy(payload,o,key); + } else { + frame::extended_header e(payload.size()); + out->set_header(frame::prepare_header(h,e)); + std::copy(payload.begin(),payload.end(),o.begin()); + } + + out->set_opcode(op); + out->set_prepared(true); + + return lib::error_code(); + } + + enum state { + HEADER_BASIC = 0, + HEADER_EXTENDED = 1, + EXTENSION = 2, + APPLICATION = 3, + READY = 4, + FATAL_ERROR = 5 + }; + + /// This data structure holds data related to processing a message, such as + /// the buffer it is being written to, its masking key, its UTF8 validation + /// state, and sometimes its compression state. + struct msg_metadata { + msg_metadata() {} + msg_metadata(message_ptr m, size_t p) : msg_ptr(m),prepared_key(p) {} + msg_metadata(message_ptr m, frame::masking_key_type p) + : msg_ptr(m) + , prepared_key(prepare_masking_key(p)) {} + + message_ptr msg_ptr; // pointer to the message data buffer + size_t prepared_key; // prepared masking key + utf8_validator::validator validator; // utf8 validation state + }; + + // Basic header of the frame being read + frame::basic_header m_basic_header; + + // Pointer to a manager that can create message buffers for us. + msg_manager_ptr m_msg_manager; + + // Number of bytes needed to complete the current operation + size_t m_bytes_needed; + + // Number of extended header bytes read + size_t m_cursor; + + // Metadata for the current data msg + msg_metadata m_data_msg; + // Metadata for the current control msg + msg_metadata m_control_msg; + + // Pointer to the metadata associated with the frame being read + msg_metadata * m_current_msg; + + // Extended header of current frame + frame::extended_header m_extended_header; + + rng_type & m_rng; + + // Overall state of the processor + state m_state; + + // Extensions + permessage_deflate_type m_permessage_deflate; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI13_HPP diff --git a/websocketpp/processors/processor.hpp b/websocketpp/processors/processor.hpp new file mode 100644 index 00000000..5131cc45 --- /dev/null +++ b/websocketpp/processors/processor.hpp @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_PROCESSOR_HPP +#define WEBSOCKETPP_PROCESSOR_HPP + +#include <websocketpp/processors/base.hpp> +#include <websocketpp/common/system_error.hpp> + +#include <websocketpp/close.hpp> +#include <websocketpp/utilities.hpp> +#include <websocketpp/uri.hpp> + +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +namespace websocketpp { +/// Processors encapsulate the protocol rules specific to each WebSocket version +/** + * The processors namespace includes a number of free functions that operate on + * various WebSocket related data structures and perform processing that is not + * related to specific versions of the protocol. + * + * It also includes the abstract interface for the protocol specific processing + * engines. These engines wrap all of the logic necessary for parsing and + * validating WebSocket handshakes and messages of specific protocol version + * and set of allowed extensions. + * + * An instance of a processor represents the state of a single WebSocket + * connection of the associated version. One processor instance is needed per + * logical WebSocket connection. + */ +namespace processor { + +/// Determine whether or not a generic HTTP request is a WebSocket handshake +/** + * @param r The HTTP request to read. + * + * @return True if the request is a WebSocket handshake, false otherwise + */ +template <typename request_type> +bool is_websocket_handshake(request_type& r) { + using utility::ci_find_substr; + + std::string const & upgrade_header = r.get_header("Upgrade"); + + if (ci_find_substr(upgrade_header, constants::upgrade_token, + sizeof(constants::upgrade_token)-1) == upgrade_header.end()) + { + return false; + } + + std::string const & con_header = r.get_header("Connection"); + + if (ci_find_substr(con_header, constants::connection_token, + sizeof(constants::connection_token)-1) == con_header.end()) + { + return false; + } + + return true; +} + +/// Extract the version from a WebSocket handshake request +/** + * A blank version header indicates a spec before versions were introduced. + * The only such versions in shipping products are Hixie Draft 75 and Hixie + * Draft 76. Draft 75 is present in Chrome 4-5 and Safari 5.0.0, Draft 76 (also + * known as hybi 00 is present in Chrome 6-13 and Safari 5.0.1+. As + * differentiating between these two sets of browsers is very difficult and + * Safari 5.0.1+ accounts for the vast majority of cases in the wild this + * function assumes that all handshakes without a valid version header are + * Hybi 00. + * + * @param r The WebSocket handshake request to read. + * + * @return The WebSocket handshake version or -1 if there was an extraction + * error. + */ +template <typename request_type> +int get_websocket_version(request_type& r) { + if (!r.ready()) { + return -2; + } + + if (r.get_header("Sec-WebSocket-Version").empty()) { + return 0; + } + + int version; + std::istringstream ss(r.get_header("Sec-WebSocket-Version")); + + if ((ss >> version).fail()) { + return -1; + } + + return version; +} + +/// Extract a URI ptr from the host header of the request +/** + * @param request The request to extract the Host header from. + * + * @param scheme The scheme under which this request was received (ws, wss, + * http, https, etc) + * + * @return A uri_pointer that encodes the value of the host header. + */ +template <typename request_type> +uri_ptr get_uri_from_host(request_type & request, std::string scheme) { + std::string h = request.get_header("Host"); + + size_t last_colon = h.rfind(":"); + size_t last_sbrace = h.rfind("]"); + + // no : = hostname with no port + // last : before ] = ipv6 literal with no port + // : with no ] = hostname with port + // : after ] = ipv6 literal with port + if (last_colon == std::string::npos || + (last_sbrace != std::string::npos && last_sbrace > last_colon)) + { + return lib::make_shared<uri>(scheme, h, request.get_uri()); + } else { + return lib::make_shared<uri>(scheme, + h.substr(0,last_colon), + h.substr(last_colon+1), + request.get_uri()); + } +} + +/// WebSocket protocol processor abstract base class +template <typename config> +class processor { +public: + typedef processor<config> type; + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + typedef typename config::message_type::ptr message_ptr; + typedef std::pair<lib::error_code,std::string> err_str_pair; + + explicit processor(bool secure, bool p_is_server) + : m_secure(secure) + , m_server(p_is_server) + , m_max_message_size(config::max_message_size) + {} + + virtual ~processor() {} + + /// Get the protocol version of this processor + virtual int get_version() const = 0; + + /// Get maximum message size + /** + * Get maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.3.0 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set maximum message size + /** + * Set maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.3.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + } + + /// Returns whether or not the permessage_compress extension is implemented + /** + * Compile time flag that indicates whether this processor has implemented + * the permessage_compress extension. By default this is false. + */ + virtual bool has_permessage_compress() const { + return false; + } + + /// Initializes extensions based on the Sec-WebSocket-Extensions header + /** + * Reads the Sec-WebSocket-Extensions header and determines if any of the + * requested extensions are supported by this processor. If they are their + * settings data is initialized and an extension string to send to the + * is returned. + * + * @param request The request or response headers to look at. + */ + virtual err_str_pair negotiate_extensions(request_type const &) { + return err_str_pair(); + } + + /// Initializes extensions based on the Sec-WebSocket-Extensions header + /** + * Reads the Sec-WebSocket-Extensions header and determines if any of the + * requested extensions were accepted by the server. If they are their + * settings data is initialized. If they are not a list of required + * extensions (if any) is returned. This list may be sent back to the server + * as a part of the 1010/Extension required close code. + * + * @param response The request or response headers to look at. + */ + virtual err_str_pair negotiate_extensions(response_type const &) { + return err_str_pair(); + } + + /// validate a WebSocket handshake request for this version + /** + * @param request The WebSocket handshake request to validate. + * is_websocket_handshake(request) must be true and + * get_websocket_version(request) must equal this->get_version(). + * + * @return A status code, 0 on success, non-zero for specific sorts of + * failure + */ + virtual lib::error_code validate_handshake(request_type const & request) const = 0; + + /// Calculate the appropriate response for this websocket request + /** + * @param req The request to process + * + * @param subprotocol The subprotocol in use + * + * @param res The response to store the processed response in + * + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code process_handshake(request_type const & req, + std::string const & subprotocol, response_type& res) const = 0; + + /// Fill in an HTTP request for an outgoing connection handshake + /** + * @param req The request to process. + * + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code client_handshake_request(request_type & req, + uri_ptr uri, std::vector<std::string> const & subprotocols) const = 0; + + /// Validate the server's response to an outgoing handshake request + /** + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code validate_server_handshake_response(request_type + const & req, response_type & res) const = 0; + + /// Given a completed response, get the raw bytes to put on the wire + virtual std::string get_raw(response_type const & request) const = 0; + + /// Return the value of the header containing the CORS origin. + virtual std::string const & get_origin(request_type const & request) const = 0; + + /// Extracts requested subprotocols from a handshake request + /** + * Extracts a list of all subprotocols that the client has requested in the + * given opening handshake request. + * + * @param [in] req The request to extract from + * @param [out] subprotocol_list A reference to a vector of strings to store + * the results in. + */ + virtual lib::error_code extract_subprotocols(const request_type & req, + std::vector<std::string> & subprotocol_list) = 0; + + /// Extracts client uri from a handshake request + virtual uri_ptr get_uri(request_type const & request) const = 0; + + /// process new websocket connection bytes + /** + * WebSocket connections are a continous stream of bytes that must be + * interpreted by a protocol processor into discrete frames. + * + * @param buf Buffer from which bytes should be read. + * @param len Length of buffer + * @param ec Reference to an error code to return any errors in + * @return Number of bytes processed + */ + virtual size_t consume(uint8_t *buf, size_t len, lib::error_code & ec) = 0; + + /// Checks if there is a message ready + /** + * Checks if the most recent consume operation processed enough bytes to + * complete a new WebSocket message. The message can be retrieved by calling + * get_message() which will reset the internal state to not-ready and allow + * consume to read more bytes. + * + * @return Whether or not a message is ready. + */ + virtual bool ready() const = 0; + + /// Retrieves the most recently processed message + /** + * Retrieves a shared pointer to the recently completed message if there is + * one. If ready() returns true then there is a message available. + * Retrieving the message with get_message will reset the state of ready. + * As such, each new message may be retrieved only once. Calling get_message + * when there is no message available will result in a null pointer being + * returned. + * + * @return A pointer to the most recently processed message or a null shared + * pointer. + */ + virtual message_ptr get_message() = 0; + + /// Tests whether the processor is in a fatal error state + virtual bool get_error() const = 0; + + /// Retrieves the number of bytes presently needed by the processor + /// This value may be used as a hint to the transport layer as to how many + /// bytes to wait for before running consume again. + virtual size_t get_bytes_needed() const { + return 1; + } + + /// Prepare a data message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) = 0; + + /// Prepare a ping frame + /** + * Ping preparation is entirely state free. There is no payload validation + * other than length. Payload need not be UTF-8. + * + * @param in The string to use for the ping payload + * @param out The message buffer to prepare the ping in. + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_ping(std::string const & in, message_ptr out) const + = 0; + + /// Prepare a pong frame + /** + * Pong preparation is entirely state free. There is no payload validation + * other than length. Payload need not be UTF-8. + * + * @param in The string to use for the pong payload + * @param out The message buffer to prepare the pong in. + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_pong(std::string const & in, message_ptr out) const + = 0; + + /// Prepare a close frame + /** + * Close preparation is entirely state free. The code and reason are both + * subject to validation. Reason must be valid UTF-8. Code must be a valid + * un-reserved WebSocket close code. Use close::status::no_status to + * indicate no code. If no code is supplied a reason may not be specified. + * + * @param code The close code to send + * @param reason The reason string to send + * @param out The message buffer to prepare the fame in + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_close(close::status::value code, + std::string const & reason, message_ptr out) const = 0; +protected: + bool const m_secure; + bool const m_server; + size_t m_max_message_size; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HPP |