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 = 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)
181 if not luci.util.contains(accs, user) then
183 local user = authen(luci.sys.user.checkpasswd, def)
184 if not user or not luci.util.contains(accs, user) then
187 local sid = luci.sys.uniqueid(16)
188 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
189 luci.sauth.write(sid, user)
192 luci.http.status(403, "Forbidden")
198 if track.setgroup then
199 luci.sys.process.setgroup(track.setgroup)
202 if track.setuser then
203 luci.sys.process.setuser(track.setuser)
206 if c and type(c.target) == "function" then
207 context.dispatched = c
208 stat, mod = luci.util.copcall(require, c.module)
210 luci.util.updfenv(c.target, mod)
213 stat, err = luci.util.copcall(c.target, unpack(args))
222 --- Generate the dispatching index using the best possible strategy.
223 function createindex()
224 local path = luci.util.libpath() .. "/controller/"
227 if luci.util.copcall(require, "luci.fastindex") then
228 createindex_fastindex(path, suff)
230 createindex_plain(path, suff)
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)
241 fi = luci.fastindex.new("index")
242 fi.add(path .. "*" .. suffix)
243 fi.add(path .. "*/*" .. suffix)
247 for k, v in pairs(fi.indexes) do
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)
260 local controllers = luci.util.combine(
261 luci.fs.glob(path .. "*" .. suffix) or {},
262 luci.fs.glob(path .. "*/*" .. suffix) or {}
266 cache = luci.fs.mtime(indexcache)
269 luci.fs.mkdir(indexcache)
270 luci.fs.chmod(indexcache, "a=,u=rwx")
271 cache = luci.fs.mtime(indexcache)
275 for i,c in ipairs(controllers) do
276 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
282 cachefile = indexcache .. "/" .. module
283 stime = luci.fs.mtime(c) or 0
284 ctime = luci.fs.mtime(cachefile) or 0
287 if not cache or stime > ctime then
288 stat, mod = luci.util.copcall(require, module)
290 if stat and mod and type(mod.index) == "function" then
291 index[module] = mod.index
294 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
298 index[module] = loadfile(cachefile)
303 --- Create the dispatching tree from the index.
304 -- Build the index before if it does not exist yet.
305 function createtree()
310 context.tree = {nodes={}}
313 -- Load default translation
314 luci.i18n.loadc("default")
316 local scope = luci.util.clone(_G)
317 for k,v in pairs(luci.dispatcher) do
318 if type(v) == "function" then
323 for k, v in pairs(index) do
327 local stat, err = luci.util.copcall(v)
329 error500("createtree failed: " .. k .. ": " .. err)
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))
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={}}
359 setmetatable(obj, {__index = c})
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))
376 c.module = getfenv(2)._NAME
381 --- Fetch or create a new dispatching node.
382 -- @param ... Virtual path
383 -- @return Dispatching tree node
385 local c = context.tree
388 for k,v in ipairs(arg) do
389 if not c.nodes[v] then
390 c.nodes[v] = {nodes={}, auto=true}
396 c.module = getfenv(2)._NAME
405 --- Create a redirect to another dispatching node.
406 -- @param ... Virtual path destination
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, ...)
421 table.remove(context.path, 1)
424 for i,r in ipairs(req) do
425 table.insert(context.path, i, r)
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, ...)
437 return function() return getfenv()[name](unpack(argv)) end
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
447 --- Create a CBI model dispatching target.
448 -- @param model CBI model tpo be rendered
451 require("luci.template")
454 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
460 for i, res in ipairs(maps) do
461 local stat, err = luci.util.copcall(res.parse, res)
468 luci.template.render("cbi/header")
469 for i, res in ipairs(maps) do
472 luci.template.render("cbi/footer")
476 --- Create a CBI form model dispatching target.
477 -- @param model CBI form model tpo be rendered
480 require("luci.template")
483 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
489 for i, res in ipairs(maps) do
490 local stat, err = luci.util.copcall(res.parse, res)
497 luci.template.render("header")
498 for i, res in ipairs(maps) do
501 luci.template.render("footer")