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)
116 --context._disable_memtrace()
119 --- Dispatches a LuCI virtual path.
120 -- @param request Virtual path
121 function dispatch(request)
122 --context._disable_memtrace = require "luci.debug".trap_memtrace()
126 require "luci.i18n".setlanguage(require "luci.config".main.lang)
139 for i, s in ipairs(request) do
146 util.update(track, c)
154 for j=n+1, #request do
155 table.insert(args, request[j])
160 require("luci.i18n").loadc(track.i18n)
163 -- Init template engine
164 if not track.notemplate then
165 local tpl = require("luci.template")
166 local viewns = setmetatable({}, {__index=_G})
167 tpl.context.viewns = viewns
168 viewns.write = luci.http.write
169 viewns.include = function(name) tpl.Template(name):render(getfenv(2)) end
170 viewns.translate = function(...) return require("luci.i18n").translate(...) end
171 viewns.striptags = util.striptags
172 viewns.controller = luci.http.getenv("SCRIPT_NAME")
173 viewns.media = luci.config.main.mediaurlbase
174 viewns.resource = luci.config.main.resourcebase
175 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
178 track.dependent = (track.dependent ~= false)
179 assert(not track.dependent or not track.auto, "Access Violation")
181 if track.sysauth then
182 local sauth = require "luci.sauth"
184 local authen = type(track.sysauth_authenticator) == "function"
185 and track.sysauth_authenticator
186 or authenticator[track.sysauth_authenticator]
188 local def = (type(track.sysauth) == "string") and track.sysauth
189 local accs = def and {track.sysauth} or track.sysauth
190 local sess = ctx.authsession or luci.http.getcookie("sysauth")
191 sess = sess and sess:match("^[A-F0-9]+$")
192 local user = sauth.read(sess)
194 if not util.contains(accs, user) then
196 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
197 if not user or not util.contains(accs, user) then
200 local sid = sess or luci.sys.uniqueid(16)
201 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
203 sauth.write(sid, user)
205 ctx.authsession = sid
208 luci.http.status(403, "Forbidden")
214 if track.setgroup then
215 luci.sys.process.setgroup(track.setgroup)
218 if track.setuser then
219 luci.sys.process.setuser(track.setuser)
222 if c and type(c.target) == "function" then
223 context.dispatched = c
225 util.copcall(function()
226 local oldenv = getfenv(c.target)
227 local module = require(c.module)
228 local env = setmetatable({}, {__index=
231 return rawget(tbl, key) or module[key] or oldenv[key]
234 setfenv(c.target, env)
237 c.target(unpack(args))
243 --- Generate the dispatching index using the best possible strategy.
244 function createindex()
245 local path = luci.util.libpath() .. "/controller/"
248 if luci.util.copcall(require, "luci.fastindex") then
249 createindex_fastindex(path, suff)
251 createindex_plain(path, suff)
255 --- Generate the dispatching index using the fastindex C-indexer.
256 -- @param path Controller base directory
257 -- @param suffix Controller file suffix
258 function createindex_fastindex(path, suffix)
262 fi = luci.fastindex.new("index")
263 fi.add(path .. "*" .. suffix)
264 fi.add(path .. "*/*" .. suffix)
268 for k, v in pairs(fi.indexes) do
273 --- Generate the dispatching index using the native file-cache based strategy.
274 -- @param path Controller base directory
275 -- @param suffix Controller file suffix
276 function createindex_plain(path, suffix)
278 local cachedate = fs.mtime(indexcache)
279 if cachedate and cachedate > fs.mtime(path) then
282 sys.process.info("uid") == fs.stat(indexcache, "uid")
283 and fs.stat(indexcache, "mode") == "rw-------",
284 "Fatal: Indexcache is not sane!"
287 index = loadfile(indexcache)()
294 local controllers = util.combine(
295 luci.fs.glob(path .. "*" .. suffix) or {},
296 luci.fs.glob(path .. "*/*" .. suffix) or {}
299 for i,c in ipairs(controllers) do
300 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
301 local mod = require(module)
302 local idx = mod.index
304 if type(idx) == "function" then
310 fs.writefile(indexcache, util.get_bytecode(index))
311 fs.chmod(indexcache, "a-rwx,u+rw")
315 --- Create the dispatching tree from the index.
316 -- Build the index before if it does not exist yet.
317 function createtree()
323 local tree = {nodes={}}
325 ctx.treecache = setmetatable({}, {__mode="v"})
328 -- Load default translation
329 require "luci.i18n".loadc("default")
331 local scope = setmetatable({}, {__index = luci.dispatcher})
333 for k, v in pairs(index) do
342 --- Clone a node of the dispatching tree to another position.
343 -- @param path Virtual path destination
344 -- @param clone Virtual path source
345 -- @param title Destination node title (optional)
346 -- @param order Destination node order value (optional)
347 -- @return Dispatching tree node
348 function assign(path, clone, title, order)
349 local obj = node(unpack(path))
356 setmetatable(obj, {__index = _create_node(clone)})
361 --- Create a new dispatching node and define common parameters.
362 -- @param path Virtual path
363 -- @param target Target function to call when dispatched.
364 -- @param title Destination node title
365 -- @param order Destination node order value (optional)
366 -- @return Dispatching tree node
367 function entry(path, target, title, order)
368 local c = node(unpack(path))
373 c.module = getfenv(2)._NAME
378 --- Fetch or create a new dispatching node.
379 -- @param ... Virtual path
380 -- @return Dispatching tree node
382 local c = _create_node({...})
384 c.module = getfenv(2)._NAME
391 function _create_node(path, cache)
396 cache = cache or context.treecache
397 local name = table.concat(path, ".")
398 local c = cache[name]
401 local last = table.remove(path)
402 c = _create_node(path, cache)
404 local new = {nodes={}, auto=true}
416 --- Create a redirect to another dispatching node.
417 -- @param ... Virtual path destination
425 --- Rewrite the first x path values of the request.
426 -- @param n Number of path values to replace
427 -- @param ... Virtual path to replace removed path values with
428 function rewrite(n, ...)
432 table.remove(context.path, 1)
435 for i,r in ipairs(req) do
436 table.insert(context.path, i, r)
443 --- Create a function-call dispatching target.
444 -- @param name Target function of local controller
445 -- @param ... Additional parameters passed to the function
446 function call(name, ...)
448 return function() return getfenv()[name](unpack(argv)) end
451 --- Create a template render dispatching target.
452 -- @param name Template to be rendered
453 function template(name)
455 require("luci.template")
456 luci.template.render(name)
460 --- Create a CBI model dispatching target.
461 -- @param model CBI model tpo be rendered
465 require("luci.template")
467 maps = luci.cbi.load(model, ...)
469 for i, res in ipairs(maps) do
473 luci.template.render("cbi/header")
474 for i, res in ipairs(maps) do
477 luci.template.render("cbi/footer")
481 --- Create a CBI form model dispatching target.
482 -- @param model CBI form model tpo be rendered
486 require("luci.template")
488 maps = luci.cbi.load(model, ...)
490 for i, res in ipairs(maps) do
494 luci.template.render("header")
495 for i, res in ipairs(maps) do
498 luci.template.render("footer")