LCOV - code coverage report
Current view: top level - templating - mustach.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 51.6 % 316 163
Test Date: 2025-12-22 22:38:17 Functions: 58.8 % 17 10

            Line data    Source code
       1              : /*
       2              :  Author: José Bollo <jobol@nonadev.net>
       3              : 
       4              :  https://gitlab.com/jobol/mustach
       5              : 
       6              :  SPDX-License-Identifier: ISC
       7              : */
       8              : 
       9              : #ifndef _GNU_SOURCE
      10              : #define _GNU_SOURCE
      11              : #endif
      12              : 
      13              : #include <stdlib.h>
      14              : #include <stdio.h>
      15              : #include <string.h>
      16              : #include <errno.h>
      17              : #include <ctype.h>
      18              : #ifdef _WIN32
      19              : #include <malloc.h>
      20              : #endif
      21              : 
      22              : #include "mustach.h"
      23              : 
      24              : struct iwrap {
      25              :         int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
      26              :         void *closure; /* closure for: enter, next, leave, emit, get */
      27              :         int (*put)(void *closure, const char *name, int escape, FILE *file);
      28              :         void *closure_put; /* closure for put */
      29              :         int (*enter)(void *closure, const char *name);
      30              :         int (*next)(void *closure);
      31              :         int (*leave)(void *closure);
      32              :         int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
      33              :         int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
      34              :         void *closure_partial; /* closure for partial */
      35              :         FILE *file;
      36              :         int flags;
      37              :         int nesting;
      38              : };
      39              : 
      40              : struct prefix {
      41              :         size_t len;
      42              :         const char *start;
      43              :         struct prefix *prefix;
      44              : };
      45              : 
      46              : #if !defined(NO_OPEN_MEMSTREAM)
      47            6 : static FILE *memfile_open(char **buffer, size_t *size)
      48              : {
      49            6 :         return open_memstream(buffer, size);
      50              : }
      51            0 : static void memfile_abort(FILE *file, char **buffer, size_t *size)
      52              : {
      53            0 :         fclose(file);
      54            0 :         free(*buffer);
      55            0 :         *buffer = NULL;
      56            0 :         *size = 0;
      57            0 : }
      58            6 : static int memfile_close(FILE *file, char **buffer, size_t *size)
      59              : {
      60              :         int rc;
      61              : 
      62              :         /* adds terminating null */
      63            6 :         rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
      64            6 :         fclose(file);
      65            6 :         if (rc == 0)
      66              :                 /* removes terminating null of the length */
      67            6 :                 (*size)--;
      68              :         else {
      69            0 :                 free(*buffer);
      70            0 :                 *buffer = NULL;
      71            0 :                 *size = 0;
      72              :         }
      73            6 :         return rc;
      74              : }
      75              : #else
      76              : static FILE *memfile_open(char **buffer, size_t *size)
      77              : {
      78              :         /*
      79              :          * We can't provide *buffer and *size as open_memstream does but
      80              :          * at least clear them so the caller won't get bad data.
      81              :          */
      82              :         *buffer = NULL;
      83              :         *size = 0;
      84              : 
      85              :         return tmpfile();
      86              : }
      87              : static void memfile_abort(FILE *file, char **buffer, size_t *size)
      88              : {
      89              :         fclose(file);
      90              :         *buffer = NULL;
      91              :         *size = 0;
      92              : }
      93              : static int memfile_close(FILE *file, char **buffer, size_t *size)
      94              : {
      95              :         int rc;
      96              :         size_t s;
      97              :         char *b;
      98              : 
      99              :         s = (size_t)ftell(file);
     100              :         b = malloc(s + 1);
     101              :         if (b == NULL) {
     102              :                 rc = MUSTACH_ERROR_SYSTEM;
     103              :                 errno = ENOMEM;
     104              :                 s = 0;
     105              :         } else {
     106              :                 rewind(file);
     107              :                 if (1 == fread(b, s, 1, file)) {
     108              :                         rc = 0;
     109              :                         b[s] = 0;
     110              :                 } else {
     111              :                         rc = MUSTACH_ERROR_SYSTEM;
     112              :                         free(b);
     113              :                         b = NULL;
     114              :                         s = 0;
     115              :                 }
     116              :         }
     117              :         *buffer = b;
     118              :         *size = s;
     119              :         return rc;
     120              : }
     121              : #endif
     122              : 
     123            7 : static inline void sbuf_reset(struct mustach_sbuf *sbuf)
     124              : {
     125            7 :         sbuf->value = NULL;
     126            7 :         sbuf->freecb = NULL;
     127            7 :         sbuf->closure = NULL;
     128            7 :         sbuf->length = 0;
     129            7 : }
     130              : 
     131            7 : static inline void sbuf_release(struct mustach_sbuf *sbuf)
     132              : {
     133            7 :         if (sbuf->releasecb)
     134            0 :                 sbuf->releasecb(sbuf->value, sbuf->closure);
     135            7 : }
     136              : 
     137            7 : static inline size_t sbuf_length(struct mustach_sbuf *sbuf)
     138              : {
     139            7 :         size_t length = sbuf->length;
     140            7 :         if (length == 0 && sbuf->value != NULL)
     141            7 :                 length = strlen(sbuf->value);
     142            7 :         return length;
     143              : }
     144              : 
     145            0 : static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file)
     146              : {
     147              :         size_t i, j, r;
     148              : 
     149              :         (void)closure; /* unused */
     150              : 
     151            0 :         if (!escape)
     152            0 :                 return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
     153              : 
     154            0 :         r = i = 0;
     155            0 :         while (i < size) {
     156            0 :                 j = i;
     157            0 :                 while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&' && buffer[j] != '"')
     158            0 :                         j++;
     159            0 :                 if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1)
     160            0 :                         return MUSTACH_ERROR_SYSTEM;
     161            0 :                 if (j < size) {
     162            0 :                         switch(buffer[j++]) {
     163            0 :                         case '<':
     164            0 :                                 r = fwrite("&lt;", 4, 1, file);
     165            0 :                                 break;
     166            0 :                         case '>':
     167            0 :                                 r = fwrite("&gt;", 4, 1, file);
     168            0 :                                 break;
     169            0 :                         case '&':
     170            0 :                                 r = fwrite("&amp;", 5, 1, file);
     171            0 :                                 break;
     172            0 :                         case '"':
     173            0 :                                 r = fwrite("&quot;", 6, 1, file);
     174            0 :                                 break;
     175              :                         }
     176            0 :                         if (r != 1)
     177            0 :                                 return MUSTACH_ERROR_SYSTEM;
     178              :                 }
     179            0 :                 i = j;
     180              :         }
     181            0 :         return MUSTACH_OK;
     182              : }
     183              : 
     184            7 : static int iwrap_put(void *closure, const char *name, int escape, FILE *file)
     185              : {
     186            7 :         struct iwrap *iwrap = closure;
     187              :         int rc;
     188              :         struct mustach_sbuf sbuf;
     189              :         size_t length;
     190              : 
     191            7 :         sbuf_reset(&sbuf);
     192            7 :         rc = iwrap->get(iwrap->closure, name, &sbuf);
     193            7 :         if (rc >= 0) {
     194            7 :                 length = sbuf_length(&sbuf);
     195            7 :                 if (length)
     196            6 :                         rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file);
     197            7 :                 sbuf_release(&sbuf);
     198              :         }
     199            7 :         return rc;
     200              : }
     201              : 
     202            0 : static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
     203              : {
     204            0 :         struct iwrap *iwrap = closure;
     205              :         int rc;
     206              :         FILE *file;
     207              :         size_t size;
     208              :         char *result;
     209              : 
     210            0 :         result = NULL;
     211            0 :         file = memfile_open(&result, &size);
     212            0 :         if (file == NULL)
     213            0 :                 rc = MUSTACH_ERROR_SYSTEM;
     214              :         else {
     215            0 :                 rc = iwrap->put(iwrap->closure_put, name, 0, file);
     216            0 :                 if (rc < 0)
     217            0 :                         memfile_abort(file, &result, &size);
     218              :                 else {
     219            0 :                         rc = memfile_close(file, &result, &size);
     220            0 :                         if (rc == 0) {
     221            0 :                                 sbuf->value = result;
     222            0 :                                 sbuf->freecb = free;
     223            0 :                                 sbuf->length = size;
     224              :                         }
     225              :                 }
     226              :         }
     227            0 :         return rc;
     228              : }
     229              : 
     230           15 : static int emitprefix(struct iwrap *iwrap, struct prefix *prefix)
     231              : {
     232           15 :         if (prefix->prefix) {
     233            0 :                 int rc = emitprefix(iwrap, prefix->prefix);
     234            0 :                 if (rc < 0)
     235            0 :                         return rc;
     236              :         }
     237           15 :         return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, iwrap->file) : 0;
     238              : }
     239              : 
     240            6 : static int process(const char *template, size_t length, struct iwrap *iwrap, struct prefix *prefix)
     241              : {
     242              :         struct mustach_sbuf sbuf;
     243              :         char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH];
     244              :         char name[MUSTACH_MAX_LENGTH + 1], c;
     245              :         const char *beg, *term, *end;
     246              :         struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } stack[MUSTACH_MAX_DEPTH];
     247              :         size_t oplen, cllen, len, l;
     248              :         int depth, rc, enabled, stdalone;
     249              :         struct prefix pref;
     250              : 
     251            6 :         pref.prefix = prefix;
     252            6 :         end = template + (length ? length : strlen(template));
     253            6 :         opstr[0] = opstr[1] = '{';
     254            6 :         clstr[0] = clstr[1] = '}';
     255            6 :         oplen = cllen = 2;
     256            6 :         stdalone = enabled = 1;
     257            6 :         depth = pref.len = 0;
     258              :         for (;;) {
     259              :                 /* search next openning delimiter */
     260           64 :                 for (beg = template ; ; beg++) {
     261           64 :                         c = beg == end ? '\n' : *beg;
     262           64 :                         if (c == '\n') {
     263            6 :                                 l = (beg != end) + (size_t)(beg - template);
     264            6 :                                 if (stdalone != 2 && enabled) {
     265            6 :                                         if (beg != template /* don't prefix empty lines */) {
     266            1 :                                                 rc = emitprefix(iwrap, &pref);
     267            1 :                                                 if (rc < 0)
     268            0 :                                                         return rc;
     269              :                                         }
     270            6 :                                         rc = iwrap->emit(iwrap->closure, template, l, 0, iwrap->file);
     271            6 :                                         if (rc < 0)
     272            0 :                                                 return rc;
     273              :                                 }
     274            6 :                                 if (beg == end) /* no more mustach */
     275            6 :                                         return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
     276            0 :                                 template += l;
     277            0 :                                 stdalone = 1;
     278            0 :                                 pref.len = 0;
     279            0 :                                 pref.prefix = prefix;
     280              :                         }
     281           58 :                         else if (!isspace(c)) {
     282           53 :                                 if (stdalone == 2 && enabled) {
     283            1 :                                         rc = emitprefix(iwrap, &pref);
     284            1 :                                         if (rc < 0)
     285            0 :                                                 return rc;
     286            1 :                                         pref.len = 0;
     287            1 :                                         stdalone = 0;
     288            1 :                                         pref.prefix = NULL;
     289              :                                 }
     290           53 :                                 if (c == *opstr && end - beg >= (ssize_t)oplen) {
     291           28 :                                         for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++);
     292           14 :                                         if (l == oplen)
     293           14 :                                                 break;
     294              :                                 }
     295           39 :                                 stdalone = 0;
     296              :                         }
     297              :                 }
     298              : 
     299           14 :                 pref.start = template;
     300           14 :                 pref.len = enabled ? (size_t)(beg - template) : 0;
     301           14 :                 beg += oplen;
     302              : 
     303              :                 /* search next closing delimiter */
     304           74 :                 for (term = beg ; ; term++) {
     305           74 :                         if (term == end)
     306            0 :                                 return MUSTACH_ERROR_UNEXPECTED_END;
     307           74 :                         if (*term == *clstr && end - term >= (ssize_t)cllen) {
     308           28 :                                 for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++);
     309           14 :                                 if (l == cllen)
     310           14 :                                         break;
     311              :                         }
     312              :                 }
     313           14 :                 template = term + cllen;
     314           14 :                 len = (size_t)(term - beg);
     315           14 :                 c = *beg;
     316           14 :                 switch(c) {
     317            0 :                 case ':':
     318            0 :                         stdalone = 0;
     319            0 :                         if (iwrap->flags & Mustach_With_Colon)
     320            0 :                                 goto exclude_first;
     321            0 :                         goto get_name;
     322            0 :                 case '!':
     323              :                 case '=':
     324            0 :                         break;
     325            0 :                 case '{':
     326            0 :                         for (l = 0 ; l < cllen && clstr[l] == '}' ; l++);
     327            0 :                         if (l < cllen) {
     328            0 :                                 if (!len || beg[len-1] != '}')
     329            0 :                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
     330            0 :                                 len--;
     331              :                         } else {
     332            0 :                                 if (term[l] != '}')
     333            0 :                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
     334            0 :                                 template++;
     335              :                         }
     336            0 :                         c = '&';
     337              :                         /*@fallthrough@*/
     338            0 :                 case '&':
     339            0 :                         stdalone = 0;
     340              :                         /*@fallthrough@*/
     341              :                 case '^':
     342              :                 case '#':
     343              :                 case '/':
     344              :                 case '>':
     345            7 : exclude_first:
     346            7 :                         beg++;
     347            7 :                         len--;
     348            7 :                         goto get_name;
     349            7 :                 default:
     350            7 :                         stdalone = 0;
     351           14 : get_name:
     352           28 :                         while (len && isspace(beg[0])) { beg++; len--; }
     353           28 :                         while (len && isspace(beg[len-1])) len--;
     354           14 :                         if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag))
     355            0 :                                 return MUSTACH_ERROR_EMPTY_TAG;
     356           14 :                         if (len > MUSTACH_MAX_LENGTH)
     357            0 :                                 return MUSTACH_ERROR_TAG_TOO_LONG;
     358           14 :                         memcpy(name, beg, len);
     359           14 :                         name[len] = 0;
     360           14 :                         break;
     361              :                 }
     362           14 :                 if (stdalone)
     363            1 :                         stdalone = 2;
     364           13 :                 else if (enabled) {
     365           13 :                         rc = emitprefix(iwrap, &pref);
     366           13 :                         if (rc < 0)
     367            0 :                                 return rc;
     368           13 :                         pref.len = 0;
     369           13 :                         pref.prefix = NULL;
     370              :                 }
     371           14 :                 switch(c) {
     372            0 :                 case '!':
     373              :                         /* comment */
     374              :                         /* nothing to do */
     375            0 :                         break;
     376            0 :                 case '=':
     377              :                         /* defines delimiters */
     378            0 :                         if (len < 5 || beg[len - 1] != '=')
     379            0 :                                 return MUSTACH_ERROR_BAD_SEPARATORS;
     380            0 :                         beg++;
     381            0 :                         len -= 2;
     382            0 :                         while (len && isspace(*beg))
     383            0 :                                 beg++, len--;
     384            0 :                         while (len && isspace(beg[len - 1]))
     385            0 :                                 len--;
     386            0 :                         for (l = 0; l < len && !isspace(beg[l]) ; l++);
     387            0 :                         if (l == len || l > MUSTACH_MAX_DELIM_LENGTH)
     388            0 :                                 return MUSTACH_ERROR_BAD_SEPARATORS;
     389            0 :                         oplen = l;
     390            0 :                         memcpy(opstr, beg, l);
     391            0 :                         while (l < len && isspace(beg[l])) l++;
     392            0 :                         if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH)
     393            0 :                                 return MUSTACH_ERROR_BAD_SEPARATORS;
     394            0 :                         cllen = len - l;
     395            0 :                         memcpy(clstr, beg + l, cllen);
     396            0 :                         break;
     397            3 :                 case '^':
     398              :                 case '#':
     399              :                         /* begin section */
     400            3 :                         if (depth == MUSTACH_MAX_DEPTH)
     401            0 :                                 return MUSTACH_ERROR_TOO_DEEP;
     402            3 :                         rc = enabled;
     403            3 :                         if (rc) {
     404            3 :                                 rc = iwrap->enter(iwrap->closure, name);
     405            3 :                                 if (rc < 0)
     406            0 :                                         return rc;
     407              :                         }
     408            3 :                         stack[depth].name = beg;
     409            3 :                         stack[depth].again = template;
     410            3 :                         stack[depth].length = len;
     411            3 :                         stack[depth].enabled = enabled != 0;
     412            3 :                         stack[depth].entered = rc != 0;
     413            3 :                         if ((c == '#') == (rc == 0))
     414            0 :                                 enabled = 0;
     415            3 :                         depth++;
     416            3 :                         break;
     417            4 :                 case '/':
     418              :                         /* end section */
     419            4 :                         if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
     420            0 :                                 return MUSTACH_ERROR_CLOSING;
     421            4 :                         rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0;
     422            4 :                         if (rc < 0)
     423            0 :                                 return rc;
     424            4 :                         if (rc) {
     425            1 :                                 template = stack[depth++].again;
     426              :                         } else {
     427            3 :                                 enabled = stack[depth].enabled;
     428            3 :                                 if (enabled && stack[depth].entered)
     429            2 :                                         iwrap->leave(iwrap->closure);
     430              :                         }
     431            4 :                         break;
     432            0 :                 case '>':
     433              :                         /* partials */
     434            0 :                         if (enabled) {
     435            0 :                                 if (iwrap->nesting >= MUSTACH_MAX_NESTING)
     436            0 :                                         rc = MUSTACH_ERROR_TOO_MUCH_NESTING;
     437              :                                 else {
     438            0 :                                         sbuf_reset(&sbuf);
     439            0 :                                         rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
     440            0 :                                         if (rc >= 0) {
     441            0 :                                                 iwrap->nesting++;
     442            0 :                                                 rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, &pref);
     443            0 :                                                 sbuf_release(&sbuf);
     444            0 :                                                 iwrap->nesting--;
     445              :                                         }
     446              :                                 }
     447            0 :                                 if (rc < 0)
     448            0 :                                         return rc;
     449              :                         }
     450            0 :                         break;
     451            7 :                 default:
     452              :                         /* replacement */
     453            7 :                         if (enabled) {
     454            7 :                                 rc = iwrap->put(iwrap->closure_put, name, c != '&', iwrap->file);
     455            7 :                                 if (rc < 0)
     456            0 :                                         return rc;
     457              :                         }
     458            7 :                         break;
     459              :                 }
     460              :         }
     461              : }
     462              : 
     463            6 : int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file)
     464              : {
     465              :         int rc;
     466              :         struct iwrap iwrap;
     467              : 
     468              :         /* check validity */
     469            6 :         if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get))
     470            0 :                 return MUSTACH_ERROR_INVALID_ITF;
     471              : 
     472              :         /* init wrap structure */
     473            6 :         iwrap.closure = closure;
     474            6 :         if (itf->put) {
     475            0 :                 iwrap.put = itf->put;
     476            0 :                 iwrap.closure_put = closure;
     477              :         } else {
     478            6 :                 iwrap.put = iwrap_put;
     479            6 :                 iwrap.closure_put = &iwrap;
     480              :         }
     481            6 :         if (itf->partial) {
     482            6 :                 iwrap.partial = itf->partial;
     483            6 :                 iwrap.closure_partial = closure;
     484            0 :         } else if (itf->get) {
     485            0 :                 iwrap.partial = itf->get;
     486            0 :                 iwrap.closure_partial = closure;
     487              :         } else {
     488            0 :                 iwrap.partial = iwrap_partial;
     489            0 :                 iwrap.closure_partial = &iwrap;
     490              :         }
     491            6 :         iwrap.emit = itf->emit ? itf->emit : iwrap_emit;
     492            6 :         iwrap.enter = itf->enter;
     493            6 :         iwrap.next = itf->next;
     494            6 :         iwrap.leave = itf->leave;
     495            6 :         iwrap.get = itf->get;
     496            6 :         iwrap.file = file;
     497            6 :         iwrap.flags = flags;
     498            6 :         iwrap.nesting = 0;
     499              : 
     500              :         /* process */
     501            6 :         rc = itf->start ? itf->start(closure) : 0;
     502            6 :         if (rc == 0)
     503            6 :                 rc = process(template, length, &iwrap, NULL);
     504            6 :         if (itf->stop)
     505            6 :                 itf->stop(closure, rc);
     506            6 :         return rc;
     507              : }
     508              : 
     509            0 : int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd)
     510              : {
     511              :         int rc;
     512              :         FILE *file;
     513              : 
     514            0 :         file = fdopen(fd, "w");
     515            0 :         if (file == NULL) {
     516            0 :                 rc = MUSTACH_ERROR_SYSTEM;
     517            0 :                 errno = ENOMEM;
     518              :         } else {
     519            0 :                 rc = mustach_file(template, length, itf, closure, flags, file);
     520            0 :                 fclose(file);
     521              :         }
     522            0 :         return rc;
     523              : }
     524              : 
     525            6 : int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size)
     526              : {
     527              :         int rc;
     528              :         FILE *file;
     529              :         size_t s;
     530              : 
     531            6 :         *result = NULL;
     532            6 :         if (size == NULL)
     533            0 :                 size = &s;
     534            6 :         file = memfile_open(result, size);
     535            6 :         if (file == NULL)
     536            0 :                 rc = MUSTACH_ERROR_SYSTEM;
     537              :         else {
     538            6 :                 rc = mustach_file(template, length, itf, closure, flags, file);
     539            6 :                 if (rc < 0)
     540            0 :                         memfile_abort(file, result, size);
     541              :                 else
     542            6 :                         rc = memfile_close(file, result, size);
     543              :         }
     544            6 :         return rc;
     545              : }
     546              : 
     547            0 : int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)
     548              : {
     549            0 :         return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file);
     550              : }
     551              : 
     552            0 : int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)
     553              : {
     554            0 :         return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd);
     555              : }
     556              : 
     557            0 : int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)
     558              : {
     559            0 :         return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size);
     560              : }
     561              : 
        

Generated by: LCOV version 2.0-1