Split out a http-parser wrapper
This commit is contained in:
parent
bb7ffe1da2
commit
2b8f52ac72
263
json-rpc-shell.c
263
json-rpc-shell.c
|
@ -1400,6 +1400,129 @@ on_config_attribute_change (struct config_item *item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- http-parser wrapper -----------------------------------------------------
|
||||||
|
|
||||||
|
struct http_parserpp
|
||||||
|
{
|
||||||
|
http_parser parser; ///< HTTP parser
|
||||||
|
bool have_header_value; ///< Parsing header value or field?
|
||||||
|
struct str field; ///< Field part buffer
|
||||||
|
struct str value; ///< Value part buffer
|
||||||
|
struct str_map headers; ///< HTTP headers
|
||||||
|
struct str message; ///< Message data
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
http_parserpp_init (struct http_parserpp *self, enum http_parser_type type)
|
||||||
|
{
|
||||||
|
http_parser_init (&self->parser, type);
|
||||||
|
self->parser.data = self;
|
||||||
|
|
||||||
|
self->field = str_make ();
|
||||||
|
self->value = str_make ();
|
||||||
|
self->headers = str_map_make (free);
|
||||||
|
self->headers.key_xfrm = tolower_ascii_strxfrm;
|
||||||
|
self->message = str_make ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
http_parserpp_free (struct http_parserpp *self)
|
||||||
|
{
|
||||||
|
str_free (&self->field);
|
||||||
|
str_free (&self->value);
|
||||||
|
str_map_free (&self->headers);
|
||||||
|
str_free (&self->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
http_parserpp_header_field_is_a_list (const char *name)
|
||||||
|
{
|
||||||
|
// This must contain all header fields we use for anything
|
||||||
|
static const char *concatenable[] =
|
||||||
|
{ SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Upgrade" };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
|
||||||
|
if (!strcasecmp_ascii (name, concatenable[i]))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
http_parserpp_on_header_read (struct http_parserpp *self)
|
||||||
|
{
|
||||||
|
// The HTTP parser unfolds values and removes preceding whitespace, but
|
||||||
|
// otherwise doesn't touch the values or the following whitespace.
|
||||||
|
|
||||||
|
// RFC 7230 states that trailing whitespace is not part of a field value
|
||||||
|
char *value = self->field.str;
|
||||||
|
size_t len = self->field.len;
|
||||||
|
while (len--)
|
||||||
|
if (value[len] == '\t' || value[len] == ' ')
|
||||||
|
value[len] = '\0';
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
self->field.len = len;
|
||||||
|
|
||||||
|
const char *field = self->field.str;
|
||||||
|
const char *current = str_map_find (&self->headers, field);
|
||||||
|
if (http_parserpp_header_field_is_a_list (field) && current)
|
||||||
|
str_map_set (&self->headers, field,
|
||||||
|
xstrdup_printf ("%s, %s", current, self->value.str));
|
||||||
|
else
|
||||||
|
// If the field cannot be concatenated, just overwrite the last value.
|
||||||
|
// Maybe we should issue a warning or something.
|
||||||
|
str_map_set (&self->headers, field, xstrdup (self->value.str));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parserpp_on_header_field (http_parser *parser, const char *at, size_t len)
|
||||||
|
{
|
||||||
|
struct http_parserpp *self = parser->data;
|
||||||
|
if (self->have_header_value)
|
||||||
|
{
|
||||||
|
http_parserpp_on_header_read (self);
|
||||||
|
str_reset (&self->field);
|
||||||
|
str_reset (&self->value);
|
||||||
|
}
|
||||||
|
str_append_data (&self->field, at, len);
|
||||||
|
self->have_header_value = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parserpp_on_header_value (http_parser *parser, const char *at, size_t len)
|
||||||
|
{
|
||||||
|
struct http_parserpp *self = parser->data;
|
||||||
|
str_append_data (&self->value, at, len);
|
||||||
|
self->have_header_value = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parserpp_on_headers_complete (http_parser *parser)
|
||||||
|
{
|
||||||
|
struct http_parserpp *self = parser->data;
|
||||||
|
if (self->have_header_value)
|
||||||
|
http_parserpp_on_header_read (self);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parserpp_on_message_begin (http_parser *parser)
|
||||||
|
{
|
||||||
|
struct http_parserpp *self = parser->data;
|
||||||
|
str_reset (&self->message);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parserpp_on_body (http_parser *parser, const char *at, size_t len)
|
||||||
|
{
|
||||||
|
struct http_parserpp *self = parser->data;
|
||||||
|
str_append_data (&self->message, at, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// --- WebSocket backend -------------------------------------------------------
|
// --- WebSocket backend -------------------------------------------------------
|
||||||
|
|
||||||
enum ws_handler_state
|
enum ws_handler_state
|
||||||
|
@ -1443,12 +1566,7 @@ struct ws_context
|
||||||
enum ws_handler_state state; ///< State
|
enum ws_handler_state state; ///< State
|
||||||
char *key; ///< Key for the current handshake
|
char *key; ///< Key for the current handshake
|
||||||
|
|
||||||
http_parser hp; ///< HTTP parser
|
struct http_parserpp http; ///< HTTP parser
|
||||||
bool have_header_value; ///< Parsing header value or field?
|
|
||||||
struct str field; ///< Field part buffer
|
|
||||||
struct str value; ///< Value part buffer
|
|
||||||
struct str_map headers; ///< HTTP Headers
|
|
||||||
|
|
||||||
struct ws_parser parser; ///< Protocol frame parser
|
struct ws_parser parser; ///< Protocol frame parser
|
||||||
bool expecting_continuation; ///< For non-control traffic
|
bool expecting_continuation; ///< For non-control traffic
|
||||||
|
|
||||||
|
@ -1529,112 +1647,44 @@ start:
|
||||||
return WS_READ_ERROR;
|
return WS_READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
backend_ws_header_field_is_a_list (const char *name)
|
|
||||||
{
|
|
||||||
// This must contain all header fields we use for anything
|
|
||||||
static const char *concatenable[] =
|
|
||||||
{ SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Upgrade" };
|
|
||||||
|
|
||||||
for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
|
|
||||||
if (!strcasecmp_ascii (name, concatenable[i]))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
backend_ws_on_header_read (struct ws_context *self)
|
|
||||||
{
|
|
||||||
// The HTTP parser unfolds values and removes preceding whitespace, but
|
|
||||||
// otherwise doesn't touch the values or the following whitespace.
|
|
||||||
|
|
||||||
// RFC 7230 states that trailing whitespace is not part of a field value
|
|
||||||
char *value = self->field.str;
|
|
||||||
size_t len = self->field.len;
|
|
||||||
while (len--)
|
|
||||||
if (value[len] == '\t' || value[len] == ' ')
|
|
||||||
value[len] = '\0';
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
self->field.len = len;
|
|
||||||
|
|
||||||
const char *field = self->field.str;
|
|
||||||
const char *current = str_map_find (&self->headers, field);
|
|
||||||
if (backend_ws_header_field_is_a_list (field) && current)
|
|
||||||
str_map_set (&self->headers, field,
|
|
||||||
xstrdup_printf ("%s, %s", current, self->value.str));
|
|
||||||
else
|
|
||||||
// If the field cannot be concatenated, just overwrite the last value.
|
|
||||||
// Maybe we should issue a warning or something.
|
|
||||||
str_map_set (&self->headers, field, xstrdup (self->value.str));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
backend_ws_on_header_field (http_parser *parser, const char *at, size_t len)
|
|
||||||
{
|
|
||||||
struct ws_context *self = parser->data;
|
|
||||||
if (self->have_header_value)
|
|
||||||
{
|
|
||||||
backend_ws_on_header_read (self);
|
|
||||||
str_reset (&self->field);
|
|
||||||
str_reset (&self->value);
|
|
||||||
}
|
|
||||||
str_append_data (&self->field, at, len);
|
|
||||||
self->have_header_value = false;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
backend_ws_on_header_value (http_parser *parser, const char *at, size_t len)
|
|
||||||
{
|
|
||||||
struct ws_context *self = parser->data;
|
|
||||||
str_append_data (&self->value, at, len);
|
|
||||||
self->have_header_value = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
backend_ws_on_headers_complete (http_parser *parser)
|
|
||||||
{
|
|
||||||
struct ws_context *self = parser->data;
|
|
||||||
if (self->have_header_value)
|
|
||||||
backend_ws_on_header_read (self);
|
|
||||||
|
|
||||||
// We require a protocol upgrade. 1 is for "skip body", 2 is the same
|
|
||||||
// + "stop processing", return another number to indicate a problem here.
|
|
||||||
if (!parser->upgrade)
|
|
||||||
return 3;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
backend_ws_finish_handshake (struct ws_context *self, struct error **e)
|
backend_ws_finish_handshake (struct ws_context *self, struct error **e)
|
||||||
{
|
{
|
||||||
if (self->hp.http_major != 1 || self->hp.http_minor < 1)
|
if (self->http.parser.http_major != 1 || self->http.parser.http_minor < 1)
|
||||||
FAIL ("incompatible HTTP version: %d.%d",
|
FAIL ("incompatible HTTP version: %d.%d",
|
||||||
self->hp.http_major, self->hp.http_minor);
|
self->http.parser.http_major, self->http.parser.http_minor);
|
||||||
|
|
||||||
const char *upgrade = str_map_find (&self->headers, "Upgrade");
|
const char *upgrade = str_map_find (&self->http.headers, "Upgrade");
|
||||||
if (!upgrade || strcasecmp_ascii (upgrade, "websocket"))
|
if (!upgrade || strcasecmp_ascii (upgrade, "websocket"))
|
||||||
FAIL ("cannot upgrade connection to WebSocket");
|
FAIL ("cannot upgrade connection to WebSocket");
|
||||||
|
|
||||||
const char *accept = str_map_find (&self->headers, SEC_WS_ACCEPT);
|
const char *accept = str_map_find (&self->http.headers, SEC_WS_ACCEPT);
|
||||||
char *accept_expected = ws_encode_response_key (self->key);
|
char *accept_expected = ws_encode_response_key (self->key);
|
||||||
bool accept_ok = accept && !strcmp (accept, accept_expected);
|
bool accept_ok = accept && !strcmp (accept, accept_expected);
|
||||||
free (accept_expected);
|
free (accept_expected);
|
||||||
if (!accept_ok)
|
if (!accept_ok)
|
||||||
FAIL ("missing or invalid " SEC_WS_ACCEPT " header");
|
FAIL ("missing or invalid " SEC_WS_ACCEPT " header");
|
||||||
|
|
||||||
const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
|
if (str_map_find (&self->http.headers, SEC_WS_EXTENSIONS)
|
||||||
const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
|
|| str_map_find (&self->http.headers, SEC_WS_PROTOCOL))
|
||||||
if (extensions || protocol)
|
|
||||||
// TODO: actually parse these fields
|
// TODO: actually parse these fields
|
||||||
FAIL ("unexpected WebSocket extension or protocol");
|
FAIL ("unexpected WebSocket extension or protocol");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
backend_ws_on_headers_complete (http_parser *parser)
|
||||||
|
{
|
||||||
|
// We require a protocol upgrade. 1 is for "skip body", 2 is the same
|
||||||
|
// + "stop processing", return another number to indicate a problem here.
|
||||||
|
http_parserpp_on_headers_complete (parser);
|
||||||
|
if (!parser->upgrade)
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
backend_ws_on_data (struct ws_context *self, const void *data, size_t len)
|
backend_ws_on_data (struct ws_context *self, const void *data, size_t len)
|
||||||
{
|
{
|
||||||
|
@ -1644,15 +1694,15 @@ backend_ws_on_data (struct ws_context *self, const void *data, size_t len)
|
||||||
// The handshake hasn't been done yet, process HTTP headers
|
// The handshake hasn't been done yet, process HTTP headers
|
||||||
static const http_parser_settings http_settings =
|
static const http_parser_settings http_settings =
|
||||||
{
|
{
|
||||||
.on_header_field = backend_ws_on_header_field,
|
.on_header_field = http_parserpp_on_header_field,
|
||||||
.on_header_value = backend_ws_on_header_value,
|
.on_header_value = http_parserpp_on_header_value,
|
||||||
.on_headers_complete = backend_ws_on_headers_complete,
|
.on_headers_complete = backend_ws_on_headers_complete,
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t n_parsed = http_parser_execute (&self->hp,
|
size_t n_parsed = http_parser_execute (&self->http.parser,
|
||||||
&http_settings, data, len);
|
&http_settings, data, len);
|
||||||
|
|
||||||
if (self->hp.upgrade)
|
if (self->http.parser.upgrade)
|
||||||
{
|
{
|
||||||
struct error *e = NULL;
|
struct error *e = NULL;
|
||||||
if (!backend_ws_finish_handshake (self, &e))
|
if (!backend_ws_finish_handshake (self, &e))
|
||||||
|
@ -1674,12 +1724,12 @@ backend_ws_on_data (struct ws_context *self, const void *data, size_t len)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum http_errno err = HTTP_PARSER_ERRNO (&self->hp);
|
enum http_errno err = HTTP_PARSER_ERRNO (&self->http.parser);
|
||||||
if (n_parsed != len || err != HPE_OK)
|
if (n_parsed != len || err != HPE_OK)
|
||||||
{
|
{
|
||||||
if (err == HPE_CB_headers_complete)
|
if (err == HPE_CB_headers_complete)
|
||||||
print_error ("WS handshake failed: %s (status code %d)",
|
print_error ("WS handshake failed: %s (status code %d)",
|
||||||
"connection cannot be upgraded", self->hp.status_code);
|
"connection cannot be upgraded", self->http.parser.status_code);
|
||||||
else
|
else
|
||||||
print_error ("WS handshake failed: %s",
|
print_error ("WS handshake failed: %s",
|
||||||
http_errno_description (err));
|
http_errno_description (err));
|
||||||
|
@ -2221,11 +2271,8 @@ backend_ws_connect (struct ws_context *self, struct error **e)
|
||||||
goto fail_2;
|
goto fail_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_parser_init (&self->hp, HTTP_RESPONSE);
|
http_parserpp_free (&self->http);
|
||||||
self->hp.data = self;
|
http_parserpp_init (&self->http, HTTP_RESPONSE);
|
||||||
str_reset (&self->field);
|
|
||||||
str_reset (&self->value);
|
|
||||||
str_map_clear (&self->headers);
|
|
||||||
ws_parser_free (&self->parser);
|
ws_parser_free (&self->parser);
|
||||||
self->parser = ws_parser_make ();
|
self->parser = ws_parser_make ();
|
||||||
self->parser.on_frame_header = backend_ws_on_frame_header;
|
self->parser.on_frame_header = backend_ws_on_frame_header;
|
||||||
|
@ -2343,9 +2390,7 @@ backend_ws_destroy (struct backend *backend)
|
||||||
if (self->ssl_ctx)
|
if (self->ssl_ctx)
|
||||||
SSL_CTX_free (self->ssl_ctx);
|
SSL_CTX_free (self->ssl_ctx);
|
||||||
free (self->key);
|
free (self->key);
|
||||||
str_free (&self->field);
|
http_parserpp_free (&self->http);
|
||||||
str_free (&self->value);
|
|
||||||
str_map_free (&self->headers);
|
|
||||||
ws_parser_free (&self->parser);
|
ws_parser_free (&self->parser);
|
||||||
str_free (&self->message_data);
|
str_free (&self->message_data);
|
||||||
}
|
}
|
||||||
|
@ -2371,11 +2416,7 @@ backend_ws_new (struct app_context *ctx,
|
||||||
ev_timer_init (&self->timeout_watcher, NULL, 0, 0);
|
ev_timer_init (&self->timeout_watcher, NULL, 0, 0);
|
||||||
self->server_fd = -1;
|
self->server_fd = -1;
|
||||||
ev_io_init (&self->read_watcher, NULL, 0, 0);
|
ev_io_init (&self->read_watcher, NULL, 0, 0);
|
||||||
http_parser_init (&self->hp, HTTP_RESPONSE);
|
http_parserpp_init (&self->http, HTTP_RESPONSE);
|
||||||
self->field = str_make ();
|
|
||||||
self->value = str_make ();
|
|
||||||
self->headers = str_map_make (free);
|
|
||||||
self->headers.key_xfrm = tolower_ascii_strxfrm;
|
|
||||||
self->parser = ws_parser_make ();
|
self->parser = ws_parser_make ();
|
||||||
self->message_data = str_make ();
|
self->message_data = str_make ();
|
||||||
self->extra_headers = strv_make ();
|
self->extra_headers = strv_make ();
|
||||||
|
|
Loading…
Reference in New Issue