From 7b521cf75376ccfd38c2ff077c854b408fe616ff Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 9 Jul 2020 18:57:29 -0400 Subject: Split code and add breadcrumb nav --- .gitignore | 1 + Makefile | 14 ++- contexts.c | 72 ++++++++++++++ events.c | 26 +++++ html.c | 91 ++++++++++++++++++ networks.c | 65 +++++++++++++ scooper.c | 319 ------------------------------------------------------------- search.c | 26 +++++ server.c | 123 ++++++++++++++++++++++++ server.h | 129 +++++++++++++++++++++++++ 10 files changed, 545 insertions(+), 321 deletions(-) create mode 100644 contexts.c create mode 100644 events.c create mode 100644 html.c create mode 100644 networks.c delete mode 100644 scooper.c create mode 100644 search.c create mode 100644 server.c create mode 100644 server.h diff --git a/.gitignore b/.gitignore index 9d35820..ac9b3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +*.o config.mk scooper diff --git a/Makefile b/Makefile index 94ca089..47f6aa4 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,20 @@ LDLIBS = -lkcgi -lkcgihtml -lsqlite3 -include config.mk -scooper: +OBJS += contexts.o +OBJS += events.o +OBJS += html.o +OBJS += networks.o +OBJS += search.o +OBJS += server.o + +scooper: ${OBJS} + ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ + +${OBJS}: server.h clean: - rm -f scooper + rm -f scooper ${OBJS} install: scooper scooper.1 install -d ${PREFIX}/bin ${MANDIR}/man1 diff --git a/contexts.c b/contexts.c new file mode 100644 index 0000000..7adfa34 --- /dev/null +++ b/contexts.c @@ -0,0 +1,72 @@ +/* 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 "server.h" + +const char *ContextsQuery = SQL( + SELECT name + FROM contexts + WHERE network = :network AND coalesce(query = :query, true) + ORDER BY query, name; +); + +enum kcgi_err pageContexts(struct kreq *req) { + if (!req->fieldmap[Network]) return httpFail(req, KHTTP_404); + enum kcgi_err error = httpHead(req, KHTTP_200, KMIME_TEXT_HTML); + if (req->method == KMETHOD_HEAD) return error; + + const char *network = req->fieldmap[Network]->parsed.s; + + struct khtmlreq html; + error = error + || khttp_body(req) + || khtml_open(&html, req, KHTML_PRETTY) + || htmlHead(&html, network) + || htmlNav(&html, network, NULL) + || khtml_elem(&html, KELEM_UL); + if (error) return error; + + dbBindText(stmt.contexts, ":network", network); + if (pagePublic) dbBindInt(stmt.contexts, ":query", false); + + int result; + while (SQLITE_ROW == (result = sqlite3_step(stmt.contexts))) { + const char *context = (const char *)sqlite3_column_text(stmt.contexts, 0); + char *href = khttp_urlpart( + NULL, NULL, Pages[Events], + Keys[Network].name, network, + Keys[Context].name, context, + NULL + ); + if (!href) err(EX_OSERR, "khttp_urlpart"); + + error = khtml_elem(&html, KELEM_LI) + || khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_puts(&html, context) + || khtml_closeelem(&html, 2); + free(href); + if (error) break; + } + if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); + sqlite3_reset(stmt.contexts); + + return error || khtml_close(&html); +} diff --git a/events.c b/events.c new file mode 100644 index 0000000..c891bff --- /dev/null +++ b/events.c @@ -0,0 +1,26 @@ +/* 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 "server.h" + +enum kcgi_err pageEvents(struct kreq *req) { + return httpFail(req, KHTTP_501); +} diff --git a/html.c b/html.c new file mode 100644 index 0000000..ad80b6f --- /dev/null +++ b/html.c @@ -0,0 +1,91 @@ +/* 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 "server.h" + +const char *htmlStylesheet; + +static enum kcgi_err stylesheet(struct khtmlreq *html) { + if (!htmlStylesheet) return KCGI_OK; + return khtml_attr( + html, KELEM_LINK, + KATTR_REL, "stylesheet", + KATTR_HREF, htmlStylesheet, + KATTR__MAX + ); +} + +enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { + return khtml_elem(html, KELEM_DOCTYPE) + || khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX) + || khtml_elem(html, KELEM_TITLE) + || khtml_puts(html, title) + || khtml_closeelem(html, 1) + || stylesheet(html) + || khtml_elem(html, KELEM_H1) + || khtml_puts(html, title) + || khtml_closeelem(html, 1); +} + +enum kcgi_err +htmlNav(struct khtmlreq *html, const char *network, const char *context) { + enum kcgi_err error = 0 + || khtml_elem(html, KELEM_NAV) + || khtml_elem(html, KELEM_OL) + || khtml_elem(html, KELEM_LI) + || khtml_attr(html, KELEM_A, KATTR_HREF, Pages[Networks], KATTR__MAX) + || khtml_puts(html, "Networks") + || khtml_closeelem(html, 2); + if (error) return error; + + if (network) { + char *href = khttp_urlpart( + NULL, NULL, Pages[Contexts], + Keys[Network].name, network, + NULL + ); + if (!href) err(EX_OSERR, "khttp_urlpart"); + error = 0 + || khtml_elem(html, KELEM_LI) + || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_puts(html, network) + || khtml_closeelem(html, 2); + if (error) return error; + } + + if (network && context) { + char *href = khttp_urlpart( + NULL, NULL, Pages[Events], + Keys[Network].name, network, + Keys[Context].name, context, + NULL + ); + if (!href) err(EX_OSERR, "khttp_urlpart"); + error = 0 + || khtml_elem(html, KELEM_LI) + || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_puts(html, context) + || khtml_closeelem(html, 2); + if (error) return error; + } + + return khtml_closeelem(html, 2); +} diff --git a/networks.c b/networks.c new file mode 100644 index 0000000..15ca95d --- /dev/null +++ b/networks.c @@ -0,0 +1,65 @@ +/* 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 "server.h" + +const char *NetworksQuery = SQL( + SELECT DISTINCT network + FROM contexts + ORDER BY network; +); + +enum kcgi_err pageNetworks(struct kreq *req) { + enum kcgi_err error = httpHead(req, KHTTP_200, KMIME_TEXT_HTML); + if (req->method == KMETHOD_HEAD) return error; + + struct khtmlreq html; + error = error + || khttp_body(req) + || khtml_open(&html, req, KHTML_PRETTY) + || htmlHead(&html, "Networks") + || htmlNav(&html, NULL, NULL) + || khtml_elem(&html, KELEM_UL); + if (error) return error; + + int result; + while (SQLITE_ROW == (result = sqlite3_step(stmt.networks))) { + const char *network = (const char *)sqlite3_column_text(stmt.networks, 0); + char *href = khttp_urlpart( + NULL, NULL, Pages[Contexts], + Keys[Network].name, network, + NULL + ); + if (!href) err(EX_OSERR, "khttp_urlpart"); + + error = 0 + || khtml_elem(&html, KELEM_LI) + || khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_puts(&html, network) + || khtml_closeelem(&html, 2); + free(href); + if (error) break; + } + if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); + sqlite3_reset(stmt.networks); + + return error || khtml_close(&html); +} diff --git a/scooper.c b/scooper.c deleted file mode 100644 index 2c7b58a..0000000 --- a/scooper.c +++ /dev/null @@ -1,319 +0,0 @@ -/* 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 -#include - -#if KCGI_VMAJOR == 0 && KCGI_VMINOR < 12 -#define khttp_urlpart(...) kutil_urlpart(req, __VA_ARGS__) -#endif - -#define SQL(...) #__VA_ARGS__ - -enum { DatabaseVersion = 4 }; - -static sqlite3 *db; - -static int dbParam(sqlite3_stmt *stmt, const char *param) { - int index = sqlite3_bind_parameter_index(stmt, param); - if (index) return index; - errx(EX_SOFTWARE, "no such parameter %s: %s", param, sqlite3_sql(stmt)); -} - -static void -dbBindInt(sqlite3_stmt *stmt, const char *param, sqlite3_int64 value) { - if (!sqlite3_bind_int64(stmt, dbParam(stmt, param), value)) return; - errx(EX_SOFTWARE, "sqlite3_bind_int64: %s", sqlite3_errmsg(db)); -} - -static void -dbBindText(sqlite3_stmt *stmt, const char *param, const char *value) { - if (!sqlite3_bind_text(stmt, dbParam(stmt, param), value, -1, NULL)) return; - errx(EX_SOFTWARE, "sqlite3_bind_text: %s", sqlite3_errmsg(db)); -} - -static struct { - sqlite3_stmt *networks; - sqlite3_stmt *contexts; - sqlite3_stmt *events; - sqlite3_stmt *search; -} stmt; - -static void dbClose(void) { - if (stmt.networks) sqlite3_finalize(stmt.networks); - if (stmt.contexts) sqlite3_finalize(stmt.contexts); - if (stmt.events) sqlite3_finalize(stmt.events); - if (stmt.search) sqlite3_finalize(stmt.search); - 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 head(struct kreq *req, enum khttp http, enum kmime mime) { - return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http]) - || khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); -} - -static enum kcgi_err fail(struct kreq *req, enum khttp http) { - return head(req, http, KMIME_TEXT_PLAIN) - || khttp_body(req) - || khttp_puts(req, khttps[http]) - || khttp_putc(req, '\n'); -} - -static const char *stylesheet; -static enum kcgi_err htmlStylesheet(struct khtmlreq *html) { - if (!stylesheet) return KCGI_OK; - return khtml_attr( - html, KELEM_LINK, - KATTR_REL, "stylesheet", - KATTR_HREF, stylesheet, - KATTR__MAX - ); -} - -static enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { - return khtml_elem(html, KELEM_DOCTYPE) - || khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX) - || khtml_elem(html, KELEM_TITLE) - || khtml_puts(html, title) - || khtml_closeelem(html, 1) - || htmlStylesheet(html) - || khtml_elem(html, KELEM_H1) - || khtml_puts(html, title) - || khtml_closeelem(html, 1); -} - -static const char *NetworksQuery = SQL( - SELECT DISTINCT network - FROM contexts - ORDER BY network; -); - -static enum kcgi_err networks(struct kreq *req) { - struct khtmlreq html; - enum kcgi_err error = head(req, KHTTP_200, KMIME_TEXT_HTML) - || khttp_body(req) - || khtml_open(&html, req, KHTML_PRETTY) - || htmlHead(&html, "Networks") - || khtml_elem(&html, KELEM_UL); - if (error) return error; - - int result; - while (SQLITE_ROW == (result = sqlite3_step(stmt.networks))) { - const char *network = (const char *)sqlite3_column_text(stmt.networks, 0); - char *href = khttp_urlpart( - NULL, NULL, Pages[Contexts], Keys[Network].name, network, NULL - ); - if (!href) err(EX_OSERR, "khttp_urlpart"); - - error = khtml_elem(&html, KELEM_LI) - || khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(&html, network) - || khtml_closeelem(&html, 2); - free(href); - if (error) break; - } - if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - sqlite3_reset(stmt.networks); - return error || khtml_close(&html); -} - -static const char *ContextsQuery = SQL( - SELECT name - FROM contexts - WHERE network = :network AND coalesce(query = :query, true) - ORDER BY query, name; -); - -bool public = false; - -static enum kcgi_err contexts(struct kreq *req) { - if (!req->fieldmap[Network]) return fail(req, KHTTP_404); - const char *network = req->fieldmap[Network]->parsed.s; - - struct khtmlreq html; - enum kcgi_err error = head(req, KHTTP_200, KMIME_TEXT_HTML) - || khttp_body(req) - || khtml_open(&html, req, KHTML_PRETTY) - || htmlHead(&html, network) - || khtml_elem(&html, KELEM_UL); - if (error) return error; - - dbBindText(stmt.contexts, ":network", network); - if (public) dbBindInt(stmt.contexts, ":query", false); - - int result; - while (SQLITE_ROW == (result = sqlite3_step(stmt.contexts))) { - const char *context = (const char *)sqlite3_column_text(stmt.contexts, 0); - char *href = khttp_urlpart( - NULL, NULL, Pages[Events], - Keys[Network].name, network, - Keys[Context].name, context, - NULL - ); - if (!href) err(EX_OSERR, "khttp_urlpart"); - - error = khtml_elem(&html, KELEM_LI) - || khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(&html, context) - || khtml_closeelem(&html, 2); - free(href); - if (error) break; - } - if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - sqlite3_reset(stmt.contexts); - return error || khtml_close(&html); -} - -static enum kcgi_err events(struct kreq *req) { - return KCGI_OK; -} - -static enum kcgi_err search(struct kreq *req) { - return KCGI_OK; -} - -static enum kcgi_err request(struct kreq *req) { - if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) { - return fail(req, KHTTP_405); - } - switch (req->page) { - case Networks: return networks(req); - case Contexts: return contexts(req); - case Events: return events(req); - case Search: return search(req); - default: return fail(req, KHTTP_404); - } -} - -static void prepare(sqlite3_stmt **stmt, const char *query) { - int error = sqlite3_prepare_v3( - db, query, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL - ); - if (error) errx(EX_SOFTWARE, "%s: %s", sqlite3_errmsg(db), query); -} - -int main(int argc, char *argv[]) { - bool fastCGI = false; - - 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 *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 != DatabaseVersion) { - errx(EX_DATAERR, "unsupported database version %d", version); - } - sqlite3_finalize(check); - - prepare(&stmt.networks, NetworksQuery); - prepare(&stmt.contexts, ContextsQuery); - - 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); - } -} diff --git a/search.c b/search.c new file mode 100644 index 0000000..c9a9044 --- /dev/null +++ b/search.c @@ -0,0 +1,26 @@ +/* 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 "server.h" + +enum kcgi_err pageSearch(struct kreq *req) { + return httpFail(req, KHTTP_501); +} diff --git a/server.c b/server.c new file mode 100644 index 0000000..de18e7d --- /dev/null +++ b/server.c @@ -0,0 +1,123 @@ +/* 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 prepare(sqlite3_stmt **stmt, const char *query) { + int error = sqlite3_prepare_v3( + db, query, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL + ); + if (error) errx(EX_SOFTWARE, "%s: %s", sqlite3_errmsg(db), query); +} + +const char *Pages[PagesLen] = { +#define X(page, path) [page] = path, + ENUM_PAGES +#undef X +}; + +const struct kvalid Keys[KeysLen] = { +#define X(key, name, valid) [key] = { valid, name }, + ENUM_KEYS +#undef X +}; + +bool pagePublic; + +static enum kcgi_err request(struct kreq *req) { + if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) { + return httpFail(req, KHTTP_405); + } + switch (req->page) { + case Networks: return pageNetworks(req); + case Contexts: return pageContexts(req); + case Events: return pageEvents(req); + case Search: return pageSearch(req); + default: return httpFail(req, KHTTP_404); + } +} + +int main(int argc, char *argv[]) { + bool fastCGI = false; + + for (int opt; 0 < (opt = getopt(argc, argv, "fps:"));) { + switch (opt) { + break; case 'f': fastCGI = true; + break; case 'p': pagePublic = true; + break; case 's': htmlStylesheet = 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 *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 != DatabaseVersion) { + errx(EX_DATAERR, "unsupported database version %d", version); + } + sqlite3_finalize(check); + + prepare(&stmt.networks, NetworksQuery); + prepare(&stmt.contexts, ContextsQuery); + + 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); + } +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..80eb5bb --- /dev/null +++ b/server.h @@ -0,0 +1,129 @@ +/* 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 + +#if KCGI_VMAJOR == 0 && KCGI_VMINOR < 12 +#define khttp_urlpart(...) kutil_urlpart(NULL, __VA_ARGS__) +#endif + +#define SQL(...) #__VA_ARGS__ + +enum { DatabaseVersion = 4 }; + +extern sqlite3 *db; + +extern const char *NetworksQuery; +extern const char *ContextsQuery; +extern const char *EventsQuery; +extern const char *SearchQuery; + +extern struct Statements { + sqlite3_stmt *networks; + sqlite3_stmt *contexts; + sqlite3_stmt *events; + sqlite3_stmt *search; +} stmt; + +static inline void dbClose(void) { + if (stmt.networks) sqlite3_finalize(stmt.networks); + if (stmt.contexts) sqlite3_finalize(stmt.contexts); + if (stmt.events) sqlite3_finalize(stmt.events); + if (stmt.search) sqlite3_finalize(stmt.search); + sqlite3_close(db); +} + +static inline int dbParam(sqlite3_stmt *stmt, const char *param) { + int index = sqlite3_bind_parameter_index(stmt, param); + if (index) return index; + errx(EX_SOFTWARE, "no such parameter %s: %s", param, sqlite3_sql(stmt)); +} + +static inline void +dbBindInt(sqlite3_stmt *stmt, const char *param, sqlite3_int64 value) { + if (!sqlite3_bind_int64(stmt, dbParam(stmt, param), value)) return; + errx(EX_SOFTWARE, "sqlite3_bind_int64: %s", sqlite3_errmsg(db)); +} + +static inline void +dbBindText(sqlite3_stmt *stmt, const char *param, const char *value) { + if (!sqlite3_bind_text(stmt, dbParam(stmt, param), value, -1, NULL)) return; + errx(EX_SOFTWARE, "sqlite3_bind_text: %s", sqlite3_errmsg(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, +}; + +extern const char *Pages[PagesLen]; + +#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, +}; + +extern const struct kvalid Keys[KeysLen]; + +extern bool pagePublic; + +enum kcgi_err pageNetworks(struct kreq *req); +enum kcgi_err pageContexts(struct kreq *req); +enum kcgi_err pageEvents(struct kreq *req); +enum kcgi_err pageSearch(struct kreq *req); + +static inline enum kcgi_err +httpHead(struct kreq *req, enum khttp http, enum kmime mime) { + return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http]) + || khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); +} + +static inline enum kcgi_err httpFail(struct kreq *req, enum khttp http) { + return httpHead(req, http, KMIME_TEXT_PLAIN) + || khttp_body(req) + || khttp_printf(req, "%s\n", khttps[http]); +} + +extern const char *htmlStylesheet; +enum kcgi_err htmlHead(struct khtmlreq *html, const char *title); +enum kcgi_err htmlNav( + struct khtmlreq *html, const char *network, const char *context +); -- cgit 1.4.1