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
128 context.args = context.path
131 for i, s in ipairs(request) do
138 for k, v in pairs(c) do
148 for j=n+1, #request do
149 table.insert(args, request[j])
154 require("luci.i18n").loadc(track.i18n)
157 -- Init template engine
158 local tpl = require("luci.template")
160 tpl.context.viewns = viewns
161 viewns.write = luci.http.write
162 viewns.translate = function(...) return require("luci.i18n").translate(...) end
163 viewns.striptags = luci.util.striptags
164 viewns.controller = luci.http.getenv("SCRIPT_NAME")
165 viewns.media = luci.config.main.mediaurlbase
166 viewns.resource = luci.config.main.resourcebase
167 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
169 if track.dependent then
170 local stat, err = pcall(assert, not track.auto)
177 if track.sysauth then
178 require("luci.sauth")
179 local authen = type(track.sysauth_authenticator) == "function"
180 and track.sysauth_authenticator
181 or authenticator[track.sysauth_authenticator]
182 local def = (type(track.sysauth) == "string") and track.sysauth
183 local accs = def and {track.sysauth} or track.sysauth
184 local sess = luci.http.getcookie("sysauth")
185 sess = sess and sess:match("^[A-F0-9]+$")
186 local user = luci.sauth.read(sess)
188 if not luci.util.contains(accs, user) then
190 local user = authen(luci.sys.user.checkpasswd, def)
191 if not user or not luci.util.contains(accs, user) then
192 luci.http.status(403, "Forbidden")
195 local sid = luci.sys.uniqueid(16)
196 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
197 luci.sauth.write(sid, user)
200 luci.http.status(403, "Forbidden")
206 if track.setgroup then
207 luci.sys.process.setgroup(track.setgroup)
210 if track.setuser then
211 luci.sys.process.setuser(track.setuser)
214 if c and type(c.target) == "function" then
215 context.dispatched = c
216 stat, mod = luci.util.copcall(require, c.module)
218 luci.util.updfenv(c.target, mod)
221 stat, err = luci.util.copcall(c.target, unpack(args))
230 --- Generate the dispatching index using the best possible strategy.
231 function createindex()
232 local path = luci.util.libpath() .. "/controller/"
235 if luci.util.copcall(require, "luci.fastindex") then
236 createindex_fastindex(path, suff)
238 createindex_plain(path, suff)
242 --- Generate the dispatching index using the fastindex C-indexer.
243 -- @param path Controller base directory
244 -- @param suffix Controller file suffix
245 function createindex_fastindex(path, suffix)
249 fi = luci.fastindex.new("index")
250 fi.add(path .. "*" .. suffix)
251 fi.add(path .. "*/*" .. suffix)
255 for k, v in pairs(fi.indexes) do
260 --- Generate the dispatching index using the native file-cache based strategy.
261 -- @param path Controller base directory
262 -- @param suffix Controller file suffix
263 function createindex_plain(path, suffix)
268 local controllers = luci.util.combine(
269 luci.fs.glob(path .. "*" .. suffix) or {},
270 luci.fs.glob(path .. "*/*" .. suffix) or {}
274 cache = luci.fs.mtime(indexcache)
277 luci.fs.mkdir(indexcache)
278 luci.fs.chmod(indexcache, "a=,u=rwx")
279 cache = luci.fs.mtime(indexcache)
283 for i,c in ipairs(controllers) do
284 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
290 cachefile = indexcache .. "/" .. module
291 stime = luci.fs.mtime(c) or 0
292 ctime = luci.fs.mtime(cachefile) or 0
295 if not cache or stime > ctime then
296 stat, mod = luci.util.copcall(require, module)
298 if stat and mod and type(mod.index) == "function" then
299 index[module] = mod.index
302 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
306 index[module] = loadfile(cachefile)
311 --- Create the dispatching tree from the index.
312 -- Build the index before if it does not exist yet.
313 function createtree()
318 context.tree = {nodes={}}
321 -- Load default translation
322 luci.i18n.loadc("default")
324 local scope = luci.util.clone(_G)
325 for k,v in pairs(luci.dispatcher) do
326 if type(v) == "function" then
331 for k, v in pairs(index) do
335 local stat, err = luci.util.copcall(v)
337 error500("createtree failed: " .. k .. ": " .. err)
344 --- Clone a node of the dispatching tree to another position.
345 -- @param path Virtual path destination
346 -- @param clone Virtual path source
347 -- @param title Destination node title (optional)
348 -- @param order Destination node order value (optional)
349 -- @return Dispatching tree node
350 function assign(path, clone, title, order)
351 local obj = node(unpack(path))
358 local c = context.tree
359 for k, v in ipairs(clone) do
360 if not c.nodes[v] then
361 c.nodes[v] = {nodes={}}
367 setmetatable(obj, {__index = c})
372 --- Create a new dispatching node and define common parameters.
373 -- @param path Virtual path
374 -- @param target Target function to call when dispatched.
375 -- @param title Destination node title
376 -- @param order Destination node order value (optional)
377 -- @return Dispatching tree node
378 function entry(path, target, title, order)
379 local c = node(unpack(path))
384 c.module = getfenv(2)._NAME
389 --- Fetch or create a new dispatching node.
390 -- @param ... Virtual path
391 -- @return Dispatching tree node
393 local c = context.tree
396 for k,v in ipairs(arg) do
397 if not c.nodes[v] then
398 c.nodes[v] = {nodes={}, auto=true}
404 c.module = getfenv(2)._NAME
413 --- Create a redirect to another dispatching node.
414 -- @param ... Virtual path destination
422 --- Rewrite the first x path values of the request.
423 -- @param n Number of path values to replace
424 -- @param ... Virtual path to replace removed path values with
425 function rewrite(n, ...)
429 table.remove(context.path, 1)
432 for i,r in ipairs(req) do
433 table.insert(context.path, i, r)
440 --- Create a function-call dispatching target.
441 -- @param name Target function of local controller
442 -- @param ... Additional parameters passed to the function
443 function call(name, ...)
445 return function() return getfenv()[name](unpack(argv)) end
448 --- Create a template render dispatching target.
449 -- @param name Template to be rendered
450 function template(name)
451 require("luci.template")
452 return function() luci.template.render(name) end
455 --- Create a CBI model dispatching target.
456 -- @param model CBI model tpo be rendered
459 require("luci.template")
462 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
468 for i, res in ipairs(maps) do
469 local stat, err = luci.util.copcall(res.parse, res)
476 luci.template.render("cbi/header")
477 for i, res in ipairs(maps) do
480 luci.template.render("cbi/footer")
484 --- Create a CBI form model dispatching target.
485 -- @param model CBI form model tpo be rendered
488 require("luci.template")
491 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
497 for i, res in ipairs(maps) do
498 local stat, err = luci.util.copcall(res.parse, res)
505 luci.template.render("header")
506 for i, res in ipairs(maps) do
509 luci.template.render("footer")