From 7ff6e16e8ccda2433006f949ffc7b1ee5eaf08cf Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 12 Jul 2020 12:17:44 -0400 Subject: Reorganize code and add earlier messages link --- contexts.c | 12 ++-- events.c | 96 +++++++++++++++++------------- html.c | 55 ++++++++--------- networks.c | 5 +- scooper.1 | 14 ++++- search.c | 12 ++-- server.c | 60 ++++++++----------- server.h | 195 +++++++++++++++++++++++++++++++++---------------------------- 8 files changed, 243 insertions(+), 206 deletions(-) diff --git a/contexts.c b/contexts.c index 04b44ac..b486ef9 100644 --- a/contexts.c +++ b/contexts.c @@ -15,12 +15,16 @@ */ #include +#include #include #include #include #include "server.h" +bool contextsPublic; +int contextsRecent = 500; + const char *ContextsQuery = SQL( WITH recentEvents AS ( SELECT time, context @@ -45,8 +49,8 @@ const char *ContextsQuery = SQL( SELECT name, query, 0 FROM allContexts; ); -enum kcgi_err pageContexts(struct kreq *req) { - struct Scope scope = htmlScope(req); +enum kcgi_err contextsPage(struct kreq *req) { + struct Scope scope = pageScope(req); if (!scope.network) return httpFail(req, KHTTP_400); enum kcgi_err error = httpHead(req, KHTTP_200, KMIME_TEXT_HTML); @@ -62,8 +66,8 @@ enum kcgi_err pageContexts(struct kreq *req) { sqlite3_reset(stmt.contexts); dbBindText(stmt.contexts, ":network", scope.network); - dbBindInt(stmt.contexts, ":recent", pageRecent); - dbBindInt(stmt.contexts, ":public", pagePublic); + dbBindInt(stmt.contexts, ":recent", contextsRecent); + dbBindInt(stmt.contexts, ":public", contextsPublic); enum State { None, diff --git a/events.c b/events.c index e018087..35e2c5b 100644 --- a/events.c +++ b/events.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -22,32 +23,21 @@ #include "server.h" -static enum kcgi_err redirect(struct kreq *req, struct Scope scope) { - struct tm *tm = gmtime(&(time_t) { time(NULL) }); - if (!tm) err(EX_OSERR, "gmtime"); - - char time[sizeof("0000-00-00T00:00:00")]; - strftime(time, sizeof(time), "%FT%T", tm); - - char *url = khttp_urlpart( - NULL, NULL, Pages[Events], - Keys[Network].name, scope.network, - Keys[Context].name, scope.context, - Keys[Before].name, time, - NULL - ); - enum kcgi_err error = httpRedirect(req, url); - free(url); - return error; +int eventsGap = 3600; +int eventsOverlap = 15; +int eventsLimit = 50; + +static const char *timestamp(time_t time) { + static char stamp[sizeof("0000-00-00T00:00:00")]; + strftime(stamp, sizeof(stamp), "%FT%T", gmtime(&time)); + return stamp; } static enum kcgi_err dateForm(struct khtmlreq *html, struct Scope scope, const char *_time) { struct tm tm = {0}; if (!strptime(_time, "%F", &tm)) { - struct tm *now = gmtime(&(time_t) { time(NULL) }); - if (!now) err(EX_OSERR, "gmtime"); - tm = *now; + tm = *gmtime(&(time_t) { time(NULL) }); } char date[sizeof("0000-00-00")]; strftime(date, sizeof(date), "%F", &tm); @@ -58,20 +48,7 @@ dateForm(struct khtmlreq *html, struct Scope scope, const char *_time) { KATTR_ACTION, Pages[Events], KATTR__MAX ) - || khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Network].name, - KATTR_VALUE, scope.network, - KATTR__MAX - ) - || khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Context].name, - KATTR_VALUE, scope.context, - KATTR__MAX - ) + || htmlScopeFields(html, scope) || khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "date", @@ -134,12 +111,24 @@ const char *EventsBeforeQuery = SQL( ORDER BY time, event; ); -enum kcgi_err pageEvents(struct kreq *req) { - struct Scope scope = htmlScope(req); +enum kcgi_err eventsPage(struct kreq *req) { + struct Scope scope = pageScope(req); if (!scope.network || !scope.context) return httpFail(req, KHTTP_400); + if (!req->fieldmap[After] && !req->fieldmap[Before]) { - return redirect(req, scope); + char *url = khttp_urlpart( + NULL, NULL, Pages[Events], + Keys[Network].name, scope.network, + Keys[Context].name, scope.context, + Keys[Before].name, timestamp(time(NULL)), + NULL + ); + if (!url) err(EX_OSERR, "khttp_urlpart"); + enum kcgi_err error = httpRedirect(req, url); + free(url); + return error; } + const char *time = req->fieldmap[Before] ? req->fieldmap[Before]->parsed.s : req->fieldmap[After]->parsed.s; @@ -164,11 +153,12 @@ enum kcgi_err pageEvents(struct kreq *req) { dbBindText(events, ":network", scope.network); dbBindText(events, ":context", scope.context); dbBindText(events, ":time", time); - dbBindInt(events, ":public", pagePublic); - dbBindInt(events, ":limit", pageLimit); + dbBindInt(events, ":public", contextsPublic); + dbBindInt(events, ":limit", eventsLimit); + int rows; int result; - while (SQLITE_ROW == (result = sqlite3_step(events))) { + for (rows = 0; SQLITE_ROW == (result = sqlite3_step(events)); ++rows) { int i = 0; struct Event event = {0}; event.event = sqlite3_column_int64(events, i++); @@ -179,6 +169,32 @@ enum kcgi_err pageEvents(struct kreq *req) { event.host = sqlite3_column_text(events, i++); event.target = sqlite3_column_text(events, i++); event.message = sqlite3_column_text(events, i++); + + if (!rows) { + char *base = khttp_urlpart( + NULL, NULL, Pages[Events], + Keys[Network].name, scope.network, + Keys[Context].name, scope.context, + Keys[Before].name, timestamp(event.time + eventsOverlap), + NULL + ); + if (!base) err(EX_OSERR, "khttp_urlpart"); + + char *href = NULL; + asprintf(&href, "%s#%" PRId64, base, event.event); + if (!href) err(EX_OSERR, "asprintf"); + free(base); + + error = 0 + || khtml_elem(&html, KELEM_TR) + || khtml_attr(&html, KELEM_TH, KATTR_COLSPAN, "3", KATTR__MAX) + || khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_puts(&html, "Earlier messages") + || khtml_closeelem(&html, 3); + free(href); + if (error) return error; + } + error = htmlEvent(&html, scope, event); if (error) return error; } diff --git a/html.c b/html.c index 49b65e9..f5ea6bd 100644 --- a/html.c +++ b/html.c @@ -48,6 +48,30 @@ enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { || khtml_closeelem(html, 1); } +enum kcgi_err htmlScopeFields(struct khtmlreq *html, struct Scope scope) { + if (scope.network) { + enum kcgi_err error = khtml_attr( + html, KELEM_INPUT, + KATTR_TYPE, "hidden", + KATTR_NAME, Keys[Network].name, + KATTR_VALUE, scope.network, + KATTR__MAX + ); + if (error) return error; + } + if (scope.context) { + enum kcgi_err error = khtml_attr( + html, KELEM_INPUT, + KATTR_TYPE, "hidden", + KATTR_NAME, Keys[Context].name, + KATTR_VALUE, scope.context, + KATTR__MAX + ); + if (error) return error; + } + return KCGI_OK; +} + enum kcgi_err htmlNav(struct khtmlreq *html, struct Scope scope) { enum kcgi_err error = 0 @@ -97,7 +121,7 @@ htmlNav(struct khtmlreq *html, struct Scope scope) { (scope.context ? scope.context : scope.network ? scope.network : "") ); - error = 0 + return 0 || khtml_closeelem(html, 1) || khtml_attr( html, KELEM_FORM, @@ -105,6 +129,7 @@ htmlNav(struct khtmlreq *html, struct Scope scope) { KATTR_ACTION, Pages[Search], KATTR__MAX ) + || htmlScopeFields(html, scope) || khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "search", @@ -117,32 +142,8 @@ htmlNav(struct khtmlreq *html, struct Scope scope) { KATTR_TYPE, "submit", KATTR_VALUE, label, KATTR__MAX - ); - if (error) return error; - - if (scope.network) { - error = khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Network].name, - KATTR_VALUE, scope.network, - KATTR__MAX - ); - if (error) return error; - } - - if (scope.context) { - error = khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Context].name, - KATTR_VALUE, scope.context, - KATTR__MAX - ); - if (error) return error; - } - - return khtml_closeelem(html, 2); + ) + || khtml_closeelem(html, 2); } static const char *SourceURL = "https://git.causal.agency/scooper"; diff --git a/networks.c b/networks.c index 289fa31..f860efc 100644 --- a/networks.c +++ b/networks.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -43,7 +44,7 @@ const char *NetworksQuery = SQL( SELECT network, 0 FROM allNetworks; ); -enum kcgi_err pageNetworks(struct kreq *req) { +enum kcgi_err networksPage(struct kreq *req) { struct Scope scope = {0}; enum kcgi_err error = httpHead(req, KHTTP_200, KMIME_TEXT_HTML); @@ -58,7 +59,7 @@ enum kcgi_err pageNetworks(struct kreq *req) { if (error) return error; sqlite3_reset(stmt.networks); - dbBindInt(stmt.networks, ":recent", pageRecent); + dbBindInt(stmt.networks, ":recent", contextsRecent); enum State { None, diff --git a/scooper.1 b/scooper.1 index e17e8bd..222db60 100644 --- a/scooper.1 +++ b/scooper.1 @@ -1,4 +1,4 @@ -.Dd July 11, 2020 +.Dd July 12, 2020 .Dt SCOOPER 1 .Os . @@ -9,7 +9,9 @@ .Sh SYNOPSIS .Nm .Op Fl p +.Op Fl g Ar gap .Op Fl l Ar limit +.Op Fl o Ar overlap .Op Fl r Ar recent .Op Fl s Ar url .Ar database @@ -35,11 +37,21 @@ Prepare all SQL statements against the given .Ar database and exit. . +.It Fl g Ar gap +Set the time in seconds between events +after which to indicate a gap. +The default is 3600 (one hour). +. .It Fl l Ar limit Limit the number of events to be displayed on one page. The default limit is 50. . +.It Fl o Ar overlap +Set the overlap in seconds +between pages of events. +The default is 15. +. .It Fl p Show only public contexts, i.e. channels. diff --git a/search.c b/search.c index 1cfc0f1..7960d6b 100644 --- a/search.c +++ b/search.c @@ -44,8 +44,8 @@ const char *SearchQuery = SQL( LIMIT :offset, :limit; ); -enum kcgi_err pageSearch(struct kreq *req) { - struct Scope scope = htmlScope(req); +enum kcgi_err searchPage(struct kreq *req) { + struct Scope scope = pageScope(req); if (!scope.query) return httpFail(req, KHTTP_400); if (scope.context && !scope.network) return httpFail(req, KHTTP_400); @@ -68,8 +68,8 @@ enum kcgi_err pageSearch(struct kreq *req) { dbBindText(stmt.search, ":network", scope.network); dbBindText(stmt.search, ":context", scope.context); dbBindText(stmt.search, ":query", scope.query); - dbBindInt(stmt.search, ":public", pagePublic); - dbBindInt(stmt.search, ":limit", pageLimit); + dbBindInt(stmt.search, ":public", contextsPublic); + dbBindInt(stmt.search, ":limit", eventsLimit); dbBindInt(stmt.search, ":offset", offset); dbBindText(stmt.search, ":highlight", "\26"); @@ -88,8 +88,8 @@ enum kcgi_err pageSearch(struct kreq *req) { event.target = sqlite3_column_text(stmt.search, i++); event.message = sqlite3_column_text(stmt.search, i++); error = htmlEvent(&html, scope, event); - if (error) break; + if (error) return error; } if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - return error || htmlFooter(&html) || khtml_close(&html); + return htmlFooter(&html) || khtml_close(&html); } diff --git a/server.c b/server.c index 6ecd921..fad9a90 100644 --- a/server.c +++ b/server.c @@ -26,13 +26,6 @@ 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 @@ -45,44 +38,44 @@ const struct kvalid Keys[KeysLen] = { #undef X }; -bool pagePublic; -int pageLimit = 50; -int pageRecent = 500; - static const char CSS[] = { #include "default.css.h" }; static const char *Cache = "public, max-age=86400, immutable"; -static enum kcgi_err request(struct kreq *req) { +static enum kcgi_err stylesheet(struct kreq *req) { + if (req->mime != KMIME_TEXT_CSS) return httpFail(req, KHTTP_404); + return httpHead(req, KHTTP_200, KMIME_TEXT_CSS) + || khttp_head(req, kresps[KRESP_CACHE_CONTROL], "%s", Cache) + || khttp_body(req) + || khttp_write(req, CSS, sizeof(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 pageNetworks(req); - case Contexts: return pageContexts(req); - case Events: return pageEvents(req); - case Search: return pageSearch(req); - case Stylesheet: { - if (req->mime != KMIME_TEXT_CSS) return httpFail(req, KHTTP_404); - return httpHead(req, KHTTP_200, KMIME_TEXT_CSS) - || khttp_head(req, kresps[KRESP_CACHE_CONTROL], "%s", Cache) - || khttp_body(req) - || khttp_write(req, CSS, sizeof(CSS)); - } - default: return httpFail(req, KHTTP_404); + 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); } } int main(int argc, char *argv[]) { bool test = false; - for (int opt; 0 < (opt = getopt(argc, argv, "cl:pr:s:"));) { + for (int opt; 0 < (opt = getopt(argc, argv, "cg:l:o:pr:s:"));) { switch (opt) { + break; case 'g': eventsGap = strtol(optarg, NULL, 10); + break; case 'o': eventsOverlap = strtol(optarg, NULL, 10); break; case 'c': test = true; - break; case 'l': pageLimit = strtol(optarg, NULL, 10); - break; case 'p': pagePublic = true; - break; case 'r': pageRecent = strtol(optarg, NULL, 10); + break; case 'l': eventsLimit = 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; } @@ -108,12 +101,7 @@ int main(int argc, char *argv[]) { } sqlite3_finalize(check); - prepare(&stmt.networks, NetworksQuery); - prepare(&stmt.contexts, ContextsQuery); - prepare(&stmt.eventsAfter, EventsAfterQuery); - prepare(&stmt.eventsBefore, EventsBeforeQuery); - prepare(&stmt.search, SearchQuery); - + dbPrepareAll(); if (test) return EX_OK; if (khttp_fcgi_test()) { @@ -127,7 +115,7 @@ int main(int argc, char *argv[]) { KCGI_OK == (error = khttp_fcgi_parse(fcgi, &req)); khttp_free(&req) ) { - error = request(&req); + error = dispatch(&req); if (error && error != KCGI_HUP) break; } if (error != KCGI_EXIT) { @@ -140,7 +128,7 @@ int main(int argc, char *argv[]) { &req, Keys, KeysLen, Pages, PagesLen, Networks ); if (error) errx(EX_PROTOCOL, "khttp_parse: %s", kcgi_strerror(error)); - error = request(&req); + error = dispatch(&req); if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error)); khttp_free(&req); } diff --git a/server.h b/server.h index c04b871..45008a1 100644 --- a/server.h +++ b/server.h @@ -29,56 +29,73 @@ #define khttp_urlpart(...) kutil_urlpart(NULL, __VA_ARGS__) #endif -#define SQL(...) #__VA_ARGS__ - // Why does it return (const unsigned char *)? #define sqlite3_column_text(...) (const char *)sqlite3_column_text(__VA_ARGS__) +#define SQL(...) #__VA_ARGS__ + +#define ENUM_PAGES \ + X(Networks, "networks") \ + X(Contexts, "contexts") \ + X(Events, "events") \ + X(Search, "search") \ + X(Stylesheet, "stylesheet") + enum { - DatabaseVersionMin = 4, - DatabaseVersionMax = 5, +#define X(page, path) page, + ENUM_PAGES +#undef X + PagesLen, }; +extern const char *Pages[PagesLen]; -#define ENUM_TYPE \ - X(Privmsg, "privmsg") \ - X(Notice, "notice") \ - X(Action, "action") \ - X(Join, "join") \ - X(Part, "part") \ - X(Quit, "quit") \ - X(Kick, "kick") \ - X(Nick, "nick") \ - X(Topic, "topic") \ - X(Ban, "ban") \ - X(Unban, "unban") +#define ENUM_KEYS \ + X(Network, "network", kvalid_stringne) \ + X(Context, "context", kvalid_stringne) \ + X(After, "after", kvalid_stringne) \ + X(Before, "before", kvalid_stringne) \ + X(Query, "query", kvalid_stringne) \ + X(Offset, "offset", kvalid_int) -enum Type { -#define X(id, name) id, - ENUM_TYPE +enum { +#define X(key, name, valid) key, + ENUM_KEYS #undef X - TypesLen, + KeysLen, }; +extern const struct kvalid Keys[KeysLen]; -struct Event { - int64_t event; - time_t time; +struct Scope { const char *network; const char *context; - enum Type type; - const char *nick; - const char *user; - const char *host; - const char *target; - const char *message; + const char *query; }; -extern sqlite3 *db; +static inline struct Scope pageScope(struct kreq *req) { + struct Scope s = {0}; + if (req->fieldmap[Network]) s.network = req->fieldmap[Network]->parsed.s; + if (req->fieldmap[Context]) s.context = req->fieldmap[Context]->parsed.s; + if (req->fieldmap[Query]) s.query = req->fieldmap[Query]->parsed.s; + return s; +} +extern int contextsRecent; +extern bool contextsPublic; extern const char *NetworksQuery; extern const char *ContextsQuery; +enum kcgi_err networksPage(struct kreq *req); +enum kcgi_err contextsPage(struct kreq *req); + +extern int eventsGap; +extern int eventsOverlap; +extern int eventsLimit; extern const char *EventsAfterQuery; extern const char *EventsBeforeQuery; extern const char *SearchQuery; +enum kcgi_err eventsPage(struct kreq *req); +enum kcgi_err searchPage(struct kreq *req); + +extern sqlite3 *db; extern struct Statements { sqlite3_stmt *networks; @@ -88,12 +105,27 @@ extern struct Statements { sqlite3_stmt *search; } stmt; +static inline void dbPrepare(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); +} + +static inline void dbPrepareAll(void) { + dbPrepare(&stmt.networks, NetworksQuery); + dbPrepare(&stmt.contexts, ContextsQuery); + dbPrepare(&stmt.eventsAfter, EventsAfterQuery); + dbPrepare(&stmt.eventsBefore, EventsBeforeQuery); + dbPrepare(&stmt.search, SearchQuery); +} + static inline void dbClose(void) { - if (stmt.networks) sqlite3_finalize(stmt.networks); - if (stmt.contexts) sqlite3_finalize(stmt.contexts); - if (stmt.eventsAfter) sqlite3_finalize(stmt.eventsAfter); - if (stmt.eventsBefore) sqlite3_finalize(stmt.eventsBefore); - if (stmt.search) sqlite3_finalize(stmt.search); + sqlite3_finalize(stmt.networks); + sqlite3_finalize(stmt.contexts); + sqlite3_finalize(stmt.eventsAfter); + sqlite3_finalize(stmt.eventsBefore); + sqlite3_finalize(stmt.search); sqlite3_close(db); } @@ -115,47 +147,52 @@ dbBindText(sqlite3_stmt *stmt, const char *param, const char *value) { 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") \ - X(Stylesheet, "stylesheet") - enum { -#define X(page, path) page, - ENUM_PAGES -#undef X - PagesLen, + DatabaseVersionMin = 4, + DatabaseVersionMax = 5, }; -extern const char *Pages[PagesLen]; - -#define ENUM_KEYS \ - X(Network, "network", kvalid_stringne) \ - X(Context, "context", kvalid_stringne) \ - X(After, "after", kvalid_stringne) \ - X(Before, "before", kvalid_stringne) \ - X(Query, "query", kvalid_stringne) \ - X(Offset, "offset", kvalid_int) +#define ENUM_TYPE \ + X(Privmsg, "privmsg") \ + X(Notice, "notice") \ + X(Action, "action") \ + X(Join, "join") \ + X(Part, "part") \ + X(Quit, "quit") \ + X(Kick, "kick") \ + X(Nick, "nick") \ + X(Topic, "topic") \ + X(Ban, "ban") \ + X(Unban, "unban") -enum { -#define X(key, name, valid) key, - ENUM_KEYS +enum Type { +#define X(id, name) id, + ENUM_TYPE #undef X - KeysLen, + TypesLen, }; -extern const struct kvalid Keys[KeysLen]; - -extern bool pagePublic; -extern int pageLimit; -extern int pageRecent; +struct Event { + int64_t event; + time_t time; + const char *network; + const char *context; + enum Type type; + const char *nick; + const char *user; + const char *host; + const char *target; + const char *message; +}; -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); +extern const char *htmlStylesheet; +enum kcgi_err htmlHead(struct khtmlreq *html, const char *title); +enum kcgi_err htmlScopeFields(struct khtmlreq *html, struct Scope scope); +enum kcgi_err htmlNav(struct khtmlreq *html, struct Scope scope); +enum kcgi_err htmlFooter(struct khtmlreq *html); +enum kcgi_err htmlEvent( + struct khtmlreq *html, struct Scope scope, struct Event event +); static inline enum kcgi_err httpHead(struct kreq *req, enum khttp http, enum kmime mime) { @@ -176,25 +213,3 @@ static inline enum kcgi_err httpFail(struct kreq *req, enum khttp http) { || khttp_body(req) || khttp_printf(req, "%s\n", khttps[http]); } - -struct Scope { - const char *network; - const char *context; - const char *query; -}; - -static inline struct Scope htmlScope(struct kreq *req) { - struct Scope s = {0}; - if (req->fieldmap[Network]) s.network = req->fieldmap[Network]->parsed.s; - if (req->fieldmap[Context]) s.context = req->fieldmap[Context]->parsed.s; - if (req->fieldmap[Query]) s.query = req->fieldmap[Query]->parsed.s; - return s; -} - -extern const char *htmlStylesheet; -enum kcgi_err htmlHead(struct khtmlreq *html, const char *title); -enum kcgi_err htmlNav(struct khtmlreq *html, struct Scope scope); -enum kcgi_err htmlFooter(struct khtmlreq *html); -enum kcgi_err htmlEvent( - struct khtmlreq *html, struct Scope scope, struct Event event -); -- cgit 1.4.1