diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp index e84ac50a4b..3c11e5b6c5 100644 --- a/websocketpp/processors/hybi13.hpp +++ b/websocketpp/processors/hybi13.hpp @@ -11,10 +11,10 @@ * * 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 + * + * 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; @@ -22,7 +22,7 @@ * 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 @@ -55,7 +55,7 @@ public: 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; @@ -64,22 +64,22 @@ public: typedef typename config::rng_type rng_type; typedef typename config::permessage_deflate_type permessage_deflate_type; - + typedef std::pair err_str_pair; - explicit hybi13(bool secure, bool server, msg_manager_ptr manager, - rng_type& rng) + explicit hybi13(bool secure, bool server, msg_manager_ptr manager, + rng_type& rng) : processor(secure,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(); } @@ -93,35 +93,35 @@ public: ret.first = make_error_code(error::extensions_disabled); return ret; } - + http::parameter_list p; bool error = req.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") { 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: " + std::cout << "permessage-compress negotiation failed: " << neg_ret.first.message() << std::endl; } else { // Note: this list will need commas if WebSocket++ ever @@ -140,11 +140,11 @@ public: 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 @@ -152,11 +152,11 @@ public: if (r.get_header("Sec-WebSocket-Key") == "") { return make_error_code(error::missing_required_header); } - + return lib::error_code(); } - - /* TODO: the 'subprotocol' parameter may need to be expanded into a more + + /* 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. */ @@ -164,36 +164,36 @@ public: std::string & 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(); } - - lib::error_code client_handshake_request(request_type& req, uri_ptr + + lib::error_code client_handshake_request(request_type& req, uri_ptr uri, std::vector 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::const_iterator it = subprotocols.begin(); @@ -201,24 +201,24 @@ public: 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)); - + return lib::error_code(); } - + lib::error_code validate_server_handshake_response(request_type const & req, response_type& res) const { @@ -226,7 +226,7 @@ public: 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, @@ -234,7 +234,7 @@ public: { 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, @@ -242,32 +242,32 @@ public: { 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); } - + 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 & 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; @@ -280,57 +280,57 @@ public: } 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 + * + * 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 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 && + // 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; + continue; } - + ec = this->validate_incoming_basic_header( m_basic_header, base::m_server, !m_data_msg.msg_ptr ); @@ -339,7 +339,7 @@ public: // 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) - + 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); @@ -347,25 +347,25 @@ public: 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(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) { @@ -390,17 +390,17 @@ public: 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)) { @@ -422,10 +422,10 @@ public: return 0; } } - + return p; } - + void reset_headers() { m_state = HEADER_BASIC; m_bytes_needed = frame::BASIC_HEADER_LENGTH; @@ -439,12 +439,12 @@ public: 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(); @@ -457,12 +457,12 @@ public: } 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; @@ -474,24 +474,24 @@ public: /// Prepare a user data message for writing /** - * Performs validation, masking, compression, etc. will return an error if + * Performs validation, masking, compression, etc. will return an error if * there was an error, otherwise msg will be ready to be written * - * By default WebSocket++ performs block masking/unmasking in a manner that - * makes assumptions about the nature of the machine and STL library used. - * In particular the assumption is either a 32 or 64 bit word size and an + * By default WebSocket++ performs block masking/unmasking in a manner that + * makes assumptions about the nature of the machine and STL library used. + * In particular the assumption is either a 32 or 64 bit word size and an * STL with std::string::data returning a contiguous char array. - * - * This method improves masking performance by 3-8x depending on the ratio + * + * This method improves masking performance by 3-8x depending on the ratio * of small to large messages and the availability of a 64 bit processor. - * - * To disable this optimization (for use with alternative STL + * + * To disable this optimization (for use with alternative STL * implementations or processors) define WEBSOCKETPP_STRICT_MASKING when * compiling the library. This will force the library to perform masking in * single byte chunks. - * + * * TODO: tests - * + * * @param in An unprepared message to prepare * @param out A message to be overwritten with the prepared message * @return error code @@ -501,42 +501,42 @@ public: if (!in || !out) { return make_error_code(error::invalid_arguments); } - + frame::opcode::value op = in->get_opcode(); - // validate opcode: only regular data frames + // 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() + bool compressed = m_permessage_deflate.is_enabled() && in->get_compressed(); bool fin = in->get_fin(); - + // generate header frame::basic_header h(op,i.size(),fin,masked,compressed); - + if (masked) { // Generate masking key. key.i = m_rng(); - + frame::extended_header e(i.size(),key.i); out->set_header(frame::prepare_header(h,e)); } else { frame::extended_header e(i.size()); out->set_header(frame::prepare_header(h,e)); } - + // prepare payload if (compressed) { // compress and store in o after header. @@ -549,7 +549,7 @@ public: } 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. @@ -561,7 +561,7 @@ public: } out->set_prepared(true); - + return lib::error_code(); } @@ -574,7 +574,7 @@ public: return this->prepare_control(frame::opcode::PONG,in,out); } - virtual lib::error_code prepare_close(close::status::value code, + virtual lib::error_code prepare_close(close::status::value code, std::string const & reason, message_ptr out) const { if (close::status::reserved(code)) { @@ -584,7 +584,7 @@ public: 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); } @@ -592,57 +592,57 @@ public: 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); - + sha1 sha; uint32_t message_digest[5]; - + sha << key.c_str(); - + if (sha.get_raw_digest(message_digest)){ // convert sha1 hash bytes to network byte order because this sha1 // library works on ints rather than bytes for (int i = 0; i < 5; i++) { message_digest[i] = htonl(message_digest[i]); } - + key = base64_encode( reinterpret_cast(message_digest), 20 ); - + return lib::error_code(); } else { return error::make_error_code(error::sha1_library); } } - + /// 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) { @@ -672,7 +672,7 @@ protected: /// 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; @@ -682,13 +682,13 @@ protected: /// Reads bytes from buf into message payload /** - * This function performs unmasking and uncompression, validates the + * 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 + * + * 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 @@ -711,17 +711,17 @@ protected: ); #endif } - + 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() + if (m_permessage_deflate.is_enabled() && frame::get_rsv1(m_basic_header)) { // Decompress current buffer into the message buffer m_permessage_deflate.decompress(buf,len,out); - + // get the length of the newly uncompressed output offset = out.size() - offset; } else { @@ -736,34 +736,29 @@ protected: return 0; } } - - // copy into message buffer - /*m_current_msg->msg_ptr->append_payload( - reinterpret_cast(buf), - len - );*/ + 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, + 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) && + if (frame::opcode::is_control(op) && frame::get_basic_size(h) > frame::limits::payload_size_basic) { return make_error_code(error::control_too_big); @@ -775,9 +770,9 @@ protected: // a control message. // // TODO: unit tests for this - if (frame::get_rsv1(h) && (!m_permessage_deflate.is_enabled() + if (frame::get_rsv1(h) && (!m_permessage_deflate.is_enabled() || frame::opcode::is_control(op))) - { + { return make_error_code(error::invalid_rsv_bit); } @@ -800,12 +795,12 @@ protected: 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) @@ -823,7 +818,7 @@ protected: return lib::error_code(); } - + /// Validate an incoming extended header /** * Validates an incoming hybi13 full header. @@ -832,23 +827,23 @@ protected: * * @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 + * @return An error_code, non-zero values indicate why the validation * failed */ - lib::error_code validate_incoming_extended_header(frame::basic_header h, + 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 && + 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 && + if (basic_size == frame::payload_size_code_64bit && payload_size <= frame::limits::payload_size_extended) { return make_error_code(error::non_minimal_encoding); @@ -861,7 +856,7 @@ protected: return lib::error_code(); } - + /// Copy and mask/unmask in one operation /** * Reads input from one string and writes unmasked output to another. @@ -911,29 +906,27 @@ protected: 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(); + 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::cout << "o: " << o.size() << std::endl; std::copy(payload.begin(),payload.end(),o.begin()); - //std::cout << "o: " << o.size() << std::endl; } - + out->set_prepared(true); - + return lib::error_code(); } @@ -945,14 +938,14 @@ protected: 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 + + /// 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_metadata(message_ptr m, frame::masking_key_type p) : msg_ptr(m) , prepared_key(prepare_masking_key(p)) {} @@ -960,19 +953,19 @@ protected: 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 @@ -980,15 +973,9 @@ protected: // 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; - - - // write/prep state - // original opcode - // utf8 validator - // compression state rng_type & m_rng;