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

          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 1.16