* luci/libs: add striptags() to global dispatcher namespace
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $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 web dispatcher.
28 module("luci.dispatcher", package.seeall)
29 require("luci.util")
30 require("luci.init")
31 require("luci.http")
32 require("luci.sys")
33 require("luci.fs")
34
35 context = luci.util.threadlocal()
36
37 authenticator = {}
38
39 -- Index table
40 local index = nil
41
42 -- Fastindex
43 local fi
44
45
46 --- Build the URL relative to the server webroot from given virtual path.
47 -- @param ... Virtual path
48 -- @return Relative URL
49 function build_url(...)
50 return luci.http.getenv("SCRIPT_NAME") .. "/" .. table.concat(arg, "/")
51 end
52
53 --- Send a 404 error code and render the "error404" template if available.
54 -- @param message Custom error message (optional)
55 -- @return false
56 function error404(message)
57 luci.http.status(404, "Not Found")
58 message = message or "Not Found"
59
60 require("luci.template")
61 if not luci.util.copcall(luci.template.render, "error404") then
62 luci.http.prepare_content("text/plain")
63 luci.http.write(message)
64 end
65 return false
66 end
67
68 --- Send a 500 error code and render the "error500" template if available.
69 -- @param message Custom error message (optional)#
70 -- @return false
71 function error500(message)
72 luci.http.status(500, "Internal Server Error")
73
74 require("luci.template")
75 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
76 luci.http.prepare_content("text/plain")
77 luci.http.write(message)
78 end
79 return false
80 end
81
82 function authenticator.htmlauth(validator, default)
83 local user = luci.http.formvalue("username")
84 local pass = luci.http.formvalue("password")
85
86 if user and validator(user, pass) then
87 return user
88 end
89
90 require("luci.i18n")
91 require("luci.template")
92 context.path = {}
93 luci.template.render("sysauth", {duser=default, fuser=user})
94 return false
95
96 end
97
98 --- Dispatch an HTTP request.
99 -- @param request LuCI HTTP Request object
100 function httpdispatch(request)
101 luci.http.context.request = request
102 context.request = {}
103 local pathinfo = request:getenv("PATH_INFO") or ""
104
105 for node in pathinfo:gmatch("[^/]+") do
106 table.insert(context.request, node)
107 end
108
109 dispatch(context.request)
110 luci.http.close()
111 end
112
113 --- Dispatches a LuCI virtual path.
114 -- @param request Virtual path
115 function dispatch(request)
116 context.path = request
117
118 require("luci.i18n")
119 luci.i18n.setlanguage(require("luci.config").main.lang)
120
121 if not context.tree then
122 createtree()
123 end
124
125 local c = context.tree
126 local track = {}
127 local args = {}
128 local n
129
130 for i, s in ipairs(request) do
131 c = c.nodes[s]
132 n = i
133 if not c or c.leaf then
134 break
135 end
136
137 for k, v in pairs(c) do
138 track[k] = v
139 end
140 end
141
142 if c and c.leaf then
143 for j=n+1, #request do
144 table.insert(args, request[j])
145 end
146 end
147
148 if track.i18n then
149 require("luci.i18n").loadc(track.i18n)
150 end
151
152 -- Init template engine
153 local tpl = require("luci.template")
154 local viewns = {}
155 tpl.context.viewns = viewns
156 viewns.write = luci.http.write
157 viewns.translate = function(...) return require("luci.i18n").translate(...) end
158 viewns.striptags = luci.util.striptags
159 viewns.controller = luci.http.getenv("SCRIPT_NAME")
160 viewns.media = luci.config.main.mediaurlbase
161 viewns.resource = luci.config.main.resourcebase
162 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
163
164 if track.dependent then
165 local stat, err = pcall(assert, not track.auto)
166 if not stat then
167 error500(err)
168 return
169 end
170 end
171
172 if track.sysauth then
173 require("luci.sauth")
174 local authen = authenticator[track.sysauth_authenticator]
175 local def = (type(track.sysauth) == "string") and track.sysauth
176 local accs = def and {track.sysauth} or track.sysauth
177 local sess = luci.http.getcookie("sysauth")
178 sess = sess and sess:match("^[A-F0-9]+$")
179 local user = luci.sauth.read(sess)
180
181 if not luci.util.contains(accs, user) then
182 if authen then
183 local user = authen(luci.sys.user.checkpasswd, def)
184 if not user or not luci.util.contains(accs, user) then
185 return
186 else
187 local sid = luci.sys.uniqueid(16)
188 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
189 luci.sauth.write(sid, user)
190 end
191 else
192 luci.http.status(403, "Forbidden")
193 return
194 end
195 end
196 end
197
198 if track.setgroup then
199 luci.sys.process.setgroup(track.setgroup)
200 end
201
202 if track.setuser then
203 luci.sys.process.setuser(track.setuser)
204 end
205
206 if c and type(c.target) == "function" then
207 context.dispatched = c
208 stat, mod = luci.util.copcall(require, c.module)
209 if stat then
210 luci.util.updfenv(c.target, mod)
211 end
212
213 stat, err = luci.util.copcall(c.target, unpack(args))
214 if not stat then
215 error500(err)
216 end
217 else
218 error404()
219 end
220 end
221
222 --- Generate the dispatching index using the best possible strategy.
223 function createindex()
224 local path = luci.util.libpath() .. "/controller/"
225 local suff = ".lua"
226
227 if luci.util.copcall(require, "luci.fastindex") then
228 createindex_fastindex(path, suff)
229 else
230 createindex_plain(path, suff)
231 end
232 end
233
234 --- Generate the dispatching index using the fastindex C-indexer.
235 -- @param path Controller base directory
236 -- @param suffix Controller file suffix
237 function createindex_fastindex(path, suffix)
238 index = {}
239
240 if not fi then
241 fi = luci.fastindex.new("index")
242 fi.add(path .. "*" .. suffix)
243 fi.add(path .. "*/*" .. suffix)
244 end
245 fi.scan()
246
247 for k, v in pairs(fi.indexes) do
248 index[v[2]] = v[1]
249 end
250 end
251
252 --- Generate the dispatching index using the native file-cache based strategy.
253 -- @param path Controller base directory
254 -- @param suffix Controller file suffix
255 function createindex_plain(path, suffix)
256 index = {}
257
258 local cache = nil
259
260 local controllers = luci.util.combine(
261 luci.fs.glob(path .. "*" .. suffix) or {},
262 luci.fs.glob(path .. "*/*" .. suffix) or {}
263 )
264
265 if indexcache then
266 cache = luci.fs.mtime(indexcache)
267
268 if not cache then
269 luci.fs.mkdir(indexcache)
270 luci.fs.chmod(indexcache, "a=,u=rwx")
271 cache = luci.fs.mtime(indexcache)
272 end
273 end
274
275 for i,c in ipairs(controllers) do
276 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
277 local cachefile
278 local stime
279 local ctime
280
281 if cache then
282 cachefile = indexcache .. "/" .. module
283 stime = luci.fs.mtime(c) or 0
284 ctime = luci.fs.mtime(cachefile) or 0
285 end
286
287 if not cache or stime > ctime then
288 stat, mod = luci.util.copcall(require, module)
289
290 if stat and mod and type(mod.index) == "function" then
291 index[module] = mod.index
292
293 if cache then
294 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
295 end
296 end
297 else
298 index[module] = loadfile(cachefile)
299 end
300 end
301 end
302
303 --- Create the dispatching tree from the index.
304 -- Build the index before if it does not exist yet.
305 function createtree()
306 if not index then
307 createindex()
308 end
309
310 context.tree = {nodes={}}
311 require("luci.i18n")
312
313 -- Load default translation
314 luci.i18n.loadc("default")
315
316 local scope = luci.util.clone(_G)
317 for k,v in pairs(luci.dispatcher) do
318 if type(v) == "function" then
319 scope[k] = v
320 end
321 end
322
323 for k, v in pairs(index) do
324 scope._NAME = k
325 setfenv(v, scope)
326
327 local stat, err = luci.util.copcall(v)
328 if not stat then
329 error500("createtree failed: " .. k .. ": " .. err)
330 luci.http.close()
331 os.exit(1)
332 end
333 end
334 end
335
336 --- Clone a node of the dispatching tree to another position.
337 -- @param path Virtual path destination
338 -- @param clone Virtual path source
339 -- @param title Destination node title (optional)
340 -- @param order Destination node order value (optional)
341 -- @return Dispatching tree node
342 function assign(path, clone, title, order)
343 local obj = node(unpack(path))
344 obj.nodes = nil
345 obj.module = nil
346
347 obj.title = title
348 obj.order = order
349
350 local c = context.tree
351 for k, v in ipairs(clone) do
352 if not c.nodes[v] then
353 c.nodes[v] = {nodes={}}
354 end
355
356 c = c.nodes[v]
357 end
358
359 setmetatable(obj, {__index = c})
360
361 return obj
362 end
363
364 --- Create a new dispatching node and define common parameters.
365 -- @param path Virtual path
366 -- @param target Target function to call when dispatched.
367 -- @param title Destination node title
368 -- @param order Destination node order value (optional)
369 -- @return Dispatching tree node
370 function entry(path, target, title, order)
371 local c = node(unpack(path))
372
373 c.target = target
374 c.title = title
375 c.order = order
376 c.module = getfenv(2)._NAME
377
378 return c
379 end
380
381 --- Fetch or create a new dispatching node.
382 -- @param ... Virtual path
383 -- @return Dispatching tree node
384 function node(...)
385 local c = context.tree
386 arg.n = nil
387
388 for k,v in ipairs(arg) do
389 if not c.nodes[v] then
390 c.nodes[v] = {nodes={}, auto=true}
391 end
392
393 c = c.nodes[v]
394 end
395
396 c.module = getfenv(2)._NAME
397 c.path = arg
398 c.auto = nil
399
400 return c
401 end
402
403 -- Subdispatchers --
404
405 --- Create a redirect to another dispatching node.
406 -- @param ... Virtual path destination
407 function alias(...)
408 local req = arg
409 return function()
410 dispatch(req)
411 end
412 end
413
414 --- Rewrite the first x path values of the request.
415 -- @param n Number of path values to replace
416 -- @param ... Virtual path to replace removed path values with
417 function rewrite(n, ...)
418 local req = arg
419 return function()
420 for i=1,n do
421 table.remove(context.path, 1)
422 end
423
424 for i,r in ipairs(req) do
425 table.insert(context.path, i, r)
426 end
427
428 dispatch()
429 end
430 end
431
432 --- Create a function-call dispatching target.
433 -- @param name Target function of local controller
434 -- @param ... Additional parameters passed to the function
435 function call(name, ...)
436 local argv = {...}
437 return function() return getfenv()[name](unpack(argv)) end
438 end
439
440 --- Create a template render dispatching target.
441 -- @param name Template to be rendered
442 function template(name)
443 require("luci.template")
444 return function() luci.template.render(name) end
445 end
446
447 --- Create a CBI model dispatching target.
448 -- @param model CBI model tpo be rendered
449 function cbi(model)
450 require("luci.cbi")
451 require("luci.template")
452
453 return function(...)
454 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
455 if not stat then
456 error500(maps)
457 return true
458 end
459
460 for i, res in ipairs(maps) do
461 local stat, err = luci.util.copcall(res.parse, res)
462 if not stat then
463 error500(err)
464 return true
465 end
466 end
467
468 luci.template.render("cbi/header")
469 for i, res in ipairs(maps) do
470 res:render()
471 end
472 luci.template.render("cbi/footer")
473 end
474 end
475
476 --- Create a CBI form model dispatching target.
477 -- @param model CBI form model tpo be rendered
478 function form(model)
479 require("luci.cbi")
480 require("luci.template")
481
482 return function(...)
483 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
484 if not stat then
485 error500(maps)
486 return true
487 end
488
489 for i, res in ipairs(maps) do
490 local stat, err = luci.util.copcall(res.parse, res)
491 if not stat then
492 error500(err)
493 return true
494 end
495 end
496
497 luci.template.render("header")
498 for i, res in ipairs(maps) do
499 res:render()
500 end
501 luci.template.render("footer")
502 end
503 end