/*
 * http_kv - HTTP version, method, status key-value string mapping
 *
 * Fully-rewritten from original
 * Copyright(c) 2018 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
 * License: BSD 3-clause (same as lighttpd)
 */
#include "first.h"

#include "http_kv.h"
#include "buffer.h"

#include <string.h>

typedef struct {
	int key;
	unsigned int vlen;
	const char *value;
} keyvalue;

static const keyvalue http_versions[] = {
	{ HTTP_VERSION_2,   CONST_LEN_STR("HTTP/2.0") }, /* SERVER_PROTOCOL */
	{ HTTP_VERSION_1_1, CONST_LEN_STR("HTTP/1.1") },
	{ HTTP_VERSION_1_0, CONST_LEN_STR("HTTP/1.0") },
	{ HTTP_VERSION_UNSET, 0, NULL }
};

static const keyvalue http_methods[] = {
	{ HTTP_METHOD_GET,                 CONST_LEN_STR("GET") },
	{ HTTP_METHOD_HEAD,                CONST_LEN_STR("HEAD") },
	{ HTTP_METHOD_POST,                CONST_LEN_STR("POST") },
	{ HTTP_METHOD_PUT,                 CONST_LEN_STR("PUT") },
	{ HTTP_METHOD_DELETE,              CONST_LEN_STR("DELETE") },
	{ HTTP_METHOD_CONNECT,             CONST_LEN_STR("CONNECT") },
	{ HTTP_METHOD_OPTIONS,             CONST_LEN_STR("OPTIONS") },
	{ HTTP_METHOD_TRACE,               CONST_LEN_STR("TRACE") },
	{ HTTP_METHOD_ACL,                 CONST_LEN_STR("ACL") },
	{ HTTP_METHOD_BASELINE_CONTROL,    CONST_LEN_STR("BASELINE-CONTROL") },
	{ HTTP_METHOD_BIND,                CONST_LEN_STR("BIND") },
	{ HTTP_METHOD_CHECKIN,             CONST_LEN_STR("CHECKIN") },
	{ HTTP_METHOD_CHECKOUT,            CONST_LEN_STR("CHECKOUT") },
	{ HTTP_METHOD_COPY,                CONST_LEN_STR("COPY") },
	{ HTTP_METHOD_LABEL,               CONST_LEN_STR("LABEL") },
	{ HTTP_METHOD_LINK,                CONST_LEN_STR("LINK") },
	{ HTTP_METHOD_LOCK,                CONST_LEN_STR("LOCK") },
	{ HTTP_METHOD_MERGE,               CONST_LEN_STR("MERGE") },
	{ HTTP_METHOD_MKACTIVITY,          CONST_LEN_STR("MKACTIVITY") },
	{ HTTP_METHOD_MKCALENDAR,          CONST_LEN_STR("MKCALENDAR") },
	{ HTTP_METHOD_MKCOL,               CONST_LEN_STR("MKCOL") },
	{ HTTP_METHOD_MKREDIRECTREF,       CONST_LEN_STR("MKREDIRECTREF") },
	{ HTTP_METHOD_MKWORKSPACE,         CONST_LEN_STR("MKWORKSPACE") },
	{ HTTP_METHOD_MOVE,                CONST_LEN_STR("MOVE") },
	{ HTTP_METHOD_ORDERPATCH,          CONST_LEN_STR("ORDERPATCH") },
	{ HTTP_METHOD_PATCH,               CONST_LEN_STR("PATCH") },
	{ HTTP_METHOD_PROPFIND,            CONST_LEN_STR("PROPFIND") },
	{ HTTP_METHOD_PROPPATCH,           CONST_LEN_STR("PROPPATCH") },
	{ HTTP_METHOD_REBIND,              CONST_LEN_STR("REBIND") },
	{ HTTP_METHOD_REPORT,              CONST_LEN_STR("REPORT") },
	{ HTTP_METHOD_SEARCH,              CONST_LEN_STR("SEARCH") },
	{ HTTP_METHOD_UNBIND,              CONST_LEN_STR("UNBIND") },
	{ HTTP_METHOD_UNCHECKOUT,          CONST_LEN_STR("UNCHECKOUT") },
	{ HTTP_METHOD_UNLINK,              CONST_LEN_STR("UNLINK") },
	{ HTTP_METHOD_UNLOCK,              CONST_LEN_STR("UNLOCK") },
	{ HTTP_METHOD_UPDATE,              CONST_LEN_STR("UPDATE") },
	{ HTTP_METHOD_UPDATEREDIRECTREF,   CONST_LEN_STR("UPDATEREDIRECTREF") },
	{ HTTP_METHOD_VERSION_CONTROL,     CONST_LEN_STR("VERSION-CONTROL") },

	{ HTTP_METHOD_PRI,                 CONST_LEN_STR("PRI") },
	{ HTTP_METHOD_UNSET, 0, NULL }
};

/* https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml */
static const keyvalue http_status[] = {
	{ 100, CONST_LEN_STR("100 Continue") },
	{ 101, CONST_LEN_STR("101 Switching Protocols") },
	{ 102, CONST_LEN_STR("102 Processing") }, /* WebDAV */
	{ 103, CONST_LEN_STR("103 Early Hints") },
	{ 200, CONST_LEN_STR("200 OK") },
	{ 201, CONST_LEN_STR("201 Created") },
	{ 202, CONST_LEN_STR("202 Accepted") },
	{ 203, CONST_LEN_STR("203 Non-Authoritative Information") },
	{ 204, CONST_LEN_STR("204 No Content") },
	{ 205, CONST_LEN_STR("205 Reset Content") },
	{ 206, CONST_LEN_STR("206 Partial Content") },
	{ 207, CONST_LEN_STR("207 Multi-status") }, /* WebDAV */
	{ 208, CONST_LEN_STR("208 Already Reported") },
	{ 226, CONST_LEN_STR("226 IM Used") },
	{ 300, CONST_LEN_STR("300 Multiple Choices") },
	{ 301, CONST_LEN_STR("301 Moved Permanently") },
	{ 302, CONST_LEN_STR("302 Found") },
	{ 303, CONST_LEN_STR("303 See Other") },
	{ 304, CONST_LEN_STR("304 Not Modified") },
	{ 305, CONST_LEN_STR("305 Use Proxy") },
	{ 306, CONST_LEN_STR("306 (Unused)") },
	{ 307, CONST_LEN_STR("307 Temporary Redirect") },
	{ 308, CONST_LEN_STR("308 Permanent Redirect") },
	{ 400, CONST_LEN_STR("400 Bad Request") },
	{ 401, CONST_LEN_STR("401 Unauthorized") },
	{ 402, CONST_LEN_STR("402 Payment Required") },
	{ 403, CONST_LEN_STR("403 Forbidden") },
	{ 404, CONST_LEN_STR("404 Not Found") },
	{ 405, CONST_LEN_STR("405 Method Not Allowed") },
	{ 406, CONST_LEN_STR("406 Not Acceptable") },
	{ 407, CONST_LEN_STR("407 Proxy Authentication Required") },
	{ 408, CONST_LEN_STR("408 Request Timeout") },
	{ 409, CONST_LEN_STR("409 Conflict") },
	{ 410, CONST_LEN_STR("410 Gone") },
	{ 411, CONST_LEN_STR("411 Length Required") },
	{ 412, CONST_LEN_STR("412 Precondition Failed") },
	{ 413, CONST_LEN_STR("413 Payload Too Large") },
	{ 414, CONST_LEN_STR("414 URI Too Long") },
	{ 415, CONST_LEN_STR("415 Unsupported Media Type") },
	{ 416, CONST_LEN_STR("416 Range Not Satisfiable") },
	{ 417, CONST_LEN_STR("417 Expectation Failed") },
	{ 421, CONST_LEN_STR("421 Misdirected Request") }, /* RFC 7540 */
	{ 422, CONST_LEN_STR("422 Unprocessable Entity") }, /* WebDAV */
	{ 423, CONST_LEN_STR("423 Locked") }, /* WebDAV */
	{ 424, CONST_LEN_STR("424 Failed Dependency") }, /* WebDAV */
	{ 426, CONST_LEN_STR("426 Upgrade Required") }, /* TLS */
	{ 428, CONST_LEN_STR("428 Precondition Required") },
	{ 429, CONST_LEN_STR("429 Too Many Requests") },
	{ 431, CONST_LEN_STR("431 Request Header Fields Too Large") },
	{ 451, CONST_LEN_STR("451 Unavailable For Legal Reasons") },
	{ 500, CONST_LEN_STR("500 Internal Server Error") },
	{ 501, CONST_LEN_STR("501 Not Implemented") },
	{ 502, CONST_LEN_STR("502 Bad Gateway") },
	{ 503, CONST_LEN_STR("503 Service Unavailable") },
	{ 504, CONST_LEN_STR("504 Gateway Timeout") },
	{ 505, CONST_LEN_STR("505 HTTP Version Not Supported") },
	{ 506, CONST_LEN_STR("506 Variant Also Negotiates") },
	{ 507, CONST_LEN_STR("507 Insufficient Storage") }, /* WebDAV */
	{ 508, CONST_LEN_STR("508 Loop Detected") },
	{ 510, CONST_LEN_STR("510 Not Extended") },
	{ 511, CONST_LEN_STR("511 Network Authentication Required") },

	{ -1, 0, NULL }
};


__attribute_noinline__
__attribute_pure__
static const keyvalue * keyvalue_from_key (const keyvalue *kv, const int k)
{
    /*(expects sentinel to have key == -1 and value == NULL)*/
    while (kv->key != k && kv->key != -1) ++kv;
    return kv;
}


__attribute_pure__
static int keyvalue_get_key(const keyvalue *kv, const char * const s, const unsigned int slen)
{
    /*(expects sentinel to have key == -1 and vlen == 0)*/
    while (kv->vlen && (kv->vlen != slen || 0 != memcmp(kv->value, s, slen)))
        ++kv;
    return kv->key;
}


const char *get_http_version_name(int i) {
    return keyvalue_from_key(http_versions, i)->value;
}

const char *get_http_status_name(int i) {
    return keyvalue_from_key(http_status, i)->value;
}

const char *get_http_method_name(http_method_t i) {
    return keyvalue_from_key(http_methods, i)->value;
}

#if 0 /*(unused)*/
int get_http_version_key(const char *s, size_t slen) {
    return keyvalue_get_key(http_versions, s, (unsigned int)slen);
}
#endif

http_method_t get_http_method_key(const char *s, const size_t slen) {
    if (slen == 3 && s[0] == 'G' && s[1] == 'E' && s[2] == 'T')
        return HTTP_METHOD_GET;
    return (http_method_t)keyvalue_get_key(http_methods, s, (unsigned int)slen);
}


void http_status_append(buffer * const b, const int status) {
    if (200 == status) { /*(short-circuit common case)*/
        buffer_append_string_len(b, CONST_STR_LEN("200 OK"));
        return;
    }

    const keyvalue * const kv = keyvalue_from_key(http_status, status);
    if (__builtin_expect( (0 != kv->vlen), 1))
        buffer_append_string_len(b, kv->value, kv->vlen);
    else {
        buffer_append_int(b, status);
        buffer_append_string_len(b, CONST_STR_LEN(" "));
    }
}

void http_method_append(buffer * const b, const http_method_t method) {
    const keyvalue * const kv = keyvalue_from_key(http_methods, method);
    if (__builtin_expect( (0 != kv->vlen), 1))
        buffer_append_string_len(b, kv->value, kv->vlen);
}

void http_version_append(buffer * const b, const http_version_t version) {
    const keyvalue * const kv = keyvalue_from_key(http_versions, version);
    if (__builtin_expect( (0 != kv->vlen), 1))
        buffer_append_string_len(b, kv->value, kv->vlen);
}
