Mercurial > paster
changeset 79:52029a52a385
pasterd: revert using ktemplate
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 17 Mar 2023 07:43:20 +0100 |
parents | 9bfe5ce3cc45 |
children | 9bc744a4a292 |
files | GNUmakefile database.c database.h extern/LICENSE.libmustach.txt extern/VERSION.libmustach.txt extern/libmustach/mustach-jansson.c extern/libmustach/mustach-jansson.h extern/libmustach/mustach-wrap.c extern/libmustach/mustach-wrap.h extern/libmustach/mustach.c extern/libmustach/mustach.h html/header.html html/index.html html/new.html html/paste.html html/search.html html/status.html http.c json-util.c json-util.h log.c page-download.c page-fork.c page-index.c page-index.h page-new.c page-new.h page-paste.c page-paste.h page-search.c page-status.c page-status.h page.c page.h paste.c paste.h pasterd.c util.c util.h |
diffstat | 39 files changed, 771 insertions(+), 2511 deletions(-) [+] |
line wrap: on
line diff
--- a/GNUmakefile Thu Mar 16 20:45:59 2023 +0100 +++ b/GNUmakefile Fri Mar 17 07:43:20 2023 +0100 @@ -30,25 +30,17 @@ VARDIR ?= $(PREFIX)/var # External libraries -KCGI_INCS := $(shell pkg-config --cflags kcgi) -KCGI_LIBS := $(shell pkg-config --libs kcgi) - -JANSSON_INCS := $(shell pkg-config --cflags jansson) -JANSSON_LIBS := $(shell pkg-config --libs jansson) +KCGI_INCS := $(shell pkg-config --cflags kcgi kcgi-html) +KCGI_LIBS := $(shell pkg-config --libs kcgi kcgi-html) # No user options below this line. VERSION := 0.3.0 -LIBPASTER_SRCS := extern/libmustach/mustach-jansson.c -LIBPASTER_SRCS += extern/libmustach/mustach-wrap.c -LIBPASTER_SRCS += extern/libmustach/mustach.c -LIBPASTER_SRCS += extern/libmustach/mustach.c LIBPASTER_SRCS += extern/libsqlite/sqlite3.c LIBPASTER_SRCS += config.c LIBPASTER_SRCS += database.c LIBPASTER_SRCS += http.c -LIBPASTER_SRCS += json-util.c LIBPASTER_SRCS += log.c LIBPASTER_SRCS += page-download.c LIBPASTER_SRCS += page-fork.c @@ -57,7 +49,9 @@ LIBPASTER_SRCS += page-paste.c LIBPASTER_SRCS += page-search.c LIBPASTER_SRCS += page-static.c +LIBPASTER_SRCS += page-status.c LIBPASTER_SRCS += page.c +LIBPASTER_SRCS += paste.c LIBPASTER_SRCS += util.c LIBPASTER_OBJS := $(LIBPASTER_SRCS:.c=.o) LIBPASTER_DEPS := $(LIBPASTER_SRCS:.c=.d) @@ -92,10 +86,8 @@ override CFLAGS += -DVARDIR=\"$(VARDIR)\" override CFLAGS += -I. override CFLAGS += -Iextern -override CFLAGS += -Iextern/libmustach override CFLAGS += -Iextern/libsqlite override CFLAGS += $(KCGI_INCS) -override CFLAGS += $(JANSSON_INCS) override CPPFLAGS := -MMD @@ -123,7 +115,7 @@ $(LIBPASTER_SRCS): $(LIBPASTER_HTML_OBJS) $(LIBPASTER_SQL_OBJS) $(LIBPASTER): $(LIBPASTER_OBJS) -pasterd: private LDLIBS += $(KCGI_LIBS) $(JANSSON_LIBS) +pasterd: private LDLIBS += $(KCGI_LIBS) pasterd: $(LIBPASTER) clean:
--- a/database.c Thu Mar 16 20:45:59 2023 +0100 +++ b/database.c Fri Mar 17 07:43:20 2023 +0100 @@ -24,8 +24,8 @@ #include <sqlite3.h> #include "database.h" -#include "json-util.h" #include "log.h" +#include "paste.h" #include "util.h" #include "sql/clear.h" @@ -35,23 +35,27 @@ #include "sql/recents.h" #include "sql/search.h" -#define ID_MAX (12 + 1) +#define CHAR(sql) (const char *)(sql) static sqlite3 *db; -static inline json_t * -convert(sqlite3_stmt *stmt) +static char * +dup(const unsigned char *s) +{ + return estrdup(s ? (const char *)(s) : ""); +} + +static void +convert(sqlite3_stmt *stmt, struct paste *paste) { - return json_pack("{ss ss ss ss ss sI si si}", - "id", sqlite3_column_text(stmt, 0), - "title", sqlite3_column_text(stmt, 1), - "author", sqlite3_column_text(stmt, 2), - "language", sqlite3_column_text(stmt, 3), - "code", sqlite3_column_text(stmt, 4), - "timestamp", (json_int_t)sqlite3_column_int64(stmt, 5), - "visible", sqlite3_column_int(stmt, 6), - "duration", sqlite3_column_int(stmt, 7) - ); + paste->id = dup(sqlite3_column_text(stmt, 0)); + paste->title = dup(sqlite3_column_text(stmt, 1)); + paste->author = dup(sqlite3_column_text(stmt, 2)); + paste->language = dup(sqlite3_column_text(stmt, 3)); + paste->code = dup(sqlite3_column_text(stmt, 4)); + paste->timestamp = sqlite3_column_int64(stmt, 5); + paste->visible = sqlite3_column_int(stmt, 6); + paste->duration = sqlite3_column_int64(stmt, 7); } static int @@ -62,7 +66,7 @@ sqlite3_stmt *stmt = NULL; int ret = 1; - if (sqlite3_prepare(db, sql_get, -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_prepare(db, CHAR(sql_get), -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, id, -1, NULL); ret = sqlite3_step(stmt) == SQLITE_ROW; sqlite3_finalize(stmt); @@ -72,19 +76,24 @@ } static const char * -create_id(char *id) +create_id(void) { static const char table[] = "abcdefghijklmnopqrstuvwxyz1234567890"; + static char id[12]; - for (int i = 0; i < ID_MAX; ++i) + for (size_t i = 0; i < sizeof (id); ++i) id[i] = table[rand() % (sizeof (table) - 1)]; - id[ID_MAX - 1] = 0; + return id; } static int -set_id(json_t *paste) +set_id(struct paste *paste) { + assert(paste); + + paste->id = NULL; + /* * Avoid infinite loop, we only try to create a new id in 30 steps. * @@ -92,18 +101,13 @@ * not try to save with that id. */ int tries = 0; - char id[ID_MAX]; do { - create_id(id); - } while (++tries < 30 && exists(id)); + free(paste->id); + paste->id = estrdup(create_id()); + } while (++tries < 30 && exists(paste->id)); - if (tries >= 30) - return -1; - - json_object_set_new(paste, "id", json_string(id)); - - return 0; + return tries < 30 ? 0 : -1; } int @@ -121,7 +125,7 @@ /* Wait for 30 seconds to lock the database. */ sqlite3_busy_timeout(db, 30000); - if (sqlite3_exec(db, sql_init, NULL, NULL, NULL) != SQLITE_OK) { + if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) { log_warn("database: unable to initialize %s: %s", path, sqlite3_errmsg(db)); return -1; } @@ -129,28 +133,30 @@ return 0; } -json_t * -database_recents(size_t limit) +int +database_recents(struct paste *pastes, size_t *max) { - json_t *array = NULL; + assert(pastes); + assert(max); + sqlite3_stmt *stmt = NULL; size_t i = 0; + memset(pastes, 0, *max * sizeof (struct paste)); log_debug("database: accessing most recents"); - if (sqlite3_prepare(db, sql_recents, -1, &stmt, NULL) != SQLITE_OK || - sqlite3_bind_int64(stmt, 1, limit) != SQLITE_OK) + if (sqlite3_prepare(db, CHAR(sql_recents), -1, &stmt, NULL) != SQLITE_OK || + sqlite3_bind_int64(stmt, 1, *max) != SQLITE_OK) goto sqlite_err; - array = json_array(); - - for (; i < limit && sqlite3_step(stmt) == SQLITE_ROW; ++i) - json_array_append_new(array, convert(stmt)); + for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i) + convert(stmt, &pastes[i]); log_debug("database: found %zu pastes", i); sqlite3_finalize(stmt); + *max = i; - return array; + return 0; sqlite_err: log_warn("database: error (recents): %s\n", sqlite3_errmsg(db)); @@ -158,26 +164,31 @@ if (stmt) sqlite3_finalize(stmt); - return NULL; + *max = 0; + + return -1; } -json_t * -database_get(const char *id) +int +database_get(struct paste *paste, const char *id) { + assert(paste); assert(id); - json_t *object = NULL; sqlite3_stmt* stmt = NULL; + int found = -1; - log_debug("database: accessing paste with uuid: %s", id); + memset(paste, 0, sizeof (struct paste)); + log_debug("database: accessing paste with id: %s", id); - if (sqlite3_prepare(db, sql_get, -1, &stmt, NULL) != SQLITE_OK || + if (sqlite3_prepare(db, CHAR(sql_get), -1, &stmt, NULL) != SQLITE_OK || sqlite3_bind_text(stmt, 1, id, -1, NULL) != SQLITE_OK) goto sqlite_err; switch (sqlite3_step(stmt)) { case SQLITE_ROW: - object = convert(stmt); + convert(stmt, paste); + found = 0; break; case SQLITE_MISUSE: case SQLITE_ERROR: @@ -188,7 +199,7 @@ sqlite3_finalize(stmt); - return object; + return found; sqlite_err: if (stmt) @@ -196,11 +207,11 @@ log_warn("database: error (get): %s", sqlite3_errmsg(db)); - return NULL; + return -1; } int -database_insert(json_t *paste) +database_insert(struct paste *paste) { assert(paste); @@ -212,23 +223,21 @@ log_warn("database: could not lock database: %s", sqlite3_errmsg(db)); return -1; } - if (set_id(paste) < 0) { log_warn("database: unable to randomize unique identifier"); sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); return -1; } - - if (sqlite3_prepare(db, sql_insert, -1, &stmt, NULL) != SQLITE_OK) + if (sqlite3_prepare(db, CHAR(sql_insert), -1, &stmt, NULL) != SQLITE_OK) goto sqlite_err; - sqlite3_bind_text(stmt, 1, ju_get_string(paste, "id"), -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, ju_get_string(paste, "title"), -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, ju_get_string(paste, "author"), -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 4, ju_get_string(paste, "language"), -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 5, ju_get_string(paste, "code"), -1, SQLITE_STATIC); - sqlite3_bind_int(stmt, 6, ju_get_bool(paste, "visible")); - sqlite3_bind_int64(stmt, 7, ju_get_int(paste, "duration")); + sqlite3_bind_text(stmt, 1, paste->id, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, paste->title, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, paste->author, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, paste->language, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 5, paste->code, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 6, paste->visible); + sqlite3_bind_int64(stmt, 7, paste->duration); if (sqlite3_step(stmt) != SQLITE_DONE) goto sqlite_err; @@ -237,10 +246,7 @@ sqlite3_finalize(stmt); log_info("database: new paste (%s) from %s expires in one %lld seconds", - ju_get_string(paste, "id"), - ju_get_string(paste, "author"), - ju_get_int(paste, "duration") - ); + paste->id, paste->author, paste->duration); return 0; @@ -251,19 +257,22 @@ if (stmt) sqlite3_finalize(stmt); - /* Make sure it is not used anymore. */ - json_object_del(paste, "id"); + free(paste->id); + paste->id = NULL; - return 0; + return -1; } -json_t * -database_search(size_t limit, +int +database_search(struct paste *pastes, + size_t *max, const char *title, const char *author, const char *language) { - json_t *array = NULL; + assert(pastes); + assert(max); + sqlite3_stmt *stmt = NULL; size_t i = 0; @@ -272,12 +281,14 @@ author ? author : "", language ? language : ""); + memset(pastes, 0, *max * sizeof (struct paste)); + /* Select everything if not specified. */ title = title ? title : "%"; author = author ? author : "%"; language = language ? language : "%"; - if (sqlite3_prepare(db, sql_search, -1, &stmt, NULL) != SQLITE_OK) + if (sqlite3_prepare(db, CHAR(sql_search), -1, &stmt, NULL) != SQLITE_OK) goto sqlite_err; if (sqlite3_bind_text(stmt, 1, title, -1, NULL) != SQLITE_OK) goto sqlite_err; @@ -285,18 +296,17 @@ goto sqlite_err; if (sqlite3_bind_text(stmt, 3, language, -1, NULL) != SQLITE_OK) goto sqlite_err; - if (sqlite3_bind_int64(stmt, 4, limit) != SQLITE_OK) + if (sqlite3_bind_int64(stmt, 4, *max) != SQLITE_OK) goto sqlite_err; - array = json_array(); - - for (; i < limit && sqlite3_step(stmt) == SQLITE_ROW; ++i) - json_array_append_new(array, convert(stmt)); + for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i) + convert(stmt, &pastes[i]); log_debug("database: found %zu pastes", i); sqlite3_finalize(stmt); + *max = i; - return array; + return 0; sqlite_err: log_warn("database: error (search): %s\n", sqlite3_errmsg(db)); @@ -304,7 +314,9 @@ if (stmt) sqlite3_finalize(stmt); - return NULL; + *max = 0; + + return -1; } void @@ -312,7 +324,7 @@ { log_debug("database: clearing deprecated pastes"); - if (sqlite3_exec(db, sql_clear, NULL, NULL, NULL) != SQLITE_OK) + if (sqlite3_exec(db, CHAR(sql_clear), NULL, NULL, NULL) != SQLITE_OK) log_warn("database: error (clear): %s\n", sqlite3_errmsg(db)); }
--- a/database.h Thu Mar 16 20:45:59 2023 +0100 +++ b/database.h Fri Mar 17 07:43:20 2023 +0100 @@ -21,77 +21,30 @@ #include <stddef.h> -#include <jansson.h> +struct paste; -/** - * Open the database specified by path. - * - * \pre path != NULL - * \param path path to the SQLite file - * \return 0 on success or -1 on error - */ int -database_open(const char *path); +database_open(const char *); -/** - * Obtain a list of recent pastes. - * - * \param limit max number of items to fetch - * \return a JSON array of paste objects or NULL on failure - */ -json_t * -database_recents(size_t limit); +int +database_recents(struct paste *, size_t *); -/** - * Obtain a specific paste by id. - * - * \pre id != NULL - * \param id the paste identifier - * \return NULL on failure or if not found - */ -json_t * -database_get(const char *id); +int +database_get(struct paste *, const char *); -/** - * Insert a new paste into the database. - * - * On insertion, the paste gets a new string "id" property generated from the - * database. - * - * \pre paste != NULL - * \param paste the paste object - * \return 0 on success or -1 on failure - */ int -database_insert(json_t *paste); +database_insert(struct paste *); -/** - * Search for pastes based on criterias. - * - * If any of the criterias is NULL it is considered as ignored (and then match a - * database item). - * - * \param limit max number of items to fetch - * \param title paste title (or NULL to match any) - * \param author paste author (or NULL to match any) - * \param language paste language (or NULL to match any) - * \return a JSON array of objects or NULL on failure - */ -json_t * -database_search(size_t limit, - const char *title, - const char *author, - const char *language); +int +database_search(struct paste *, + size_t *, + const char *, + const char *, + const char *); -/** - * Cleanup expired pastes. - */ void database_clear(void); -/** - * Close the database handle - */ void database_finish(void);
--- a/extern/LICENSE.libmustach.txt Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ - -Copyright (c) 2017-2020 by José Bollo - -Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC 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.
--- a/extern/VERSION.libmustach.txt Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -1.2.5
--- a/extern/libmustach/mustach-jansson.c Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,251 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#define _GNU_SOURCE - -#include <stdio.h> -#include <string.h> - -#include "mustach.h" -#include "mustach-wrap.h" -#include "mustach-jansson.h" - -struct expl { - json_t *root; - json_t *selection; - int depth; - struct { - json_t *cont; - json_t *obj; - void *iter; - int is_objiter; - size_t index, count; - } stack[MUSTACH_MAX_DEPTH]; -}; - -static int start(void *closure) -{ - struct expl *e = closure; - e->depth = 0; - e->selection = json_null(); - e->stack[0].cont = NULL; - e->stack[0].obj = e->root; - e->stack[0].index = 0; - e->stack[0].count = 1; - return MUSTACH_OK; -} - -static int compare(void *closure, const char *value) -{ - struct expl *e = closure; - json_t *o = e->selection; - double d; - json_int_t i; - - switch (json_typeof(o)) { - case JSON_REAL: - d = json_number_value(o) - atof(value); - return d < 0 ? -1 : d > 0 ? 1 : 0; - case JSON_INTEGER: - i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value); - return i < 0 ? -1 : i > 0 ? 1 : 0; - case JSON_STRING: - return strcmp(json_string_value(o), value); - case JSON_TRUE: - return strcmp("true", value); - case JSON_FALSE: - return strcmp("false", value); - case JSON_NULL: - return strcmp("null", value); - default: - return 1; - } -} - -static int sel(void *closure, const char *name) -{ - struct expl *e = closure; - json_t *o; - int i, r; - - if (name == NULL) { - o = e->stack[e->depth].obj; - r = 1; - } else { - i = e->depth; - while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name))) - i--; - if (i >= 0) - r = 1; - else { - o = json_null(); - r = 0; - } - } - e->selection = o; - return r; -} - -static int subsel(void *closure, const char *name) -{ - struct expl *e = closure; - json_t *o; - int r; - - o = json_object_get(e->selection, name); - r = o != NULL; - if (r) - e->selection = o; - return r; -} - -static int enter(void *closure, int objiter) -{ - struct expl *e = closure; - json_t *o; - - if (++e->depth >= MUSTACH_MAX_DEPTH) - return MUSTACH_ERROR_TOO_DEEP; - - o = e->selection; - e->stack[e->depth].is_objiter = 0; - if (objiter) { - if (!json_is_object(o)) - goto not_entering; - e->stack[e->depth].iter = json_object_iter(o); - if (e->stack[e->depth].iter == NULL) - goto not_entering; - e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); - e->stack[e->depth].cont = o; - e->stack[e->depth].is_objiter = 1; - } else if (json_is_array(o)) { - e->stack[e->depth].count = json_array_size(o); - if (e->stack[e->depth].count == 0) - goto not_entering; - e->stack[e->depth].cont = o; - e->stack[e->depth].obj = json_array_get(o, 0); - e->stack[e->depth].index = 0; - } else if ((json_is_object(o) && json_object_size(0)) || (!json_is_false(o) && !json_is_null(o))) { - e->stack[e->depth].count = 1; - e->stack[e->depth].cont = NULL; - e->stack[e->depth].obj = o; - e->stack[e->depth].index = 0; - } else - goto not_entering; - return 1; - -not_entering: - e->depth--; - return 0; -} - -static int next(void *closure) -{ - struct expl *e = closure; - - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - - if (e->stack[e->depth].is_objiter) { - e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter); - if (e->stack[e->depth].iter == NULL) - return 0; - e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); - return 1; - } - - e->stack[e->depth].index++; - if (e->stack[e->depth].index >= e->stack[e->depth].count) - return 0; - - e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index); - return 1; -} - -static int leave(void *closure) -{ - struct expl *e = closure; - - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - - e->depth--; - return 0; -} - -static int get(void *closure, struct mustach_sbuf *sbuf, int key) -{ - struct expl *e = closure; - const char *s; - - if (key) { - s = e->stack[e->depth].is_objiter - ? json_object_iter_key(e->stack[e->depth].iter) - : ""; - } - else if (json_is_string(e->selection)) - s = json_string_value(e->selection); - else if (json_is_null(e->selection)) - s = ""; - else { - s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT); - if (s == NULL) - return MUSTACH_ERROR_SYSTEM; - sbuf->freecb = free; - } - sbuf->value = s; - return 1; -} - -const struct mustach_wrap_itf mustach_jansson_wrap_itf = { - .start = start, - .stop = NULL, - .compare = compare, - .sel = sel, - .subsel = subsel, - .enter = enter, - .next = next, - .leave = leave, - .get = get -}; - -int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) -{ - struct expl e; - e.root = root; - return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); -} - -int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) -{ - struct expl e; - e.root = root; - return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); -} - -int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) -{ - struct expl e; - e.root = root; - return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); -} - -int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure) -{ - struct expl e; - e.root = root; - return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); -} - -int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure) -{ - struct expl e; - e.root = root; - return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); -} -
--- a/extern/libmustach/mustach-jansson.h Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#ifndef _mustach_jansson_h_included_ -#define _mustach_jansson_h_included_ - -/* - * mustach-jansson is intended to make integration of jansson - * library by providing integrated functions. - */ - -#include <jansson.h> -#include "mustach-wrap.h" - -/** - * Wrap interface used internally by mustach jansson functions. - * Can be used for overriding behaviour. - */ -extern const struct mustach_wrap_itf mustach_jansson_wrap_itf; - -/** - * mustach_jansson_file - Renders the mustache 'template' in 'file' for 'root'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @root: the root json object to render - * @file: the file where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file); - -/** - * mustach_jansson_fd - Renders the mustache 'template' in 'fd' for 'root'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @root: the root json object to render - * @fd: the file descriptor number where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd); - - -/** - * mustach_jansson_mem - Renders the mustache 'template' in 'result' for 'root'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @root: the root json object to render - * @result: the pointer receiving the result when 0 is returned - * @size: the size of the returned result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size); - -/** - * mustach_jansson_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @root: the root json object to render - * @writecb: the function that write values - * @closure: the closure for the write function - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure); - -/** - * mustach_jansson_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @root: the root json object to render - * @emitcb: the function that emit values - * @closure: the closure for the write function - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure); - -#endif -
--- a/extern/libmustach/mustach-wrap.c Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,456 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#define _GNU_SOURCE - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#ifdef _WIN32 -#include <malloc.h> -#endif - -#include "mustach.h" -#include "mustach-wrap.h" - -#if !defined(INCLUDE_PARTIAL_EXTENSION) -# define INCLUDE_PARTIAL_EXTENSION ".mustache" -#endif - -/* global hook for partials */ -int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL; - -/* internal structure for wrapping */ -struct wrap { - /* original interface */ - const struct mustach_wrap_itf *itf; - - /* original closure */ - void *closure; - - /* flags */ - int flags; - - /* emiter callback */ - mustach_emit_cb_t *emitcb; - - /* write callback */ - mustach_write_cb_t *writecb; -}; - -/* length given by masking with 3 */ -enum comp { - C_no = 0, - C_eq = 1, - C_lt = 5, - C_le = 6, - C_gt = 9, - C_ge = 10 -}; - -enum sel { - S_none = 0, - S_ok = 1, - S_objiter = 2, - S_ok_or_objiter = S_ok | S_objiter -}; - -static enum comp getcomp(char *head, int sflags) -{ - return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq - : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt) - : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt) - : C_no; -} - -static char *keyval(char *head, int sflags, enum comp *comp) -{ - char *w, car, escaped; - enum comp k; - - k = C_no; - w = head; - car = *head; - escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no); - while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) { - if (escaped) - escaped = 0; - else - escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\') - && (getcomp(head + 1, sflags) != C_no); - if (!escaped) - *w++ = car; - head++; - car = *head; - } - *w = 0; - *comp = k; - return k == C_no ? NULL : &head[k & 3]; -} - -static char *getkey(char **head, int sflags) -{ - char *result, *iter, *write, car; - - car = *(iter = *head); - if (!car) - result = NULL; - else { - result = write = iter; - if (sflags & Mustach_With_JsonPointer) - { - while (car && car != '/') { - if (car == '~') - switch (iter[1]) { - case '1': car = '/'; /*@fallthrough@*/ - case '0': iter++; - } - *write++ = car; - car = *++iter; - } - *write = 0; - while (car == '/') - car = *++iter; - } - else - { - while (car && car != '.') { - if (car == '\\' && (iter[1] == '.' || iter[1] == '\\')) - car = *++iter; - *write++ = car; - car = *++iter; - } - *write = 0; - while (car == '.') - car = *++iter; - } - *head = iter; - } - return result; -} - -static enum sel sel(struct wrap *w, const char *name) -{ - enum sel result; - int i, j, sflags, scmp; - char *key, *value; - enum comp k; - - /* make a local writeable copy */ - size_t lenname = 1 + strlen(name); - char buffer[lenname]; - char *copy = buffer; - memcpy(copy, name, lenname); - - /* check if matches json pointer selection */ - sflags = w->flags; - if (sflags & Mustach_With_JsonPointer) { - if (copy[0] == '/') - copy++; - else - sflags ^= Mustach_With_JsonPointer; - } - - /* extract the value, translate the key and get the comparator */ - if (sflags & (Mustach_With_Equal | Mustach_With_Compare)) - value = keyval(copy, sflags, &k); - else { - k = C_no; - value = NULL; - } - - /* case of . alone if Mustach_With_SingleDot? */ - if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/) - /* yes, select current */ - result = w->itf->sel(w->closure, NULL) ? S_ok : S_none; - else - { - /* not the single dot, extract the first key */ - key = getkey(©, sflags); - if (key == NULL) - return 0; - - /* select the root item */ - if (w->itf->sel(w->closure, key)) - result = S_ok; - else if (key[0] == '*' - && !key[1] - && !value - && !*copy - && (w->flags & Mustach_With_ObjectIter) - && w->itf->sel(w->closure, NULL)) - result = S_ok_or_objiter; - else - result = S_none; - if (result == S_ok) { - /* iterate the selection of sub items */ - key = getkey(©, sflags); - while(result == S_ok && key) { - if (w->itf->subsel(w->closure, key)) - /* nothing */; - else if (key[0] == '*' - && !key[1] - && !value - && !*copy - && (w->flags & Mustach_With_ObjectIter)) - result = S_objiter; - else - result = S_none; - key = getkey(©, sflags); - } - } - } - /* should it be compared? */ - if (result == S_ok && value) { - if (!w->itf->compare) - result = S_none; - else { - i = value[0] == '!'; - scmp = w->itf->compare(w->closure, &value[i]); - switch (k) { - case C_eq: j = scmp == 0; break; - case C_lt: j = scmp < 0; break; - case C_le: j = scmp <= 0; break; - case C_gt: j = scmp > 0; break; - case C_ge: j = scmp >= 0; break; - default: j = i; break; - } - if (i == j) - result = S_none; - } - } - return result; -} - -static int start(void *closure) -{ - struct wrap *w = closure; - return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; -} - -static void stop(void *closure, int status) -{ - struct wrap *w = closure; - if (w->itf->stop) - w->itf->stop(w->closure, status); -} - -static int write(struct wrap *w, const char *buffer, size_t size, FILE *file) -{ - int r; - - if (w->writecb) - r = w->writecb(file, buffer, size); - else - r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM; - return r; -} - -static int emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) -{ - struct wrap *w = closure; - int r; - size_t s, i; - char car; - - if (w->emitcb) - r = w->emitcb(file, buffer, size, escape); - else if (!escape) - r = write(w, buffer, size, file); - else { - i = 0; - r = MUSTACH_OK; - while(i < size && r == MUSTACH_OK) { - s = i; - while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"') - i++; - if (i != s) - r = write(w, &buffer[s], i - s, file); - if (i < size && r == MUSTACH_OK) { - switch(car) { - case '<': r = write(w, "<", 4, file); break; - case '>': r = write(w, ">", 4, file); break; - case '&': r = write(w, "&", 5, file); break; - case '"': r = write(w, """, 6, file); break; - } - i++; - } - } - } - return r; -} - -static int enter(void *closure, const char *name) -{ - struct wrap *w = closure; - enum sel s = sel(w, name); - return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter); -} - -static int next(void *closure) -{ - struct wrap *w = closure; - return w->itf->next(w->closure); -} - -static int leave(void *closure) -{ - struct wrap *w = closure; - return w->itf->leave(w->closure); -} - -static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf) -{ - enum sel s = sel(w, name); - if (!(s & S_ok)) - return 0; - return w->itf->get(w->closure, sbuf, s & S_objiter); -} - -static int get(void *closure, const char *name, struct mustach_sbuf *sbuf) -{ - struct wrap *w = closure; - if (getoptional(w, name, sbuf) <= 0) { - if (w->flags & Mustach_With_ErrorUndefined) - return MUSTACH_ERROR_UNDEFINED_TAG; - sbuf->value = ""; - } - return MUSTACH_OK; -} - -static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf) -{ - static char extension[] = INCLUDE_PARTIAL_EXTENSION; - size_t s; - long pos; - FILE *file; - char *path, *buffer; - - /* allocate path */ - s = strlen(name); - path = malloc(s + sizeof extension); - if (path == NULL) - return MUSTACH_ERROR_SYSTEM; - - /* try without extension first */ - memcpy(path, name, s + 1); - file = fopen(path, "r"); - if (file == NULL) { - memcpy(&path[s], extension, sizeof extension); - file = fopen(path, "r"); - } - free(path); - - /* if file opened */ - if (file == NULL) - return MUSTACH_ERROR_PARTIAL_NOT_FOUND; - - /* compute file size */ - if (fseek(file, 0, SEEK_END) >= 0 - && (pos = ftell(file)) >= 0 - && fseek(file, 0, SEEK_SET) >= 0) { - /* allocate value */ - s = (size_t)pos; - buffer = malloc(s + 1); - if (buffer != NULL) { - /* read value */ - if (1 == fread(buffer, s, 1, file)) { - /* force zero at end */ - sbuf->value = buffer; - buffer[s] = 0; - sbuf->freecb = free; - fclose(file); - return MUSTACH_OK; - } - free(buffer); - } - } - fclose(file); - return MUSTACH_ERROR_SYSTEM; -} - -static int partial(void *closure, const char *name, struct mustach_sbuf *sbuf) -{ - struct wrap *w = closure; - int rc; - if (mustach_wrap_get_partial != NULL) - rc = mustach_wrap_get_partial(name, sbuf); - else if (w->flags & Mustach_With_PartialDataFirst) { - if (getoptional(w, name, sbuf) > 0) - rc = MUSTACH_OK; - else - rc = get_partial_from_file(name, sbuf); - } - else { - rc = get_partial_from_file(name, sbuf); - if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0) - rc = MUSTACH_OK; - } - if (rc != MUSTACH_OK) - sbuf->value = ""; - return MUSTACH_OK; -} - -const struct mustach_itf mustach_wrap_itf = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = partial, - .get = get, - .emit = emit, - .stop = stop -}; - -static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb) -{ - if (flags & Mustach_With_Compare) - flags |= Mustach_With_Equal; - wrap->closure = closure; - wrap->itf = itf; - wrap->flags = flags; - wrap->emitcb = emitcb; - wrap->writecb = writecb; -} - -int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file) -{ - struct wrap w; - wrap_init(&w, itf, closure, flags, NULL, NULL); - return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file); -} - -int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd) -{ - struct wrap w; - wrap_init(&w, itf, closure, flags, NULL, NULL); - return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd); -} - -int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size) -{ - struct wrap w; - wrap_init(&w, itf, closure, flags, NULL, NULL); - return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size); -} - -int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure) -{ - struct wrap w; - wrap_init(&w, itf, closure, flags, NULL, writecb); - return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure); -} - -int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure) -{ - struct wrap w; - wrap_init(&w, itf, closure, flags, emitcb, NULL); - return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure); -} -
--- a/extern/libmustach/mustach-wrap.h Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,234 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#ifndef _mustach_wrap_h_included_ -#define _mustach_wrap_h_included_ - -/* - * mustach-wrap is intended to make integration of JSON - * libraries easier by wrapping mustach extensions in a - * single place. - * - * As before, using mustach and only mustach is possible - * (by using only mustach.h) but does not implement high - * level features coming with extensions implemented by - * this high level wrapper. - */ -#include "mustach.h" -/* - * Definition of the writing callbacks for mustach functions - * producing output to callbacks. - * - * Two callback types are defined: - * - * @mustach_write_cb_t: - * - * callback receiving the escaped data to be written as 3 parameters: - * - * 1. the 'closure', the same given to the wmustach_... function - * 2. a pointer to a 'buffer' containing the characters to be written - * 3. the size in bytes of the data pointed by 'buffer' - * - * @mustach_emit_cb_t: - * - * callback receiving the data to be written and a flag indicating - * if escaping should be done or not as 4 parameters: - * - * 1. the 'closure', the same given to the emustach_... function - * 2. a pointer to a 'buffer' containing the characters to be written - * 3. the size in bytes of the data pointed by 'buffer' - * 4. a boolean indicating if 'escape' should be done - */ -#ifndef _mustach_output_callbacks_defined_ -#define _mustach_output_callbacks_defined_ -typedef int mustach_write_cb_t(void *closure, const char *buffer, size_t size); -typedef int mustach_emit_cb_t(void *closure, const char *buffer, size_t size, int escape); -#endif - -/** - * Flags specific to mustach wrap - */ -#define Mustach_With_SingleDot 4 /* obsolete, always set */ -#define Mustach_With_Equal 8 -#define Mustach_With_Compare 16 -#define Mustach_With_JsonPointer 32 -#define Mustach_With_ObjectIter 64 -#define Mustach_With_IncPartial 128 /* obsolete, always set */ -#define Mustach_With_EscFirstCmp 256 -#define Mustach_With_PartialDataFirst 512 -#define Mustach_With_ErrorUndefined 1024 - -#undef Mustach_With_AllExtensions -#define Mustach_With_AllExtensions 1023 /* don't include ErrorUndefined */ - -/** - * mustach_wrap_itf - high level wrap of mustach - interface for callbacks - * - * The functions sel, subsel, enter and next should return 0 or 1. - * - * All other functions should normally return MUSTACH_OK (zero). - * - * If any function returns a negative value, it means an error that - * stop the processing and that is reported to the caller. Mustach - * also has its own error codes. Using the macros MUSTACH_ERROR_USER - * and MUSTACH_IS_ERROR_USER could help to avoid clashes. - * - * @start: If defined (can be NULL), starts the mustach processing - * of the closure, called at the very beginning before any - * mustach processing occurs. - * - * @stop: If defined (can be NULL), stops the mustach processing - * of the closure, called at the very end after all mustach - * processing occurered. The status returned by the processing - * is passed to the stop. - * - * @compare: If defined (can be NULL), compares the value of the - * currently selected item with the given value and returns - * a negative value if current value is lesser, a positive - * value if the current value is greater or zero when - * values are equals. - * If 'compare' is NULL, any comparison in mustach - * is going to fails. - * - * @sel: Selects the item of the given 'name'. If 'name' is NULL - * Selects the current item. Returns 1 if the selection is - * effective or else 0 if the selection failed. - * - * @subsel: Selects from the currently selected object the value of - * the field of given name. Returns 1 if the selection is - * effective or else 0 if the selection failed. - * - * @enter: Enters the section of 'name' if possible. - * Musts return 1 if entered or 0 if not entered. - * When 1 is returned, the function 'leave' will always be called. - * Conversely 'leave' is never called when enter returns 0 or - * a negative value. - * When 1 is returned, the function must activate the first - * item of the section. - * - * @next: Activates the next item of the section if it exists. - * Musts return 1 when the next item is activated. - * Musts return 0 when there is no item to activate. - * - * @leave: Leaves the last entered section - * - * @get: Returns in 'sbuf' the value of the current selection if 'key' - * is zero. Otherwise, when 'key' is not zero, return in 'sbuf' - * the name of key of the current selection, or if no such key - * exists, the empty string. Must return 1 if possible or - * 0 when not possible or an error code. - */ -struct mustach_wrap_itf { - int (*start)(void *closure); - void (*stop)(void *closure, int status); - int (*compare)(void *closure, const char *value); - int (*sel)(void *closure, const char *name); - int (*subsel)(void *closure, const char *name); - int (*enter)(void *closure, int objiter); - int (*next)(void *closure); - int (*leave)(void *closure); - int (*get)(void *closure, struct mustach_sbuf *sbuf, int key); -}; - -/** - * Mustach interface used internally by mustach wrapper functions. - * Can be used for overriding behaviour. - */ -extern const struct mustach_itf mustach_wrap_itf; - -/** - * Global hook for providing partials. When set to a not NULL value, the pointed - * function replaces the default behaviour and is called to provide the partial - * of the given 'name' in 'sbuf'. - * The function must return MUSTACH_OK when it filled 'sbuf' with value of partial - * or must return an error code if it failed. - */ -extern int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf); - -/** - * mustach_wrap_file - Renders the mustache 'template' in 'file' for an abstract - * wrapper of interface 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface of the abstract wrapper - * @closure: the closure of the abstract wrapper - * @file: the file where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file); - -/** - * mustach_wrap_fd - Renders the mustache 'template' in 'fd' for an abstract - * wrapper of interface 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface of the abstract wrapper - * @closure: the closure of the abstract wrapper - * @fd: the file descriptor number where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd); - -/** - * mustach_wrap_mem - Renders the mustache 'template' in 'result' for an abstract - * wrapper of interface 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface of the abstract wrapper - * @closure: the closure of the abstract wrapper - * @result: the pointer receiving the result when 0 is returned - * @size: the size of the returned result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size); - -/** - * mustach_wrap_write - Renders the mustache 'template' for an abstract - * wrapper of interface 'itf' and 'closure' to custom writer - * 'writecb' with 'writeclosure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface of the abstract wrapper - * @closure: the closure of the abstract wrapper - * @writecb: the function that write values - * @closure: the closure for the write function - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure); - -/** - * mustach_wrap_emit - Renders the mustache 'template' for an abstract - * wrapper of interface 'itf' and 'closure' to custom emiter 'emitcb' - * with 'emitclosure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface of the abstract wrapper - * @closure: the closure of the abstract wrapper - * @emitcb: the function that emit values - * @closure: the closure for the write function - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure); - -#endif -
--- a/extern/libmustach/mustach.c Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,546 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#define _GNU_SOURCE - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <errno.h> -#include <ctype.h> -#ifdef _WIN32 -#include <malloc.h> -#endif - -#include "mustach.h" - -struct iwrap { - int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); - void *closure; /* closure for: enter, next, leave, emit, get */ - int (*put)(void *closure, const char *name, int escape, FILE *file); - void *closure_put; /* closure for put */ - int (*enter)(void *closure, const char *name); - int (*next)(void *closure); - int (*leave)(void *closure); - int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); - int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); - void *closure_partial; /* closure for partial */ - int flags; -}; - -struct prefix { - size_t len; - const char *start; - struct prefix *prefix; -}; - -#if !defined(NO_OPEN_MEMSTREAM) -static FILE *memfile_open(char **buffer, size_t *size) -{ - return open_memstream(buffer, size); -} -static void memfile_abort(FILE *file, char **buffer, size_t *size) -{ - fclose(file); - free(*buffer); - *buffer = NULL; - *size = 0; -} -static int memfile_close(FILE *file, char **buffer, size_t *size) -{ - int rc; - - /* adds terminating null */ - rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; - fclose(file); - if (rc == 0) - /* removes terminating null of the length */ - (*size)--; - else { - free(*buffer); - *buffer = NULL; - *size = 0; - } - return rc; -} -#else -static FILE *memfile_open(char **buffer, size_t *size) -{ - /* - * We can't provide *buffer and *size as open_memstream does but - * at least clear them so the caller won't get bad data. - */ - *buffer = NULL; - *size = 0; - - return tmpfile(); -} -static void memfile_abort(FILE *file, char **buffer, size_t *size) -{ - fclose(file); - *buffer = NULL; - *size = 0; -} -static int memfile_close(FILE *file, char **buffer, size_t *size) -{ - int rc; - size_t s; - char *b; - - s = (size_t)ftell(file); - b = malloc(s + 1); - if (b == NULL) { - rc = MUSTACH_ERROR_SYSTEM; - errno = ENOMEM; - s = 0; - } else { - rewind(file); - if (1 == fread(b, s, 1, file)) { - rc = 0; - b[s] = 0; - } else { - rc = MUSTACH_ERROR_SYSTEM; - free(b); - b = NULL; - s = 0; - } - } - *buffer = b; - *size = s; - return rc; -} -#endif - -static inline void sbuf_reset(struct mustach_sbuf *sbuf) -{ - sbuf->value = NULL; - sbuf->freecb = NULL; - sbuf->closure = NULL; - sbuf->length = 0; -} - -static inline void sbuf_release(struct mustach_sbuf *sbuf) -{ - if (sbuf->releasecb) - sbuf->releasecb(sbuf->value, sbuf->closure); -} - -static inline size_t sbuf_length(struct mustach_sbuf *sbuf) -{ - size_t length = sbuf->length; - if (length == 0 && sbuf->value != NULL) - length = strlen(sbuf->value); - return length; -} - -static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) -{ - size_t i, j, r; - - (void)closure; /* unused */ - - if (!escape) - return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; - - r = i = 0; - while (i < size) { - j = i; - while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&' && buffer[j] != '"') - j++; - if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; - if (j < size) { - switch(buffer[j++]) { - case '<': - r = fwrite("<", 4, 1, file); - break; - case '>': - r = fwrite(">", 4, 1, file); - break; - case '&': - r = fwrite("&", 5, 1, file); - break; - case '"': - r = fwrite(""", 6, 1, file); - break; - } - if (r != 1) - return MUSTACH_ERROR_SYSTEM; - } - i = j; - } - return MUSTACH_OK; -} - -static int iwrap_put(void *closure, const char *name, int escape, FILE *file) -{ - struct iwrap *iwrap = closure; - int rc; - struct mustach_sbuf sbuf; - size_t length; - - sbuf_reset(&sbuf); - rc = iwrap->get(iwrap->closure, name, &sbuf); - if (rc >= 0) { - length = sbuf_length(&sbuf); - if (length) - rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); - sbuf_release(&sbuf); - } - return rc; -} - -static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf) -{ - struct iwrap *iwrap = closure; - int rc; - FILE *file; - size_t size; - char *result; - - result = NULL; - file = memfile_open(&result, &size); - if (file == NULL) - rc = MUSTACH_ERROR_SYSTEM; - else { - rc = iwrap->put(iwrap->closure_put, name, 0, file); - if (rc < 0) - memfile_abort(file, &result, &size); - else { - rc = memfile_close(file, &result, &size); - if (rc == 0) { - sbuf->value = result; - sbuf->freecb = free; - sbuf->length = size; - } - } - } - return rc; -} - -static int emitprefix(struct iwrap *iwrap, FILE *file, struct prefix *prefix) -{ - if (prefix->prefix) { - int rc = emitprefix(iwrap, file, prefix->prefix); - if (rc < 0) - return rc; - } - return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, file) : 0; -} - -static int process(const char *template, size_t length, struct iwrap *iwrap, FILE *file, struct prefix *prefix) -{ - struct mustach_sbuf sbuf; - char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH]; - char name[MUSTACH_MAX_LENGTH + 1], c; - const char *beg, *term, *end; - struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } stack[MUSTACH_MAX_DEPTH]; - size_t oplen, cllen, len, l; - int depth, rc, enabled, stdalone; - struct prefix pref; - - pref.prefix = prefix; - end = template + (length ? length : strlen(template)); - opstr[0] = opstr[1] = '{'; - clstr[0] = clstr[1] = '}'; - oplen = cllen = 2; - stdalone = enabled = 1; - depth = pref.len = 0; - for (;;) { - /* search next openning delimiter */ - for (beg = template ; ; beg++) { - c = beg == end ? '\n' : *beg; - if (c == '\n') { - l = (beg != end) + (size_t)(beg - template); - if (stdalone != 2 && enabled) { - if (beg != template /* don't prefix empty lines */) { - rc = emitprefix(iwrap, file, &pref); - if (rc < 0) - return rc; - } - rc = iwrap->emit(iwrap->closure, template, l, 0, file); - if (rc < 0) - return rc; - } - if (beg == end) /* no more mustach */ - return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; - template += l; - stdalone = 1; - pref.len = 0; - } - else if (!isspace(c)) { - if (stdalone == 2 && enabled) { - rc = emitprefix(iwrap, file, &pref); - if (rc < 0) - return rc; - pref.len = 0; - stdalone = 0; - } - if (c == *opstr && end - beg >= (ssize_t)oplen) { - for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++); - if (l == oplen) - break; - } - stdalone = 0; - } - } - - pref.start = template; - pref.len = enabled ? (size_t)(beg - template) : 0; - beg += oplen; - - /* search next closing delimiter */ - for (term = beg ; ; term++) { - if (term == end) - return MUSTACH_ERROR_UNEXPECTED_END; - if (*term == *clstr && end - term >= (ssize_t)cllen) { - for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++); - if (l == cllen) - break; - } - } - template = term + cllen; - len = (size_t)(term - beg); - c = *beg; - switch(c) { - case ':': - stdalone = 0; - if (iwrap->flags & Mustach_With_Colon) - goto exclude_first; - goto get_name; - case '!': - case '=': - break; - case '{': - for (l = 0 ; l < cllen && clstr[l] == '}' ; l++); - if (l < cllen) { - if (!len || beg[len-1] != '}') - return MUSTACH_ERROR_BAD_UNESCAPE_TAG; - len--; - } else { - if (term[l] != '}') - return MUSTACH_ERROR_BAD_UNESCAPE_TAG; - template++; - } - c = '&'; - /*@fallthrough@*/ - case '&': - stdalone = 0; - /*@fallthrough@*/ - case '^': - case '#': - case '/': - case '>': -exclude_first: - beg++; - len--; - goto get_name; - default: - stdalone = 0; -get_name: - while (len && isspace(beg[0])) { beg++; len--; } - while (len && isspace(beg[len-1])) len--; - if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag)) - return MUSTACH_ERROR_EMPTY_TAG; - if (len > MUSTACH_MAX_LENGTH) - return MUSTACH_ERROR_TAG_TOO_LONG; - memcpy(name, beg, len); - name[len] = 0; - break; - } - if (stdalone) - stdalone = 2; - else if (enabled) { - rc = emitprefix(iwrap, file, &pref); - if (rc < 0) - return rc; - pref.len = 0; - } - switch(c) { - case '!': - /* comment */ - /* nothing to do */ - break; - case '=': - /* defines delimiters */ - if (len < 5 || beg[len - 1] != '=') - return MUSTACH_ERROR_BAD_SEPARATORS; - beg++; - len -= 2; - while (len && isspace(*beg)) - beg++, len--; - while (len && isspace(beg[len - 1])) - len--; - for (l = 0; l < len && !isspace(beg[l]) ; l++); - if (l == len || l > MUSTACH_MAX_DELIM_LENGTH) - return MUSTACH_ERROR_BAD_SEPARATORS; - oplen = l; - memcpy(opstr, beg, l); - while (l < len && isspace(beg[l])) l++; - if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH) - return MUSTACH_ERROR_BAD_SEPARATORS; - cllen = len - l; - memcpy(clstr, beg + l, cllen); - break; - case '^': - case '#': - /* begin section */ - if (depth == MUSTACH_MAX_DEPTH) - return MUSTACH_ERROR_TOO_DEEP; - rc = enabled; - if (rc) { - rc = iwrap->enter(iwrap->closure, name); - if (rc < 0) - return rc; - } - stack[depth].name = beg; - stack[depth].again = template; - stack[depth].length = len; - stack[depth].enabled = enabled != 0; - stack[depth].entered = rc != 0; - if ((c == '#') == (rc == 0)) - enabled = 0; - depth++; - break; - case '/': - /* end section */ - if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) - return MUSTACH_ERROR_CLOSING; - rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0; - if (rc < 0) - return rc; - if (rc) { - template = stack[depth++].again; - } else { - enabled = stack[depth].enabled; - if (enabled && stack[depth].entered) - iwrap->leave(iwrap->closure); - } - break; - case '>': - /* partials */ - if (enabled) { - sbuf_reset(&sbuf); - rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); - if (rc >= 0) { - rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, file, &pref); - sbuf_release(&sbuf); - } - if (rc < 0) - return rc; - } - break; - default: - /* replacement */ - if (enabled) { - rc = iwrap->put(iwrap->closure_put, name, c != '&', file); - if (rc < 0) - return rc; - } - break; - } - } -} - -int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file) -{ - int rc; - struct iwrap iwrap; - - /* check validity */ - if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get)) - return MUSTACH_ERROR_INVALID_ITF; - - /* init wrap structure */ - iwrap.closure = closure; - if (itf->put) { - iwrap.put = itf->put; - iwrap.closure_put = closure; - } else { - iwrap.put = iwrap_put; - iwrap.closure_put = &iwrap; - } - if (itf->partial) { - iwrap.partial = itf->partial; - iwrap.closure_partial = closure; - } else if (itf->get) { - iwrap.partial = itf->get; - iwrap.closure_partial = closure; - } else { - iwrap.partial = iwrap_partial; - iwrap.closure_partial = &iwrap; - } - iwrap.emit = itf->emit ? itf->emit : iwrap_emit; - iwrap.enter = itf->enter; - iwrap.next = itf->next; - iwrap.leave = itf->leave; - iwrap.get = itf->get; - iwrap.flags = flags; - - /* process */ - rc = itf->start ? itf->start(closure) : 0; - if (rc == 0) - rc = process(template, length, &iwrap, file, 0); - if (itf->stop) - itf->stop(closure, rc); - return rc; -} - -int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd) -{ - int rc; - FILE *file; - - file = fdopen(fd, "w"); - if (file == NULL) { - rc = MUSTACH_ERROR_SYSTEM; - errno = ENOMEM; - } else { - rc = mustach_file(template, length, itf, closure, flags, file); - fclose(file); - } - return rc; -} - -int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size) -{ - int rc; - FILE *file; - size_t s; - - *result = NULL; - if (size == NULL) - size = &s; - file = memfile_open(result, size); - if (file == NULL) - rc = MUSTACH_ERROR_SYSTEM; - else { - rc = mustach_file(template, length, itf, closure, flags, file); - if (rc < 0) - memfile_abort(file, result, size); - else - rc = memfile_close(file, result, size); - } - return rc; -} - -int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file) -{ - return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file); -} - -int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd) -{ - return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd); -} - -int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size) -{ - return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size); -} -
--- a/extern/libmustach/mustach.h Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,313 +0,0 @@ -/* - Author: José Bollo <jobol@nonadev.net> - - https://gitlab.com/jobol/mustach - - SPDX-License-Identifier: ISC -*/ - -#ifndef _mustach_h_included_ -#define _mustach_h_included_ - -struct mustach_sbuf; /* see below */ - -/** - * Current version of mustach and its derivates - */ -#define MUSTACH_VERSION 102 -#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100) -#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100) - -/** - * Maximum nested imbrications supported - */ -#define MUSTACH_MAX_DEPTH 256 - -/** - * Maximum length of tags in mustaches {{...}} - */ -#define MUSTACH_MAX_LENGTH 4096 - -/** - * Maximum length of delimitors (2 normally but extended here) - */ -#define MUSTACH_MAX_DELIM_LENGTH 8 - -/** - * Flags specific to mustach core - */ -#define Mustach_With_NoExtensions 0 -#define Mustach_With_Colon 1 -#define Mustach_With_EmptyTag 2 -#define Mustach_With_AllExtensions 3 - -/* - * Definition of error codes returned by mustach - */ -#define MUSTACH_OK 0 -#define MUSTACH_ERROR_SYSTEM -1 -#define MUSTACH_ERROR_UNEXPECTED_END -2 -#define MUSTACH_ERROR_EMPTY_TAG -3 -#define MUSTACH_ERROR_TAG_TOO_LONG -4 -#define MUSTACH_ERROR_BAD_SEPARATORS -5 -#define MUSTACH_ERROR_TOO_DEEP -6 -#define MUSTACH_ERROR_CLOSING -7 -#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 -#define MUSTACH_ERROR_INVALID_ITF -9 -#define MUSTACH_ERROR_ITEM_NOT_FOUND -10 -#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 -#define MUSTACH_ERROR_UNDEFINED_TAG -12 - -/* - * You can use definition below for user specific error - * - * The macro MUSTACH_ERROR_USER is involutive so for any value - * value = MUSTACH_ERROR_USER(MUSTACH_ERROR_USER(value)) - */ -#define MUSTACH_ERROR_USER_BASE -100 -#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) -#define MUSTACH_IS_ERROR_USER(x) (MUSTACH_ERROR_USER(x) >= 0) - -/** - * mustach_itf - pure abstract mustach - interface for callbacks - * - * The functions enter and next should return 0 or 1. - * - * All other functions should normally return MUSTACH_OK (zero). - * - * If any function returns a negative value, it means an error that - * stop the processing and that is reported to the caller. Mustach - * also has its own error codes. Using the macros MUSTACH_ERROR_USER - * and MUSTACH_IS_ERROR_USER could help to avoid clashes. - * - * @start: If defined (can be NULL), starts the mustach processing - * of the closure, called at the very beginning before any - * mustach processing occurs. - * - * @put: If defined (can be NULL), writes the value of 'name' - * to 'file' with 'escape' or not. - * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be - * the empty string. In that later case an implementation can - * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. - * If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF - * is returned. - * - * @enter: Enters the section of 'name' if possible. - * Musts return 1 if entered or 0 if not entered. - * When 1 is returned, the function 'leave' will always be called. - * Conversely 'leave' is never called when enter returns 0 or - * a negative value. - * When 1 is returned, the function must activate the first - * item of the section. - * - * @next: Activates the next item of the section if it exists. - * Musts return 1 when the next item is activated. - * Musts return 0 when there is no item to activate. - * - * @leave: Leaves the last entered section - * - * @partial: If defined (can be NULL), returns in 'sbuf' the content of the - * partial of 'name'. @see mustach_sbuf - * If NULL but 'get' not NULL, 'get' is used instead of partial. - * If NULL and 'get' NULL and 'put' not NULL, 'put' is called with - * a true FILE. - * - * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'. - * If NULL the standard function 'fwrite' is used with a true FILE. - * If not NULL that function is called instead of 'fwrite' to output - * text. - * It implies that if you define either 'partial' or 'get' callback, - * the meaning of 'FILE *file' is abstract for mustach's process and - * then you can use 'FILE*file' pass any kind of pointer (including NULL) - * to the function 'fmustach'. An example of a such behaviour is given by - * the implementation of 'mustach_json_c_write'. - * - * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'. - * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be - * the empty string. In that later case an implementation can - * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. - * If 'get' is NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF - * is returned. - * - * @stop: If defined (can be NULL), stops the mustach processing - * of the closure, called at the very end after all mustach - * processing occurered. The status returned by the processing - * is passed to the stop. - * - * The array below summarize status of callbacks: - * - * FULLY OPTIONAL: start partial - * MANDATORY: enter next leave - * COMBINATORIAL: put emit get - * - * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF. - * - * For COMBINATORIAL callbacks the array below summarize possible combinations: - * - * combination : put : emit : get : abstract FILE - * -------------+---------+---------+---------+----------------------- - * HISTORIC : defined : NULL : NULL : NO: standard FILE - * MINIMAL : NULL : NULL : defined : NO: standard FILE - * CUSTOM : NULL : defined : defined : YES: abstract FILE - * DUCK : defined : NULL : defined : NO: standard FILE - * DANGEROUS : defined : defined : any : YES or NO, depends on 'partial' - * INVALID : NULL : any : NULL : - - * - * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined - * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use - * it that way but define 'partial' and let 'get' be NULL. - * - * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined - * but forbids abstract FILE when 'partial' is NULL. - * - * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF. - */ -struct mustach_itf { - int (*start)(void *closure); - int (*put)(void *closure, const char *name, int escape, FILE *file); - int (*enter)(void *closure, const char *name); - int (*next)(void *closure); - int (*leave)(void *closure); - int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); - int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); - int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); - void (*stop)(void *closure, int status); -}; - -/** - * mustach_sbuf - Interface for handling zero terminated strings - * - * That structure is used for returning zero terminated strings -in 'value'- - * to mustach. The callee can provide a function for releasing the returned - * 'value'. Three methods for releasing the string are possible. - * - * 1. no release: set either 'freecb' or 'releasecb' with NULL (done by default) - * 2. release without closure: set 'freecb' to its expected value - * 3. release with closure: set 'releasecb' and 'closure' to their expected values - * - * @value: The value of the string. That value is not changed by mustach -const-. - * - * @freecb: The function to call for freeing the value without closure. - * For convenience, signature of that callback is compatible with 'free'. - * Can be NULL. - * - * @releasecb: The function to release with closure. - * Can be NULL. - * - * @closure: The closure to use for 'releasecb'. - * - * @length: Length of the value or zero if unknown and value null terminated. - * To return the empty string, let it to zero and let value to NULL. - */ -struct mustach_sbuf { - const char *value; - union { - void (*freecb)(void*); - void (*releasecb)(const char *value, void *closure); - }; - void *closure; - size_t length; -}; - -/** - * mustach_file - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @file: the file where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file); - -/** - * mustach_fd - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @fd: the file descriptor number where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd); - -/** - * mustach_mem - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. - * - * @template: the template string to instantiate - * @length: length of the template or zero if unknown and template null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @result: the pointer receiving the result when 0 is returned - * @size: the size of the returned result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -extern int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size); - -/*************************************************************************** -* compatibility with version before 1.0 -*/ -#ifdef __GNUC__ -#define DEPRECATED_MUSTACH(func) func __attribute__ ((deprecated)) -#elif defined(_MSC_VER) -#define DEPRECATED_MUSTACH(func) __declspec(deprecated) func -#elif !defined(DEPRECATED_MUSTACH) -#pragma message("WARNING: You need to implement DEPRECATED_MUSTACH for this compiler") -#define DEPRECATED_MUSTACH(func) func -#endif -/** - * OBSOLETE use mustach_file - * - * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. - * - * @template: the template string to instantiate, null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @file: the file where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -DEPRECATED_MUSTACH(extern int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)); - -/** - * OBSOLETE use mustach_fd - * - * fdmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. - * - * @template: the template string to instantiate, null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @fd: the file descriptor number where to write the result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -DEPRECATED_MUSTACH(extern int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)); - -/** - * OBSOLETE use mustach_mem - * - * mustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. - * - * @template: the template string to instantiate, null terminated - * @itf: the interface to the functions that mustach calls - * @closure: the closure to pass to functions called - * @result: the pointer receiving the result when 0 is returned - * @size: the size of the returned result - * - * Returns 0 in case of success, -1 with errno set in case of system error - * a other negative value in case of error. - */ -DEPRECATED_MUSTACH(extern int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)); - -#endif -
--- a/html/header.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/header.html Fri Mar 17 07:43:20 2023 +0100 @@ -4,7 +4,7 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/style.css"> - <title>{{title}}</title> + <title>@@title@@</title> </head> <body>
--- a/html/index.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/index.html Fri Mar 17 07:43:20 2023 +0100 @@ -11,14 +11,6 @@ <tr> </thead> <tbody> - {{#pastes}} - <tr> - <td><a href="/paste/{{id}}">{{title}}</a></td> - <td>{{author}}</td> - <td>{{language}}</td> - <td>{{date}}</td> - <td>{{expiration}}</td> - </tr> - {{/pastes}} + @@pastes@@ </tbody> </table>
--- a/html/new.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/new.html Fri Mar 17 07:43:20 2023 +0100 @@ -4,21 +4,19 @@ <table> <tr> <td class="label">Title</td> - <td><input name="title" type="text" placeholder="Untitled" value="{{title}}" /></td> + <td><input name="title" type="text" placeholder="Untitled" value="@@title@@" /></td> </tr> <tr> <td class="label">Author</td> - <td><input name="author" type="text" placeholder="Anonymous" value="{{author}}" /></td> + <td><input name="author" type="text" placeholder="Anonymous" value="@@author@@" /></td> </tr> <tr> <td class="label">Language</td> <td> <select name="language"> - {{#languages}} - <option value="{{name}}" {{selected}}>{{name}}</option> - {{/languages}} + @@languages@@ </select> </td> </tr> @@ -27,9 +25,7 @@ <td class="label">Expires in</td> <td> <select name="duration"> - {{#durations}} - <option value="{{value}}">{{value}}</option> - {{/durations}} + @@durations@@ </select> </td> </tr> @@ -40,6 +36,6 @@ </tr> </table> - <textarea id="code" class="textarea" placeholder="What do you want to share?" rows="10" name="code">{{code}}</textarea> + <textarea id="code" class="textarea" placeholder="What do you want to share?" rows="10" name="code">@@code@@</textarea> <input class="submit" type="submit" value="paste" /> </form>
--- a/html/paste.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/paste.html Fri Mar 17 07:43:20 2023 +0100 @@ -1,42 +1,42 @@ - <h1>Paste {{title}}</h1> + <h1>Paste @@title@@</h1> <ul id="paste-menu"> - <li><a href="/fork/{{id}}">Fork</a></li> - <li><a href="/download/{{id}}">Download</a></li> + <li><a href="/fork/@@id@@">Fork</a></li> + <li><a href="/download/@@id@@">Download</a></li> </ul> <table> <tbody> <tr> <td class="label">Identifier</td> - <td>{{id}}</td> + <td>@@id@@</td> </tr> <tr> <td class="label">Title</td> - <td>{{title}}</td> + <td>@@title@@</td> </tr> <tr> <td class="label">Author</td> - <td>{{author}}</td> + <td>@@author@@</td> </tr> <tr> <td class="label">Language</td> - <td>{{language}}</td> + <td>@@language@@</td> </tr> <tr> <td class="label">Date</td> - <td>{{date}}</td> + <td>@@date@@</td> </tr> <tr> <td class="label">Public</td> - <td>{{public}}</td> + <td>@@public@@</td> </tr> <tr> <td class="label">Expires in</td> - <td>{{expires}}</td> + <td>@@expires@@</td> </tr> </tbody> </table> <pre> -{{code}} +@@code@@ </pre>
--- a/html/search.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/search.html Fri Mar 17 07:43:20 2023 +0100 @@ -16,9 +16,7 @@ <td>Language</td> <td> <select name="language"> - {{#languages}} - <option name="{{name}}">{{name}}</option> - {{/languages}} + @@languages@@ </select> </td> </tr>
--- a/html/status.html Thu Mar 16 20:45:59 2023 +0100 +++ b/html/status.html Fri Mar 17 07:43:20 2023 +0100 @@ -1,1 +1,1 @@ - <h1>{{code}} -- {{status}}</h1> + <h1>@@code@@ -- @@message@@</h1>
--- a/http.c Thu Mar 16 20:45:59 2023 +0100 +++ b/http.c Fri Mar 17 07:43:20 2023 +0100 @@ -16,17 +16,11 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/types.h> #include <assert.h> -#include <stdarg.h> -#include <stdint.h> - -#include <kcgi.h> #include "database.h" #include "http.h" #include "log.h" - #include "page-download.h" #include "page-fork.h" #include "page-index.h" @@ -34,6 +28,7 @@ #include "page-paste.h" #include "page-search.h" #include "page-static.h" +#include "page-status.h" #include "page.h" enum page {
--- a/json-util.c Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * json-util.c -- utilities for JSON - * - * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or 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 <assert.h> -#include <string.h> - -#include "json-util.h" -#include "util.h" - -json_t * -ju_languages(const char *selected) -{ - json_t *array, *obj; - - array = json_array(); - - for (size_t i = 0; i < languagesz; ++i) { - if (selected && strcmp(languages[i], selected) == 0) - obj = json_pack("{ss ss}", - "name", languages[i], - "selected", "selected" - ); - else - obj = json_pack("{ss}", "name", languages[i]); - - json_array_append_new(array, obj); - } - - return array; -} - -json_t * -ju_durations(void) -{ - json_t *array = json_array(); - - for (size_t i = 0; i < durationsz; ++i) - json_array_append_new(array, json_pack("{ss}", - "value", durations[i].title) - ); - - return array; -} - -json_t * -ju_date(time_t timestamp) -{ - return json_string(bstrftime("%c", localtime(×tamp))); -} - -json_t * -ju_expires(time_t timestamp, int duration) -{ - return json_string(ttl(timestamp, duration)); -} - -const char * -ju_get_string(const json_t *doc, const char *key) -{ - const json_t *val; - - if (!doc || !(val = json_object_get(doc, key)) || !json_is_string(val)) - return NULL; - - return json_string_value(val); -} - -intmax_t -ju_get_int(const json_t *doc, const char *key) -{ - const json_t *val; - - if (!doc || !(val = json_object_get(doc, key)) || !json_is_integer(val)) - return 0; - - return json_integer_value(val); -} - -int -ju_get_bool(const json_t *doc, const char *key) -{ - const json_t *val; - - if (!doc || !(val = json_object_get(doc, key)) || !json_is_boolean(val)) - return 0; - - return json_boolean_value(val); -} - -json_t * -ju_paste_new(void) -{ - return json_pack("{ss ss ss ss si si}", - "title", "Untitled", - "author", "Anonymous", - "language", "nohighlight", - "code", "The best code is no code", - "visible", 0, - "duration", PASTE_DURATION_HOUR - ); -} - -json_t * -ju_extend(json_t *doc, const char *fmt, ...) -{ - assert(fmt); - - json_t *ret, *val; - json_error_t err; - va_list ap; - const char *key; - - va_start(ap, fmt); - ret = json_vpack_ex(&err, 0, fmt, ap); - va_end(ap); - - /* Now steal every nodes from doc and put them in ret. */ - if (doc) { - json_object_foreach(doc, key, val) - json_object_set(ret, key, val); - - json_decref(doc); - } - - return ret; -}
--- a/json-util.h Thu Mar 16 20:45:59 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/* - * json-util.h -- utilities for JSON - * - * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or 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. - */ - -#ifndef PASTER_PASTER_JSON_UTIL_H -#define PASTER_PASTER_JSON_UTIL_H - -#include <stdint.h> -#include <time.h> - -#include <jansson.h> - -/** - * Create an array of all possible languages supported by the application. If - * the selected argument is not null it will also add a JSON property selected - * (mostly used when rendering an existing paste). - * - * Example of generated schema: - * - * ```javascript - * [ - * { - * "name": "nohighlight" - * } - * { - * "name": "c", - * "selected": "selected" - * } - * { - * "name": "cpp" - * } - * ] - * ``` - * - * \param selected the current selected language (or NULL if none) - * \return a JSON array of objects - */ -json_t * -ju_languages(const char *selected); - -/** - * Create a list of duration in the form: - * - * ```javascript - * [ - * { - * "value": "day" - * } - * { - * "value": "hour" - * } - * ] - * ``` - * - * \return a JSON array of objects - */ -json_t * -ju_durations(void); - -/** - * Create a convenient ISO date string containing the paste creation date. - * - * \param timestamp the timestamp - * \return a string with an ISO date - */ -json_t * -ju_date(time_t timestamp); - -/** - * Create a convenient remaining time for the given timestamp/duration. - * - * Returns strings in the form: - * - * - `2 day(s)` - * - `3 hours(s)` - * - * \param timestamp the timestamp - * \param duration the duration in seconds (e.g. 3600) - * \return a string containing the expiration time - */ -json_t * -ju_expires(time_t timestamp, int duration); - -const char * -ju_get_string(const json_t *, const char *); - -intmax_t -ju_get_int(const json_t *, const char *); - -int -ju_get_bool(const json_t *, const char *); - -json_t * -ju_paste_new(void); - -json_t * -ju_extend(json_t *doc, const char *fmt, ...); - -#endif /* !PASTER_PASTER_JSON_UTIL_H */
--- a/log.c Thu Mar 16 20:45:59 2023 +0100 +++ b/log.c Fri Mar 17 07:43:20 2023 +0100 @@ -2,11 +2,11 @@ * log.c -- logging routines * * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> - * + * * Permission to use, copy, modify, and/or 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 @@ -42,7 +42,7 @@ assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); assert(fmt); - if (config.verbosity >= level) { + if (config.verbosity >= (int)level) { va_list ap; va_start(ap, fmt);
--- a/page-download.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-download.c Fri Mar 17 07:43:20 2023 +0100 @@ -19,15 +19,16 @@ #include <assert.h> #include "database.h" +#include "page-status.h" #include "page.h" -#include "json-util.h" +#include "paste.h" static void get(struct kreq *req) { - json_t *paste; + struct paste paste; - if (!(paste = database_get(req->path))) + if (database_get(&paste, req->path) < 0) page_status(req, KHTTP_404); else { khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]); @@ -37,13 +38,12 @@ #endif khttp_head(req, kresps[KRESP_CONNECTION], "keep-alive"); khttp_head(req, kresps[KRESP_CONTENT_DISPOSITION], "attachment; filename=\"%s.%s\"", - ju_get_string(paste, "id"), - ju_get_string(paste, "language") + paste.id, paste.language ); khttp_body(req); - khttp_puts(req, ju_get_string(paste, "code")); + khttp_puts(req, paste.code); khttp_free(req); - json_decref(paste); + paste_finish(&paste); } }
--- a/page-fork.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-fork.c Fri Mar 17 07:43:20 2023 +0100 @@ -19,19 +19,22 @@ #include <assert.h> #include "database.h" -#include "json-util.h" #include "page-new.h" +#include "page-status.h" #include "page.h" +#include "paste.h" static void get(struct kreq *req) { - json_t *paste; + struct paste paste; - if (!(paste = database_get(req->path))) + if (database_get(&paste, req->path) < 0) page_status(req, KHTTP_404); - else - page_new_render(req, paste); + else { + page_new_render(req, &paste); + paste_finish(&paste); + } } void
--- a/page-index.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-index.c Fri Mar 17 07:43:20 2023 +0100 @@ -19,9 +19,10 @@ #include <assert.h> #include "database.h" -#include "json-util.h" #include "page-index.h" +#include "page-status.h" #include "page.h" +#include "paste.h" #include "util.h" #include "html/index.h" @@ -29,27 +30,112 @@ #define LIMIT 16 #define TITLE "paster -- recent pastes" +struct page { + struct kreq *req; + struct ktemplate template; + const struct paste *pastes; + const size_t pastesz; +}; + +enum { + KEYWORD_PASTES +}; + +static const char * const keywords[] = { + [KEYWORD_PASTES] = "pastes" +}; + +static int +format(size_t keyword, void *data) +{ + struct page *page = data; + struct khtmlreq html; + const struct paste *paste; + + khtml_open(&html, page->req, KHTML_PRETTY); + + switch (keyword) { + case KEYWORD_PASTES: + for (size_t i = 0; i < page->pastesz; ++i) { + paste = &page->pastes[i]; + + khtml_elem(&html, KELEM_TR); + + /* link */ + khtml_elem(&html, KELEM_TD); + khtml_attr(&html, KELEM_A, + KATTR_HREF, bprintf("/paste/%s", paste->id), KATTR__MAX); + khtml_printf(&html, paste->title); + khtml_closeelem(&html, 1); + + /* author */ + khtml_elem(&html, KELEM_TD); + khtml_puts(&html, paste->author); + khtml_closeelem(&html, 1); + + /* language */ + khtml_elem(&html, KELEM_TD); + khtml_puts(&html, paste->language); + khtml_closeelem(&html, 1); + + /* date */ + khtml_elem(&html, KELEM_TD); + khtml_puts(&html, bstrftime("%F %T", localtime(&paste->timestamp))); + khtml_closeelem(&html, 1); + + /* expiration */ + khtml_elem(&html, KELEM_TD); + khtml_puts(&html, ttl(paste->timestamp, paste->duration)); + khtml_closeelem(&html, 1); + + khtml_closeelem(&html, 1); + + } + break; + default: + break; + } + + khtml_close(&html); + + return 1; +} + static void get(struct kreq *req) { - json_t *pastes; + struct paste pastes[LIMIT]; + size_t pastesz = NELEM(pastes); - if (!(pastes = database_recents(LIMIT))) + if (database_recents(pastes, &pastesz) < 0) page_status(req, KHTTP_500); - else - page_index_render(req, pastes); + else { + page_index_render(req, pastes, pastesz); + + for (size_t i = 0; i < pastesz; ++i) + paste_finish(&pastes[i]); + } } void -page_index_render(struct kreq *req, json_t *pastes) +page_index_render(struct kreq *req, const struct paste *pastes, size_t pastesz) { assert(req); assert(pastes); - page(req, KHTTP_200, html_index, json_pack("{ss so}", - "title", TITLE, - "pastes", pastes - )); + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + }, + .pastes = pastes, + .pastesz = pastesz + }; + + page(req, KHTTP_200, TITLE, html_index, &self.template); } void
--- a/page-index.h Thu Mar 16 20:45:59 2023 +0100 +++ b/page-index.h Fri Mar 17 07:43:20 2023 +0100 @@ -19,12 +19,15 @@ #ifndef PASTER_PAGE_INDEX_H #define PASTER_PAGE_INDEX_H -#include <jansson.h> +#include <stddef.h> struct kreq; +struct paste; void -page_index_render(struct kreq *, json_t *pastes); +page_index_render(struct kreq *req, + const struct paste *pastes, + size_t pastesz); void page_index(struct kreq *);
--- a/page-new.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-new.c Fri Mar 17 07:43:20 2023 +0100 @@ -18,17 +18,41 @@ #include <assert.h> #include <string.h> +#include <stdlib.h> #include "database.h" -#include "json-util.h" #include "page-new.h" +#include "page-status.h" #include "page.h" +#include "paste.h" #include "util.h" #include "html/new.h" #define TITLE "paster -- create a new paste" +enum { + KEYWORD_TITLE, + KEYWORD_AUTHOR, + KEYWORD_LANGUAGES, + KEYWORD_DURATIONS, + KEYWORD_CODE +}; + +struct page { + struct kreq *req; + struct ktemplate template; + const struct paste *paste; +}; + +static const char * const keywords[] = { + [KEYWORD_TITLE] = "title", + [KEYWORD_AUTHOR] = "author", + [KEYWORD_LANGUAGES] = "languages", + [KEYWORD_DURATIONS] = "durations", + [KEYWORD_CODE] = "code" +}; + static long long int duration(const char *val) { @@ -40,6 +64,57 @@ return 60 * 60 * 24; } +static int +format(size_t kw, void *data) +{ + struct page *page = data; + struct khtmlreq html; + + khtml_open(&html, page->req, KHTML_PRETTY); + + switch (kw) { + case KEYWORD_TITLE: + if (page->paste) + khtml_printf(&html, page->paste->title); + break; + case KEYWORD_AUTHOR: + if (page->paste) + khtml_printf(&html, page->paste->author); + break; + case KEYWORD_LANGUAGES: + for (size_t i = 0; i < languagesz; ++i) { + if (page->paste && strcmp(page->paste->language, languages[i]) == 0) + khtml_attr(&html, KELEM_OPTION, + KATTR_VALUE, languages[i], + KATTR_SELECTED, "selected", + KATTR__MAX + ); + else + khtml_attr(&html, KELEM_OPTION, KATTR_VALUE, languages[i], KATTR__MAX); + khtml_printf(&html, "%s", languages[i]); + khtml_closeelem(&html, 1); + } + break; + case KEYWORD_DURATIONS: + for (size_t i = 0; i < durationsz; ++i) { + khtml_attr(&html, KELEM_OPTION, KATTR_VALUE, durations[i].title, KATTR__MAX); + khtml_printf(&html, "%s", durations[i].title); + khtml_closeelem(&html, 1); + } + break; + case KEYWORD_CODE: + if (page->paste) + khtml_puts(&html, page->paste->code); + break; + default: + break; + } + + khtml_close(&html); + + return 1; +} + static void get(struct kreq *r) { @@ -49,68 +124,73 @@ static void post(struct kreq *req) { - const char *key, *val, *id, *scheme; - json_t *paste; + struct paste paste; + const char *key, *val, *scheme; int raw = 0; - paste = ju_paste_new(); + paste_init(&paste); + // TODO: add verification support. for (size_t i = 0; i < req->fieldsz; ++i) { key = req->fields[i].key; val = req->fields[i].val; if (strcmp(key, "title") == 0 && strlen(val)) - json_object_set_new(paste, "title", json_string(val)); + replace(&paste.title, val); else if (strcmp(key, "author") == 0 && strlen(val)) - json_object_set_new(paste, "author", json_string(val)); + replace(&paste.author, val); else if (strcmp(key, "language") == 0) - json_object_set_new(paste, "language", json_string(val)); + replace(&paste.language, val); else if (strcmp(key, "duration") == 0) - json_object_set_new(paste, "duration", json_integer(duration(val))); + paste.duration = duration(val); else if (strcmp(key, "code") == 0) - json_object_set_new(paste, "code", json_string(val)); + replace(&paste.code, val); else if (strcmp(key, "visible") == 0) - json_object_set_new(paste, "visible", json_boolean(strcmp(val, "on") == 0)); + paste.visible = strcmp(val, "on") == 0; else if (strcmp(key, "raw") == 0) raw = strcmp(val, "on") == 0; } - if (database_insert(paste) < 0) + if (database_insert(&paste) < 0) page_status(req, KHTTP_500); else { - id = ju_get_string(paste, "id"); scheme = req->scheme == KSCHEME_HTTP ? "http" : "https"; if (raw) { /* For CLI users (e.g. paster) just print the location. */ khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_201]); khttp_body(req); - khttp_printf(req, "%s://%s/paste/%s\n", scheme, req->host, id); + khttp_printf(req, "%s://%s/paste/%s\n", scheme, req->host, paste.id); } else { /* Otherwise, redirect to paste details. */ khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]); - khttp_head(req, kresps[KRESP_LOCATION], "/paste/%s", id); + khttp_head(req, kresps[KRESP_LOCATION], "/paste/%s", paste.id); khttp_body(req); } khttp_free(req); } - json_decref(paste); + paste_finish(&paste); } -#include "log.h" - void -page_new_render(struct kreq *req, json_t *paste) +page_new_render(struct kreq *req, const struct paste *paste) { assert(req); - page(req, KHTTP_200, html_new, ju_extend(paste, "{ss so so}", - "pagetitle", TITLE, - "durations", ju_durations(), - "languages", ju_languages(ju_get_string(paste, "language")) - )); + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + }, + .paste = paste + }; + + page(req, KHTTP_200, TITLE, html_new, &self.template); } void
--- a/page-new.h Thu Mar 16 20:45:59 2023 +0100 +++ b/page-new.h Fri Mar 17 07:43:20 2023 +0100 @@ -19,12 +19,11 @@ #ifndef PASTER_PAGE_NEW_H #define PASTER_PAGE_NEW_H -#include <jansson.h> - struct kreq; +struct paste; void -page_new_render(struct kreq *, json_t *); +page_new_render(struct kreq *, const struct paste *); void page_new(struct kreq *);
--- a/page-paste.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-paste.c Fri Mar 17 07:43:20 2023 +0100 @@ -19,56 +19,104 @@ #include <assert.h> #include "database.h" -#include "json-util.h" #include "page-paste.h" +#include "page-status.h" #include "page.h" +#include "paste.h" #include "util.h" #include "html/paste.h" -static inline json_t * -mk_pagetitle(const json_t *paste) -{ - return json_sprintf("paster -- %s", ju_get_string(paste, "title")); -} +#define TITLE "paster -- paste details" + +enum { + KEYWORD_TITLE, + KEYWORD_ID, + KEYWORD_AUTHOR, + KEYWORD_LANGUAGE, + KEYWORD_DATE, + KEYWORD_PUBLIC, + KEYWORD_EXPIRES, + KEYWORD_CODE +}; -static inline json_t * -mk_date(const json_t *paste) -{ - return ju_date(ju_get_int(paste, "timestamp")); -} +struct page { + struct kreq *req; + struct ktemplate template; + struct paste paste; +}; + +static const char * const keywords[] = { + [KEYWORD_TITLE] = "title", + [KEYWORD_ID] = "id", + [KEYWORD_AUTHOR] = "author", + [KEYWORD_LANGUAGE] = "language", + [KEYWORD_DATE] = "date", + [KEYWORD_PUBLIC] = "public", + [KEYWORD_EXPIRES] = "expires", + [KEYWORD_CODE] = "code" +}; -static inline json_t * -mk_public(const json_t *paste) +static int +format(size_t keyword, void *data) { - const intmax_t visible = ju_get_int(paste, "visible"); + struct page *page = data; + struct khtmlreq html; - return json_string(visible ? "Yes" : "No"); -} + khtml_open(&html, page->req, 0); -static inline json_t * -mk_expires(const json_t *paste) -{ - return ju_expires( - ju_get_int(paste, "timestamp"), - ju_get_int(paste, "duration") - ); + switch (keyword) { + case KEYWORD_TITLE: + khtml_puts(&html, page->paste.title); + break; + case KEYWORD_ID: + khtml_puts(&html, page->paste.id); + break; + case KEYWORD_AUTHOR: + khtml_puts(&html, page->paste.author); + break; + case KEYWORD_LANGUAGE: + khtml_puts(&html, page->paste.language); + break; + case KEYWORD_DATE: + khtml_puts(&html, bstrftime("%F %T", localtime(&page->paste.timestamp))); + break; + case KEYWORD_PUBLIC: + khtml_puts(&html, page->paste.visible ? "Yes" : "No"); + break; + case KEYWORD_EXPIRES: + khtml_puts(&html, ttl(page->paste.timestamp, page->paste.duration)); + break; + case KEYWORD_CODE: + khtml_puts(&html, page->paste.code); + break; + default: + break; + } + + khtml_close(&html); + + return 1; } static void get(struct kreq *req) { - json_t *paste; + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + } + }; - if (!(paste = database_get(req->path))) + if (database_get(&self.paste, req->path) < 0) page_status(req, KHTTP_404); else { - page(req, KHTTP_200, html_paste, ju_extend(paste, "{so so so so}", - "pagetitle", mk_pagetitle(paste), - "date", mk_date(paste), - "public", mk_public(paste), - "expires", mk_expires(paste) - )); + page(req, KHTTP_200, TITLE, html_paste, &self.template); + paste_finish(&self.paste); } }
--- a/page-paste.h Thu Mar 16 20:45:59 2023 +0100 +++ b/page-paste.h Fri Mar 17 07:43:20 2023 +0100 @@ -2,11 +2,11 @@ * page-paste.h -- page /paste/<id> * * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> - * + * * Permission to use, copy, modify, and/or 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 @@ -16,12 +16,12 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef PASTER_PAGE_IMAGE_H -#define PASTER_PAGE_IMAGE_H +#ifndef PASTER_PAGE_PASTE_H +#define PASTER_PAGE_PASTE_H struct kreq; void page_paste(struct kreq *); -#endif /* !PASTER_PAGE_IMAGE_H */ +#endif /* !PASTER_PAGE_PASTE_H */
--- a/page-search.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page-search.c Fri Mar 17 07:43:20 2023 +0100 @@ -20,10 +20,11 @@ #include <string.h> #include "database.h" -#include "json-util.h" #include "page-index.h" #include "page-search.h" +#include "page-status.h" #include "page.h" +#include "paste.h" #include "util.h" #include "html/search.h" @@ -31,19 +32,65 @@ #define TITLE "paster -- search" #define LIMIT 16 +enum { + KEYWORD_LANGUAGES +}; + +struct page { + struct kreq *req; + struct ktemplate template; +}; + +static const char * const keywords[] = { + [KEYWORD_LANGUAGES] = "languages" +}; + +static int +format(size_t keyword, void *data) +{ + struct page *page = data; + struct khtmlreq html; + + khtml_open(&html, page->req, 0); + + switch (keyword) { + case KEYWORD_LANGUAGES: + for (size_t i = 0; i < languagesz; ++i) { + khtml_attr(&html, KELEM_OPTION, KATTR_NAME, languages[i], KATTR__MAX); + khtml_puts(&html, languages[i]); + khtml_closeelem(&html, 1); + } + break; + default: + break; + } + + khtml_close(&html); + + return 1; +} + static void get(struct kreq *req) { - page(req, KHTTP_200, html_search, json_pack("{ss so}", - "pagetitle", "paster -- search", - "languages", ju_languages(NULL) - )); + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + } + }; + + page(req, KHTTP_200, TITLE, html_search, &self.template); } static void post(struct kreq *req) { - json_t *pastes; + struct paste pastes[LIMIT]; + size_t pastesz = NELEM(pastes); const char *key, *val, *title = NULL, *author = NULL, *language = NULL; for (size_t i = 0; i < req->fieldsz; ++i) { @@ -64,10 +111,14 @@ if (author && strlen(author) == 0) author = NULL; - if (!(pastes = database_search(16, title, author, language))) + if (database_search(pastes, &pastesz, title, author, language) < 0) page_status(req, KHTTP_500); - else - page_index_render(req, pastes); + else { + page_index_render(req, pastes, pastesz); + + for (size_t i = 0; i < pastesz; ++i) + paste_finish(&pastes[i]); + } } void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/page-status.c Fri Mar 17 07:43:20 2023 +0100 @@ -0,0 +1,92 @@ +/* + * page-status.c -- error page + * + * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or 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 <assert.h> + +#include "page.h" +#include "util.h" + +#include "html/status.h" + +enum { + KEYWORD_CODE, + KEYWORD_MESSAGE +}; + +struct page { + struct kreq *req; + struct ktemplate template; + enum khttp status; +}; + +static const int status_codes[] = { + [KHTTP_200] = 200, + [KHTTP_400] = 400, + [KHTTP_404] = 404, + [KHTTP_500] = 500 +}; + +static const char * const status_messages[] = { + [KHTTP_200] = "OK", + [KHTTP_400] = "Bad Request", + [KHTTP_404] = "Not Found", + [KHTTP_500] = "Internal Server Error" +}; + +static const char *keywords[] = { + [KEYWORD_CODE] = "code", + [KEYWORD_MESSAGE] = "message" +}; + +static int +format(size_t keyword, void *data) +{ + struct page *page = data; + + switch (keyword) { + case KEYWORD_CODE: + khttp_printf(page->req, "%d", status_codes[page->status]); + break; + case KEYWORD_MESSAGE: + khttp_printf(page->req, "%s", status_messages[page->status]); + break; + default: + break; + } + + return 1; +} + +void +page_status(struct kreq *req, enum khttp status) +{ + assert(req); + + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + }, + .status = status + }; + + page(req, status, "paster -- error", html_status, &self.template); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/page-status.h Fri Mar 17 07:43:20 2023 +0100 @@ -0,0 +1,29 @@ +/* + * page-status.h -- error page + * + * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or 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. + */ + +#ifndef PASTER_PAGE_STATUS_H +#define PASTER_PAGE_STATUS_H + +struct kreq; + +enum khttp; + +void +page_status(struct kreq *req, enum khttp status); + +#endif /* !PASTER_PAGE_NEW_H */
--- a/page.c Thu Mar 16 20:45:59 2023 +0100 +++ b/page.c Fri Mar 17 07:43:20 2023 +0100 @@ -19,8 +19,6 @@ #include <assert.h> #include <string.h> -#include <mustach-jansson.h> - #include "config.h" #include "page.h" #include "util.h" @@ -29,64 +27,66 @@ #include "html/header.h" #include "html/status.h" -static const int statustab[] = { - [KHTTP_200] = 200, - [KHTTP_400] = 400, - [KHTTP_404] = 404, - [KHTTP_500] = 500 +#define CHAR(html) (const char *)(html) + +enum { + KEYWORD_TITLE, }; -static const char * const statusmsg[] = { - [KHTTP_200] = "OK", - [KHTTP_400] = "Bad Request", - [KHTTP_404] = "Not Found", - [KHTTP_500] = "Internal Server Error" +struct page { + struct kreq *req; + struct ktemplate template; + const char *title; +}; + +static const char *keywords[] = { + [KEYWORD_TITLE] = "title" }; static int -writer(void *data, const char *buffer, size_t size) +format(size_t keyword, void *data) { - struct kreq *req = data; - - khttp_write(req, buffer, size); + struct page *page = data; - return MUSTACH_OK; -} + switch (keyword) { + case KEYWORD_TITLE: + khttp_printf(page->req, "%s", page->title); + break; + default: + break; + } -static void -format(struct kreq *req, const char *html, json_t *doc) -{ - if (!doc) - khttp_template_buf(req, NULL, html, strlen(html)); - else - mustach_jansson_write(html, strlen(html), doc, 0, writer, req); + return 1; } void -page(struct kreq *req, enum khttp status, const unsigned char *html, json_t *doc) +page(struct kreq *req, + enum khttp status, + const char *title, + const unsigned char *html, + const struct ktemplate *tmpl) { assert(req); + assert(title); assert(html); + assert(tmpl); + + struct page self = { + .req = req, + .template = { + .cb = format, + .arg = &self, + .key = keywords, + .keysz = NELEM(keywords) + }, + .title = title, + }; khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]); khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); khttp_body(req); - format(req, (const char *)html_header, doc); - format(req, (const char *)html, doc); - format(req, (const char *)html_footer, doc); + khttp_template_buf(req, &self.template, CHAR(html_header), strlen(CHAR(html_header))); + khttp_template_buf(req, tmpl, CHAR(html), strlen(CHAR(html))); + khttp_template_buf(req, NULL, CHAR(html_footer), strlen(CHAR(html_footer))); khttp_free(req); - - if (doc) - json_decref(doc); } - -void -page_status(struct kreq *req, enum khttp status) -{ - assert(req); - - page(req, status, html_status, json_pack("{si ss}", - "code", statustab[status], - "status", statusmsg[status] - )); -}
--- a/page.h Thu Mar 16 20:45:59 2023 +0100 +++ b/page.h Fri Mar 17 07:43:20 2023 +0100 @@ -2,11 +2,11 @@ * page.h -- page renderer * * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> - * + * * Permission to use, copy, modify, and/or 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 @@ -23,14 +23,14 @@ #include <stdarg.h> #include <stdint.h> -#include <jansson.h> - #include <kcgi.h> +#include <kcgihtml.h> void -page(struct kreq *req, enum khttp status, const unsigned char *html, json_t *doc); - -void -page_status(struct kreq *req, enum khttp status); +page(struct kreq *req, + enum khttp status, + const char *title, + const unsigned char *html, + const struct ktemplate *tmpl); #endif /* !PASTER_PAGE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/paste.c Fri Mar 17 07:43:20 2023 +0100 @@ -0,0 +1,50 @@ +/* + * paste.c -- paste definition + * + * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or 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 <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "paste.h" +#include "util.h" + +void +paste_init(struct paste *paste) +{ + assert(paste); + + memset(paste, 0, sizeof (*paste)); + paste->title = estrdup(PASTE_DEFAULT_TITLE); + paste->author = estrdup(PASTE_DEFAULT_AUTHOR); + paste->language = estrdup(PASTE_DEFAULT_LANGUAGE); + paste->timestamp = time(NULL); + paste->duration = PASTE_DURATION_DAY; +} + +void +paste_finish(struct paste *paste) +{ + assert(paste); + + free(paste->id); + free(paste->title); + free(paste->author); + free(paste->language); + free(paste->code); + memset(paste, 0, sizeof (struct paste)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/paste.h Fri Mar 17 07:43:20 2023 +0100 @@ -0,0 +1,50 @@ +/* + * paste.h -- paste definition + * + * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or 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. + */ + +#ifndef PASTER_PASTE_H +#define PASTER_PASTE_H + +#include <time.h> + +#define PASTE_DURATION_HOUR 3600 /* Seconds in one hour. */ +#define PASTE_DURATION_DAY 86400 /* Seconds in one day. */ +#define PASTE_DURATION_WEEK 604800 /* Seconds in one week. */ +#define PASTE_DURATION_MONTH 2592000 /* Rounded to 30 days. */ + +#define PASTE_DEFAULT_TITLE "Untitled" +#define PASTE_DEFAULT_AUTHOR "Anonymous" +#define PASTE_DEFAULT_LANGUAGE "nohighlight" + +struct paste { + char *id; + char *title; + char *author; + char *language; + char *code; + time_t timestamp; + int visible; + int duration; +}; + +void +paste_init(struct paste *paste); + +void +paste_finish(struct paste *paste); + +#endif /* !PASTER_PASTE_H */
--- a/pasterd.c Thu Mar 16 20:45:59 2023 +0100 +++ b/pasterd.c Fri Mar 17 07:43:20 2023 +0100 @@ -67,7 +67,7 @@ } int -main(int argc, char **argv, char **env) +main(int argc, char **argv) { const char *value; int opt;
--- a/util.c Thu Mar 16 20:45:59 2023 +0100 +++ b/util.c Fri Mar 17 07:43:20 2023 +0100 @@ -27,6 +27,7 @@ #include <time.h> #include "config.h" +#include "paste.h" #include "util.h" const char * const languages[] = {
--- a/util.h Thu Mar 16 20:45:59 2023 +0100 +++ b/util.h Fri Mar 17 07:43:20 2023 +0100 @@ -24,11 +24,6 @@ #define NELEM(x) (sizeof (x) / sizeof (x)[0]) -#define PASTE_DURATION_HOUR (3600) -#define PASTE_DURATION_DAY (PASTE_DURATION_HOUR * 24) -#define PASTE_DURATION_WEEK (PASTE_DURATION_DAY * 7) -#define PASTE_DURATION_MONTH (PASTE_DURATION_DAY * 30) - struct tm; struct kreq;