| /*****************************************************************************\ |
| * 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; |
| } |