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