LCOV - code coverage report
Current view: top level - templating - mustach-wrap.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 120 231 51.9 %
Date: 2025-06-05 21:03:14 Functions: 15 21 71.4 %

          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             : #ifdef _WIN32
      17             : #include <malloc.h>
      18             : #endif
      19             : 
      20             : #include "mustach.h"
      21             : #include "mustach-wrap.h"
      22             : 
      23             : /*
      24             : * It was stated that allowing to include files
      25             : * through template is not safe when the mustache
      26             : * template is open to any value because it could
      27             : * create leaks (example: {{>/etc/passwd}}).
      28             : */
      29             : #if MUSTACH_SAFE
      30             : # undef MUSTACH_LOAD_TEMPLATE
      31             : #elif !defined(MUSTACH_LOAD_TEMPLATE)
      32             : # define MUSTACH_LOAD_TEMPLATE 1
      33             : #endif
      34             : 
      35             : #if !defined(INCLUDE_PARTIAL_EXTENSION)
      36             : # define INCLUDE_PARTIAL_EXTENSION ".mustache"
      37             : #endif
      38             : 
      39             : /* global hook for partials */
      40             : int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL;
      41             : 
      42             : /* internal structure for wrapping */
      43             : struct wrap {
      44             :         /* original interface */
      45             :         const struct mustach_wrap_itf *itf;
      46             : 
      47             :         /* original closure */
      48             :         void *closure;
      49             : 
      50             :         /* flags */
      51             :         int flags;
      52             : 
      53             :         /* emiter callback */
      54             :         mustach_emit_cb_t *emitcb;
      55             : 
      56             :         /* write callback */
      57             :         mustach_write_cb_t *writecb;
      58             : };
      59             : 
      60             : /* length given by masking with 3 */
      61             : enum comp {
      62             :         C_no = 0,
      63             :         C_eq = 1,
      64             :         C_lt = 5,
      65             :         C_le = 6,
      66             :         C_gt = 9,
      67             :         C_ge = 10
      68             : };
      69             : 
      70             : enum sel {
      71             :         S_none = 0,
      72             :         S_ok = 1,
      73             :         S_objiter = 2,
      74             :         S_ok_or_objiter = S_ok | S_objiter
      75             : };
      76             : 
      77          27 : static enum comp getcomp(char *head, int sflags)
      78             : {
      79           0 :         return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq
      80          54 :                 : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt)
      81          27 :                 : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt)
      82             :                 : C_no;
      83             : }
      84             : 
      85          10 : static char *keyval(char *head, int sflags, enum comp *comp)
      86             : {
      87             :         char *w, car, escaped;
      88             :         enum comp k;
      89             : 
      90          10 :         k = C_no;
      91          10 :         w = head;
      92          10 :         car = *head;
      93          10 :         escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no);
      94          27 :         while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) {
      95          17 :                 if (escaped)
      96           0 :                         escaped = 0;
      97             :                 else
      98          17 :                         escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\')
      99          17 :                             && (getcomp(head + 1, sflags) != C_no);
     100          17 :                 if (!escaped)
     101          17 :                         *w++ = car;
     102          17 :                 head++;
     103          17 :                 car = *head;
     104             :         }
     105          10 :         *w = 0;
     106          10 :         *comp = k;
     107          10 :         return k == C_no ? NULL : &head[k & 3];
     108             : }
     109             : 
     110          16 : static char *getkey(char **head, int sflags)
     111             : {
     112             :         char *result, *iter, *write, car;
     113             : 
     114          16 :         car = *(iter = *head);
     115          16 :         if (!car)
     116           7 :                 result = NULL;
     117             :         else {
     118           9 :                 result = write = iter;
     119           9 :                 if (sflags & Mustach_With_JsonPointer)
     120             :                 {
     121           0 :                         while (car && car != '/') {
     122           0 :                                 if (car == '~')
     123           0 :                                         switch (iter[1]) {
     124           0 :                                         case '1': car = '/'; /*@fallthrough@*/
     125           0 :                                         case '0': iter++;
     126             :                                         }
     127           0 :                                 *write++ = car;
     128           0 :                                 car = *++iter;
     129             :                         }
     130           0 :                         *write = 0;
     131           0 :                         while (car == '/')
     132           0 :                                 car = *++iter;
     133             :                 }
     134             :                 else
     135             :                 {
     136          23 :                         while (car && car != '.') {
     137          14 :                                 if (car == '\\' && (iter[1] == '.' || iter[1] == '\\'))
     138           0 :                                         car = *++iter;
     139          14 :                                 *write++ = car;
     140          14 :                                 car = *++iter;
     141             :                         }
     142           9 :                         *write = 0;
     143          10 :                         while (car == '.')
     144           1 :                                 car = *++iter;
     145             :                 }
     146           9 :                 *head = iter;
     147             :         }
     148          16 :         return result;
     149             : }
     150             : 
     151          10 : static enum sel sel(struct wrap *w, const char *name)
     152          10 : {
     153             :         enum sel result;
     154             :         int i, j, sflags, scmp;
     155             :         char *key, *value;
     156             :         enum comp k;
     157             : 
     158             :         /* make a local writeable copy */
     159          10 :         size_t lenname = 1 + strlen(name);
     160          10 :         char buffer[lenname];
     161          10 :         char *copy = buffer;
     162          10 :         memcpy(copy, name, lenname);
     163             : 
     164             :         /* check if matches json pointer selection */
     165          10 :         sflags = w->flags;
     166          10 :         if (sflags & Mustach_With_JsonPointer) {
     167          10 :                 if (copy[0] == '/')
     168           0 :                         copy++;
     169             :                 else
     170          10 :                         sflags ^= Mustach_With_JsonPointer;
     171             :         }
     172             : 
     173             :         /* extract the value, translate the key and get the comparator */
     174          10 :         if (sflags & (Mustach_With_Equal | Mustach_With_Compare))
     175          10 :                 value = keyval(copy, sflags, &k);
     176             :         else {
     177           0 :                 k = C_no;
     178           0 :                 value = NULL;
     179             :         }
     180             : 
     181             :         /* case of . alone if Mustach_With_SingleDot? */
     182          10 :         if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/)
     183             :                 /* yes, select current */
     184           2 :                 result = w->itf->sel(w->closure, NULL) ? S_ok : S_none;
     185             :         else
     186             :         {
     187             :                 /* not the single dot, extract the first key */
     188           8 :                 key = getkey(&copy, sflags);
     189           8 :                 if (key == NULL)
     190           0 :                         return 0;
     191             : 
     192             :                 /* select the root item */
     193           8 :                 if (w->itf->sel(w->closure, key))
     194           7 :                         result = S_ok;
     195           1 :                 else if (key[0] == '*'
     196           0 :                       && !key[1]
     197           0 :                       && !value
     198           0 :                       && !*copy
     199           0 :                       && (w->flags & Mustach_With_ObjectIter)
     200           0 :                       && w->itf->sel(w->closure, NULL))
     201           0 :                         result = S_ok_or_objiter;
     202             :                 else
     203           1 :                         result = S_none;
     204           8 :                 if (result == S_ok) {
     205             :                         /* iterate the selection of sub items */
     206           7 :                         key = getkey(&copy, sflags);
     207           8 :                         while(result == S_ok && key) {
     208           1 :                                 if (w->itf->subsel(w->closure, key))
     209             :                                         /* nothing */;
     210           0 :                                 else if (key[0] == '*'
     211           0 :                                       && !key[1]
     212           0 :                                       && !value
     213           0 :                                       && !*copy
     214           0 :                                       && (w->flags & Mustach_With_ObjectIter))
     215           0 :                                         result = S_objiter;
     216             :                                 else
     217           0 :                                         result = S_none;
     218           1 :                                 key = getkey(&copy, sflags);
     219             :                         }
     220             :                 }
     221             :         }
     222             :         /* should it be compared? */
     223          10 :         if (result == S_ok && value) {
     224           0 :                 if (!w->itf->compare)
     225           0 :                         result = S_none;
     226             :                 else {
     227           0 :                         i = value[0] == '!';
     228           0 :                         scmp = w->itf->compare(w->closure, &value[i]);
     229           0 :                         switch (k) {
     230           0 :                         case C_eq: j = scmp == 0; break;
     231           0 :                         case C_lt: j = scmp < 0; break;
     232           0 :                         case C_le: j = scmp <= 0; break;
     233           0 :                         case C_gt: j = scmp > 0; break;
     234           0 :                         case C_ge: j = scmp >= 0; break;
     235           0 :                         default: j = i; break;
     236             :                         }
     237           0 :                         if (i == j)
     238           0 :                                 result = S_none;
     239             :                 }
     240             :         }
     241          10 :         return result;
     242             : }
     243             : 
     244           6 : static int start_callback(void *closure)
     245             : {
     246           6 :         struct wrap *w = closure;
     247           6 :         return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK;
     248             : }
     249             : 
     250           6 : static void stop_callback(void *closure, int status)
     251             : {
     252           6 :         struct wrap *w = closure;
     253           6 :         if (w->itf->stop)
     254           0 :                 w->itf->stop(w->closure, status);
     255           6 : }
     256             : 
     257          18 : static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file)
     258             : {
     259             :         int r;
     260             : 
     261          18 :         if (w->writecb)
     262           0 :                 r = w->writecb(file, buffer, size);
     263             :         else
     264          18 :                 r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM;
     265          18 :         return r;
     266             : }
     267             : 
     268          18 : static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file)
     269             : {
     270          18 :         struct wrap *w = closure;
     271             :         int r;
     272             :         size_t s, i;
     273             :         char car;
     274             : 
     275          18 :         if (w->emitcb)
     276           0 :                 r = w->emitcb(file, buffer, size, escape);
     277          18 :         else if (!escape)
     278          12 :                 r = emit(w, buffer, size, file);
     279             :         else {
     280           6 :                 i = 0;
     281           6 :                 r = MUSTACH_OK;
     282          12 :                 while(i < size && r == MUSTACH_OK) {
     283           6 :                         s = i;
     284          27 :                         while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"')
     285          21 :                                 i++;
     286           6 :                         if (i != s)
     287           6 :                                 r = emit(w, &buffer[s], i - s, file);
     288           6 :                         if (i < size && r == MUSTACH_OK) {
     289           0 :                                 switch(car) {
     290           0 :                                 case '<': r = emit(w, "&lt;", 4, file); break;
     291           0 :                                 case '>': r = emit(w, "&gt;", 4, file); break;
     292           0 :                                 case '&': r = emit(w, "&amp;", 5, file); break;
     293           0 :                                 case '"': r = emit(w, "&quot;", 6, file); break;
     294             :                                 }
     295           0 :                                 i++;
     296             :                         }
     297             :                 }
     298             :         }
     299          18 :         return r;
     300             : }
     301             : 
     302           3 : static int enter_callback(void *closure, const char *name)
     303             : {
     304           3 :         struct wrap *w = closure;
     305           3 :         enum sel s = sel(w, name);
     306           3 :         return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter);
     307             : }
     308             : 
     309           3 : static int next_callback(void *closure)
     310             : {
     311           3 :         struct wrap *w = closure;
     312           3 :         return w->itf->next(w->closure);
     313             : }
     314             : 
     315           2 : static int leave_callback(void *closure)
     316             : {
     317           2 :         struct wrap *w = closure;
     318           2 :         return w->itf->leave(w->closure);
     319             : }
     320             : 
     321           7 : static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf)
     322             : {
     323           7 :         enum sel s = sel(w, name);
     324           7 :         if (!(s & S_ok))
     325           1 :                 return 0;
     326           6 :         return w->itf->get(w->closure, sbuf, s & S_objiter);
     327             : }
     328             : 
     329           7 : static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
     330             : {
     331           7 :         struct wrap *w = closure;
     332           7 :         if (getoptional(w, name, sbuf) <= 0) {
     333           1 :                 if (w->flags & Mustach_With_ErrorUndefined)
     334           0 :                         return MUSTACH_ERROR_UNDEFINED_TAG;
     335           1 :                 sbuf->value = "";
     336             :         }
     337           7 :         return MUSTACH_OK;
     338             : }
     339             : 
     340             : #if MUSTACH_LOAD_TEMPLATE
     341           0 : static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf)
     342             : {
     343             :         static char extension[] = INCLUDE_PARTIAL_EXTENSION;
     344             :         size_t s;
     345             :         long pos;
     346             :         FILE *file;
     347             :         char *path, *buffer;
     348             : 
     349             :         /* allocate path */
     350           0 :         s = strlen(name);
     351           0 :         path = malloc(s + sizeof extension);
     352           0 :         if (path == NULL)
     353           0 :                 return MUSTACH_ERROR_SYSTEM;
     354             : 
     355             :         /* try without extension first */
     356           0 :         memcpy(path, name, s + 1);
     357           0 :         file = fopen(path, "r");
     358           0 :         if (file == NULL) {
     359           0 :                 memcpy(&path[s], extension, sizeof extension);
     360           0 :                 file = fopen(path, "r");
     361             :         }
     362           0 :         free(path);
     363             : 
     364             :         /* if file opened */
     365           0 :         if (file == NULL)
     366           0 :                 return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
     367             : 
     368             :         /* compute file size */
     369           0 :         if (fseek(file, 0, SEEK_END) >= 0
     370           0 :          && (pos = ftell(file)) >= 0
     371           0 :          && fseek(file, 0, SEEK_SET) >= 0) {
     372             :                 /* allocate value */
     373           0 :                 s = (size_t)pos;
     374           0 :                 buffer = malloc(s + 1);
     375           0 :                 if (buffer != NULL) {
     376             :                         /* read value */
     377           0 :                         if (1 == fread(buffer, s, 1, file)) {
     378             :                                 /* force zero at end */
     379           0 :                                 sbuf->value = buffer;
     380           0 :                                 buffer[s] = 0;
     381           0 :                                 sbuf->freecb = free;
     382           0 :                                 fclose(file);
     383           0 :                                 return MUSTACH_OK;
     384             :                         }
     385           0 :                         free(buffer);
     386             :                 }
     387             :         }
     388           0 :         fclose(file);
     389           0 :         return MUSTACH_ERROR_SYSTEM;
     390             : }
     391             : #endif
     392             : 
     393           0 : static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
     394             : {
     395           0 :         struct wrap *w = closure;
     396             :         int rc;
     397           0 :         if (mustach_wrap_get_partial != NULL) {
     398           0 :                 rc = mustach_wrap_get_partial(name, sbuf);
     399           0 :                 if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) {
     400           0 :                         if (rc != MUSTACH_OK)
     401           0 :                                 sbuf->value = "";
     402           0 :                         return rc;
     403             :                 }
     404             :         }
     405             : #if MUSTACH_LOAD_TEMPLATE
     406           0 :         if (w->flags & Mustach_With_PartialDataFirst) {
     407           0 :                 if (getoptional(w, name, sbuf) > 0)
     408           0 :                         rc = MUSTACH_OK;
     409             :                 else
     410           0 :                         rc = get_partial_from_file(name, sbuf);
     411             :         }
     412             :         else {
     413           0 :                 rc = get_partial_from_file(name, sbuf);
     414           0 :                 if (rc != MUSTACH_OK &&  getoptional(w, name, sbuf) > 0)
     415           0 :                         rc = MUSTACH_OK;
     416             :         }
     417             : #else
     418             :         rc = getoptional(w, name, sbuf) > 0 ?  MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND;
     419             : #endif
     420           0 :         if (rc != MUSTACH_OK)
     421           0 :                 sbuf->value = "";
     422           0 :         return MUSTACH_OK;
     423             : }
     424             : 
     425             : const struct mustach_itf mustach_wrap_itf = {
     426             :         .start = start_callback,
     427             :         .put = NULL,
     428             :         .enter = enter_callback,
     429             :         .next = next_callback,
     430             :         .leave = leave_callback,
     431             :         .partial = partial_callback,
     432             :         .get = get_callback,
     433             :         .emit = emit_callback,
     434             :         .stop = stop_callback
     435             : };
     436             : 
     437           6 : 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)
     438             : {
     439           6 :         if (flags & Mustach_With_Compare)
     440           6 :                 flags |= Mustach_With_Equal;
     441           6 :         wrap->closure = closure;
     442           6 :         wrap->itf = itf;
     443           6 :         wrap->flags = flags;
     444           6 :         wrap->emitcb = emitcb;
     445           6 :         wrap->writecb = writecb;
     446           6 : }
     447             : 
     448           0 : int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file)
     449             : {
     450             :         struct wrap w;
     451           0 :         wrap_init(&w, itf, closure, flags, NULL, NULL);
     452           0 :         return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file);
     453             : }
     454             : 
     455           0 : int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd)
     456             : {
     457             :         struct wrap w;
     458           0 :         wrap_init(&w, itf, closure, flags, NULL, NULL);
     459           0 :         return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd);
     460             : }
     461             : 
     462           6 : 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)
     463             : {
     464             :         struct wrap w;
     465           6 :         wrap_init(&w, itf, closure, flags, NULL, NULL);
     466           6 :         return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size);
     467             : }
     468             : 
     469           0 : 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)
     470             : {
     471             :         struct wrap w;
     472           0 :         wrap_init(&w, itf, closure, flags, NULL, writecb);
     473           0 :         return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure);
     474             : }
     475             : 
     476           0 : 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)
     477             : {
     478             :         struct wrap w;
     479           0 :         wrap_init(&w, itf, closure, flags, emitcb, NULL);
     480           0 :         return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure);
     481             : }
     482             : 

Generated by: LCOV version 1.16