/*****************************************************************************\
 *  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_REST_UNKNOWN_URL, 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(ESLURM_REST_BAD_REQUEST, HTTP_STATUS_CODE_ERROR_BAD_REQUEST),
	// 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_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");
}

/*
 * 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, 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++) {
		if (_is_valid_url_char(*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;
			}
			break;
		}
		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;

	/* If error comes from the slurmctld or slurmdbd, the REST API
	 * query is formatted correctly but may be semantically
	 * incorrect; Respond with HTTP code 422 */
	if (((error >= ESLURM_INVALID_PARTITION_NAME) &&
	     (error <= ESLURM_INVALID_SLUID)) ||
	    ((error >= ESLURM_DB_CONNECTION) && (error <= ESLURM_NO_RPC_STATS)))
		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;
}
