blob: 935deee6e2a66a30e088ab18f750e6bb9c974347 [file] [log] [blame]
/*
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;
}
}