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, "&");
324 break;
325
326 case '"':
327 BufferAppendString(out, """);
328 break;
329
330 case '<':
331 BufferAppendString(out, "<");
332 break;
333
334 case '>':
335 BufferAppendString(out, ">");
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