/* This file is part of libmicrohttpd Copyright (C) 2021 David Gausmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file microhttpd_ws/mhd_websocket.c * @brief Support for the websocket protocol * @author David Gausmann */ #include "platform.h" #include "microhttpd.h" #include "microhttpd_ws.h" #include "sha1.h" struct MHD_WebSocketStream { /* The function pointer to malloc for payload (can be used to use different memory management) */ MHD_WebSocketMallocCallback malloc; /* The function pointer to realloc for payload (can be used to use different memory management) */ MHD_WebSocketReallocCallback realloc; /* The function pointer to free for payload (can be used to use different memory management) */ MHD_WebSocketFreeCallback free; /* A closure for the random number generator (only used for client mode; usually not required) */ void *cls_rng; /* The random number generator (only used for client mode; usually not required) */ MHD_WebSocketRandomNumberGenerator rng; /* The flags specified upon initialization. It may alter the behavior of decoding/encoding */ int flags; /* The current step for the decoder. 0 means start of a frame. */ char decode_step; /* Specifies whether the stream is valid (1) or not (0), if a close frame has been received this is (-1) to indicate that no data frames are allowed anymore */ char validity; /* The current step of the UTF-8 encoding check in the data payload */ char data_utf8_step; /* The current step of the UTF-8 encoding check in the control payload */ char control_utf8_step; /* if != 0 means that we expect a CONTINUATION frame */ char data_type; /* The start of the current frame (may differ from data_payload for CONTINUATION frames) */ char *data_payload_start; /* The buffer for the data frame */ char *data_payload; /* The buffer for the control frame */ char *control_payload; /* Configuration for the maximum allowed buffer size for payload data */ size_t max_payload_size; /* The current frame header size */ size_t frame_header_size; /* The current data payload size (can be greater than payload_size for fragmented frames) */ size_t data_payload_size; /* The size of the payload of the current frame (control or data) */ size_t payload_size; /* The processing offset to the start of the payload of the current frame (control or data) */ size_t payload_index; /* The frame header of the current frame (control or data) */ char frame_header[32]; /* The mask key of the current frame (control or data); this is 0 if no masking used */ char mask_key[4]; }; #define MHD_WEBSOCKET_FLAG_MASK_SERVERCLIENT MHD_WEBSOCKET_FLAG_CLIENT #define MHD_WEBSOCKET_FLAG_MASK_FRAGMENTATION \ MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS #define MHD_WEBSOCKET_FLAG_MASK_GENERATE_CLOSE_FRAMES \ MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR #define MHD_WEBSOCKET_FLAG_MASK_ALL \ (MHD_WEBSOCKET_FLAG_MASK_SERVERCLIENT \ | MHD_WEBSOCKET_FLAG_MASK_FRAGMENTATION \ | MHD_WEBSOCKET_FLAG_MASK_GENERATE_CLOSE_FRAMES) enum MHD_WebSocket_Opcode { MHD_WebSocket_Opcode_Continuation = 0x0, MHD_WebSocket_Opcode_Text = 0x1, MHD_WebSocket_Opcode_Binary = 0x2, MHD_WebSocket_Opcode_Close = 0x8, MHD_WebSocket_Opcode_Ping = 0x9, MHD_WebSocket_Opcode_Pong = 0xA }; enum MHD_WebSocket_DecodeStep { MHD_WebSocket_DecodeStep_Start = 0, MHD_WebSocket_DecodeStep_Length1ofX = 1, MHD_WebSocket_DecodeStep_Length1of2 = 2, MHD_WebSocket_DecodeStep_Length2of2 = 3, MHD_WebSocket_DecodeStep_Length1of8 = 4, MHD_WebSocket_DecodeStep_Length2of8 = 5, MHD_WebSocket_DecodeStep_Length3of8 = 6, MHD_WebSocket_DecodeStep_Length4of8 = 7, MHD_WebSocket_DecodeStep_Length5of8 = 8, MHD_WebSocket_DecodeStep_Length6of8 = 9, MHD_WebSocket_DecodeStep_Length7of8 = 10, MHD_WebSocket_DecodeStep_Length8of8 = 11, MHD_WebSocket_DecodeStep_Mask1Of4 = 12, MHD_WebSocket_DecodeStep_Mask2Of4 = 13, MHD_WebSocket_DecodeStep_Mask3Of4 = 14, MHD_WebSocket_DecodeStep_Mask4Of4 = 15, MHD_WebSocket_DecodeStep_HeaderCompleted = 16, MHD_WebSocket_DecodeStep_PayloadOfDataFrame = 17, MHD_WebSocket_DecodeStep_PayloadOfControlFrame = 18, MHD_WebSocket_DecodeStep_BrokenStream = 99 }; enum MHD_WebSocket_UTF8Result { MHD_WebSocket_UTF8Result_Invalid = 0, MHD_WebSocket_UTF8Result_Valid = 1, MHD_WebSocket_UTF8Result_Incomplete = 2 }; static void MHD_websocket_copy_payload (char *dst, const char *src, size_t len, uint32_t mask, unsigned long mask_offset); static int MHD_websocket_check_utf8 (const char *buf, size_t buf_len, int *utf8_step, size_t *buf_offset); static enum MHD_WEBSOCKET_STATUS MHD_websocket_decode_header_complete (struct MHD_WebSocketStream *ws, char **payload, size_t *payload_len); static enum MHD_WEBSOCKET_STATUS MHD_websocket_decode_payload_complete (struct MHD_WebSocketStream *ws, char **payload, size_t *payload_len); static char MHD_websocket_encode_is_masked (struct MHD_WebSocketStream *ws); static char MHD_websocket_encode_overhead_size (struct MHD_WebSocketStream *ws, size_t payload_len); static enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_data (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, int fragmentation, char **frame, size_t *frame_len, char opcode); static enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_ping_pong (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, char **frame, size_t *frame_len, char opcode); static uint32_t MHD_websocket_generate_mask (struct MHD_WebSocketStream *ws); static uint16_t MHD_htons (uint16_t value); static uint64_t MHD_htonll (uint64_t value); /** * Checks whether the HTTP version is 1.1 or above. */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_check_http_version (const char *http_version) { /* validate parameters */ if (NULL == http_version) { /* Like with the other check routines, */ /* NULL is threated as "value not given" and not as parameter error */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /* Check whether the version has a valid format */ /* RFC 1945 3.1: The format must be "HTTP/x.x" where x is */ /* any digit and must appear at least once */ if (('H' != http_version[0]) || ('T' != http_version[1]) || ('T' != http_version[2]) || ('P' != http_version[3]) || ('/' != http_version[4])) { return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /* Find the major and minor part of the version */ /* RFC 1945 3.1: Both numbers must be threated as separate integers. */ /* Leading zeros must be ignored and both integers may have multiple digits */ const char *major = NULL; const char *dot = NULL; size_t i = 5; for (;;) { char c = http_version[i]; if (('0' <= c) && ('9' >= c)) { if ((NULL == major) || ((http_version + i == major + 1) && ('0' == *major)) ) { major = http_version + i; } ++i; } else if ('.' == http_version[i]) { dot = http_version + i; ++i; break; } else { return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } } const char *minor = NULL; const char *end = NULL; for (;;) { char c = http_version[i]; if (('0' <= c) && ('9' >= c)) { if ((NULL == minor) || ((http_version + i == minor + 1) && ('0' == *minor)) ) { minor = http_version + i; } ++i; } else if (0 == c) { end = http_version + i; break; } else { return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } } if ((NULL == major) || (NULL == dot) || (NULL == minor) || (NULL == end)) { return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } if ((2 <= dot - major) || ('2' <= *major) || (('1' == *major) && ((2 <= end - minor) || ('1' <= *minor))) ) { return MHD_WEBSOCKET_STATUS_OK; } return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /** * Checks whether the "Connection" request header has the 'Upgrade' token. */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_check_connection_header (const char *connection_header) { /* validate parameters */ if (NULL == connection_header) { /* To be compatible with the return value */ /* of MHD_lookup_connection_value, */ /* NULL is threated as "value not given" and not as parameter error */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /* Check whether the Connection includes an Upgrade token */ /* RFC 7230 6.1: Multiple tokens may appear. */ /* RFC 7230 3.2.6: Tokens are comma separated */ const char *token_start = NULL; const char *token_end = NULL; for (size_t i = 0; ; ++i) { char c = connection_header[i]; /* RFC 7230 3.2.6: The list of allowed characters is a token is: */ /* "!" / "#" / "$" / "%" / "&" / "'" / "*" / */ /* "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" */ /* DIGIT / ALPHA */ if (('!' == c) || ('#' == c) || ('$' == c) || ('%' == c) || ('&' == c) || ('\'' == c) || ('*' == c) || ('+' == c) || ('-' == c) || ('.' == c) || ('^' == c) || ('_' == c) || ('`' == c) || ('|' == c) || ('~' == c) || (('0' <= c) && ('9' >= c)) || (('A' <= c) && ('Z' >= c)) || (('a' <= c) && ('z' >= c)) ) { /* This is a valid token character */ if (NULL == token_start) { token_start = connection_header + i; } token_end = connection_header + i + 1; } else if ((' ' == c) || ('\t' == c)) { /* White-spaces around tokens will be ignored */ } else if ((',' == c) || (0 == c)) { /* Check the token (case-insensitive) */ if (NULL != token_start) { if (7 == (token_end - token_start) ) { if ( (('U' == token_start[0]) || ('u' == token_start[0])) && (('P' == token_start[1]) || ('p' == token_start[1])) && (('G' == token_start[2]) || ('g' == token_start[2])) && (('R' == token_start[3]) || ('r' == token_start[3])) && (('A' == token_start[4]) || ('a' == token_start[4])) && (('D' == token_start[5]) || ('d' == token_start[5])) && (('E' == token_start[6]) || ('e' == token_start[6])) ) { /* The token equals to "Upgrade" */ return MHD_WEBSOCKET_STATUS_OK; } } } if (0 == c) { break; } token_start = NULL; token_end = NULL; } else { /* RFC 7230 3.2.6: Other characters are not allowed */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } } return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /** * Checks whether the "Upgrade" request header has the "websocket" keyword. */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_check_upgrade_header (const char *upgrade_header) { /* validate parameters */ if (NULL == upgrade_header) { /* To be compatible with the return value */ /* of MHD_lookup_connection_value, */ /* NULL is threated as "value not given" and not as parameter error */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /* Check whether the Connection includes an Upgrade token */ /* RFC 7230 6.1: Multiple tokens may appear. */ /* RFC 7230 3.2.6: Tokens are comma separated */ const char *keyword_start = NULL; const char *keyword_end = NULL; for (size_t i = 0; ; ++i) { char c = upgrade_header[i]; /* RFC 7230 3.2.6: The list of allowed characters is a token is: */ /* "!" / "#" / "$" / "%" / "&" / "'" / "*" / */ /* "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" */ /* DIGIT / ALPHA */ /* We also allow "/" here as the sub-delimiter for the protocol version */ if (('!' == c) || ('#' == c) || ('$' == c) || ('%' == c) || ('&' == c) || ('\'' == c) || ('*' == c) || ('+' == c) || ('-' == c) || ('.' == c) || ('^' == c) || ('_' == c) || ('`' == c) || ('|' == c) || ('~' == c) || ('/' == c) || (('0' <= c) && ('9' >= c)) || (('A' <= c) && ('Z' >= c)) || (('a' <= c) && ('z' >= c)) ) { /* This is a valid token character */ if (NULL == keyword_start) { keyword_start = upgrade_header + i; } keyword_end = upgrade_header + i + 1; } else if ((' ' == c) || ('\t' == c)) { /* White-spaces around tokens will be ignored */ } else if ((',' == c) || (0 == c)) { /* Check the token (case-insensitive) */ if (NULL != keyword_start) { if (9 == (keyword_end - keyword_start) ) { if ( (('W' == keyword_start[0]) || ('w' == keyword_start[0])) && (('E' == keyword_start[1]) || ('e' == keyword_start[1])) && (('B' == keyword_start[2]) || ('b' == keyword_start[2])) && (('S' == keyword_start[3]) || ('s' == keyword_start[3])) && (('O' == keyword_start[4]) || ('o' == keyword_start[4])) && (('C' == keyword_start[5]) || ('c' == keyword_start[5])) && (('K' == keyword_start[6]) || ('k' == keyword_start[6])) && (('E' == keyword_start[7]) || ('e' == keyword_start[7])) && (('T' == keyword_start[8]) || ('t' == keyword_start[8])) ) { /* The keyword equals to "websocket" */ return MHD_WEBSOCKET_STATUS_OK; } } } if (0 == c) { break; } keyword_start = NULL; keyword_end = NULL; } else { /* RFC 7230 3.2.6: Other characters are not allowed */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } } return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /** * Checks whether the "Sec-WebSocket-Version" request header * equals to "13" */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_check_version_header (const char *version_header) { /* validate parameters */ if (NULL == version_header) { /* To be compatible with the return value */ /* of MHD_lookup_connection_value, */ /* NULL is threated as "value not given" and not as parameter error */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } if (('1' == version_header[0]) && ('3' == version_header[1]) && (0 == version_header[2])) { /* The version equals to "13" */ return MHD_WEBSOCKET_STATUS_OK; } return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /** * Creates the response for the Sec-WebSocket-Accept header */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_create_accept_header (const char *sec_websocket_key, char *sec_websocket_accept) { /* initialize output variables for errors cases */ if (NULL != sec_websocket_accept) *sec_websocket_accept = 0; /* validate parameters */ if (NULL == sec_websocket_accept) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } if (NULL == sec_websocket_key) { /* NULL is not a parameter error, */ /* because MHD_lookup_connection_value returns NULL */ /* if the header wasn't found */ return MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER; } /* build SHA1 hash of the given key and the UUID appended */ char sha1[20]; const char *suffix = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; int length = (int) strlen (sec_websocket_key); struct sha1_ctx ctx; MHD_SHA1_init (&ctx); MHD_SHA1_update (&ctx, (const uint8_t *) sec_websocket_key, length); MHD_SHA1_update (&ctx, (const uint8_t *) suffix, 36); MHD_SHA1_finish (&ctx, (uint8_t *) sha1); /* base64 encode that SHA1 hash */ /* (simple algorithm here; SHA1 has always 20 bytes, */ /* which will always result in a 28 bytes base64 hash) */ const char *base64_encoding_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (int i = 0, j = 0; i < 20;) { uint32_t octet_a = i < 20 ? (unsigned char) sha1[i++] : 0; uint32_t octet_b = i < 20 ? (unsigned char) sha1[i++] : 0; uint32_t octet_c = i < 20 ? (unsigned char) sha1[i++] : 0; uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; sec_websocket_accept[j++] = base64_encoding_table[(triple >> 3 * 6) & 0x3F]; sec_websocket_accept[j++] = base64_encoding_table[(triple >> 2 * 6) & 0x3F]; sec_websocket_accept[j++] = base64_encoding_table[(triple >> 1 * 6) & 0x3F]; sec_websocket_accept[j++] = base64_encoding_table[(triple >> 0 * 6) & 0x3F]; } sec_websocket_accept[27] = '='; sec_websocket_accept[28] = 0; return MHD_WEBSOCKET_STATUS_OK; } /** * Initializes a new websocket stream */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_stream_init (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size) { return MHD_websocket_stream_init2 (ws, flags, max_payload_size, malloc, realloc, free, NULL, NULL); } /** * Initializes a new websocket stream with * additional parameters for allocation functions */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_stream_init2 (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size, MHD_WebSocketMallocCallback callback_malloc, MHD_WebSocketReallocCallback callback_realloc, MHD_WebSocketFreeCallback callback_free, void *cls_rng, MHD_WebSocketRandomNumberGenerator callback_rng) { /* initialize output variables for errors cases */ if (NULL != ws) *ws = NULL; /* validate parameters */ if ((NULL == ws) || (0 != (flags & ~MHD_WEBSOCKET_FLAG_MASK_ALL)) || ((uint64_t) 0x7FFFFFFFFFFFFFFF < max_payload_size) || (NULL == callback_malloc) || (NULL == callback_realloc) || (NULL == callback_free) || ((0 != (flags & MHD_WEBSOCKET_FLAG_CLIENT)) && (NULL == callback_rng))) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* allocate stream */ struct MHD_WebSocketStream *ws_ = (struct MHD_WebSocketStream *) malloc ( sizeof (struct MHD_WebSocketStream)); if (NULL == ws_) return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; /* initialize stream */ memset (ws_, 0, sizeof (struct MHD_WebSocketStream)); ws_->flags = flags; ws_->max_payload_size = max_payload_size; ws_->malloc = callback_malloc; ws_->realloc = callback_realloc; ws_->free = callback_free; ws_->cls_rng = cls_rng; ws_->rng = callback_rng; ws_->validity = MHD_WEBSOCKET_VALIDITY_VALID; /* return stream */ *ws = ws_; return MHD_WEBSOCKET_STATUS_OK; } /** * Frees a previously allocated websocket stream */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_stream_free (struct MHD_WebSocketStream *ws) { /* validate parameters */ if (NULL == ws) return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; /* free allocated payload data */ if (ws->data_payload) ws->free (ws->data_payload); if (ws->control_payload) ws->free (ws->control_payload); /* free the stream */ free (ws); return MHD_WEBSOCKET_STATUS_OK; } /** * Invalidates a websocket stream (no more decoding possible) */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_stream_invalidate (struct MHD_WebSocketStream *ws) { /* validate parameters */ if (NULL == ws) return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; /* invalidate stream */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; return MHD_WEBSOCKET_STATUS_OK; } /** * Returns whether a websocket stream is valid */ _MHD_EXTERN enum MHD_WEBSOCKET_VALIDITY MHD_websocket_stream_is_valid (struct MHD_WebSocketStream *ws) { /* validate parameters */ if (NULL == ws) return MHD_WEBSOCKET_VALIDITY_INVALID; return ws->validity; } /** * Decodes incoming data to a websocket frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_decode (struct MHD_WebSocketStream *ws, const char *streambuf, size_t streambuf_len, size_t *streambuf_read_len, char **payload, size_t *payload_len) { /* initialize output variables for errors cases */ if (NULL != streambuf_read_len) *streambuf_read_len = 0; if (NULL != payload) *payload = NULL; if (NULL != payload_len) *payload_len = 0; /* validate parameters */ if ((NULL == ws) || ((NULL == streambuf) && (0 != streambuf_len)) || (NULL == streambuf_read_len) || (NULL == payload) || (NULL == payload_len) ) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* validate stream validity */ if (MHD_WEBSOCKET_VALIDITY_INVALID == ws->validity) return MHD_WEBSOCKET_STATUS_STREAM_BROKEN; /* decode loop */ size_t current = 0; while (current < streambuf_len) { switch (ws->decode_step) { /* start of frame */ case MHD_WebSocket_DecodeStep_Start: { /* The first byte contains the opcode, the fin flag and three reserved bits */ if (MHD_WEBSOCKET_VALIDITY_INVALID != ws->validity) { char opcode = streambuf [current]; if (0 != (opcode & 0x70)) { /* RFC 6455 5.2 RSV1-3: If a reserved flag is set */ /* (while it isn't specified by an extension) the communication must fail. */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } switch (opcode & 0x0F) { case MHD_WebSocket_Opcode_Continuation: if (0 == ws->data_type) { /* RFC 6455 5.4: Continuation frame without previous data frame */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if (MHD_WEBSOCKET_VALIDITY_ONLY_VALID_FOR_CONTROL_FRAMES == ws->validity) { /* RFC 6455 5.5.1: After a close frame has been sent, */ /* no data frames may be sent (so we don't accept data frames */ /* for decoding anymore) */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } break; case MHD_WebSocket_Opcode_Text: case MHD_WebSocket_Opcode_Binary: if (0 != ws->data_type) { /* RFC 6455 5.4: Continuation expected, but new data frame */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if (MHD_WEBSOCKET_VALIDITY_ONLY_VALID_FOR_CONTROL_FRAMES == ws->validity) { /* RFC 6455 5.5.1: After a close frame has been sent, */ /* no data frames may be sent (so we don't accept data frames */ /* for decoding anymore) */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } break; case MHD_WebSocket_Opcode_Close: case MHD_WebSocket_Opcode_Ping: case MHD_WebSocket_Opcode_Pong: if ((opcode & 0x80) == 0) { /* RFC 6455 5.4: Control frames may not be fragmented */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if (MHD_WebSocket_Opcode_Close == (opcode & 0x0F)) { /* RFC 6455 5.5.1: After a close frame has been sent, */ /* no data frames may be sent (so we don't accept data frames */ /* for decoding anymore) */ ws->validity = MHD_WEBSOCKET_VALIDITY_ONLY_VALID_FOR_CONTROL_FRAMES; } break; default: /* RFC 6455 5.2 OPCODE: Only six opcodes are specified. */ /* All other are invalid in version 13 of the protocol. */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } } ws->frame_header [ws->frame_header_size++] = streambuf [current++]; ws->decode_step = MHD_WebSocket_DecodeStep_Length1ofX; } break; case MHD_WebSocket_DecodeStep_Length1ofX: { /* The second byte specifies whether the data is masked and the size */ /* (the client MUST mask the payload, the server MUST NOT mask the payload) */ char frame_len = streambuf [current]; char is_masked = (frame_len & 0x80); frame_len &= 0x7f; if (MHD_WEBSOCKET_VALIDITY_INVALID != ws->validity) { if (0 != is_masked) { if (MHD_WEBSOCKET_FLAG_CLIENT == (ws->flags & MHD_WEBSOCKET_FLAG_CLIENT)) { /* RFC 6455 5.1: All frames from the server must be unmasked */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } } else { if (MHD_WEBSOCKET_FLAG_SERVER == (ws->flags & MHD_WEBSOCKET_FLAG_CLIENT)) { /* RFC 6455 5.1: All frames from the client must be masked */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } } if (126 <= frame_len) { if (0 != (ws->frame_header [0] & 0x08)) { /* RFC 6455 5.5: Control frames may not have more payload than 125 bytes */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } } if (1 == frame_len) { if (MHD_WebSocket_Opcode_Close == (ws->frame_header [0] & 0x0F)) { /* RFC 6455 5.5.1: The close frame must have at least */ /* two bytes of payload if payload is used */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } } } ws->frame_header [ws->frame_header_size++] = streambuf [current++]; if (126 == frame_len) { ws->decode_step = MHD_WebSocket_DecodeStep_Length1of2; } else if (127 == frame_len) { ws->decode_step = MHD_WebSocket_DecodeStep_Length1of8; } else { size_t size = (size_t) frame_len; if ((SIZE_MAX < size) || (ws->max_payload_size && (ws->max_payload_size < size)) ) { /* RFC 6455 7.4.1 1009: If the message is too big to process, we may close the connection */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_MAXIMUM_ALLOWED_PAYLOAD_SIZE_EXCEEDED, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } ws->payload_size = size; if (0 != is_masked) { /* with mask */ ws->decode_step = MHD_WebSocket_DecodeStep_Mask1Of4; } else { /* without mask */ *((uint32_t *) ws->mask_key) = 0; ws->decode_step = MHD_WebSocket_DecodeStep_HeaderCompleted; } } } break; /* Payload size first byte of 2 bytes */ case MHD_WebSocket_DecodeStep_Length1of2: /* Payload size first 7 bytes of 8 bytes */ case MHD_WebSocket_DecodeStep_Length1of8: case MHD_WebSocket_DecodeStep_Length2of8: case MHD_WebSocket_DecodeStep_Length3of8: case MHD_WebSocket_DecodeStep_Length4of8: case MHD_WebSocket_DecodeStep_Length5of8: case MHD_WebSocket_DecodeStep_Length6of8: case MHD_WebSocket_DecodeStep_Length7of8: /* Mask first 3 bytes of 4 bytes */ case MHD_WebSocket_DecodeStep_Mask1Of4: case MHD_WebSocket_DecodeStep_Mask2Of4: case MHD_WebSocket_DecodeStep_Mask3Of4: ws->frame_header [ws->frame_header_size++] = streambuf [current++]; ++ws->decode_step; break; /* 2 byte length finished */ case MHD_WebSocket_DecodeStep_Length2of2: { ws->frame_header [ws->frame_header_size++] = streambuf [current++]; size_t size = (size_t) MHD_htons ( *((uint16_t *) &ws->frame_header [2])); if (125 >= size) { /* RFC 6455 5.2 Payload length: The minimal number of bytes */ /* must be used for the length */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if ((SIZE_MAX < size) || (ws->max_payload_size && (ws->max_payload_size < size)) ) { /* RFC 6455 7.4.1 1009: If the message is too big to process, */ /* we may close the connection */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_MAXIMUM_ALLOWED_PAYLOAD_SIZE_EXCEEDED, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } ws->payload_size = size; if (0 != (ws->frame_header [1] & 0x80)) { /* with mask */ ws->decode_step = MHD_WebSocket_DecodeStep_Mask1Of4; } else { /* without mask */ *((uint32_t *) ws->mask_key) = 0; ws->decode_step = MHD_WebSocket_DecodeStep_HeaderCompleted; } } break; /* 8 byte length finished */ case MHD_WebSocket_DecodeStep_Length8of8: { ws->frame_header [ws->frame_header_size++] = streambuf [current++]; uint64_t size = MHD_htonll (*((uint64_t *) &ws->frame_header [2])); if (0x7fffffffffffffff < size) { /* RFC 6455 5.2 frame-payload-length-63: The length may */ /* not exceed 0x7fffffffffffffff */ ws->decode_step = MHD_WebSocket_DecodeStep_BrokenStream; ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if (65535 >= size) { /* RFC 6455 5.2 Payload length: The minimal number of bytes */ /* must be used for the length */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; } if ((SIZE_MAX < size) || (ws->max_payload_size && (ws->max_payload_size < size)) ) { /* RFC 6455 7.4.1 1009: If the message is too big to process, */ /* we may close the connection */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_MAXIMUM_ALLOWED_PAYLOAD_SIZE_EXCEEDED, 0, 0, payload, payload_len); } *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } ws->payload_size = (size_t) size; if (0 != (ws->frame_header [1] & 0x80)) { /* with mask */ ws->decode_step = MHD_WebSocket_DecodeStep_Mask1Of4; } else { /* without mask */ *((uint32_t *) ws->mask_key) = 0; ws->decode_step = MHD_WebSocket_DecodeStep_HeaderCompleted; } } break; /* mask finished */ case MHD_WebSocket_DecodeStep_Mask4Of4: ws->frame_header [ws->frame_header_size++] = streambuf [current++]; *((uint32_t *) ws->mask_key) = *((uint32_t *) &ws->frame_header [ws-> frame_header_size - 4]); ws->decode_step = MHD_WebSocket_DecodeStep_HeaderCompleted; break; /* header finished */ case MHD_WebSocket_DecodeStep_HeaderCompleted: /* return or assign either to data or control */ { int ret = MHD_websocket_decode_header_complete (ws, payload, payload_len); if (MHD_WEBSOCKET_STATUS_OK != ret) { *streambuf_read_len = current; return ret; } } break; /* payload data */ case MHD_WebSocket_DecodeStep_PayloadOfDataFrame: case MHD_WebSocket_DecodeStep_PayloadOfControlFrame: { size_t bytes_needed = ws->payload_size - ws->payload_index; size_t bytes_remaining = streambuf_len - current; size_t bytes_to_take = bytes_needed < bytes_remaining ? bytes_needed : bytes_remaining; if (0 != bytes_to_take) { size_t utf8_start = ws->payload_index; char *decode_payload = ws->decode_step == MHD_WebSocket_DecodeStep_PayloadOfDataFrame ? ws->data_payload_start : ws->control_payload; /* copy the new payload data (with unmasking if necessary */ MHD_websocket_copy_payload (decode_payload + ws->payload_index, &streambuf [current], bytes_to_take, *((uint32_t *) ws->mask_key), (unsigned long) (ws->payload_index & 0x03)); current += bytes_to_take; ws->payload_index += bytes_to_take; if (((MHD_WebSocket_DecodeStep_PayloadOfDataFrame == ws->decode_step) && (MHD_WebSocket_Opcode_Text == ws->data_type)) || ((MHD_WebSocket_DecodeStep_PayloadOfControlFrame == ws->decode_step) && (MHD_WebSocket_Opcode_Close == (ws->frame_header [0] & 0x0f)) && (2 < ws->payload_index)) ) { /* RFC 6455 8.1: We need to check the UTF-8 validity */ int utf8_step; char *decode_payload_utf8; size_t bytes_to_check; size_t utf8_error_offset = 0; if (MHD_WebSocket_DecodeStep_PayloadOfDataFrame == ws->decode_step) { utf8_step = ws->data_utf8_step; decode_payload_utf8 = decode_payload + utf8_start; bytes_to_check = bytes_to_take; } else { utf8_step = ws->control_utf8_step; if ((MHD_WebSocket_Opcode_Close == (ws->frame_header [0] & 0x0f)) && (2 > utf8_start) ) { /* The first two bytes of the close frame are binary content and */ /* must be skipped in the UTF-8 check */ utf8_start = 2; utf8_error_offset = 2; } decode_payload_utf8 = decode_payload + utf8_start; bytes_to_check = bytes_to_take - utf8_start; } size_t utf8_check_offset = 0; int utf8_result = MHD_websocket_check_utf8 (decode_payload_utf8, bytes_to_check, &utf8_step, &utf8_check_offset); if (MHD_WebSocket_UTF8Result_Invalid != utf8_result) { /* memorize current validity check step to continue later */ ws->data_utf8_step = utf8_step; } else { /* RFC 6455 8.1: We must fail on broken UTF-8 sequence */ ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_MALFORMED_UTF8, 0, 0, payload, payload_len); } *streambuf_read_len = current - bytes_to_take + utf8_check_offset + utf8_error_offset; return MHD_WEBSOCKET_STATUS_UTF8_ENCODING_ERROR; } } } } if (ws->payload_size == ws->payload_index) { /* all payload data of the current frame has been received */ int ret = MHD_websocket_decode_payload_complete (ws, payload, payload_len); if (MHD_WEBSOCKET_STATUS_OK != ret) { *streambuf_read_len = current; return ret; } } break; case MHD_WebSocket_DecodeStep_BrokenStream: *streambuf_read_len = current; return MHD_WEBSOCKET_STATUS_STREAM_BROKEN; } } /* Special treatment for zero payload length messages */ if (MHD_WebSocket_DecodeStep_HeaderCompleted == ws->decode_step) { int ret = MHD_websocket_decode_header_complete (ws, payload, payload_len); if (MHD_WEBSOCKET_STATUS_OK != ret) { *streambuf_read_len = current; return ret; } } switch (ws->decode_step) { case MHD_WebSocket_DecodeStep_PayloadOfDataFrame: case MHD_WebSocket_DecodeStep_PayloadOfControlFrame: if (ws->payload_size == ws->payload_index) { /* all payload data of the current frame has been received */ int ret = MHD_websocket_decode_payload_complete (ws, payload, payload_len); if (MHD_WEBSOCKET_STATUS_OK != ret) { *streambuf_read_len = current; return ret; } } break; } *streambuf_read_len = current; /* more data needed */ return MHD_WEBSOCKET_STATUS_OK; } static enum MHD_WEBSOCKET_STATUS MHD_websocket_decode_header_complete (struct MHD_WebSocketStream *ws, char **payload, size_t *payload_len) { /* assign either to data or control */ char opcode = ws->frame_header [0] & 0x0f; switch (opcode) { case MHD_WebSocket_Opcode_Continuation: { /* validate payload size */ size_t new_size_total = ws->payload_size + ws->data_payload_size; if ((0 != ws->max_payload_size) && (ws->max_payload_size < new_size_total) ) { /* RFC 6455 7.4.1 1009: If the message is too big to process, */ /* we may close the connection */ ws->decode_step = MHD_WebSocket_DecodeStep_BrokenStream; ws->validity = MHD_WEBSOCKET_VALIDITY_INVALID; if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR)) { MHD_websocket_encode_close (ws, MHD_WEBSOCKET_CLOSEREASON_MAXIMUM_ALLOWED_PAYLOAD_SIZE_EXCEEDED, 0, 0, payload, payload_len); } return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } /* allocate buffer for continued data frame */ char *new_buf = NULL; if (0 != new_size_total) { new_buf = ws->realloc (ws->data_payload, new_size_total + 1); if (NULL == new_buf) { return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; } new_buf [new_size_total] = 0; ws->data_payload_start = &new_buf[ws->data_payload_size]; } else { ws->data_payload_start = new_buf; } ws->data_payload = new_buf; ws->data_payload_size = new_size_total; } ws->decode_step = MHD_WebSocket_DecodeStep_PayloadOfDataFrame; break; case MHD_WebSocket_Opcode_Text: case MHD_WebSocket_Opcode_Binary: /* allocate buffer for data frame */ { size_t new_size_total = ws->payload_size; char *new_buf = NULL; if (0 != new_size_total) { new_buf = ws->malloc (new_size_total + 1); if (NULL == new_buf) { return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; } new_buf [new_size_total] = 0; } ws->data_payload = new_buf; ws->data_payload_start = new_buf; ws->data_payload_size = new_size_total; ws->data_type = opcode; } ws->decode_step = MHD_WebSocket_DecodeStep_PayloadOfDataFrame; break; case MHD_WebSocket_Opcode_Close: case MHD_WebSocket_Opcode_Ping: case MHD_WebSocket_Opcode_Pong: /* allocate buffer for control frame */ { size_t new_size_total = ws->payload_size; char *new_buf = NULL; if (0 != new_size_total) { new_buf = ws->malloc (new_size_total + 1); if (NULL == new_buf) { return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; } new_buf[new_size_total] = 0; } ws->control_payload = new_buf; } ws->decode_step = MHD_WebSocket_DecodeStep_PayloadOfControlFrame; break; } return MHD_WEBSOCKET_STATUS_OK; } static enum MHD_WEBSOCKET_STATUS MHD_websocket_decode_payload_complete (struct MHD_WebSocketStream *ws, char **payload, size_t *payload_len) { /* all payload data of the current frame has been received */ char is_continue = MHD_WebSocket_Opcode_Continuation == (ws->frame_header [0] & 0x0F); char is_fin = ws->frame_header [0] & 0x80; if (0 != is_fin) { /* the frame is complete */ if (MHD_WebSocket_DecodeStep_PayloadOfDataFrame == ws->decode_step) { /* data frame */ char data_type = ws->data_type; if ((0 != (ws->flags & MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS)) && (0 != is_continue)) { data_type |= 0x40; /* mark as last fragment */ } *payload = ws->data_payload; *payload_len = ws->data_payload_size; ws->data_payload = 0; ws->data_payload_start = 0; ws->data_payload_size = 0; ws->decode_step = MHD_WebSocket_DecodeStep_Start; ws->payload_index = 0; ws->data_type = 0; ws->frame_header_size = 0; return data_type; } else { /* control frame */ *payload = ws->control_payload; *payload_len = ws->payload_size; ws->control_payload = 0; ws->decode_step = MHD_WebSocket_DecodeStep_Start; ws->payload_index = 0; ws->frame_header_size = 0; return (ws->frame_header [0] & 0x0f); } } else if (0 != (ws->flags & MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS)) { /* RFC 6455 5.4: To allow streaming, the user can choose */ /* to return fragments */ if ((MHD_WebSocket_Opcode_Text == ws->data_type) && (MHD_WEBSOCKET_UTF8STEP_NORMAL != ws->data_utf8_step) ) { /* the last UTF-8 sequence is incomplete, so we keep the start of that and only return the part before */ size_t given_utf8 = 0; switch (ws->data_utf8_step) { /* one byte given */ case MHD_WEBSOCKET_UTF8STEP_UTF2TAIL_1OF1: case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL1_1OF2: case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL2_1OF2: case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_1OF2: case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL1_1OF3: case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL2_1OF3: case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_1OF3: given_utf8 = 1; break; /* two bytes given */ case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2: case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3: given_utf8 = 2; break; /* three bytes given */ case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_3OF3: given_utf8 = 3; break; } size_t new_len = ws->data_payload_size - given_utf8; if (0 != new_len) { char *next_payload = ws->malloc (given_utf8 + 1); if (NULL == next_payload) { return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; } memcpy (next_payload, ws->data_payload_start + ws->payload_index - given_utf8, given_utf8); next_payload[given_utf8] = 0; ws->data_payload[new_len] = 0; *payload = ws->data_payload; *payload_len = new_len; ws->data_payload = next_payload; ws->data_payload_size = given_utf8; } else { *payload = NULL; *payload_len = 0; } ws->decode_step = MHD_WebSocket_DecodeStep_Start; ws->payload_index = 0; ws->frame_header_size = 0; if (0 != is_continue) return ws->data_type | 0x20; /* mark as middle fragment */ else return ws->data_type | 0x10; /* mark as first fragment */ } else { /* we simply pass the entire data frame */ *payload = ws->data_payload; *payload_len = ws->data_payload_size; ws->data_payload = 0; ws->data_payload_start = 0; ws->data_payload_size = 0; ws->decode_step = MHD_WebSocket_DecodeStep_Start; ws->payload_index = 0; ws->frame_header_size = 0; if (0 != is_continue) return ws->data_type | 0x20; /* mark as middle fragment */ else return ws->data_type | 0x10; /* mark as first fragment */ } } else { /* RFC 6455 5.4: We must await a continuation frame to get */ /* the remainder of this data frame */ ws->decode_step = MHD_WebSocket_DecodeStep_Start; ws->frame_header_size = 0; ws->payload_index = 0; return MHD_WEBSOCKET_STATUS_OK; } } /** * Splits the received close reason */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_split_close_reason (const char *payload, size_t payload_len, unsigned short *reason_code, const char **reason_utf8, size_t *reason_utf8_len) { /* initialize output variables for errors cases */ if (NULL != reason_code) *reason_code = MHD_WEBSOCKET_CLOSEREASON_NO_REASON; if (NULL != reason_utf8) *reason_utf8 = NULL; if (NULL != reason_utf8_len) *reason_utf8_len = 0; /* validate parameters */ if ((NULL == payload) && (0 != payload_len)) return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; if (1 == payload_len) return MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR; if (125 < payload_len) return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; /* decode reason code */ if (2 > payload_len) { if (NULL != reason_code) *reason_code = MHD_WEBSOCKET_CLOSEREASON_NO_REASON; } else { if (NULL != reason_code) *reason_code = MHD_htons (*((uint16_t *) payload)); } /* decode reason text */ if (2 >= payload_len) { if (NULL != reason_utf8) *reason_utf8 = NULL; if (NULL != reason_utf8_len) *reason_utf8_len = 0; } else { if (NULL != reason_utf8) *reason_utf8 = payload + 2; if (NULL != reason_utf8_len) *reason_utf8_len = payload_len - 2; } return MHD_WEBSOCKET_STATUS_OK; } /** * Encodes a text into a websocket text frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_text (struct MHD_WebSocketStream *ws, const char *payload_utf8, size_t payload_utf8_len, int fragmentation, char **frame, size_t *frame_len, int *utf8_step) { /* initialize output variables for errors cases */ if (NULL != frame) *frame = NULL; if (NULL != frame_len) *frame_len = 0; if ((NULL != utf8_step) && ((MHD_WEBSOCKET_FRAGMENTATION_FIRST == fragmentation) || (MHD_WEBSOCKET_FRAGMENTATION_NONE == fragmentation) )) { /* the old UTF-8 step will be ignored for new fragments */ *utf8_step = MHD_WEBSOCKET_UTF8STEP_NORMAL; } /* validate parameters */ if ((NULL == ws) || ((0 != payload_utf8_len) && (NULL == payload_utf8)) || (NULL == frame) || (NULL == frame_len) || (MHD_WEBSOCKET_FRAGMENTATION_NONE > fragmentation) || (MHD_WEBSOCKET_FRAGMENTATION_LAST < fragmentation) || ((MHD_WEBSOCKET_FRAGMENTATION_NONE != fragmentation) && (NULL == utf8_step)) ) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* check max length */ if ((uint64_t) 0x7FFFFFFFFFFFFFFF < (uint64_t) payload_utf8_len) { return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } /* check UTF-8 */ int utf8_result = MHD_websocket_check_utf8 (payload_utf8, payload_utf8_len, utf8_step, NULL); if ((MHD_WebSocket_UTF8Result_Invalid == utf8_result) || ((MHD_WebSocket_UTF8Result_Incomplete == utf8_result) && (MHD_WEBSOCKET_FRAGMENTATION_NONE == fragmentation)) ) { return MHD_WEBSOCKET_STATUS_UTF8_ENCODING_ERROR; } /* encode data */ return MHD_websocket_encode_data (ws, payload_utf8, payload_utf8_len, fragmentation, frame, frame_len, MHD_WebSocket_Opcode_Text); } /** * Encodes binary data into a websocket binary frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_binary (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, int fragmentation, char **frame, size_t *frame_len) { /* initialize output variables for errors cases */ if (NULL != frame) *frame = NULL; if (NULL != frame_len) *frame_len = 0; /* validate parameters */ if ((NULL == ws) || ((0 != payload_len) && (NULL == payload)) || (NULL == frame) || (NULL == frame_len) || (MHD_WEBSOCKET_FRAGMENTATION_NONE > fragmentation) || (MHD_WEBSOCKET_FRAGMENTATION_LAST < fragmentation) ) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* check max length */ if ((uint64_t) 0x7FFFFFFFFFFFFFFF < (uint64_t) payload_len) { return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; } return MHD_websocket_encode_data (ws, payload, payload_len, fragmentation, frame, frame_len, MHD_WebSocket_Opcode_Binary); } /** * Internal function for encoding text/binary data into a websocket frame */ static enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_data (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, int fragmentation, char **frame, size_t *frame_len, char opcode) { /* calculate length and masking */ char is_masked = MHD_websocket_encode_is_masked (ws); size_t overhead_len = MHD_websocket_encode_overhead_size (ws, payload_len); size_t total_len = overhead_len + payload_len; uint32_t mask = 0 != is_masked ? MHD_websocket_generate_mask (ws) : 0; /* allocate memory */ char *result = ws->malloc (total_len + 1); if (NULL == result) return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; result [total_len] = 0; *frame = result; *frame_len = total_len; /* add the opcode */ switch (fragmentation) { case MHD_WEBSOCKET_FRAGMENTATION_NONE: *(result++) = 0x80 | opcode; break; case MHD_WEBSOCKET_FRAGMENTATION_FIRST: *(result++) = opcode; break; case MHD_WEBSOCKET_FRAGMENTATION_FOLLOWING: *(result++) = MHD_WebSocket_Opcode_Continuation; break; case MHD_WEBSOCKET_FRAGMENTATION_LAST: *(result++) = 0x80 | MHD_WebSocket_Opcode_Continuation; break; } /* add the length */ if (126 > payload_len) { *(result++) = is_masked | (char) payload_len; } else if (65536 > payload_len) { *(result++) = is_masked | 126; *((uint16_t *) result) = MHD_htons ((uint16_t) payload_len); result += 2; } else { *(result++) = is_masked | 127; *((uint64_t *) result) = MHD_htonll ((uint64_t) payload_len); result += 8; } /* add the mask */ if (0 != is_masked) { *(result++) = ((char *) &mask)[0]; *(result++) = ((char *) &mask)[1]; *(result++) = ((char *) &mask)[2]; *(result++) = ((char *) &mask)[3]; } /* add the payload */ if (0 != payload_len) { MHD_websocket_copy_payload (result, payload, payload_len, mask, 0); } return MHD_WEBSOCKET_STATUS_OK; } /** * Encodes a websocket ping frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_ping (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, char **frame, size_t *frame_len) { /* encode the ping frame */ return MHD_websocket_encode_ping_pong (ws, payload, payload_len, frame, frame_len, MHD_WebSocket_Opcode_Ping); } /** * Encodes a websocket pong frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_pong (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, char **frame, size_t *frame_len) { /* encode the pong frame */ return MHD_websocket_encode_ping_pong (ws, payload, payload_len, frame, frame_len, MHD_WebSocket_Opcode_Pong); } /** * Internal function for encoding ping/pong frames */ static enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_ping_pong (struct MHD_WebSocketStream *ws, const char *payload, size_t payload_len, char **frame, size_t *frame_len, char opcode) { /* initialize output variables for errors cases */ if (NULL != frame) *frame = NULL; if (NULL != frame_len) *frame_len = 0; /* validate the parameters */ if ((NULL == ws) || ((0 != payload_len) && (NULL == payload)) || (NULL == frame) || (NULL == frame_len) ) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* RFC 6455 5.5: Control frames may only have up to 125 bytes of payload data */ if (125 < payload_len) return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; /* calculate length and masking */ char is_masked = MHD_websocket_encode_is_masked (ws); size_t overhead_len = MHD_websocket_encode_overhead_size (ws, payload_len); size_t total_len = overhead_len + payload_len; uint32_t mask = is_masked != 0 ? MHD_websocket_generate_mask (ws) : 0; /* allocate memory */ char *result = ws->malloc (total_len + 1); if (NULL == result) return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; result [total_len] = 0; *frame = result; *frame_len = total_len; /* add the opcode */ *(result++) = 0x80 | opcode; /* add the length */ *(result++) = is_masked | (char) payload_len; /* add the mask */ if (0 != is_masked) { *(result++) = ((char *) &mask)[0]; *(result++) = ((char *) &mask)[1]; *(result++) = ((char *) &mask)[2]; *(result++) = ((char *) &mask)[3]; } /* add the payload */ if (0 != payload_len) { MHD_websocket_copy_payload (result, payload, payload_len, mask, 0); } return MHD_WEBSOCKET_STATUS_OK; } /** * Encodes a websocket close frame */ _MHD_EXTERN enum MHD_WEBSOCKET_STATUS MHD_websocket_encode_close (struct MHD_WebSocketStream *ws, unsigned short reason_code, const char *reason_utf8, size_t reason_utf8_len, char **frame, size_t *frame_len) { /* initialize output variables for errors cases */ if (NULL != frame) *frame = NULL; if (NULL != frame_len) *frame_len = 0; /* validate the parameters */ if ((NULL == ws) || ((0 != reason_utf8_len) && (NULL == reason_utf8)) || (NULL == frame) || (NULL == frame_len) || ((MHD_WEBSOCKET_CLOSEREASON_NO_REASON != reason_code) && (1000 > reason_code)) || ((0 != reason_utf8_len) && (MHD_WEBSOCKET_CLOSEREASON_NO_REASON == reason_code)) ) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } /* RFC 6455 5.5: Control frames may only have up to 125 bytes of payload data, */ /* but in this case only 123 bytes, because 2 bytes are reserved */ /* for the close reason code. */ if (123 < reason_utf8_len) return MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED; /* RFC 6455 5.5.1: If close payload data is given, it must be valid UTF-8 */ if (0 != reason_utf8_len) { int utf8_result = MHD_websocket_check_utf8 (reason_utf8, reason_utf8_len, NULL, NULL); if (MHD_WebSocket_UTF8Result_Valid != utf8_result) return MHD_WEBSOCKET_STATUS_UTF8_ENCODING_ERROR; } /* calculate length and masking */ char is_masked = MHD_websocket_encode_is_masked (ws); size_t payload_len = (MHD_WEBSOCKET_CLOSEREASON_NO_REASON != reason_code ? 2 + reason_utf8_len : 0); size_t overhead_len = MHD_websocket_encode_overhead_size (ws, payload_len); size_t total_len = overhead_len + payload_len; uint32_t mask = is_masked != 0 ? MHD_websocket_generate_mask (ws) : 0; /* allocate memory */ char *result = ws->malloc (total_len + 1); if (NULL == result) return MHD_WEBSOCKET_STATUS_MEMORY_ERROR; result [total_len] = 0; *frame = result; *frame_len = total_len; /* add the opcode */ *(result++) = 0x88; /* add the length */ *(result++) = is_masked | (char) payload_len; /* add the mask */ if (0 != is_masked) { *(result++) = ((char *) &mask)[0]; *(result++) = ((char *) &mask)[1]; *(result++) = ((char *) &mask)[2]; *(result++) = ((char *) &mask)[3]; } /* add the payload */ if (0 != reason_code) { /* close reason code */ uint16_t reason_code_nb = MHD_htons (reason_code); MHD_websocket_copy_payload (result, (const char *) &reason_code_nb, 2, mask, 0); result += 2; /* custom reason payload */ if (0 != reason_utf8_len) { MHD_websocket_copy_payload (result, reason_utf8, reason_utf8_len, mask, 2); } } return MHD_WEBSOCKET_STATUS_OK; } /** * Returns the 0x80 prefix for masked data, 0x00 otherwise */ static char MHD_websocket_encode_is_masked (struct MHD_WebSocketStream *ws) { return (ws->flags & MHD_WEBSOCKET_FLAG_MASK_SERVERCLIENT) == MHD_WEBSOCKET_FLAG_CLIENT ? 0x80 : 0x00; } /** * Calculates the size of the overhead in bytes */ static char MHD_websocket_encode_overhead_size (struct MHD_WebSocketStream *ws, size_t payload_len) { return 2 + (MHD_websocket_encode_is_masked (ws) != 0 ? 4 : 0) + (125 < payload_len ? (65535 < payload_len ? 8 : 2) : 0); } /** * Copies the payload to the destination (using mask) */ static void MHD_websocket_copy_payload (char *dst, const char *src, size_t len, uint32_t mask, unsigned long mask_offset) { if (0 != len) { if (0 == mask) { /* when the mask is zero, we can just copy the data */ memcpy (dst, src, len); } else { /* mask is used */ char mask_[4]; *((uint32_t *) mask_) = mask; for (size_t i = 0; i < len; ++i) { dst[i] = src[i] ^ mask_[(i + mask_offset) & 3]; } } } } /** * Checks a UTF-8 sequence */ static int MHD_websocket_check_utf8 (const char *buf, size_t buf_len, int *utf8_step, size_t *buf_offset) { int utf8_step_ = (NULL != utf8_step) ? *utf8_step : MHD_WEBSOCKET_UTF8STEP_NORMAL; for (size_t i = 0; i < buf_len; ++i) { unsigned char character = (unsigned char) buf[i]; switch (utf8_step_) { case MHD_WEBSOCKET_UTF8STEP_NORMAL: if ((0x00 <= character) && (0x7F >= character)) { /* RFC 3629 4: single byte UTF-8 sequence */ /* (nothing to do here) */ } else if ((0xC2 <= character) && (0xDF >= character)) { /* RFC 3629 4: two byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF2TAIL_1OF1; } else if (0xE0 == character) { /* RFC 3629 4: three byte UTF-8 sequence, but the second byte must be 0xA0-0xBF */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL1_1OF2; } else if (0xED == character) { /* RFC 3629 4: three byte UTF-8 sequence, but the second byte must be 0x80-0x9F */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL2_1OF2; } else if (((0xE1 <= character) && (0xEC >= character)) || ((0xEE <= character) && (0xEF >= character)) ) { /* RFC 3629 4: three byte UTF-8 sequence, both tail bytes must be 0x80-0xBF */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_1OF2; } else if (0xF0 == character) { /* RFC 3629 4: four byte UTF-8 sequence, but the second byte must be 0x90-0xBF */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL1_1OF3; } else if (0xF4 == character) { /* RFC 3629 4: four byte UTF-8 sequence, but the second byte must be 0x80-0x8F */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL2_1OF3; } else if ((0xF1 <= character) && (0xF3 >= character)) { /* RFC 3629 4: four byte UTF-8 sequence, all three tail bytes must be 0x80-0xBF */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_1OF3; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL1_1OF2: if ((0xA0 <= character) && (0xBF >= character)) { /* RFC 3629 4: Second byte of three byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL2_1OF2: if ((0x80 <= character) && (0x9F >= character)) { /* RFC 3629 4: Second byte of three byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_1OF2: if ((0x80 <= character) && (0xBF >= character)) { /* RFC 3629 4: Second byte of three byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL1_1OF3: if ((0x90 <= character) && (0xBF >= character)) { /* RFC 3629 4: Second byte of four byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL2_1OF3: if ((0x80 <= character) && (0x8F >= character)) { /* RFC 3629 4: Second byte of four byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_1OF3: if ((0x80 <= character) && (0xBF >= character)) { /* RFC 3629 4: Second byte of four byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3: if ((0x80 <= character) && (0xBF >= character)) { /* RFC 3629 4: Third byte of four byte UTF-8 sequence */ utf8_step_ = MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_3OF3; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; /* RFC 3629 4: Second byte of two byte UTF-8 sequence */ case MHD_WEBSOCKET_UTF8STEP_UTF2TAIL_1OF1: /* RFC 3629 4: Third byte of three byte UTF-8 sequence */ case MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2: /* RFC 3629 4: Fourth byte of four byte UTF-8 sequence */ case MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_3OF3: if ((0x80 <= character) && (0xBF >= character)) { utf8_step_ = MHD_WEBSOCKET_UTF8STEP_NORMAL; } else { /* RFC 3629 4: Invalid UTF-8 byte */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } break; default: /* Invalid last step...? */ if (NULL != buf_offset) *buf_offset = i; return MHD_WebSocket_UTF8Result_Invalid; } } /* return values */ if (NULL != utf8_step) *utf8_step = utf8_step_; if (NULL != buf_offset) *buf_offset = buf_len; if (MHD_WEBSOCKET_UTF8STEP_NORMAL != utf8_step_) { return MHD_WebSocket_UTF8Result_Incomplete; } return MHD_WebSocket_UTF8Result_Valid; } /** * Generates a mask for masking by calling * a random number generator. */ static uint32_t MHD_websocket_generate_mask (struct MHD_WebSocketStream *ws) { unsigned char mask_[4]; if (NULL != ws->rng) { size_t offset = 0; while (offset < 4) { size_t encoded = ws->rng (ws->cls_rng, mask_ + offset, 4 - offset); offset += encoded; } } else { /* this case should never happen */ mask_ [0] = 0; mask_ [1] = 0; mask_ [2] = 0; mask_ [3] = 0; } return *((uint32_t *) mask_); } /** * Calls the malloc function associated with the websocket steam */ _MHD_EXTERN void * MHD_websocket_malloc (struct MHD_WebSocketStream *ws, size_t buf_len) { if (NULL == ws) { return NULL; } return ws->malloc (buf_len); } /** * Calls the realloc function associated with the websocket steam */ _MHD_EXTERN void * MHD_websocket_realloc (struct MHD_WebSocketStream *ws, void *buf, size_t new_buf_len) { if (NULL == ws) { return NULL; } return ws->realloc (buf, new_buf_len); } /** * Calls the free function associated with the websocket steam */ _MHD_EXTERN int MHD_websocket_free (struct MHD_WebSocketStream *ws, void *buf) { if (NULL == ws) { return MHD_WEBSOCKET_STATUS_PARAMETER_ERROR; } ws->free (buf); return MHD_WEBSOCKET_STATUS_OK; } /** * Converts a 16 bit value into network byte order (MSB first) * in dependence of the host system */ static uint16_t MHD_htons (uint16_t value) { uint16_t endian = 0x0001; if (((char *) &endian)[0] == 0x01) { /* least significant byte first */ ((char *) &endian)[0] = ((char *) &value)[1]; ((char *) &endian)[1] = ((char *) &value)[0]; return endian; } else { /* most significant byte first */ return value; } } /** * Converts a 64 bit value into network byte order (MSB first) * in dependence of the host system */ static uint64_t MHD_htonll (uint64_t value) { uint64_t endian = 0x0000000000000001; if (((char *) &endian)[0] == 0x01) { /* least significant byte first */ ((char *) &endian)[0] = ((char *) &value)[7]; ((char *) &endian)[1] = ((char *) &value)[6]; ((char *) &endian)[2] = ((char *) &value)[5]; ((char *) &endian)[3] = ((char *) &value)[4]; ((char *) &endian)[4] = ((char *) &value)[3]; ((char *) &endian)[5] = ((char *) &value)[2]; ((char *) &endian)[6] = ((char *) &value)[1]; ((char *) &endian)[7] = ((char *) &value)[0]; return endian; } else { /* most significant byte first */ return value; } }