/* * liberty-proto.c: the ultimate C unlibrary: protocols * * Copyright (c) 2014 - 2015, Přemysl Janouch * All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ // Mostly parsers and various utilities relating to various protocols #ifdef LIBERTY_WANT_PROTO_IRC // --- IRC utilities ----------------------------------------------------------- struct irc_message { struct str_map tags; ///< IRC 3.2 message tags char *prefix; ///< Message prefix char *command; ///< IRC command struct str_vector params; ///< Command parameters }; static char * irc_unescape_message_tag (const char *value) { struct str s; str_init (&s); bool escape = false; for (const char *p = value; *p; p++) { if (escape) { switch (*p) { case ':': str_append_c (&s, ';'); break; case 's': str_append_c (&s, ' '); break; case 'r': str_append_c (&s, '\r'); break; case 'n': str_append_c (&s, '\n'); break; default: str_append_c (&s, *p); } escape = false; } else if (*p == '\\') escape = true; else str_append_c (&s, *p); } return str_steal (&s); } static void irc_parse_message_tags (const char *tags, struct str_map *out) { struct str_vector v; str_vector_init (&v); cstr_split (tags, ";", true, &v); for (size_t i = 0; i < v.len; i++) { char *key = v.vector[i], *equal_sign = strchr (key, '='); if (equal_sign) { *equal_sign = '\0'; str_map_set (out, key, irc_unescape_message_tag (equal_sign + 1)); } else str_map_set (out, key, xstrdup ("")); } str_vector_free (&v); } static void irc_parse_message (struct irc_message *msg, const char *line) { str_map_init (&msg->tags); msg->tags.free = free; msg->prefix = NULL; msg->command = NULL; str_vector_init (&msg->params); // IRC 3.2 message tags if (*line == '@') { size_t tags_len = strcspn (++line, " "); char *tags = xstrndup (line, tags_len); irc_parse_message_tags (tags, &msg->tags); free (tags); line += tags_len; while (*line == ' ') line++; } // Prefix if (*line == ':') { size_t prefix_len = strcspn (++line, " "); msg->prefix = xstrndup (line, prefix_len); line += prefix_len; } // Command name { while (*line == ' ') line++; size_t cmd_len = strcspn (line, " "); msg->command = xstrndup (line, cmd_len); line += cmd_len; } // Arguments while (true) { while (*line == ' ') line++; if (*line == ':') { str_vector_add (&msg->params, ++line); break; } size_t param_len = strcspn (line, " "); if (!param_len) break; str_vector_add_owned (&msg->params, xstrndup (line, param_len)); line += param_len; } } static void irc_free_message (struct irc_message *msg) { str_map_free (&msg->tags); free (msg->prefix); free (msg->command); str_vector_free (&msg->params); } static void irc_process_buffer (struct str *buf, void (*callback)(const struct irc_message *, const char *, void *), void *user_data) { char *start = buf->str, *end = start + buf->len; for (char *p = start; p + 1 < end; p++) { // Split the input on newlines if (p[0] != '\r' || p[1] != '\n') continue; *p = 0; struct irc_message msg; irc_parse_message (&msg, start); callback (&msg, start, user_data); irc_free_message (&msg); start = p + 2; } // XXX: we might want to just advance some kind of an offset to avoid // moving memory around unnecessarily. str_remove_slice (buf, 0, start - buf->str); } static int irc_tolower (int c) { if (c == '[') return '{'; if (c == ']') return '}'; if (c == '\\') return '|'; if (c == '~') return '^'; return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } static int irc_tolower_strict (int c) { if (c == '[') return '{'; if (c == ']') return '}'; if (c == '\\') return '|'; return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } TRIVIAL_STRXFRM (irc_strxfrm, irc_tolower) TRIVIAL_STRXFRM (irc_strxfrm_strict, irc_tolower_strict) static int irc_strcmp (const char *a, const char *b) { int x; while (*a || *b) if ((x = irc_tolower (*a++) - irc_tolower (*b++))) return x; return 0; } static int irc_fnmatch (const char *pattern, const char *string) { size_t pattern_size = strlen (pattern) + 1; size_t string_size = strlen (string) + 1; char x_pattern[pattern_size], x_string[string_size]; irc_strxfrm (x_pattern, pattern, pattern_size); irc_strxfrm (x_string, string, string_size); return fnmatch (x_pattern, x_string, 0); } #endif #ifdef LIBERTY_WANT_PROTO_HTTP // --- HTTP parsing ------------------------------------------------------------ // 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: if (parameters) 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 end up non-NULL /// even if the function fails. @a parameters should be case-insensitive, /// and may be NULL for validation only. 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; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct http_protocol { LIST_HEADER (struct http_protocol) char *name; ///< The protocol to upgrade to char *version; ///< Version of the protocol, if any }; static void http_protocol_destroy (struct http_protocol *self) { free (self->name); free (self->version); free (self); } static bool http_parse_upgrade (const char *upgrade, struct http_protocol **out) { // HTTP grammar makes this more complicated than it should be bool result = false; struct http_protocol *list = NULL; struct http_protocol *tail = NULL; struct http_tokenizer t; http_tokenizer_init (&t, upgrade, strlen (upgrade)); enum { STATE_PROTOCOL_NAME, STATE_SLASH, STATE_PROTOCOL_VERSION, STATE_EXPECT_COMMA } state = STATE_PROTOCOL_NAME; struct http_protocol *proto = NULL; while (true) switch (state) { case STATE_PROTOCOL_NAME: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter != ',') goto end; case HTTP_T_WHITESPACE: break; case HTTP_T_TOKEN: proto = xcalloc (1, sizeof *proto); proto->name = xstrdup (t.string.str); LIST_APPEND_WITH_TAIL (list, tail, proto); state = STATE_SLASH; break; case HTTP_T_EOF: result = true; default: goto end; } break; case STATE_SLASH: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter == '/') state = STATE_PROTOCOL_VERSION; else if (t.delimiter == ',') state = STATE_PROTOCOL_NAME; else goto end; break; case HTTP_T_WHITESPACE: state = STATE_EXPECT_COMMA; break; case HTTP_T_EOF: result = true; default: goto end; } break; case STATE_PROTOCOL_VERSION: switch (http_tokenizer_next (&t, false)) { case HTTP_T_TOKEN: proto->version = xstrdup (t.string.str); state = STATE_EXPECT_COMMA; break; default: goto end; } break; case STATE_EXPECT_COMMA: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter != ',') goto end; state = STATE_PROTOCOL_NAME; case HTTP_T_WHITESPACE: break; case HTTP_T_EOF: result = true; default: goto end; } } end: if (result) *out = list; else LIST_FOR_EACH (struct http_protocol, iter, list) http_protocol_destroy (iter); http_tokenizer_free (&t); return result; } #endif #ifdef LIBERTY_WANT_PROTO_SCGI // --- SCGI -------------------------------------------------------------------- enum scgi_parser_state { SCGI_READING_NETSTRING_LENGTH, ///< The length of the header netstring SCGI_READING_NAME, ///< Header name SCGI_READING_VALUE, ///< Header value SCGI_READING_CONTENT ///< Incoming data }; struct scgi_parser { enum scgi_parser_state state; ///< Parsing state struct str input; ///< Input buffer struct str_map headers; ///< Headers parsed size_t headers_len; ///< Length of the netstring contents struct str name; ///< Header name so far struct str value; ///< Header value so far /// Finished parsing request headers. /// Return false to abort further processing of input. bool (*on_headers_read) (void *user_data); /// Content available; len == 0 means end of file. /// Return false to abort further processing of input. bool (*on_content) (void *user_data, const void *data, size_t len); void *user_data; ///< User data passed to callbacks }; static void scgi_parser_init (struct scgi_parser *self) { memset (self, 0, sizeof *self); str_init (&self->input); str_map_init (&self->headers); self->headers.free = free; str_init (&self->name); str_init (&self->value); } static void scgi_parser_free (struct scgi_parser *self) { str_free (&self->input); str_map_free (&self->headers); str_free (&self->name); str_free (&self->value); } static bool scgi_parser_push (struct scgi_parser *self, const void *data, size_t len, struct error **e) { if (!len) { if (self->state != SCGI_READING_CONTENT) return error_set (e, "premature EOF"); // Indicate end of file return self->on_content (self->user_data, NULL, 0); } // Notice that this madness is significantly harder to parse than FastCGI; // this procedure could also be optimized significantly str_append_data (&self->input, data, len); bool keep_running = true; while (keep_running) switch (self->state) { case SCGI_READING_NETSTRING_LENGTH: { if (self->input.len < 1) return true; char digit = *self->input.str; // XXX: this allows for omitting the netstring length altogether if (digit == ':') { self->state = SCGI_READING_NAME; break; } if (digit < '0' || digit >= '9') return error_set (e, "invalid header netstring"); size_t new_len = self->headers_len * 10 + (digit - '0'); if (new_len < self->headers_len) return error_set (e, "header netstring is too long"); self->headers_len = new_len; str_remove_slice (&self->input, 0, 1); break; } case SCGI_READING_NAME: { if (self->input.len < 1) return true; char c = *self->input.str; if (!self->headers_len) { // The netstring is ending but we haven't finished parsing it, // or the netstring doesn't end with a comma if (self->name.len || c != ',') return error_set (e, "invalid header netstring"); self->state = SCGI_READING_CONTENT; keep_running = self->on_headers_read (self->user_data); } else if (c != '\0') str_append_c (&self->name, c); else self->state = SCGI_READING_VALUE; str_remove_slice (&self->input, 0, 1); break; } case SCGI_READING_VALUE: { if (self->input.len < 1) return true; char c = *self->input.str; if (!self->headers_len) { // The netstring is ending but we haven't finished parsing it return error_set (e, "invalid header netstring"); } else if (c != '\0') str_append_c (&self->value, c); else { // We've got a name-value pair, let's put it in the map str_map_set (&self->headers, self->name.str, str_steal (&self->value)); str_reset (&self->name); str_init (&self->value); self->state = SCGI_READING_NAME; } str_remove_slice (&self->input, 0, 1); break; } case SCGI_READING_CONTENT: keep_running = self->on_content (self->user_data, self->input.str, self->input.len); str_remove_slice (&self->input, 0, self->input.len); return keep_running; } return false; } #endif #ifdef LIBERTY_WANT_PROTO_FASTCGI // --- FastCGI ----------------------------------------------------------------- // Constants from the FastCGI specification document #define FCGI_HEADER_LEN 8 #define FCGI_VERSION_1 1 #define FCGI_NULL_REQUEST_ID 0 #define FCGI_KEEP_CONN 1 enum fcgi_type { FCGI_BEGIN_REQUEST = 1, FCGI_ABORT_REQUEST = 2, FCGI_END_REQUEST = 3, FCGI_PARAMS = 4, FCGI_STDIN = 5, FCGI_STDOUT = 6, FCGI_STDERR = 7, FCGI_DATA = 8, FCGI_GET_VALUES = 9, FCGI_GET_VALUES_RESULT = 10, FCGI_UNKNOWN_TYPE = 11, FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE }; enum fcgi_role { FCGI_RESPONDER = 1, FCGI_AUTHORIZER = 2, FCGI_FILTER = 3 }; enum fcgi_protocol_status { FCGI_REQUEST_COMPLETE = 0, FCGI_CANT_MPX_CONN = 1, FCGI_OVERLOADED = 2, FCGI_UNKNOWN_ROLE = 3 }; #define FCGI_MAX_CONNS "FCGI_MAX_CONNS" #define FCGI_MAX_REQS "FCGI_MAX_REQS" #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" // - - Message stream parser - - - - - - - - - - - - - - - - - - - - - - - - - - struct fcgi_parser; typedef void (*fcgi_message_fn) (const struct fcgi_parser *parser, void *user_data); enum fcgi_parser_state { FCGI_READING_HEADER, ///< Reading the fixed header portion FCGI_READING_CONTENT, ///< Reading the message content FCGI_READING_PADDING ///< Reading the padding }; struct fcgi_parser { enum fcgi_parser_state state; ///< Parsing state struct str input; ///< Input buffer // The next block of fields is considered public: uint8_t version; ///< FastCGI protocol version uint8_t type; ///< FastCGI record type uint16_t request_id; ///< FastCGI request ID struct str content; ///< Message data uint16_t content_length; ///< Message content length uint8_t padding_length; ///< Message padding length fcgi_message_fn on_message; ///< Callback on message void *user_data; ///< User data }; static void fcgi_parser_init (struct fcgi_parser *self) { memset (self, 0, sizeof *self); str_init (&self->input); str_init (&self->content); } static void fcgi_parser_free (struct fcgi_parser *self) { str_free (&self->input); str_free (&self->content); } static void fcgi_parser_unpack_header (struct fcgi_parser *self) { struct msg_unpacker unpacker; msg_unpacker_init (&unpacker, self->input.str, self->input.len); bool success = true; uint8_t reserved; success &= msg_unpacker_u8 (&unpacker, &self->version); success &= msg_unpacker_u8 (&unpacker, &self->type); success &= msg_unpacker_u16 (&unpacker, &self->request_id); success &= msg_unpacker_u16 (&unpacker, &self->content_length); success &= msg_unpacker_u8 (&unpacker, &self->padding_length); success &= msg_unpacker_u8 (&unpacker, &reserved); hard_assert (success); str_remove_slice (&self->input, 0, unpacker.offset); } static void fcgi_parser_push (struct fcgi_parser *self, const void *data, size_t len) { // This could be made considerably faster for high-throughput applications // if we use a circular buffer instead of constantly calling memmove() str_append_data (&self->input, data, len); while (true) switch (self->state) { case FCGI_READING_HEADER: if (self->input.len < FCGI_HEADER_LEN) return; fcgi_parser_unpack_header (self); self->state = FCGI_READING_CONTENT; break; case FCGI_READING_CONTENT: if (self->input.len < self->content_length) return; // Move an appropriate part of the input buffer to the content buffer str_reset (&self->content); str_append_data (&self->content, self->input.str, self->content_length); str_remove_slice (&self->input, 0, self->content_length); self->state = FCGI_READING_PADDING; break; case FCGI_READING_PADDING: if (self->input.len < self->padding_length) return; // Call the callback to further process the message self->on_message (self, self->user_data); // Remove the padding from the input buffer str_remove_slice (&self->input, 0, self->padding_length); self->state = FCGI_READING_HEADER; break; } } // - - Name-value pair parser - - - - - - - - - - - - - - - - - - - - - - - - - enum fcgi_nv_parser_state { FCGI_NV_PARSER_NAME_LEN, ///< The first name length octet FCGI_NV_PARSER_NAME_LEN_FULL, ///< Remaining name length octets FCGI_NV_PARSER_VALUE_LEN, ///< The first value length octet FCGI_NV_PARSER_VALUE_LEN_FULL, ///< Remaining value length octets FCGI_NV_PARSER_NAME, ///< Reading the name FCGI_NV_PARSER_VALUE ///< Reading the value }; struct fcgi_nv_parser { struct str_map *output; ///< Where the pairs will be stored enum fcgi_nv_parser_state state; ///< Parsing state struct str input; ///< Input buffer uint32_t name_len; ///< Length of the name uint32_t value_len; ///< Length of the value char *name; ///< The current name, 0-terminated char *value; ///< The current value, 0-terminated }; static void fcgi_nv_parser_init (struct fcgi_nv_parser *self) { memset (self, 0, sizeof *self); str_init (&self->input); } static void fcgi_nv_parser_free (struct fcgi_nv_parser *self) { str_free (&self->input); free (self->name); free (self->value); } static void fcgi_nv_parser_push (struct fcgi_nv_parser *self, const void *data, size_t len) { // This could be optimized significantly; I'm not even trying str_append_data (&self->input, data, len); while (true) { struct msg_unpacker unpacker; msg_unpacker_init (&unpacker, self->input.str, self->input.len); switch (self->state) { uint8_t len; uint32_t len_full; case FCGI_NV_PARSER_NAME_LEN: if (!msg_unpacker_u8 (&unpacker, &len)) return; if (len >> 7) self->state = FCGI_NV_PARSER_NAME_LEN_FULL; else { self->name_len = len; str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_VALUE_LEN; } break; case FCGI_NV_PARSER_NAME_LEN_FULL: if (!msg_unpacker_u32 (&unpacker, &len_full)) return; self->name_len = len_full & ~(1U << 31); str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_VALUE_LEN; break; case FCGI_NV_PARSER_VALUE_LEN: if (!msg_unpacker_u8 (&unpacker, &len)) return; if (len >> 7) self->state = FCGI_NV_PARSER_VALUE_LEN_FULL; else { self->value_len = len; str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_NAME; } break; case FCGI_NV_PARSER_VALUE_LEN_FULL: if (!msg_unpacker_u32 (&unpacker, &len_full)) return; self->value_len = len_full & ~(1U << 31); str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_NAME; break; case FCGI_NV_PARSER_NAME: if (self->input.len < self->name_len) return; self->name = xmalloc (self->name_len + 1); self->name[self->name_len] = '\0'; memcpy (self->name, self->input.str, self->name_len); str_remove_slice (&self->input, 0, self->name_len); self->state = FCGI_NV_PARSER_VALUE; break; case FCGI_NV_PARSER_VALUE: if (self->input.len < self->value_len) return; self->value = xmalloc (self->value_len + 1); self->value[self->value_len] = '\0'; memcpy (self->value, self->input.str, self->value_len); str_remove_slice (&self->input, 0, self->value_len); self->state = FCGI_NV_PARSER_NAME_LEN; // The map takes ownership of the value str_map_set (self->output, self->name, self->value); free (self->name); self->name = NULL; self->value = NULL; break; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void fcgi_nv_convert_len (size_t len, struct str *output) { if (len < 0x80) str_pack_u8 (output, len); else { len |= (uint32_t) 1 << 31; str_pack_u32 (output, len); } } static void fcgi_nv_convert (struct str_map *map, struct str *output) { struct str_map_iter iter; str_map_iter_init (&iter, map); while (str_map_iter_next (&iter)) { const char *name = iter.link->key; const char *value = iter.link->data; size_t name_len = iter.link->key_length; size_t value_len = strlen (value); fcgi_nv_convert_len (name_len, output); fcgi_nv_convert_len (value_len, output); str_append_data (output, name, name_len); str_append_data (output, value, value_len); } } #endif #ifdef LIBERTY_WANT_PROTO_WS // --- WebSockets -------------------------------------------------------------- #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.7 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, uint64_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. uint64_t end = len & ~(uint64_t) 3; for (uint64_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; } #endif