Fix bug in mustach.c
[src/app-framework-main.git] / src / mustach.c
1 /*
2  Author: José Bollo <jobol@nonadev.net>
3  Author: José Bollo <jose.bollo@iot.bzh>
4
5  https://gitlab.com/jobol/mustach
6
7  Licensed under the Apache License, Version 2.0 (the "License");
8  you may not use this file except in compliance with the License.
9  You may obtain a copy of the License at
10
11      http://www.apache.org/licenses/LICENSE-2.0
12
13  Unless required by applicable law or agreed to in writing, software
14  distributed under the License is distributed on an "AS IS" BASIS,
15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  See the License for the specific language governing permissions and
17  limitations under the License.
18 */
19
20 #define _GNU_SOURCE
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <ctype.h>
27
28 #include "mustach.h"
29
30 #define NAME_LENGTH_MAX   1024
31 #define DEPTH_MAX         256
32
33 static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
34 {
35         int rc;
36         FILE *file;
37         size_t size;
38
39         *result = NULL;
40         file = open_memstream(result, &size);
41         if (file == NULL)
42                 rc = MUSTACH_ERROR_SYSTEM;
43         else {
44                 rc = itf->put(closure, name, 0, file);
45                 if (rc == 0)
46                         /* adds terminating null */
47                         rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
48                 fclose(file);
49                 if (rc < 0) {
50                         free(*result);
51                         *result = NULL;
52                 }
53         }
54         return rc;
55 }
56
57 static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
58 {
59         char name[NAME_LENGTH_MAX + 1], *partial, c;
60         const char *beg, *term;
61         struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
62         size_t oplen, cllen, len, l;
63         int depth, rc, emit;
64
65         emit = 1;
66         oplen = strlen(opstr);
67         cllen = strlen(clstr);
68         depth = 0;
69         for(;;) {
70                 beg = strstr(template, opstr);
71                 if (beg == NULL) {
72                         /* no more mustach */
73                         if (emit)
74                                 fwrite(template, strlen(template), 1, file);
75                         return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
76                 }
77                 if (emit)
78                         fwrite(template, (size_t)(beg - template), 1, file);
79                 term = strstr(template, clstr);
80                 if (term == NULL)
81                         return MUSTACH_ERROR_UNEXPECTED_END;
82                 template = term + cllen;
83                 beg += oplen;
84                 len = (size_t)(term - beg);
85                 c = *beg;
86                 switch(c) {
87                 case '!':
88                 case '=':
89                         break;
90                 case '{':
91                         for (l = 0 ; clstr[l] == '}' ; l++);
92                         if (clstr[l]) {
93                                 if (!len || beg[len-1] != '}')
94                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
95                                 len--;
96                         } else {
97                                 if (term[l] != '}')
98                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
99                                 template++;
100                         }
101                         c = '&';
102                 case '^':
103                 case '#':
104                 case '/':
105                 case '&':
106                 case '>':
107 #if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
108                 case ':':
109 #endif
110                         beg++; len--;
111                 default:
112                         while (len && isspace(beg[0])) { beg++; len--; }
113                         while (len && isspace(beg[len-1])) len--;
114                         if (len == 0)
115                                 return MUSTACH_ERROR_EMPTY_TAG;
116                         if (len > NAME_LENGTH_MAX)
117                                 return MUSTACH_ERROR_TAG_TOO_LONG;
118                         memcpy(name, beg, len);
119                         name[len] = 0;
120                         break;
121                 }
122                 switch(c) {
123                 case '!':
124                         /* comment */
125                         /* nothing to do */
126                         break;
127                 case '=':
128                         /* defines separators */
129                         if (len < 5 || beg[len - 1] != '=')
130                                 return MUSTACH_ERROR_BAD_SEPARATORS;
131                         beg++;
132                         len -= 2;
133                         for (l = 0; l < len && !isspace(beg[l]) ; l++);
134                         if (l == len)
135                                 return MUSTACH_ERROR_BAD_SEPARATORS;
136                         opstr = strndupa(beg, l);
137                         while (l < len && isspace(beg[l])) l++;
138                         if (l == len)
139                                 return MUSTACH_ERROR_BAD_SEPARATORS;
140                         clstr = strndupa(beg + l, len - l);
141                         oplen = strlen(opstr);
142                         cllen = strlen(clstr);
143                         break;
144                 case '^':
145                 case '#':
146                         /* begin section */
147                         if (depth == DEPTH_MAX)
148                                 return MUSTACH_ERROR_TOO_DEPTH;
149                         rc = emit;
150                         if (rc) {
151                                 rc = itf->enter(closure, name);
152                                 if (rc < 0)
153                                         return rc;
154                         }
155                         stack[depth].name = beg;
156                         stack[depth].again = template;
157                         stack[depth].length = len;
158                         stack[depth].emit = emit;
159                         stack[depth].entered = rc;
160                         if ((c == '#') == (rc == 0))
161                                 emit = 0;
162                         depth++;
163                         break;
164                 case '/':
165                         /* end section */
166                         if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
167                                 return MUSTACH_ERROR_CLOSING;
168                         rc = emit && stack[depth].entered ? itf->next(closure) : 0;
169                         if (rc < 0)
170                                 return rc;
171                         if (rc) {
172                                 template = stack[depth++].again;
173                         } else {
174                                 emit = stack[depth].emit;
175                                 if (emit && stack[depth].entered)
176                                         itf->leave(closure);
177                         }
178                         break;
179                 case '>':
180                         /* partials */
181                         if (emit) {
182                                 rc = getpartial(itf, closure, name, &partial);
183                                 if (rc == 0) {
184                                         rc = process(partial, itf, closure, file, opstr, clstr);
185                                         free(partial);
186                                 }
187                                 if (rc < 0)
188                                         return rc;
189                         }
190                         break;
191                 default:
192                         /* replacement */
193                         if (emit) {
194                                 rc = itf->put(closure, name, c != '&', file);
195                                 if (rc < 0)
196                                         return rc;
197                         }
198                         break;
199                 }
200         }
201 }
202
203 int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
204 {
205         int rc = itf->start ? itf->start(closure) : 0;
206         if (rc == 0)
207                 rc = process(template, itf, closure, file, "{{", "}}");
208         return rc;
209 }
210
211 int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
212 {
213         int rc;
214         FILE *file;
215
216         file = fdopen(fd, "w");
217         if (file == NULL) {
218                 rc = MUSTACH_ERROR_SYSTEM;
219                 errno = ENOMEM;
220         } else {
221                 rc = fmustach(template, itf, closure, file);
222                 fclose(file);
223         }
224         return rc;
225 }
226
227 int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
228 {
229         int rc;
230         FILE *file;
231         size_t s;
232
233         *result = NULL;
234         if (size == NULL)
235                 size = &s;
236         file = open_memstream(result, size);
237         if (file == NULL) {
238                 rc = MUSTACH_ERROR_SYSTEM;
239                 errno = ENOMEM;
240         } else {
241                 rc = fmustach(template, itf, closure, file);
242                 if (rc == 0)
243                         /* adds terminating null */
244                         rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
245                 fclose(file);
246                 if (rc >= 0)
247                         /* removes terminating null of the length */
248                         (*size)--;
249                 else {
250                         free(*result);
251                         *result = NULL;
252                         *size = 0;
253                 }
254         }
255         return rc;
256 }
257