diff --git a/examples/example1.c b/examples/example1.c new file mode 100644 index 0000000..b224901 --- /dev/null +++ b/examples/example1.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +int +main(void) +{ + + puts("Status: 200 OK\r"); + puts("Content-Type: text/html\r"); + puts("\r"); + puts("Hello, world!"); + return(EXIT_SUCCESS); +} diff --git a/examples/example2.c b/examples/example2.c new file mode 100644 index 0000000..8485f3d --- /dev/null +++ b/examples/example2.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +int +main(void) +{ + struct kreq r; + const char *page = "index"; + + if (KCGI_OK != khttp_parse(&r, NULL, 0, &page, 1, 0)) + return(EXIT_FAILURE); + + khttp_head(&r, kresps[KRESP_STATUS], + "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_CONTENT_TYPE], + "%s", kmimetypes[r.mime]); + khttp_body(&r); + khttp_puts(&r, "Hello, world!\n"); + khttp_free(&r); + return(EXIT_SUCCESS); +} diff --git a/examples/example3.c b/examples/example3.c new file mode 100644 index 0000000..363f23e --- /dev/null +++ b/examples/example3.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +#include "kcgi.h" +#include "kcgihtml.h" + +enum page { + PAGE_INDEX, /* /index or just / */ + PAGE_SENDDATA, /* /senddata */ + PAGE__MAX +}; + +enum key { + KEY_INTEGER, + KEY_FILE, + KEY_PAGECOUNT, + KEY_PAGESIZE, + KEY__MAX +}; + +typedef void (*disp)(struct kreq *); + +static void senddata(struct kreq *); +static void sendindex(struct kreq *); + +static const disp disps[PAGE__MAX] = { + sendindex, /* PAGE_INDEX */ + senddata, /* PAGE_SENDDATA */ +}; + +static const struct kvalid keys[KEY__MAX] = { + { kvalid_int, "integer" }, /* KEY_INTEGER */ + { NULL, "file" }, /* KEY_FILE */ + { kvalid_uint, "count" }, /* KEY_PAGECOUNT */ + { kvalid_uint, "size" }, /* KEY_PAGESIZE */ +}; + +static const char *const pages[PAGE__MAX] = { + "index", /* PAGE_INDEX */ + "senddata" /* PAGE_SENDDATA */ +}; + +static void +resp_open(struct kreq *req, enum khttp http) +{ + + khttp_head(req, kresps[KRESP_STATUS], + "%s", khttps[http]); + khttp_head(req, kresps[KRESP_CONTENT_TYPE], + "%s", kmimetypes[req->mime]); + khttp_body(req); +} + +static void +senddata(struct kreq *req) +{ + int64_t i, j, k, nm, sz; + char *buf; + struct khtmlreq r; + + nm = 1024 * 1024; + if (NULL != req->fieldmap[KEY_PAGECOUNT]) + nm = req->fieldmap[KEY_PAGECOUNT]->parsed.i; + if (0 == nm) + nm = 1; + + sz = 1; + if (NULL != req->fieldmap[KEY_PAGESIZE]) + sz = req->fieldmap[KEY_PAGESIZE]->parsed.i; + if (0 == sz || (uint64_t)sz > SIZE_MAX) + sz = 1; + + buf = kmalloc(sz); + + resp_open(req, KHTTP_200); + khtml_open(&r, req); + khtml_elem(&r, KELEM_DOCTYPE); + khtml_elem(&r, KELEM_HTML); + khtml_elem(&r, KELEM_HEAD); + khtml_elem(&r, KELEM_TITLE); + khtml_puts(&r, "Have a banana."); + khtml_closeelem(&r, 2); + khtml_elem(&r, KELEM_BODY); + for (i = k = 0; i < nm; i++) { + for (j = 0; j < sz; j++) { + if (72 == k++) { + buf[j] = '\n'; + k = 0; + } else + buf[j] = 65 + arc4random_uniform(24); + } + khtml_write(buf, sz, &r); + } + + khtml_close(&r); + free(buf); +} + +static void +sendindex(struct kreq *req) +{ + char *page; + struct khtmlreq r; + const char *cp; + + kasprintf(&page, "%s/%s", req->pname, pages[PAGE_INDEX]); + + resp_open(req, KHTTP_200); + khtml_open(&r, req); + khtml_elem(&r, KELEM_DOCTYPE); + khtml_elem(&r, KELEM_HTML); + khtml_elem(&r, KELEM_HEAD); + khtml_elem(&r, KELEM_TITLE); + khtml_puts(&r, "Welcome!"); + khtml_closeelem(&r, 2); + khtml_elem(&r, KELEM_BODY); + khtml_puts(&r, "Welcome!"); + khtml_attr(&r, KELEM_FORM, + KATTR_METHOD, "post", + KATTR_ENCTYPE, "multipart/form-data", + KATTR_ACTION, page, + KATTR__MAX); + khtml_elem(&r, KELEM_FIELDSET); + khtml_elem(&r, KELEM_LEGEND); + khtml_puts(&r, "Post (multipart)"); + khtml_closeelem(&r, 1); + khtml_elem(&r, KELEM_P); + cp = NULL == req->fieldmap[KEY_INTEGER] ? + "" : req->fieldmap[KEY_INTEGER]->val; + khtml_attr(&r, KELEM_INPUT, + KATTR_TYPE, "number", + KATTR_NAME, keys[KEY_INTEGER].name, + KATTR_VALUE, cp, KATTR__MAX); + khtml_closeelem(&r, 1); + khtml_elem(&r, KELEM_P); + khtml_attr(&r, KELEM_INPUT, + KATTR_TYPE, "file", + KATTR_MULTIPLE, "", + KATTR_NAME, keys[KEY_FILE].name, + KATTR__MAX); + if (NULL != req->fieldmap[KEY_FILE]) { + if (NULL != req->fieldmap[KEY_FILE]->file) { + khtml_puts(&r, "file: "); + khtml_puts(&r, req->fieldmap[KEY_FILE]->file); + khtml_puts(&r, " "); + } + if (NULL != req->fieldmap[KEY_FILE]->ctype) { + khtml_puts(&r, "ctype: "); + khtml_puts(&r, req->fieldmap[KEY_FILE]->ctype); + } + } + khtml_closeelem(&r, 1); + khtml_elem(&r, KELEM_P); + khtml_attr(&r, KELEM_INPUT, + KATTR_TYPE, "submit", + KATTR__MAX); + khtml_closeelem(&r, 0); + khtml_close(&r); + free(page); +} + +int +main(void) +{ + struct kreq r; + + /* + * Set up our main HTTP context. + */ + if (KCGI_OK != khttp_parse + (&r, keys, KEY__MAX, pages, PAGE__MAX, PAGE_INDEX)) + return(EXIT_FAILURE); + + if (KMETHOD_OPTIONS == r.method) { + /* + * Indicate that we accept GET and POST methods. + * This isn't really needed. + */ + khttp_head(&r, kresps[KRESP_STATUS], + "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_ALLOW], + "OPTIONS GET POST"); + khttp_body(&r); + } else if (KMETHOD_GET != r.method && + KMETHOD_POST != r.method) { + /* + * Don't accept non-GET and non-POST methods. + */ + resp_open(&r, KHTTP_405); + } else if (PAGE__MAX == r.page || + KMIME_TEXT_HTML != r.mime) { + /* + * We've been asked for an unknown page or something + * with an unknown extension. + */ + resp_open(&r, KHTTP_404); + khttp_puts(&r, "Page not found."); + } else + /* + * Route to page handler. + */ + (*disps[r.page])(&r); + + /* + * Clean up our context. + */ + khttp_free(&r); + return(EXIT_SUCCESS); +} + diff --git a/examples/example4.c b/examples/example4.c new file mode 100644 index 0000000..7d9c0d7 --- /dev/null +++ b/examples/example4.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +enum page { + PAGE_INDEX, /* /index or just / */ + PAGE__MAX +}; + +static const char *const pages[PAGE__MAX] = { + "index", /* PAGE_INDEX */ +}; + +/* + * Generic routine to prime the HTTP document. + */ +static void +resp_open(struct kreq *req, enum khttp http) +{ + + khttp_head(req, kresps[KRESP_STATUS], + "%s", khttps[http]); + khttp_head(req, kresps[KRESP_CONTENT_TYPE], + "%s", kmimetypes[req->mime]); + khttp_body(req); +} + +/* + * When the database is busy or locked, sleep for a random amount of + * time before continuing. + * We could put all sorts of heuristics in here to back off, but we do + * something really simple. + */ +static void +db_sleep(size_t attempt) +{ + + if (attempt < 10) + usleep(arc4random_uniform(100000)); + else + usleep(arc4random_uniform(500000)); +} + +/* + * Keep trying to open the database forever. + */ +static sqlite3 * +db_open(void) +{ + size_t attempt; + sqlite3 *db; + int rc; + + attempt = 0; +again: + rc = sqlite3_open(DATADIR "/example4.db", &db); + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_open: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_open: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) { + sqlite3_busy_timeout(db, 500); + return(db); + } + + fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db)); + return(NULL); +} + +/* + * Close the database. + */ +static void +db_close(sqlite3 *db) +{ + + if (SQLITE_OK == sqlite3_close(db)) + return; + fprintf(stderr, "sqlite3_close: %s\n", sqlite3_errmsg(db)); +} + +/* + * Step through the result set of a database query. + */ +static int +db_step(sqlite3 *db, sqlite3_stmt *stmt) +{ + int rc; + size_t attempt = 0; + +again: + rc = sqlite3_step(stmt); + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_step: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_step: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } + + if (SQLITE_DONE == rc || SQLITE_ROW == rc) + return(rc); + + fprintf(stderr, "sqlite3_step: %s\n", sqlite3_errmsg(db)); + return(rc); +} + +/* + * Execute a database query. + */ +static int +db_exec(sqlite3 *db, const char *sql) +{ + size_t attempt = 0; + int rc; + +again: + rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_exec: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_exec: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) + return(1); + + fprintf(stderr, "sqlite3_exec: %s (%s)\n", + sqlite3_errmsg(db), sql); + return(0); +} + +/* + * Prepare a database statement. + * If we fail, try to populate the database: it might not have been + * initialised yet. + */ +static sqlite3_stmt * +db_stmt(sqlite3 *db, const char *sql, int recurse) +{ + sqlite3_stmt *stmt; + const char *esql; + size_t attempt = 0; + int rc, bailed; + + bailed = 0; +again: + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_prepare_v2: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_prepare_v2: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) + return(stmt); + + fprintf(stderr, "sqlite3_prepare_v2: %s (%s) (%d)\n", + sqlite3_errmsg(db), sql, sqlite3_errcode(db)); + sqlite3_finalize(stmt); + + /* + * Only attempt to do this if (1) we have a certain error, (2) + * we haven't already bailed out, and (3) we haven't recursed + * into the db_stmt() function. + */ + if (SQLITE_ERROR == rc && 0 == bailed && recurse) { + esql = "CREATE TABLE record " + "(count INTEGER NOT NULL DEFAULT(0))"; + if (db_exec(db, esql)) { + esql = "INSERT INTO record DEFAULT VALUES"; + if (NULL != (stmt = db_stmt(db, esql, 0))) { + db_step(db, stmt); + sqlite3_finalize(stmt); + } + } + bailed = 1; + goto again; + } + + return(NULL); +} + +static void +sendindex(struct kreq *req) +{ + struct khtmlreq r; + int rc; + sqlite3 *db; + sqlite3_stmt *stmt; + + resp_open(req, KHTTP_200); + khtml_open(&r, req); + khtml_elem(&r, KELEM_DOCTYPE); + khtml_elem(&r, KELEM_HTML); + khtml_elem(&r, KELEM_HEAD); + khtml_elem(&r, KELEM_TITLE); + khtml_puts(&r, "Test"); + khtml_closeelem(&r, 2); + khtml_elem(&r, KELEM_BODY); + + if (NULL != (db = db_open())) { + stmt = db_stmt(db, "SELECT count FROM record", 1); + if (NULL != stmt) { + rc = db_step(db, stmt); + if (SQLITE_ROW == rc) + khtml_puts(&r, (char *) + sqlite3_column_text(stmt, 0)); + else if (SQLITE_DONE == rc) + khtml_puts(&r, "No value!?"); + else + khtml_puts(&r, "Error getting value."); + sqlite3_finalize(stmt); + } else + khtml_puts(&r, "Error getting statement."); + + stmt = db_stmt(db, "UPDATE record SET count=count+1", 0); + (void)db_step(db, stmt); + sqlite3_finalize(stmt); + + db_close(db); + } else + khtml_puts(&r, "Error opening database."); + + khtml_close(&r); +} + +int +main(void) +{ + struct kreq r; + + /* + * Set up our main HTTP context. + */ + if (KCGI_OK != khttp_parse + (&r, NULL, 0, pages, PAGE__MAX, PAGE_INDEX)) + return(EXIT_FAILURE); + + if (KMETHOD_OPTIONS == r.method) { + /* + * Indicate that we accept GET and POST methods. + * This isn't really needed. + */ + khttp_head(&r, kresps[KRESP_STATUS], + "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_ALLOW], + "OPTIONS GET POST"); + khttp_body(&r); + } else if (KMETHOD_GET != r.method) { + /* + * Don't accept non-GET and non-POST methods. + */ + resp_open(&r, KHTTP_405); + } else if (PAGE__MAX == r.page || + KMIME_TEXT_HTML != r.mime) { + /* + * We've been asked for an unknown page or something + * with an unknown extension. + */ + resp_open(&r, KHTTP_404); + khttp_puts(&r, "Page not found."); + } else + /* + * Route to page handler. + */ + sendindex(&r); + + /* + * Clean up our context. + */ + khttp_free(&r); + return(EXIT_SUCCESS); +} + diff --git a/examples/example5.c b/examples/example5.c new file mode 100644 index 0000000..5c5cabd --- /dev/null +++ b/examples/example5.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if defined(__APPLE__) +#include +#endif +#include +#include +#include + +int +main(void) +{ +#if defined(__APPLE__) + char *ep; + int rc; +#endif + + /* + * Create a sandbox that only lets us do pure computation: we + * don't open any more file descriptors or do anything fancy. + */ +#if defined(__APPLE__) + rc = sandbox_init + (kSBXProfilePureComputation, + SANDBOX_NAMED, &ep); + if (-1 == rc) { + fprintf(stderr, "sandbox_init: %s\n", ep); + sandbox_free_error(ep); + return(EXIT_FAILURE); + } +#elif defined(__OpenBSD__) + if (-1 == pledge("stdio", NULL)) { + perror("pledge"); + return(EXIT_FAILURE); + } +#endif + + puts("Status: 200 OK\r"); + puts("Content-Type: text/html\r"); + puts("\r"); + puts("Hello, world!"); + return(EXIT_SUCCESS); +} diff --git a/examples/example6.c b/examples/example6.c new file mode 100644 index 0000000..42a4002 --- /dev/null +++ b/examples/example6.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if defined(__APPLE__) +#include +#endif +#include +#include +#include +#include + +#include + +int +main(void) +{ + struct kreq r; + const char *page = "index"; +#if defined(__APPLE__) + char *ep; + int rc; +#endif + + if (KCGI_OK != khttp_parse(&r, NULL, 0, &page, 1, 0)) + return(EXIT_FAILURE); + + /* + * kcgi(3) won't do anything special with the writing routines + * except flush to the stream, possibly by way of zlib. + * Thus, enact a sandbox that allows open file descriptors but + * not anything more. + */ +#if defined(__APPLE__) + rc = sandbox_init + (kSBXProfilePureComputation, + SANDBOX_NAMED, &ep); + if (-1 == rc) { + fprintf(stderr, "sandbox_init: %s\n", ep); + sandbox_free_error(ep); + khttp_free(&r); + return(EXIT_FAILURE); + } +#elif defined(__OpenBSD__) + if (-1 == pledge("stdio", NULL)) { + perror("pledge"); + khttp_free(&r); + return(EXIT_FAILURE); + } +#endif + + khttp_head(&r, kresps[KRESP_STATUS], + "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_CONTENT_TYPE], + "%s", kmimetypes[r.mime]); + khttp_body(&r); + khttp_puts(&r, "Hello, world!\n"); + khttp_free(&r); + return(EXIT_SUCCESS); +} diff --git a/examples/example7.c b/examples/example7.c new file mode 100644 index 0000000..a85044b --- /dev/null +++ b/examples/example7.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2016 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if defined(__APPLE__) +#include +#endif +#include +#include +#include +#include +#include + +#include +#include +#include + +enum page { + PAGE_INDEX, /* /index or just / */ + PAGE__MAX +}; + +static const char *const pages[PAGE__MAX] = { + "index", /* PAGE_INDEX */ +}; + +/* + * Generic routine to prime the HTTP document. + */ +static void +resp_open(struct kreq *req, enum khttp http) +{ + + khttp_head(req, kresps[KRESP_STATUS], + "%s", khttps[http]); + khttp_head(req, kresps[KRESP_CONTENT_TYPE], + "%s", kmimetypes[req->mime]); + khttp_body(req); +} + +/* + * When the database is busy or locked, sleep for a random amount of + * time before continuing. + * We could put all sorts of heuristics in here to back off, but we do + * something really simple. + */ +static void +db_sleep(size_t attempt) +{ + + if (attempt < 10) + usleep(arc4random_uniform(100000)); + else + usleep(arc4random_uniform(500000)); +} + +/* + * Keep trying to open the database forever. + */ +static sqlite3 * +db_open(void) +{ + size_t attempt; + sqlite3 *db; + int rc; + + attempt = 0; +again: + rc = sqlite3_open(DATADIR "/example7.db", &db); + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_open: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_open: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) { + sqlite3_busy_timeout(db, 500); + return(db); + } + + fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db)); + return(NULL); +} + +/* + * Close the database. + */ +static void +db_close(sqlite3 *db) +{ + + if (SQLITE_OK == sqlite3_close(db)) + return; + fprintf(stderr, "sqlite3_close: %s\n", sqlite3_errmsg(db)); +} + +/* + * Step through the result set of a database query. + */ +static int +db_step(sqlite3 *db, sqlite3_stmt *stmt) +{ + int rc; + size_t attempt = 0; + +again: + rc = sqlite3_step(stmt); + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_step: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_step: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } + + if (SQLITE_DONE == rc || SQLITE_ROW == rc) + return(rc); + + fprintf(stderr, "sqlite3_step: %s\n", sqlite3_errmsg(db)); + return(rc); +} + +/* + * Execute a database query. + */ +static int +db_exec(sqlite3 *db, const char *sql) +{ + size_t attempt = 0; + int rc; + +again: + rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_exec: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_exec: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) + return(1); + + fprintf(stderr, "sqlite3_exec: %s (%s)\n", + sqlite3_errmsg(db), sql); + return(0); +} + +/* + * Prepare a database statement. + * If we fail, try to populate the database: it might not have been + * initialised yet. + */ +static sqlite3_stmt * +db_stmt(sqlite3 *db, const char *sql, int recurse) +{ + sqlite3_stmt *stmt; + const char *esql; + size_t attempt = 0; + int rc, bailed; + + bailed = 0; +again: + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + + if (SQLITE_BUSY == rc) { + db_sleep(attempt++); + goto again; + } else if (SQLITE_LOCKED == rc) { + fprintf(stderr, "sqlite3_prepare_v2: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_PROTOCOL == rc) { + fprintf(stderr, "sqlite3_prepare_v2: %s\n", + sqlite3_errmsg(db)); + db_sleep(attempt++); + goto again; + } else if (SQLITE_OK == rc) + return(stmt); + + fprintf(stderr, "sqlite3_prepare_v2: %s (%s) (%d)\n", + sqlite3_errmsg(db), sql, sqlite3_errcode(db)); + sqlite3_finalize(stmt); + + /* + * Only attempt to do this if (1) we have a certain error, (2) + * we haven't already bailed out, and (3) we haven't recursed + * into the db_stmt() function. + */ + if (SQLITE_ERROR == rc && 0 == bailed && recurse) { + esql = "CREATE TABLE record " + "(count INTEGER NOT NULL DEFAULT(0))"; + if (db_exec(db, esql)) { + esql = "INSERT INTO record DEFAULT VALUES"; + if (NULL != (stmt = db_stmt(db, esql, 0))) { + db_step(db, stmt); + sqlite3_finalize(stmt); + } + } + esql = "PRAGMA journal_mode=WAL"; + (void)db_exec(db, esql); + bailed = 1; + goto again; + } + + return(NULL); +} + +static void +sendindex(struct kreq *req) +{ + struct khtmlreq r; + int rc; + sqlite3 *db; + sqlite3_stmt *stmt; + + resp_open(req, KHTTP_200); + khtml_open(&r, req); + khtml_elem(&r, KELEM_DOCTYPE); + khtml_elem(&r, KELEM_HTML); + khtml_elem(&r, KELEM_HEAD); + khtml_elem(&r, KELEM_TITLE); + khtml_puts(&r, "Test"); + khtml_closeelem(&r, 2); + khtml_elem(&r, KELEM_BODY); + + if (NULL != (db = db_open())) { + stmt = db_stmt(db, "SELECT count FROM record", 1); + if (NULL != stmt) { + rc = db_step(db, stmt); + if (SQLITE_ROW == rc) + khtml_puts(&r, (char *) + sqlite3_column_text(stmt, 0)); + else if (SQLITE_DONE == rc) + khtml_puts(&r, "No value!?"); + else + khtml_puts(&r, "Error getting value."); + sqlite3_finalize(stmt); + } else + khtml_puts(&r, "Error getting statement."); + + stmt = db_stmt(db, "UPDATE record SET count=count+1", 0); + (void)db_step(db, stmt); + sqlite3_finalize(stmt); + + db_close(db); + } else + khtml_puts(&r, "Error opening database."); + + khtml_close(&r); +} + +int +main(void) +{ + struct kreq r; +#if defined(__APPLE__) + char *ep; + int rc; +#endif + + /* + * Set up our main HTTP context. + */ + if (KCGI_OK != khttp_parse + (&r, NULL, 0, pages, PAGE__MAX, PAGE_INDEX)) + return(EXIT_FAILURE); + + /* + * Set up a sandbox allowing for file-system access. + * We need this for the database, which is WAL and will thus use + * shared memory primitives. + */ +#if defined(__APPLE__) + rc = sandbox_init + (kSBXProfileNoNetwork, + SANDBOX_NAMED, &ep); + if (-1 == rc) { + fprintf(stderr, "sandbox_init: %s\n", ep); + sandbox_free_error(ep); + khttp_free(&r); + return(EXIT_FAILURE); + } +#elif defined(__OpenBSD__) + if (-1 == pledge("stdio rpath cpath wpath flock", NULL)) { + perror("pledge"); + khttp_free(&r); + return(EXIT_FAILURE); + } +#endif + + if (KMETHOD_OPTIONS == r.method) { + /* + * Indicate that we accept GET and POST methods. + * This isn't really needed. + */ + khttp_head(&r, kresps[KRESP_STATUS], + "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_ALLOW], + "OPTIONS GET POST"); + khttp_body(&r); + } else if (KMETHOD_GET != r.method) { + /* + * Don't accept non-GET and non-POST methods. + */ + resp_open(&r, KHTTP_405); + } else if (PAGE__MAX == r.page || + KMIME_TEXT_HTML != r.mime) { + /* + * We've been asked for an unknown page or something + * with an unknown extension. + */ + resp_open(&r, KHTTP_404); + khttp_puts(&r, "Page not found."); + } else + /* + * Route to page handler. + */ + sendindex(&r); + + /* + * Clean up our context. + */ + khttp_free(&r); + return(EXIT_SUCCESS); +} +