Split out a http-parser wrapper

This commit is contained in:
Přemysl Eric Janouch 2020-10-14 09:40:47 +02:00
parent bb7ffe1da2
commit 2b8f52ac72
Signed by: p
GPG Key ID: A0420B94F92B9493
1 changed files with 152 additions and 111 deletions

View File

@ -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 -------------------------------------------------------
enum ws_handler_state
@ -1443,12 +1566,7 @@ struct ws_context
enum ws_handler_state state; ///< State
char *key; ///< Key for the current handshake
http_parser hp; ///< 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 http_parserpp http; ///< HTTP parser
struct ws_parser parser; ///< Protocol frame parser
bool expecting_continuation; ///< For non-control traffic
@ -1529,112 +1647,44 @@ start:
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
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",
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"))
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);
bool accept_ok = accept && !strcmp (accept, accept_expected);
free (accept_expected);
if (!accept_ok)
FAIL ("missing or invalid " SEC_WS_ACCEPT " header");
const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
if (extensions || protocol)
if (str_map_find (&self->http.headers, SEC_WS_EXTENSIONS)
|| str_map_find (&self->http.headers, SEC_WS_PROTOCOL))
// TODO: actually parse these fields
FAIL ("unexpected WebSocket extension or protocol");
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
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
static const http_parser_settings http_settings =
{
.on_header_field = backend_ws_on_header_field,
.on_header_value = backend_ws_on_header_value,
.on_header_field = http_parserpp_on_header_field,
.on_header_value = http_parserpp_on_header_value,
.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);
if (self->hp.upgrade)
if (self->http.parser.upgrade)
{
struct error *e = NULL;
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;
}
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 (err == HPE_CB_headers_complete)
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
print_error ("WS handshake failed: %s",
http_errno_description (err));
@ -2221,11 +2271,8 @@ backend_ws_connect (struct ws_context *self, struct error **e)
goto fail_2;
}
http_parser_init (&self->hp, HTTP_RESPONSE);
self->hp.data = self;
str_reset (&self->field);
str_reset (&self->value);
str_map_clear (&self->headers);
http_parserpp_free (&self->http);
http_parserpp_init (&self->http, HTTP_RESPONSE);
ws_parser_free (&self->parser);
self->parser = ws_parser_make ();
self->parser.on_frame_header = backend_ws_on_frame_header;
@ -2343,9 +2390,7 @@ backend_ws_destroy (struct backend *backend)
if (self->ssl_ctx)
SSL_CTX_free (self->ssl_ctx);
free (self->key);
str_free (&self->field);
str_free (&self->value);
str_map_free (&self->headers);
http_parserpp_free (&self->http);
ws_parser_free (&self->parser);
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);
self->server_fd = -1;
ev_io_init (&self->read_watcher, NULL, 0, 0);
http_parser_init (&self->hp, HTTP_RESPONSE);
self->field = str_make ();
self->value = str_make ();
self->headers = str_map_make (free);
self->headers.key_xfrm = tolower_ascii_strxfrm;
http_parserpp_init (&self->http, HTTP_RESPONSE);
self->parser = ws_parser_make ();
self->message_data = str_make ();
self->extra_headers = strv_make ();