afm-unit: Fix http port multi allocation
[src/app-framework-main.git] / src / wgtpkg-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 <stdio.h>
23 #include <string.h>
24 #include <errno.h>
25
26 #include <json-c/json.h>
27
28 #include "mustach.h"
29 #include "verbose.h"
30
31 #define MAX_DEPTH 256
32
33 /*
34  * exploration state when instantiating mustache
35  */
36 struct expl {
37         struct json_object *root;
38         int depth;
39         struct {
40                 struct json_object *cont;
41                 struct json_object *obj;
42                 int index, count;
43         } stack[MAX_DEPTH];
44 };
45
46 /*
47  * Scan a key=val text.
48  * If the sign = is found, drop it and returns a pointer to the value.
49  * If the sign = is not here, returns NULL.
50  * Replace any \= of the key by its unescaped version =.
51  */
52 static char *keyval(char *read, int isptr)
53 {
54         char *write, c;
55
56         c = *(write = read);
57         while (c && c != '=') {
58                 if (isptr) {
59                         if (c == '~' && read[1] == '=') {
60                                 c = *++read;
61                         }
62                 } else {
63                         if (c == '\\') {
64                                 switch (read[1]) {
65                                 case '\\': *write++ = c; /*@fallthrough@*/
66                                 case '=': c = *++read; /*@fallthrough@*/
67                                 default: break;
68                                 }
69                         }
70                 }
71                 *write++ = c;
72                 c = *++read;
73         }
74         *write = 0;
75         return c == '=' ? ++read : NULL;
76 }
77
78 /*
79  * Returns the unescaped version of the first component
80  * and update 'name' to point the next components if any.
81  */
82 static char *first(char **name, int isptr)
83 {
84         char *r, *read, *write, c;
85
86         c = *(read = *name);
87         if (!c)
88                 r = NULL;
89         else {
90                 r = write = read;
91                 if (isptr) {
92                         while (c && c != '/') {
93                                 if (c == '~') {
94                                         switch(read[1]) {
95                                         case '1': c = '/'; /*@fallthrough@*/
96                                         case '0': read++; /*@fallthrough@*/
97                                         default: break;
98                                         }
99                                 }
100                                 *write++ = c;
101                                 c = *++read;
102                         }
103                 } else {
104                         while (c && c != '.') {
105                                 if (c == '\\' && (read[1] == '.' || read[1] == '\\'))
106                                         c = *++read;
107                                 *write++ = c;
108                                 c = *++read;
109                         }
110                 }
111                 *write = 0;
112                 *name = read + !!c;
113         }
114         return r;
115 }
116
117 /*
118  * Returns the unescaped version of the first value
119  * and update 'val' to point the next value if any.
120  */
121 static char *value(char **val)
122 {
123         char *r, *read, *write, c;
124
125         c = *(read = *val);
126         if (!c)
127                 r = NULL;
128         else {
129                 r = write = read;
130                 while (c && c != '|') {
131                         if (c == '\\' && (read[1] == '|' || read[1] == '\\'))
132                                 c = *++read;
133                         *write++ = c;
134                         c = *++read;
135                 }
136                 *write = 0;
137                 *val = read + !!c;
138         }
139         return r;
140 }
141
142 /*
143  * Replace the last occurence of ':' followed by
144  * any character not being '*' by ':*', the
145  * globalisation of the key.
146  * Returns NULL if no globalisation is done
147  * or else the key globalized.
148  */
149 static char *globalize(char *key)
150 {
151         char *f, *r;
152
153         f = NULL;
154         for (r = key; *r ; r++) {
155                 if (r[0] == ':' && r[1] && r[1] != '*')
156                         f = r;
157         }
158         if (f) {
159                 f[1] = '*';
160                 f[2] = 0;
161                 f = key;
162         }
163         return f;
164 }
165
166 /*
167  * find the object of 'name'
168  */
169 static struct json_object *find(struct expl *e, const char *name)
170 {
171         int i, isptr;
172         struct json_object *o, *r;
173         char *n, *c, *v;
174
175         /* get a local key */
176         n = strdupa(name);
177
178         /* is it a JSON pointer? */
179         isptr = n[0] == '/';
180         n += isptr;
181
182         /* extract its value */
183         v = keyval(n, isptr);
184
185         /* search the first component for each valid globalisation */
186         i = e->depth;
187         c = first(&n, isptr);
188         while (c) {
189                 if (i < 0) {
190                         /* next globalisation */
191                         i = e->depth;
192                         c = globalize(c);
193                 }
194                 else if (json_object_object_get_ex(e->stack[i].obj, c, &o)) {
195
196                         /* found the root, search the subcomponents */
197                         c = first(&n, isptr);
198                         while(c) {
199                                 while (!json_object_object_get_ex(o, c, &r)) {
200                                         c = globalize(c);
201                                         if (!c)
202                                                 return NULL;
203                                 }
204                                 o = r;
205                                 c = first(&n, isptr);
206                         }
207
208                         /* check the value if requested */
209                         if (v) {
210                                 i = v[0] == '!';
211                                 v += i;
212                                 do {
213                                         c = value(&v);
214                                 } while (c && strcmp(c, json_object_get_string(o)));
215                                 if (i != !c)
216                                         o = NULL;
217                         }
218                         return o;
219                 }
220                 i--;
221         }
222         return NULL;
223 }
224
225 static int start(void *closure)
226 {
227         struct expl *e = closure;
228         e->depth = 0;
229         e->stack[0].cont = NULL;
230         e->stack[0].obj = e->root;
231         e->stack[0].index = 0;
232         e->stack[0].count = 1;
233         return 0;
234 }
235
236 static void print(FILE *file, const char *string, int escape)
237 {
238         if (!escape)
239                 fputs(string, file);
240         else if (*string)
241                 do {
242                         switch(*string) {
243                         case '%': fputs("%%", file); break;
244                         case '\n': fputs("\\n\\\n", file); break;
245                         default: putc(*string, file); break;
246                         }
247                 } while(*++string);
248 }
249
250 static int put(void *closure, const char *name, int escape, FILE *file)
251 {
252         struct expl *e = closure;
253         struct json_object *o = find(e, name);
254         if (o)
255                 print(file, json_object_get_string(o), escape);
256         return 0;
257 }
258
259 static int enter(void *closure, const char *name)
260 {
261         struct expl *e = closure;
262         struct json_object *o = find(e, name);
263         if (++e->depth >= MAX_DEPTH)
264                 return MUSTACH_ERROR_TOO_DEPTH;
265         if (json_object_is_type(o, json_type_array)) {
266                 e->stack[e->depth].count = json_object_array_length(o);
267                 if (e->stack[e->depth].count == 0) {
268                         e->depth--;
269                         return 0;
270                 }
271                 e->stack[e->depth].cont = o;
272                 e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
273                 e->stack[e->depth].index = 0;
274         } else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) {
275                 e->stack[e->depth].count = 1;
276                 e->stack[e->depth].cont = NULL;
277                 e->stack[e->depth].obj = o;
278                 e->stack[e->depth].index = 0;
279         } else {
280                 e->depth--;
281                 return 0;
282         }
283         return 1;
284 }
285
286 static int next(void *closure)
287 {
288         struct expl *e = closure;
289         if (e->depth <= 0)
290                 return MUSTACH_ERROR_CLOSING;
291         e->stack[e->depth].index++;
292         if (e->stack[e->depth].index >= e->stack[e->depth].count)
293                 return 0;
294         e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
295         return 1;
296 }
297
298 static int leave(void *closure)
299 {
300         struct expl *e = closure;
301         if (e->depth <= 0)
302                 return MUSTACH_ERROR_CLOSING;
303         e->depth--;
304         return 0;
305 }
306
307 static struct mustach_itf itf = {
308         .start = start,
309         .put = put,
310         .enter = enter,
311         .next = next,
312         .leave = leave
313 };
314
315 /*
316  * Apply the object 'root' to the mustache 'template'.
317  * In case of success, the function returns 0, the pointer
318  * 'result' receives the allocated instanciation and
319  * the pointer 'size' its size. Note that the real size
320  * is one byte more to effectively store the terminating
321  * null.
322  * In case of error, it returns a negative error code.
323  */
324 int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size)
325 {
326         int rc;
327         struct expl e;
328
329         e.root = root;
330         rc = mustach(template, &itf, &e, result, size);
331         if (rc < 0) {
332                 static const char *msgs[] = {
333                         "SYSTEM",
334                         "UNEXPECTED_END",
335                         "EMPTY_TAG",
336                         "TAG_TOO_LONG",
337                         "BAD_SEPARATORS",
338                         "TOO_DEPTH",
339                         "CLOSING",
340                         "BAD_UNESCAPE_TAG"
341                 };
342
343                 rc = -(rc + 1);
344                 ERROR("mustach error found: MUSTACH_ERROR_%s",
345                         rc < 0 || rc >= (int)(sizeof msgs / sizeof * msgs) ? "???" : msgs[rc]);
346                 rc = -1;
347                 errno = EINVAL;
348         }
349         return rc;
350 }
351