blob: 36cb6e2a2a851456d4e64d42d438122eb47ca236 [file] [log] [blame]
/*****************************************************************************\
* libhttp_parser.c - libhttp_parser handler
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <http_parser.h>
#include <stdint.h>
#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/slurm_xlator.h"
#include "src/common/http.h"
#include "src/common/log.h"
#include "src/common/pack.h"
#include "src/common/read_config.h"
#include "src/common/xstring.h"
#include "src/common/xmalloc.h"
#include "src/interfaces/http_parser.h"
/* Required Slurm plugin symbols: */
const char plugin_name[] = "Slurm http_parser libhttp_parser plugin";
const char plugin_type[] = HTTP_PARSER_PREFIX LIBHTTP_PARSER_PLUGIN;
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
#define UNIX_PREFIX "unix:"
#define UNIX_PREFIX_BYTES strlen("unix:")
#define LOG_PARSE(state, fmt, ...) \
_log_parse(state, NULL, 0, __func__, fmt, ##__VA_ARGS__)
#define LOG_PARSE_AT(state, at, bytes, fmt, ...) \
_log_parse(state, (at), (bytes), __func__, fmt, ##__VA_ARGS__)
#define PARSE_ERROR(error_number, state) \
_on_parse_error(error_number, state, NULL, 0, __func__)
#define PARSE_ERROR_AT(error_number, state, at, bytes) \
_on_parse_error(error_number, state, (at), (bytes), __func__)
#define LOG_URL_PARSE(name, buffer, fmt, ...) \
_log_url_parse(name, buffer, __func__, fmt, ##__VA_ARGS__)
#define URL_PARSE_ERROR(error_number, name, buffer) \
_on_url_parse_error(error_number, name, buffer, __func__)
#define STATE_MAGIC 0xDFBFBEA0
typedef struct http_parser_state_s {
int magic; /* STATE_MAGIC */
/* Name of connection for logging */
const char *name;
/* callback to call on events */
const http_parser_callbacks_t *callbacks;
/* pointer to hand to callbacks */
void *callback_arg;
/* libhttp_parser context */
http_parser parser;
/* Requested URL */
url_t url;
/* state tracking of last header received */
char *last_header;
/* Bytes already parsed (cumulative) */
ssize_t total_bytes;
/* Current buffer getting parsed or NULL for EOF */
const buf_t *buffer;
/* Status code to return from http_parser_p_parse_request() */
int rc;
/* True if libhttp_parser just called _on_message_complete() */
bool is_message_complete;
} http_parser_state_t;
/* reduce length of typename from long global typedef */
typedef http_parser_state_t state_t;
#define T(l, s) [l] = { l, s }
static const struct {
uint16_t library;
http_request_method_t slurm;
} methods[] = {
/* WARNING: sync with enum http_request_method_t */
T(HTTP_GET, HTTP_REQUEST_GET),
T(HTTP_POST, HTTP_REQUEST_POST),
T(HTTP_PUT, HTTP_REQUEST_PUT),
T(HTTP_DELETE, HTTP_REQUEST_DELETE),
T(HTTP_OPTIONS, HTTP_REQUEST_OPTIONS),
T(HTTP_HEAD, HTTP_REQUEST_HEAD),
T(HTTP_PATCH, HTTP_REQUEST_PATCH),
T(HTTP_TRACE, HTTP_REQUEST_TRACE),
};
#undef T
#define T(l, s) [HPE_##l] = { HPE_##l, s }
static const struct {
uint32_t library;
slurm_err_t slurm;
} errors[] = {
T(OK, SLURM_SUCCESS),
T(CB_message_begin, ESLURM_HTTP_PARSING_FAILURE),
T(CB_url, ESLURM_HTTP_PARSING_FAILURE),
T(CB_header_field, ESLURM_HTTP_PARSING_FAILURE),
T(CB_header_value, ESLURM_HTTP_PARSING_FAILURE),
T(CB_headers_complete, ESLURM_HTTP_PARSING_FAILURE),
T(CB_body, ESLURM_HTTP_PARSING_FAILURE),
T(CB_message_complete, ESLURM_HTTP_PARSING_FAILURE),
T(CB_status, ESLURM_HTTP_PARSING_FAILURE),
T(CB_chunk_header, ESLURM_HTTP_PARSING_FAILURE),
T(CB_chunk_complete, ESLURM_HTTP_PARSING_FAILURE),
T(INVALID_EOF_STATE, ESLURM_PROTOCOL_INCOMPLETE_PACKET),
T(HEADER_OVERFLOW, SLURM_PROTOCOL_INSANE_MSG_LENGTH),
T(CLOSED_CONNECTION, SLURM_UNEXPECTED_MSG_ERROR),
T(INVALID_VERSION, SLURM_PROTOCOL_VERSION_ERROR),
T(INVALID_STATUS, ESLURM_HTTP_INVALID_STATUS_CODE),
T(INVALID_METHOD, ESLURM_HTTP_INVALID_METHOD),
T(INVALID_URL, ESLURM_URL_INVALID_FORMATING),
T(INVALID_HOST, ESLURM_URL_INVALID_HOST),
T(INVALID_PORT, ESLURM_URL_INVALID_PORT),
T(INVALID_PATH, ESLURM_URL_INVALID_PATH),
T(INVALID_QUERY_STRING, ESLURM_URL_INVALID_QUERY),
T(INVALID_FRAGMENT, ESLURM_URL_INVALID_FRAGMENT),
T(LF_EXPECTED, ESLURM_HTTP_MISSING_LF),
T(INVALID_HEADER_TOKEN, ESLURM_HTTP_INVALID_CHARACTER),
T(INVALID_CONTENT_LENGTH, ESLURM_HTTP_INVALID_CONTENT_LENGTH),
T(UNEXPECTED_CONTENT_LENGTH, ESLURM_HTTP_INVALID_CONTENT_LENGTH),
T(INVALID_CHUNK_SIZE, ESLURM_HTTP_INVALID_CHARACTER),
T(INVALID_CONSTANT, ESLURM_HTTP_INVALID_CHARACTER),
T(INVALID_INTERNAL_STATE, ESLURM_HTTP_PARSING_FAILURE),
T(STRICT, ESLURM_HTTP_PARSING_FAILURE),
T(PAUSED, ESLURM_HTTP_PARSING_FAILURE),
T(UNKNOWN, ESLURM_HTTP_PARSING_FAILURE),
#ifdef HPE_INVALID_TRANSFER_ENCODING
T(INVALID_TRANSFER_ENCODING, ESLURM_HTTP_INVALID_TRANSFER_ENCODING),
#endif
};
#undef T
/* convert libhttp_parser enum to slurm method enum */
static http_request_method_t _get_parser_method(const http_parser *parser)
{
if (parser->method < ARRAY_SIZE(methods))
return methods[parser->method].slurm;
return HTTP_REQUEST_INVALID;
}
/* convert libhttp_parser enum to slurm error enum */
static slurm_err_t _get_parser_error(const http_parser *parser)
{
if (parser->http_errno < ARRAY_SIZE(errors))
return errors[parser->http_errno].slurm;
return ESLURM_HTTP_PARSING_FAILURE;
}
extern void http_parser_p_free_parse_request(state_t **state_ptr)
{
state_t *state = NULL;
xassert(state_ptr);
SWAP(state, *state_ptr);
if (!state)
return;
xassert(state->magic == STATE_MAGIC);
url_free_members(&state->url);
xassert(!state->last_header);
xassert(!state->buffer);
xassert(state->total_bytes >= 0);
state->total_bytes = -1;
state->magic = ~STATE_MAGIC;
xfree(state);
}
static void _state_parsing_reset(state_t *state)
{
xassert(state->magic == STATE_MAGIC);
http_parser_init(&state->parser, HTTP_REQUEST);
url_free_members(&state->url);
xfree(state->last_header);
state->is_message_complete = false;
}
extern int http_parser_p_new_parse_request(const char *name,
const http_parser_callbacks_t
*callbacks,
void *callback_arg,
http_parser_state_t **state_ptr)
{
state_t *state = NULL;
if (!(state = try_xmalloc(sizeof(*state))))
return ENOMEM;
state->name = name;
state->magic = STATE_MAGIC;
state->callbacks = callbacks;
state->callback_arg = callback_arg;
state->url = URL_INITIALIZER;
state->parser.data = state;
_state_parsing_reset(state);
*state_ptr = state;
return SLURM_SUCCESS;
}
static void _log_parse_buffer(state_t *state, const char *caller,
const char *log_str, const void *at,
const size_t at_bytes)
{
const void *buffer_ptr = get_buf_data(state->buffer);
const size_t buffer_bytes = get_buf_offset(state->buffer);
const void *buffer_begin = buffer_ptr;
const void *begin = NULL;
#ifndef NDEBUG
const void *buffer_end = (buffer_ptr + buffer_bytes);
const void *end = (at ? (at + at_bytes) : buffer_end);
#endif
size_t offset_begin = 0, offset_end = 0;
xassert(buffer_begin);
xassert(buffer_begin <= buffer_end);
if (at) {
begin = at;
offset_begin = (begin - buffer_begin);
offset_end = (offset_begin + at_bytes);
} else {
xassert(!at_bytes);
begin = buffer_begin;
offset_begin = 0;
offset_end = buffer_bytes;
}
/* assert that pointers are in the buffer */
xassert(begin >= buffer_begin);
xassert(end <= buffer_end);
xassert(offset_begin <= offset_end);
xassert(offset_begin <= buffer_bytes);
xassert(offset_end <= buffer_bytes);
log_flag(DATA, "%s: [%s] PARSE [%zu,%zu)@0x%"PRIxPTR" %s", caller,
state->name, offset_begin, offset_end, (uintptr_t) buffer_ptr,
log_str);
log_flag_hex_range(NET_RAW, buffer_ptr, buffer_bytes, offset_begin,
offset_end, "%s: [%s] %s", caller, state->name,
log_str);
}
static void _log_parse(state_t *state, const void *at, const size_t at_bytes,
const char *caller, const char *fmt, ...)
{
char *log_str = NULL;
xassert(state->magic == STATE_MAGIC);
if (!(slurm_conf.debug_flags & DEBUG_FLAG_DATA) ||
(get_log_level() < LOG_LEVEL_VERBOSE))
return;
xassert(fmt);
if (fmt) {
va_list ap;
va_start(ap, fmt);
log_str = vxstrfmt(fmt, ap);
va_end(ap);
}
if (state->buffer)
_log_parse_buffer(state, caller, log_str, at, at_bytes);
else
log_flag(DATA, "%s: [%s] PARSE EOF %s",
caller, state->name, log_str);
xfree(log_str);
}
/*
* Notify caller that parsing failed
* NOTE: use PARSE_ERROR() or PARSE_ERROR_AT() instead of calling directly
* IN error_number - Slurm error encountered
* IN state - state pointer
* IN at - pointer to where failure happened or NULL if N/A
* IN caller - function that caught error
* RET 1 - always 1 to return to libhttp_parser to stop parsing
*/
static int _on_parse_error(slurm_err_t error_number, state_t *state,
const void *at, const size_t at_bytes,
const char *caller)
{
http_parser_error_t error = {
.error_number = error_number,
.offset = state->total_bytes,
.at = at,
.at_bytes = (at ? at_bytes : -1),
};
xassert(state->magic == STATE_MAGIC);
if (at) {
xassert(state->buffer);
xassert(at >= (const void *) get_buf_data(state->buffer));
xassert((at + at_bytes) <=
(const void *) (get_buf_data(state->buffer) +
get_buf_offset(state->buffer)));
/* Shift total_bytes to at pointer */
error.offset +=
(at - (const void *) get_buf_data(state->buffer));
}
LOG_PARSE_AT(state, at, at_bytes, "Parsing failed: %s",
slurm_strerror(error_number));
if (state->callbacks->on_parse_error)
state->rc =
state->callbacks->on_parse_error(&error,
state->callback_arg);
else
state->rc = error_number;
return 1;
}
static void _log_url_parse_buffer(const char *name, const char *caller,
const char *log_str, const buf_t *buffer)
{
const void *begin = get_buf_data(buffer);
const size_t bytes = get_buf_offset(buffer);
xassert(buffer->magic == BUF_MAGIC);
xassert(begin);
xassert(begin <= (begin + bytes));
log_flag(DATA, "%s: [%s] URL PARSE [0,%zu)@0x%"PRIxPTR" %s",
caller, name, bytes, (uintptr_t) begin, log_str);
log_flag_hex_range(NET_RAW, begin, bytes, 0, bytes, "%s: [%s] %s",
caller, name, log_str);
}
static void _log_url_parse(const char *name, const buf_t *buffer,
const char *caller, const char *fmt, ...)
{
char *log_str = NULL;
if (!(slurm_conf.debug_flags & DEBUG_FLAG_DATA) ||
(get_log_level() < LOG_LEVEL_VERBOSE))
return;
xassert(fmt);
if (fmt) {
va_list ap;
va_start(ap, fmt);
log_str = vxstrfmt(fmt, ap);
va_end(ap);
}
if (buffer)
_log_url_parse_buffer(name, caller, log_str, buffer);
else
log_flag(DATA, "%s: [%s] URL PARSE %s", caller, name, log_str);
xfree(log_str);
}
/*
* Log that URL parsing failed
* NOTE: use URL_PARSE_ERROR() instead of calling directly
* IN error_number - Slurm error encountered
* IN buffer - buffer being parsed
* RET error_number
*/
static int _on_url_parse_error(slurm_err_t error_number, const char *name,
const buf_t *buffer, const char *caller)
{
LOG_URL_PARSE(name, buffer, "Parsing failed: %s",
slurm_strerror(error_number));
return error_number;
}
static void _on_http_parse_error(state_t *state)
{
http_parser_error_t error = {
.error_number = _get_parser_error(&state->parser),
.description = http_errno_description(state->parser.http_errno),
.offset = state->total_bytes,
.at = NULL,
.at_bytes = -1,
};
xassert(state->magic == STATE_MAGIC);
LOG_PARSE(state, "Failure parsing HTTP: %s -> %s", __func__,
http_errno_name(state->parser.http_errno),
http_errno_description(state->parser.http_errno));
if (state->callbacks->on_parse_error)
state->rc =
state->callbacks->on_parse_error(&error,
state->callback_arg);
else
state->rc = error.error_number;
}
static void _http_parser_url_init(struct http_parser_url *url)
{
#if (HTTP_PARSER_VERSION_MAJOR == 2 && HTTP_PARSER_VERSION_MINOR >= 6) || \
(HTTP_PARSER_VERSION_MAJOR > 2)
http_parser_url_init(url);
#else
/*
* Explicit init was only added with 2.6.0
* https://github.com/nodejs/http-parser/pull/225
*/
memset(url, 0, sizeof(*url));
#endif
}
/*
* Parse URL where only the port is given.
* Examples:
* unix:/path/to/socket
*
* RET
* SLURM_SUCCESS: parsed port successfully
* ESLURM_URL_UNSUPPORTED_FORMAT: not a port only URL
* *: error
*/
static int _parse_unix_url(const char *name, const buf_t *buffer, url_t *dst)
{
const char *data = get_buf_data(buffer);
const size_t bytes = get_buf_offset(buffer);
if (xstrncmp(UNIX_PREFIX, data, UNIX_PREFIX_BYTES))
return ESLURM_URL_UNSUPPORTED_FORMAT;
if (data[UNIX_PREFIX_BYTES] == '\0')
return ESLURM_URL_EMPTY;
dst->scheme = URL_SCHEME_UNIX;
dst->path = xstrndup((data + UNIX_PREFIX_BYTES),
(bytes - UNIX_PREFIX_BYTES));
return SLURM_SUCCESS;
}
/*
* Parse URL where only the port is given.
* Examples:
* :8080
* :ssh
*
* RET
* SLURM_SUCCESS: parsed port successfully
* ESLURM_URL_UNSUPPORTED_FORMAT: not a port only URL
* *: error
*/
static int _parse_only_port(const char *name, const buf_t *buffer, url_t *dst)
{
const char *data = get_buf_data(buffer);
const size_t bytes = get_buf_offset(buffer);
if (data[0] != ':')
return ESLURM_URL_UNSUPPORTED_FORMAT;
dst->port = xstrndup((data + 1), (bytes - 1));
return SLURM_SUCCESS;
}
/*
* Parse URL using libhttp_parser's URL parser
* Examples:
* host:port
* https://user@[host]:port/path/?query#fragment
* RET
* SLURM_SUCCESS: parsed port successfully
* ESLURM_URL_UNSUPPORTED_FORMAT: library doesn't support format
* *: error
*/
static int _library_url_parse(const char *name, const buf_t *buffer, url_t *dst)
{
struct http_parser_url url;
const void *data = get_buf_data(buffer);
const size_t bytes = get_buf_offset(buffer);
int rc = EINVAL;
_http_parser_url_init(&url);
xassert(data);
xassert(bytes > 0);
xassert(size_buf(buffer) >= bytes);
/* Try parsing a full URL and then try parsing only a host:port pair */
if (http_parser_parse_url(data, bytes, false, &url) &&
http_parser_parse_url(data, bytes, true, &url))
return ESLURM_URL_UNSUPPORTED_FORMAT;
if ((url.field_set & (1 << UF_SCHEMA)) &&
(rc = url_get_scheme((data + url.field_data[UF_SCHEMA].off),
url.field_data[UF_SCHEMA].len, &dst->scheme)))
return rc;
if (url.field_set & (1 << UF_HOST)) {
xassert(url.field_data[UF_HOST].len <= bytes);
dst->host = xstrndup((data + url.field_data[UF_HOST].off),
url.field_data[UF_HOST].len);
}
if (url.field_set & (1 << UF_PORT)) {
xassert(url.field_data[UF_PORT].len <= bytes);
dst->port = xstrndup((data + url.field_data[UF_PORT].off),
url.field_data[UF_PORT].len);
}
if (url.field_set & (1 << UF_PATH)) {
xassert(url.field_data[UF_PATH].len <= bytes);
dst->path = xstrndup((data + url.field_data[UF_PATH].off),
url.field_data[UF_PATH].len);
}
if (url.field_set & (1 << UF_QUERY)) {
xassert(url.field_data[UF_QUERY].len <= bytes);
dst->query = xstrndup((data + url.field_data[UF_QUERY].off),
url.field_data[UF_QUERY].len);
}
if (url.field_set & (1 << UF_FRAGMENT)) {
xassert(url.field_data[UF_FRAGMENT].len <= bytes);
dst->fragment =
xstrndup((data + url.field_data[UF_FRAGMENT].off),
url.field_data[UF_FRAGMENT].len);
}
if (url.field_set & (1 << UF_USERINFO)) {
xassert(url.field_data[UF_USERINFO].len <= bytes);
dst->user = xstrndup((data + url.field_data[UF_USERINFO].off),
url.field_data[UF_USERINFO].len);
}
return SLURM_SUCCESS;
}
extern int init(void)
{
debug("loaded");
return SLURM_SUCCESS;
}
extern void fini(void)
{
debug("unloaded");
}
extern int url_parser_p_parse(const char *name, const buf_t *buffer, url_t *dst)
{
int rc = EINVAL;
xassert(!buffer || (buffer->magic == BUF_MAGIC));
xassert(dst);
xassert(name && name[0]);
url_free_members(dst);
if (!buffer || !get_buf_offset(buffer))
return URL_PARSE_ERROR(ESLURM_URL_EMPTY, name, buffer);
/* Catch any errant NULL terminators */
if (strnlen(get_buf_data(buffer), get_buf_offset(buffer)) !=
get_buf_offset(buffer))
return URL_PARSE_ERROR(ESLURM_URL_NON_NULL_TERMINATOR, name,
buffer);
/*
* Try using libhttp_parser's builtin URL parser and then try additional
* parsers for formats it doesn't support
*/
rc = _library_url_parse(name, buffer, dst);
if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) {
url_free_members(dst);
rc = _parse_only_port(name, buffer, dst);
}
if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) {
url_free_members(dst);
rc = _parse_unix_url(name, buffer, dst);
}
if (rc) {
/*
* If none of the parsers apply, then consider the URL to be an
* invalid format
*/
if (rc == ESLURM_URL_UNSUPPORTED_FORMAT)
rc = ESLURM_URL_INVALID_FORMATING;
url_free_members(dst);
return URL_PARSE_ERROR(rc, name, buffer);
}
LOG_URL_PARSE(
name, buffer,
"Parsed URL scheme:%s host:%s port:%s user:%s path:%s query:%s fragment:%s",
url_get_scheme_string(dst->scheme), dst->host, dst->port,
dst->user, dst->path, dst->query, dst->fragment);
return SLURM_SUCCESS;
}
static int _on_url(http_parser *parser, const char *at, size_t length)
{
state_t *state = parser->data;
buf_t buffer = {
.magic = BUF_MAGIC,
.head = (void *) at,
.processed = length,
.size = length,
};
int rc = EINVAL;
xassert(state->magic == STATE_MAGIC);
if (state->url.scheme != URL_SCHEME_INVALID)
return PARSE_ERROR_AT(ESLURM_HTTP_UNEXPECTED_URL, state, at,
length);
if ((rc = url_parser_p_parse(state->name, &buffer, &state->url)))
return PARSE_ERROR_AT(rc, state, at, length);
return rc;
}
static int _on_message_begin(http_parser *parser)
{
/* Do nothing successfully */
return 0;
}
static int _on_status(http_parser *parser, const char *at, size_t length)
{
/* Do nothing successfully */
return 0;
}
static int _on_header_field(http_parser *parser, const char *at, size_t length)
{
state_t *state = parser->data;
xassert(state->magic == STATE_MAGIC);
xassert(!state->last_header);
xassert(length > 0);
xassert(length <= get_buf_offset(state->buffer));
xassert((const void *) at >=
(const void *) get_buf_data(state->buffer));
if (!state->callbacks->on_header)
return 0;
xfree(state->last_header);
state->last_header = xstrndup(at, length);
/* Trim header field-name per RFC2616:4.2 */
xstrtrim(state->last_header);
return 0;
}
static int _on_header_value(http_parser *parser, const char *at, size_t length)
{
state_t *state = parser->data;
http_parser_header_t header = {
.name = state->last_header,
};
char *value = NULL;
xassert(state->magic == STATE_MAGIC);
if (!state->callbacks->on_header)
return 0;
if (!state->last_header)
return PARSE_ERROR_AT(ESLURM_HTTP_EMPTY_HEADER, state, at,
length);
/* Copy value to trim it */
value = xstrndup(at, length);
/* trim header field-name per rfc2616:4.2 */
xstrtrim(value);
header.value = value;
LOG_PARSE_AT(state, at, length, "Parsed Header:%s Value:%s",
header.name, header.value);
if ((state->rc =
state->callbacks->on_header(&header, state->callback_arg)))
return 1;
xfree(state->last_header);
xfree(value);
return 0;
}
static int _headers_callback(http_parser *parser, state_t *state)
{
http_parser_request_t request = {
.http_version = {
.major = parser->http_major,
.minor = parser->http_minor,
},
.method = _get_parser_method(parser),
.url = &state->url,
};
xassert(state->magic == STATE_MAGIC);
if ((state->rc = state->callbacks->on_request(&request,
state->callback_arg)))
return INFINITE;
return 0;
}
/*
* Note: Special return rules for this callback
* return 0 for SUCCESS
* return 1 to tell parser to not expect a body.
* return 2 to tell parser to not expect a body or
* anything else from this HTTP request.
* return all others to indicate failure to parse
*/
static int _on_headers_complete(http_parser *parser)
{
state_t *state = parser->data;
xassert(state->magic == STATE_MAGIC);
if (!state->callbacks->on_request)
return 0;
else
return _headers_callback(parser, state);
}
static int _on_body(http_parser *parser, const char *at, size_t length)
{
state_t *state = parser->data;
buf_t buffer = {
.magic = BUF_MAGIC,
.head = (void *) at,
.size = length,
.processed = length,
};
http_parser_content_t content = {
.buffer = &buffer,
};
xassert(state->magic == STATE_MAGIC);
LOG_PARSE_AT(state, at, length, "received HTTP body");
if (state->callbacks->on_content &&
(state->rc = state->callbacks->on_content(&content,
state->callback_arg)))
return 1;
return 0;
}
static int _on_message_complete(http_parser *parser)
{
state_t *state = parser->data;
xassert(state->magic == STATE_MAGIC);
xassert(!state->is_message_complete);
state->is_message_complete = true;
LOG_PARSE(state, "message complete");
if (state->callbacks->on_content_complete &&
(state->rc = state->callbacks
->on_content_complete(state->callback_arg)))
return 1;
return 0;
}
static int _on_chunk_header(http_parser *parser)
{
state_t *state = parser->data;
xassert(state->magic == STATE_MAGIC);
return PARSE_ERROR(ESLURM_HTTP_UNSUPPORTED_CHUNK_ENCODING, state);
}
static int _on_chunk_complete(http_parser *parser)
{
state_t *state = parser->data;
xassert(state->magic == STATE_MAGIC);
return PARSE_ERROR(ESLURM_HTTP_UNSUPPORTED_CHUNK_ENCODING, state);
}
static void _parse(state_t *state, ssize_t *bytes_parsed_ptr)
{
static const http_parser_settings settings = {
.on_url = _on_url,
.on_message_begin = _on_message_begin,
.on_status = _on_status,
.on_header_field = _on_header_field,
.on_header_value = _on_header_value,
.on_headers_complete = _on_headers_complete,
.on_body = _on_body,
.on_message_complete = _on_message_complete,
.on_chunk_header = _on_chunk_header,
.on_chunk_complete = _on_chunk_complete
};
size_t parsed_bytes = 0;
void *data = NULL;
size_t bytes = 0;
if (state->buffer) {
xassert(state->buffer->magic == BUF_MAGIC);
/* Assert that the buffer is populated */
xassert(get_buf_data(state->buffer));
xassert(get_buf_offset(state->buffer) > 0);
data = get_buf_data(state->buffer);
/*
* Never include NULL terminator as library will consider any \0
* while parsing headers as an invalid token and false error
* out.
*/
bytes = get_buf_offset(state->buffer);
/* Invalidate being message complete on more data to parse */
state->is_message_complete = false;
LOG_PARSE(state, "BEGIN: Parsing %zu bytes", bytes);
} else if (state->is_message_complete) {
/*
* Ignore EOF call when parser already thinks message is
* complete
*/
*bytes_parsed_ptr = 0;
/*
* Reset parsing state to avoid inheriting incorrect state for
* parsing next message
*/
_state_parsing_reset(state);
LOG_PARSE(state, "SKIP: Parsing EOF after total %zu bytes",
state->total_bytes);
return;
} else {
/* Send NULL terminated empty string to signify EOF */
data = "";
bytes = 0;
LOG_PARSE(state, "BEGIN: Parsing EOF after total %zu bytes",
state->total_bytes);
}
if ((parsed_bytes = http_parser_execute(&state->parser, &settings, data,
bytes))) {
/*
* Avoid including NULL terminator in total bytes parsed since
* that would inflate the bytes per the number of times parsing
* is called uselessly.
*/
state->total_bytes += parsed_bytes;
} else if (state->parser.http_errno) {
/* Only check for HTTP errors when nothing parsed */
_on_http_parse_error(state);
}
if (!state->rc) {
if (state->buffer) {
*bytes_parsed_ptr = parsed_bytes;
} else {
/*
* library just assumes there is always a NULL
* terminator if there is a valid pointer
*/
xassert(parsed_bytes == 1);
/* Always return 0 for NULL buffers */
*bytes_parsed_ptr = 0;
}
LOG_PARSE(
state,
"END: Parsed %zu/%zu bytes totalling %zu bytes successfully",
parsed_bytes, bytes, state->total_bytes);
} else {
/* Error already logged */
*bytes_parsed_ptr = -1;
}
}
extern int http_parser_p_parse_request(state_t *state, const buf_t *buffer,
ssize_t *bytes_parsed_ptr)
{
xassert(state);
xassert(state->magic == STATE_MAGIC);
xassert(state->parser.data == state);
state->buffer = buffer;
/* Skip run if nothing to parse in buffer */
if (buffer && !get_buf_offset(buffer))
LOG_PARSE(state, "Skipping parse on empty buffer");
else
_parse(state, bytes_parsed_ptr);
xassert(state->buffer == buffer);
state->buffer = NULL;
return state->rc;
}