/*
 * Copyright (c) 2014 Joris Vink <joris@coders.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/types.h>

#include <ctype.h>

#include "kore.h"
#include "http.h"

TAILQ_HEAD(, kore_auth)		auth_list;

void
kore_auth_init(void)
{
	TAILQ_INIT(&auth_list);
}

int
kore_auth_new(const char *name)
{
	struct kore_auth	*auth;

	if (kore_auth_lookup(name) != NULL)
		return (KORE_RESULT_ERROR);

	auth = kore_malloc(sizeof(*auth));
	auth->type = 0;
	auth->value = NULL;
	auth->redirect = NULL;
	auth->validator = NULL;
	auth->name = kore_strdup(name);

	TAILQ_INSERT_TAIL(&auth_list, auth, list);

	return (KORE_RESULT_OK);
}

int
kore_auth_run(struct http_request *req, struct kore_auth *auth)
{
	int		r;

	switch (auth->type) {
	case KORE_AUTH_TYPE_COOKIE:
		r = kore_auth_cookie(req, auth);
		break;
	case KORE_AUTH_TYPE_HEADER:
		r = kore_auth_header(req, auth);
		break;
	case KORE_AUTH_TYPE_REQUEST:
		r = kore_auth_request(req, auth);
		break;
	default:
		kore_log(LOG_NOTICE, "unknown auth type %d", auth->type);
		return (KORE_RESULT_ERROR);
	}

	switch (r) {
	case KORE_RESULT_OK:
		req->flags |= HTTP_REQUEST_AUTHED;
		/* FALLTHROUGH */
	case KORE_RESULT_RETRY:
		return (r);
	default:
		break;
	}

	/* Authentication types of "request" send their own HTTP responses. */
	if (auth->type == KORE_AUTH_TYPE_REQUEST)
		return (r);

	if (auth->redirect == NULL) {
		http_response(req, HTTP_STATUS_FORBIDDEN, NULL, 0);
		return (KORE_RESULT_ERROR);
	}

	http_response_header(req, "location", auth->redirect);
	http_response(req, HTTP_STATUS_FOUND, NULL, 0);

	return (KORE_RESULT_ERROR);
}

int
kore_auth_cookie(struct http_request *req, struct kore_auth *auth)
{
	const char	*hdr;
	int		i, v;
	size_t		len, slen;
	char		*value, *c, *cookie, *cookies[HTTP_MAX_COOKIES];

	if (!http_request_header(req, "cookie", &hdr))
		return (KORE_RESULT_ERROR);

	cookie = kore_strdup(hdr);

	slen = strlen(auth->value);
	v = kore_split_string(cookie, ";", cookies, HTTP_MAX_COOKIES);
	for (i = 0; i < v; i++) {
		for (c = cookies[i]; isspace(*(unsigned char *)c); c++)
			;

		len = MIN(slen, strlen(cookies[i]));
		if (!strncmp(c, auth->value, len))
			break;
	}

	if (i == v) {
		kore_free(cookie);
		return (KORE_RESULT_ERROR);
	}

	c = cookies[i];
	if ((value = strchr(c, '=')) == NULL) {
		kore_free(cookie);
		return (KORE_RESULT_ERROR);
	}

	i = kore_validator_check(req, auth->validator, ++value);
	kore_free(cookie);

	return (i);
}

int
kore_auth_header(struct http_request *req, struct kore_auth *auth)
{
	const char	*header;

	if (!http_request_header(req, auth->value, &header))
		return (KORE_RESULT_ERROR);

	return (kore_validator_check(req, auth->validator, header));
}

int
kore_auth_request(struct http_request *req, struct kore_auth *auth)
{
	int		ret;

	req->flags |= HTTP_VALIDATOR_IS_REQUEST;
	ret = kore_validator_check(req, auth->validator, req);
	req->flags &= ~HTTP_VALIDATOR_IS_REQUEST;

	return (ret);
}

struct kore_auth *
kore_auth_lookup(const char *name)
{
	struct kore_auth	*auth;

	TAILQ_FOREACH(auth, &auth_list, list) {
		if (!strcmp(auth->name, name))
			return (auth);
	}

	return (NULL);
}
