/*
 * Copyright (c) Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * 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/types.h> /* size_t, ssize_t */
#include <stdarg.h> /* va_list */
#include <stddef.h> /* NULL */
#include <stdint.h> /* int64_t */
#include <stdlib.h>
#include <string.h> /* memset */

#include "kcgi.h"
#include "kcgihtml.h"

/*
 * Simple CGI application.  Compile it with `make samples` (or using gmake) and
 * install it into your web server's /cgi-bin.  The "template.xml" file should
 * be in the /cgi-bin directory as well and readable by the server process.
 * (Obviously this is just for a sample.)
 *
 * Assuming localhost/cgi-bin, the script is localhost/cgi-bin/sample.  The
 * pages recognised are:
 *
 *   - /cgi-bin/sample/index.html
 *   - /cgi-bin/sample/template.html
 *   - /cgi-bin/sample/senddata.html
 *
 * See the sendindex et al. functions for what these do.
 */

/* Recognised page requests.  See pages[]. */
enum	page {
	PAGE_INDEX,
	PAGE_TEMPLATE,
	PAGE_SENDDATA,
	PAGE__MAX
};

/*
 * All of the keys (input field names) we accept.  The key names are in the
 * "keys" array.  See sendindex() for how these are used.
 */
enum	key {
	KEY_INTEGER, 
	KEY_FILE,
	KEY_PAGECOUNT,
	KEY_PAGESIZE,
	KEY__MAX
};

/*
 * The elements in our template file.  The element key names are in the "templs"
 * array.  See sendtemplate() for how this is used.
 */
enum	templ {
	TEMPL_TITLE,
	TEMPL_NAME,
	TEMPL_REMOTE_ADDR,
	TEMPL__MAX
};

/*
 * We need a structure because we can't get the "r" from the request.  This is
 * used by our template callback.
 */
struct	tstrct {
	struct khtmlreq	 req;
	struct kreq	*r;
};

/*
 * We'll use this to route pages by creating an array indexed by our page.  Then
 * when the page is parsed, we'll route directly into it.
 */
typedef	void (*disp)(struct kreq *);

static void senddata(struct kreq *);
static void sendindex(struct kreq *);
static void sendtemplate(struct kreq *);

static const disp disps[PAGE__MAX] = {
	sendindex, /* PAGE_INDEX */
	sendtemplate, /* PAGE_TEMPLATE */
	senddata, /* PAGE_SENDDATA */
};

static const struct kvalid keys[KEY__MAX] = {
	{ kvalid_int, "integer" }, /* KEY_INTEGER */
	{ NULL, "file" }, /* KEY_FILE */
	{ kvalid_uint, "count" }, /* KEY_PAGECOUNT */
	{ kvalid_uint, "size" }, /* KEY_PAGESIZE */
};

/*
 * Template key names (as in @@TITLE@@ in the file).
 */
static const char *const templs[TEMPL__MAX] = {
	"title", /* TEMPL_TITLE */
	"name", /* TEMPL_NAME */
	"remote_addr", /* TEMPL_REMOTE_ADDR */
};

/* 
 * Page names (as in the URL component) mapped from the first name part of
 * requests, e.g., /sample.cgi/index.html -> index -> PAGE_INDEX.
 */
static const char *const pages[PAGE__MAX] = {
	"index", /* PAGE_INDEX */
	"template", /* PAGE_TEMPLATE */
	"senddata" /* PAGE_SENDDATA */
};

/*
 * Open an HTTP response with a status code and a particular content-type, then
 * open the HTTP content body.  You can call khttp_head(3) before this: CGI
 * doesn't dictate any particular header order.
 */
static void
resp_open(struct kreq *req, enum khttp http)
{
	enum kmime	 mime;

	/*
	 * If we've been sent an unknown suffix like '.foo', we won't know what
	 * it is.  Default to an octet-stream response.
	 */

	if ((mime = req->mime) == KMIME__MAX)
		mime = KMIME_APP_OCTET_STREAM;
	khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http]);
	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
	khttp_body(req);
}

/*
 * Callback for filling in a particular template part.  Let's just be simple for
 * simplicity's sake.
 */
static int
template(size_t key, void *arg)
{
	struct tstrct	*p = arg;

	switch (key) {
	case TEMPL_TITLE:
		khtml_puts(&p->req, "title");
		break;
	case TEMPL_NAME:
		khtml_puts(&p->req, "name");
		break;
	case TEMPL_REMOTE_ADDR:
		khtml_puts(&p->req, p->r->remote);
		break;
	default:
		return 0;
	}

	return 1;
}

/*
 * Demonstrates how to use templates.  Returns HTTP 200 and the template
 * content.
 */
static void
sendtemplate(struct kreq *req)
{
	struct ktemplate t;
	struct tstrct	 p;

	memset(&t, 0, sizeof(struct ktemplate));
	memset(&p, 0, sizeof(struct tstrct));

	p.r = req;
	t.key = templs;
	t.keysz = TEMPL__MAX;
	t.arg = &p;
	t.cb = template;

	resp_open(req, KHTTP_200);
	khtml_open(&p.req, req, 0);
	khttp_template(req, &t, "template.xml");
	khtml_close(&p.req);
}

/*
 * Send a random amount of data.  Requires KEY_PAGECOUNT (optional),
 * KEY_PAGESIZE (optional).  Page count is the number of times we flush a page
 * (with the given size) to the wire.  Returns HTTP 200 and the random data.
 */
static void
senddata(struct kreq *req)
{
	int64_t	  i, j, nm, sz;
	char	 *buf;

	nm = 1024 * 1024;
	if (req->fieldmap[KEY_PAGECOUNT] != NULL)
		nm = req->fieldmap[KEY_PAGECOUNT]->parsed.i;
	if (nm == 0)
		nm = 1;

	sz = 1;
	if (req->fieldmap[KEY_PAGESIZE] != NULL)
		sz = req->fieldmap[KEY_PAGESIZE]->parsed.i;
	if (sz == 0 || (uint64_t)sz > SIZE_MAX)
		sz = 1;
	
	buf = kmalloc(sz);

	resp_open(req, KHTTP_200);
	for (i = 0; i < nm; i++) {
		for (j = 0; j < sz; j++)
#ifndef __linux__
			buf[j] = 65 + arc4random_uniform(24);
#else
			buf[j] = 65 + (random() % 24);
#endif
		khttp_write(req, buf, sz);
	}

	free(buf);
}

/*
 * Demonstrates how to use GET and POST forms and building with the HTML builder
 * functions.  Returns HTTP 200 and HTML content.
 */
static void
sendindex(struct kreq *req)
{
	char		*page;
	struct khtmlreq	 r;
	const char	*cp;

	cp = req->fieldmap[KEY_INTEGER] == NULL ?
		"" : req->fieldmap[KEY_INTEGER]->val;
	kasprintf(&page, "%s/%s", req->pname, pages[PAGE_INDEX]);

	resp_open(req, KHTTP_200);
	khtml_open(&r, req, 0);
	khtml_elem(&r, KELEM_DOCTYPE);
	khtml_elem(&r, KELEM_HTML);
	khtml_elem(&r, KELEM_HEAD);
	khtml_elem(&r, KELEM_TITLE);
	khtml_puts(&r, "Welcome!");
	khtml_closeelem(&r, 2);
	khtml_elem(&r, KELEM_BODY);
	khtml_puts(&r, "Welcome!");
	khtml_attr(&r, KELEM_FORM,
		KATTR_METHOD, "post",
		KATTR_ENCTYPE, "multipart/form-data",
		KATTR_ACTION, page,
		KATTR__MAX);
	khtml_elem(&r, KELEM_FIELDSET);
	khtml_elem(&r, KELEM_LEGEND);
	khtml_puts(&r, "Post (multipart)");
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "number",
		KATTR_NAME, keys[KEY_INTEGER].name,
		KATTR_VALUE, cp, KATTR__MAX);
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "file",
		KATTR_MULTIPLE, "",
		KATTR_NAME, keys[KEY_FILE].name,
		KATTR__MAX);
	if (req->fieldmap[KEY_FILE] != NULL) {
		if (req->fieldmap[KEY_FILE]->file != NULL) {
			khtml_puts(&r, "file: ");
			khtml_puts(&r, req->fieldmap[KEY_FILE]->file);
			khtml_puts(&r, " ");
		} 
		if (req->fieldmap[KEY_FILE]->ctype != NULL) {
			khtml_puts(&r, "ctype: ");
			khtml_puts(&r, req->fieldmap[KEY_FILE]->ctype);
		} 
	}
	khtml_closeelem(&r, 1);
	khtml_elem(&r, KELEM_P);
	khtml_attr(&r, KELEM_INPUT,
		KATTR_TYPE, "submit",
		KATTR__MAX);
	khtml_closeelem(&r, 0);
	khtml_close(&r);
	free(page);
}

int
main(void)
{
	struct kreq	 r;
	enum kcgi_err	 er;

	/* Set up our main HTTP context. */

	er = khttp_parse(&r, keys, KEY__MAX, pages, PAGE__MAX, PAGE_INDEX);
	if (er != KCGI_OK)
		return 1;

	/* 
	 * Accept only GET, POST, and OPTIONS.  Restrict to text/html and a
	 * valid page.  If all of our parameters are valid, use a dispatch array
	 * to send us to the page handlers.  Start with CORS handling, which
	 * we'll allow to any origin.  XXX: this ignores errors in handling,
	 * which is probably not what you want.
	 */

	if (r.reqmap[KREQU_ORIGIN] != NULL)
		khttp_head(&r, kresps[KRESP_ACCESS_CONTROL_ALLOW_ORIGIN],
			"%s", r.reqmap[KREQU_ORIGIN]->val);

	if (r.method == KMETHOD_OPTIONS) {
		khttp_head(&r, kresps[KRESP_ALLOW], "OPTIONS, GET, POST");
		resp_open(&r, KHTTP_204);
	} else if (r.method != KMETHOD_GET && r.method != KMETHOD_POST) {
		resp_open(&r, KHTTP_405);
	} else if (r.page == PAGE__MAX || r.mime != KMIME_TEXT_HTML) {
		resp_open(&r, KHTTP_404);
	} else
		(*disps[r.page])(&r);

	khttp_free(&r);
	return 0;
}