Mercurial > paster
diff extern/libmustach/mustach.c @ 74:67b3d13a5035
pasterd: make own HTML code for good
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 15 Mar 2023 19:34:00 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libmustach/mustach.c Wed Mar 15 19:34:00 2023 +0100 @@ -0,0 +1,546 @@ +/* + 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); +} +