5 The request dispatcher and module dispatcher generators
11 Copyright 2008 Steven Barth <steven@midlink.org>
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
17 http://www.apache.org/licenses/LICENSE-2.0
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.
27 --- LuCI web dispatcher.
28 module("luci.dispatcher", package.seeall)
35 context = luci.util.threadlocal()
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, "/")
53 --- Send a 404 error code and render the "error404" template if available.
54 -- @param message Custom error message (optional)
56 function error404(message)
57 luci.http.status(404, "Not Found")
58 message = message or "Not Found"
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)
68 --- Send a 500 error code and render the "error500" template if available.
69 -- @param message Custom error message (optional)#
71 function error500(message)
72 luci.http.status(500, "Internal Server Error")
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)
82 function authenticator.htmlauth(validator, default)
83 local user = luci.http.formvalue("username")
84 local pass = luci.http.formvalue("password")
86 if user and validator(user, pass) then
91 require("luci.template")
93 luci.template.render("sysauth", {duser=default, fuser=user})
98 --- Dispatch an HTTP request.
99 -- @param request LuCI HTTP Request object
100 function httpdispatch(request)
101 luci.http.context.request = request
103 local pathinfo = request:getenv("PATH_INFO") or ""
105 for node in pathinfo:gmatch("[^/]+") do
106 table.insert(context.request, node)
109 dispatch(context.request)
113 --- Dispatches a LuCI virtual path.
114 -- @param request Virtual path
115 function dispatch(request)
116 context.path = request
119 luci.i18n.setlanguage(require("luci.config").main.lang)
121 if not context.tree then
125 local c = context.tree
130 for i, s in ipairs(request) do
133 if not c or c.leaf then
137 for k, v in pairs(c) do
143 for j=n+1, #request do
144 table.insert(args, request[j])
149 require("luci.i18n").loadc(track.i18n)
152 -- Init template engine
153 local tpl = require("luci.template")
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 "")
164 if track.dependent then
165 local stat, err = pcall(assert, not track.auto)
172 if track.sysauth then
173 require("luci.sauth")
174 local authen = type(track.sysauth_authenticator) == "function"
175 and track.sysauth_authenticator
176 or authenticator[track.sysauth_authenticator]
177 local def = (type(track.sysauth) == "string") and track.sysauth
178 local accs = def and {track.sysauth} or track.sysauth
179 local sess = luci.http.getcookie("sysauth")
180 sess = sess and sess:match("^[A-F0-9]+$")
181 local user = luci.sauth.read(sess)
183 if not luci.util.contains(accs, user) then
185 local user = authen(luci.sys.user.checkpasswd, def)
186 if not user or not luci.util.contains(accs, user) then
189 local sid = luci.sys.uniqueid(16)
190 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
191 luci.sauth.write(sid, user)
194 luci.http.status(403, "Forbidden")
200 if track.setgroup then
201 luci.sys.process.setgroup(track.setgroup)
204 if track.setuser then
205 luci.sys.process.setuser(track.setuser)
208 if c and type(c.target) == "function" then
209 context.dispatched = c
210 stat, mod = luci.util.copcall(require, c.module)
212 luci.util.updfenv(c.target, mod)
215 stat, err = luci.util.copcall(c.target, unpack(args))
224 --- Generate the dispatching index using the best possible strategy.
225 function createindex()
226 local path = luci.util.libpath() .. "/controller/"
229 if luci.util.copcall(require, "luci.fastindex") then
230 createindex_fastindex(path, suff)
232 createindex_plain(path, suff)
236 --- Generate the dispatching index using the fastindex C-indexer.
237 -- @param path Controller base directory
238 -- @param suffix Controller file suffix
239 function createindex_fastindex(path, suffix)
243 fi = luci.fastindex.new("index")
244 fi.add(path .. "*" .. suffix)
245 fi.add(path .. "*/*" .. suffix)
249 for k, v in pairs(fi.indexes) do
254 --- Generate the dispatching index using the native file-cache based strategy.
255 -- @param path Controller base directory
256 -- @param suffix Controller file suffix
257 function createindex_plain(path, suffix)
262 local controllers = luci.util.combine(
263 luci.fs.glob(path .. "*" .. suffix) or {},
264 luci.fs.glob(path .. "*/*" .. suffix) or {}
268 cache = luci.fs.mtime(indexcache)
271 luci.fs.mkdir(indexcache)
272 luci.fs.chmod(indexcache, "a=,u=rwx")
273 cache = luci.fs.mtime(indexcache)
277 for i,c in ipairs(controllers) do
278 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
284 cachefile = indexcache .. "/" .. module
285 stime = luci.fs.mtime(c) or 0
286 ctime = luci.fs.mtime(cachefile) or 0
289 if not cache or stime > ctime then
290 stat, mod = luci.util.copcall(require, module)
292 if stat and mod and type(mod.index) == "function" then
293 index[module] = mod.index
296 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
300 index[module] = loadfile(cachefile)
305 --- Create the dispatching tree from the index.
306 -- Build the index before if it does not exist yet.
307 function createtree()
312 context.tree = {nodes={}}
315 -- Load default translation
316 luci.i18n.loadc("default")
318 local scope = luci.util.clone(_G)
319 for k,v in pairs(luci.dispatcher) do
320 if type(v) == "function" then
325 for k, v in pairs(index) do
329 local stat, err = luci.util.copcall(v)
331 error500("createtree failed: " .. k .. ": " .. err)
338 --- Clone a node of the dispatching tree to another position.
339 -- @param path Virtual path destination
340 -- @param clone Virtual path source
341 -- @param title Destination node title (optional)
342 -- @param order Destination node order value (optional)
343 -- @return Dispatching tree node
344 function assign(path, clone, title, order)
345 local obj = node(unpack(path))
352 local c = context.tree
353 for k, v in ipairs(clone) do
354 if not c.nodes[v] then
355 c.nodes[v] = {nodes={}}
361 setmetatable(obj, {__index = c})
366 --- Create a new dispatching node and define common parameters.
367 -- @param path Virtual path
368 -- @param target Target function to call when dispatched.
369 -- @param title Destination node title
370 -- @param order Destination node order value (optional)
371 -- @return Dispatching tree node
372 function entry(path, target, title, order)
373 local c = node(unpack(path))
378 c.module = getfenv(2)._NAME
383 --- Fetch or create a new dispatching node.
384 -- @param ... Virtual path
385 -- @return Dispatching tree node
387 local c = context.tree
390 for k,v in ipairs(arg) do
391 if not c.nodes[v] then
392 c.nodes[v] = {nodes={}, auto=true}
398 c.module = getfenv(2)._NAME
407 --- Create a redirect to another dispatching node.
408 -- @param ... Virtual path destination
416 --- Rewrite the first x path values of the request.
417 -- @param n Number of path values to replace
418 -- @param ... Virtual path to replace removed path values with
419 function rewrite(n, ...)
423 table.remove(context.path, 1)
426 for i,r in ipairs(req) do
427 table.insert(context.path, i, r)
434 --- Create a function-call dispatching target.
435 -- @param name Target function of local controller
436 -- @param ... Additional parameters passed to the function
437 function call(name, ...)
439 return function() return getfenv()[name](unpack(argv)) end
442 --- Create a template render dispatching target.
443 -- @param name Template to be rendered
444 function template(name)
445 require("luci.template")
446 return function() luci.template.render(name) end
449 --- Create a CBI model dispatching target.
450 -- @param model CBI model tpo be rendered
453 require("luci.template")
456 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
462 for i, res in ipairs(maps) do
463 local stat, err = luci.util.copcall(res.parse, res)
470 luci.template.render("cbi/header")
471 for i, res in ipairs(maps) do
474 luci.template.render("cbi/footer")
478 --- Create a CBI form model dispatching target.
479 -- @param model CBI form model tpo be rendered
482 require("luci.template")
485 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
491 for i, res in ipairs(maps) do
492 local stat, err = luci.util.copcall(res.parse, res)
499 luci.template.render("header")
500 for i, res in ipairs(maps) do
503 luci.template.render("footer")