/* Copyright (C) 2020 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "server.h" sqlite3 *db; struct Statements stmt; static void prepareAll(void) { #define X(name, query) dbPrepare(&stmt.name, query); ENUM_STMTS #undef X } static void finalizeAll(void) { #define X(name, query) sqlite3_finalize(stmt.name); ENUM_STMTS #undef X sqlite3_close(db); } const char *Pages[PagesLen] = { #define X(path, page) [page] = path, ENUM_PAGES #undef X }; const struct kvalid Keys[KeysLen] = { #define X(valid, name, key) [key] = { valid, name }, ENUM_KEYS #undef X }; static enum kcgi_err stylesheet(struct kreq *req) { if (req->mime != KMIME_TEXT_CSS) return httpFail(req, KHTTP_404); enum kcgi_err error = 0 || httpHead(req, KHTTP_200, KMIME_TEXT_CSS) || khttp_head( req, kresps[KRESP_CACHE_CONTROL], "public, max-age=86400, immutable" ) || khttp_body(req); if (req->method == KMETHOD_HEAD) return error; return error || khttp_puts(req, CSS); } static enum kcgi_err dispatch(struct kreq *req) { if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) { return httpFail(req, KHTTP_405); } switch (req->page) { case Networks: return networksPage(req); case Contexts: return contextsPage(req); case Events: return eventsPage(req); case Search: return searchPage(req); case Stylesheet: return stylesheet(req); default: return httpFail(req, KHTTP_404); } } #define ENV "SCOOPER_" int main(int argc, char *argv[]) { const char *path = NULL; bool test = false; const char *val; if ((val = getenv(ENV "DATABASE"))) path = val; if ((val = getenv(ENV "GAP"))) eventsGap = strtol(val, NULL, 10); if ((val = getenv(ENV "HIDE_HOST"))) htmlHideHost = (val[0] != '0'); if ((val = getenv(ENV "LIMIT"))) eventsLimit = strtol(val, NULL, 10); if ((val = getenv(ENV "OVERLAP"))) eventsOverlap = strtol(val, NULL, 10); if ((val = getenv(ENV "PUBLIC"))) contextsPublic = (val[0] != '0'); if ((val = getenv(ENV "RECENT"))) contextsRecent = strtol(val, NULL, 10); if ((val = getenv(ENV "STYLESHEET"))) htmlStylesheet = val; for (int opt; 0 < (opt = getopt(argc, argv, "cg:hl:o:pr:s:"));) { switch (opt) { break; case 'c': test = true; break; case 'g': eventsGap = strtol(optarg, NULL, 10); break; case 'h': htmlHideHost = true; break; case 'l': eventsLimit = strtol(optarg, NULL, 10); break; case 'o': eventsOverlap = strtol(optarg, NULL, 10); break; case 'p': contextsPublic = true; break; case 'r': contextsRecent = strtol(optarg, NULL, 10); break; case 's': htmlStylesheet = optarg; break; default: return EX_USAGE; } } if (optind < argc) path = argv[optind]; if (!path) errx(EX_USAGE, "database path required"); int error = sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY, NULL); if (error) errx(EX_NOINPUT, "%s: %s", path, sqlite3_errmsg(db)); sqlite3_busy_timeout(db, 1000); atexit(finalizeAll); sqlite3_stmt *check; error = sqlite3_prepare_v2( db, SQL(PRAGMA user_version;), -1, &check, NULL ); if (error) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); error = sqlite3_step(check); if (error != SQLITE_ROW) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); int version = sqlite3_column_int(check, 0); if (version < DatabaseVersionMin || version > DatabaseVersionMax) { errx(EX_DATAERR, "unsupported database version %d", version); } sqlite3_finalize(check); prepareAll(); if (test) return EX_OK; if (khttp_fcgi_test()) { struct kfcgi *fcgi; enum kcgi_err error = khttp_fcgi_init( &fcgi, Keys, KeysLen, Pages, PagesLen, Networks ); if (error) errx(EX_CONFIG, "khttp_fcgi_init: %s", kcgi_strerror(error)); for ( struct kreq req; KCGI_OK == (error = khttp_fcgi_parse(fcgi, &req)); khttp_free(&req) ) { error = dispatch(&req); if (error && error != KCGI_HUP) break; } if (error != KCGI_EXIT) { errx(EX_PROTOCOL, "khttp_fcgi_parse: %s", kcgi_strerror(error)); } khttp_fcgi_free(fcgi); } else { struct kreq req; enum kcgi_err error = khttp_parse( &req, Keys, KeysLen, Pages, PagesLen, Networks ); if (error) errx(EX_PROTOCOL, "khttp_parse: %s", kcgi_strerror(error)); error = dispatch(&req); if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error)); khttp_free(&req); } }