diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2012-11-25 19:17:55 +0000 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2012-11-25 19:17:55 +0000 |
commit | 0e50aa690af6cd9f37fa97b4a521fe523cce3c39 (patch) | |
tree | c0ab4edc0dd221dfa3e6fb4eeba049ecc05326fe /libs/web | |
parent | c647ff9f0e1af211a762dc9a773c1b5c4aacd168 (diff) |
libs/web: rewrite template engine, merge lmo library
- template parser: merge lmo library
- template parser: rewrite to operate on memory mapped files
- template parser: implement proper line number reporting on syntax errors
- template parser: process translate tags directly and bypass Lua
- template lmo: introduce load_catalog(), change_catalog() and close_catalog()
- template lmo: rewrite index processing to operate directly on the memory mapped file
- template lmo: implement binary search keys, reducing the lookup complexity to O(log n)
- po2lmo: write sorted indixes when generating *.lmo archives
- i18n: use the template parser for translations
- i18n: stub load(), loadc() and clear()
- i18n: map setlanguage() to load_catalog()
Diffstat (limited to 'libs/web')
-rw-r--r-- | libs/web/Makefile | 22 | ||||
-rw-r--r-- | libs/web/luasrc/i18n.lua | 44 | ||||
-rw-r--r-- | libs/web/luasrc/template.lua | 5 | ||||
-rw-r--r-- | libs/web/src/po2lmo.c | 242 | ||||
-rw-r--r-- | libs/web/src/template_lmo.c | 325 | ||||
-rw-r--r-- | libs/web/src/template_lmo.h | 91 | ||||
-rw-r--r-- | libs/web/src/template_lualib.c | 92 | ||||
-rw-r--r-- | libs/web/src/template_lualib.h | 1 | ||||
-rw-r--r-- | libs/web/src/template_parser.c | 603 | ||||
-rw-r--r-- | libs/web/src/template_parser.h | 70 | ||||
-rw-r--r-- | libs/web/src/template_utils.c | 121 | ||||
-rw-r--r-- | libs/web/src/template_utils.h | 17 | ||||
-rw-r--r-- | libs/web/standalone.mk | 56 |
13 files changed, 1223 insertions, 466 deletions
diff --git a/libs/web/Makefile b/libs/web/Makefile index d9f9700c1..1d28a3a4c 100644 --- a/libs/web/Makefile +++ b/libs/web/Makefile @@ -1,19 +1,28 @@ +ifneq (,$(wildcard ../../build/config.mk)) include ../../build/config.mk include ../../build/module.mk include ../../build/gccconfig.mk +else +include standalone.mk +endif TPL_LDFLAGS = TPL_CFLAGS = TPL_SO = parser.so +TPL_PO2LMO = po2lmo +TPL_PO2LMO_OBJ = src/po2lmo.o +TPL_LMO_OBJ = src/template_lmo.o TPL_COMMON_OBJ = src/template_parser.o src/template_utils.o TPL_LUALIB_OBJ = src/template_lualib.o %.o: %.c $(COMPILE) $(TPL_CFLAGS) $(LUA_CFLAGS) $(FPIC) -c -o $@ $< -compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ) +compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ) $(LINK) $(SHLIB_FLAGS) $(TPL_LDFLAGS) -o src/$(TPL_SO) \ - $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ) + $(TPL_COMMON_OBJ) $(TPL_LMO_OBJ) $(TPL_LUALIB_OBJ) + $(LINK) -o src/$(TPL_PO2LMO) \ + $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ) mkdir -p dist$(LUCI_LIBRARYDIR)/template cp src/$(TPL_SO) dist$(LUCI_LIBRARYDIR)/template/$(TPL_SO) @@ -24,3 +33,12 @@ clean: build-clean build-clean: rm -f src/*.o src/$(TPL_SO) + +host-compile: build-clean host-clean $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ) + $(LINK) -o src/$(TPL_PO2LMO) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ) + +host-install: host-compile + cp src/$(TPL_PO2LMO) ../../build/$(TPL_PO2LMO) + +host-clean: + rm -f ../../build/$(TPL_PO2LMO) diff --git a/libs/web/luasrc/i18n.lua b/libs/web/luasrc/i18n.lua index 816d90310..ff917c6f3 100644 --- a/libs/web/luasrc/i18n.lua +++ b/libs/web/luasrc/i18n.lua @@ -27,7 +27,8 @@ limitations under the License. --- LuCI translation library. module("luci.i18n", package.seeall) require("luci.util") -require("lmo") + +local tparser = require "luci.template.parser" table = {} i18ndir = luci.util.libpath() .. "/i18n/" @@ -37,7 +38,6 @@ default = "en" --- Clear the translation table. function clear() - table = {} end --- Load a translation and copy its data into the translation table. @@ -46,33 +46,6 @@ end -- @param force Force reload even if already loaded (optional) -- @return Success status function load(file, lang, force) - lang = lang and lang:gsub("_", "-") or "" - if force or not loaded[lang] or not loaded[lang][file] then - local f = lmo.open(i18ndir .. file .. "." .. lang .. ".lmo") - if f then - if not table[lang] then - table[lang] = { f } - setmetatable(table[lang], { - __index = function(tbl, key) - for i = 1, #tbl do - local s = rawget(tbl, i):lookup(key) - if s then return s end - end - end - }) - else - table[lang][#table[lang]+1] = f - end - - loaded[lang] = loaded[lang] or {} - loaded[lang][file] = true - return true - else - return false - end - else - return true - end end --- Load a translation file using the default translation language. @@ -80,9 +53,6 @@ end -- @param file Language file -- @param force Force reload even if already loaded (optional) function loadc(file, force) - load(file, default, force) - if context.parent then load(file, context.parent, force) end - return load(file, context.lang, force) end --- Set the context default translation language. @@ -90,16 +60,18 @@ end function setlanguage(lang) context.lang = lang:gsub("_", "-") context.parent = (context.lang:match("^([a-z][a-z])_")) + if not tparser.load_catalog(context.lang, i18ndir) then + if context.parent then + tparser.load_catalog(context.parent, i18ndir) + end + end end --- Return the translated value for a specific translation key. -- @param key Default translation text -- @return Translated string function translate(key) - return (table[context.lang] and table[context.lang][key]) - or (table[context.parent] and table[context.parent][key]) - or (table[default] and table[default][key]) - or key + return tparser.translate(key) or key end --- Return the translated value for a specific translation key and use it as sprintf pattern. diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua index 962c2ea88..72127d1df 100644 --- a/libs/web/luasrc/template.lua +++ b/libs/web/luasrc/template.lua @@ -79,9 +79,8 @@ function Template.__init__(self, name) -- If we have no valid template throw error, otherwise cache the template if not self.template then error("Failed to load template '" .. name .. "'.\n" .. - "Error while parsing template '" .. sourcefile .. "'.\n" .. - "A syntax error occured near '" .. - (err or "(nil)"):gsub("\t", "\\t"):gsub("\n", "\\n") .. "'.") + "Error while parsing template '" .. sourcefile .. "':\n" .. + (err or "Unknown syntax error")) else self.cache[name] = self.template end diff --git a/libs/web/src/po2lmo.c b/libs/web/src/po2lmo.c new file mode 100644 index 000000000..fb607a46f --- /dev/null +++ b/libs/web/src/po2lmo.c @@ -0,0 +1,242 @@ +/* + * lmo - Lua Machine Objects - PO to LMO conversion tool + * + * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org> + * + * 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_lmo.h" + +static void die(const char *msg) +{ + fprintf(stderr, "Error: %s\n", msg); + exit(1); +} + +static void usage(const char *name) +{ + fprintf(stderr, "Usage: %s input.po output.lmo\n", name); + exit(1); +} + +static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if( fwrite(ptr, size, nmemb, stream) == 0 ) + die("Failed to write stdout"); +} + +static int extract_string(const char *src, char *dest, int len) +{ + int pos = 0; + int esc = 0; + int off = -1; + + for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ ) + { + if( (off == -1) && (src[pos] == '"') ) + { + off = pos + 1; + } + else if( off >= 0 ) + { + if( esc == 1 ) + { + switch (src[pos]) + { + case '"': + case '\\': + off++; + break; + } + dest[pos-off] = src[pos]; + esc = 0; + } + else if( src[pos] == '\\' ) + { + dest[pos-off] = src[pos]; + esc = 1; + } + else if( src[pos] != '"' ) + { + dest[pos-off] = src[pos]; + } + else + { + dest[pos-off] = '\0'; + break; + } + } + } + + return (off > -1) ? strlen(dest) : -1; +} + +static int cmp_index(const void *a, const void *b) +{ + uint32_t x = ntohl(((const lmo_entry_t *)a)->key_id); + uint32_t y = ntohl(((const lmo_entry_t *)b)->key_id); + + if (x < y) + return -1; + else if (x > y) + return 1; + + return 0; +} + +static void print_index(void *array, int n, FILE *out) +{ + lmo_entry_t *e; + + qsort(array, n, sizeof(*e), cmp_index); + + for (e = array; n > 0; n--, e++) + { + print(&e->key_id, sizeof(uint32_t), 1, out); + print(&e->val_id, sizeof(uint32_t), 1, out); + print(&e->offset, sizeof(uint32_t), 1, out); + print(&e->length, sizeof(uint32_t), 1, out); + } +} + +int main(int argc, char *argv[]) +{ + char line[4096]; + char key[4096]; + char val[4096]; + char tmp[4096]; + int state = 0; + int offset = 0; + int length = 0; + int n_entries = 0; + void *array = NULL; + lmo_entry_t *entry = NULL; + uint32_t key_id, val_id; + + FILE *in; + FILE *out; + + if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) ) + usage(argv[0]); + + memset(line, 0, sizeof(key)); + memset(key, 0, sizeof(val)); + memset(val, 0, sizeof(val)); + + while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) ) + { + if( state == 0 && strstr(line, "msgid \"") == line ) + { + switch(extract_string(line, key, sizeof(key))) + { + case -1: + die("Syntax error in msgid"); + case 0: + state = 1; + break; + default: + state = 2; + } + } + else if( state == 1 || state == 2 ) + { + if( strstr(line, "msgstr \"") == line || state == 2 ) + { + switch(extract_string(line, val, sizeof(val))) + { + case -1: + state = 4; + break; + default: + state = 3; + } + } + else + { + switch(extract_string(line, tmp, sizeof(tmp))) + { + case -1: + state = 2; + break; + default: + strcat(key, tmp); + } + } + } + else if( state == 3 ) + { + switch(extract_string(line, tmp, sizeof(tmp))) + { + case -1: + state = 4; + break; + default: + strcat(val, tmp); + } + } + + if( state == 4 ) + { + if( strlen(key) > 0 && strlen(val) > 0 ) + { + key_id = sfh_hash(key, strlen(key)); + val_id = sfh_hash(val, strlen(val)); + + if( key_id != val_id ) + { + n_entries++; + array = realloc(array, n_entries * sizeof(lmo_entry_t)); + entry = (lmo_entry_t *)array + n_entries - 1; + + if (!array) + die("Out of memory"); + + entry->key_id = htonl(key_id); + entry->val_id = htonl(val_id); + entry->offset = htonl(offset); + entry->length = htonl(strlen(val)); + + length = strlen(val) + ((4 - (strlen(val) % 4)) % 4); + + print(val, length, 1, out); + offset += length; + } + } + + state = 0; + memset(key, 0, sizeof(key)); + memset(val, 0, sizeof(val)); + } + + memset(line, 0, sizeof(line)); + } + + print_index(array, n_entries, out); + + if( offset > 0 ) + { + offset = htonl(offset); + print(&offset, sizeof(uint32_t), 1, out); + fsync(fileno(out)); + fclose(out); + } + else + { + fclose(out); + unlink(argv[2]); + } + + fclose(in); + return(0); +} diff --git a/libs/web/src/template_lmo.c b/libs/web/src/template_lmo.c new file mode 100644 index 000000000..7fcd2cda1 --- /dev/null +++ b/libs/web/src/template_lmo.c @@ -0,0 +1,325 @@ +/* + * lmo - Lua Machine Objects - Base functions + * + * Copyright (C) 2009-2010 Jo-Philipp Wich <xm@subsignal.org> + * + * 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_lmo.h" + +/* + * Hash function from http://www.azillionmonkeys.com/qed/hash.html + * Copyright (C) 2004-2008 by Paul Hsieh + */ + +uint32_t sfh_hash(const char *data, int len) +{ + uint32_t hash = len, tmp; + int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += sfh_get16(data); + tmp = (sfh_get16(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += sfh_get16(data); + hash ^= hash << 16; + hash ^= data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += sfh_get16(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +uint32_t lmo_canon_hash(const char *str, int len) +{ + char res[4096]; + char *ptr, prev; + int off; + + if (!str || len >= sizeof(res)) + return 0; + + for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++) + { + if (isspace(*str)) + { + if (!isspace(prev)) + *ptr++ = ' '; + } + else + { + *ptr++ = *str; + } + } + + if ((ptr > res) && isspace(*(ptr-1))) + ptr--; + + return sfh_hash(res, ptr - res); +} + +lmo_archive_t * lmo_open(const char *file) +{ + int in = -1; + uint32_t idx_offset = 0; + struct stat s; + + lmo_archive_t *ar = NULL; + + if (stat(file, &s) == -1) + goto err; + + if ((in = open(file, O_RDONLY)) == -1) + goto err; + + if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL) + { + memset(ar, 0, sizeof(*ar)); + + ar->fd = in; + ar->size = s.st_size; + + fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC); + + if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED) + goto err; + + idx_offset = *((const uint32_t *) + (ar->mmap + ar->size - sizeof(uint32_t))); + + if (idx_offset >= ar->size) + goto err; + + ar->index = (lmo_entry_t *)(ar->mmap + idx_offset); + ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t); + ar->end = ar->mmap + ar->size; + + return ar; + } + +err: + if (in > -1) + close(in); + + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + free(ar); + } + + return NULL; +} + +void lmo_close(lmo_archive_t *ar) +{ + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + close(ar->fd); + free(ar); + + ar = NULL; + } +} + + +lmo_catalog_t *_lmo_catalogs = NULL; +lmo_catalog_t *_lmo_active_catalog = NULL; + +int lmo_load_catalog(const char *lang, const char *dir) +{ + DIR *dh = NULL; + char pattern[16]; + char path[PATH_MAX]; + struct dirent *de = NULL; + + lmo_archive_t *ar = NULL; + lmo_catalog_t *cat = NULL; + + if (!lmo_change_catalog(lang)) + return 0; + + if (!dir || !(dh = opendir(dir))) + goto err; + + if (!(cat = malloc(sizeof(*cat)))) + goto err; + + memset(cat, 0, sizeof(*cat)); + + snprintf(cat->lang, sizeof(cat->lang), "%s", lang); + snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang); + + while ((de = readdir(dh)) != NULL) + { + if (!fnmatch(pattern, de->d_name, 0)) + { + snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); + ar = lmo_open(path); + + if (ar) + { + ar->next = cat->archives; + cat->archives = ar; + } + } + } + + closedir(dh); + + cat->next = _lmo_catalogs; + _lmo_catalogs = cat; + + if (!_lmo_active_catalog) + _lmo_active_catalog = cat; + + return 0; + +err: + if (dh) closedir(dh); + if (cat) free(cat); + + return -1; +} + +int lmo_change_catalog(const char *lang) +{ + lmo_catalog_t *cat; + + for (cat = _lmo_catalogs; cat; cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + _lmo_active_catalog = cat; + return 0; + } + } + + return -1; +} + +static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash) +{ + unsigned int m, l, r; + + l = 0; + r = ar->length - 1; + + while (1) + { + m = l + ((r - l) / 2); + + if (r < l) + break; + + if (ar->index[m].key_id == hash) + return &ar->index[m]; + + if (ar->index[m].key_id > hash) + { + if (!m) + break; + + r = m - 1; + } + else + { + l = m + 1; + } + } + + return NULL; +} + +int lmo_translate(const char *key, int keylen, char **out, int *outlen) +{ + uint32_t hash; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!key || !_lmo_active_catalog) + return -2; + + hash = htonl(lmo_canon_hash(key, keylen)); + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + { + if ((e = lmo_find_entry(ar, hash)) != NULL) + { + *out = ar->mmap + e->offset; + *outlen = e->length; + return 0; + } + } + + return -1; +} + +void lmo_close_catalog(const char *lang) +{ + lmo_archive_t *ar, *next; + lmo_catalog_t *cat, *prev; + + for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + if (prev) + prev->next = cat->next; + else + _lmo_catalogs = cat->next; + + for (ar = cat->archives; ar; ar = next) + { + next = ar->next; + lmo_close(ar); + } + + free(cat); + break; + } + } +} diff --git a/libs/web/src/template_lmo.h b/libs/web/src/template_lmo.h new file mode 100644 index 000000000..a40d7587a --- /dev/null +++ b/libs/web/src/template_lmo.h @@ -0,0 +1,91 @@ +/* + * lmo - Lua Machine Objects - General header + * + * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org> + * + * 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. + */ + +#ifndef _TEMPLATE_LMO_H_ +#define _TEMPLATE_LMO_H_ + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <errno.h> +#include <fnmatch.h> +#include <dirent.h> +#include <ctype.h> + +#if (defined(__GNUC__) && defined(__i386__)) +#define sfh_get16(d) (*((const uint16_t *) (d))) +#else +#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + + +struct lmo_entry { + uint32_t key_id; + uint32_t val_id; + uint32_t offset; + uint32_t length; +} __attribute__((packed)); + +typedef struct lmo_entry lmo_entry_t; + + +struct lmo_archive { + int fd; + int length; + uint32_t size; + lmo_entry_t *index; + char *mmap; + char *end; + struct lmo_archive *next; +}; + +typedef struct lmo_archive lmo_archive_t; + + +struct lmo_catalog { + char lang[6]; + struct lmo_archive *archives; + struct lmo_catalog *next; +}; + +typedef struct lmo_catalog lmo_catalog_t; + + +uint32_t sfh_hash(const char *data, int len); +uint32_t lmo_canon_hash(const char *data, int len); + +lmo_archive_t * lmo_open(const char *file); +void lmo_close(lmo_archive_t *ar); + + +extern lmo_catalog_t *_lmo_catalogs; +extern lmo_catalog_t *_lmo_active_catalog; + +int lmo_load_catalog(const char *lang, const char *dir); +int lmo_change_catalog(const char *lang); +int lmo_translate(const char *key, int keylen, char **out, int *outlen); +void lmo_close_catalog(const char *lang); + +#endif diff --git a/libs/web/src/template_lualib.c b/libs/web/src/template_lualib.c index d3a5f89bb..f40ef2d6a 100644 --- a/libs/web/src/template_lualib.c +++ b/libs/web/src/template_lualib.c @@ -21,37 +21,27 @@ int template_L_parse(lua_State *L) { const char *file = luaL_checkstring(L, 1); - struct template_parser parser; - int lua_status; + struct template_parser *parser = template_open(file); + int lua_status, rv; - if( (parser.fd = open(file, O_RDONLY)) > 0 ) + if (!parser) { - parser.flags = 0; - parser.bufsize = 0; - parser.state = T_STATE_TEXT_NEXT; - - lua_status = lua_load(L, template_reader, &parser, file); + lua_pushnil(L); + lua_pushinteger(L, errno); + lua_pushstring(L, strerror(errno)); + return 3; + } - (void) close(parser.fd); + lua_status = lua_load(L, template_reader, parser, file); + if (lua_status == 0) + rv = 1; + else + rv = template_error(L, parser); - if( lua_status == 0 ) - { - return 1; - } - else - { - lua_pushnil(L); - lua_pushinteger(L, lua_status); - lua_pushlstring(L, parser.out, parser.outsize); - return 3; - } - } + template_close(parser); - lua_pushnil(L); - lua_pushinteger(L, 255); - lua_pushstring(L, "No such file or directory"); - return 3; + return rv; } int template_L_sanitize_utf8(lua_State *L) @@ -88,12 +78,64 @@ int template_L_sanitize_pcdata(lua_State *L) return 0; } +static int template_L_load_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + const char *dir = luaL_optstring(L, 2, NULL); + lua_pushboolean(L, !lmo_load_catalog(lang, dir)); + return 1; +} + +static int template_L_close_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + lmo_close_catalog(lang); + return 0; +} + +static int template_L_change_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + lua_pushboolean(L, !lmo_change_catalog(lang)); + return 1; +} + +static int template_L_translate(lua_State *L) { + size_t len; + char *tr; + int trlen; + const char *key = luaL_checklstring(L, 1, &len); + + switch (lmo_translate(key, len, &tr, &trlen)) + { + case 0: + lua_pushlstring(L, tr, trlen); + return 1; + + case -1: + return 0; + } + + lua_pushnil(L); + lua_pushstring(L, "no catalog loaded"); + return 2; +} + +static int template_L_hash(lua_State *L) { + size_t len; + const char *key = luaL_checklstring(L, 1, &len); + lua_pushinteger(L, sfh_hash(key, len)); + return 1; +} + /* module table */ static const luaL_reg R[] = { { "parse", template_L_parse }, { "sanitize_utf8", template_L_sanitize_utf8 }, { "sanitize_pcdata", template_L_sanitize_pcdata }, + { "load_catalog", template_L_load_catalog }, + { "close_catalog", template_L_close_catalog }, + { "change_catalog", template_L_change_catalog }, + { "translate", template_L_translate }, + { "hash", template_L_hash }, { NULL, NULL } }; diff --git a/libs/web/src/template_lualib.h b/libs/web/src/template_lualib.h index d628b9dce..1b659be12 100644 --- a/libs/web/src/template_lualib.h +++ b/libs/web/src/template_lualib.h @@ -21,6 +21,7 @@ #include "template_parser.h" #include "template_utils.h" +#include "template_lmo.h" #define TEMPLATE_LUALIB_META "template.parser" diff --git a/libs/web/src/template_parser.c b/libs/web/src/template_parser.c index a0a400bdf..69f0f1c48 100644 --- a/libs/web/src/template_parser.c +++ b/libs/web/src/template_parser.c @@ -1,7 +1,7 @@ /* * LuCI Template - Parser implementation * - * Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org> + * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,21 @@ */ #include "template_parser.h" +#include "template_utils.h" +#include "template_lmo.h" /* leading and trailing code for different types */ -const char * gen_code[7][2] = { +const char *gen_code[9][2] = { + { NULL, NULL }, { "write(\"", "\")" }, { NULL, NULL }, { "write(tostring(", " or \"\"))" }, { "include(\"", "\")" }, - { "write(pcdata(translate(\"", "\")))" }, - { "write(translate(\"", "\"))" }, - { NULL, " " } + { "write(\"", "\")" }, + { "write(\"", "\")" }, + { NULL, " " }, + { NULL, NULL }, }; /* Simple strstr() like function that takes len arguments for both haystack and needle. */ @@ -59,407 +63,326 @@ static char *strfind(char *haystack, int hslen, const char *needle, int ndlen) return NULL; } -/* - * Inspect current read buffer and find the number of "vague" characters at the end - * which could indicate an opening token. Returns the number of "vague" chars. - * The last continuous sequence of whitespace, optionally followed by a "<" is - * treated as "vague" because whitespace may be discarded if the upcoming opening - * token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char - * can't be differentiated from an opening token ("<%"), so it's kept to be processed - * in the next cycle. - */ -static int stokscan(struct template_parser *data, int off, int no_whitespace) +struct template_parser * template_open(const char *file) { - int i; - int skip = 0; - int tokoff = data->bufsize - 1; + struct stat s; + static struct template_parser *parser; - for( i = tokoff; i >= off; i-- ) - { - if( data->buf[i] == T_TOK_START[0] ) - { - skip = tokoff - i + 1; - tokoff = i - 1; - break; - } - } + if (!(parser = malloc(sizeof(*parser)))) + goto err; + + memset(parser, 0, sizeof(*parser)); + parser->fd = -1; + parser->file = file; + + if (stat(file, &s)) + goto err; + + if ((parser->fd = open(file, O_RDONLY)) < 0) + goto err; - if( !no_whitespace ) + parser->size = s.st_size; + parser->mmap = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, + parser->fd, 0); + + if (parser->mmap != MAP_FAILED) { - for( i = tokoff; i >= off; i-- ) - { - if( isspace(data->buf[i]) ) - skip++; - else - break; - } + parser->off = parser->mmap; + parser->cur_chunk.type = T_TYPE_INIT; + parser->cur_chunk.s = parser->mmap; + parser->cur_chunk.e = parser->mmap; + + return parser; } - return skip; +err: + template_close(parser); + return NULL; } -/* - * Similar to stokscan() but looking for closing token indicators. - * Matches "-", optionally followed by a "%" char. - */ -static int etokscan(struct template_parser *data) +void template_close(struct template_parser *parser) { - int skip = 0; + if (!parser) + return; + + if (parser->gc != NULL) + free(parser->gc); - if( (data->bufsize > 0) && (data->buf[data->bufsize-1] == T_TOK_END[0]) ) - skip++; + if ((parser->mmap != NULL) && (parser->mmap != MAP_FAILED)) + munmap(parser->mmap, parser->size); - if( (data->bufsize > skip) && (data->buf[data->bufsize-skip-1] == T_TOK_SKIPWS[0]) ) - skip++; + if (parser->fd >= 0) + close(parser->fd); - return skip; + free(parser); } -/* - * Generate Lua expressions from the given raw code, write it into the - * output buffer and set the lua_Reader specific size pointer. - * Takes parser-state, lua_Reader's size pointer and generator flags - * as parameter. The given flags indicate whether leading or trailing - * code should be added. Returns a pointer to the output buffer. - */ -static const char * generate_expression(struct template_parser *data, size_t *sz, int what) +void template_text(struct template_parser *parser, const char *e) { - char tmp[T_OUTBUFSZ]; - int i; - int size = 0; - int start = 0; - int whitespace = 0; - - memset(tmp, 0, T_OUTBUFSZ); - - /* Inject leading expression code (if any) */ - if( (what & T_GEN_START) && (gen_code[data->type][0] != NULL) ) - { - memcpy(tmp, gen_code[data->type][0], strlen(gen_code[data->type][0])); - size += strlen(gen_code[data->type][0]); - } + const char *s = parser->off; - /* Parse source buffer */ - for( i = 0; i < data->outsize; i++ ) + if (s < (parser->mmap + parser->size)) { - /* Skip leading whitespace for non-raw and non-expr chunks */ - if( !start && isspace(data->out[i]) && (data->type == T_TYPE_I18N || - data->type == T_TYPE_I18N_RAW || data->type == T_TYPE_INCLUDE) ) - continue; - else if( !start ) - start = 1; - - /* Found whitespace after i18n key */ - if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW ) + if (parser->strip_after) { - /* Is initial whitespace, insert space */ - if( !whitespace && isspace(data->out[i]) ) - { - tmp[size++] = ' '; - whitespace = 1; - } - - /* Suppress subsequent whitespace, escape special chars */ - else if( !isspace(data->out[i]) ) - { - if( data->out[i] == '\\' || data->out[i] == '"' ) - tmp[size++] = '\\'; - - tmp[size++] = data->out[i]; - whitespace = 0; - } + while ((s <= e) && isspace(*s)) + s++; } - /* Escape quotes, backslashes and newlines for plain and include expressions */ - else if( (data->type == T_TYPE_TEXT || data->type == T_TYPE_INCLUDE) && - (data->out[i] == '\\' || data->out[i] == '"' || data->out[i] == '\n' || data->out[i] == '\t') ) - { - tmp[size++] = '\\'; + parser->cur_chunk.type = T_TYPE_TEXT; + } + else + { + parser->cur_chunk.type = T_TYPE_EOF; + } - switch(data->out[i]) - { - case '\n': - tmp[size++] = 'n'; - break; + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; +} - case '\t': - tmp[size++] = 't'; - break; +void template_code(struct template_parser *parser, const char *e) +{ + const char *s = parser->off; - default: - tmp[size++] = data->out[i]; - } - } + parser->strip_before = 0; + parser->strip_after = 0; - /* Normal char */ - else - { - tmp[size++] = data->out[i]; - } + if (*s == '-') + { + parser->strip_before = 1; + for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++); } - /* Inject trailing expression code (if any) */ - if( (what & T_GEN_END) && (gen_code[data->type][1] != NULL) ) + if (*(e-1) == '-') { - /* Strip trailing space for i18n expressions */ - if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW ) - if( (size > 0) && (tmp[size-1] == ' ') ) - size--; - - memcpy(&tmp[size], gen_code[data->type][1], strlen(gen_code[data->type][1])); - size += strlen(gen_code[data->type][1]); + parser->strip_after = 1; + for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--); } - *sz = data->outsize = size; - memset(data->out, 0, T_OUTBUFSZ); - memcpy(data->out, tmp, size); + switch (*s) + { + /* comment */ + case '#': + s++; + parser->cur_chunk.type = T_TYPE_COMMENT; + break; - //printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out); + /* include */ + case '+': + s++; + parser->cur_chunk.type = T_TYPE_INCLUDE; + break; - return data->out; -} + /* translate */ + case ':': + s++; + parser->cur_chunk.type = T_TYPE_I18N; + break; -/* - * Move the number of bytes specified in data->bufsize from the - * given source pointer to the beginning of the read buffer. - */ -static void bufmove(struct template_parser *data, const char *src) -{ - if( data->bufsize > 0 ) - memmove(data->buf, src, data->bufsize); - else if( data->bufsize < 0 ) - data->bufsize = 0; + /* translate raw */ + case '_': + s++; + parser->cur_chunk.type = T_TYPE_I18N_RAW; + break; + + /* expr */ + case '=': + s++; + parser->cur_chunk.type = T_TYPE_EXPR; + break; - data->buf[data->bufsize] = 0; + /* code */ + default: + parser->cur_chunk.type = T_TYPE_CODE; + break; + } + + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; } -/* - * Move the given amount of bytes from the given source pointer - * to the output buffer and set data->outputsize. - */ -static void bufout(struct template_parser *data, const char *src, int len) +static const char * +template_format_chunk(struct template_parser *parser, size_t *sz) { - if( len >= 0 ) - { - memset(data->out, 0, T_OUTBUFSZ); - memcpy(data->out, src, len); - data->outsize = len; - } - else + const char *s, *p; + const char *head, *tail; + struct template_chunk *c = &parser->prv_chunk; + struct template_buffer *buf; + + *sz = 0; + s = parser->gc = NULL; + + if (parser->strip_before && c->type == T_TYPE_TEXT) { - data->outsize = 0; + while ((c->e > c->s) && isspace(*(c->e - 1))) + c->e--; } -} -/* - * lua_Reader compatible function that parses template code on demand from - * the given file handle. - */ -const char *template_reader(lua_State *L, void *ud, size_t *sz) -{ - struct template_parser *data = ud; - char *match = NULL; - int off = 0; - int ignore = 0; - int genflags = 0; - int readlen = 0; - int vague = 0; - - while( !(data->flags & T_FLAG_EOF) || (data->bufsize > 0) ) + /* empty chunk */ + if (c->s == c->e) { - /* Fill buffer */ - if( !(data->flags & T_FLAG_EOF) && (data->bufsize < T_READBUFSZ) ) + if (c->type == T_TYPE_EOF) + { + *sz = 0; + s = NULL; + } + else { - if( (readlen = read(data->fd, &data->buf[data->bufsize], T_READBUFSZ - data->bufsize)) > 0 ) - data->bufsize += readlen; - else if( readlen == 0 ) - data->flags |= T_FLAG_EOF; - else - return NULL; + *sz = 1; + s = " "; } + } + + /* format chunk */ + else if ((buf = buf_init(c->e - c->s)) != NULL) + { + if ((head = gen_code[c->type][0]) != NULL) + buf_append(buf, head, strlen(head)); - /* Evaluate state */ - switch(data->state) + switch (c->type) { - /* Plain text chunk (before "<%") */ - case T_STATE_TEXT_INIT: - case T_STATE_TEXT_NEXT: - off = 0; ignore = 0; *sz = 0; - data->type = T_TYPE_TEXT; - - /* Skip leading whitespace if requested */ - if( data->flags & T_FLAG_SKIPWS ) - { - data->flags &= ~T_FLAG_SKIPWS; - while( (off < data->bufsize) && isspace(data->buf[off]) ) - off++; - } + case T_TYPE_TEXT: + escape_luastr(buf, c->s, c->e - c->s, 0); + break; - /* Found "<%" */ - if( (match = strfind(&data->buf[off], data->bufsize - off - 1, T_TOK_START, strlen(T_TOK_START))) != NULL ) - { - readlen = (int)(match - &data->buf[off]); - data->bufsize -= (readlen + strlen(T_TOK_START) + off); - match += strlen(T_TOK_START); - - /* Check for leading '-' */ - if( match[0] == T_TOK_SKIPWS[0] ) - { - data->bufsize--; - match++; - - while( (readlen > 1) && isspace(data->buf[off+readlen-1]) ) - { - readlen--; - } - } - - bufout(data, &data->buf[off], readlen); - bufmove(data, match); - data->state = T_STATE_CODE_INIT; - } + 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; - /* Maybe plain chunk */ - else - { - /* Preserve trailing "<" or white space, maybe a start token */ - vague = stokscan(data, off, 0); - - /* We can process some bytes ... */ - if( vague < data->bufsize ) - { - readlen = data->bufsize - vague - off; - } - - /* No bytes to process, so try to remove at least whitespace ... */ - else - { - /* ... but try to preserve trailing "<" ... */ - vague = stokscan(data, off, 1); - - if( vague < data->bufsize ) - { - readlen = data->bufsize - vague - off; - } - - /* ... no chance, push out buffer */ - else - { - readlen = vague - off; - vague = 0; - } - } - - bufout(data, &data->buf[off], readlen); - - data->state = T_STATE_TEXT_NEXT; - data->bufsize = vague; - bufmove(data, &data->buf[off+readlen]); - } + case T_TYPE_INCLUDE: + escape_luastr(buf, c->s, c->e - c->s, 0); + break; - if( ignore || data->outsize == 0 ) - continue; - else - return generate_expression(data, sz, T_GEN_START | T_GEN_END); + case T_TYPE_I18N: + translate_luastr(buf, c->s, c->e - c->s, 1); + break; + case T_TYPE_I18N_RAW: + translate_luastr(buf, c->s, c->e - c->s, 0); break; - /* Ignored chunk (inside "<%# ... %>") */ - case T_STATE_SKIP: - ignore = 1; + 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; + } - /* Initial code chunk ("<% ...") */ - case T_STATE_CODE_INIT: - off = 0; + if ((tail = gen_code[c->type][1]) != NULL) + buf_append(buf, tail, strlen(tail)); - /* Check for leading '-' */ - if( data->buf[off] == T_TOK_SKIPWS[0] ) - off++; + *sz = buf_length(buf); + s = parser->gc = buf_destroy(buf); - /* Determine code type */ - switch(data->buf[off]) - { - case '#': - ignore = 1; - off++; - data->type = T_TYPE_COMMENT; - break; - - case '=': - off++; - data->type = T_TYPE_EXPR; - break; - - case '+': - off++; - data->type = T_TYPE_INCLUDE; - break; - - case ':': - off++; - data->type = T_TYPE_I18N; - break; - - case '_': - off++; - data->type = T_TYPE_I18N_RAW; - break; - - default: - data->type = T_TYPE_CODE; - break; - } + if (!*sz) + { + *sz = 1; + s = " "; + } + } - /* Subsequent code chunk ("..." or "... %>") */ - case T_STATE_CODE_NEXT: - /* Found "%>" */ - if( (match = strfind(&data->buf[off], data->bufsize - off, T_TOK_END, strlen(T_TOK_END))) != NULL ) - { - genflags = ( data->state == T_STATE_CODE_INIT ) - ? (T_GEN_START | T_GEN_END) : T_GEN_END; + return s; +} - readlen = (int)(match - &data->buf[off]); +const char *template_reader(lua_State *L, void *ud, size_t *sz) +{ + struct template_parser *parser = ud; + int rem = parser->size - (parser->off - parser->mmap); + char *tag; - /* Check for trailing '-' */ - if( (match > data->buf) && (*(match-1) == T_TOK_SKIPWS[0]) ) - { - readlen--; - data->flags |= T_FLAG_SKIPWS; - } + parser->prv_chunk = parser->cur_chunk; - bufout(data, &data->buf[off], readlen); + /* free previous string */ + if (parser->gc) + { + free(parser->gc); + parser->gc = NULL; + } - data->state = T_STATE_TEXT_INIT; - data->bufsize -= ((int)(match - &data->buf[off]) + strlen(T_TOK_END) + off); - bufmove(data, &match[strlen(T_TOK_END)]); - } + /* before tag */ + if (!parser->in_expr) + { + if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) + { + template_text(parser, tag); + parser->off = tag + 2; + parser->in_expr = 1; + } + else + { + template_text(parser, parser->mmap + parser->size); + parser->off = parser->mmap + parser->size; + } + } - /* Code chunk */ - else - { - genflags = ( data->state == T_STATE_CODE_INIT ) ? T_GEN_START : 0; + /* inside tag */ + else + { + if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) + { + template_code(parser, tag); + parser->off = tag + 2; + parser->in_expr = 0; + } + else + { + /* unexpected EOF */ + template_code(parser, parser->mmap + parser->size); - /* Preserve trailing "%" and "-", maybe an end token */ - vague = etokscan(data); - readlen = data->bufsize - off - vague; - bufout(data, &data->buf[off], readlen); + *sz = 1; + return "\033"; + } + } - data->state = T_STATE_CODE_NEXT; - data->bufsize = vague; - bufmove(data, &data->buf[readlen+off]); - } + return template_format_chunk(parser, sz); +} - if( ignore || (data->outsize == 0 && !genflags) ) - continue; - else - return generate_expression(data, sz, genflags); +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; + fprintf(stderr, "E[%s]\n", err); + + if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) + { + chunkline = atoi(ptr + 2) - parser->prv_chunk.line; + + while (*ptr) + { + if (*ptr++ == ' ') + { + err = ptr; break; + } } } - *sz = 0; - return NULL; -} + if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) + { + off = parser->mmap + parser->size; + err = "'%>' expected before end of file"; + chunkline = 0; + } + + for (ptr = parser->mmap; ptr < off; ptr++) + if (*ptr == '\n') + line++; + snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", + parser->file, line + chunkline, err ? err : "(unknown error)"); + lua_pushnil(L); + lua_pushinteger(L, line + chunkline); + lua_pushstring(L, msg); + + return 3; +} diff --git a/libs/web/src/template_parser.h b/libs/web/src/template_parser.h index 24933f0c9..d1c606272 100644 --- a/libs/web/src/template_parser.h +++ b/libs/web/src/template_parser.h @@ -21,61 +21,59 @@ #include <stdlib.h> #include <stdio.h> +#include <stdint.h> #include <unistd.h> #include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> #include <string.h> #include <ctype.h> +#include <errno.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> -#define T_READBUFSZ 1024 -#define T_OUTBUFSZ T_READBUFSZ * 3 - -/* parser states */ -#define T_STATE_TEXT_INIT 0 -#define T_STATE_TEXT_NEXT 1 -#define T_STATE_CODE_INIT 2 -#define T_STATE_CODE_NEXT 3 -#define T_STATE_SKIP 4 - -/* parser flags */ -#define T_FLAG_EOF 0x01 -#define T_FLAG_SKIPWS 0x02 - -/* tokens used in matching and expression generation */ -#define T_TOK_START "<%" -#define T_TOK_END "%>" -#define T_TOK_SKIPWS "-" +/* 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 -/* generator flags */ -#define T_GEN_START 0x01 -#define T_GEN_END 0x02 -/* code types */ -#define T_TYPE_TEXT 0 -#define T_TYPE_COMMENT 1 -#define T_TYPE_EXPR 2 -#define T_TYPE_INCLUDE 3 -#define T_TYPE_I18N 4 -#define T_TYPE_I18N_RAW 5 -#define T_TYPE_CODE 6 +struct template_chunk { + const char *s; + const char *e; + int type; + int line; +}; /* parser state */ struct template_parser { int fd; - int bufsize; - int outsize; - int state; - int flags; - int type; - char buf[T_READBUFSZ]; - char out[T_OUTBUFSZ]; + uint32_t size; + char *mmap; + char *off; + char *gc; + int line; + int in_expr; + int strip_before; + int strip_after; + struct template_chunk prv_chunk; + struct template_chunk cur_chunk; + const char *file; }; +struct template_parser * template_open(const char *file); +void template_close(struct template_parser *parser); const char *template_reader(lua_State *L, void *ud, size_t *sz); +int template_error(lua_State *L, struct template_parser *parser); #endif diff --git a/libs/web/src/template_utils.c b/libs/web/src/template_utils.c index 36f08aa22..6ed20d355 100644 --- a/libs/web/src/template_utils.c +++ b/libs/web/src/template_utils.c @@ -17,19 +17,23 @@ */ #include "template_utils.h" +#include "template_lmo.h" /* initialize a buffer object */ -static struct template_buffer * buf_init(void) +struct template_buffer * buf_init(int size) { struct template_buffer *buf; + if (size <= 0) + size = 1024; + buf = (struct template_buffer *)malloc(sizeof(struct template_buffer)); if (buf != NULL) { buf->fill = 0; - buf->size = 1024; - buf->data = (unsigned char *)malloc(buf->size); + buf->size = size; + buf->data = malloc(buf->size); if (buf->data != NULL) { @@ -46,17 +50,21 @@ static struct template_buffer * buf_init(void) } /* grow buffer */ -static int buf_grow(struct template_buffer *buf) +int buf_grow(struct template_buffer *buf, int size) { unsigned int off = (buf->dptr - buf->data); - unsigned char *data = - (unsigned char *)realloc(buf->data, buf->size + 1024); + char *data; + + if (size <= 0) + size = 1024; + + data = realloc(buf->data, buf->size + size); if (data != NULL) { buf->data = data; buf->dptr = data + off; - buf->size += 1024; + buf->size += size; return buf->size; } @@ -65,9 +73,9 @@ static int buf_grow(struct template_buffer *buf) } /* put one char into buffer object */ -static int buf_putchar(struct template_buffer *buf, unsigned char c) +int buf_putchar(struct template_buffer *buf, char c) { - if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf) ) + if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) ) return 0; *(buf->dptr++) = c; @@ -78,11 +86,11 @@ static int buf_putchar(struct template_buffer *buf, unsigned char c) } /* append data to buffer */ -static int buf_append(struct template_buffer *buf, unsigned char *s, int len) +int buf_append(struct template_buffer *buf, const char *s, int len) { - while ((buf->fill + len + 1) >= buf->size) + if ((buf->fill + len + 1) >= buf->size) { - if (!buf_grow(buf)) + if (!buf_grow(buf, len + 1)) return 0; } @@ -95,13 +103,19 @@ static int buf_append(struct template_buffer *buf, unsigned char *s, int len) return len; } +/* read buffer length */ +int buf_length(struct template_buffer *buf) +{ + return buf->fill; +} + /* destroy buffer object and return pointer to data */ -static char * buf_destroy(struct template_buffer *buf) +char * buf_destroy(struct template_buffer *buf) { - unsigned char *data = buf->data; + char *data = buf->data; free(buf); - return (char *)data; + return data; } @@ -229,7 +243,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf) !mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n)) { /* copy sequence */ - if (!buf_append(buf, ptr, n)) + if (!buf_append(buf, (char *)ptr, n)) return 0; } @@ -266,7 +280,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf) /* sanitize given string and replace all invalid UTF-8 sequences with "?" */ char * sanitize_utf8(const char *s, unsigned int l) { - struct template_buffer *buf = buf_init(); + struct template_buffer *buf = buf_init(l); unsigned char *ptr = (unsigned char *)s; unsigned int v, o; @@ -278,7 +292,7 @@ char * sanitize_utf8(const char *s, unsigned int l) /* ascii char */ if ((*ptr >= 0x01) && (*ptr <= 0x7F)) { - if (!buf_putchar(buf, *ptr++)) + if (!buf_putchar(buf, (char)*ptr++)) break; } @@ -300,7 +314,7 @@ char * sanitize_utf8(const char *s, unsigned int l) * Escape XML control chars */ char * sanitize_pcdata(const char *s, unsigned int l) { - struct template_buffer *buf = buf_init(); + struct template_buffer *buf = buf_init(l); unsigned char *ptr = (unsigned char *)s; unsigned int o, v; char esq[8]; @@ -329,7 +343,7 @@ char * sanitize_pcdata(const char *s, unsigned int l) { esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - if (!buf_append(buf, (unsigned char *)esq, esl)) + if (!buf_append(buf, esq, esl)) break; ptr++; @@ -338,7 +352,7 @@ char * sanitize_pcdata(const char *s, unsigned int l) /* ascii char */ else if (*ptr <= 0x7F) { - buf_putchar(buf, *ptr++); + buf_putchar(buf, (char)*ptr++); } /* multi byte sequence */ @@ -353,3 +367,68 @@ char * sanitize_pcdata(const char *s, unsigned int l) return buf_destroy(buf); } + +void escape_luastr(struct template_buffer *out, const char *s, unsigned int l, + int escape_xml) +{ + int esl; + char esq[8]; + char *ptr; + + for (ptr = (char *)s; ptr < (s + l); ptr++) + { + switch (*ptr) + { + case '\\': + buf_append(out, "\\\\", 2); + break; + + case '"': + if (escape_xml) + buf_append(out, """, 5); + else + buf_append(out, "\\\"", 2); + break; + + case '\n': + buf_append(out, "\\n", 2); + break; + + case '\'': + case '&': + case '<': + case '>': + if (escape_xml) + { + esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); + buf_append(out, esq, esl); + break; + } + + default: + buf_putchar(out, *ptr); + } + } +} + +void translate_luastr(struct template_buffer *out, const char *s, unsigned int l, + int escape_xml) +{ + char *tr; + int trlen; + + switch (lmo_translate(s, l, &tr, &trlen)) + { + case 0: + escape_luastr(out, tr, trlen, escape_xml); + break; + + case -1: + escape_luastr(out, s, l, escape_xml); + break; + + default: + /* no catalog loaded */ + break; + } +} diff --git a/libs/web/src/template_utils.h b/libs/web/src/template_utils.h index 1f7d438c6..371b6a37c 100644 --- a/libs/web/src/template_utils.h +++ b/libs/web/src/template_utils.h @@ -1,7 +1,7 @@ /* * LuCI Template - Utility header * - * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,28 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <dlfcn.h> /* buffer object */ struct template_buffer { - unsigned char *data; - unsigned char *dptr; + char *data; + char *dptr; unsigned int size; unsigned int fill; }; +struct template_buffer * buf_init(int size); +int buf_grow(struct template_buffer *buf, int size); +int buf_putchar(struct template_buffer *buf, char c); +int buf_append(struct template_buffer *buf, const char *s, int len); +int buf_length(struct template_buffer *buf); +char * buf_destroy(struct template_buffer *buf); + char * sanitize_utf8(const char *s, unsigned int l); char * sanitize_pcdata(const char *s, unsigned int l); +void escape_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); +void translate_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); + #endif diff --git a/libs/web/standalone.mk b/libs/web/standalone.mk new file mode 100644 index 000000000..66a0e5a2e --- /dev/null +++ b/libs/web/standalone.mk @@ -0,0 +1,56 @@ +LUAC = luac +LUAC_OPTIONS = -s +LUA_TARGET ?= source + +LUA_MODULEDIR = /usr/local/share/lua/5.1 +LUA_LIBRARYDIR = /usr/local/lib/lua/5.1 + +OS ?= $(shell uname) + +LUA_SHLIBS = $(shell pkg-config --silence-errors --libs lua5.1 || pkg-config --silence-errors --libs lua-5.1 || pkg-config --silence-errors --libs lua) +LUA_LIBS = $(if $(LUA_SHLIBS),$(LUA_SHLIBS),$(firstword $(wildcard /usr/lib/liblua.a /usr/local/lib/liblua.a /opt/local/lib/liblua.a))) +LUA_CFLAGS = $(shell pkg-config --silence-errors --cflags lua5.1 || pkg-config --silence-errors --cflags lua-5.1 || pkg-config --silence-errors --cflags lua) + +CC = gcc +AR = ar +RANLIB = ranlib +CFLAGS = -O2 +FPIC = -fPIC +EXTRA_CFLAGS = --std=gnu99 +WFLAGS = -Wall -Werror -pedantic +CPPFLAGS = +COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(WFLAGS) +ifeq ($(OS),Darwin) + SHLIB_FLAGS = -bundle -undefined dynamic_lookup +else + SHLIB_FLAGS = -shared +endif +LINK = $(CC) $(LDFLAGS) + +.PHONY: all build compile luacompile luasource clean luaclean + +all: build + +build: luabuild gccbuild + +luabuild: lua$(LUA_TARGET) + +gccbuild: compile +compile: + +clean: luaclean + +luasource: + mkdir -p dist$(LUA_MODULEDIR) + cp -pR root/* dist 2>/dev/null || true + cp -pR lua/* dist$(LUA_MODULEDIR) 2>/dev/null || true + for i in $$(find dist -name .svn); do rm -rf $$i || true; done + +luastrip: luasource + for i in $$(find dist -type f -name '*.lua'); do perl -e 'undef $$/; open( F, "< $$ARGV[0]" ) || die $$!; $$src = <F>; close F; $$src =~ s/--\[\[.*?\]\](--)?//gs; $$src =~ s/^\s*--.*?\n//gm; open( F, "> $$ARGV[0]" ) || die $$!; print F $$src; close F' $$i; done + +luacompile: luasource + for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done + +luaclean: + rm -rf dist |