view http.c @ 1:836a698946f8

pasterd: add basic routes
author David Demelier <markand@malikania.fr>
date Tue, 04 Feb 2020 16:44:43 +0100
parents
children 65607ae124b1
line wrap: on
line source

/*
 * http.c -- HTTP parsing and rendering
 *
 * Copyright (c) 2020 David Demelier <markand@malikania.fr>
 * 
 * Permission to use, copy, modify, and/or 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>
#include <assert.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <kcgi.h>

#include "config.h"
#include "database.h"
#include "http.h"
#include "log.h"
#include "paste.h"
#include "util.h"

static void page_index(struct kreq *);
static void page_new(struct kreq *);
static void page_fork(struct kreq *);
static void page_paste(struct kreq *);
static void page_about(struct kreq *);
static void page_download(struct kreq *);

enum page {
	PAGE_INDEX,
	PAGE_NEW,
	PAGE_FORK,
	PAGE_PASTE,
	PAGE_ABOUT,
	PAGE_DOWNLOAD,
	PAGE_LAST       /* Not used. */
};

static const char *pages[] = {
	[PAGE_INDEX]    = "",
	[PAGE_NEW]      = "new",
	[PAGE_FORK]     = "fork",
	[PAGE_PASTE]    = "paste",
	[PAGE_ABOUT]    = "about",
	[PAGE_DOWNLOAD] = "download",
};

static void (*handlers[])(struct kreq *req) = {
	[PAGE_INDEX]    = page_index,
	[PAGE_NEW]      = page_new,
	[PAGE_FORK]     = page_fork,
	[PAGE_PASTE]    = page_paste,
	[PAGE_ABOUT]    = page_about,
	[PAGE_DOWNLOAD] = page_download
};

struct tmpl_index {
	struct kreq *req;
	struct paste pastes[10];
	size_t count;
	size_t current;
};

struct tmpl_paste {
	struct kreq *req;
	struct paste paste;
};

static const char *
template(const char *filename)
{
	/* Build path to the template file. */
	static char path[PATH_MAX];

	snprintf(path, sizeof (path), "%s/%s", config.themedir, filename);

	return path;
}

static int
tmpl_paste(size_t index, void *arg)
{
	struct tmpl_paste *data = arg;
	struct paste *paste = &data->paste;

	switch (index) {
	case 0:
		khttp_puts(data->req, paste->uuid);
		break;
	case 1:
		khttp_puts(data->req, paste->title);
		break;
	case 2:
		khttp_puts(data->req, paste->author);
		break;
	case 3:
		khttp_puts(data->req, paste->language);
		break;
	case 4:
		khttp_puts(data->req, paste->code);
		break;
	case 5:
		/* TODO: timestamp here. */
		khttp_puts(data->req, "TODO");
		break;
	case 6:
		khttp_puts(data->req, bprintf("%s", paste->visible ? "Yes" : "No"));
		break;
	case 7:
		/* TODO: convert time left. */
		khttp_puts(data->req, "TODO");
		break;
	default:
		break;
	}

	return true;
}

static int
tmpl_index_pastes(size_t index, void *arg)
{
	struct tmpl_index *data = arg;
	struct paste *paste = &data->pastes[data->current];

	switch (index) {
	case 0:
		khttp_puts(data->req, paste->uuid);
		break;
	case 1:
		khttp_puts(data->req, paste->title);
		break;
	case 2:
		khttp_puts(data->req, paste->author);
		break;
	case 3:
		khttp_puts(data->req, paste->language);
		break;
	case 4:
		khttp_puts(data->req, bprintf("%d", paste->duration));
		break;
	default:
		break;
	}

	return true;
}

static int
tmpl_index(size_t index, void *arg)
{
	/* No check, only one index. */
	struct tmpl_index *data = arg;
	const char *keywords[] = {
		"uuid",
		"name",
		"author",
		"language",
		"expiration"
	};
	struct ktemplate kt = {
		.key    = keywords,
		.keysz  = 5,
		.arg    = data,
		.cb     = tmpl_index_pastes
	};

	for (size_t i = 0; i < data->count; ++i) {
		khttp_template(data->req, &kt, template("index-paste.html"));
		data->current++;
	}

	return true;
}

static void
header(struct kreq *req)
{
	khttp_template(req, NULL, template("header.html"));
}

static void
footer(struct kreq *req)
{
	khttp_template(req, NULL, template("footer.html"));
}

static void
page_index(struct kreq *req)
{
	struct tmpl_index data = {
		.req    = req,
		.count  = 10
	};

	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]);

	if (!database_recents(data.pastes, &data.count)) {
		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_500]);
		khttp_body(req);
		khttp_template(req, NULL, template("500.html"));
	} else {
		const char *keywords[] = { "pastes" };
		struct ktemplate kt = {
			.key    = keywords,
			.keysz  = 1,
			.arg    = &data,
			.cb     = tmpl_index
		};

		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);
		khttp_body(req);
		header(req);
		khttp_template(req, &kt, template("index.html"));
		footer(req);
	}

	khttp_free(req);
}

static void
page_new(struct kreq *req)
{
	(void)req;
}

static void
page_fork(struct kreq *req)
{
	(void)req;
}

static void
page_paste(struct kreq *req)
{
	struct tmpl_paste data = {
		.req = req
	};

	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]);

	if (!database_get(&data.paste, req->path)) {
		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_404]);
		khttp_body(req);
		khttp_template(req, NULL, template("404.html"));
	} else {
		const char *keywords[] = {
			"uuid",
			"title",
			"author",
			"language",
			"code",
			"timestamp",
			"visible",
			"duration"
		};
		const struct ktemplate kt = {
			.key    = keywords,
			.keysz  = 8,
			.cb     = tmpl_paste,
			.arg    = &data
		};

		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);
		khttp_body(req);
		header(req);
		khttp_template(req, &kt, template("paste.html"));
		footer(req);
		khttp_free(req);
	}
}

static void
page_about(struct kreq *req)
{
	(void)req;
}

static void
page_download(struct kreq *req)
{
	(void)req;
}

static void
process(struct kreq *req)
{
	assert(req);

	handlers[req->page](req);
}

void
http_fcgi_run(void)
{
	struct kreq req;
	struct kfcgi *fcgi;

	if (khttp_fcgi_init(&fcgi, NULL, 0, pages, PAGE_LAST, 0) != KCGI_OK)
		return;
 
	while (khttp_fcgi_parse(fcgi, &req) == KCGI_OK)
		process(&req);
 
	khttp_fcgi_free(fcgi);
}

void
http_cgi_run(void)
{
	struct kreq req;

	if (khttp_parse(&req, NULL, 0, pages, PAGE_LAST, 0) == KCGI_OK)
		process(&req);
}