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)
117 --- Dispatches a LuCI virtual path.
118 -- @param request Virtual path
119 function dispatch(request)
123 require "luci.i18n".setlanguage(require "luci.config".main.lang)
136 for i, s in ipairs(request) do
143 util.update(track, c)
151 for j=n+1, #request do
152 table.insert(args, request[j])
157 require("luci.i18n").loadc(track.i18n)
160 -- Init template engine
161 if not track.notemplate then
162 local tpl = require("luci.template")
164 tpl.context.viewns = viewns
165 viewns.write = luci.http.write
166 viewns.translate = function(...) return require("luci.i18n").translate(...) end
167 viewns.striptags = util.striptags
168 viewns.controller = luci.http.getenv("SCRIPT_NAME")
169 viewns.media = luci.config.main.mediaurlbase
170 viewns.resource = luci.config.main.resourcebase
171 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
174 track.dependent = (track.dependent ~= false)
175 assert(not track.dependent or not track.auto, "Access Violation")
177 if track.sysauth then
178 local sauth = require "luci.sauth"
180 local authen = type(track.sysauth_authenticator) == "function"
181 and track.sysauth_authenticator
182 or authenticator[track.sysauth_authenticator]
184 local def = (type(track.sysauth) == "string") and track.sysauth
185 local accs = def and {track.sysauth} or track.sysauth
186 local sess = ctx.authsession or luci.http.getcookie("sysauth")
187 sess = sess and sess:match("^[A-F0-9]+$")
188 local user = sauth.read(sess)
190 if not util.contains(accs, user) then
192 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
193 if not user or not util.contains(accs, user) then
196 local sid = sess or luci.sys.uniqueid(16)
197 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
199 sauth.write(sid, user)
201 ctx.authsession = sid
204 luci.http.status(403, "Forbidden")
210 if track.setgroup then
211 luci.sys.process.setgroup(track.setgroup)
214 if track.setuser then
215 luci.sys.process.setuser(track.setuser)
218 if c and type(c.target) == "function" then
219 context.dispatched = c
221 util.copcall(function()
222 util.updfenv(c.target, require(c.module))
225 c.target(unpack(args))
231 --- Generate the dispatching index using the best possible strategy.
232 function createindex()
233 local path = luci.util.libpath() .. "/controller/"
236 if luci.util.copcall(require, "luci.fastindex") then
237 createindex_fastindex(path, suff)
239 createindex_plain(path, suff)
243 --- Generate the dispatching index using the fastindex C-indexer.
244 -- @param path Controller base directory
245 -- @param suffix Controller file suffix
246 function createindex_fastindex(path, suffix)
250 fi = luci.fastindex.new("index")
251 fi.add(path .. "*" .. suffix)
252 fi.add(path .. "*/*" .. suffix)
256 for k, v in pairs(fi.indexes) do
261 --- Generate the dispatching index using the native file-cache based strategy.
262 -- @param path Controller base directory
263 -- @param suffix Controller file suffix
264 function createindex_plain(path, suffix)
266 local cachedate = fs.mtime(indexcache)
267 if cachedate and cachedate > fs.mtime(path) then
270 sys.process.info("uid") == fs.stat(indexcache, "uid")
271 and fs.stat(indexcache, "mode") == "rw-------",
272 "Fatal: Indexcache is not sane!"
275 index = loadfile(indexcache)()
282 local controllers = util.combine(
283 luci.fs.glob(path .. "*" .. suffix) or {},
284 luci.fs.glob(path .. "*/*" .. suffix) or {}
287 for i,c in ipairs(controllers) do
288 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
289 local mod = require(module)
290 local idx = mod.index
292 if type(idx) == "function" then
298 fs.writefile(indexcache, util.get_bytecode(index))
299 fs.chmod(indexcache, "a-rwx,u+rw")
303 --- Create the dispatching tree from the index.
304 -- Build the index before if it does not exist yet.
305 function createtree()
311 local tree = {nodes={}}
313 ctx.treecache = setmetatable({}, {__mode="v"})
316 -- Load default translation
317 require "luci.i18n".loadc("default")
319 local scope = setmetatable({}, {__index = _G})
320 for k,v in pairs(luci.dispatcher) do
321 if type(v) == "function" then
326 for k, v in pairs(index) do
335 --- Clone a node of the dispatching tree to another position.
336 -- @param path Virtual path destination
337 -- @param clone Virtual path source
338 -- @param title Destination node title (optional)
339 -- @param order Destination node order value (optional)
340 -- @return Dispatching tree node
341 function assign(path, clone, title, order)
342 local obj = node(unpack(path))
349 local c = context.tree
350 for k, v in ipairs(clone) do
351 if not c.nodes[v] then
352 c.nodes[v] = {nodes={}}
358 setmetatable(obj, {__index = c})
363 --- Create a new dispatching node and define common parameters.
364 -- @param path Virtual path
365 -- @param target Target function to call when dispatched.
366 -- @param title Destination node title
367 -- @param order Destination node order value (optional)
368 -- @return Dispatching tree node
369 function entry(path, target, title, order)
370 local c = node(unpack(path))
375 c.module = getfenv(2)._NAME
380 --- Fetch or create a new dispatching node.
381 -- @param ... Virtual path
382 -- @return Dispatching tree node
384 local c = _create_node(arg)
386 c.module = getfenv(2)._NAME
393 function _create_node(path, cache)
398 cache = cache or context.treecache
399 local name = table.concat(path, ".")
400 local c = cache[name]
403 local last = table.remove(path)
404 c = _create_node(path, cache)
406 local new = {nodes={}, auto=true}
418 --- Create a redirect to another dispatching node.
419 -- @param ... Virtual path destination
427 --- Rewrite the first x path values of the request.
428 -- @param n Number of path values to replace
429 -- @param ... Virtual path to replace removed path values with
430 function rewrite(n, ...)
434 table.remove(context.path, 1)
437 for i,r in ipairs(req) do
438 table.insert(context.path, i, r)
445 --- Create a function-call dispatching target.
446 -- @param name Target function of local controller
447 -- @param ... Additional parameters passed to the function
448 function call(name, ...)
450 return function() return getfenv()[name](unpack(argv)) end
453 --- Create a template render dispatching target.
454 -- @param name Template to be rendered
455 function template(name)
457 require("luci.template")
458 luci.template.render(name)
462 --- Create a CBI model dispatching target.
463 -- @param model CBI model tpo be rendered
467 require("luci.template")
469 maps = luci.cbi.load(model, ...)
471 for i, res in ipairs(maps) do
475 luci.template.render("cbi/header")
476 for i, res in ipairs(maps) do
479 luci.template.render("cbi/footer")
483 --- Create a CBI form model dispatching target.
484 -- @param model CBI form model tpo be rendered
488 require("luci.template")
490 maps = luci.cbi.load(model, ...)
492 for i, res in ipairs(maps) do
496 luci.template.render("header")
497 for i, res in ipairs(maps) do
500 luci.template.render("footer")