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 assert(not track.dependent or not track.auto, "Access Violation")
176 if track.sysauth then
177 local sauth = require "luci.sauth"
179 local authen = type(track.sysauth_authenticator) == "function"
180 and track.sysauth_authenticator
181 or authenticator[track.sysauth_authenticator]
183 local def = (type(track.sysauth) == "string") and track.sysauth
184 local accs = def and {track.sysauth} or track.sysauth
185 local sess = luci.http.getcookie("sysauth")
186 sess = sess and sess:match("^[A-F0-9]+$")
187 local user = sauth.read(sess)
189 if not util.contains(accs, user) then
191 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
192 if not user or not util.contains(accs, user) then
195 local sid = sess or luci.sys.uniqueid(16)
196 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
198 sauth.write(sid, user)
202 luci.http.status(403, "Forbidden")
208 if track.setgroup then
209 luci.sys.process.setgroup(track.setgroup)
212 if track.setuser then
213 luci.sys.process.setuser(track.setuser)
216 if c and type(c.target) == "function" then
217 context.dispatched = c
219 util.copcall(function()
220 util.updfenv(c.target, require(c.module))
223 c.target(unpack(args))
229 --- Generate the dispatching index using the best possible strategy.
230 function createindex()
231 local path = luci.util.libpath() .. "/controller/"
234 if luci.util.copcall(require, "luci.fastindex") then
235 createindex_fastindex(path, suff)
237 createindex_plain(path, suff)
241 --- Generate the dispatching index using the fastindex C-indexer.
242 -- @param path Controller base directory
243 -- @param suffix Controller file suffix
244 function createindex_fastindex(path, suffix)
248 fi = luci.fastindex.new("index")
249 fi.add(path .. "*" .. suffix)
250 fi.add(path .. "*/*" .. suffix)
254 for k, v in pairs(fi.indexes) do
259 --- Generate the dispatching index using the native file-cache based strategy.
260 -- @param path Controller base directory
261 -- @param suffix Controller file suffix
262 function createindex_plain(path, suffix)
264 local cachedate = fs.mtime(indexcache)
265 if cachedate and cachedate > fs.mtime(path) then
268 sys.process.info("uid") == fs.stat(indexcache, "uid")
269 and fs.stat(indexcache, "mode") == "rw-------",
270 "Fatal: Indexcache is not sane!"
273 index = loadfile(indexcache)()
280 local controllers = util.combine(
281 luci.fs.glob(path .. "*" .. suffix) or {},
282 luci.fs.glob(path .. "*/*" .. suffix) or {}
285 for i,c in ipairs(controllers) do
286 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
287 local mod = require(module)
288 local idx = mod.index
290 if type(idx) == "function" then
296 fs.writefile(indexcache, util.get_bytecode(index))
297 fs.chmod(indexcache, "a-rwx,u+rw")
301 --- Create the dispatching tree from the index.
302 -- Build the index before if it does not exist yet.
303 function createtree()
309 local tree = {nodes={}}
311 ctx.treecache = setmetatable({}, {__mode="v"})
314 -- Load default translation
315 require "luci.i18n".loadc("default")
317 local scope = setmetatable({}, {__index = _G})
318 for k,v in pairs(luci.dispatcher) do
319 if type(v) == "function" then
324 for k, v in pairs(index) do
333 --- Clone a node of the dispatching tree to another position.
334 -- @param path Virtual path destination
335 -- @param clone Virtual path source
336 -- @param title Destination node title (optional)
337 -- @param order Destination node order value (optional)
338 -- @return Dispatching tree node
339 function assign(path, clone, title, order)
340 local obj = node(unpack(path))
347 local c = context.tree
348 for k, v in ipairs(clone) do
349 if not c.nodes[v] then
350 c.nodes[v] = {nodes={}}
356 setmetatable(obj, {__index = c})
361 --- Create a new dispatching node and define common parameters.
362 -- @param path Virtual path
363 -- @param target Target function to call when dispatched.
364 -- @param title Destination node title
365 -- @param order Destination node order value (optional)
366 -- @return Dispatching tree node
367 function entry(path, target, title, order)
368 local c = node(unpack(path))
373 c.module = getfenv(2)._NAME
378 --- Fetch or create a new dispatching node.
379 -- @param ... Virtual path
380 -- @return Dispatching tree node
382 local c = _create_node(arg)
384 c.module = getfenv(2)._NAME
391 function _create_node(path, cache)
396 cache = cache or context.treecache
397 local name = table.concat(path, ".")
398 local c = cache[name]
401 local last = table.remove(path)
402 c = _create_node(path, cache)
404 local new = {nodes={}, auto=true}
416 --- Create a redirect to another dispatching node.
417 -- @param ... Virtual path destination
425 --- Rewrite the first x path values of the request.
426 -- @param n Number of path values to replace
427 -- @param ... Virtual path to replace removed path values with
428 function rewrite(n, ...)
432 table.remove(context.path, 1)
435 for i,r in ipairs(req) do
436 table.insert(context.path, i, r)
443 --- Create a function-call dispatching target.
444 -- @param name Target function of local controller
445 -- @param ... Additional parameters passed to the function
446 function call(name, ...)
448 return function() return getfenv()[name](unpack(argv)) end
451 --- Create a template render dispatching target.
452 -- @param name Template to be rendered
453 function template(name)
455 require("luci.template")
456 luci.template.render(name)
460 --- Create a CBI model dispatching target.
461 -- @param model CBI model tpo be rendered
465 require("luci.template")
467 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
473 for i, res in ipairs(maps) do
474 local stat, err = luci.util.copcall(res.parse, res)
481 luci.template.render("cbi/header")
482 for i, res in ipairs(maps) do
485 luci.template.render("cbi/footer")
489 --- Create a CBI form model dispatching target.
490 -- @param model CBI form model tpo be rendered
494 require("luci.template")
496 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
502 for i, res in ipairs(maps) do
503 local stat, err = luci.util.copcall(res.parse, res)
510 luci.template.render("header")
511 for i, res in ipairs(maps) do
514 luci.template.render("footer")