gluon/package/gluon-web/src/template_parser.c

397 lines
8.1 KiB
C
Raw Normal View History

/*
2018-02-22 13:23:33 +00:00
* gluon-web Template - Parser implementation
*
* Copyright (C) 2009-2012 Jo-Philipp Wich <jow@openwrt.org>
2018-02-22 13:23:33 +00:00
* Copyright (C) 2018 Matthias Schiffer <mschiffer@universe-factory.net>
*
* 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"
2018-02-22 20:09:35 +00:00
#include <lualib.h>
#include <lauxlib.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* 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 {
int fd;
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 */
2018-02-22 20:09:35 +00:00
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(\"", "\")"},
[T_TYPE_I18N_RAW] = {"write(\"", "\")"},
[T_TYPE_CODE] = {NULL, " "},
[T_TYPE_EOF] = {NULL, NULL},
};
2018-02-22 20:09:35 +00:00
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)
{
struct stat s;
struct template_parser *parser;
2018-02-22 13:11:06 +00:00
if (!(parser = calloc(1, sizeof(*parser))))
goto err;
parser->fd = -1;
parser->file = file;
if ((parser->fd = open(file, O_RDONLY|O_CLOEXEC)) < 0)
goto err;
if (fstat(parser->fd, &s))
goto err;
parser->size = s.st_size;
parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
parser->fd, 0);
2018-02-22 20:09:35 +00:00
if (parser->data == MAP_FAILED)
goto err;
2018-02-22 20:09:35 +00:00
return template_init(parser);
err:
template_close(parser);
return NULL;
}
2018-02-22 20:09:35 +00:00
struct template_parser * template_string(const char *str, size_t len)
{
struct template_parser *parser;
2018-02-22 13:11:06 +00:00
if (!(parser = calloc(1, sizeof(*parser))))
goto err;
parser->fd = -1;
parser->size = len;
2018-02-22 20:09:35 +00:00
parser->data = (char *)str;
2018-02-22 20:09:35 +00:00
return template_init(parser);
err:
template_close(parser);
return NULL;
}
void template_close(struct template_parser *parser)
{
if (!parser)
return;
2018-02-22 20:09:35 +00:00
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);
if (parser->fd >= 0)
close(parser->fd);
}
free(parser);
}
static void template_text(struct template_parser *parser, const char *e)
{
const char *s = parser->off;
2018-02-22 20:09:35 +00:00
if (s < (parser->data + parser->size)) {
if (parser->strip_after) {
while ((s < e) && isspace(s[0]))
s++;
}
parser->cur_chunk.type = T_TYPE_TEXT;
2018-02-22 20:09:35 +00:00
} 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;
2018-02-22 20:09:35 +00:00
parser->strip_before = false;
parser->strip_after = false;
2018-02-22 20:09:35 +00:00
if (s < e && s[0] == '-') {
parser->strip_before = true;
s++;
}
2018-02-22 20:09:35 +00:00
if (s < e && e[-1] == '-') {
parser->strip_after = true;
e--;
}
2018-02-22 20:09:35 +00:00
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;
}
2018-02-22 20:09:35 +00:00
static struct template_buffer * template_format_chunk(struct template_parser *parser)
{
2018-02-22 20:09:35 +00:00
const char *p;
const char *head, *tail;
struct template_chunk *c = &parser->prv_chunk;
2018-02-22 20:09:35 +00:00
if (parser->strip_before && c->type == T_TYPE_TEXT) {
while ((c->e > c->s) && isspace(c->e[-1]))
c->e--;
}
/* empty chunk */
2018-02-22 20:09:35 +00:00
if (c->type == T_TYPE_EOF)
return NULL;
struct template_buffer *buf = buf_init(c->e - c->s);
if (!buf)
return NULL;
2018-02-22 20:09:35 +00:00
if (c->e > c->s) {
if ((head = gen_code[c->type][0]) != NULL)
buf_append(buf, head, strlen(head));
2018-02-22 20:09:35 +00:00
switch (c->type) {
case T_TYPE_TEXT:
luastr_escape(buf, c->s, c->e - c->s, false);
break;
2018-02-22 20:09:35 +00:00
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;
2018-02-22 20:09:35 +00:00
case T_TYPE_INCLUDE:
luastr_escape(buf, c->s, c->e - c->s, false);
break;
2018-02-22 20:09:35 +00:00
case T_TYPE_I18N:
luastr_translate(buf, c->s, c->e - c->s, true);
break;
2018-02-22 20:09:35 +00:00
case T_TYPE_I18N_RAW:
luastr_translate(buf, c->s, c->e - c->s, false);
break;
2018-02-22 20:09:35 +00:00
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));
}
2018-02-22 20:09:35 +00:00
return buf;
}
2018-02-22 20:09:35 +00:00
const char * template_reader(lua_State *L __attribute__((unused)), void *ud, size_t *sz)
{
struct template_parser *parser = ud;
2018-02-22 20:09:35 +00:00
/* 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;
}
}
2018-02-22 20:09:35 +00:00
/* 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";
}
}
2018-02-22 20:09:35 +00:00
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;
2018-02-22 20:09:35 +00:00
if ((ptr = memmem(err, strlen(err), "]:", 2)) != NULL) {
chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
2018-02-22 20:09:35 +00:00
while (*ptr) {
if (*ptr++ == ' ') {
err = ptr;
break;
}
}
}
2018-02-22 20:09:35 +00:00
if (memmem(err, strlen(err), "'char(27)'", 10) != NULL) {
off = parser->data + parser->size;
err = "'%>' expected before end of file";
chunkline = 0;
}
2018-02-22 20:09:35 +00:00
for (ptr = parser->data; ptr < off; ptr++) {
if (*ptr == '\n')
line++;
2018-02-22 20:09:35 +00:00
}
snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
2018-02-22 20:09:35 +00:00
parser->file ?: "[string]", line + chunkline, err ?: "(unknown error)");
lua_pushnil(L);
lua_pushinteger(L, line + chunkline);
lua_pushstring(L, msg);
return 3;
}