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 local fs = require "luci.fs"
29 local sys = require "luci.sys"
30 local init = require "luci.init"
31 local util = require "luci.util"
32 local http = require "luci.http"
34 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, accs, 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 local stat, err = util.copcall(dispatch, context.request)
117 --- Dispatches a LuCI virtual path.
118 -- @param request Virtual path
119 function dispatch(request)
123 require "luci.i18n".setlanguage(require "luci.config".main.lang)
136 for i, s in ipairs(request) do
143 util.update(track, c)
151 for j=n+1, #request do
152 table.insert(args, request[j])
157 require("luci.i18n").loadc(track.i18n)
160 -- Init template engine
161 if not track.notemplate then
162 local tpl = require("luci.template")
163 local viewns = setmetatable({}, {__index=_G})
164 tpl.context.viewns = viewns
165 viewns.write = luci.http.write
166 viewns.include = function(name) tpl.Template(name):render(getfenv(2)) end
167 viewns.translate = function(...) return require("luci.i18n").translate(...) end
168 viewns.striptags = util.striptags
169 viewns.controller = luci.http.getenv("SCRIPT_NAME")
170 viewns.media = luci.config.main.mediaurlbase
171 viewns.resource = luci.config.main.resourcebase
172 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
175 track.dependent = (track.dependent ~= false)
176 assert(not track.dependent or not track.auto, "Access Violation")
178 if track.sysauth then
179 local sauth = require "luci.sauth"
181 local authen = type(track.sysauth_authenticator) == "function"
182 and track.sysauth_authenticator
183 or authenticator[track.sysauth_authenticator]
185 local def = (type(track.sysauth) == "string") and track.sysauth
186 local accs = def and {track.sysauth} or track.sysauth
187 local sess = ctx.authsession or luci.http.getcookie("sysauth")
188 sess = sess and sess:match("^[A-F0-9]+$")
189 local user = sauth.read(sess)
191 if not util.contains(accs, user) then
193 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
194 if not user or not util.contains(accs, user) then
197 local sid = sess or luci.sys.uniqueid(16)
198 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
200 sauth.write(sid, user)
202 ctx.authsession = sid
205 luci.http.status(403, "Forbidden")
211 if track.setgroup then
212 luci.sys.process.setgroup(track.setgroup)
215 if track.setuser then
216 luci.sys.process.setuser(track.setuser)
219 if c and type(c.target) == "function" then
220 context.dispatched = c
222 util.copcall(function()
223 local oldenv = getfenv(c.target)
224 local module = require(c.module)
225 local env = setmetatable({}, {__index=
228 return rawget(tbl, key) or module[key] or oldenv[key]
231 setfenv(c.target, env)
234 c.target(unpack(args))
240 --- Generate the dispatching index using the best possible strategy.
241 function createindex()
242 local path = luci.util.libpath() .. "/controller/"
245 if luci.util.copcall(require, "luci.fastindex") then
246 createindex_fastindex(path, suff)
248 createindex_plain(path, suff)
252 --- Generate the dispatching index using the fastindex C-indexer.
253 -- @param path Controller base directory
254 -- @param suffix Controller file suffix
255 function createindex_fastindex(path, suffix)
259 fi = luci.fastindex.new("index")
260 fi.add(path .. "*" .. suffix)
261 fi.add(path .. "*/*" .. suffix)
265 for k, v in pairs(fi.indexes) do
270 --- Generate the dispatching index using the native file-cache based strategy.
271 -- @param path Controller base directory
272 -- @param suffix Controller file suffix
273 function createindex_plain(path, suffix)
275 local cachedate = fs.mtime(indexcache)
276 if cachedate and cachedate > fs.mtime(path) then
279 sys.process.info("uid") == fs.stat(indexcache, "uid")
280 and fs.stat(indexcache, "mode") == "rw-------",
281 "Fatal: Indexcache is not sane!"
284 index = loadfile(indexcache)()
291 local controllers = util.combine(
292 luci.fs.glob(path .. "*" .. suffix) or {},
293 luci.fs.glob(path .. "*/*" .. suffix) or {}
296 for i,c in ipairs(controllers) do
297 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
298 local mod = require(module)
299 local idx = mod.index
301 if type(idx) == "function" then
307 fs.writefile(indexcache, util.get_bytecode(index))
308 fs.chmod(indexcache, "a-rwx,u+rw")
312 --- Create the dispatching tree from the index.
313 -- Build the index before if it does not exist yet.
314 function createtree()
320 local tree = {nodes={}}
322 ctx.treecache = setmetatable({}, {__mode="v"})
325 -- Load default translation
326 require "luci.i18n".loadc("default")
328 local scope = setmetatable({}, {__index = _G})
329 for k,v in pairs(luci.dispatcher) do
330 if type(v) == "function" then
335 for k, v in pairs(index) do
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 = _create_node(arg)
395 c.module = getfenv(2)._NAME
402 function _create_node(path, cache)
407 cache = cache or context.treecache
408 local name = table.concat(path, ".")
409 local c = cache[name]
412 local last = table.remove(path)
413 c = _create_node(path, cache)
415 local new = {nodes={}, auto=true}
427 --- Create a redirect to another dispatching node.
428 -- @param ... Virtual path destination
436 --- Rewrite the first x path values of the request.
437 -- @param n Number of path values to replace
438 -- @param ... Virtual path to replace removed path values with
439 function rewrite(n, ...)
443 table.remove(context.path, 1)
446 for i,r in ipairs(req) do
447 table.insert(context.path, i, r)
454 --- Create a function-call dispatching target.
455 -- @param name Target function of local controller
456 -- @param ... Additional parameters passed to the function
457 function call(name, ...)
459 return function() return getfenv()[name](unpack(argv)) end
462 --- Create a template render dispatching target.
463 -- @param name Template to be rendered
464 function template(name)
466 require("luci.template")
467 luci.template.render(name)
471 --- Create a CBI model dispatching target.
472 -- @param model CBI model tpo be rendered
476 require("luci.template")
478 maps = luci.cbi.load(model, ...)
480 for i, res in ipairs(maps) do
484 luci.template.render("cbi/header")
485 for i, res in ipairs(maps) do
488 luci.template.render("cbi/footer")
492 --- Create a CBI form model dispatching target.
493 -- @param model CBI form model tpo be rendered
497 require("luci.template")
499 maps = luci.cbi.load(model, ...)
501 for i, res in ipairs(maps) do
505 luci.template.render("header")
506 for i, res in ipairs(maps) do
509 luci.template.render("footer")