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)
137 ctx.requestargs = ctx.requestargs or args
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)
302 local controllers = util.combine(
303 luci.fs.glob(path .. "*" .. suffix) or {},
304 luci.fs.glob(path .. "*/*" .. suffix) or {}
308 local cachedate = fs.mtime(indexcache)
311 for _, obj in ipairs(controllers) do
312 local omtime = fs.mtime(path .. "/" .. obj)
313 realdate = (omtime and omtime > realdate) and omtime or realdate
316 if cachedate > realdate then
318 sys.process.info("uid") == fs.stat(indexcache, "uid")
319 and fs.stat(indexcache, "mode") == "rw-------",
320 "Fatal: Indexcache is not sane!"
323 index = loadfile(indexcache)()
331 for i,c in ipairs(controllers) do
332 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
333 local mod = require(module)
334 local idx = mod.index
336 if type(idx) == "function" then
342 fs.writefile(indexcache, util.get_bytecode(index))
343 fs.chmod(indexcache, "a-rwx,u+rw")
347 --- Create the dispatching tree from the index.
348 -- Build the index before if it does not exist yet.
349 function createtree()
355 local tree = {nodes={}}
357 ctx.treecache = setmetatable({}, {__mode="v"})
360 -- Load default translation
361 require "luci.i18n".loadc("default")
363 local scope = setmetatable({}, {__index = luci.dispatcher})
365 for k, v in pairs(index) do
374 --- Clone a node of the dispatching tree to another position.
375 -- @param path Virtual path destination
376 -- @param clone Virtual path source
377 -- @param title Destination node title (optional)
378 -- @param order Destination node order value (optional)
379 -- @return Dispatching tree node
380 function assign(path, clone, title, order)
381 local obj = node(unpack(path))
388 setmetatable(obj, {__index = _create_node(clone)})
393 --- Create a new dispatching node and define common parameters.
394 -- @param path Virtual path
395 -- @param target Target function to call when dispatched.
396 -- @param title Destination node title
397 -- @param order Destination node order value (optional)
398 -- @return Dispatching tree node
399 function entry(path, target, title, order)
400 local c = node(unpack(path))
405 c.module = getfenv(2)._NAME
410 --- Fetch or create a new dispatching node.
411 -- @param ... Virtual path
412 -- @return Dispatching tree node
414 local c = _create_node({...})
416 c.module = getfenv(2)._NAME
422 function _create_node(path, cache)
427 cache = cache or context.treecache
428 local name = table.concat(path, ".")
429 local c = cache[name]
432 local new = {nodes={}, auto=true, path=util.clone(path)}
433 local last = table.remove(path)
435 c = _create_node(path, cache)
448 --- Create a redirect to another dispatching node.
449 -- @param ... Virtual path destination
453 for _, r in ipairs({...}) do
461 --- Rewrite the first x path values of the request.
462 -- @param n Number of path values to replace
463 -- @param ... Virtual path to replace removed path values with
464 function rewrite(n, ...)
467 local dispatched = util.clone(context.dispatched)
470 table.remove(dispatched, 1)
473 for i, r in ipairs(req) do
474 table.insert(dispatched, i, r)
477 for _, r in ipairs({...}) do
478 dispatched[#dispatched+1] = r
485 --- Create a function-call dispatching target.
486 -- @param name Target function of local controller
487 -- @param ... Additional parameters passed to the function
488 function call(name, ...)
492 return getfenv()[name](unpack(argv), ...)
494 return getfenv()[name](...)
499 --- Create a template render dispatching target.
500 -- @param name Template to be rendered
501 function template(name)
503 require("luci.template")
504 luci.template.render(name)
508 --- Create a CBI model dispatching target.
509 -- @param model CBI model tpo be rendered
510 function cbi(model, config)
511 config = config or {}
514 require("luci.template")
515 local http = require "luci.http"
517 maps = luci.cbi.load(model, ...)
521 for i, res in ipairs(maps) do
522 if config.autoapply then
523 res.autoapply = config.autoapply
525 local cstate = res:parse()
526 if not state or cstate < state then
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
536 luci.template.render("cbi/footer", {state = state, autoapply = config.autoapply})
540 --- Create a CBI form model dispatching target.
541 -- @param model CBI form model tpo be rendered
545 require("luci.template")
546 local http = require "luci.http"
548 maps = luci.cbi.load(model, ...)
552 for i, res in ipairs(maps) do
553 local cstate = res:parse()
554 if not state or cstate < state then
559 http.header("X-CBI-State", state or 0)
560 luci.template.render("header")
561 for i, res in ipairs(maps) do
564 luci.template.render("footer")