diff --git a/json-rpc-shell.c b/json-rpc-shell.c index 35f41ca..a6d319c 100644 --- a/json-rpc-shell.c +++ b/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 #include -// --- 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[] = diff --git a/liberty b/liberty index 0876458..8c6d187 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 087645848baec5e59e4296817850bd5dd240cbb2 +Subproject commit 8c6d18757d2d4135963f3dbab6d2d5ec8c8b6af3