/*
 * http.c - Minimal HTTP/1.1 request parser.
 *
 * Parses only what is needed for the WebSocket upgrade handshake:
 * the request line (method + path) and the headers.
 */

#include "http.h"
#include "log.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

/* Finds and terminates the next line */
static char *next_line(char **cursor)
{
	char *start = *cursor;
	char *crlf;

	crlf = strstr(start, "\r\n");
	if (!crlf)
		return NULL;

	*crlf = '\0';
	*cursor = crlf + 2;
	return start;
}

/* Trims whitespace from both ends of a string. */
static char *trim(char *s)
{
	char *end;

	while (isspace((unsigned char)*s))
		s++;

	end = s + strlen(s);
	while (end > s && isspace((unsigned char)*(end - 1)))
		end--;
	*end = '\0';

	return s;
}

static int add_header(struct http_request *req,
		      const char *name, const char *value)
{
	if (req->nr_headers >= req->max_headers) {
		int new_max = req->max_headers * 2;
		struct http_header *tmp;

		tmp = realloc(req->headers,
			      (size_t)new_max * sizeof(*tmp));
		if (!tmp)
			return -1;

		req->headers     = tmp;
		req->max_headers = new_max;
	}

	req->headers[req->nr_headers].name  = strdup(name);
	req->headers[req->nr_headers].value = strdup(value);

	if (!req->headers[req->nr_headers].name ||
	    !req->headers[req->nr_headers].value)
		return -1;

	req->nr_headers++;
	return 0;
}

int http_request_init(struct http_request *req)
{
	memset(req, 0, sizeof(*req));

	req->max_headers = HTTP_MAX_HEADERS;
	req->headers = malloc((size_t)req->max_headers * sizeof(*req->headers));
	if (!req->headers)
		return -1;

	return 0;
}

void http_request_free(struct http_request *req)
{
	int i;

	free(req->method);
	free(req->path);

	for (i = 0; i < req->nr_headers; i++) {
		free(req->headers[i].name);
		free(req->headers[i].value);
	}

	free(req->headers);
	memset(req, 0, sizeof(*req));
}

int http_parse_request(char *buf, size_t len,
		       struct http_request *req)
{
	char *start = buf, *end = buf + len;
	char *cursor, *line;
	char *saveptr;
	char *token;

	LOG_DBG("http_parse_request (len=%zu): %s", len, buf);

	cursor = start;

	// Parse HTTP request head: <METHOD> <PATH> HTTP/1.1 --- */
	line = next_line(&cursor);
	if (cursor >= end)
		goto fail;

	if (!line) {
		LOG_ERR("Malformed HTTP request: no request line");
		goto fail;
	}

	token = strtok_r(line, " ", &saveptr);
	if (!token)
		goto fail;
	req->method = strdup(token);

	token = strtok_r(NULL, " ", &saveptr);
	if (!token)
		goto fail;
	req->path = strdup(token);

	// Skip HTTP version token
	token = strtok_r(NULL, " ", &saveptr);
	if (!token)
		goto fail;

	if (!req->method || !req->path)
		goto fail;

	// HTTP headers
	while (cursor < end && (line = next_line(&cursor)) != NULL) {
		char *colon;

		// Empty line: end of headers
		if (*line == '\0')
			break;

		colon = strchr(line, ':');
		if (!colon)
			continue; // skip malformed header

		*colon = '\0';

		if (add_header(req, trim(line), trim(colon + 1)) < 0)
			goto fail;
	}

	LOG_DBG("HTTP %s %s (%d headers)",
		req->method, req->path, req->nr_headers);

	return 0;

fail:
	return -1;
}

const char *http_get_header(const struct http_request *req, const char *name)
{
	for (int i = 0; i < req->nr_headers; i++) {
		if (strcasecmp(req->headers[i].name, name) == 0)
			return req->headers[i].value;
	}

	return NULL;
}
