Mercurial > paster
view http.c @ 12:93f0440d452e
pasterd: add siimple theme
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 06 Feb 2020 10:29:02 +0100 |
parents | 75cfe3795de3 |
children | 29033b4fe558 |
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 <sys/stat.h> #include <assert.h> #include <ctype.h> #include <limits.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <kcgi.h> #include <kcgihtml.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_download(struct kreq *); static void page_static(struct kreq *); enum page { PAGE_INDEX, PAGE_NEW, PAGE_FORK, PAGE_PASTE, PAGE_DOWNLOAD, PAGE_STATIC, PAGE_LAST /* Not used. */ }; static const char *pages[] = { [PAGE_INDEX] = "", [PAGE_NEW] = "new", [PAGE_FORK] = "fork", [PAGE_PASTE] = "paste", [PAGE_DOWNLOAD] = "download", [PAGE_STATIC] = "static" }; static void (*handlers[])(struct kreq *req) = { [PAGE_INDEX] = page_index, [PAGE_NEW] = page_new, [PAGE_FORK] = page_fork, [PAGE_PASTE] = page_paste, [PAGE_DOWNLOAD] = page_download, [PAGE_STATIC] = page_static }; 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 *tmpl_index_keywords[] = { "pastes" }; static const char *tmpl_index_pastes_keywords[] = { "uuid", "name", "author", "language", "expiration", "date" }; static const char *tmpl_paste_keywords[] = { "uuid", "title", "author", "language", "code", "timestamp", "visible", "duration" }; static const char *tmpl_new_keywords[] = { "title", /* /fork only */ "author", /* /fork only */ "code", /* /fork only */ "private", /* /fork only */ "languages", "durations" }; static const char *languages[] = { "nohighlight", "1c", "abnf", "accesslog", "actionscript", "ada", "apache", "applescript", "arduino", "armasm", "asciidoc", "aspectj", "autohotkey", "autoit", "avrasm", "awk", "axapta", "bash", "basic", "bnf", "brainfuck", "cal", "capnproto", "ceylon", "clean", "clojure", "clojure-repl", "cmake", "coffeescript", "coq", "cos", "cpp", "crmsh", "crystal", "cs", "csp", "css", "dart", "delphi", "diff", "django", "d", "dns", "dockerfile", "dos", "dsconfig", "dts", "dust", "ebnf", "elixir", "elm", "erb", "erlang", "erlang-repl", "excel", "fix", "flix", "fortran", "fsharp", "gams", "gauss", "gcode", "gherkin", "glsl", "go", "golo", "gradle", "groovy", "haml", "handlebars", "haskell", "haxe", "hsp", "htmlbars", "http", "hy", "inform7", "ini", "irpf90", "java", "javascript", "jboss-cli", "json", "julia", "julia-repl", "kotlin", "lasso", "ldif", "leaf", "less", "lisp", "livecodeserver", "livescript", "llvm", "lsl", "lua", "makefile", "markdown", "mathematica", "matlab", "maxima", "mel", "mercury", "mipsasm", "mizar", "mojolicious", "monkey", "moonscript", "n1ql", "nginx", "nimrod", "nix", "nsis", "objectivec", "ocaml", "openscad", "oxygene", "parser3", "perl", "pf", "php", "pony", "powershell", "processing", "profile", "prolog", "protobuf", "puppet", "purebasic", "python", "q", "qml", "rib", "r", "roboconf", "routeros", "rsl", "ruby", "ruleslanguage", "rust", "scala", "scheme", "scilab", "scss", "shell", "smali", "smalltalk", "sml", "sqf", "sql", "stan", "stata", "step21", "stylus", "subunit", "swift", "taggerscript", "tap", "tcl", "tex", "thrift", "tp", "twig", "typescript", "vala", "vbnet", "vbscript-html", "vbscript", "verilog", "vhdl", "vim", "x86asm", "xl", "xml", "xquery", "yaml", "zephir", NULL }; static const struct { const char *title; long long int secs; } durations[] = { { "month", PASTE_MONTH }, { "week", PASTE_WEEK }, { "day", PASTE_DAY }, { "hour", PASTE_HOUR }, { NULL, -1 } }; 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 long long int duration(const char *val) { if (strcmp(val, "hour") == 0) return PASTE_HOUR; if (strcmp(val, "day") == 0) return PASTE_DAY; if (strcmp(val, "week") == 0) return PASTE_WEEK; if (strcmp(val, "month") == 0) return PASTE_MONTH; /* Default to month. */ return PASTE_MONTH; } static const char * ttl(time_t timestamp, long long int duration) { const time_t now = time(NULL); const long long int left = duration - difftime(now, timestamp); if (left < PASTE_HOUR) return bprintf("%lld minute(s)", left / 60); if (left < PASTE_DAY) return bprintf("%lld hour(s)", left / 3600); /* Other in days. */ return bprintf("%lld day(s)", left / 86400); } static void replace(char **dst, const char *s) { assert(dst); assert(s); /* Trim leading spaces. */ while (*s && isspace(*s)) s++; if (*s) { free(*dst); *dst = estrdup(s); } } static void render_languages(struct kreq *req, const struct paste *paste) { for (const char **l = languages; *l != NULL; ++l) { const char *line; if (paste->language && strcmp(paste->language, *l) == 0) line = bprintf("<option value=\"%s\" selected>%s</option>", *l, *l); else line = bprintf("<option value=\"%s\">%s</option>", *l, *l); khttp_puts(req, line); } } static void render_durations(struct kreq *req, const struct paste *paste) { for (size_t i = 0; durations[i].title != NULL; ++i) { const char *line; if (paste->duration == durations[i].secs) line = bprintf("<option value=\"%s\" selected>%s</option>", durations[i].title, durations[i].title); else line = bprintf("<option value=\"%s\">%s</option>", durations[i].title, durations[i].title); khttp_puts(req, line); } } static int tmpl_paste(size_t index, void *arg) { struct tmpl_paste *data = arg; struct paste *paste = &data->paste; struct khtmlreq htmlreq; khtml_open(&htmlreq, data->req, KHTML_PRETTY); switch (index) { case 0: khtml_puts(&htmlreq, paste->uuid); break; case 1: khtml_puts(&htmlreq, paste->title); break; case 2: khtml_puts(&htmlreq, paste->author); break; case 3: khtml_puts(&htmlreq, paste->language); break; case 4: khtml_puts(&htmlreq, paste->code); break; case 5: khtml_puts(&htmlreq, bstrftime("%c", localtime(&paste->timestamp))); break; case 6: khtml_puts(&htmlreq, bprintf("%s", paste->visible ? "Yes" : "No")); break; case 7: khtml_puts(&htmlreq, ttl(paste->timestamp, paste->duration)); break; default: break; } khtml_close(&htmlreq); return true; } static int tmpl_index_pastes(size_t index, void *arg) { struct tmpl_index *data = arg; struct paste *paste = &data->pastes[data->current]; struct khtmlreq htmlreq; khtml_open(&htmlreq, data->req, KHTML_PRETTY); switch (index) { case 0: khtml_puts(&htmlreq, paste->uuid); break; case 1: khtml_puts(&htmlreq, paste->title); break; case 2: khtml_puts(&htmlreq, paste->author); break; case 3: khtml_puts(&htmlreq, paste->language); break; case 4: khtml_puts(&htmlreq, ttl(paste->timestamp, paste->duration)); break; case 5: khtml_puts(&htmlreq, bstrftime("%c", localtime(&paste->timestamp))); break; default: break; } khtml_close(&htmlreq); return true; } static int tmpl_index(size_t index, void *arg) { /* No check, only one index. */ (void)index; struct tmpl_index *data = arg; const struct ktemplate kt = { .key = tmpl_index_pastes_keywords, .keysz = 6, .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 int tmpl_new(size_t index, void *arg) { struct tmpl_paste *data = arg; struct paste *paste = &data->paste; struct khtmlreq htmlreq; khtml_open(&htmlreq, data->req, KHTML_PRETTY); switch (index) { case 0: if (paste->title) khtml_puts(&htmlreq, paste->title); break; case 1: if (paste->author) khtml_puts(&htmlreq, paste->author); break; case 2: if (paste->code) khtml_puts(&htmlreq, paste->code); break; case 3: /* Add checked attribute to combobox. */ if (!paste->visible) khttp_puts(data->req, "checked"); break; case 4: render_languages(data->req, paste); break; case 5: render_durations(data->req, paste); break; default: break; }; khtml_close(&htmlreq); return true; } static void page_header(struct kreq *req) { khttp_template(req, NULL, template("header.html")); } static void page_footer(struct kreq *req) { khttp_template(req, NULL, template("footer.html")); } static void page(struct kreq *req, const struct ktemplate *tmpl, enum khttp status, const char *file) { khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]); khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); khttp_body(req); page_header(req); khttp_template(req, tmpl, template(file)); page_footer(req); khttp_free(req); } static void page_index_get(struct kreq *req) { struct tmpl_index data = { .req = req, .count = 10 }; if (!database_recents(data.pastes, &data.count)) page(req, NULL, KHTTP_500, "500.html"); else { struct ktemplate kt = { .key = tmpl_index_keywords, .keysz = 1, .arg = &data, .cb = tmpl_index }; page(req, &kt, KHTTP_200, "index.html"); } for (size_t i = 0; i < data.count; ++i) paste_finish(&data.pastes[i]); } static void page_index(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_index_get(req); break; default: page(req, NULL, KHTTP_400, "400.html"); break; } } static void page_new_get(struct kreq *req) { struct tmpl_paste data = { .req = req }; const struct ktemplate kt = { .key = tmpl_new_keywords, .keysz = 6, .cb = tmpl_new, .arg = &data }; page(req, &kt, KHTTP_200, "new.html"); } static void page_new_post(struct kreq *req) { struct paste paste = { .author = estrdup("Anonymous"), .title = estrdup("Untitled"), .language = estrdup("nohighlight"), .visible = true }; for (size_t i = 0; i < req->fieldsz; ++i) { const char *key = req->fields[i].key; const char *val = req->fields[i].val; if (strcmp(key, "title") == 0) replace(&paste.title, val); else if (strcmp(key, "author") == 0) replace(&paste.author, val); else if (strcmp(key, "language") == 0) replace(&paste.language, val); else if (strcmp(key, "duration") == 0) paste.duration = duration(val); else if (strcmp(key, "code") == 0) paste.code = estrdup(val); else if (strcmp(key, "private") == 0) paste.visible = strcmp(val, "on") != 0; } if (!database_insert(&paste)) page(req, NULL, KHTTP_500, "500.html"); else { /* Redirect to paste details. */ khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]); khttp_head(req, kresps[KRESP_LOCATION], "/paste/%s", paste.uuid); khttp_body(req); khttp_free(req); } paste_finish(&paste); } static void page_new(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_new_get(req); break; case KMETHOD_POST: page_new_post(req); break; default: break; } } static void page_fork_get(struct kreq *req) { struct tmpl_paste data = { .req = req }; if (!database_get(&data.paste, req->path)) page(req, NULL, KHTTP_404, "404.html"); else { const struct ktemplate kt = { .key = tmpl_new_keywords, .keysz = 6, .cb = tmpl_new, .arg = &data }; page(req, &kt, KHTTP_200, "new.html"); paste_finish(&data.paste); } } static void page_fork(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_fork_get(req); break; default: page(req, NULL, KHTTP_400, "400.html"); break; } } static void page_paste_get(struct kreq *req) { struct tmpl_paste data = { .req = req }; if (!database_get(&data.paste, req->path)) page(req, NULL, KHTTP_404, "404.html"); else { const struct ktemplate kt = { .key = tmpl_paste_keywords, .keysz = 8, .cb = tmpl_paste, .arg = &data }; page(req, &kt, KHTTP_200, "paste.html"); paste_finish(&data.paste); } } static void page_paste(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_paste_get(req); break; default: page(req, NULL, KHTTP_400, "400.html"); break; } } static void page_download_get(struct kreq *req) { struct paste paste; if (!database_get(&paste, req->path)) page(req, NULL, KHTTP_404, "404.html"); else { khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]); #if 0 /* TODO: this seems to generated truncated files. */ khttp_head(req, kresps[KRESP_CONTENT_LENGTH], "%zu", strlen(paste.code)); #endif khttp_head(req, kresps[KRESP_CONNECTION], "keep-alive"); khttp_head(req, kresps[KRESP_CONTENT_DISPOSITION], "attachment; filename=\"%s.%s\"", paste.uuid, paste.language); khttp_body(req); khttp_puts(req, paste.code); khttp_free(req); paste_finish(&paste); } } static void page_download(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_download_get(req); break; default: page(req, NULL, KHTTP_400, "400.html"); break; } } static void page_static_get(struct kreq *req) { struct stat st; char path[PATH_MAX]; snprintf(path, sizeof (path), "%s%s", config.themedir, req->fullpath); if (stat(path, &st) < 0) page(req, NULL, KHTTP_404, "404.html"); else { khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[req->mime]); khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%llu", (unsigned long long)(st.st_size)); khttp_body(req); khttp_template(req, NULL, path); khttp_free(req); } } static void page_static(struct kreq *req) { switch (req->method) { case KMETHOD_GET: page_static_get(req); break; default: page(req, NULL, KHTTP_400, "400.html"); break; } } static void process(struct kreq *req) { assert(req); if (req->page == PAGE_LAST) page(req, NULL, KHTTP_404, "404.html"); else 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); }