Rewrote state based redirection
[project/luci.git] / libs / web / luasrc / template.lua
index d49bcea..dc6e5bb 100644 (file)
@@ -24,56 +24,56 @@ limitations under the License.
 
 ]]--
 
---- LuCI template library.
-module("luci.template", package.seeall)
+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"
+
+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
 
-require("luci.config")
-require("luci.util")
-require("luci.fs")
-require("luci.sys")
-require("luci.http")
+--- LuCI template library.
+module "luci.template"
 
-luci.config.template = luci.config.template or {}
+config.template = config.template or {}
 
-viewdir    = luci.config.template.viewdir or luci.util.libpath() .. "/view"
-compiledir = luci.config.template.compiledir or luci.util.libpath() .. "/view"
+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()
 
 --- Manually  compile a given template into an executable Lua function
 -- @param template     LuCI template
 -- @return                     Lua template function
 function compile(template)     
+       local expr = {}
+
        -- Search all <% %> expressions
        local function expr_add(ws1, skip1, command, skip2, ws2)
-               table.insert(expr, command)
+               expr[#expr+1] = command
                return ( #skip1 > 0 and "" or ws1 ) .. 
                       "<%" .. tostring(#expr) .. "%>" ..
                       ( #skip2 > 0 and "" or ws2 )
        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)
-       
        -- Save all expressiosn to table "expr"
        template = template:gsub("(%s*)<%%(%-?)(.-)(%-?)%%>(%s*)", expr_add)
        
        local function sanitize(s)
-               s = string.format("%q", s)
+               s = "%q" % s
                return s:sub(2, #s-1)
        end
        
@@ -121,39 +121,42 @@ end
 
 --- Render a certain template.
 -- @param name         Template name
--- @param scope                Scope to assign to template
-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
+-- @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
@@ -161,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.get_bytecode(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
@@ -218,20 +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
-       local stat, err = luci.util.copcall(self.template)
+       local stat, err = util.copcall(self.template)
        if not stat then
                error("Error in template %s: %s" % {self.name, err})
        end
-       
-       -- Reset environment
-       setfenv(self.template, oldfenv)
 end