/* * gluon-web Template - Parser implementation * * Copyright (C) 2009-2012 Jo-Philipp Wich * Copyright (C) 2018 Matthias Schiffer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "template_parser.h" #include "template_utils.h" #include "template_lmo.h" #include #include #include #include #include #include #include #include #include #include /* code types */ #define T_TYPE_INIT 0 #define T_TYPE_TEXT 1 #define T_TYPE_COMMENT 2 #define T_TYPE_EXPR 3 #define T_TYPE_INCLUDE 4 #define T_TYPE_I18N 5 #define T_TYPE_I18N_RAW 6 #define T_TYPE_CODE 7 #define T_TYPE_EOF 8 struct template_chunk { const char *s; const char *e; int type; int line; }; /* parser state */ struct template_parser { size_t size; char *data; char *off; char *lua_chunk; int line; int in_expr; bool strip_before; bool strip_after; struct template_chunk prv_chunk; struct template_chunk cur_chunk; const char *file; }; /* leading and trailing code for different types */ static const char *const gen_code[][2] = { [T_TYPE_INIT] = {NULL, NULL}, [T_TYPE_TEXT] = {"write('", "')"}, [T_TYPE_COMMENT] = {NULL, NULL}, [T_TYPE_EXPR] = {"write(tostring(", " or ''))"}, [T_TYPE_INCLUDE] = {"include('", "')"}, [T_TYPE_I18N] = {"write(pcdata(translate('", "')))"}, [T_TYPE_I18N_RAW] = {"write(translate('", "'))"}, [T_TYPE_CODE] = {NULL, " "}, [T_TYPE_EOF] = {NULL, NULL}, }; static struct template_parser * template_init(struct template_parser *parser) { parser->off = parser->data; parser->cur_chunk.type = T_TYPE_INIT; parser->cur_chunk.s = parser->data; parser->cur_chunk.e = parser->data; return parser; } struct template_parser * template_open(const char *file) { int fd = -1; struct stat s; struct template_parser *parser; if (!(parser = calloc(1, sizeof(*parser)))) goto err; parser->file = file; fd = open(file, O_RDONLY|O_CLOEXEC); if (fd < 0) goto err; if (fstat(fd, &s)) goto err; parser->size = s.st_size; parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); fd = -1; if (parser->data == MAP_FAILED) goto err; return template_init(parser); err: if (fd >= 0) close(fd); template_close(parser); return NULL; } struct template_parser * template_string(const char *str, size_t len) { struct template_parser *parser; if (!(parser = calloc(1, sizeof(*parser)))) goto err; parser->size = len; parser->data = (char *)str; return template_init(parser); err: template_close(parser); return NULL; } void template_close(struct template_parser *parser) { if (!parser) return; free(parser->lua_chunk); /* if file is not set, we were parsing a string */ if (parser->file) { if ((parser->data != NULL) && (parser->data != MAP_FAILED)) munmap(parser->data, parser->size); } free(parser); } static void template_text(struct template_parser *parser, const char *e) { const char *s = parser->off; if (s < (parser->data + parser->size)) { if (parser->strip_after) { while ((s < e) && isspace(s[0])) s++; } parser->cur_chunk.type = T_TYPE_TEXT; } else { parser->cur_chunk.type = T_TYPE_EOF; } parser->cur_chunk.line = parser->line; parser->cur_chunk.s = s; parser->cur_chunk.e = e; } static void template_code(struct template_parser *parser, const char *e) { const char *s = parser->off; parser->strip_before = false; parser->strip_after = false; if (s < e && s[0] == '-') { parser->strip_before = true; s++; } if (s < e && e[-1] == '-') { parser->strip_after = true; e--; } switch (*s) { /* comment */ case '#': s++; parser->cur_chunk.type = T_TYPE_COMMENT; break; /* include */ case '+': s++; parser->cur_chunk.type = T_TYPE_INCLUDE; break; /* translate */ case ':': s++; parser->cur_chunk.type = T_TYPE_I18N; break; /* translate raw */ case '_': s++; parser->cur_chunk.type = T_TYPE_I18N_RAW; break; /* expr */ case '=': s++; parser->cur_chunk.type = T_TYPE_EXPR; break; /* code */ default: parser->cur_chunk.type = T_TYPE_CODE; } parser->cur_chunk.line = parser->line; parser->cur_chunk.s = s; parser->cur_chunk.e = e; } static void luastr_escape(struct template_buffer *out, const char *s, const char *e) { for (const char *ptr = s; ptr < e; ptr++) { switch (*ptr) { case '\\': buf_append(out, "\\\\", 2); break; case '\'': buf_append(out, "\\\'", 2); break; case '\n': buf_append(out, "\\n", 2); break; default: buf_putchar(out, *ptr); } } } static struct template_buffer * template_format_chunk(struct template_parser *parser) { const char *p; const char *head, *tail; struct template_chunk *c = &parser->prv_chunk; if (parser->strip_before && c->type == T_TYPE_TEXT) { while ((c->e > c->s) && isspace(c->e[-1])) c->e--; } /* empty chunk */ if (c->type == T_TYPE_EOF) return NULL; struct template_buffer *buf = buf_init(c->e - c->s); if (!buf) return NULL; if (c->e > c->s) { if ((head = gen_code[c->type][0]) != NULL) buf_append(buf, head, strlen(head)); switch (c->type) { case T_TYPE_TEXT: case T_TYPE_INCLUDE: case T_TYPE_I18N: case T_TYPE_I18N_RAW: luastr_escape(buf, c->s, c->e); break; case T_TYPE_EXPR: buf_append(buf, c->s, c->e - c->s); for (p = c->s; p < c->e; p++) parser->line += (*p == '\n'); break; case T_TYPE_CODE: buf_append(buf, c->s, c->e - c->s); for (p = c->s; p < c->e; p++) parser->line += (*p == '\n'); break; } if ((tail = gen_code[c->type][1]) != NULL) buf_append(buf, tail, strlen(tail)); } return buf; } const char * template_reader(lua_State *L __attribute__((unused)), void *ud, size_t *sz) { struct template_parser *parser = ud; /* free previous chunk */ free(parser->lua_chunk); parser->lua_chunk = NULL; while (true) { int rem = parser->size - (parser->off - parser->data); char *tag; parser->prv_chunk = parser->cur_chunk; /* before tag */ if (!parser->in_expr) { if ((tag = memmem(parser->off, rem, "<%", 2)) != NULL) { template_text(parser, tag); parser->off = tag + 2; parser->in_expr = 1; } else { template_text(parser, parser->data + parser->size); parser->off = parser->data + parser->size; } } /* inside tag */ else { if ((tag = memmem(parser->off, rem, "%>", 2)) != NULL) { template_code(parser, tag); parser->off = tag + 2; parser->in_expr = 0; } else { /* unexpected EOF */ template_code(parser, parser->data + parser->size); *sz = 1; return "\033"; } } struct template_buffer *buf = template_format_chunk(parser); if (!buf) return NULL; *sz = buf_length(buf); if (*sz) { parser->lua_chunk = buf_destroy(buf); return parser->lua_chunk; } } } int template_error(lua_State *L, struct template_parser *parser) { const char *err = luaL_checkstring(L, -1); const char *off = parser->prv_chunk.s; const char *ptr; char msg[1024]; int line = 0; int chunkline = 0; if ((ptr = memmem(err, strlen(err), "]:", 2)) != NULL) { chunkline = atoi(ptr + 2) - parser->prv_chunk.line; while (*ptr) { if (*ptr++ == ' ') { err = ptr; break; } } } if (memmem(err, strlen(err), "'char(27)'", 10) != NULL) { off = parser->data + parser->size; err = "'%>' expected before end of file"; chunkline = 0; } for (ptr = parser->data; ptr < off; ptr++) { if (*ptr == '\n') line++; } snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", parser->file ?: "[string]", line + chunkline, err ?: "(unknown error)"); lua_pushnil(L); lua_pushinteger(L, line + chunkline); lua_pushstring(L, msg); return 3; }