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 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.resource = luci.config.main.resourcebase
187 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
190 track.dependent = (track.dependent ~= false)
191 assert(not track.dependent or not track.auto, "Access Violation")
193 if track.sysauth then
194 local sauth = require "luci.sauth"
196 local authen = type(track.sysauth_authenticator) == "function"
197 and track.sysauth_authenticator
198 or authenticator[track.sysauth_authenticator]
200 local def = (type(track.sysauth) == "string") and track.sysauth
201 local accs = def and {track.sysauth} or track.sysauth
202 local sess = ctx.authsession or luci.http.getcookie("sysauth")
203 sess = sess and sess:match("^[A-F0-9]+$")
204 local user = sauth.read(sess)
206 if not util.contains(accs, user) then
208 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
209 if not user or not util.contains(accs, user) then
212 local sid = sess or luci.sys.uniqueid(16)
213 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
215 sauth.write(sid, user)
217 ctx.authsession = sid
220 luci.http.status(403, "Forbidden")
226 if track.setgroup then
227 luci.sys.process.setgroup(track.setgroup)
230 if track.setuser then
231 luci.sys.process.setuser(track.setuser)
234 if c and type(c.target) == "function" then
235 context.dispatched = c
237 util.copcall(function()
238 local oldenv = getfenv(c.target)
239 local module = require(c.module)
240 local env = setmetatable({}, {__index=
243 return rawget(tbl, key) or module[key] or oldenv[key]
246 setfenv(c.target, env)
249 c.target(unpack(args))
255 --- Generate the dispatching index using the best possible strategy.
256 function createindex()
257 local path = luci.util.libpath() .. "/controller/"
260 if luci.util.copcall(require, "luci.fastindex") then
261 createindex_fastindex(path, suff)
263 createindex_plain(path, suff)
267 --- Generate the dispatching index using the fastindex C-indexer.
268 -- @param path Controller base directory
269 -- @param suffix Controller file suffix
270 function createindex_fastindex(path, suffix)
274 fi = luci.fastindex.new("index")
275 fi.add(path .. "*" .. suffix)
276 fi.add(path .. "*/*" .. suffix)
280 for k, v in pairs(fi.indexes) do
285 --- Generate the dispatching index using the native file-cache based strategy.
286 -- @param path Controller base directory
287 -- @param suffix Controller file suffix
288 function createindex_plain(path, suffix)
290 local cachedate = fs.mtime(indexcache)
291 if cachedate and cachedate > fs.mtime(path) then
294 sys.process.info("uid") == fs.stat(indexcache, "uid")
295 and fs.stat(indexcache, "mode") == "rw-------",
296 "Fatal: Indexcache is not sane!"
299 index = loadfile(indexcache)()
306 local controllers = util.combine(
307 luci.fs.glob(path .. "*" .. suffix) or {},
308 luci.fs.glob(path .. "*/*" .. suffix) or {}
311 for i,c in ipairs(controllers) do
312 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
313 local mod = require(module)
314 local idx = mod.index
316 if type(idx) == "function" then
322 fs.writefile(indexcache, util.get_bytecode(index))
323 fs.chmod(indexcache, "a-rwx,u+rw")
327 --- Create the dispatching tree from the index.
328 -- Build the index before if it does not exist yet.
329 function createtree()
335 local tree = {nodes={}}
337 ctx.treecache = setmetatable({}, {__mode="v"})
340 -- Load default translation
341 require "luci.i18n".loadc("default")
343 local scope = setmetatable({}, {__index = luci.dispatcher})
345 for k, v in pairs(index) do
354 --- Clone a node of the dispatching tree to another position.
355 -- @param path Virtual path destination
356 -- @param clone Virtual path source
357 -- @param title Destination node title (optional)
358 -- @param order Destination node order value (optional)
359 -- @return Dispatching tree node
360 function assign(path, clone, title, order)
361 local obj = node(unpack(path))
368 setmetatable(obj, {__index = _create_node(clone)})
373 --- Create a new dispatching node and define common parameters.
374 -- @param path Virtual path
375 -- @param target Target function to call when dispatched.
376 -- @param title Destination node title
377 -- @param order Destination node order value (optional)
378 -- @return Dispatching tree node
379 function entry(path, target, title, order)
380 local c = node(unpack(path))
385 c.module = getfenv(2)._NAME
390 --- Fetch or create a new dispatching node.
391 -- @param ... Virtual path
392 -- @return Dispatching tree node
394 local c = _create_node({...})
396 c.module = getfenv(2)._NAME
403 function _create_node(path, cache)
408 cache = cache or context.treecache
409 local name = table.concat(path, ".")
410 local c = cache[name]
413 local last = table.remove(path)
414 c = _create_node(path, cache)
416 local new = {nodes={}, auto=true}
428 --- Create a redirect to another dispatching node.
429 -- @param ... Virtual path destination
437 --- Rewrite the first x path values of the request.
438 -- @param n Number of path values to replace
439 -- @param ... Virtual path to replace removed path values with
440 function rewrite(n, ...)
444 table.remove(context.path, 1)
447 for i,r in ipairs(req) do
448 table.insert(context.path, i, r)
455 --- Create a function-call dispatching target.
456 -- @param name Target function of local controller
457 -- @param ... Additional parameters passed to the function
458 function call(name, ...)
460 return function() return getfenv()[name](unpack(argv)) end
463 --- Create a template render dispatching target.
464 -- @param name Template to be rendered
465 function template(name)
467 require("luci.template")
468 luci.template.render(name)
472 --- Create a CBI model dispatching target.
473 -- @param model CBI model tpo be rendered
477 require("luci.template")
479 maps = luci.cbi.load(model, ...)
481 for i, res in ipairs(maps) do
485 luci.template.render("cbi/header")
486 for i, res in ipairs(maps) do
489 luci.template.render("cbi/footer")
493 --- Create a CBI form model dispatching target.
494 -- @param model CBI form model tpo be rendered
498 require("luci.template")
500 maps = luci.cbi.load(model, ...)
502 for i, res in ipairs(maps) do
506 luci.template.render("header")
507 for i, res in ipairs(maps) do
510 luci.template.render("footer")