
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <event.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <glib/glist.h>

#include "libhttp.h"

//#define DEBUG
#ifdef DEBUG
#define Dprintf		printf
#else
#define Dprintf( a... )
#endif

int http_get_cmd(char *cmd) {
	if (strcasecmp("GET", cmd) == 0)
		return HC_CMD_GET;
	if (strcasecmp("POST", cmd) == 0)
		return HC_CMD_POST;
	if (strcasecmp("HEAD", cmd) == 0)
		return HC_CMD_HEAD;

	return HC_CMD_UNKNOWN;
}

/* Split line into string elements seperated by space or tab */
static int split_string_by_space(char *input, char **elem, int max) {
	int	no;
	char	*i=input;

	for(no=0;no<max;) {
		/* Skip space or tab */
		while(*i == ' ' || *i == '\t' || *i == '\r' || *i == '\n')
			*i++=0x0;
		if(*i == 0x0)
			break;
		/* Store pointer */
		elem[no++]=i;
		/* Skip non space */
		while(*i != ' ' && *i != '\t' && *i != 0x0)
			i++;
		/* End of String ? */
		if(*i == 0x0)
			break;
	}

	return no;
}

/*
 * Parse a string like HTTP/1.0 or HTTP/1.1 and return
 * the protocol and version
 *
 *
 * HP_UNKNOWN		- Unknown protocol
 * HP_HTTP		- Seems to be HTTP without/with broken/with unknown version
 * HP_HTTP10		- HTTP/1.0
 * HP_HTTP11		- HTTP/1.1
 *
 */
static int http_parse_proto(char *pstr) {
	char		*pver, *dotptr, *endptr;
	int		major, minor;

	/* Check if we are dealing with HTTP */
	if (strncasecmp(pstr, "HTTP", 4) != 0)
		return HP_UNKNOWN;

	pver=strchr(pstr, '/');
	if (!pver)
		return HP_HTTP;

	major=strtol(++pver, &dotptr, 10);

	/* 1.0 or 1.1 */
	if (*dotptr != '.')
		return HP_HTTP;

	minor=strtol(++dotptr, &endptr, 10);

	/* String should end after [major].[minor] */
	if (*endptr != 0x0)
		return HP_HTTP;

	Dprintf("%s: major %d minor %d\n", __FUNCTION__, major, minor);

	if (major == 1 && minor == 0)
		return HP_HTTP10;
	if (major == 1 && minor == 1)
		return HP_HTTP11;

	return HP_HTTP;
}

/*
 * Read the first line - PUT/GET/POST etc ...
 *
 * GET <url> HTTP/<version>
 *
 */
static int http_parse_command_url(struct http_connection *hc, struct evbuffer *input) {
	char	*p=(char *) EVBUFFER_DATA(input);
	char	*elem[10];
	int	no;

	no=split_string_by_space(p, elem, 10);

	/* Need "CMD URL PROTO/VERSION" which makes it 3 parms */
	if (no != 3)
		return 0;

	if ((hc->cmd=http_get_cmd(elem[0])) == HC_CMD_UNKNOWN)
		return 0;

	hc->url=strdup(elem[1]);
	hc->proto=http_parse_proto(elem[2]);

	if (hc->proto == HP_HTTP11)
		hc->keepalive=1;
	else
		hc->keepalive=0;

	return 1;
}

static int http_parse_attrib(struct http_connection *hc, struct evbuffer *input) {
	char	*p=(char *) EVBUFFER_DATA(input);
	struct	http_attrib	*ha;
	char	*token, *value;

	token=p;

	while(*p != ':' && *p != ' ' && *p != 0x0)
		p++;

	/* Did we find the colon as delimiter ?
	 * If not something went really wrong - terminate the connection
	 */
	if (*p == ':')
		*p++=0x0;
	else
		return 0;

	while(*p == ' ' || *p == '\t')
		p++;

	value=p;

	ha=malloc(sizeof(struct http_attrib));
	ha->token=strdup(token);
	ha->value=strdup(value);

	hc->attrib=g_list_append(hc->attrib, ha);

	Dprintf("%s: %s %s\n", __FUNCTION__, token, value);

	return 1;
}

/*
 * HTTP/1.1 404 Not Found
 * Date: Wed, 17 May 2006 17:33:00 GMT
 * Server: Apache/2.0.54 (Debian GNU/Linux)
 * Content-Length: 310
 * Connection: close
 * Content-Type: text/html; charset=iso-8859-1
 *
 */
static char *http_static_msg_404 =
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\"> \
<html><head> \
<title>404 Not Found</title> \
</head><body> \
<h1>404 Not Found</h1> \
</body> \
</html>";

int http_url_handler_404(struct http_connection *hc, int cbtype, void *arg) {
	int		err;

	Dprintf("%s: cbtype %d\n", __FUNCTION__, cbtype);

	/* Answer with 404 page on QUERY */
	if (cbtype == HCB_QUERY) {
		err=http_return_simple(hc,
				"404 Not found",
				"text/html",
				http_static_msg_404,
				strlen(http_static_msg_404));
		return 1;
	}

	/*
	 * Drop connection if socket gets writeable again
	 * or we have an error
	 */
	http_request_end(hc);

	return 1;
}


size_t http_get_queue(struct http_connection *hc) {
	return EVBUFFER_LENGTH(hc->bev->output);
}

/*
 * Function to return an http chunk with previous unknown size. HTTP/1.1 allows
 * for chunked transfers which HTTP/1.0 doesnt know about. There is no legal way
 * to do this so we hope our best.
 *
 */
int http_return_stream(struct http_connection *hc, void *data, size_t datalen) {
	/* We are chunked and done sending - end session */
	if (!data) {
		/* Chunked transfer - send a 0 chunk */
		if (hc->proto == HP_HTTP11) {
			evbuffer_add_printf(hc->evb,"0\r\n\r\n");
			bufferevent_write_buffer(hc->bev, hc->evb);
		}
		hc->status=HC_STATUS_END;
		return 1;
	}

	/* In case of Transfer-Encoding: chunked send a chunk - otherwise just data */
	if (hc->proto == HP_HTTP11) {
		evbuffer_add_printf(hc->evb,"%x\r\n", datalen);
		evbuffer_add(hc->evb, data, datalen);
		evbuffer_add_printf(hc->evb, "\r\n");
	} else {
		evbuffer_add(hc->evb, data, datalen);
	}

	bufferevent_write_buffer(hc->bev, hc->evb);

	Dprintf("%s: output buffer len %d\n", __FUNCTION__,
			EVBUFFER_LENGTH(hc->bev->output));

	return 1;
}

int http_header_add(struct http_connection *hc, char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	hc->hsize+=vsprintf(&hc->hdr[hc->hsize], fmt, ap);
	hc->hdr[hc->hsize++]=0x0d;
	hc->hdr[hc->hsize++]=0x0a;
	hc->hdr[hc->hsize]=0x00;
	va_end(ap);
	return 0;
}

int http_header_end(struct http_connection *hc) {
	if (hc->keepalive)
		http_header_add(hc, "Connection: keep-alive");
	else
		http_header_add(hc, "Connection: close");

	hc->hsize+=sprintf(&hc->hdr[hc->hsize], "\r\n");
	bufferevent_write(hc->bev, hc->hdr, hc->hsize);
	hc->hsize=0;
	return 0;
}

int http_header_clength(struct http_connection *hc, ssize_t length) {
	if (length >= 0)
		http_header_add(hc, "Content-Length: %d", length);
	else {
		if (hc->proto == HP_HTTP11)
			http_header_add(hc, "Transfer-Encoding: chunked");
	}
	return 0;
}

int http_header_nocache(struct http_connection *hc) {
	http_header_add(hc, "Pragma: no-cache");
	http_header_add(hc, "Cache-Control: no-cache");
	return 0;
}

int http_header_start(struct http_connection *hc, char *result, char *type) {
	struct tm	*tm;
	time_t		t;

	/* Wed, 17 May 2006 17:33:00 GMT */
	t=time(NULL);
	tm=gmtime(&t);

	hc->hsize=0;
	hc->hsize+=sprintf(&hc->hdr[hc->hsize],
			"HTTP/1.1 %s\r\n", result);
	hc->hsize+=sprintf(&hc->hdr[hc->hsize],
			"Date: ");
	hc->hsize+=strftime(&hc->hdr[hc->hsize],
			MAX_HEADER_SIZE-hc->hsize,
			"%a, %d %b %Y %H:%M:%S %Z\r\n", tm);

	http_header_add(hc, "Content-Type: %s", type);

	return 0;
}

/*
 * Send a simple request answer:
 * Content-Length: is set - No chunked, partial transfer,
 * After requests gets answered - connection will be closed
 *
 */
int http_return_simple(struct http_connection *hc, char *result,
		char *type, void *data, size_t datalen) {

	http_header_start(hc, result, type);
	http_header_clength(hc, datalen);
	http_header_end(hc);

	bufferevent_write(hc->bev, data, datalen);

	hc->status=HC_STATUS_END;
	return 1;
}

static int http_process_query(struct http_connection *hc) {
	struct http_url *hu;

	/*
	 * FIXME Need to parse url to parameters, unescaping
	 * characters etc ...
	 *
	 */
	hu=g_hash_table_lookup(hc->server->urls, hc->url);

	Dprintf("%s: hu %p\n", __FUNCTION__, hu);

	if (hu) {
		hc->url_handler=hu->cb;
		return hc->url_handler(hc, HCB_QUERY, hu->arg);
	}

	return hc->url_handler(hc, HCB_QUERY, NULL);
}

static int http_read_head(struct http_connection *hc, struct bufferevent *bev) {
	struct evbuffer	*input = EVBUFFER_INPUT(bev);
	u_char		*eol, *p;
	int		err, drainlen;

	while((eol=evbuffer_find(input, (u_char *) "\n", 1)) != NULL) {

		/* get begin of line */
		p=EVBUFFER_DATA(input);

		/* Line length */
		drainlen=eol-p+1;

		/*
		 * Remove \n and if exists the \r
		 * FIXME Is this robust enough ?
		 */
		*eol=0x0;
		if (eol > p && *(eol-1) == '\r')
			*(--eol) = 0x0;

		if (!hc->url) {
			/* Did we have the first line with command and url ? */
			err=http_parse_command_url(hc, input);
		} else if (eol == p) {
			err=http_process_query(hc);
		} else {
			err=http_parse_attrib(hc, input);
		}

		/* tell buffer to remove leading bytes */
		evbuffer_drain(input, drainlen);

		if (!err)
			return 0;
	}

	return 1;
}

/*
 * Free Request based resources from the http_connection
 *
 */
static void http_request_free(struct http_connection *hc) {
	GList			*item;
	struct http_attrib	*ha;

	if (hc->url)
		free(hc->url);
	hc->url=NULL;

	/* Free http attributes */
	while((item=g_list_first(hc->attrib))) {
		hc->attrib=g_list_remove_link(hc->attrib, item);

		ha=item->data;
		free(ha->token);
		free(ha->value);
		free(ha);

		g_list_free(item);
	}
}

/*
 * Drop the connection in case of error of non keepalive
 */
void http_drop_connection(struct http_connection *hc) {
	struct http_server	*hs=hc->server;

	Dprintf("%s:%d\n", __FUNCTION__, __LINE__);
	hs->conn=g_list_remove(hs->conn, hc);

	bufferevent_disable(hc->bev, EV_READ|EV_WRITE);
	bufferevent_free(hc->bev);

	http_request_free(hc);

	close(hc->fd);

	evbuffer_free(hc->evb);

	free(hc);
}

/*
 * Application signals http request end - either
 * drop the connection or simply free the request
 * resources.
 *
 */
void http_request_end(struct http_connection *hc) {
	if (!hc->keepalive) {
		http_drop_connection(hc);
	} else {
		http_request_free(hc);
		hc->status=HC_STATUS_HEAD;
		hc->url_handler=&http_url_handler_404;
		hc->hsize=0;
	}
}

/*
 * Callback handler for EV_READ of the client connection. If data
 * arrives for our handler
 */
static void http_cb_read(struct bufferevent *bev, void *arg) {
	struct http_connection	*hc=arg;
	Dprintf("%s:%d\n", __FUNCTION__, __LINE__);
	switch(hc->status) {
		case(HC_STATUS_END):
			hc->url_handler(hc, HCB_END, hc->arg);
			break;
		case(HC_STATUS_HEAD):
			if (!http_read_head(hc, bev))
				hc->url_handler(hc, HCB_ERROR, hc->arg);
			break;
		case(HC_STATUS_BODY):
			hc->url_handler(hc, HCB_READ, hc->arg);
			break;
	}
}

static void http_cb_write(struct bufferevent *bev, void *arg) {
	struct http_connection	*hc=arg;
	Dprintf("%s:%d\n", __FUNCTION__, __LINE__);

	if (hc->status != HC_STATUS_END)
		hc->url_handler(hc, HCB_WRITE, hc->arg);

	if (hc->status == HC_STATUS_END)
		hc->url_handler(hc, HCB_END, hc->arg);
}

static void http_cb_error(struct bufferevent *bev, short what, void *arg) {
	struct http_connection	*hc=arg;
	Dprintf("%s:%d\n", __FUNCTION__, __LINE__);
	hc->url_handler(hc, HCB_ERROR, hc->arg);
}

static void http_connect(int fd, short ev, void *arg) {
	struct http_server	*hs=arg;
	struct http_connection	*hc;
	struct sockaddr_in	sin;
	socklen_t		slen=sizeof(struct sockaddr_in);
	int			nfd;
	unsigned long		flags;

	nfd=accept(fd, (struct sockaddr *) &sin, &slen);

	if (nfd < 0)
		return;

	Dprintf("%s:%d New connection\n", __FUNCTION__, __LINE__);

	/* Set new socket O_NONBLOCK */
	flags=fcntl(nfd, F_GETFL);
	fcntl(nfd, F_SETFL, flags | O_NONBLOCK);

	hc=calloc(1, sizeof(struct http_connection));
	hc->fd=nfd;
	hc->server=hs;
	hc->status=HC_STATUS_HEAD;
	hc->cid=hs->cid++;
	hc->evb=evbuffer_new();

	/* Copy remote endpoint sockaddr to http_connection structure */
	memcpy(&hc->sin, &sin, sizeof(struct sockaddr_in));

	hc->bev=bufferevent_new(hc->fd, http_cb_read, http_cb_write, http_cb_error, hc);
	bufferevent_enable(hc->bev, EV_READ);

	hc->url_handler=&http_url_handler_404;

	hs->conn=g_list_append(hs->conn, hc);
}

int http_register_url(struct http_server *hs, struct http_url *hu) {
	while(hu->url) {
		g_hash_table_insert(hs->urls, hu->url, hu);
		hu++;
	}
	return 1;
}

struct http_server *http_init(int port) {
	struct http_server	*hs;
	unsigned long		flags;
	int			one=1;

	hs=calloc(1, sizeof(struct http_server));
	hs->port=port;
	hs->urls=g_hash_table_new(g_str_hash, g_str_equal);

	hs->fd=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	setsockopt(hs->fd, SOL_SOCKET, SO_REUSEADDR,
			(void *) &one, sizeof(int));

	hs->sin.sin_family=AF_INET;
	hs->sin.sin_port=htons(port);
	hs->sin.sin_addr.s_addr=INADDR_ANY;

	if (bind(hs->fd, (struct sockaddr *) &hs->sin,
				sizeof(struct sockaddr_in)))
		return NULL;

	if (listen(hs->fd, 3))
		return NULL;

	flags=fcntl(hs->fd, F_GETFL);
	fcntl(hs->fd, F_GETFL, flags | O_NONBLOCK);

	event_set(&hs->ev, hs->fd, EV_READ|EV_PERSIST, http_connect, (void *) hs);
	event_add(&hs->ev, NULL);

	return hs;
}
