luci-0.11: merge outstanding trunk changes
[project/luci.git] / libs / web / src / template_parser.c
1 /*
2 * LuCI Template - Parser implementation
3 *
4 * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.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 static 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->mmap = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
86 parser->fd, 0);
87
88 if (parser->mmap != MAP_FAILED)
89 {
90 parser->off = parser->mmap;
91 parser->cur_chunk.type = T_TYPE_INIT;
92 parser->cur_chunk.s = parser->mmap;
93 parser->cur_chunk.e = parser->mmap;
94
95 return parser;
96 }
97
98 err:
99 template_close(parser);
100 return NULL;
101 }
102
103 void template_close(struct template_parser *parser)
104 {
105 if (!parser)
106 return;
107
108 if (parser->gc != NULL)
109 free(parser->gc);
110
111 if ((parser->mmap != NULL) && (parser->mmap != MAP_FAILED))
112 munmap(parser->mmap, parser->size);
113
114 if (parser->fd >= 0)
115 close(parser->fd);
116
117 free(parser);
118 }
119
120 void template_text(struct template_parser *parser, const char *e)
121 {
122 const char *s = parser->off;
123
124 if (s < (parser->mmap + parser->size))
125 {
126 if (parser->strip_after)
127 {
128 while ((s <= e) && isspace(*s))
129 s++;
130 }
131
132 parser->cur_chunk.type = T_TYPE_TEXT;
133 }
134 else
135 {
136 parser->cur_chunk.type = T_TYPE_EOF;
137 }
138
139 parser->cur_chunk.line = parser->line;
140 parser->cur_chunk.s = s;
141 parser->cur_chunk.e = e;
142 }
143
144 void template_code(struct template_parser *parser, const char *e)
145 {
146 const char *s = parser->off;
147
148 parser->strip_before = 0;
149 parser->strip_after = 0;
150
151 if (*s == '-')
152 {
153 parser->strip_before = 1;
154 for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
155 }
156
157 if (*(e-1) == '-')
158 {
159 parser->strip_after = 1;
160 for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
161 }
162
163 switch (*s)
164 {
165 /* comment */
166 case '#':
167 s++;
168 parser->cur_chunk.type = T_TYPE_COMMENT;
169 break;
170
171 /* include */
172 case '+':
173 s++;
174 parser->cur_chunk.type = T_TYPE_INCLUDE;
175 break;
176
177 /* translate */
178 case ':':
179 s++;
180 parser->cur_chunk.type = T_TYPE_I18N;
181 break;
182
183 /* translate raw */
184 case '_':
185 s++;
186 parser->cur_chunk.type = T_TYPE_I18N_RAW;
187 break;
188
189 /* expr */
190 case '=':
191 s++;
192 parser->cur_chunk.type = T_TYPE_EXPR;
193 break;
194
195 /* code */
196 default:
197 parser->cur_chunk.type = T_TYPE_CODE;
198 break;
199 }
200
201 parser->cur_chunk.line = parser->line;
202 parser->cur_chunk.s = s;
203 parser->cur_chunk.e = e;
204 }
205
206 static const char *
207 template_format_chunk(struct template_parser *parser, size_t *sz)
208 {
209 const char *s, *p;
210 const char *head, *tail;
211 struct template_chunk *c = &parser->prv_chunk;
212 struct template_buffer *buf;
213
214 *sz = 0;
215 s = parser->gc = NULL;
216
217 if (parser->strip_before && c->type == T_TYPE_TEXT)
218 {
219 while ((c->e > c->s) && isspace(*(c->e - 1)))
220 c->e--;
221 }
222
223 /* empty chunk */
224 if (c->s == c->e)
225 {
226 if (c->type == T_TYPE_EOF)
227 {
228 *sz = 0;
229 s = NULL;
230 }
231 else
232 {
233 *sz = 1;
234 s = " ";
235 }
236 }
237
238 /* format chunk */
239 else if ((buf = buf_init(c->e - c->s)) != NULL)
240 {
241 if ((head = gen_code[c->type][0]) != NULL)
242 buf_append(buf, head, strlen(head));
243
244 switch (c->type)
245 {
246 case T_TYPE_TEXT:
247 luastr_escape(buf, c->s, c->e - c->s, 0);
248 break;
249
250 case T_TYPE_EXPR:
251 buf_append(buf, c->s, c->e - c->s);
252 for (p = c->s; p < c->e; p++)
253 parser->line += (*p == '\n');
254 break;
255
256 case T_TYPE_INCLUDE:
257 luastr_escape(buf, c->s, c->e - c->s, 0);
258 break;
259
260 case T_TYPE_I18N:
261 luastr_translate(buf, c->s, c->e - c->s, 1);
262 break;
263
264 case T_TYPE_I18N_RAW:
265 luastr_translate(buf, c->s, c->e - c->s, 0);
266 break;
267
268 case T_TYPE_CODE:
269 buf_append(buf, c->s, c->e - c->s);
270 for (p = c->s; p < c->e; p++)
271 parser->line += (*p == '\n');
272 break;
273 }
274
275 if ((tail = gen_code[c->type][1]) != NULL)
276 buf_append(buf, tail, strlen(tail));
277
278 *sz = buf_length(buf);
279 s = parser->gc = buf_destroy(buf);
280
281 if (!*sz)
282 {
283 *sz = 1;
284 s = " ";
285 }
286 }
287
288 return s;
289 }
290
291 const char *template_reader(lua_State *L, void *ud, size_t *sz)
292 {
293 struct template_parser *parser = ud;
294 int rem = parser->size - (parser->off - parser->mmap);
295 char *tag;
296
297 parser->prv_chunk = parser->cur_chunk;
298
299 /* free previous string */
300 if (parser->gc)
301 {
302 free(parser->gc);
303 parser->gc = NULL;
304 }
305
306 /* before tag */
307 if (!parser->in_expr)
308 {
309 if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
310 {
311 template_text(parser, tag);
312 parser->off = tag + 2;
313 parser->in_expr = 1;
314 }
315 else
316 {
317 template_text(parser, parser->mmap + parser->size);
318 parser->off = parser->mmap + parser->size;
319 }
320 }
321
322 /* inside tag */
323 else
324 {
325 if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
326 {
327 template_code(parser, tag);
328 parser->off = tag + 2;
329 parser->in_expr = 0;
330 }
331 else
332 {
333 /* unexpected EOF */
334 template_code(parser, parser->mmap + parser->size);
335
336 *sz = 1;
337 return "\033";
338 }
339 }
340
341 return template_format_chunk(parser, sz);
342 }
343
344 int template_error(lua_State *L, struct template_parser *parser)
345 {
346 const char *err = luaL_checkstring(L, -1);
347 const char *off = parser->prv_chunk.s;
348 const char *ptr;
349 char msg[1024];
350 int line = 0;
351 int chunkline = 0;
352
353 if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
354 {
355 chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
356
357 while (*ptr)
358 {
359 if (*ptr++ == ' ')
360 {
361 err = ptr;
362 break;
363 }
364 }
365 }
366
367 if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
368 {
369 off = parser->mmap + parser->size;
370 err = "'%>' expected before end of file";
371 chunkline = 0;
372 }
373
374 for (ptr = parser->mmap; ptr < off; ptr++)
375 if (*ptr == '\n')
376 line++;
377
378 snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
379 parser->file, line + chunkline, err ? err : "(unknown error)");
380
381 lua_pushnil(L);
382 lua_pushinteger(L, line + chunkline);
383 lua_pushstring(L, msg);
384
385 return 3;
386 }