timekeeper

[Abandoned unfinished] CGI web application in C for time tracking. (My first, just a learning project)
Log | Files | Refs | README

commit 25431f2fde33ec1fc58d5c7bd27142356f4a1b03
parent e46326f376410983fc97685498425a6c5a361760
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Tue,  5 Mar 2024 22:55:12 -0800

Move most page functions into the pages directory

To support this, CGI/HTTP keys were moved to key.c & key.h. Also,
a list of page names was added to pagedata.

Diffstat:
MMakefile | 5+++--
Akey.c | 43+++++++++++++++++++++++++++++++++++++++++++
Akey.h | 11+++++++++++
Mpage.h | 1+
Apages/404.c | 7+++++++
Apages/account.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Apages/common.h | 12++++++++++++
Apages/index.c | 34++++++++++++++++++++++++++++++++++
Apages/login.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apages/logout.c | 19+++++++++++++++++++
Apages/pages.h | 15+++++++++++++++
Mpages/util.c | 14++++++++++++++
Mpages/util.h | 1+
Mtimekeeper.c | 264++-----------------------------------------------------------------------------
14 files changed, 321 insertions(+), 262 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,8 +4,9 @@ cflags = ${CFLAGS} -O0 -Wall -Wextra -I/usr/local/include lddflags = ${LDDFLAGS} -static -L/usr/local/lib -lkcgi -lkcgihtml -lz -lsqlbox -lsqlite3 -lm -lpthread prefix = /var/www/htdocs/${name}.primus.lan -src = page.c html.c user.c stmt.c pages/util.c -hdr = ${src:.c=.h} util.h +pages = pages/index.c pages/404.c pages/login.c pages/logout.c pages/account.c +src = page.c html.c user.c stmt.c key.c pages/util.c ${pages} +hdr = page.h html.h user.h stmt.h key.h util.h pages/util.h pages/pages.h obj = ${src:.c=.o} all: ${name} diff --git a/key.c b/key.c @@ -0,0 +1,43 @@ +#include <sys/types.h> +#include <stdarg.h> +#include <stdint.h> +#include <kcgi.h> +#include <kcgihtml.h> + +#include <ctype.h> + +#include "key.h" + +#include "page.h" +#include "user.h" + +int +kvalid_name(struct kpair *p) +{ + char *s; + + if (!kvalid_stringne(p) || p->valsz < NameMin || p->valsz > NameMax) + return 0; + + for (s = p->val; *s; ++s) + if (!isalnum(*s) && *s != '-') + return 0; + return 1; +} + +int +kvalid_pass(struct kpair *p) +{ + if (!kvalid_stringne(p) || p->valsz < PassMin || p->valsz > PassMax) + return 0; + return 1; +} + +struct kvalid keys[] = { + [KeyUsername] = { kvalid_name, "username" }, + [KeyPassword] = { kvalid_pass, "password" }, + [KeyHash] = { kvalid_stringne, "hash" }, + [KeyCreate] = { kvalid_stringne, "create" }, + [KeyDelete] = { kvalid_stringne, "delete" }, + [KeyTime] = { kvalid_stringne, "time" } +}; diff --git a/key.h b/key.h @@ -0,0 +1,11 @@ +enum Field { + KeyUsername, + KeyPassword, + KeyHash, /* Authentication hash */ + KeyCreate, /* Whether to create a new user in login page */ + KeyDelete, /* Delete account in account page */ + KeyTime, /* one of start, startbreak, endbreak, end */ + KeyMax +}; + +extern struct kvalid keys[]; diff --git a/page.h b/page.h @@ -4,6 +4,7 @@ struct pagedata { struct sqlbox *db; size_t dbid; struct kvalid *keys; + char **pages; /* page names */ }; enum kcgi_err showpage(struct pagedata *pd, char **names, diff --git a/pages/404.c b/pages/404.c @@ -0,0 +1,7 @@ +#include "common.h" + +enum kcgi_err +page404(struct pagedata *pd) +{ + return errorpage(pd, KHTTP_404); +} diff --git a/pages/account.c b/pages/account.c @@ -0,0 +1,50 @@ +#include <string.h> + +#include "common.h" +#include "pages.h" + +enum kcgi_err +pageaccount(struct pagedata *pd) +{ + enum kcgi_err status; + struct user *user; + + user = sitegetlogin(pd); + if (!user) + return errorpage(pd, KHTTP_401); + + if (pd->req.fieldmap[KeyDelete] && strcmp(pd->req.fieldmap[KeyDelete]->parsed.s, "yes") == 0) { + if (deleteuser(pd, user->hash) == 0) + return redirect(pd, pd->pages[PageIndex], "Account deleted"); + else + return errorpage(pd, KHTTP_500); + } + + if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK || + (status = htmlwithin(pd, KELEM_H1, "Account")) != KCGI_OK || + (status = htmlwithin(pd, KELEM_H2, "Delete account")) != KCGI_OK || + (status = htmlwithin(pd, KELEM_P, "Deleting an account is irreversible:" + " no account data can be restored after deletion.")) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || + (status = khtml_puts(&pd->html, "Confirm permanent deletion:")) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "checkbox", + KATTR_NAME, pd->keys[KeyDelete].name, + KATTR_VALUE, "yes", + KATTR_REQUIRED, "true", + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || + (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "submit", + KATTR_VALUE, "Delete", + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) { + freeuser(user); + return status; + } + + freeuser(user); + return khtml_close(&pd->html); +} diff --git a/pages/common.h b/pages/common.h @@ -0,0 +1,12 @@ +#include <sys/types.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <kcgi.h> +#include <kcgihtml.h> + +#include "../page.h" +#include "../html.h" +#include "../user.h" +#include "../key.h" +#include "util.h" diff --git a/pages/index.c b/pages/index.c @@ -0,0 +1,34 @@ +#include "common.h" + +#include "pages.h" + +enum kcgi_err +pageindex(struct pagedata *pd) +{ + enum kcgi_err status; + struct user *user; + + user = sitegetlogin(pd); + + if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK) + return status; + + khtml_elem(&pd->html, KELEM_H1); + khtml_printf(&pd->html, "Welcome %s!", user ? user->name : "friend"); + khtml_closeelem(&pd->html, 1); + + if ((status = htmllink(pd, user ? "/logout" : "/login", + user ? "Logout" : "Login")) != KCGI_OK) + return status; + + if (user) { + if ((status = khtml_puts(&pd->html, " | ")) != KCGI_OK || + (status = htmllink(pd, pd->pages[PageAccount], "Manage account"))) + return status; + if ((status = khtml_puts(&pd->html, " | ")) != KCGI_OK || + (status = htmllink(pd, pd->pages[PageMain], "Timekeeper"))) + return status; + } + + return khtml_close(&pd->html); +} diff --git a/pages/login.c b/pages/login.c @@ -0,0 +1,107 @@ +#include <assert.h> + +#define const + +#include <ctype.h> + +#include "common.h" +#include "pages.h" + +enum kcgi_err +htmlinput(struct pagedata *pd, enum Field field, char *type, int64_t minl, int64_t maxl) +{ + enum kcgi_err status; + + assert(pd->keys[field].name[0] != 0); + + if ((status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || + (status = khtml_printf(&pd->html, "%c%s: ", + toupper(pd->keys[field].name[0]), pd->keys[field].name + 1)) != KCGI_OK || + (status = khtml_attrx(&pd->html, KELEM_INPUT, + KATTR_NAME, KATTRX_STRING, pd->keys[field].name, + KATTR_TYPE, KATTRX_STRING, type, + KATTR_MINLENGTH, KATTRX_INT, minl, + KATTR_MAXLENGTH, KATTRX_INT, maxl, + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + + return KCGI_OK; +} + +enum kcgi_err +pagelogin(struct pagedata *pd) +{ + enum kcgi_err status; + struct user *user; + char *fuser, *fpass; + enum LoginStatus ls; + char *msg; + + user = NULL; + ls = LoginUntried; + if (pd->req.fieldmap[KeyUsername] && pd->req.fieldmap[KeyPassword]) { + fuser = pd->req.fieldmap[KeyUsername]->parsed.s; + fpass = pd->req.fieldmap[KeyPassword]->parsed.s; + + if (pd->req.fieldmap[KeyCreate]) { + if (adduser(pd, fuser, fpass)) + ls = LoginError; + + } + if (ls == LoginUntried) { + ls = loginuser(&user, pd, fuser, fpass); + if (ls == LoginValid) { + status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], "%s=%s; Path=/", + pd->keys[KeyHash].name, user->hash); + if (status != KCGI_OK) + return status; + } + } + } else { + user = sitegetlogin(pd); + } + + if (user) + return redirect(pd, pd->pages[PageIndex], "Logged in"); + + if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK) + return status; + + if ((status = htmlwithin(pd, KELEM_H1, "Login")) != KCGI_OK) + return status; + + if (ls > 0) { + if (ls == LoginInvalid) + msg = "Error: Invalid credentials"; + else if (ls == LoginError) + msg = "Error: System error"; + else + msg = "Error: Undefined error"; + + if ((status = htmlwithin(pd, KELEM_P, msg)) != KCGI_OK) + return status; + } + + if ((status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || + (status = htmlinput(pd, KeyUsername, "text", NameMin, NameMax)) != KCGI_OK || + (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || + (status = htmlinput(pd, KeyPassword, "password", PassMin, PassMax)) != KCGI_OK || + (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || + (status = khtml_puts(&pd->html, "Create user ")) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "checkbox", + KATTR_NAME, pd->keys[KeyCreate].name, + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || + (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "submit", + KATTR_VALUE, "Submit", + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + + return khtml_close(&pd->html); +} diff --git a/pages/logout.c b/pages/logout.c @@ -0,0 +1,19 @@ +#include "common.h" + +#include "pages.h" + +enum kcgi_err +pagelogout(struct pagedata *pd) +{ + enum kcgi_err status; + struct user *user; + + if ((user = sitegetlogin(pd))) { + freeuser(user); + if ((status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], + "%s=; Path=/", pd->keys[KeyHash].name)) != KCGI_OK) + return status; + } + + return redirect(pd, pd->pages[PageIndex], "You are being logged out"); +} diff --git a/pages/pages.h b/pages/pages.h @@ -0,0 +1,15 @@ +enum Page { + PageIndex, + PageLogin, + PageLogout, + PageAccount, + PageMain, + Page404, + PageMax +}; + +enum kcgi_err page404(struct pagedata *pd); +enum kcgi_err pageindex(struct pagedata *pd); +enum kcgi_err pagelogin(struct pagedata *pd); +enum kcgi_err pagelogout(struct pagedata *pd); +enum kcgi_err pageaccount(struct pagedata *pd); diff --git a/pages/util.c b/pages/util.c @@ -1,10 +1,15 @@ +#define const + #include <sys/types.h> #include <stdarg.h> +#include <stddef.h> #include <stdint.h> #include <kcgi.h> #include <kcgihtml.h> #include "../page.h" +#include "../user.h" +#include "../key.h" enum kcgi_err errorpage(struct pagedata *pd, enum khttp code) @@ -64,3 +69,12 @@ redirect(struct pagedata *pd, char *to, char *msg) return status; return khtml_close(&pd->html); } + +/* getlogin is taken */ +struct user * +sitegetlogin(struct pagedata *pd) +{ + if (!pd->req.cookiemap[KeyHash]) + return NULL; + return getuser(pd, "hash", pd->req.cookiemap[KeyHash]->parsed.s); +} diff --git a/pages/util.h b/pages/util.h @@ -1,3 +1,4 @@ enum kcgi_err errorpage(struct pagedata *pd, enum khttp code); enum kcgi_err starthtmldoc(struct pagedata *pd, enum khttp code); enum kcgi_err redirect(struct pagedata *pd, char *to, char *msg); +struct user *sitegetlogin(struct pagedata *pd); diff --git a/timekeeper.c b/timekeeper.c @@ -3,10 +3,8 @@ #define const #include <sys/types.h> -#include <ctype.h> #include <err.h> #include <limits.h> -#include <pwd.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -24,32 +22,14 @@ #include "html.h" #include "user.h" #include "stmt.h" +#include "key.h" #include "pages/util.h" +#include "pages/pages.h" #include "util.h" #define Promises "stdio rpath wpath cpath proc recvfd unix sendfd" -enum Field { - KeyUsername, - KeyPassword, - KeyHash, /* Authentication hash */ - KeyCreate, /* Whether to create a new user in login page */ - KeyDelete, /* Delete account in account page */ - KeyTime, /* one of start, startbreak, endbreak, end */ - KeyMax -}; - -enum Page { - PageIndex, - PageLogin, - PageLogout, - PageAccount, - PageMain, - Page404, - PageMax -}; - enum TimeField { StartTime, BreakStartTime, @@ -74,28 +54,6 @@ char *timefields[] = { }; int -kvalid_name(struct kpair *p) -{ - char *s; - - if (!kvalid_stringne(p) || p->valsz < NameMin || p->valsz > NameMax) - return 0; - - for (s = p->val; *s; ++s) - if (!isalnum(*s) && *s != '-') - return 0; - return 1; -} - -int -kvalid_pass(struct kpair *p) -{ - if (!kvalid_stringne(p) || p->valsz < PassMin || p->valsz > PassMax) - return 0; - return 1; -} - -int initdb(struct pagedata *pd) { enum StmtID i; @@ -106,213 +64,6 @@ initdb(struct pagedata *pd) return 0; } -/* getlogin is taken */ -struct user * -sitegetlogin(struct pagedata *pd) -{ - if (!pd->req.cookiemap[KeyHash]) - return NULL; - return getuser(pd, "hash", pd->req.cookiemap[KeyHash]->parsed.s); -} - -enum kcgi_err -page404(struct pagedata *pd) -{ - return errorpage(pd, KHTTP_404); -} - -enum kcgi_err -pageindex(struct pagedata *pd) -{ - enum kcgi_err status; - struct user *user; - - user = sitegetlogin(pd); - - if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK) - return status; - - khtml_elem(&pd->html, KELEM_H1); - khtml_printf(&pd->html, "Welcome %s!", user ? user->name : "friend"); - khtml_closeelem(&pd->html, 1); - - if ((status = htmllink(pd, user ? "/logout" : "/login", - user ? "Logout" : "Login")) != KCGI_OK) - return status; - - if (user) { - if ((status = khtml_puts(&pd->html, " | ")) != KCGI_OK || - (status = htmllink(pd, pages[PageAccount], "Manage account"))) - return status; - if ((status = khtml_puts(&pd->html, " | ")) != KCGI_OK || - (status = htmllink(pd, pages[PageMain], "Timekeeper"))) - return status; - } - - return khtml_close(&pd->html); -} - -enum kcgi_err -htmlinput(struct pagedata *pd, enum Field field, char *type, int64_t minl, int64_t maxl) -{ - enum kcgi_err status; - - assert(pd->keys[field].name[0] != 0); - - if ((status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || - (status = khtml_printf(&pd->html, "%c%s: ", - toupper(pd->keys[field].name[0]), pd->keys[field].name + 1)) != KCGI_OK || - (status = khtml_attrx(&pd->html, KELEM_INPUT, - KATTR_NAME, KATTRX_STRING, pd->keys[field].name, - KATTR_TYPE, KATTRX_STRING, type, - KATTR_MINLENGTH, KATTRX_INT, minl, - KATTR_MAXLENGTH, KATTRX_INT, maxl, - KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) - return status; - - return KCGI_OK; -} - -enum kcgi_err -pagelogin(struct pagedata *pd) -{ - enum kcgi_err status; - struct user *user; - char *fuser, *fpass; - enum LoginStatus ls; - char *msg; - - user = NULL; - ls = LoginUntried; - if (pd->req.fieldmap[KeyUsername] && pd->req.fieldmap[KeyPassword]) { - fuser = pd->req.fieldmap[KeyUsername]->parsed.s; - fpass = pd->req.fieldmap[KeyPassword]->parsed.s; - - if (pd->req.fieldmap[KeyCreate]) { - if (adduser(pd, fuser, fpass)) - ls = LoginError; - - } - if (ls == LoginUntried) { - ls = loginuser(&user, pd, fuser, fpass); - if (ls == LoginValid) { - status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], "%s=%s; Path=/", - pd->keys[KeyHash].name, user->hash); - if (status != KCGI_OK) - return status; - } - } - } else { - user = sitegetlogin(pd); - } - - if (user) - return redirect(pd, pages[PageIndex], "Logged in"); - - if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK) - return status; - - if ((status = htmlwithin(pd, KELEM_H1, "Login")) != KCGI_OK) - return status; - - if (ls > 0) { - if (ls == LoginInvalid) - msg = "Error: Invalid credentials"; - else if (ls == LoginError) - msg = "Error: System error"; - else - msg = "Error: Undefined error"; - - if ((status = htmlwithin(pd, KELEM_P, msg)) != KCGI_OK) - return status; - } - - if ((status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || - (status = htmlinput(pd, KeyUsername, "text", NameMin, NameMax)) != KCGI_OK || - (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || - (status = htmlinput(pd, KeyPassword, "password", PassMin, PassMax)) != KCGI_OK || - (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || - (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || - (status = khtml_puts(&pd->html, "Create user ")) != KCGI_OK || - (status = khtml_attr(&pd->html, KELEM_INPUT, - KATTR_TYPE, "checkbox", - KATTR_NAME, pd->keys[KeyCreate].name, - KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || - (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || - (status = khtml_attr(&pd->html, KELEM_INPUT, - KATTR_TYPE, "submit", - KATTR_VALUE, "Submit", - KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) - return status; - - return khtml_close(&pd->html); -} - -enum kcgi_err -pagelogout(struct pagedata *pd) -{ - enum kcgi_err status; - struct user *user; - - if ((user = sitegetlogin(pd))) { - freeuser(user); - if ((status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], - "%s=; Path=/", pd->keys[KeyHash].name)) != KCGI_OK) - return status; - } - - return redirect(pd, pages[PageIndex], "You are being logged out"); -} - -enum kcgi_err -pageaccount(struct pagedata *pd) -{ - enum kcgi_err status; - struct user *user; - - user = sitegetlogin(pd); - if (!user) - return errorpage(pd, KHTTP_401); - - if (pd->req.fieldmap[KeyDelete] && strcmp(pd->req.fieldmap[KeyDelete]->parsed.s, "yes") == 0) { - if (deleteuser(pd, user->hash) == 0) - return redirect(pd, pages[PageIndex], "Account deleted"); - else - return errorpage(pd, KHTTP_500); - } - - if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK || - (status = htmlwithin(pd, KELEM_H1, "Account")) != KCGI_OK || - (status = htmlwithin(pd, KELEM_H2, "Delete account")) != KCGI_OK || - (status = htmlwithin(pd, KELEM_P, "Deleting an account is irreversible:" - " no account data can be restored after deletion.")) != KCGI_OK || - (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || - (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || - (status = khtml_puts(&pd->html, "Confirm permanent deletion:")) != KCGI_OK || - (status = khtml_attr(&pd->html, KELEM_INPUT, - KATTR_TYPE, "checkbox", - KATTR_NAME, pd->keys[KeyDelete].name, - KATTR_VALUE, "yes", - KATTR_REQUIRED, "true", - KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || - (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || - (status = khtml_attr(&pd->html, KELEM_INPUT, - KATTR_TYPE, "submit", - KATTR_VALUE, "Delete", - KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) { - freeuser(user); - return status; - } - - freeuser(user); - return khtml_close(&pd->html); -} - enum sqlbox_code newrow(struct pagedata *pd, char *hash) { @@ -660,16 +411,9 @@ main(void) { enum kcgi_err status; struct kfcgi *fcgi; - struct kvalid keys[] = { - [KeyUsername] = { kvalid_name, "username" }, - [KeyPassword] = { kvalid_pass, "password" }, - [KeyHash] = { kvalid_stringne, "hash" }, - [KeyCreate] = { kvalid_stringne, "create" }, - [KeyDelete] = { kvalid_stringne, "delete" }, - [KeyTime] = { kvalid_stringne, "time" } - }; struct pagedata pd = { - .keys = keys + .keys = keys, + .pages = pages }; struct sqlbox_cfg cfg; struct sqlbox_src srcs[] = {