libs/web: Fixed template escaping
[project/luci.git] / libs / web / luasrc / template.lua
1 --[[
2 LuCI - Template Parser
3
4 Description:
5 A template parser supporting includes, translations, Lua code blocks
6 and more. It can be used either as a compiler or as an interpreter.
7
8 FileId: $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 --- LuCI template library.
28 module("luci.template", package.seeall)
29
30 require("luci.config")
31 require("luci.util")
32 require("luci.fs")
33 require("luci.http")
34
35 luci.config.template = luci.config.template or {}
36
37 viewdir = luci.config.template.viewdir or luci.sys.libpath() .. "/view"
38 compiledir = luci.config.template.compiledir or luci.sys.libpath() .. "/view"
39
40
41 -- Compile modes:
42 -- none: Never compile, only use precompiled data from files
43 -- memory: Always compile, do not save compiled files, ignore precompiled
44 -- file: Compile on demand, save compiled files, update precompiled
45 compiler_mode = luci.config.template.compiler_mode or "memory"
46
47
48 -- Define the namespace for template modules
49 context = luci.util.threadlocal()
50
51 viewns = {
52 include = function(name) Template(name):render(getfenv(2)) end,
53 }
54
55 --- Manually compile a given template into an executable Lua function
56 -- @param template LuCI template
57 -- @return Lua template function
58 function compile(template)
59 -- Search all <% %> expressions
60 local function expr_add(ws1, skip1, command, skip2, ws2)
61 table.insert(expr, command)
62 return ( #skip1 > 0 and "" or ws1 ) ..
63 "<%" .. tostring(#expr) .. "%>" ..
64 ( #skip2 > 0 and "" or ws2 )
65 end
66
67 -- As "expr" should be local, we have to assign it to the "expr_add" scope
68 local expr = {}
69 luci.util.extfenv(expr_add, "expr", expr)
70
71 -- Save all expressiosn to table "expr"
72 template = template:gsub("(%s*)<%%(%-?)(.-)(%-?)%%>(%s*)", expr_add)
73
74 local function sanitize(s)
75 s = string.format("%q", s)
76 return s:sub(2, #s-1)
77 end
78
79 -- Escape and sanitize all the template (all non-expressions)
80 template = sanitize(template)
81
82 -- Template module header/footer declaration
83 local header = 'write("'
84 local footer = '")'
85
86 template = header .. template .. footer
87
88 -- Replacements
89 local r_include = '")\ninclude("%s")\nwrite("'
90 local r_i18n = '"..translate("%1","%2").."'
91 local r_i18n2 = '"..translate("%1", "").."'
92 local r_pexec = '"..(%s or "").."'
93 local r_exec = '")\n%s\nwrite("'
94
95 -- Parse the expressions
96 for k,v in pairs(expr) do
97 local p = v:sub(1, 1)
98 v = v:gsub("%%", "%%%%")
99 local re = nil
100 if p == "+" then
101 re = r_include:format(sanitize(string.sub(v, 2)))
102 elseif p == ":" then
103 if v:find(" ") then
104 re = sanitize(v):gsub(":(.-) (.*)", r_i18n)
105 else
106 re = sanitize(v):gsub(":(.+)", r_i18n2)
107 end
108 elseif p == "=" then
109 re = r_pexec:format(v:sub(2))
110 elseif p == "#" then
111 re = ""
112 else
113 re = r_exec:format(v)
114 end
115 template = template:gsub("<%%"..tostring(k).."%%>", re)
116 end
117
118 return loadstring(template)
119 end
120
121 --- Render a certain template.
122 -- @param name Template name
123 -- @param scope Scope to assign to template
124 function render(name, scope, ...)
125 scope = scope or getfenv(2)
126 local s, t = luci.util.copcall(Template, name)
127 if not s then
128 error(t)
129 else
130 t:render(scope, ...)
131 end
132 end
133
134
135 -- Template class
136 Template = luci.util.class()
137
138 -- Shared template cache to store templates in to avoid unnecessary reloading
139 Template.cache = {}
140 setmetatable(Template.cache, {__mode = "v"})
141
142
143 -- Constructor - Reads and compiles the template on-demand
144 function Template.__init__(self, name)
145 self.template = self.cache[name]
146
147 -- Create a new namespace for this template
148 self.viewns = {}
149
150 -- Copy over from general namespace
151 luci.util.update(self.viewns, viewns)
152 if context.viewns then
153 luci.util.update(self.viewns, context.viewns)
154 end
155
156 -- If we have a cached template, skip compiling and loading
157 if self.template then
158 return
159 end
160
161 -- Enforce cache security
162 local cdir = compiledir .. "/" .. luci.sys.process.info("uid")
163
164 -- Compile and build
165 local sourcefile = viewdir .. "/" .. name .. ".htm"
166 local compiledfile = cdir .. "/" .. luci.http.urlencode(name) .. ".lua"
167 local err
168
169 if compiler_mode == "file" then
170 local tplmt = luci.fs.mtime(sourcefile)
171 local commt = luci.fs.mtime(compiledfile)
172
173 if not luci.fs.mtime(cdir) then
174 luci.fs.mkdir(cdir, true)
175 luci.fs.chmod(luci.fs.dirname(cdir), "a+rxw")
176 end
177
178 -- Build if there is no compiled file or if compiled file is outdated
179 if ((commt == nil) and not (tplmt == nil))
180 or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then
181 local source
182 source, err = luci.fs.readfile(sourcefile)
183
184 if source then
185 local compiled, err = compile(source)
186
187 luci.fs.writefile(compiledfile, luci.util.get_bytecode(compiled))
188 self.template = compiled
189 end
190 else
191 self.template, err = loadfile(compiledfile)
192 end
193
194 elseif compiler_mode == "none" then
195 self.template, err = loadfile(self.compiledfile)
196
197 elseif compiler_mode == "memory" then
198 local source
199 source, err = luci.fs.readfile(sourcefile)
200 if source then
201 self.template, err = compile(source)
202 end
203
204 end
205
206 -- If we have no valid template throw error, otherwise cache the template
207 if not self.template then
208 error(err)
209 else
210 self.cache[name] = self.template
211 end
212 end
213
214
215 -- Renders a template
216 function Template.render(self, scope)
217 scope = scope or getfenv(2)
218
219 -- Save old environment
220 local oldfenv = getfenv(self.template)
221
222 -- Put our predefined objects in the scope of the template
223 luci.util.resfenv(self.template)
224 luci.util.updfenv(self.template, scope)
225 luci.util.updfenv(self.template, self.viewns)
226
227 -- Now finally render the thing
228 self.template()
229
230 -- Reset environment
231 setfenv(self.template, oldfenv)
232 end