X-Git-Url: https://git.archive.openwrt.org/?a=blobdiff_plain;f=libs%2Fweb%2Fluasrc%2Ftemplate.lua;h=dc6e5bb7ddc9417f54bd281da10cb09ad69228c9;hb=9d85bd73e5562ae72715935187ce0d55c3009d24;hp=e5a4884ce53bb4ab152abaf0d26880e7ff06eee5;hpb=a41f33ba0188f897c6277cb23f9371749d2403f5;p=project%2Fluci.git diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua index e5a4884ce..dc6e5bb7d 100644 --- a/libs/web/luasrc/template.lua +++ b/libs/web/luasrc/template.lua @@ -23,74 +23,80 @@ 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"luci.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" -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 = '"..translate("%1","%2").."' + local r_i18n2 = '"..translate("%1", "").."' + local r_pexec = '"..(%s or "").."' + 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))) @@ -113,38 +119,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 = {} -setmetatable(Template.cache, {__mode = "v"}) +Template.cache = setmetatable({}, {__mode = "v"}) -- Constructor - Reads and compiles the template on-demand function Template.__init__(self, name) + 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 @@ -152,44 +164,48 @@ 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.mtime(sourcefile) or fs.mtime(sourcefile .. ".htm") + local commt = fs.mtime(compiledfile) - 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.mkdir(cdir, true) + fs.chmod(fs.dirname(cdir), "a+rxw") 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)) + fs.writefile(compiledfile, util.get_bytecode(compiled)) + fs.chmod(compiledfile, "a-rwx,u+rw") self.template = compiled end else + assert( + sys.process.info("uid") == fs.stat(compiledfile, "uid") + and fs.stat(compiledfile, "mode") == "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 @@ -209,17 +225,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