/* $Id$ */ /* * 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 #include #include #include "extern.h" /* * Start with five pages. * As you add more pages, you'll start by giving them an identifier key * in this enum. */ enum page { PAGE_INDEX, PAGE_BOOKS, PAGE_AUTHORS, PAGE_SERIES, PAGE__MAX }; enum key { KEY_ID, KEY_PAGE, KEY_PERPAGE, KEY__MAX }; static const char *const pages[PAGE__MAX] = { "index", /* PAGE_INDEX */ "books", /* PAGE_BOOKS */ "authors", /* PAGE_AUTHORS */ "series", /* PAGE_SERIES */ }; static const struct kvalid keys[KEY__MAX] = { { kvalid_int, "id" }, /* KEY_ID */ { kvalid_int, "page" }, /* KEY_PAGE */ { kvalid_int, "perpage" }, /* KEY_PERPAGE */ }; /* * Fill out all HTTP secure headers. * Use the existing document's MIME type. */ static void http_alloc(struct kreq *r, enum khttp code) { khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[code]); khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[r->mime]); khttp_head(r, "X-Content-Type-Options", "nosniff"); khttp_head(r, "X-Frame-Options", "DENY"); khttp_head(r, "X-XSS-Protection", "1; mode=block"); } /* * Fill out all headers with http_alloc() then start the HTTP document * body (no more headers after this point!) */ static void http_open(struct kreq *r, enum khttp code) { http_alloc(r, code); khttp_body(r); } static void puterror(struct kjsonreq *req, char *message) { kjson_obj_open(req); kjson_putstringp(req, "msg", message); kjson_obj_close(req); } /* * Append author object to json (NOT CLOSED). */ static void putauthor(struct kjsonreq *req, Author *a) { kjson_obj_open(req); kjson_putintp(req, "id", a->id); kjson_putstringp(req, "name", a->name); } /* * Append book object to json (NOT CLOSED). */ static void putbook(struct kjsonreq *req, Book *b) { kjson_obj_open(req); kjson_putintp(req, "id", b->id); kjson_putstringp(req, "title", b->title); if (b->s.id >= 0) { kjson_objp_open(req, "series"); kjson_putintp(req, "id", b->s.id); kjson_putstringp(req, "name", b->s.name); kjson_putdoublep(req, "idx", b->s_idx); kjson_obj_close(req); } } static void putbookdata(struct kjsonreq *req, BookFull *b) { if (b->dsize > 0) { kjson_arrayp_open(req, "data"); for (size_t i=0;idsize;i++) { kjson_obj_open(req); kjson_putstringp(req, "name", b->data[i]->name); kjson_putstringp(req, "format", b->data[i]->format); kjson_putintp(req, "size", b->data[i]->size); kjson_obj_close(req); } kjson_array_close(req); } } static void putauthors(struct kjsonreq *req, BookAdv *b) { if (b->asize > 0) { kjson_arrayp_open(req, "authors"); for (size_t j=0; jasize; j++) { putauthor(req, b->a[j]); kjson_obj_close(req); } kjson_array_close(req); } } static void putbookfull(struct kjsonreq *req, BookFull *b) { char tsstr[30]; putbook(req, &b->ba.b); kutil_epoch2str(b->timestamp, tsstr, sizeof(tsstr)); kjson_putstringp(req, "timestamp", tsstr); kjson_putintp(req, "pubdate", b->pubdate); kjson_putstringp(req, "isbn", b->isbn); kjson_putstringp(req, "lccn", b->lccn); kjson_putstringp(req, "path", b->path); kjson_putstringp(req, "uuid", b->uuid); kjson_putboolp(req, "has_cover", b->has_cover); kjson_putstringp(req, "lang", b->lang); if (NULL != b->publisher) kjson_putstringp(req, "publisher", b->publisher); } static void sendbooks(struct kreq *r) { struct kjsonreq req; const char *errid; int64_t id = -1; BookFull *b = NULL; int res, i = 0, page = 1, per = 20; if (NULL != r->fieldmap[KEY_PAGE]) page = r->fieldmap[KEY_PAGE]->parsed.i; if (NULL != r->fieldmap[KEY_PERPAGE]) per = r->fieldmap[KEY_PERPAGE]->parsed.i; if (NULL != r->fieldmap[KEY_ID]) id = r->fieldmap[KEY_ID]->parsed.i; if (r->path[0] != '\0') id = strtonum(r->path, INT64_MIN, INT64_MAX, &errid); if (id >= 0) { b = db_book_load(r, id); } http_open(r, (id > 0 && NULL == b) ? KHTTP_404 : KHTTP_200); kjson_open(&req, r); if (id >= 0 && NULL == b) { puterror(&req, "Unknown book"); } else if (NULL != b) { putbookfull(&req, b); putauthors(&req, &b->ba); putbookdata(&req, b); kjson_obj_close(&req); db_book_full_free(b); } else { kjson_array_open(&req); BookAdv **books = kcalloc(per, sizeof(BookAdv)); res = db_books_load(r, books, per, per * (page-1)); while (i < res) { Book b = books[i]->b; putbook(&req, &b); putauthors(&req, books[i]); kjson_obj_close(&req); db_book_adv_free(books[i]); i++; } free(books); kjson_array_close(&req); } kjson_close(&req); } static void sendauthors(struct kreq *r) { struct kjsonreq req; http_open(r, KHTTP_200); kjson_open(&req, r); kjson_obj_open(&req); kjson_putstringp(&req, "data", "authors"); kjson_obj_close(&req); kjson_close(&req); } static void sendseries(struct kreq *r) { struct kjsonreq req; http_open(r, KHTTP_200); kjson_open(&req, r); kjson_obj_open(&req); kjson_putstringp(&req, "data", "series"); kjson_obj_close(&req); kjson_close(&req); } static void sendindex(struct kreq *r) { struct kjsonreq req; http_open(r, KHTTP_200); kjson_open(&req, r); kjson_obj_open(&req); kjson_putintp(&req, "count", db_books_count(r)); kjson_obj_close(&req); kjson_close(&req); } int main(void) { struct kreq r; enum kcgi_err er; /* Actually parse HTTP document. */ er = khttp_parsex(&r, ksuffixmap, kmimetypes, KMIME__MAX, keys, KEY__MAX, pages, PAGE__MAX, KMIME_APP_JSON, PAGE_INDEX, NULL, NULL, 0, NULL); if (KCGI_OK != er) { fprintf(stderr, "HTTP parse error: %d\n", er); return(EXIT_FAILURE); } #ifdef __OpenBSD__ if (-1 == pledge("stdio rpath cpath wpath flock fattr", NULL)) { fputs("pledge", stderr); khttp_free(&r); return(EXIT_FAILURE); } #endif /* * Front line of defence: make sure we're a proper method, make * sure we're a page, make sure we're a JSON file. */ if (KMETHOD_GET != r.method && KMETHOD_POST != r.method) { http_open(&r, KHTTP_405); khttp_free(&r); return(EXIT_SUCCESS); } else if (PAGE__MAX == r.page || KMIME_APP_JSON != r.mime) { http_open(&r, KHTTP_404); khttp_puts(&r, "Page not found."); khttp_free(&r); return(EXIT_SUCCESS); } if ( ! db_open(&r, DATADIR "/" DATABASE)) { http_open(&r, KHTTP_500); json_emptydoc(&r); khttp_free(&r); return(EXIT_SUCCESS); } switch (r.page) { case (PAGE_INDEX): sendindex(&r); break; case (PAGE_BOOKS): sendbooks(&r); break; case (PAGE_AUTHORS): sendauthors(&r); break; case (PAGE_SERIES): sendseries(&r); break; default: abort(); } db_close(&r); khttp_free(&r); return(EXIT_SUCCESS); }