blob: cd68e920500c43cebe14feab5d518b59a95b0dab [file] [log] [blame]
/*****************************************************************************\
* 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;
}