| /*****************************************************************************\ | |
| * http.c - handling HTTP | |
| ***************************************************************************** | |
| * 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 "config.h" | |
| #include <ctype.h> | |
| #include <limits.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #include "slurm/slurm.h" | |
| #include "slurm/slurm_errno.h" | |
| #include "src/common/http.h" | |
| #include "src/common/read_config.h" | |
| #include "src/common/slurm_protocol_api.h" | |
| #include "src/common/xmalloc.h" | |
| #include "src/common/xstring.h" | |
| typedef struct { | |
| http_status_code_t code; | |
| char *text; | |
| } http_status_code_txt_t; | |
| #define T(code, text) { code, text } | |
| static const http_status_code_txt_t http_status_codes[] = { | |
| // clang-format off | |
| T(HTTP_STATUS_CODE_CONTINUE, "CONTINUE"), | |
| T(HTTP_STATUS_CODE_SWITCH_PROTOCOLS, "SWITCH PROTOCOLS"), | |
| T(HTTP_STATUS_CODE_SUCCESS_OK, "OK"), | |
| T(HTTP_STATUS_CODE_SUCCESS_CREATED, "CREATED"), | |
| T(HTTP_STATUS_CODE_SUCCESS_ACCEPTED, "ACCEPTED"), | |
| T(HTTP_STATUS_CODE_SUCCESS_NON_AUTHORITATIVE, "OK (NON AUTHORITATIVE)"), | |
| T(HTTP_STATUS_CODE_SUCCESS_NO_CONTENT, "NO CONTENT"), | |
| T(HTTP_STATUS_CODE_SUCCESS_RESET_CONNECTION, "RESET CONNECTION"), | |
| T(HTTP_STATUS_CODE_SUCCESS_PARTIAL_CONTENT, "PARTIAL CONTENT"), | |
| T(HTTP_STATUS_CODE_REDIRECT_MULTIPLE_CHOICES, "REDIRECT MULTIPLE CHOICES"), | |
| T(HTTP_STATUS_CODE_REDIRECT_MOVED_PERMANENTLY, "MOVED PERMANENTLY"), | |
| T(HTTP_STATUS_CODE_REDIRECT_FOUND, "REDIRECT FOUND"), | |
| T(HTTP_STATUS_CODE_REDIRECT_SEE_OTHER, "REDIRECT SEE OTHER"), | |
| T(HTTP_STATUS_CODE_REDIRECT_NOT_MODIFIED, "NOT MODIFIED"), | |
| T(HTTP_STATUS_CODE_REDIRECT_USE_PROXY, "USE PROXY"), | |
| T(HTTP_STATUS_CODE_REDIRECT_TEMP_REDIRCT, "TEMP REDIRECT"), | |
| T(HTTP_STATUS_CODE_ERROR_BAD_REQUEST, "BAD REQUEST"), | |
| T(HTTP_STATUS_CODE_ERROR_UNAUTHORIZED, "UNAUTHORIZED"), | |
| T(HTTP_STATUS_CODE_ERROR_PAYMENT_REQUIRED, "PAYMENT REQUIRED"), | |
| T(HTTP_STATUS_CODE_ERROR_FORBIDDEN, "FORBIDDEN"), | |
| T(HTTP_STATUS_CODE_ERROR_NOT_FOUND, "NOT FOUND"), | |
| T(HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED, "NOT ALLOWED"), | |
| T(HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE, "NOT ACCEPTABLE"), | |
| T(HTTP_STATUS_CODE_ERROR_PROXY_AUTH_REQ, "PROXY AUTHENTICATION REQUIRED"), | |
| T(HTTP_STATUS_CODE_ERROR_REQUEST_TIMEOUT, "REQUEST TIMEOUT"), | |
| T(HTTP_STATUS_CODE_ERROR_CONFLICT, "CONFLICT"), | |
| T(HTTP_STATUS_CODE_ERROR_GONE, "GONE"), | |
| T(HTTP_STATUS_CODE_ERROR_LENGTH_REQUIRED, "LENGTH REQUIRED"), | |
| T(HTTP_STATUS_CODE_ERROR_PRECONDITION_FAILED, "PRECONDITION FAILED"), | |
| T(HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE, "ENTITY TOO LARGE"), | |
| T(HTTP_STATUS_CODE_ERROR_URI_TOO_LONG, "URI TOO LONG"), | |
| T(HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE, "UNSUPPORTED MEDIA TYPE"), | |
| T(HTTP_STATUS_CODE_ERROR_REQUEST_RANGE_UNSATISFIABLE, "REQUEST RANGE UNJUSTIFIABLE"), | |
| T(HTTP_STATUS_CODE_ERROR_EXPECTATION_FAILED, "EXPECTATION FAILED"), | |
| /* rfc7168 */ | |
| T(HTTP_STATUS_CODE_ERROR_IM_A_TEAPOT, "I'm a Teapot"), | |
| /* rfc9110 15.5.20 */ | |
| T(HTTP_STATUS_CODE_ERROR_MISDIRECT_REQUESTED, "MISDIRECTED REQUEST"), | |
| /* rfc9110 15.5.21 */ | |
| T(HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT, "UNPROCESSABLE CONTENT"), | |
| /* rfc7231 6.5.15 */ | |
| T(HTTP_STATUS_CODE_ERROR_UPGRADE_REQUIRED, "UPGRADE REQUIRED"), | |
| T(HTTP_STATUS_CODE_SRVERR_INTERNAL, "INTERNAL ERROR"), | |
| T(HTTP_STATUS_CODE_SRVERR_NOT_IMPLEMENTED, "NOT IMPLEMENTED"), | |
| T(HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY, "BAD GATEWAY"), | |
| T(HTTP_STATUS_CODE_SRVERR_SERVICE_UNAVAILABLE, "SERVICE UNAVAILABLE"), | |
| T(HTTP_STATUS_CODE_SRVERR_GATEWAY_TIMEOUT, "GATEWAY TIMEOUT"), | |
| T(HTTP_STATUS_CODE_SRVERR_HTTP_VERSION_NOT_SUPPORTED, "HTTP VERSION NOT SUPPORTED"), | |
| T(HTTP_STATUS_CODE_SRVERR_VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates"), | |
| T(HTTP_STATUS_CODE_SRVERR_INSUFFICENT_STORAGE, "Insufficient Storage"), | |
| T(HTTP_STATUS_CODE_SRVERR_LOOP_DETECTED, "Loop Detected"), | |
| T(HTTP_STATUS_CODE_SRVERR_NOT_EXTENDED, "Not Extended"), | |
| T(HTTP_STATUS_CODE_SRVERR_NETWORK_AUTH_REQ, "Network Authentication Required"), | |
| T(HTTP_STATUS_CODE_DEFAULT, "default"), | |
| // clang-format on | |
| }; | |
| #undef T | |
| /* Conversion from slurm_error to http_status code map */ | |
| #define T(error, code) { error, code } | |
| static const struct { | |
| slurm_err_t error; | |
| http_status_code_t code; | |
| } http_status_errors[] = { | |
| // clang-format off | |
| T(SLURM_SUCCESS, HTTP_STATUS_CODE_SUCCESS_OK), | |
| T(SLURM_NO_CHANGE_IN_DATA, HTTP_STATUS_CODE_REDIRECT_NOT_MODIFIED), | |
| T(ESLURM_REST_INVALID_QUERY, HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT), | |
| T(ESLURM_REST_FAIL_PARSING, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_REST_INVALID_JOBS_DESC, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_DATA_UNKNOWN_MIME_TYPE, HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE), | |
| T(ESLURM_INVALID_JOB_ID, HTTP_STATUS_CODE_ERROR_NOT_FOUND), | |
| T(ESLURM_URL_INVALID_PATH, HTTP_STATUS_CODE_ERROR_NOT_FOUND), | |
| T(SLURM_PROTOCOL_SOCKET_ZERO_BYTES_SENT, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURM_COMMUNICATIONS_CONNECTION_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURM_COMMUNICATIONS_SEND_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURM_COMMUNICATIONS_RECEIVE_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURM_COMMUNICATIONS_SHUTDOWN_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURMCTLD_COMMUNICATIONS_CONNECTION_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURMCTLD_COMMUNICATIONS_SEND_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURMCTLD_COMMUNICATIONS_RECEIVE_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURMCTLD_COMMUNICATIONS_SHUTDOWN_ERROR, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURMCTLD_COMMUNICATIONS_BACKOFF, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(ESLURM_DB_CONNECTION, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(ESLURM_PROTOCOL_INCOMPLETE_PACKET, HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY), | |
| T(SLURM_PROTOCOL_SOCKET_IMPL_TIMEOUT, HTTP_STATUS_CODE_SRVERR_GATEWAY_TIMEOUT), | |
| T(SLURM_PROTOCOL_AUTHENTICATION_ERROR, HTTP_STATUS_CODE_SRVERR_NETWORK_AUTH_REQ), | |
| T(ESLURM_HTTP_INVALID_CONTENT_LENGTH, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_HTTP_CONTENT_LENGTH_TOO_LARGE, HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE), | |
| T(ESLURM_HTTP_POST_MISSING_CONTENT_LENGTH, HTTP_STATUS_CODE_ERROR_LENGTH_REQUIRED), | |
| T(ESLURM_HTTP_INVALID_CONTENT_ENCODING, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE), | |
| T(ESLURM_HTTP_UNSUPPORTED_EXPECT, HTTP_STATUS_CODE_ERROR_EXPECTATION_FAILED), | |
| T(ESLURM_HTTP_UNSUPPORTED_KEEP_ALIVE, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE), | |
| T(ESLURM_HTTP_INVALID_METHOD, HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED), | |
| T(ESLURM_HTTP_UNSUPPORTED_UPGRADE, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE), | |
| T(ESLURM_HTTP_INVALID_TRANSFER_ENCODING, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE), | |
| T(ESLURM_AUTH_CRED_INVALID, HTTP_STATUS_CODE_ERROR_UNAUTHORIZED), | |
| T(ESLURM_AUTH_EXPIRED, HTTP_STATUS_CODE_ERROR_UNAUTHORIZED), | |
| T(ESLURM_AUTH_SKIP, HTTP_STATUS_CODE_ERROR_UNAUTHORIZED), | |
| T(ESLURM_REST_AUTH_FAIL, HTTP_STATUS_CODE_ERROR_UNAUTHORIZED), | |
| T(ESLURM_AUTH_UNABLE_TO_GENERATE_TOKEN, HTTP_STATUS_CODE_ERROR_UNAUTHORIZED), | |
| T(ESLURM_HTTP_UNEXPECTED_BODY, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_DATA_UNKNOWN_MIME_TYPE, HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE), | |
| T(ESLURM_HTTP_UNKNOWN_ACCEPT_MIME_TYPE, HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE), | |
| T(ESLURM_REST_UNKNOWN_URL_METHOD, HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED), | |
| T(ESLURM_DATA_PATH_NOT_FOUND, HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT), | |
| T(ESLURM_DATA_PARSE_BAD_INPUT, HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT), | |
| T(EPERM, HTTP_STATUS_CODE_ERROR_FORBIDDEN), | |
| T(SLURM_SHUTTING_DOWN, HTTP_STATUS_CODE_SRVERR_SERVICE_UNAVAILABLE), | |
| T(ESLURM_REST_BAD_REQUEST, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_INVALID_CHAR, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_INVALID_ESCAPED, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_INVALID_COMMENT, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNEXPECTED_DICTIONARY_END, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNCLOSED_DICTIONARY, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_INVALID_DICTIONARY_KEY, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_INVALID_DICTIONARY_COLON, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNEXPECTED_LIST_END, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNCLOSED_LIST, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_MISSING_COMMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNEXPECTED_COMMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_UNCLOSED_QUOTED_STRING, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_PARSE_FAILED, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_PARSE_DEPTH_MIN, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_JSON_PARSE_DEPTH_MAX, HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE), | |
| T(ESLURM_UTF_INVALID_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF8_INVALID_READ, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF8_READ_ILLEGAL_TERMINATION, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF8_INVALID_BYTE_2, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF8_INVALID_BYTE_3, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF8_INVALID_BYTE_4, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF_RESERVED_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF16_SURROGATE_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF_PRIVATE_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF_NONCHARACTER_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF_NULL_CODE, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| /* Only UTF-8 is accepted; a UTF-16/32 BOM is an unusable encoding */ | |
| T(ESLURM_UTF16BE_SCHEMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF16LE_SCHEMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF32BE_SCHEMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_UTF32LE_SCHEMA, HTTP_STATUS_CODE_ERROR_BAD_REQUEST), | |
| T(ESLURM_REST_TOPO_NOT_FOUND, HTTP_STATUS_CODE_ERROR_NOT_FOUND), | |
| // clang-format on | |
| }; | |
| #undef T | |
| #define T(method, upper_case, lower_case) \ | |
| [method] = { method, upper_case, lower_case } | |
| static const struct { | |
| http_request_method_t method; | |
| const char *uc_text; | |
| const char *lc_text; | |
| } method_strings[] = { | |
| T(HTTP_REQUEST_INVALID, NULL, NULL), | |
| T(HTTP_REQUEST_GET, "GET", "get"), | |
| T(HTTP_REQUEST_POST, "POST", "post"), | |
| T(HTTP_REQUEST_PUT, "PUT", "put"), | |
| T(HTTP_REQUEST_DELETE, "DELETE", "delete"), | |
| T(HTTP_REQUEST_OPTIONS, "OPTIONS", "options"), | |
| T(HTTP_REQUEST_HEAD, "HEAD", "head"), | |
| T(HTTP_REQUEST_PATCH, "PATCH", "patch"), | |
| T(HTTP_REQUEST_TRACE, "TRACE", "trace"), | |
| T(HTTP_REQUEST_CONNECT, "CONNECT", "connect"), | |
| T(HTTP_REQUEST_INVALID_MAX, NULL, NULL), | |
| }; | |
| #undef T | |
| #define T(tscheme, str) \ | |
| { \ | |
| .scheme = tscheme, \ | |
| .string = str, \ | |
| .bytes = (sizeof(str) - 1), \ | |
| } | |
| static const struct { | |
| url_scheme_t scheme; | |
| const char *string; | |
| size_t bytes; | |
| } schemes[] = { | |
| T(URL_SCHEME_INVALID, "INVALID"), | |
| T(URL_SCHEME_HTTP, "http"), | |
| T(URL_SCHEME_HTTPS, "https"), | |
| T(URL_SCHEME_UNIX, "unix"), | |
| T(URL_SCHEME_INVALID_MAX, "INVALID_MAX"), | |
| }; | |
| #undef T | |
| #define PARSE_URL_ARGS_MAGIC 0x8aa83331 | |
| typedef struct { | |
| int magic; /* PARSE_URL_ARGS_MAGIC */ | |
| bool convert_types; | |
| data_t *paths; | |
| } parse_url_args_t; | |
| extern int url_get_scheme(const char *str, size_t bytes, | |
| url_scheme_t *scheme_ptr) | |
| { | |
| if (!str || !str[0] || !bytes) { | |
| *scheme_ptr = URL_SCHEME_INVALID; | |
| return ESLURM_URL_EMPTY; | |
| } | |
| for (int i = (URL_SCHEME_INVALID + 1); i < URL_SCHEME_INVALID_MAX; | |
| i++) { | |
| if (bytes != schemes[i].bytes) | |
| continue; | |
| if (xstrncasecmp(schemes[i].string, str, bytes)) | |
| continue; | |
| *scheme_ptr = schemes[i].scheme; | |
| return SLURM_SUCCESS; | |
| } | |
| *scheme_ptr = URL_SCHEME_INVALID; | |
| return ESLURM_URL_UNKNOWN_SCHEME; | |
| } | |
| extern const char *url_get_scheme_string(const url_scheme_t scheme) | |
| { | |
| xassert(scheme >= URL_SCHEME_INVALID); | |
| xassert(scheme < URL_SCHEME_INVALID_MAX); | |
| if (scheme == URL_SCHEME_INVALID) | |
| return NULL; | |
| for (int i = (URL_SCHEME_INVALID + 1); i < URL_SCHEME_INVALID_MAX; | |
| i++) { | |
| if (scheme == schemes[i].scheme) | |
| return schemes[i].string; | |
| } | |
| fatal_abort("should never happen"); | |
| } | |
| extern bool url_is_unreserved_char(const char c) | |
| { | |
| return ((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')) || | |
| ((c >= 'A') && (c <= 'Z')) || (c == '~') || (c == '-') || | |
| (c == '.') || (c == '_'); | |
| } | |
| extern unsigned char url_decode_escape_seq(const char *ptr, const char *ptr_end) | |
| { | |
| if ((ptr + 2 < ptr_end) && isxdigit(*(ptr + 1)) && | |
| isxdigit(*(ptr + 2))) { | |
| /* using uint16_t char to catch any overflows */ | |
| uint16_t high = *(ptr + 1); | |
| uint16_t low = *(ptr + 2); | |
| uint16_t decoded = ((slurm_char_to_hex(high) << 4) + | |
| slurm_char_to_hex(low)); | |
| //TODO: find more invalid characters? | |
| if (decoded == '\0') { | |
| log_flag(DATA, "%s: invalid URL escape sequence for 0x00", | |
| __func__); | |
| return '\0'; | |
| } else if (decoded >= 0xff) { | |
| log_flag(DATA, "%s: invalid URL escape sequence for 0x%02" PRIx16, | |
| __func__, decoded); | |
| return '\0'; | |
| } | |
| log_flag(DATA, "%s: URL decoded: 0x%c%c -> %c (0x%02" PRIx16 ")", | |
| __func__, (unsigned char) high, (unsigned char) low, | |
| (unsigned char) decoded, decoded); | |
| return (unsigned char) decoded; | |
| } else { | |
| log_flag_hex(DATA, ptr, strnlen(ptr, MIN(3, ptr_end - ptr)), | |
| "%s: invalid URL escape sequence: %.*s", __func__, | |
| (int) (ptr_end - ptr), ptr); | |
| return '\0'; | |
| } | |
| } | |
| static int _add_path(const char *entry, bool template, void *arg) | |
| { | |
| parse_url_args_t *args = arg; | |
| const bool convert_types = args->convert_types; | |
| data_t *d = args->paths; | |
| xassert(args->magic == PARSE_URL_ARGS_MAGIC); | |
| if (!xstrcasecmp(entry, ".")) { | |
| debug5("%s: ignoring path . entry", __func__); | |
| } else if (!xstrcasecmp(entry, "..")) { | |
| //TODO: pop last directory off sequence instead of fail | |
| debug5("%s: rejecting path .. entry", __func__); | |
| return SLURM_ERROR; | |
| } else { | |
| data_t *c = data_list_append(d); | |
| data_set_string(c, entry); | |
| if (convert_types) | |
| (void) data_convert_type(c, DATA_TYPE_NONE); | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| extern data_t *parse_url_path(const char *path, bool convert_types, | |
| bool allow_templates) | |
| { | |
| parse_url_args_t args = { | |
| .magic = PARSE_URL_ARGS_MAGIC, | |
| .convert_types = convert_types, | |
| .paths = data_set_list(data_new()), | |
| }; | |
| if (url_path_walk(path, allow_templates, _add_path, &args)) | |
| FREE_NULL_DATA(args.paths); | |
| return args.paths; | |
| } | |
| extern int url_path_walk(const char *path, bool allow_templates, | |
| on_url_path_entry_t on_entry, void *arg) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| char *buffer = NULL, *buffer_at = NULL; | |
| const char *path_end = NULL; | |
| /* Skip empty path strings */ | |
| if (!path || !path[0]) | |
| return SLURM_SUCCESS; | |
| path_end = path + strlen(path) + 1; | |
| /* extract each word */ | |
| for (const char *ptr = path; !rc && (*ptr != '\0'); ptr++) { | |
| /* | |
| * Accept literal spaces even though rfc3986 does not, as | |
| * entries are kept exactly as given, including any spaces | |
| */ | |
| if (url_is_unreserved_char(*ptr) || (*ptr == ' ')) { | |
| char str[2] = { *ptr, '\0' }; | |
| xstrcatat(buffer, &buffer_at, str); | |
| continue; | |
| } | |
| switch (*ptr) { | |
| case '{': /* OASv3.0.3 section 4.7.8.2 template variable */ | |
| if (!allow_templates) { | |
| debug("%s: unexpected OAS template character: %c", | |
| __func__, *ptr); | |
| rc = ESLURM_URL_INVALID_FORMATING; | |
| goto cleanup; | |
| } else { | |
| /* find end of template */ | |
| const char *start = (ptr + 1); | |
| const char *end = xstrstr(ptr, "}"); | |
| if (!end) { | |
| debug("%s: missing terminated OAS template character: }", | |
| __func__); | |
| rc = ESLURM_URL_INVALID_FORMATING; | |
| goto cleanup; | |
| } | |
| xassert(end > start); | |
| xstrncatat(buffer, &buffer_at, start, | |
| (end - start)); | |
| rc = on_entry(buffer, true, arg); | |
| ptr = end; | |
| } | |
| break; | |
| case '%': /* rfc3986 */ | |
| { | |
| const char c = url_decode_escape_seq(ptr, path_end); | |
| if (c != '\0') { | |
| char str[2] = { c, '\0' }; | |
| /* shift past the hex value */ | |
| ptr += 2; | |
| xstrcatat(buffer, &buffer_at, str); | |
| } else { | |
| debug("%s: invalid URL escape sequence: %s", | |
| __func__, ptr); | |
| rc = ESLURM_URL_UNSUPPORTED_FORMAT; | |
| goto cleanup; | |
| } | |
| /* | |
| * Decoded byte belongs to the current segment: | |
| * keep accumulating rather than falling through | |
| * to the buffer reset below, which would drop it. | |
| */ | |
| continue; | |
| } | |
| case '/': /* rfc3986 */ | |
| if (buffer && buffer[0]) | |
| rc = on_entry(buffer, false, arg); | |
| break; | |
| default: | |
| debug("%s: unexpected URL character: %c", | |
| __func__, *ptr); | |
| rc = ESLURM_URL_INVALID_FORMATING; | |
| goto cleanup; | |
| } | |
| if (buffer) { | |
| (void) memset(buffer, '\0', (buffer_at - buffer)); | |
| buffer_at = buffer; | |
| } | |
| } | |
| /* last part of path */ | |
| if (!rc && buffer && buffer[0]) | |
| rc = on_entry(buffer, false, arg); | |
| cleanup: | |
| xfree(buffer); | |
| return rc; | |
| } | |
| extern http_status_code_t get_http_status_code(const char *str) | |
| { | |
| if (isdigit(str[0])) { | |
| uint64_t n = slurm_atoul(str); | |
| /* | |
| * Check for default explicitly as it is outside the valid range | |
| * that is checked by next if() | |
| */ | |
| if (n == HTTP_STATUS_CODE_DEFAULT) | |
| return HTTP_STATUS_CODE_DEFAULT; | |
| if ((n <= HTTP_STATUS_CODE_INVALID) || | |
| (n >= HTTP_STATUS_CODE_INVALID_MAX)) | |
| return HTTP_STATUS_CODE_INVALID; | |
| return n; | |
| } | |
| for (int i = 0; i < ARRAY_SIZE(http_status_codes); i++) | |
| if (!xstrcasecmp(http_status_codes[i].text, str)) | |
| return http_status_codes[i].code; | |
| return HTTP_STATUS_CODE_INVALID; | |
| } | |
| extern http_status_code_t http_status_from_error(slurm_err_t error) | |
| { | |
| for (int i = 0; i < ARRAY_SIZE(http_status_errors); i++) | |
| if (error == http_status_errors[i].error) | |
| return http_status_errors[i].code; | |
| /* | |
| * An error from the slurmctld or accounting range means the REST API | |
| * query is formatted correctly but may be semantically | |
| * incorrect; Respond with HTTP code 422 | |
| */ | |
| if (IS_CTLD_ERROR(error) || IS_DBD_ERROR(error)) | |
| return HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT; | |
| return HTTP_STATUS_CODE_SRVERR_INTERNAL; | |
| } | |
| extern const char *get_http_status_code_string(http_status_code_t code) | |
| { | |
| for (int i = 0; i < ARRAY_SIZE(http_status_codes); i++) | |
| if (http_status_codes[i].code == code) | |
| return http_status_codes[i].text; | |
| return NULL; | |
| } | |
| extern const char *get_http_method_string(http_request_method_t method) | |
| { | |
| if ((method <= HTTP_REQUEST_INVALID) || | |
| (method >= HTTP_REQUEST_INVALID_MAX)) | |
| return NULL; | |
| return method_strings[method].uc_text; | |
| } | |
| extern const char *get_http_method_string_lc(http_request_method_t method) | |
| { | |
| if ((method <= HTTP_REQUEST_INVALID) || | |
| (method >= HTTP_REQUEST_INVALID_MAX)) | |
| return NULL; | |
| return method_strings[method].lc_text; | |
| } | |
| extern http_request_method_t get_http_method(const char *str) | |
| { | |
| if (!str || !str[0]) | |
| return HTTP_REQUEST_INVALID; | |
| for (int i = 0; i < ARRAY_SIZE(method_strings); i++) | |
| if (method_strings[i].method && | |
| !xstrcasecmp(method_strings[i].lc_text, str)) | |
| return method_strings[i].method; | |
| return HTTP_REQUEST_INVALID; | |
| } | |
| extern void url_free_members(url_t *url) | |
| { | |
| url->scheme = URL_SCHEME_INVALID; | |
| xfree(url->host); | |
| xfree(url->port); | |
| xfree(url->user); | |
| xfree(url->path); | |
| xfree(url->query); | |
| xfree(url->fragment); | |
| } | |
| extern void url_copy_members(url_t *dst, const url_t *src) | |
| { | |
| url_free_members(dst); | |
| dst->scheme = src->scheme; | |
| dst->host = xstrdup(src->host); | |
| dst->port = xstrdup(src->port); | |
| dst->user = xstrdup(src->user); | |
| dst->path = xstrdup(src->path); | |
| dst->query = xstrdup(src->query); | |
| dst->fragment = xstrdup(src->fragment); | |
| } | |
| extern http_header_t *http_header_new(const char *name, const char *value) | |
| { | |
| http_header_t *header = NULL; | |
| header = xmalloc(sizeof(*header)); | |
| header->magic = HTTP_HEADER_MAGIC; | |
| header->name = xstrdup(name); | |
| header->value = xstrdup(value); | |
| return header; | |
| } | |
| extern void free_http_header(http_header_t *header) | |
| { | |
| xassert(header->magic == HTTP_HEADER_MAGIC); | |
| xfree(header->name); | |
| xfree(header->value); | |
| header->magic = ~HTTP_HEADER_MAGIC; | |
| xfree(header); | |
| } | |
| /* find operator against http_header_t */ | |
| static int _http_header_find_key(void *x, void *y) | |
| { | |
| const http_header_t *entry = x; | |
| const char *key = y; | |
| xassert(entry->name); | |
| xassert(entry->magic == HTTP_HEADER_MAGIC); | |
| if (key == NULL) | |
| return 0; | |
| /* case insensitive compare per rfc2616:4.2 */ | |
| if (entry->name && !xstrcasecmp(entry->name, key)) | |
| return 1; | |
| else | |
| return 0; | |
| } | |
| extern const char *find_http_header(list_t *headers, const char *name) | |
| { | |
| http_header_t *header = NULL; | |
| if (!headers || !name) | |
| return NULL; | |
| header = (http_header_t *) list_find_first(headers, | |
| _http_header_find_key, | |
| (void *) name); | |
| if (header) { | |
| xassert(header->magic == HTTP_HEADER_MAGIC); | |
| return header->value; | |
| } | |
| return NULL; | |
| } |