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 = 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 then
236 local tpl = require "luci.template"
237 if util.copcall(tpl.render, "indexer") then
242 if c and type(c.target) == "function" then
243 context.dispatched = c
245 util.copcall(function()
246 local oldenv = getfenv(c.target)
247 local module = require(c.module)
248 local env = setmetatable({}, {__index=
251 return rawget(tbl, key) or module[key] or oldenv[key]
254 setfenv(c.target, env)
257 c.target(unpack(args))
263 --- Generate the dispatching index using the best possible strategy.
264 function createindex()
265 local path = luci.util.libpath() .. "/controller/"
268 if luci.util.copcall(require, "luci.fastindex") then
269 createindex_fastindex(path, suff)
271 createindex_plain(path, suff)
275 --- Generate the dispatching index using the fastindex C-indexer.
276 -- @param path Controller base directory
277 -- @param suffix Controller file suffix
278 function createindex_fastindex(path, suffix)
282 fi = luci.fastindex.new("index")
283 fi.add(path .. "*" .. suffix)
284 fi.add(path .. "*/*" .. suffix)
288 for k, v in pairs(fi.indexes) do
293 --- Generate the dispatching index using the native file-cache based strategy.
294 -- @param path Controller base directory
295 -- @param suffix Controller file suffix
296 function createindex_plain(path, suffix)
298 local cachedate = fs.mtime(indexcache)
299 if cachedate and cachedate > fs.mtime(path) then
302 sys.process.info("uid") == fs.stat(indexcache, "uid")
303 and fs.stat(indexcache, "mode") == "rw-------",
304 "Fatal: Indexcache is not sane!"
307 index = loadfile(indexcache)()
314 local controllers = util.combine(
315 luci.fs.glob(path .. "*" .. suffix) or {},
316 luci.fs.glob(path .. "*/*" .. suffix) or {}
319 for i,c in ipairs(controllers) do
320 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
321 local mod = require(module)
322 local idx = mod.index
324 if type(idx) == "function" then
330 fs.writefile(indexcache, util.get_bytecode(index))
331 fs.chmod(indexcache, "a-rwx,u+rw")
335 --- Create the dispatching tree from the index.
336 -- Build the index before if it does not exist yet.
337 function createtree()
343 local tree = {nodes={}}
345 ctx.treecache = setmetatable({}, {__mode="v"})
348 -- Load default translation
349 require "luci.i18n".loadc("default")
351 local scope = setmetatable({}, {__index = luci.dispatcher})
353 for k, v in pairs(index) do
362 --- Clone a node of the dispatching tree to another position.
363 -- @param path Virtual path destination
364 -- @param clone Virtual path source
365 -- @param title Destination node title (optional)
366 -- @param order Destination node order value (optional)
367 -- @return Dispatching tree node
368 function assign(path, clone, title, order)
369 local obj = node(unpack(path))
376 setmetatable(obj, {__index = _create_node(clone)})
381 --- Create a new dispatching node and define common parameters.
382 -- @param path Virtual path
383 -- @param target Target function to call when dispatched.
384 -- @param title Destination node title
385 -- @param order Destination node order value (optional)
386 -- @return Dispatching tree node
387 function entry(path, target, title, order)
388 local c = node(unpack(path))
393 c.module = getfenv(2)._NAME
398 --- Fetch or create a new dispatching node.
399 -- @param ... Virtual path
400 -- @return Dispatching tree node
402 local c = _create_node({...})
404 c.module = getfenv(2)._NAME
411 function _create_node(path, cache)
416 cache = cache or context.treecache
417 local name = table.concat(path, ".")
418 local c = cache[name]
421 local last = table.remove(path)
422 c = _create_node(path, cache)
424 local new = {nodes={}, auto=true}
436 --- Create a redirect to another dispatching node.
437 -- @param ... Virtual path destination
445 --- Rewrite the first x path values of the request.
446 -- @param n Number of path values to replace
447 -- @param ... Virtual path to replace removed path values with
448 function rewrite(n, ...)
452 table.remove(context.path, 1)
455 for i,r in ipairs(req) do
456 table.insert(context.path, i, r)
463 --- Create a function-call dispatching target.
464 -- @param name Target function of local controller
465 -- @param ... Additional parameters passed to the function
466 function call(name, ...)
468 return function() return getfenv()[name](unpack(argv)) end
471 --- Create a template render dispatching target.
472 -- @param name Template to be rendered
473 function template(name)
475 require("luci.template")
476 luci.template.render(name)
480 --- Create a CBI model dispatching target.
481 -- @param model CBI model tpo be rendered
485 require("luci.template")
487 maps = luci.cbi.load(model, ...)
489 for i, res in ipairs(maps) do
493 luci.template.render("cbi/header")
494 for i, res in ipairs(maps) do
497 luci.template.render("cbi/footer")
501 --- Create a CBI form model dispatching target.
502 -- @param model CBI form model tpo be rendered
506 require("luci.template")
508 maps = luci.cbi.load(model, ...)
510 for i, res in ipairs(maps) do
514 luci.template.render("header")
515 for i, res in ipairs(maps) do
518 luci.template.render("footer")