bouquins-bchs/main.c

324 lines
7.3 KiB
C
Raw Normal View History

2016-12-18 16:09:20 +00:00
/* $Id$ */
/*
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
*
* 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 <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <kcgi.h>
#include <kcgijson.h>
#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
};
2016-12-29 17:48:26 +00:00
enum key {
KEY_ID,
2016-12-30 15:16:00 +00:00
KEY_PAGE,
KEY_PERPAGE,
2016-12-29 17:48:26 +00:00
KEY__MAX
};
2016-12-18 16:09:20 +00:00
static const char *const pages[PAGE__MAX] = {
"index", /* PAGE_INDEX */
"books", /* PAGE_BOOKS */
"authors", /* PAGE_AUTHORS */
"series", /* PAGE_SERIES */
};
2016-12-29 17:48:26 +00:00
static const struct kvalid keys[KEY__MAX] = {
{ kvalid_int, "id" }, /* KEY_ID */
2016-12-30 15:16:00 +00:00
{ kvalid_int, "page" }, /* KEY_PAGE */
{ kvalid_int, "perpage" }, /* KEY_PERPAGE */
2016-12-29 17:48:26 +00:00
};
2016-12-18 16:09:20 +00:00
/*
* 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);
}
2016-12-30 14:40:21 +00:00
static void
2016-12-29 23:38:31 +00:00
puterror(struct kjsonreq *req, char *message)
{
kjson_obj_open(req);
kjson_putstringp(req, "msg", message);
kjson_obj_close(req);
}
2016-12-30 14:40:21 +00:00
/*
* 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
2016-12-30 09:10:55 +00:00
putbook(struct kjsonreq *req, Book *b)
2016-12-29 23:38:31 +00:00
{
kjson_obj_open(req);
kjson_putintp(req, "id", b->id);
kjson_putstringp(req, "title", b->title);
2016-12-30 10:19:23 +00:00
if (b->s.id >= 0) {
kjson_objp_open(req, "series");
kjson_putintp(req, "id", b->s.id);
kjson_putstringp(req, "name", b->s.name);
2016-12-30 10:26:26 +00:00
kjson_putdoublep(req, "idx", b->s_idx);
2016-12-30 10:19:23 +00:00
kjson_obj_close(req);
}
2016-12-29 23:38:31 +00:00
}
2016-12-31 14:33:09 +00:00
static void
putauthors(struct kjsonreq *req, BookAdv *b)
{
if (b->asize > 0) {
kjson_arrayp_open(req, "authors");
for (size_t j=0; j<b->asize; 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];
char pubdatestr[30];
putbook(req, &b->ba.b);
kutil_epoch2str(b->timestamp, tsstr, sizeof(tsstr));
kutil_epoch2str(b->pubdate, pubdatestr, sizeof(pubdatestr));
kjson_putstringp(req, "timestamp", tsstr);
kjson_putstringp(req, "pubdate", pubdatestr);
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);
kjson_putstringp(req, "format", b->format);
kjson_putstringp(req, "data", b->dataname);
kjson_putintp(req, "uncompressed_size", b->unz_size);
kjson_putstringp(req, "publisher", b->publisher);
}
2016-12-18 16:09:20 +00:00
static void
sendbooks(struct kreq *r)
{
struct kjsonreq req;
2016-12-29 18:33:51 +00:00
const char *errid;
int64_t id = -1;
2016-12-31 14:33:09 +00:00
BookFull *b = NULL;
2016-12-30 15:16:00 +00:00
int res, i = 0, page = 1, per = 20;
2016-12-18 16:09:20 +00:00
2016-12-30 15:16:00 +00:00
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;
2016-12-29 18:33:51 +00:00
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);
2016-12-30 09:15:14 +00:00
if (id >= 0) {
2016-12-29 22:25:42 +00:00
b = db_book_load(r, id);
}
http_open(r, (id > 0 && NULL == b) ? KHTTP_404 : KHTTP_200);
kjson_open(&req, r);
2016-12-30 09:15:14 +00:00
if (id >= 0 && NULL == b) {
2016-12-29 23:38:31 +00:00
puterror(&req, "Unknown book");
} else if (NULL != b) {
2016-12-31 14:33:09 +00:00
putbookfull(&req, b);
putauthors(&req, &b->ba);
2016-12-30 14:40:21 +00:00
kjson_obj_close(&req);
2016-12-31 14:33:09 +00:00
db_book_full_free(b);
2016-12-29 22:25:42 +00:00
} else {
2016-12-29 23:38:31 +00:00
kjson_array_open(&req);
2016-12-30 15:16:00 +00:00
BookAdv **books = kcalloc(per, sizeof(BookAdv));
res = db_books_load(r, books, per, per * (page-1));
2016-12-29 23:38:31 +00:00
while (i < res) {
2016-12-30 10:19:23 +00:00
Book b = books[i]->b;
putbook(&req, &b);
2016-12-31 14:33:09 +00:00
putauthors(&req, books[i]);
2016-12-30 14:40:21 +00:00
kjson_obj_close(&req);
2016-12-30 10:19:23 +00:00
db_book_adv_free(books[i]);
2016-12-29 23:45:20 +00:00
i++;
2016-12-29 23:38:31 +00:00
}
2016-12-29 23:45:20 +00:00
free(books);
2016-12-29 23:38:31 +00:00
kjson_array_close(&req);
2016-12-29 20:02:58 +00:00
}
2016-12-18 16:09:20 +00:00
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);
2016-12-18 17:28:00 +00:00
kjson_putstringp(&req, "data", "authors");
2016-12-18 16:09:20 +00:00
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);
2016-12-18 17:28:00 +00:00
kjson_putstringp(&req, "data", "series");
2016-12-18 16:09:20 +00:00
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);
2016-12-31 09:52:48 +00:00
kjson_putintp(&req, "count", db_books_count(r));
2016-12-18 16:09:20 +00:00
kjson_obj_close(&req);
kjson_close(&req);
}
int
main(void)
{
struct kreq r;
enum kcgi_err er;
/* Actually parse HTTP document. */
2016-12-29 17:26:47 +00:00
er = khttp_parsex(&r, ksuffixmap,
2016-12-29 17:48:26 +00:00
kmimetypes, KMIME__MAX, keys, KEY__MAX,
2016-12-29 17:26:47 +00:00
pages, PAGE__MAX, KMIME_APP_JSON,
PAGE_INDEX, NULL, NULL, 0, NULL);
2016-12-18 16:09:20 +00:00
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)) {
2016-12-18 17:28:00 +00:00
fputs("pledge", stderr);
2016-12-18 16:09:20 +00:00
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);
}