X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fweb%2Fluasrc%2Ftemplate.lua;h=e8f65e3bec969ce10ae98456ed3a154e33020c84;hp=29aedcdadd85a467d7d7ff7e20c9d33103d3588d;hb=8fcd841aa9af96c8a4a4d3c1a555d2d1ed42332c;hpb=855b7582d3576f45693e3a48fdb253c813cf4dce diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua index 29aedcdad..e8f65e3be 100644 --- a/libs/web/luasrc/template.lua +++ b/libs/web/luasrc/template.lua @@ -23,74 +23,81 @@ See the License for the specific language governing permissions and limitations under the License. ]]-- -module("luci.template", package.seeall) -require("luci.config") -require("luci.util") -require("luci.fs") -require("luci.http") +local fs = require "nixio.fs" +local sys = require "luci.sys" +local util = require "luci.util" +local table = require "table" +local string = require "string" +local config = require "luci.config" +local coroutine = require "coroutine" +local nixio = require "nixio", require "nixio.util" -luci.config.template = luci.config.template or {} +local tostring, pairs, loadstring = tostring, pairs, loadstring +local setmetatable, loadfile = setmetatable, loadfile +local getfenv, setfenv, rawget = getfenv, setfenv, rawget +local assert, type, error = assert, type, error -viewdir = luci.config.template.viewdir or luci.sys.libpath() .. "/view" -compiledir = luci.config.template.compiledir or luci.sys.libpath() .. "/view" +--- LuCI template library. +module "luci.template" + +config.template = config.template or {} + +viewdir = config.template.viewdir or util.libpath() .. "/view" +compiledir = config.template.compiledir or util.libpath() .. "/view" -- Compile modes: --- none: Never compile, only use precompiled data from files -- memory: Always compile, do not save compiled files, ignore precompiled -- file: Compile on demand, save compiled files, update precompiled -compiler_mode = luci.config.template.compiler_mode or "memory" +compiler_mode = config.template.compiler_mode or "memory" -- Define the namespace for template modules -context = luci.util.threadlocal() - -viewns = { - include = function(name) Template(name):render(getfenv(2)) end, -} +context = util.threadlocal() --- Compiles a given template into an executable Lua module +--- Manually compile a given template into an executable Lua function +-- @param template LuCI template +-- @return Lua template function function compile(template) - -- Search all <% %> expressions (remember: Lua table indexes begin with #1) - local function expr_add(command) - table.insert(expr, command) - return "<%" .. tostring(#expr) .. "%>" - end - - -- As "expr" should be local, we have to assign it to the "expr_add" scope local expr = {} - luci.util.extfenv(expr_add, "expr", expr) + + -- Search all <% %> expressions + local function expr_add(ws1, skip1, command, skip2, ws2) + expr[#expr+1] = command + return ( #skip1 > 0 and "" or ws1 ) .. + "<%" .. tostring(#expr) .. "%>" .. + ( #skip2 > 0 and "" or ws2 ) + end -- Save all expressiosn to table "expr" - template = template:gsub("<%%(.-)%%>", expr_add) + template = template:gsub("(%s*)<%%(%-?)(.-)(%-?)%%>(%s*)", expr_add) local function sanitize(s) - s = luci.util.escape(s) - s = luci.util.escape(s, "'") - s = luci.util.escape(s, "\n") - return s + s = "%q" % s + return s:sub(2, #s-1) end -- Escape and sanitize all the template (all non-expressions) template = sanitize(template) -- Template module header/footer declaration - local header = "write('" - local footer = "')" + local header = 'write("' + local footer = '")' template = header .. template .. footer -- Replacements - local r_include = "')\ninclude('%s')\nwrite('" - local r_i18n = "'..translate('%1','%2')..'" - local r_i18n2 = "'..translate('%1', '')..'" - local r_pexec = "'..(%s or '')..'" - local r_exec = "')\n%s\nwrite('" + local r_include = '")\ninclude("%s")\nwrite("' + local r_i18n = '")\nwrite(translate("%1","%2"))\nwrite("' + local r_i18n2 = '")\nwrite(translate("%1", ""))\nwrite("' + local r_pexec = '")\nwrite(tostring(%s or ""))\nwrite("' + local r_exec = '")\n%s\nwrite("' -- Parse the expressions for k,v in pairs(expr) do local p = v:sub(1, 1) + v = v:gsub("%%", "%%%%") local re = nil if p == "+" then re = r_include:format(sanitize(string.sub(v, 2))) @@ -102,6 +109,8 @@ function compile(template) end elseif p == "=" then re = r_pexec:format(v:sub(2)) + elseif p == "#" then + re = "" else re = r_exec:format(v) end @@ -111,41 +120,44 @@ function compile(template) return loadstring(template) end --- Oldstyle render shortcut -function render(name, scope, ...) - scope = scope or getfenv(2) - local s, t = luci.util.copcall(Template, name) - if not s then - error(t) - else - t:render(scope, ...) - end +--- Render a certain template. +-- @param name Template name +-- @param scope Scope to assign to template (optional) +function render(name, scope) + return Template(name):render(scope or getfenv(2)) end -- Template class -Template = luci.util.class() +Template = util.class() -- Shared template cache to store templates in to avoid unnecessary reloading -Template.cache = {} +Template.cache = setmetatable({}, {__mode = "v"}) -- Constructor - Reads and compiles the template on-demand function Template.__init__(self, name) - if self.cache[name] then - self.template = self.cache[name] - else - self.template = nil + local function _encode_filename(str) + + local function __chrenc( chr ) + return "%%%02x" % string.byte( chr ) + end + + if type(str) == "string" then + str = str:gsub( + "([^a-zA-Z0-9$_%-%.%+!*'(),])", + __chrenc + ) + end + + return str end + + self.template = self.cache[name] + self.name = name -- Create a new namespace for this template - self.viewns = {} - - -- Copy over from general namespace - luci.util.update(self.viewns, viewns) - if context.viewns then - luci.util.update(self.viewns, context.viewns) - end + self.viewns = context.viewns -- If we have a cached template, skip compiling and loading if self.template then @@ -153,44 +165,49 @@ function Template.__init__(self, name) end -- Enforce cache security - local cdir = compiledir .. "/" .. luci.sys.process.info("uid") + local cdir = compiledir .. "/" .. sys.process.info("uid") -- Compile and build - local sourcefile = viewdir .. "/" .. name .. ".htm" - local compiledfile = cdir .. "/" .. luci.http.urlencode(name) .. ".lua" + local sourcefile = viewdir .. "/" .. name + local compiledfile = cdir .. "/" .. _encode_filename(name) .. ".lua" local err if compiler_mode == "file" then - local tplmt = luci.fs.mtime(sourcefile) - local commt = luci.fs.mtime(compiledfile) + local tplmt = fs.stat(sourcefile, "mtime") or fs.stat(sourcefile .. ".htm", "mtime") + local commt = fs.stat(compiledfile, "mtime") - if not luci.fs.mtime(cdir) then - luci.fs.mkdir(cdir, true) - luci.fs.chmod(luci.fs.dirname(cdir), "a+rxw") + if not fs.mtime(cdir) then + fs.mkdirr(cdir) + fs.chmod(fs.dirname(cdir), 777) end + + assert(tplmt or commt, "No such template: " .. name) -- Build if there is no compiled file or if compiled file is outdated - if ((commt == nil) and not (tplmt == nil)) - or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then + if not commt or (commt and tplmt and commt < tplmt) then local source - source, err = luci.fs.readfile(sourcefile) + source, err = fs.readfile(sourcefile) or fs.readfile(sourcefile .. ".htm") if source then local compiled, err = compile(source) - luci.fs.writefile(compiledfile, luci.util.dump(compiled)) + local f = nixio.open(compiledfile, "w", 600) + f:writeall(util.get_bytecode(compiled)) + f:close() self.template = compiled end else + assert( + sys.process.info("uid") == fs.stat(compiledfile, "uid") + and fs.stat(compiledfile, "modestr") == "rw-------", + "Fatal: Cachefile is not sane!" + ) self.template, err = loadfile(compiledfile) end - elseif compiler_mode == "none" then - self.template, err = loadfile(self.compiledfile) - elseif compiler_mode == "memory" then local source - source, err = luci.fs.readfile(sourcefile) + source, err = fs.readfile(sourcefile) or fs.readfile(sourcefile .. ".htm") if source then self.template, err = compile(source) end @@ -210,17 +227,15 @@ end function Template.render(self, scope) scope = scope or getfenv(2) - -- Save old environment - local oldfenv = getfenv(self.template) - -- Put our predefined objects in the scope of the template - luci.util.resfenv(self.template) - luci.util.updfenv(self.template, scope) - luci.util.updfenv(self.template, self.viewns) + setfenv(self.template, setmetatable({}, {__index = + function(tbl, key) + return rawget(tbl, key) or self.viewns[key] or scope[key] + end})) -- Now finally render the thing - self.template() - - -- Reset environment - setfenv(self.template, oldfenv) + local stat, err = util.copcall(self.template) + if not stat then + error("Error in template %s: %s" % {self.name, err}) + end end