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)
111 luci.util.perror(err)
117 --context._disable_memtrace()
120 --- Dispatches a LuCI virtual path.
121 -- @param request Virtual path
122 function dispatch(request)
123 --context._disable_memtrace = require "luci.debug".trap_memtrace()
127 require "luci.i18n".setlanguage(require "luci.config".main.lang)
140 for i, s in ipairs(request) do
147 util.update(track, c)
155 for j=n+1, #request do
156 table.insert(args, request[j])
161 require("luci.i18n").loadc(track.i18n)
164 -- Init template engine
165 if (c and c.index) or not track.notemplate then
166 local tpl = require("luci.template")
167 local media = track.mediaurlbase or luci.config.main.mediaurlbase
168 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
170 for name, theme in pairs(luci.config.themes) do
171 if name:sub(1,1) ~= "." and pcall(tpl.Template,
172 "themes/%s/header" % fs.basename(theme)) then
176 assert(media, "No valid theme found")
179 local viewns = setmetatable({}, {__index=_G})
180 tpl.context.viewns = viewns
181 viewns.write = luci.http.write
182 viewns.include = function(name) tpl.Template(name):render(getfenv(2)) end
183 viewns.translate = function(...) return require("luci.i18n").translate(...) end
184 viewns.striptags = util.striptags
185 viewns.controller = luci.http.getenv("SCRIPT_NAME")
187 viewns.theme = fs.basename(media)
188 viewns.resource = luci.config.main.resourcebase
189 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
192 track.dependent = (track.dependent ~= false)
193 assert(not track.dependent or not track.auto, "Access Violation")
195 if track.sysauth then
196 local sauth = require "luci.sauth"
198 local authen = type(track.sysauth_authenticator) == "function"
199 and track.sysauth_authenticator
200 or authenticator[track.sysauth_authenticator]
202 local def = (type(track.sysauth) == "string") and track.sysauth
203 local accs = def and {track.sysauth} or track.sysauth
204 local sess = ctx.authsession or luci.http.getcookie("sysauth")
205 sess = sess and sess:match("^[A-F0-9]+$")
206 local user = sauth.read(sess)
208 if not util.contains(accs, user) then
210 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
211 if not user or not util.contains(accs, user) then
214 local sid = sess or luci.sys.uniqueid(16)
215 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
217 sauth.write(sid, user)
219 ctx.authsession = sid
222 luci.http.status(403, "Forbidden")
228 if track.setgroup then
229 luci.sys.process.setgroup(track.setgroup)
232 if track.setuser then
233 luci.sys.process.setuser(track.setuser)
236 if c and (c.index or type(c.target) == "function") then
238 ctx.requested = ctx.requested or ctx.dispatched
241 if c and c.index then
242 local tpl = require "luci.template"
244 if util.copcall(tpl.render, "indexer", {}) then
249 if c and type(c.target) == "function" then
250 util.copcall(function()
251 local oldenv = getfenv(c.target)
252 local module = require(c.module)
253 local env = setmetatable({}, {__index=
256 return rawget(tbl, key) or module[key] or oldenv[key]
259 setfenv(c.target, env)
262 c.target(unpack(args))
268 --- Generate the dispatching index using the best possible strategy.
269 function createindex()
270 local path = luci.util.libpath() .. "/controller/"
273 if luci.util.copcall(require, "luci.fastindex") then
274 createindex_fastindex(path, suff)
276 createindex_plain(path, suff)
280 --- Generate the dispatching index using the fastindex C-indexer.
281 -- @param path Controller base directory
282 -- @param suffix Controller file suffix
283 function createindex_fastindex(path, suffix)
287 fi = luci.fastindex.new("index")
288 fi.add(path .. "*" .. suffix)
289 fi.add(path .. "*/*" .. suffix)
293 for k, v in pairs(fi.indexes) do
298 --- Generate the dispatching index using the native file-cache based strategy.
299 -- @param path Controller base directory
300 -- @param suffix Controller file suffix
301 function createindex_plain(path, suffix)
303 local cachedate = fs.mtime(indexcache)
304 if cachedate and cachedate > fs.mtime(path) then
307 sys.process.info("uid") == fs.stat(indexcache, "uid")
308 and fs.stat(indexcache, "mode") == "rw-------",
309 "Fatal: Indexcache is not sane!"
312 index = loadfile(indexcache)()
319 local controllers = util.combine(
320 luci.fs.glob(path .. "*" .. suffix) or {},
321 luci.fs.glob(path .. "*/*" .. suffix) or {}
324 for i,c in ipairs(controllers) do
325 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
326 local mod = require(module)
327 local idx = mod.index
329 if type(idx) == "function" then
335 fs.writefile(indexcache, util.get_bytecode(index))
336 fs.chmod(indexcache, "a-rwx,u+rw")
340 --- Create the dispatching tree from the index.
341 -- Build the index before if it does not exist yet.
342 function createtree()
348 local tree = {nodes={}}
350 ctx.treecache = setmetatable({}, {__mode="v"})
353 -- Load default translation
354 require "luci.i18n".loadc("default")
356 local scope = setmetatable({}, {__index = luci.dispatcher})
358 for k, v in pairs(index) do
367 --- Clone a node of the dispatching tree to another position.
368 -- @param path Virtual path destination
369 -- @param clone Virtual path source
370 -- @param title Destination node title (optional)
371 -- @param order Destination node order value (optional)
372 -- @return Dispatching tree node
373 function assign(path, clone, title, order)
374 local obj = node(unpack(path))
381 setmetatable(obj, {__index = _create_node(clone)})
386 --- Create a new dispatching node and define common parameters.
387 -- @param path Virtual path
388 -- @param target Target function to call when dispatched.
389 -- @param title Destination node title
390 -- @param order Destination node order value (optional)
391 -- @return Dispatching tree node
392 function entry(path, target, title, order)
393 local c = node(unpack(path))
398 c.module = getfenv(2)._NAME
403 --- Fetch or create a new dispatching node.
404 -- @param ... Virtual path
405 -- @return Dispatching tree node
407 local c = _create_node({...})
409 c.module = getfenv(2)._NAME
416 function _create_node(path, cache)
421 cache = cache or context.treecache
422 local name = table.concat(path, ".")
423 local c = cache[name]
426 local last = table.remove(path)
427 c = _create_node(path, cache)
429 local new = {nodes={}, auto=true}
441 --- Create a redirect to another dispatching node.
442 -- @param ... Virtual path destination
446 for _, r in ipairs({...}) do
454 --- Rewrite the first x path values of the request.
455 -- @param n Number of path values to replace
456 -- @param ... Virtual path to replace removed path values with
457 function rewrite(n, ...)
460 local dispatched = util.clone(context.dispatched)
463 table.remove(dispatched, 1)
466 for i, r in ipairs(req) do
467 table.insert(dispatched, i, r)
470 for _, r in ipairs({...}) do
471 dispatched[#dispatched+1] = r
478 --- Create a function-call dispatching target.
479 -- @param name Target function of local controller
480 -- @param ... Additional parameters passed to the function
481 function call(name, ...)
485 return getfenv()[name](unpack(argv), ...)
487 return getfenv()[name](...)
492 --- Create a template render dispatching target.
493 -- @param name Template to be rendered
494 function template(name)
496 require("luci.template")
497 luci.template.render(name)
501 --- Create a CBI model dispatching target.
502 -- @param model CBI model to be rendered
503 function cbi(model, config)
504 config = config or {}
507 require("luci.template")
508 local http = require "luci.http"
510 maps = luci.cbi.load(model, ...)
514 for i, res in ipairs(maps) do
515 if config.autoapply then
516 res.autoapply = config.autoapply
518 local cstate = res:parse()
519 if not state or cstate < state then
524 if config.state_handler then
525 if not config.state_handler(state, maps) then
530 local pageaction = true
531 http.header("X-CBI-State", state or 0)
532 luci.template.render("cbi/header", {state = state})
533 for i, res in ipairs(maps) do
535 if res.pageaction == false then
539 luci.template.render("cbi/footer", {pageaction=pageaction, state = state, autoapply = config.autoapply})
543 --- Create a CBI form model dispatching target.
544 -- @param model CBI form model tpo be rendered
548 require("luci.template")
549 local http = require "luci.http"
551 maps = luci.cbi.load(model, ...)
555 for i, res in ipairs(maps) do
556 local cstate = res:parse()
557 if not state or cstate < state then
562 http.header("X-CBI-State", state or 0)
563 luci.template.render("header")
564 for i, res in ipairs(maps) do
567 luci.template.render("footer")