luci-base: Include Openwrt build revision in displayed version string
[project/luci.git] / modules / luci-base / src / template_parser.c
1 /*
2  * LuCI Template - Parser implementation
3  *
4  *   Copyright (C) 2009-2012 Jo-Philipp Wich <jow@openwrt.org>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18
19 #include "template_parser.h"
20 #include "template_utils.h"
21 #include "template_lmo.h"
22
23
24 /* leading and trailing code for different types */
25 const char *gen_code[9][2] = {
26         { NULL,                                 NULL                    },
27         { "write(\"",                   "\")"                   },
28         { NULL,                                 NULL                    },
29         { "write(tostring(",    " or \"\"))"    },
30         { "include(\"",                 "\")"                   },
31         { "write(\"",                   "\")"                   },
32         { "write(\"",                   "\")"                   },
33         { NULL,                                 " "                             },
34         { NULL,                                 NULL                    },
35 };
36
37 /* Simple strstr() like function that takes len arguments for both haystack and needle. */
38 static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
39 {
40         int match = 0;
41         int i, j;
42
43         for( i = 0; i < hslen; i++ )
44         {
45                 if( haystack[i] == needle[0] )
46                 {
47                         match = ((ndlen == 1) || ((i + ndlen) <= hslen));
48
49                         for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ )
50                         {
51                                 if( haystack[i+j] != needle[j] )
52                                 {
53                                         match = 0;
54                                         break;
55                                 }
56                         }
57
58                         if( match )
59                                 return &haystack[i];
60                 }
61         }
62
63         return NULL;
64 }
65
66 struct template_parser * template_open(const char *file)
67 {
68         struct stat s;
69         struct template_parser *parser;
70
71         if (!(parser = malloc(sizeof(*parser))))
72                 goto err;
73
74         memset(parser, 0, sizeof(*parser));
75         parser->fd = -1;
76         parser->file = file;
77
78         if (stat(file, &s))
79                 goto err;
80
81         if ((parser->fd = open(file, O_RDONLY)) < 0)
82                 goto err;
83
84         parser->size = s.st_size;
85         parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
86                                                 parser->fd, 0);
87
88         if (parser->data != MAP_FAILED)
89         {
90                 parser->off = parser->data;
91                 parser->cur_chunk.type = T_TYPE_INIT;
92                 parser->cur_chunk.s    = parser->data;
93                 parser->cur_chunk.e    = parser->data;
94
95                 return parser;
96         }
97
98 err:
99         template_close(parser);
100         return NULL;
101 }
102
103 struct template_parser * template_string(const char *str, uint32_t len)
104 {
105         struct template_parser *parser;
106
107         if (!str) {
108                 errno = EINVAL;
109                 goto err;
110         }
111
112         if (!(parser = malloc(sizeof(*parser))))
113                 goto err;
114
115         memset(parser, 0, sizeof(*parser));
116         parser->fd = -1;
117
118         parser->size = len;
119         parser->data = (char*)str;
120
121         parser->off = parser->data;
122         parser->cur_chunk.type = T_TYPE_INIT;
123         parser->cur_chunk.s    = parser->data;
124         parser->cur_chunk.e    = parser->data;
125
126         return parser;
127
128 err:
129         template_close(parser);
130         return NULL;
131 }
132
133 void template_close(struct template_parser *parser)
134 {
135         if (!parser)
136                 return;
137
138         if (parser->gc != NULL)
139                 free(parser->gc);
140
141         /* if file is not set, we were parsing a string */
142         if (parser->file) {
143                 if ((parser->data != NULL) && (parser->data != MAP_FAILED))
144                         munmap(parser->data, parser->size);
145
146                 if (parser->fd >= 0)
147                         close(parser->fd);
148         }
149
150         free(parser);
151 }
152
153 void template_text(struct template_parser *parser, const char *e)
154 {
155         const char *s = parser->off;
156
157         if (s < (parser->data + parser->size))
158         {
159                 if (parser->strip_after)
160                 {
161                         while ((s <= e) && isspace(*s))
162                                 s++;
163                 }
164
165                 parser->cur_chunk.type = T_TYPE_TEXT;
166         }
167         else
168         {
169                 parser->cur_chunk.type = T_TYPE_EOF;
170         }
171
172         parser->cur_chunk.line = parser->line;
173         parser->cur_chunk.s = s;
174         parser->cur_chunk.e = e;
175 }
176
177 void template_code(struct template_parser *parser, const char *e)
178 {
179         const char *s = parser->off;
180
181         parser->strip_before = 0;
182         parser->strip_after = 0;
183
184         if (*s == '-')
185         {
186                 parser->strip_before = 1;
187                 for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
188         }
189
190         if (*(e-1) == '-')
191         {
192                 parser->strip_after = 1;
193                 for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
194         }
195
196         switch (*s)
197         {
198                 /* comment */
199                 case '#':
200                         s++;
201                         parser->cur_chunk.type = T_TYPE_COMMENT;
202                         break;
203
204                 /* include */
205                 case '+':
206                         s++;
207                         parser->cur_chunk.type = T_TYPE_INCLUDE;
208                         break;
209
210                 /* translate */
211                 case ':':
212                         s++;
213                         parser->cur_chunk.type = T_TYPE_I18N;
214                         break;
215
216                 /* translate raw */
217                 case '_':
218                         s++;
219                         parser->cur_chunk.type = T_TYPE_I18N_RAW;
220                         break;
221
222                 /* expr */
223                 case '=':
224                         s++;
225                         parser->cur_chunk.type = T_TYPE_EXPR;
226                         break;
227
228                 /* code */
229                 default:
230                         parser->cur_chunk.type = T_TYPE_CODE;
231                         break;
232         }
233
234         parser->cur_chunk.line = parser->line;
235         parser->cur_chunk.s = s;
236         parser->cur_chunk.e = e;
237 }
238
239 static const char *
240 template_format_chunk(struct template_parser *parser, size_t *sz)
241 {
242         const char *s, *p;
243         const char *head, *tail;
244         struct template_chunk *c = &parser->prv_chunk;
245         struct template_buffer *buf;
246
247         *sz = 0;
248         s = parser->gc = NULL;
249
250         if (parser->strip_before && c->type == T_TYPE_TEXT)
251         {
252                 while ((c->e > c->s) && isspace(*(c->e - 1)))
253                         c->e--;
254         }
255
256         /* empty chunk */
257         if (c->s == c->e)
258         {
259                 if (c->type == T_TYPE_EOF)
260                 {
261                         *sz = 0;
262                         s = NULL;
263                 }
264                 else
265                 {
266                         *sz = 1;
267                         s = " ";
268                 }
269         }
270
271         /* format chunk */
272         else if ((buf = buf_init(c->e - c->s)) != NULL)
273         {
274                 if ((head = gen_code[c->type][0]) != NULL)
275                         buf_append(buf, head, strlen(head));
276
277                 switch (c->type)
278                 {
279                         case T_TYPE_TEXT:
280                                 luastr_escape(buf, c->s, c->e - c->s, 0);
281                                 break;
282
283                         case T_TYPE_EXPR:
284                                 buf_append(buf, c->s, c->e - c->s);
285                                 for (p = c->s; p < c->e; p++)
286                                         parser->line += (*p == '\n');
287                                 break;
288
289                         case T_TYPE_INCLUDE:
290                                 luastr_escape(buf, c->s, c->e - c->s, 0);
291                                 break;
292
293                         case T_TYPE_I18N:
294                                 luastr_translate(buf, c->s, c->e - c->s, 1);
295                                 break;
296
297                         case T_TYPE_I18N_RAW:
298                                 luastr_translate(buf, c->s, c->e - c->s, 0);
299                                 break;
300
301                         case T_TYPE_CODE:
302                                 buf_append(buf, c->s, c->e - c->s);
303                                 for (p = c->s; p < c->e; p++)
304                                         parser->line += (*p == '\n');
305                                 break;
306                 }
307
308                 if ((tail = gen_code[c->type][1]) != NULL)
309                         buf_append(buf, tail, strlen(tail));
310
311                 *sz = buf_length(buf);
312                 s = parser->gc = buf_destroy(buf);
313
314                 if (!*sz)
315                 {
316                         *sz = 1;
317                         s = " ";
318                 }
319         }
320
321         return s;
322 }
323
324 const char *template_reader(lua_State *L, void *ud, size_t *sz)
325 {
326         struct template_parser *parser = ud;
327         int rem = parser->size - (parser->off - parser->data);
328         char *tag;
329
330         parser->prv_chunk = parser->cur_chunk;
331
332         /* free previous string */
333         if (parser->gc)
334         {
335                 free(parser->gc);
336                 parser->gc = NULL;
337         }
338
339         /* before tag */
340         if (!parser->in_expr)
341         {
342                 if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
343                 {
344                         template_text(parser, tag);
345                         parser->off = tag + 2;
346                         parser->in_expr = 1;
347                 }
348                 else
349                 {
350                         template_text(parser, parser->data + parser->size);
351                         parser->off = parser->data + parser->size;
352                 }
353         }
354
355         /* inside tag */
356         else
357         {
358                 if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
359                 {
360                         template_code(parser, tag);
361                         parser->off = tag + 2;
362                         parser->in_expr = 0;
363                 }
364                 else
365                 {
366                         /* unexpected EOF */
367                         template_code(parser, parser->data + parser->size);
368
369                         *sz = 1;
370                         return "\033";
371                 }
372         }
373
374         return template_format_chunk(parser, sz);
375 }
376
377 int template_error(lua_State *L, struct template_parser *parser)
378 {
379         const char *err = luaL_checkstring(L, -1);
380         const char *off = parser->prv_chunk.s;
381         const char *ptr;
382         char msg[1024];
383         int line = 0;
384         int chunkline = 0;
385
386         if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
387         {
388                 chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
389
390                 while (*ptr)
391                 {
392                         if (*ptr++ == ' ')
393                         {
394                                 err = ptr;
395                                 break;
396                         }
397                 }
398         }
399
400         if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
401         {
402                 off = parser->data + parser->size;
403                 err = "'%>' expected before end of file";
404                 chunkline = 0;
405         }
406
407         for (ptr = parser->data; ptr < off; ptr++)
408                 if (*ptr == '\n')
409                         line++;
410
411         snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
412                          parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)");
413
414         lua_pushnil(L);
415         lua_pushinteger(L, line + chunkline);
416         lua_pushstring(L, msg);
417
418         return 3;
419 }