Moved luci.sys.libpath to luci.util
[project/luci.git] / libs / web / luasrc / template.lua
index 369aa0a..907403f 100644 (file)
@@ -23,43 +23,46 @@ See the License for the specific language governing permissions and
 limitations under the License.
 
 ]]--
+
+--- LuCI template library.
 module("luci.template", package.seeall)
 
 require("luci.config")
 require("luci.util")
 require("luci.fs")
+require("luci.sys")
 require("luci.http")
 
-viewdir = luci.sys.libpath() .. "/view/"
+luci.config.template = luci.config.template or {}
+
+viewdir    = luci.config.template.viewdir or luci.util.libpath() .. "/view"
+compiledir = luci.config.template.compiledir or luci.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 = "memory"
-
-
--- This applies to compiler modes "always" and "smart"
---
--- Produce compiled lua code rather than lua sourcecode
--- WARNING: Increases template size heavily!!!
--- This produces the same bytecode as luac but does not have a strip option
-compiler_enable_bytecode = false
+compiler_mode = luci.config.template.compiler_mode or "memory"
 
 
 -- Define the namespace for template modules
+context = luci.util.threadlocal()
+
 viewns = {
-       write      = io.write,
-       include    = function(name) Template(name):render(getfenv(2)) end,      
+       include    = function(name) Template(name):render(getfenv(2)) end,
 }
 
--- 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)
+       -- Search all <% %> expressions
+       local function expr_add(ws1, skip1, command, skip2, ws2)
                table.insert(expr, command)
-               return "<%" .. tostring(#expr) .. "%>"
+               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 
@@ -67,58 +70,61 @@ function compile(template)
        luci.util.extfenv(expr_add, "expr", expr)
        
        -- 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 = string.format("%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_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)))
                elseif p == ":" then
-                       re = sanitize(v):gsub(":(.-) (.+)", r_i18n)
+                       if v:find(" ") then
+                               re = sanitize(v):gsub(":(.-) (.*)", r_i18n)
+                       else
+                               re = sanitize(v):gsub(":(.+)", r_i18n2)
+                       end
                elseif p == "=" then
                        re = r_pexec:format(v:sub(2))
+               elseif p == "#" then
+                       re = ""
                else
                        re = r_exec:format(v)
                end
                template = template:gsub("<%%"..tostring(k).."%%>", re)
        end
 
-       if compiler_enable_bytecode then 
-               tf = loadstring(template)
-               template = string.dump(tf)
-       end
-       
-       return template
+       return loadstring(template)
 end
 
--- Oldstyle render shortcut
+--- 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 = pcall(Template, name)
+       local s, t = luci.util.copcall(Template, name)
        if not s then
                error(t)
        else
@@ -132,37 +138,43 @@ Template = luci.util.class()
 
 -- Shared template cache to store templates in to avoid unnecessary reloading
 Template.cache = {}
+setmetatable(Template.cache, {__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
-       end
+       self.template = self.cache[name]
        
        -- Create a new namespace for this template
        self.viewns = {}
        
        -- Copy over from general namespace
-       for k, v in pairs(viewns) do
-               self.viewns[k] = v
-       end     
+       luci.util.update(self.viewns, viewns)
+       if context.viewns then
+               luci.util.update(self.viewns, context.viewns)
+       end
        
        -- If we have a cached template, skip compiling and loading
        if self.template then
                return
        end
        
+       -- Enforce cache security
+       local cdir = compiledir .. "/" .. luci.sys.process.info("uid")
+       
        -- Compile and build
-       local sourcefile   = viewdir .. name .. ".htm"
-       local compiledfile = viewdir .. name .. ".lua"
+       local sourcefile   = viewdir    .. "/" .. name .. ".htm"
+       local compiledfile = cdir .. "/" .. luci.http.urlencode(name) .. ".lua"
        local err       
        
        if compiler_mode == "file" then
                local tplmt = luci.fs.mtime(sourcefile)
                local commt = luci.fs.mtime(compiledfile)
+               
+               if not luci.fs.mtime(cdir) then
+                       luci.fs.mkdir(cdir, true)
+                       luci.fs.chmod(luci.fs.dirname(cdir), "a+rxw")
+               end
                                
                -- Build if there is no compiled file or if compiled file is outdated
                if ((commt == nil) and not (tplmt == nil))
@@ -171,9 +183,10 @@ function Template.__init__(self, name)
                        source, err = luci.fs.readfile(sourcefile)
                        
                        if source then
-                               local compiled = compile(source)
-                               luci.fs.writefile(compiledfile, compiled)
-                               self.template, err = loadstring(compiled)
+                               local compiled, err = compile(source)
+                               
+                               luci.fs.writefile(compiledfile, luci.util.get_bytecode(compiled))
+                               self.template = compiled
                        end
                else
                        self.template, err = loadfile(compiledfile)
@@ -186,7 +199,7 @@ function Template.__init__(self, name)
                local source
                source, err = luci.fs.readfile(sourcefile)
                if source then
-                       self.template, err = loadstring(compile(source))
+                       self.template, err = compile(source)
                end
                        
        end