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 (c and c.index) or not track.notemplate then
165 local tpl = require("luci.template")
166 local media = track.mediaurlbase or luci.config.main.mediaurlbase
167 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
169 for name, theme in pairs(luci.config.themes) do
170 if name:sub(1,1) ~= "." and pcall(tpl.Template,
171 "themes/%s/header" % fs.basename(theme)) then
175 assert(media, "No valid theme found")
178 local viewns = setmetatable({}, {__index=_G})
179 tpl.context.viewns = viewns
180 viewns.write = luci.http.write
181 viewns.include = function(name) tpl.Template(name):render(getfenv(2)) end
182 viewns.translate = function(...) return require("luci.i18n").translate(...) end
183 viewns.striptags = util.striptags
184 viewns.controller = luci.http.getenv("SCRIPT_NAME")
186 viewns.theme = fs.basename(media)
187 viewns.resource = luci.config.main.resourcebase
188 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
191 track.dependent = (track.dependent ~= false)
192 assert(not track.dependent or not track.auto, "Access Violation")
194 if track.sysauth then
195 local sauth = require "luci.sauth"
197 local authen = type(track.sysauth_authenticator) == "function"
198 and track.sysauth_authenticator
199 or authenticator[track.sysauth_authenticator]
201 local def = (type(track.sysauth) == "string") and track.sysauth
202 local accs = def and {track.sysauth} or track.sysauth
203 local sess = ctx.authsession or luci.http.getcookie("sysauth")
204 sess = sess and sess:match("^[A-F0-9]+$")
205 local user = sauth.read(sess)
207 if not util.contains(accs, user) then
209 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
210 if not user or not util.contains(accs, user) then
213 local sid = sess or luci.sys.uniqueid(16)
214 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
216 sauth.write(sid, user)
218 ctx.authsession = sid
221 luci.http.status(403, "Forbidden")
227 if track.setgroup then
228 luci.sys.process.setgroup(track.setgroup)
231 if track.setuser then
232 luci.sys.process.setuser(track.setuser)
235 if c and (c.index or type(c.target) == "function") then
237 ctx.requested = ctx.requested or ctx.dispatched
240 if c and c.index then
241 local tpl = require "luci.template"
243 if util.copcall(tpl.render, "indexer", {}) then
248 if c and type(c.target) == "function" then
249 util.copcall(function()
250 local oldenv = getfenv(c.target)
251 local module = require(c.module)
252 local env = setmetatable({}, {__index=
255 return rawget(tbl, key) or module[key] or oldenv[key]
258 setfenv(c.target, env)
261 c.target(unpack(args))
267 --- Generate the dispatching index using the best possible strategy.
268 function createindex()
269 local path = luci.util.libpath() .. "/controller/"
272 if luci.util.copcall(require, "luci.fastindex") then
273 createindex_fastindex(path, suff)
275 createindex_plain(path, suff)
279 --- Generate the dispatching index using the fastindex C-indexer.
280 -- @param path Controller base directory
281 -- @param suffix Controller file suffix
282 function createindex_fastindex(path, suffix)
286 fi = luci.fastindex.new("index")
287 fi.add(path .. "*" .. suffix)
288 fi.add(path .. "*/*" .. suffix)
292 for k, v in pairs(fi.indexes) do
297 --- Generate the dispatching index using the native file-cache based strategy.
298 -- @param path Controller base directory
299 -- @param suffix Controller file suffix
300 function createindex_plain(path, suffix)
302 local cachedate = fs.mtime(indexcache)
303 if cachedate and cachedate > fs.mtime(path) then
306 sys.process.info("uid") == fs.stat(indexcache, "uid")
307 and fs.stat(indexcache, "mode") == "rw-------",
308 "Fatal: Indexcache is not sane!"
311 index = loadfile(indexcache)()
318 local controllers = util.combine(
319 luci.fs.glob(path .. "*" .. suffix) or {},
320 luci.fs.glob(path .. "*/*" .. suffix) or {}
323 for i,c in ipairs(controllers) do
324 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
325 local mod = require(module)
326 local idx = mod.index
328 if type(idx) == "function" then
334 fs.writefile(indexcache, util.get_bytecode(index))
335 fs.chmod(indexcache, "a-rwx,u+rw")
339 --- Create the dispatching tree from the index.
340 -- Build the index before if it does not exist yet.
341 function createtree()
347 local tree = {nodes={}}
349 ctx.treecache = setmetatable({}, {__mode="v"})
352 -- Load default translation
353 require "luci.i18n".loadc("default")
355 local scope = setmetatable({}, {__index = luci.dispatcher})
357 for k, v in pairs(index) do
366 --- Clone a node of the dispatching tree to another position.
367 -- @param path Virtual path destination
368 -- @param clone Virtual path source
369 -- @param title Destination node title (optional)
370 -- @param order Destination node order value (optional)
371 -- @return Dispatching tree node
372 function assign(path, clone, title, order)
373 local obj = node(unpack(path))
380 setmetatable(obj, {__index = _create_node(clone)})
385 --- Create a new dispatching node and define common parameters.
386 -- @param path Virtual path
387 -- @param target Target function to call when dispatched.
388 -- @param title Destination node title
389 -- @param order Destination node order value (optional)
390 -- @return Dispatching tree node
391 function entry(path, target, title, order)
392 local c = node(unpack(path))
397 c.module = getfenv(2)._NAME
402 --- Fetch or create a new dispatching node.
403 -- @param ... Virtual path
404 -- @return Dispatching tree node
406 local c = _create_node({...})
408 c.module = getfenv(2)._NAME
415 function _create_node(path, cache)
420 cache = cache or context.treecache
421 local name = table.concat(path, ".")
422 local c = cache[name]
425 local last = table.remove(path)
426 c = _create_node(path, cache)
428 local new = {nodes={}, auto=true}
440 --- Create a redirect to another dispatching node.
441 -- @param ... Virtual path destination
445 for _, r in ipairs({...}) do
453 --- Rewrite the first x path values of the request.
454 -- @param n Number of path values to replace
455 -- @param ... Virtual path to replace removed path values with
456 function rewrite(n, ...)
459 local dispatched = util.clone(context.dispatched)
462 table.remove(dispatched, 1)
465 for i, r in ipairs(req) do
466 table.insert(dispatched, i, r)
469 for _, r in ipairs({...}) do
470 dispatched[#dispatched+1] = r
477 --- Create a function-call dispatching target.
478 -- @param name Target function of local controller
479 -- @param ... Additional parameters passed to the function
480 function call(name, ...)
484 return getfenv()[name](unpack(argv), ...)
486 return getfenv()[name](...)
491 --- Create a template render dispatching target.
492 -- @param name Template to be rendered
493 function template(name)
495 require("luci.template")
496 luci.template.render(name)
500 --- Create a CBI model dispatching target.
501 -- @param model CBI model tpo be rendered
505 require("luci.template")
506 local http = require "luci.http"
508 maps = luci.cbi.load(model, ...)
512 for i, res in ipairs(maps) do
513 local cstate = res:parse()
514 if not state or cstate < state then
519 http.header("X-CBI-State", state or 0)
520 luci.template.render("cbi/header", {state = state})
521 for i, res in ipairs(maps) do
524 luci.template.render("cbi/footer", {state = state})
528 --- Create a CBI form model dispatching target.
529 -- @param model CBI form model tpo be rendered
533 require("luci.template")
534 local http = require "luci.http"
536 maps = luci.cbi.load(model, ...)
540 for i, res in ipairs(maps) do
541 local cstate = res:parse()
542 if not state or cstate < state then
547 http.header("X-CBI-State", state or 0)
548 luci.template.render("header")
549 for i, res in ipairs(maps) do
552 luci.template.render("footer")