| /*****************************************************************************\ |
| * 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 "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; |
| |
| static const http_status_code_txt_t http_status_codes[] = { |
| { HTTP_STATUS_CODE_CONTINUE, "CONTINUE" }, |
| { HTTP_STATUS_CODE_SWITCH_PROTOCOLS, "SWITCH PROTOCOLS" }, |
| { HTTP_STATUS_CODE_SUCCESS_OK, "OK" }, |
| { HTTP_STATUS_CODE_SUCCESS_CREATED, "CREATED" }, |
| { HTTP_STATUS_CODE_SUCCESS_ACCEPTED, "ACCEPTED" }, |
| { HTTP_STATUS_CODE_SUCCESS_NON_AUTHORITATIVE, |
| "OK (NON AUTHORITATIVE)" }, |
| { HTTP_STATUS_CODE_SUCCESS_NO_CONTENT, "NO CONTENT" }, |
| { HTTP_STATUS_CODE_SUCCESS_RESET_CONNECTION, "RESET CONNECTION" }, |
| { HTTP_STATUS_CODE_SUCCESS_PARTIAL_CONTENT, "PARTIAL CONTENT" }, |
| { HTTP_STATUS_CODE_REDIRECT_MULTIPLE_CHOICES, |
| "REDIRECT MULTIPLE CHOICES" }, |
| { HTTP_STATUS_CODE_REDIRECT_MOVED_PERMANENTLY, "MOVED PERMANENTLY" }, |
| { HTTP_STATUS_CODE_REDIRECT_FOUND, "REDIRECT FOUND" }, |
| { HTTP_STATUS_CODE_REDIRECT_SEE_OTHER, "REDIRECT SEE OTHER" }, |
| { HTTP_STATUS_CODE_REDIRECT_NOT_MODIFIED, "NOT MODIFIED" }, |
| { HTTP_STATUS_CODE_REDIRECT_USE_PROXY, "USE PROXY" }, |
| { HTTP_STATUS_CODE_REDIRECT_TEMP_REDIRCT, "TEMP REDIRECT" }, |
| { HTTP_STATUS_CODE_ERROR_BAD_REQUEST, "BAD REQUEST" }, |
| { HTTP_STATUS_CODE_ERROR_UNAUTHORIZED, "UNAUTHORIZED" }, |
| { HTTP_STATUS_CODE_ERROR_PAYMENT_REQUIRED, "PAYMENT REQUIRED" }, |
| { HTTP_STATUS_CODE_ERROR_FORBIDDEN, "FORBIDDEN" }, |
| { HTTP_STATUS_CODE_ERROR_NOT_FOUND, "NOT FOUND" }, |
| { HTTP_STATUS_CODE_ERROR_METHOD_NOT_ALLOWED, "NOT ALLOWED" }, |
| { HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE, "NOT ACCEPTABLE" }, |
| { HTTP_STATUS_CODE_ERROR_PROXY_AUTH_REQ, |
| "PROXY AUTHENTICATION REQUIRED" }, |
| { HTTP_STATUS_CODE_ERROR_REQUEST_TIMEOUT, "REQUEST TIMEOUT" }, |
| { HTTP_STATUS_CODE_ERROR_CONFLICT, "CONFLICT" }, |
| { HTTP_STATUS_CODE_ERROR_GONE, "GONE" }, |
| { HTTP_STATUS_CODE_ERROR_LENGTH_REQUIRED, "LENGTH REQUIRED" }, |
| { HTTP_STATUS_CODE_ERROR_PRECONDITION_FAILED, "PRECONDITION FAILED" }, |
| { HTTP_STATUS_CODE_ERROR_ENTITY_TOO_LARGE, "ENTITY TOO LARGE" }, |
| { HTTP_STATUS_CODE_ERROR_URI_TOO_LONG, "URI TOO LONG" }, |
| { HTTP_STATUS_CODE_ERROR_UNSUPPORTED_MEDIA_TYPE, |
| "UNSUPPORTED MEDIA TYPE" }, |
| { HTTP_STATUS_CODE_ERROR_REQUEST_RANGE_UNSATISFIABLE, |
| "REQUEST RANGE UNJUSTIFIABLE" }, |
| { HTTP_STATUS_CODE_ERROR_EXPECTATION_FAILED, "EXPECTATION FAILED" }, |
| { HTTP_STATUS_CODE_ERROR_IM_A_TEAPOT, "I'm a Teapot" }, /* rfc7168 */ |
| { HTTP_STATUS_CODE_ERROR_MISDIRECT_REQUESTED, |
| "MISDIRECTED REQUEST" }, /* rfc9110 15.5.20 */ |
| { HTTP_STATUS_CODE_ERROR_UNPROCESSABLE_CONTENT, |
| "UNPROCESSABLE CONTENT" }, /* rfc9110 15.5.21 */ |
| { HTTP_STATUS_CODE_ERROR_UPGRADE_REQUIRED, |
| "UPGRADE REQUIRED" }, /* rfc7231 6.5.15 */ |
| { HTTP_STATUS_CODE_SRVERR_INTERNAL, "INTERNAL ERROR" }, |
| { HTTP_STATUS_CODE_SRVERR_NOT_IMPLEMENTED, "NOT IMPLEMENTED" }, |
| { HTTP_STATUS_CODE_SRVERR_BAD_GATEWAY, "BAD GATEWAY" }, |
| { HTTP_STATUS_CODE_SRVERR_SERVICE_UNAVAILABLE, "SERVICE UNAVAILABLE" }, |
| { HTTP_STATUS_CODE_SRVERR_GATEWAY_TIMEOUT, "GATEWAY TIMEOUT" }, |
| { HTTP_STATUS_CODE_SRVERR_HTTP_VERSION_NOT_SUPPORTED, |
| "HTTP VERSION NOT SUPPORTED" }, |
| { HTTP_STATUS_CODE_SRVERR_VARIANT_ALSO_NEGOTIATES, |
| "Variant Also Negotiates" }, |
| { HTTP_STATUS_CODE_SRVERR_INSUFFICENT_STORAGE, "Insufficient Storage" }, |
| { HTTP_STATUS_CODE_SRVERR_LOOP_DETECTED, "Loop Detected" }, |
| { HTTP_STATUS_CODE_SRVERR_NOT_EXTENDED, "Not Extended" }, |
| { HTTP_STATUS_CODE_SRVERR_NETWORK_AUTH_REQ, |
| "Network Authentication Required" }, |
| { HTTP_STATUS_CODE_DEFAULT, "default" }, |
| }; |
| |
| static const struct { |
| http_request_method_t method; |
| const char *uc_text; |
| const char *lc_text; |
| } method_strings[] = { |
| { HTTP_REQUEST_GET, "GET", "get" }, |
| { HTTP_REQUEST_POST, "POST", "post" }, |
| { HTTP_REQUEST_PUT, "PUT", "put" }, |
| { HTTP_REQUEST_DELETE, "DELETE", "delete" }, |
| { HTTP_REQUEST_OPTIONS, "OPTIONS", "options" }, |
| { HTTP_REQUEST_HEAD, "HEAD", "head" }, |
| { HTTP_REQUEST_PATCH, "PATCH", "patch" }, |
| { HTTP_REQUEST_TRACE, "TRACE", "trace" }, |
| }; |
| |
| #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 |
| |
| 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"); |
| } |
| |
| /* |
| * chars that can pass without decoding. |
| * rfc3986: unreserved characters. |
| */ |
| static bool _is_valid_url_char(char buffer) |
| { |
| return (isxdigit(buffer) || isalpha(buffer) || buffer == '~' || |
| buffer == '-' || buffer == '.' || buffer == '_'); |
| } |
| |
| extern unsigned char url_decode_escape_seq(const char *ptr) |
| { |
| if (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, 3), |
| "%s: invalid URL escape sequence: %s", __func__, |
| ptr); |
| return '\0'; |
| } |
| } |
| |
| static int _add_path(data_t *d, char **buffer, bool convert_types) |
| { |
| if (!xstrcasecmp(*buffer, ".")) { |
| debug5("%s: ignoring path . entry", __func__); |
| } else if (!xstrcasecmp(*buffer, "..")) { |
| //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, *buffer); |
| |
| if (convert_types) |
| (void) data_convert_type(c, DATA_TYPE_NONE); |
| |
| xfree(*buffer); |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern data_t *parse_url_path(const char *path, bool convert_types, |
| bool allow_templates) |
| { |
| int rc = SLURM_SUCCESS; |
| data_t *d = data_set_list(data_new()); |
| char *buffer = NULL; |
| |
| /* extract each word */ |
| for (const char *ptr = path; !rc && *ptr != '\0'; ++ptr) { |
| if (_is_valid_url_char(*ptr)) { |
| xstrcatchar(buffer, *ptr); |
| 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 = SLURM_ERROR; |
| break; |
| } else { |
| /* find end of template */ |
| char *end = xstrstr(ptr, "}"); |
| |
| if (!end) { |
| debug("%s: missing terminated OAS template character: }", |
| __func__); |
| rc = SLURM_ERROR; |
| break; |
| } |
| |
| xstrncat(buffer, ptr, (end - ptr + 1)); |
| ptr = end; |
| break; |
| } |
| case '%': /* rfc3986 */ |
| { |
| const char c = url_decode_escape_seq(ptr); |
| if (c != '\0') { |
| /* shift past the hex value */ |
| ptr += 2; |
| |
| xstrcatchar(buffer, c); |
| } else { |
| debug("%s: invalid URL escape sequence: %s", |
| __func__, ptr); |
| rc = SLURM_ERROR; |
| break; |
| } |
| break; |
| } |
| case '/': /* rfc3986 */ |
| if (buffer != NULL) |
| rc = _add_path(d, &buffer, convert_types); |
| break; |
| default: |
| debug("%s: unexpected URL character: %c", |
| __func__, *ptr); |
| rc = SLURM_ERROR; |
| } |
| } |
| |
| /* last part of path */ |
| if (!rc && buffer != NULL) |
| rc = _add_path(d, &buffer, convert_types); |
| |
| if (rc) { |
| FREE_NULL_DATA(d); |
| return NULL; |
| } |
| |
| return d; |
| } |
| |
| extern http_status_code_t get_http_status_code(const char *str) |
| { |
| if (isdigit(str[0])) { |
| uint64_t n = slurm_atoul(str); |
| |
| if (!n || (n > HTTP_STATUS_CODE_DEFAULT)) |
| return HTTP_STATUS_NONE; |
| |
| 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_NONE; |
| } |
| |
| 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) |
| { |
| for (int i = 0; i < ARRAY_SIZE(method_strings); i++) |
| if (method_strings[i].method == method) |
| return method_strings[i].uc_text; |
| |
| return "INVALID"; |
| } |
| |
| extern const char *get_http_method_string_lc(http_request_method_t method) |
| { |
| for (int i = 0; i < ARRAY_SIZE(method_strings); i++) |
| if (method_strings[i].method == method) |
| return method_strings[i].lc_text; |
| |
| return "INVALID"; |
| } |
| |
| 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 (!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 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; |
| } |