Move a lot of stuff to liberty
This commit is contained in:
parent
b0c712c146
commit
64381f8d46
753
json-rpc-shell.c
753
json-rpc-shell.c
@ -35,6 +35,8 @@
|
||||
#define print_warning_data ATTR_WARNING
|
||||
|
||||
#define LIBERTY_WANT_SSL
|
||||
#define LIBERTY_WANT_PROTO_HTTP
|
||||
#define LIBERTY_WANT_PROTO_WS
|
||||
|
||||
#include "config.h"
|
||||
#include "liberty/liberty.c"
|
||||
@ -57,757 +59,6 @@
|
||||
#include <curses.h>
|
||||
#include <term.h>
|
||||
|
||||
// --- Extensions to liberty ---------------------------------------------------
|
||||
|
||||
// COPIED OVER FROM ACID, DON'T CHANGE SEPARATELY
|
||||
|
||||
#define UNPACKER_INT_BEGIN \
|
||||
if (self->len - self->offset < sizeof *value) \
|
||||
return false; \
|
||||
uint8_t *x = (uint8_t *) self->data + self->offset; \
|
||||
self->offset += sizeof *value;
|
||||
|
||||
static bool
|
||||
msg_unpacker_u16 (struct msg_unpacker *self, uint16_t *value)
|
||||
{
|
||||
UNPACKER_INT_BEGIN
|
||||
*value
|
||||
= (uint16_t) x[0] << 24 | (uint16_t) x[1] << 16;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
msg_unpacker_u32 (struct msg_unpacker *self, uint32_t *value)
|
||||
{
|
||||
UNPACKER_INT_BEGIN
|
||||
*value
|
||||
= (uint32_t) x[0] << 24 | (uint32_t) x[1] << 16
|
||||
| (uint32_t) x[2] << 8 | (uint32_t) x[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef UNPACKER_INT_BEGIN
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// "msg_writer" should be rewritten on top of this
|
||||
|
||||
static void
|
||||
str_pack_u8 (struct str *self, uint8_t x)
|
||||
{
|
||||
str_append_data (self, &x, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
str_pack_u16 (struct str *self, uint64_t x)
|
||||
{
|
||||
uint8_t tmp[2] = { x >> 8, x };
|
||||
str_append_data (self, tmp, sizeof tmp);
|
||||
}
|
||||
|
||||
static void
|
||||
str_pack_u32 (struct str *self, uint32_t x)
|
||||
{
|
||||
uint32_t u = x;
|
||||
uint8_t tmp[4] = { u >> 24, u >> 16, u >> 8, u };
|
||||
str_append_data (self, tmp, sizeof tmp);
|
||||
}
|
||||
|
||||
static void
|
||||
str_pack_i32 (struct str *self, int32_t x)
|
||||
{
|
||||
str_pack_u32 (self, (uint32_t) x);
|
||||
}
|
||||
|
||||
static void
|
||||
str_pack_u64 (struct str *self, uint64_t x)
|
||||
{
|
||||
uint8_t tmp[8] =
|
||||
{ x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x };
|
||||
str_append_data (self, tmp, sizeof tmp);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static int
|
||||
tolower_ascii (int c)
|
||||
{
|
||||
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
|
||||
}
|
||||
|
||||
static size_t
|
||||
tolower_ascii_strxfrm (char *dest, const char *src, size_t n)
|
||||
{
|
||||
size_t len = strlen (src);
|
||||
while (n-- && (*dest++ = tolower_ascii (*src++)))
|
||||
;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
strcasecmp_ascii (const char *a, const char *b)
|
||||
{
|
||||
while (*a && tolower_ascii (*a) == tolower_ascii (*b))
|
||||
{
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *(const unsigned char *) a - *(const unsigned char *) b;
|
||||
}
|
||||
|
||||
static bool
|
||||
isspace_ascii (int c)
|
||||
{
|
||||
return c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v';
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/// Return a pointer to the next UTF-8 character, or NULL on error
|
||||
// TODO: decode the sequence while we're at it
|
||||
static const char *
|
||||
utf8_next (const char *s, size_t len)
|
||||
{
|
||||
// End of string, we go no further
|
||||
if (!len)
|
||||
return NULL;
|
||||
|
||||
// In the middle of a character -> error
|
||||
const uint8_t *p = (const unsigned char *) s;
|
||||
if ((*p & 0xC0) == 0x80)
|
||||
return NULL;
|
||||
|
||||
// Find out how long the sequence is
|
||||
unsigned mask = 0xC0;
|
||||
unsigned tail_len = 0;
|
||||
while ((*p & mask) == mask)
|
||||
{
|
||||
// Invalid start of sequence
|
||||
if (mask == 0xFE)
|
||||
return NULL;
|
||||
|
||||
mask |= mask >> 1;
|
||||
tail_len++;
|
||||
}
|
||||
|
||||
p++;
|
||||
|
||||
// Check the rest of the sequence
|
||||
if (tail_len > --len)
|
||||
return NULL;
|
||||
|
||||
while (tail_len--)
|
||||
if ((*p++ & 0xC0) != 0x80)
|
||||
return NULL;
|
||||
|
||||
return (const char *) p;
|
||||
}
|
||||
|
||||
/// Very rough UTF-8 validation, just makes sure codepoints can be iterated
|
||||
// TODO: also validate the codepoints
|
||||
static bool
|
||||
utf8_validate (const char *s, size_t len)
|
||||
{
|
||||
const char *next;
|
||||
while (len)
|
||||
{
|
||||
if (!(next = utf8_next (s, len)))
|
||||
return false;
|
||||
|
||||
len -= next - s;
|
||||
s = next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static uint8_t g_base64_table[256] =
|
||||
{
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 0, 64, 64,
|
||||
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
||||
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
};
|
||||
|
||||
static inline bool
|
||||
base64_decode_group (const char **s, bool ignore_ws, struct str *output)
|
||||
{
|
||||
uint8_t input[4];
|
||||
size_t loaded = 0;
|
||||
for (; loaded < 4; (*s)++)
|
||||
{
|
||||
if (!**s)
|
||||
return loaded == 0;
|
||||
if (!ignore_ws || !isspace_ascii (**s))
|
||||
input[loaded++] = **s;
|
||||
}
|
||||
|
||||
size_t len = 3;
|
||||
if (input[0] == '=' || input[1] == '=')
|
||||
return false;
|
||||
if (input[2] == '=' && input[3] != '=')
|
||||
return false;
|
||||
if (input[2] == '=')
|
||||
len--;
|
||||
if (input[3] == '=')
|
||||
len--;
|
||||
|
||||
uint8_t a = g_base64_table[input[0]];
|
||||
uint8_t b = g_base64_table[input[1]];
|
||||
uint8_t c = g_base64_table[input[2]];
|
||||
uint8_t d = g_base64_table[input[3]];
|
||||
|
||||
if (((a | b) | (c | d)) & 0x40)
|
||||
return false;
|
||||
|
||||
uint32_t block = a << 18 | b << 12 | c << 6 | d;
|
||||
switch (len)
|
||||
{
|
||||
case 1:
|
||||
str_append_c (output, block >> 16);
|
||||
break;
|
||||
case 2:
|
||||
str_append_c (output, block >> 16);
|
||||
str_append_c (output, block >> 8);
|
||||
break;
|
||||
case 3:
|
||||
str_append_c (output, block >> 16);
|
||||
str_append_c (output, block >> 8);
|
||||
str_append_c (output, block);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
base64_decode (const char *s, bool ignore_ws, struct str *output)
|
||||
{
|
||||
while (*s)
|
||||
if (!base64_decode_group (&s, ignore_ws, output))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
base64_encode (const void *data, size_t len, struct str *output)
|
||||
{
|
||||
const char *alphabet =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
const uint8_t *p = data;
|
||||
size_t n_groups = len / 3;
|
||||
size_t tail = len - n_groups * 3;
|
||||
uint32_t group;
|
||||
|
||||
for (; n_groups--; p += 3)
|
||||
{
|
||||
group = p[0] << 16 | p[1] << 8 | p[2];
|
||||
str_append_c (output, alphabet[(group >> 18) & 63]);
|
||||
str_append_c (output, alphabet[(group >> 12) & 63]);
|
||||
str_append_c (output, alphabet[(group >> 6) & 63]);
|
||||
str_append_c (output, alphabet[ group & 63]);
|
||||
}
|
||||
|
||||
switch (tail)
|
||||
{
|
||||
case 2:
|
||||
group = p[0] << 16 | p[1] << 8;
|
||||
str_append_c (output, alphabet[(group >> 18) & 63]);
|
||||
str_append_c (output, alphabet[(group >> 12) & 63]);
|
||||
str_append_c (output, alphabet[(group >> 6) & 63]);
|
||||
str_append_c (output, '=');
|
||||
break;
|
||||
case 1:
|
||||
group = p[0] << 16;
|
||||
str_append_c (output, alphabet[(group >> 18) & 63]);
|
||||
str_append_c (output, alphabet[(group >> 12) & 63]);
|
||||
str_append_c (output, '=');
|
||||
str_append_c (output, '=');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- HTTP parsing ------------------------------------------------------------
|
||||
|
||||
// COPIED OVER FROM ACID, DON'T CHANGE SEPARATELY
|
||||
|
||||
// Basic tokenizer for HTTP header field values, to be used in various parsers.
|
||||
// The input should already be unwrapped.
|
||||
|
||||
// Recommended literature:
|
||||
// http://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
// http://tools.ietf.org/html/rfc7230#appendix-B
|
||||
// http://tools.ietf.org/html/rfc5234#appendix-B.1
|
||||
|
||||
#define HTTP_TOKENIZER_CLASS(name, definition) \
|
||||
static inline bool \
|
||||
http_tokenizer_is_ ## name (int c) \
|
||||
{ \
|
||||
return (definition); \
|
||||
}
|
||||
|
||||
HTTP_TOKENIZER_CLASS (vchar, c >= 0x21 && c <= 0x7E)
|
||||
HTTP_TOKENIZER_CLASS (delimiter, !!strchr ("\"(),/:;<=>?@[\\]{}", c))
|
||||
HTTP_TOKENIZER_CLASS (whitespace, c == '\t' || c == ' ')
|
||||
HTTP_TOKENIZER_CLASS (obs_text, c >= 0x80 && c <= 0xFF)
|
||||
|
||||
HTTP_TOKENIZER_CLASS (tchar,
|
||||
http_tokenizer_is_vchar (c) && !http_tokenizer_is_delimiter (c))
|
||||
|
||||
HTTP_TOKENIZER_CLASS (qdtext,
|
||||
c == '\t' || c == ' ' || c == '!'
|
||||
|| (c >= 0x23 && c <= 0x5B)
|
||||
|| (c >= 0x5D && c <= 0x7E)
|
||||
|| http_tokenizer_is_obs_text (c))
|
||||
|
||||
HTTP_TOKENIZER_CLASS (quoted_pair,
|
||||
c == '\t' || c == ' '
|
||||
|| http_tokenizer_is_vchar (c)
|
||||
|| http_tokenizer_is_obs_text (c))
|
||||
|
||||
#undef HTTP_TOKENIZER_CLASS
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
enum http_tokenizer_token
|
||||
{
|
||||
HTTP_T_EOF, ///< Input error
|
||||
HTTP_T_ERROR, ///< End of input
|
||||
|
||||
HTTP_T_TOKEN, ///< "token"
|
||||
HTTP_T_QUOTED_STRING, ///< "quoted-string"
|
||||
HTTP_T_DELIMITER, ///< "delimiters"
|
||||
HTTP_T_WHITESPACE ///< RWS/OWS/BWS
|
||||
};
|
||||
|
||||
struct http_tokenizer
|
||||
{
|
||||
const unsigned char *input; ///< The input string
|
||||
size_t input_len; ///< Length of the input
|
||||
size_t offset; ///< Position in the input
|
||||
|
||||
char delimiter; ///< The delimiter character
|
||||
struct str string; ///< "token" / "quoted-string" content
|
||||
};
|
||||
|
||||
static void
|
||||
http_tokenizer_init (struct http_tokenizer *self, const char *input, size_t len)
|
||||
{
|
||||
memset (self, 0, sizeof *self);
|
||||
self->input = (const unsigned char *) input;
|
||||
self->input_len = len;
|
||||
|
||||
str_init (&self->string);
|
||||
}
|
||||
|
||||
static void
|
||||
http_tokenizer_free (struct http_tokenizer *self)
|
||||
{
|
||||
str_free (&self->string);
|
||||
}
|
||||
|
||||
static enum http_tokenizer_token
|
||||
http_tokenizer_quoted_string (struct http_tokenizer *self)
|
||||
{
|
||||
bool quoted_pair = false;
|
||||
while (self->offset < self->input_len)
|
||||
{
|
||||
int c = self->input[self->offset++];
|
||||
if (quoted_pair)
|
||||
{
|
||||
if (!http_tokenizer_is_quoted_pair (c))
|
||||
return HTTP_T_ERROR;
|
||||
|
||||
str_append_c (&self->string, c);
|
||||
quoted_pair = false;
|
||||
}
|
||||
else if (c == '\\')
|
||||
quoted_pair = true;
|
||||
else if (c == '"')
|
||||
return HTTP_T_QUOTED_STRING;
|
||||
else if (http_tokenizer_is_qdtext (c))
|
||||
str_append_c (&self->string, c);
|
||||
else
|
||||
return HTTP_T_ERROR;
|
||||
}
|
||||
|
||||
// Premature end of input
|
||||
return HTTP_T_ERROR;
|
||||
}
|
||||
|
||||
static enum http_tokenizer_token
|
||||
http_tokenizer_next (struct http_tokenizer *self, bool skip_ows)
|
||||
{
|
||||
str_reset (&self->string);
|
||||
if (self->offset >= self->input_len)
|
||||
return HTTP_T_EOF;
|
||||
|
||||
int c = self->input[self->offset++];
|
||||
|
||||
if (skip_ows)
|
||||
while (http_tokenizer_is_whitespace (c))
|
||||
{
|
||||
if (self->offset >= self->input_len)
|
||||
return HTTP_T_EOF;
|
||||
c = self->input[self->offset++];
|
||||
}
|
||||
|
||||
if (c == '"')
|
||||
return http_tokenizer_quoted_string (self);
|
||||
|
||||
if (http_tokenizer_is_delimiter (c))
|
||||
{
|
||||
self->delimiter = c;
|
||||
return HTTP_T_DELIMITER;
|
||||
}
|
||||
|
||||
// Simple variable-length tokens
|
||||
enum http_tokenizer_token result;
|
||||
bool (*eater) (int c) = NULL;
|
||||
if (http_tokenizer_is_whitespace (c))
|
||||
{
|
||||
eater = http_tokenizer_is_whitespace;
|
||||
result = HTTP_T_WHITESPACE;
|
||||
}
|
||||
else if (http_tokenizer_is_tchar (c))
|
||||
{
|
||||
eater = http_tokenizer_is_tchar;
|
||||
result = HTTP_T_TOKEN;
|
||||
}
|
||||
else
|
||||
return HTTP_T_ERROR;
|
||||
|
||||
str_append_c (&self->string, c);
|
||||
while (self->offset < self->input_len)
|
||||
{
|
||||
if (!eater (c = self->input[self->offset]))
|
||||
break;
|
||||
|
||||
str_append_c (&self->string, c);
|
||||
self->offset++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static bool
|
||||
http_parse_media_type_parameter
|
||||
(struct http_tokenizer *t, struct str_map *parameters)
|
||||
{
|
||||
bool result = false;
|
||||
char *attribute = NULL;
|
||||
|
||||
if (http_tokenizer_next (t, true) != HTTP_T_TOKEN)
|
||||
goto end;
|
||||
attribute = xstrdup (t->string.str);
|
||||
|
||||
if (http_tokenizer_next (t, false) != HTTP_T_DELIMITER
|
||||
|| t->delimiter != '=')
|
||||
goto end;
|
||||
|
||||
switch (http_tokenizer_next (t, false))
|
||||
{
|
||||
case HTTP_T_TOKEN:
|
||||
case HTTP_T_QUOTED_STRING:
|
||||
str_map_set (parameters, attribute, xstrdup (t->string.str));
|
||||
result = true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
free (attribute);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parser for "Content-Type". @a type and @a subtype may be non-NULL
|
||||
/// even if the function fails. @a parameters should be case-insensitive.
|
||||
static bool
|
||||
http_parse_media_type (const char *media_type,
|
||||
char **type, char **subtype, struct str_map *parameters)
|
||||
{
|
||||
bool result = false;
|
||||
struct http_tokenizer t;
|
||||
http_tokenizer_init (&t, media_type, strlen (media_type));
|
||||
|
||||
if (http_tokenizer_next (&t, true) != HTTP_T_TOKEN)
|
||||
goto end;
|
||||
*type = xstrdup (t.string.str);
|
||||
|
||||
if (http_tokenizer_next (&t, false) != HTTP_T_DELIMITER
|
||||
|| t.delimiter != '/')
|
||||
goto end;
|
||||
|
||||
if (http_tokenizer_next (&t, false) != HTTP_T_TOKEN)
|
||||
goto end;
|
||||
*subtype = xstrdup (t.string.str);
|
||||
|
||||
while (true)
|
||||
switch (http_tokenizer_next (&t, true))
|
||||
{
|
||||
case HTTP_T_DELIMITER:
|
||||
if (t.delimiter != ';')
|
||||
goto end;
|
||||
if (!http_parse_media_type_parameter (&t, parameters))
|
||||
goto end;
|
||||
break;
|
||||
case HTTP_T_EOF:
|
||||
result = true;
|
||||
default:
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
http_tokenizer_free (&t);
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- WebSockets --------------------------------------------------------------
|
||||
|
||||
// COPIED OVER FROM ACID, DON'T CHANGE SEPARATELY
|
||||
|
||||
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
#define SEC_WS_KEY "Sec-WebSocket-Key"
|
||||
#define SEC_WS_ACCEPT "Sec-WebSocket-Accept"
|
||||
#define SEC_WS_PROTOCOL "Sec-WebSocket-Protocol"
|
||||
#define SEC_WS_EXTENSIONS "Sec-WebSocket-Extensions"
|
||||
#define SEC_WS_VERSION "Sec-WebSocket-Version"
|
||||
|
||||
#define WS_MAX_CONTROL_PAYLOAD_LEN 125
|
||||
|
||||
static char *
|
||||
ws_encode_response_key (const char *key)
|
||||
{
|
||||
char *response_key = xstrdup_printf ("%s" WS_GUID, key);
|
||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
||||
SHA1 ((unsigned char *) response_key, strlen (response_key), hash);
|
||||
free (response_key);
|
||||
|
||||
struct str base64;
|
||||
str_init (&base64);
|
||||
base64_encode (hash, sizeof hash, &base64);
|
||||
return str_steal (&base64);
|
||||
}
|
||||
|
||||
enum ws_status
|
||||
{
|
||||
// Named according to the meaning specified in RFC 6455, section 11.2
|
||||
|
||||
WS_STATUS_NORMAL_CLOSURE = 1000,
|
||||
WS_STATUS_GOING_AWAY = 1001,
|
||||
WS_STATUS_PROTOCOL_ERROR = 1002,
|
||||
WS_STATUS_UNSUPPORTED_DATA = 1003,
|
||||
WS_STATUS_INVALID_PAYLOAD_DATA = 1007,
|
||||
WS_STATUS_POLICY_VIOLATION = 1008,
|
||||
WS_STATUS_MESSAGE_TOO_BIG = 1009,
|
||||
WS_STATUS_MANDATORY_EXTENSION = 1010,
|
||||
WS_STATUS_INTERNAL_SERVER_ERROR = 1011,
|
||||
|
||||
// Reserved for internal usage
|
||||
WS_STATUS_NO_STATUS_RECEIVED = 1005,
|
||||
WS_STATUS_ABNORMAL_CLOSURE = 1006,
|
||||
WS_STATUS_TLS_HANDSHAKE = 1015
|
||||
};
|
||||
|
||||
// - - Frame parser - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
enum ws_parser_state
|
||||
{
|
||||
WS_PARSER_FIXED, ///< Parsing fixed length part
|
||||
WS_PARSER_PAYLOAD_LEN_16, ///< Parsing extended payload length
|
||||
WS_PARSER_PAYLOAD_LEN_64, ///< Parsing extended payload length
|
||||
WS_PARSER_MASK, ///< Parsing masking-key
|
||||
WS_PARSER_PAYLOAD ///< Parsing payload
|
||||
};
|
||||
|
||||
enum ws_opcode
|
||||
{
|
||||
// Non-control
|
||||
WS_OPCODE_CONT = 0,
|
||||
WS_OPCODE_TEXT = 1,
|
||||
WS_OPCODE_BINARY = 2,
|
||||
|
||||
// Control
|
||||
WS_OPCODE_CLOSE = 8,
|
||||
WS_OPCODE_PING = 9,
|
||||
WS_OPCODE_PONG = 10
|
||||
};
|
||||
|
||||
static bool
|
||||
ws_is_control_frame (int opcode)
|
||||
{
|
||||
return opcode >= WS_OPCODE_CLOSE;
|
||||
}
|
||||
|
||||
struct ws_parser
|
||||
{
|
||||
struct str input; ///< External input buffer
|
||||
enum ws_parser_state state; ///< Parsing state
|
||||
|
||||
unsigned is_fin : 1; ///< Final frame of a message?
|
||||
unsigned is_masked : 1; ///< Is the frame masked?
|
||||
unsigned reserved_1 : 1; ///< Reserved
|
||||
unsigned reserved_2 : 1; ///< Reserved
|
||||
unsigned reserved_3 : 1; ///< Reserved
|
||||
enum ws_opcode opcode; ///< Opcode
|
||||
uint32_t mask; ///< Frame mask
|
||||
uint64_t payload_len; ///< Payload length
|
||||
|
||||
bool (*on_frame_header) (void *user_data, const struct ws_parser *self);
|
||||
|
||||
/// Callback for when a message is successfully parsed.
|
||||
/// The actual payload is stored in "input", of length "payload_len".
|
||||
bool (*on_frame) (void *user_data, const struct ws_parser *self);
|
||||
|
||||
void *user_data; ///< User data for callbacks
|
||||
};
|
||||
|
||||
static void
|
||||
ws_parser_init (struct ws_parser *self)
|
||||
{
|
||||
memset (self, 0, sizeof *self);
|
||||
str_init (&self->input);
|
||||
}
|
||||
|
||||
static void
|
||||
ws_parser_free (struct ws_parser *self)
|
||||
{
|
||||
str_free (&self->input);
|
||||
}
|
||||
|
||||
static void
|
||||
ws_parser_unmask (char *payload, size_t len, uint32_t mask)
|
||||
{
|
||||
// This could be made faster. For example by reading the mask in
|
||||
// native byte ordering and applying it directly here.
|
||||
|
||||
size_t end = len & ~(size_t) 3;
|
||||
for (size_t i = 0; i < end; i += 4)
|
||||
{
|
||||
payload[i + 3] ^= mask & 0xFF;
|
||||
payload[i + 2] ^= (mask >> 8) & 0xFF;
|
||||
payload[i + 1] ^= (mask >> 16) & 0xFF;
|
||||
payload[i ] ^= (mask >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
switch (len - end)
|
||||
{
|
||||
case 3:
|
||||
payload[end + 2] ^= (mask >> 8) & 0xFF;
|
||||
case 2:
|
||||
payload[end + 1] ^= (mask >> 16) & 0xFF;
|
||||
case 1:
|
||||
payload[end ] ^= (mask >> 24) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
ws_parser_push (struct ws_parser *self, const void *data, size_t len)
|
||||
{
|
||||
bool success = false;
|
||||
str_append_data (&self->input, data, len);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->input.str, self->input.len);
|
||||
|
||||
while (true)
|
||||
switch (self->state)
|
||||
{
|
||||
uint8_t u8;
|
||||
uint16_t u16;
|
||||
|
||||
case WS_PARSER_FIXED:
|
||||
if (unpacker.len - unpacker.offset < 2)
|
||||
goto need_data;
|
||||
|
||||
(void) msg_unpacker_u8 (&unpacker, &u8);
|
||||
self->is_fin = (u8 >> 7) & 1;
|
||||
self->reserved_1 = (u8 >> 6) & 1;
|
||||
self->reserved_2 = (u8 >> 5) & 1;
|
||||
self->reserved_3 = (u8 >> 4) & 1;
|
||||
self->opcode = u8 & 15;
|
||||
|
||||
(void) msg_unpacker_u8 (&unpacker, &u8);
|
||||
self->is_masked = (u8 >> 7) & 1;
|
||||
self->payload_len = u8 & 127;
|
||||
|
||||
if (self->payload_len == 127)
|
||||
self->state = WS_PARSER_PAYLOAD_LEN_64;
|
||||
else if (self->payload_len == 126)
|
||||
self->state = WS_PARSER_PAYLOAD_LEN_16;
|
||||
else
|
||||
self->state = WS_PARSER_MASK;
|
||||
break;
|
||||
|
||||
case WS_PARSER_PAYLOAD_LEN_16:
|
||||
if (!msg_unpacker_u16 (&unpacker, &u16))
|
||||
goto need_data;
|
||||
self->payload_len = u16;
|
||||
|
||||
self->state = WS_PARSER_MASK;
|
||||
break;
|
||||
|
||||
case WS_PARSER_PAYLOAD_LEN_64:
|
||||
if (!msg_unpacker_u64 (&unpacker, &self->payload_len))
|
||||
goto need_data;
|
||||
|
||||
self->state = WS_PARSER_MASK;
|
||||
break;
|
||||
|
||||
case WS_PARSER_MASK:
|
||||
if (!self->is_masked)
|
||||
goto end_of_header;
|
||||
if (!msg_unpacker_u32 (&unpacker, &self->mask))
|
||||
goto need_data;
|
||||
|
||||
end_of_header:
|
||||
self->state = WS_PARSER_PAYLOAD;
|
||||
if (!self->on_frame_header (self->user_data, self))
|
||||
goto fail;
|
||||
break;
|
||||
|
||||
case WS_PARSER_PAYLOAD:
|
||||
// Move the buffer so that payload data is at the front
|
||||
str_remove_slice (&self->input, 0, unpacker.offset);
|
||||
|
||||
// And continue unpacking frames past the payload
|
||||
msg_unpacker_init (&unpacker, self->input.str, self->input.len);
|
||||
unpacker.offset = self->payload_len;
|
||||
|
||||
if (self->input.len < self->payload_len)
|
||||
goto need_data;
|
||||
if (self->is_masked)
|
||||
ws_parser_unmask (self->input.str, self->payload_len, self->mask);
|
||||
if (!self->on_frame (self->user_data, self))
|
||||
goto fail;
|
||||
|
||||
self->state = WS_PARSER_FIXED;
|
||||
break;
|
||||
}
|
||||
|
||||
need_data:
|
||||
success = true;
|
||||
fail:
|
||||
str_remove_slice (&self->input, 0, unpacker.offset);
|
||||
return success;
|
||||
}
|
||||
|
||||
// --- Configuration (application-specific) ------------------------------------
|
||||
|
||||
static struct config_item g_config_table[] =
|
||||
|
2
liberty
2
liberty
@ -1 +1 @@
|
||||
Subproject commit 087645848baec5e59e4296817850bd5dd240cbb2
|
||||
Subproject commit 8c6d18757d2d4135963f3dbab6d2d5ec8c8b6af3
|
Loading…
Reference in New Issue
Block a user