/* 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 #include #include #include #include #define SQL(...) #__VA_ARGS__ enum { DatabaseVersion = 4 }; static sqlite3 *db; static void dbClose(void) { // TODO: Finalize statements. sqlite3_close(db); } #define ENUM_PAGES \ X(Networks, "networks") \ X(Contexts, "contexts") \ X(Events, "events") \ X(Search, "search") enum { #define X(page, path) page, ENUM_PAGES #undef X PagesLen, }; static const char *Pages[PagesLen] = { #define X(page, path) [page] = path, ENUM_PAGES #undef X }; #define ENUM_KEYS \ X(Network, "network", kvalid_stringne) \ X(Context, "context", kvalid_stringne) \ X(After, "after", kvalid_stringne) \ X(Query, "query", kvalid_stringne) enum { #define X(key, name, valid) key, ENUM_KEYS #undef X KeysLen, }; static const struct kvalid Keys[KeysLen] = { #define X(key, name, valid) [key] = { valid, name }, ENUM_KEYS #undef X }; static enum kcgi_err fail(struct kreq *req, enum khttp status) { return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]) || khttp_head( req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_PLAIN] ) || khttp_body(req) || khttp_puts(req, khttps[status]) || khttp_putc(req, '\n'); } static enum kcgi_err request(struct kreq *req) { if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) { return fail(req, KHTTP_405); } if (req->mime != KMIME_TEXT_HTML || req->page == PagesLen) { return fail(req, KHTTP_404); } return KCGI_OK; } int main(int argc, char *argv[]) { bool fastCGI = false; bool public = false; const char *stylesheet = NULL; for (int opt; 0 < (opt = getopt(argc, argv, "fps:"));) { switch (opt) { break; case 'f': fastCGI = true; break; case 'p': public = true; break; case 's': stylesheet = optarg; break; default: return EX_USAGE; } } if (optind == argc) errx(EX_USAGE, "database path required"); int error = sqlite3_open_v2(argv[optind], &db, SQLITE_OPEN_READONLY, NULL); if (error) errx(EX_NOINPUT, "%s: %s", argv[optind], sqlite3_errmsg(db)); atexit(dbClose); sqlite3_stmt *stmt; error = sqlite3_prepare_v2( db, SQL(PRAGMA user_version;), -1, &stmt, NULL ); if (error) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); error = sqlite3_step(stmt); if (error != SQLITE_ROW) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); int version = sqlite3_column_int(stmt, 0); if (version != DatabaseVersion) { errx(EX_DATAERR, "unsupported database version %d", version); } sqlite3_finalize(stmt); // TODO: Prepare all statements with persist flag. if (fastCGI) { 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 = request(&req); if (error && error != KCGI_HUP) break; } errx(EX_PROTOCOL, "khttp_fcgi_parse: %s", kcgi_strerror(error)); } 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 = request(&req); if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error)); khttp_free(&req); } }