1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <platform.h>
26 #include <mustache.h>
27 
28 #include <string_lib.h>
29 #include <logging.h>
30 #include <alloc.h>
31 #include <sequence.h>
32 
33 typedef enum
34 {
35     TAG_TYPE_VAR,
36     TAG_TYPE_VAR_UNESCAPED,
37     TAG_TYPE_VAR_SERIALIZED,
38     TAG_TYPE_VAR_SERIALIZED_COMPACT,
39     TAG_TYPE_SECTION,
40     TAG_TYPE_SECTION_END,
41     TAG_TYPE_INVERTED,
42     TAG_TYPE_COMMENT,
43     TAG_TYPE_DELIM,
44     TAG_TYPE_ERR,
45     TAG_TYPE_NONE
46 } TagType;
47 
48 typedef struct
49 {
50     TagType type;
51     const char *begin;
52     const char *end;
53     const char *content;
54     size_t content_len;
55 } Mustache;
56 
57 #define MUSTACHE_MAX_DELIM_SIZE 10
58 
IsSpace(char c)59 static bool IsSpace(char c)
60 {
61     return c == '\t' || c == ' ';
62 }
63 
IsTagStandalone(const char * start,const char * tag_start,const char * tag_end,const char ** line_begin,const char ** line_end)64 static bool IsTagStandalone(const char *start, const char *tag_start, const char *tag_end, const char **line_begin, const char **line_end)
65 {
66     assert(start != NULL);
67     assert(tag_start != NULL);
68     assert(tag_end != NULL);
69     assert(line_begin != NULL);
70     assert(line_end != NULL);
71 
72     assert(start <= tag_start);
73 
74     *line_begin = start;
75     for (const char *cur = tag_start - 1; cur >= start; cur--)
76     {
77         if (IsSpace(*cur))
78         {
79             *line_begin = cur;
80             if (cur == start)
81             {
82                 break;
83             }
84             continue;
85         }
86         else if (*cur == '\n')
87         {
88             *line_begin = cur + 1;
89             break;
90         }
91         else
92         {
93             return false;
94         }
95     }
96 
97     *line_end = NULL;
98     for (const char *cur = tag_end; true; cur++)
99     {
100         if (IsSpace(*cur))
101         {
102             continue;
103         }
104         else if (*cur == '\n')
105         {
106             *line_end = cur + 1;
107             break;
108         }
109         else if (*cur == '\r')
110         {
111             if (*(cur + 1) == '\n')
112             {
113                 *line_end = cur + 2;
114                 break;
115             }
116             continue;
117         }
118         else if (*cur == '\0')
119         {
120             *line_end = cur;
121             break;
122         }
123         else
124         {
125             return false;
126         }
127     }
128 
129     assert(*line_end);
130 
131     return true;
132 }
133 
IsTagTypeRenderable(TagType type)134 static bool IsTagTypeRenderable(TagType type)
135 {
136     switch (type)
137     {
138     case TAG_TYPE_COMMENT:
139     case TAG_TYPE_DELIM:
140     case TAG_TYPE_ERR:
141     case TAG_TYPE_INVERTED:
142     case TAG_TYPE_SECTION:
143     case TAG_TYPE_SECTION_END:
144         return false;
145     default:
146         return true;
147     }
148 }
149 
LookupVariable(Seq * hash_stack,const char * name,size_t name_len)150 static JsonElement *LookupVariable(Seq *hash_stack, const char *name, size_t name_len)
151 {
152     assert(SeqLength(hash_stack) > 0);
153 
154     size_t num_comps = StringCountTokens(name, name_len, ".");
155 
156     JsonElement *base_var = NULL;
157     {
158         StringRef base_comp = StringGetToken(name, name_len, 0, ".");
159         char *base_comp_str = xstrndup(base_comp.data, base_comp.len);
160 
161         if (strcmp("-top-", base_comp_str) == 0)
162         {
163             base_var = SeqAt(hash_stack, 0);
164         }
165 
166         for (ssize_t i = SeqLength(hash_stack) - 1; i >= 0; i--)
167         {
168             JsonElement *hash = SeqAt(hash_stack, i);
169             if (!hash)
170             {
171                 continue;
172             }
173 
174             if (JsonGetType(hash) == JSON_TYPE_OBJECT)
175             {
176                 JsonElement *var = JsonObjectGet(hash, base_comp_str);
177                 if (var)
178                 {
179                     base_var = var;
180                     break;
181                 }
182             }
183         }
184         free(base_comp_str);
185     }
186 
187     if (!base_var)
188     {
189         return NULL;
190     }
191 
192     for (size_t i = 1; i < num_comps; i++)
193     {
194         if (JsonGetType(base_var) != JSON_TYPE_OBJECT)
195         {
196             return NULL;
197         }
198 
199         StringRef comp = StringGetToken(name, name_len, i, ".");
200         char *comp_str = xstrndup(comp.data, comp.len);
201         base_var = JsonObjectGet(base_var, comp_str);
202         free(comp_str);
203 
204         if (!base_var)
205         {
206             return NULL;
207         }
208     }
209 
210     assert(base_var);
211     return base_var;
212 }
213 
NextTag(const char * input,const char * delim_start,size_t delim_start_len,const char * delim_end,size_t delim_end_len)214 static Mustache NextTag(const char *input,
215                         const char *delim_start, size_t delim_start_len,
216                         const char *delim_end, size_t delim_end_len)
217 {
218     Mustache ret = {0};
219     ret.type = TAG_TYPE_NONE;
220 
221     ret.begin = strstr(input, delim_start);
222     if (!ret.begin)
223     {
224         return ret;
225     }
226 
227     ret.content = ret.begin + delim_start_len;
228     char *extra_end = NULL;
229 
230     switch (ret.content[0])
231     {
232     case '#':
233         ret.type = TAG_TYPE_SECTION;
234         ret.content++;
235         break;
236     case '^':
237         ret.type = TAG_TYPE_INVERTED;
238         ret.content++;
239         break;
240     case '/':
241         ret.type = TAG_TYPE_SECTION_END;
242         ret.content++;
243         break;
244     case '!':
245         ret.type = TAG_TYPE_COMMENT;
246         ret.content++;
247         break;
248     case '=':
249         extra_end = "=";
250         ret.type = TAG_TYPE_DELIM;
251         ret.content++;
252         break;
253     case '{':
254         extra_end = "}";
255         // fall through
256     case '&':
257         ret.type = TAG_TYPE_VAR_UNESCAPED;
258         ret.content++;
259         break;
260     case '%':
261         ret.type = TAG_TYPE_VAR_SERIALIZED;
262         ret.content++;
263         break;
264     case '$':
265         ret.type = TAG_TYPE_VAR_SERIALIZED_COMPACT;
266         ret.content++;
267         break;
268     default:
269         ret.type = TAG_TYPE_VAR;
270         break;
271     }
272 
273     if (extra_end)
274     {
275         const char *escape_end = strstr(ret.content, extra_end);
276         if (!escape_end || strncmp(escape_end + 1, delim_end, delim_end_len) != 0)
277         {
278             Log(LOG_LEVEL_ERR, "Broken mustache template, couldn't find end tag for quoted begin tag at '%20s'...", input);
279             ret.type = TAG_TYPE_ERR;
280             return ret;
281         }
282 
283         ret.content_len = escape_end - ret.content;
284         ret.end = escape_end + 1 + delim_end_len;
285     }
286     else
287     {
288         ret.end = strstr(ret.content, delim_end);
289         if (!ret.end)
290         {
291             Log(LOG_LEVEL_ERR, "Broken Mustache template, could not find end delimiter after reading start delimiter at '%20s'...", input);
292             ret.type = TAG_TYPE_ERR;
293             return ret;
294         }
295 
296         ret.content_len = ret.end - ret.content;
297         ret.end += delim_end_len;
298     }
299 
300     while (*ret.content == ' ' || *ret.content == '\t')
301     {
302         ret.content++;
303         ret.content_len--;
304     }
305 
306     while (ret.content_len > 0
307            && (ret.content[ret.content_len - 1] == ' '
308                || ret.content[ret.content_len - 1] == '\t'))
309     {
310         ret.content_len--;
311     }
312 
313     return ret;
314 }
315 
RenderHTMLContent(Buffer * out,const char * input,size_t len)316 static void RenderHTMLContent(Buffer *out, const char *input, size_t len)
317 {
318     for (size_t i = 0; i < len; i++)
319     {
320         switch (input[i])
321         {
322         case '&':
323             BufferAppendString(out, "&amp;");
324             break;
325 
326         case '"':
327             BufferAppendString(out, "&quot;");
328             break;
329 
330         case '<':
331             BufferAppendString(out, "&lt;");
332             break;
333 
334         case '>':
335             BufferAppendString(out, "&gt;");
336             break;
337 
338         default:
339             BufferAppendChar(out, input[i]);
340             break;
341         }
342     }
343 }
344 
RenderContent(Buffer * out,const char * content,size_t len,bool html,bool skip_content)345 static void RenderContent(Buffer *out, const char *content, size_t len, bool html, bool skip_content)
346 {
347     if (skip_content)
348     {
349         return;
350     }
351 
352     if (html)
353     {
354         RenderHTMLContent(out, content, len);
355     }
356     else
357     {
358         BufferAppend(out, content, len);
359     }
360 }
361 
RenderVariablePrimitive(Buffer * out,const JsonElement * primitive,const bool escaped,const char * json_key)362 static bool RenderVariablePrimitive(Buffer *out, const JsonElement *primitive, const bool escaped, const char* json_key)
363 {
364     if (json_key != NULL)
365     {
366         if (escaped)
367         {
368             RenderHTMLContent(out, json_key, strlen(json_key));
369         }
370         else
371         {
372             BufferAppendString(out, json_key);
373         }
374         return true;
375     }
376 
377     switch (JsonGetPrimitiveType(primitive))
378     {
379     case JSON_PRIMITIVE_TYPE_STRING:
380         if (escaped)
381         {
382             RenderHTMLContent(out, JsonPrimitiveGetAsString(primitive), strlen(JsonPrimitiveGetAsString(primitive)));
383         }
384         else
385         {
386             BufferAppendString(out, JsonPrimitiveGetAsString(primitive));
387         }
388         return true;
389 
390     case JSON_PRIMITIVE_TYPE_INTEGER:
391         {
392             char *str = StringFromLong(JsonPrimitiveGetAsInteger(primitive));
393             BufferAppendString(out, str);
394             free(str);
395         }
396         return true;
397 
398     case JSON_PRIMITIVE_TYPE_REAL:
399         {
400             char *str = StringFromDouble(JsonPrimitiveGetAsReal(primitive));
401             BufferAppendString(out, str);
402             free(str);
403         }
404         return true;
405 
406     case JSON_PRIMITIVE_TYPE_BOOL:
407         BufferAppendString(out, JsonPrimitiveGetAsBool(primitive) ? "true" : "false");
408         return true;
409 
410     case JSON_PRIMITIVE_TYPE_NULL:
411         return true;
412 
413     default:
414         assert(!"Unrecognised JSON primitive type");
415     }
416     return false;
417 }
418 
RenderVariableContainer(Buffer * out,const JsonElement * container,bool compact)419 static bool RenderVariableContainer(Buffer *out, const JsonElement *container, bool compact)
420 {
421     Writer *w = StringWriter();
422     if (compact)
423     {
424         JsonWriteCompact(w, container);
425     }
426     else
427     {
428         JsonWrite(w, container, 0);
429     }
430 
431     BufferAppendString(out, StringWriterData(w));
432     WriterClose(w);
433     return true;
434 }
435 
RenderVariable(Buffer * out,const char * content,size_t content_len,TagType conversion,Seq * hash_stack,const char * json_key)436 static bool RenderVariable(Buffer *out,
437                            const char *content, size_t content_len,
438                            TagType conversion,
439                            Seq *hash_stack,
440                            const char *json_key)
441 {
442     JsonElement *var = NULL;
443     bool escape = conversion == TAG_TYPE_VAR;
444     bool serialize = conversion == TAG_TYPE_VAR_SERIALIZED;
445     bool serialize_compact = conversion == TAG_TYPE_VAR_SERIALIZED_COMPACT;
446 
447     const bool item_mode = strncmp(content, ".", content_len) == 0;
448     const bool key_mode = strncmp(content, "@", content_len) == 0;
449 
450     if (item_mode || key_mode)
451     {
452         var = SeqAt(hash_stack, SeqLength(hash_stack) - 1);
453 
454         // Leave this in, it's really useful when debugging here but useless otherwise
455         // for (int i=1; i < SeqLength(hash_stack); i++)
456         // {
457         //     JsonElement *dump = SeqAt(hash_stack, i);
458         //     Writer *w = StringWriter();
459         //     JsonWrite(w, dump, 0);
460         //     Log(LOG_LEVEL_ERR, "RenderVariable: at hash_stack position %d, we found var '%s'", i, StringWriterClose(w));
461         // }
462     }
463     else
464     {
465         var = LookupVariable(hash_stack, content, content_len);
466     }
467 
468     if (key_mode && json_key == NULL)
469     {
470         Log(LOG_LEVEL_WARNING, "RenderVariable: {{@}} Mustache tag must be used in a context where there's a valid key or iteration position");
471         return false;
472     }
473 
474     if (!var)
475     {
476         return true;
477     }
478 
479     switch (JsonGetElementType(var))
480     {
481     case JSON_ELEMENT_TYPE_PRIMITIVE:
482         // note that this also covers 'serialize' on primitives
483         return RenderVariablePrimitive(out, var, escape, key_mode ? json_key : NULL);
484 
485     case JSON_ELEMENT_TYPE_CONTAINER:
486         if (serialize || serialize_compact)
487         {
488             return RenderVariableContainer(out, var, serialize_compact);
489         }
490         else if (key_mode)
491         {
492             // this will only use the JSON key property, which we know is good
493             // because we return false earlier otherwise
494             return RenderVariablePrimitive(out, var, escape, json_key);
495         }
496         else if (item_mode)
497         {
498             // This can happen in 2 cases:
499             // if you try to use {{.}} on the top element (without iterating)
500             // if you try to use {{.}} while iterating and the current element is a container
501             // In both cases we want to give an error(warning) since this is not clear in the
502             // mustache spec
503             Log(LOG_LEVEL_WARNING,
504                 "{{.}} mustache tag cannot expand a container without specifying conversion"
505                 " - consider {{$.}} or {{%%.}}");
506             return false;
507         }
508     }
509 
510     assert(false);
511     return false;
512 }
513 
SetDelimiters(const char * content,size_t content_len,char * delim_start,size_t * delim_start_len,char * delim_end,size_t * delim_end_len)514 static bool SetDelimiters(const char *content, size_t content_len,
515                           char *delim_start, size_t *delim_start_len,
516                           char *delim_end, size_t *delim_end_len)
517 {
518     size_t num_tokens = StringCountTokens(content, content_len, " \t");
519     if (num_tokens != 2)
520     {
521         Log(LOG_LEVEL_WARNING, "Could not parse delimiter mustache, number of tokens is %zu, expected 2 in '%s'",
522             num_tokens, content);
523         return false;
524     }
525 
526     StringRef first = StringGetToken(content, content_len, 0, " \t");
527     if (first.len > MUSTACHE_MAX_DELIM_SIZE)
528     {
529         Log(LOG_LEVEL_WARNING, "New mustache start delimiter exceeds the allowed size of %d in '%s'",
530             MUSTACHE_MAX_DELIM_SIZE, content);
531         return false;
532     }
533     strncpy(delim_start, first.data, first.len);
534     delim_start[first.len] = '\0';
535     *delim_start_len = first.len;
536 
537     StringRef second = StringGetToken(content, content_len, 1, " \t");
538     if (second.len > MUSTACHE_MAX_DELIM_SIZE)
539     {
540         Log(LOG_LEVEL_WARNING, "New mustache start delimiter exceeds the allowed size of %d in '%s'",
541             MUSTACHE_MAX_DELIM_SIZE, content);
542         return false;
543     }
544     strncpy(delim_end, second.data, second.len);
545     delim_end[second.len] = '\0';
546     *delim_end_len = second.len;
547 
548     return true;
549 }
550 
IsKeyExtensionVar(TagType tag_type,const char * tag_start,char * delim_start,size_t delim_start_len,char * delim_end,size_t delim_end_len)551 static bool IsKeyExtensionVar(TagType tag_type, const char *tag_start,
552                               char *delim_start, size_t delim_start_len,
553                               char *delim_end, size_t delim_end_len)
554 {
555     /* This whole function is ugly, but tries to avoid memory allocation and copying. */
556 
557     /* the easiest case first: {{@}} */
558     if (tag_type == TAG_TYPE_VAR)
559     {
560         if (StringStartsWith(tag_start, delim_start) &&
561             *(tag_start + delim_start_len) == '@' &&
562             StringStartsWith(tag_start + delim_start_len + 1, delim_end))
563         {
564             return true;
565         }
566         else
567         {
568             /* no other way a VAR could be {{@}} */
569             return false;
570         }
571     }
572 
573     if (tag_type == TAG_TYPE_VAR_UNESCAPED)
574     {
575         /* the case with unescaped form using &: {{&@}} */
576         if  (StringStartsWith(tag_start, delim_start) &&
577              StringStartsWith(tag_start + delim_start_len, "&@") &&
578              StringStartsWith(tag_start + delim_start_len + 2, delim_end))
579         {
580             return true;
581         }
582 
583         /* the special case of unescaped form using {{{@}}} iff "{{" and "}}" are
584          * used as delimiters */
585         if ((delim_start_len == 2) && (delim_end_len == 2) &&
586             StringEqual(delim_start, "{{") &&
587             StringEqual(delim_end, "}}") &&
588             StringStartsWith(tag_start, "{{{@}}}"))
589         {
590             return true;
591         }
592     }
593 
594     /* nothing else is our special '@' variable inside delimiters */
595     return false;
596 }
597 
598 /**
599  * Checks if a Json object in the current context (specified by #input) should
600  * be iterated over or not.
601  *
602  * We need to iterate over a Json object if the current section directly
603  * contains (not in a subsection) our {{@}} mustache extension.
604  */
ShouldIterateObject(const char * input,char * delim_start,size_t delim_start_len,char * delim_end,size_t delim_end_len)605 static bool ShouldIterateObject(const char *input,
606                                 char *delim_start, size_t delim_start_len,
607                                 char *delim_end, size_t delim_end_len)
608 {
609     assert (input != NULL);
610 
611     size_t loc_delim_start_len = delim_start_len;
612     char loc_delim_start[MUSTACHE_MAX_DELIM_SIZE] = {0};
613     strncpy(loc_delim_start, delim_start, delim_start_len);
614 
615     size_t loc_delim_end_len = delim_end_len;
616     char loc_delim_end[MUSTACHE_MAX_DELIM_SIZE] = {0};
617     strncpy(loc_delim_end, delim_end, delim_end_len);
618 
619     /* This loop should only terminate when the answer is clear and thus the
620      * whole function returns. It's iterating over the template which has to end
621      * somewhere (if it's not NUL-terminated, we have a much bigger issue than
622      * this loop). */
623     while (true)
624     {
625         Mustache tag = NextTag(input,
626                                loc_delim_start, loc_delim_start_len,
627                                loc_delim_end, loc_delim_end_len);
628         switch (tag.type)
629         {
630         case TAG_TYPE_ERR:
631         case TAG_TYPE_NONE:
632         case TAG_TYPE_SECTION:
633         case TAG_TYPE_SECTION_END:
634             /* these clearly mean there cannot be {{@}} directly in the current section */
635             return false;
636         case TAG_TYPE_VAR:
637         case TAG_TYPE_VAR_UNESCAPED:
638             /* check if the variable is {{@}} respecting possibly changed delimiters */
639             if (IsKeyExtensionVar(tag.type, tag.begin,
640                                   loc_delim_start, loc_delim_start_len,
641                                   loc_delim_end, loc_delim_end_len))
642             {
643                 return true;
644             }
645             else
646             {
647                 /* just continue to the next tag */
648                 break;
649             }
650         case TAG_TYPE_DELIM:
651             if (!SetDelimiters(tag.content, tag.content_len,
652                                loc_delim_start, &loc_delim_start_len,
653                                loc_delim_end, &loc_delim_end_len))
654             {
655                 return false;
656             }
657             break;
658 
659         default:
660             /* just continue to the next tag */
661             break;
662         }
663         /* move on in the template */
664         input = tag.end;
665     }
666     return false;
667 }
668 
Render(Buffer * out,const char * start,const char * input,Seq * hash_stack,const char * json_key,char * delim_start,size_t * delim_start_len,char * delim_end,size_t * delim_end_len,bool skip_content,const char * section,const char ** section_end)669 static bool Render(Buffer *out, const char *start, const char *input, Seq *hash_stack,
670                    const char *json_key,
671                    char *delim_start, size_t *delim_start_len,
672                    char *delim_end, size_t *delim_end_len,
673                    bool skip_content,
674                    const char *section,
675                    const char **section_end)
676 {
677     while (true)
678     {
679         if (!input)
680         {
681             Log(LOG_LEVEL_ERR, "Unexpected end to Mustache template");
682             return false;
683         }
684 
685         Mustache tag = NextTag(input, delim_start, *delim_start_len, delim_end, *delim_end_len);
686 
687         {
688             const char *line_begin = NULL;
689             const char *line_end = NULL;
690             if (!IsTagTypeRenderable(tag.type)
691                 && tag.end != NULL
692                 && IsTagStandalone(start, tag.begin, tag.end, &line_begin, &line_end))
693             {
694                 RenderContent(out, input, line_begin - input, false, skip_content);
695                 input = line_end;
696             }
697             else
698             {
699                 RenderContent(out, input, tag.begin - input, false, skip_content);
700                 input = tag.end;
701             }
702         }
703 
704         switch (tag.type)
705         {
706         case TAG_TYPE_ERR:
707             return false;
708 
709         case TAG_TYPE_DELIM:
710             if (!SetDelimiters(tag.content, tag.content_len,
711                                delim_start, delim_start_len,
712                                delim_end, delim_end_len))
713             {
714                 return false;
715             }
716             continue;
717 
718         case TAG_TYPE_COMMENT:
719             continue;
720 
721         case TAG_TYPE_NONE:
722             return true;
723 
724         case TAG_TYPE_VAR_SERIALIZED:
725         case TAG_TYPE_VAR_SERIALIZED_COMPACT:
726         case TAG_TYPE_VAR_UNESCAPED:
727         case TAG_TYPE_VAR:
728             if (!skip_content)
729             {
730                 if (tag.content_len > 0)
731                 {
732                     if (!RenderVariable(out, tag.content, tag.content_len, tag.type, hash_stack, json_key))
733                     {
734                         return false;
735                     }
736                 }
737                 else
738                 {
739                     RenderContent(out, delim_start, *delim_start_len, false, false);
740                     RenderContent(out, delim_end, *delim_end_len, false, false);
741                 }
742             }
743             continue;
744 
745         case TAG_TYPE_INVERTED:
746         case TAG_TYPE_SECTION:
747             {
748                 char *cur_section = xstrndup(tag.content, tag.content_len);
749                 JsonElement *var = LookupVariable(hash_stack, tag.content, tag.content_len);
750                 SeqAppend(hash_stack, var);
751 
752                 if (!var)
753                 {
754                     /* XXX: no variable with the name of the section, why are we renderning anything? */
755                     const char *cur_section_end = NULL;
756                     if (!Render(out, start, input, hash_stack, NULL, delim_start, delim_start_len, delim_end, delim_end_len,
757                                 skip_content || tag.type != TAG_TYPE_INVERTED, cur_section, &cur_section_end))
758                     {
759                         free(cur_section);
760                         return false;
761                     }
762                     free(cur_section);
763                     input = cur_section_end;
764                     continue;
765                 }
766 
767                 switch (JsonGetElementType(var))
768                 {
769                 case JSON_ELEMENT_TYPE_PRIMITIVE:
770                     if (JsonGetPrimitiveType(var) == JSON_PRIMITIVE_TYPE_BOOL)
771                     {
772                         bool skip = skip_content || (!JsonPrimitiveGetAsBool(var) ^ (tag.type == TAG_TYPE_INVERTED));
773 
774                         const char *cur_section_end = NULL;
775                         if (!Render(out, start, input, hash_stack, NULL, delim_start, delim_start_len, delim_end, delim_end_len,
776                                     skip, cur_section, &cur_section_end))
777                         {
778                             free(cur_section);
779                             return false;
780                         }
781                         free(cur_section);
782                         input = cur_section_end;
783                         continue;
784                     }
785                     else
786                     {
787                         Log(LOG_LEVEL_WARNING, "Mustache sections can only take a boolean or a container (array or map) value, but section '%s' isn't getting one of those.",
788                             cur_section);
789                         free(cur_section);
790                         return false;
791                     }
792                     break;
793 
794                 case JSON_ELEMENT_TYPE_CONTAINER:
795                     switch (JsonGetContainerType(var))
796                     {
797                     case JSON_CONTAINER_TYPE_OBJECT:
798                         {
799                             if (!ShouldIterateObject(input, delim_start, *delim_start_len, delim_end, *delim_end_len))
800                             {
801                                 const char *cur_section_end = NULL;
802 
803                                 if (!Render(out, start, input,
804                                             hash_stack,
805                                             NULL,
806                                             delim_start, delim_start_len, delim_end, delim_end_len,
807                                             skip_content || tag.type == TAG_TYPE_INVERTED, cur_section, &cur_section_end))
808                                 {
809                                     free(cur_section);
810                                     return false;
811                                 }
812                                 input = cur_section_end;
813                                 free(cur_section);
814                                 break;
815                             }
816                         }
817                         /* fall through */
818                         /* Because iterated objects and arrays are processed in the
819                          * same way */
820                     case JSON_CONTAINER_TYPE_ARRAY:
821                         if (JsonLength(var) > 0)
822                         {
823                             const char *cur_section_end = NULL;
824                             for (size_t i = 0; i < JsonLength(var); i++)
825                             {
826                                 JsonElement *child_hash = JsonAt(var, i);
827                                 SeqAppend(hash_stack, child_hash);
828 
829                                 Buffer *kstring = BufferNew();
830                                 if (JSON_CONTAINER_TYPE_OBJECT == JsonGetContainerType(var))
831                                 {
832                                     BufferAppendString(kstring, JsonElementGetPropertyName(child_hash));
833                                 }
834                                 else
835                                 {
836                                     BufferAppendF(kstring, "%zu", i);
837                                 }
838 
839                                 if (!Render(out, start, input,
840                                             hash_stack,
841                                             BufferData(kstring),
842                                             delim_start, delim_start_len, delim_end, delim_end_len,
843                                             skip_content || tag.type == TAG_TYPE_INVERTED, cur_section, &cur_section_end))
844                                 {
845                                     free(cur_section);
846                                     BufferDestroy(kstring);
847                                     return false;
848                                 }
849 
850                                 BufferDestroy(kstring);
851                             }
852                             input = cur_section_end;
853                             free(cur_section);
854                         }
855                         else
856                         {
857                             /* XXX: empty array -- why are we rendering anything? */
858                             const char *cur_section_end = NULL;
859                             if (!Render(out, start, input, hash_stack, NULL, delim_start, delim_start_len, delim_end, delim_end_len,
860                                         tag.type != TAG_TYPE_INVERTED, cur_section, &cur_section_end))
861                             {
862                                 free(cur_section);
863                                 return false;
864                             }
865                             free(cur_section);
866                             input = cur_section_end;
867                         }
868                         break;
869                     }
870                     break;
871                 }
872             }
873             continue;
874         case TAG_TYPE_SECTION_END:
875             if (!section)
876             {
877                 char *varname = xstrndup(tag.content, tag.content_len);
878                 Log(LOG_LEVEL_WARNING, "Unknown section close in mustache template '%s'", varname);
879                 free(varname);
880                 return false;
881             }
882             else
883             {
884                 SeqRemove(hash_stack, SeqLength(hash_stack) - 1);
885                 *section_end = input;
886                 return true;
887             }
888             break;
889 
890         default:
891             assert(false);
892             return false;
893         }
894     }
895 
896     assert(false);
897 }
898 
MustacheRender(Buffer * out,const char * input,const JsonElement * hash)899 bool MustacheRender(Buffer *out, const char *input, const JsonElement *hash)
900 {
901     char delim_start[MUSTACHE_MAX_DELIM_SIZE] = "{{";
902     size_t delim_start_len = strlen(delim_start);
903 
904     char delim_end[MUSTACHE_MAX_DELIM_SIZE] = "}}";
905     size_t delim_end_len = strlen(delim_end);
906 
907     Seq *hash_stack = SeqNew(10, NULL);
908     SeqAppend(hash_stack, (JsonElement*)hash);
909 
910     bool success = Render(out, input, input,
911                           hash_stack,
912                           NULL,
913                           delim_start, &delim_start_len,
914                           delim_end, &delim_end_len,
915                           false, NULL, NULL);
916 
917     SeqDestroy(hash_stack);
918 
919     return success;
920 }
921