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)
111 luci.util.perror(err)
117 --context._disable_memtrace()
120 --- Dispatches a LuCI virtual path.
121 -- @param request Virtual path
122 function dispatch(request)
123 --context._disable_memtrace = require "luci.debug".trap_memtrace()
127 require "luci.i18n".setlanguage(require "luci.config".main.lang)
138 ctx.requestargs = ctx.requestargs or args
141 for i, s in ipairs(request) do
148 util.update(track, c)
156 for j=n+1, #request do
157 table.insert(args, request[j])
162 require("luci.i18n").loadc(track.i18n)
165 -- Init template engine
166 if (c and c.index) or not track.notemplate then
167 local tpl = require("luci.template")
168 local media = track.mediaurlbase or luci.config.main.mediaurlbase
169 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
171 for name, theme in pairs(luci.config.themes) do
172 if name:sub(1,1) ~= "." and pcall(tpl.Template,
173 "themes/%s/header" % fs.basename(theme)) then
177 assert(media, "No valid theme found")
180 local viewns = setmetatable({}, {__index=_G})
181 tpl.context.viewns = viewns
182 viewns.write = luci.http.write
183 viewns.include = function(name) tpl.Template(name):render(getfenv(2)) end
184 viewns.translate = function(...) return require("luci.i18n").translate(...) end
185 viewns.striptags = util.striptags
186 viewns.controller = luci.http.getenv("SCRIPT_NAME")
188 viewns.theme = fs.basename(media)
189 viewns.resource = luci.config.main.resourcebase
190 viewns.REQUEST_URI = (luci.http.getenv("SCRIPT_NAME") or "") .. (luci.http.getenv("PATH_INFO") or "")
193 track.dependent = (track.dependent ~= false)
194 assert(not track.dependent or not track.auto, "Access Violation")
196 if track.sysauth then
197 local sauth = require "luci.sauth"
199 local authen = type(track.sysauth_authenticator) == "function"
200 and track.sysauth_authenticator
201 or authenticator[track.sysauth_authenticator]
203 local def = (type(track.sysauth) == "string") and track.sysauth
204 local accs = def and {track.sysauth} or track.sysauth
205 local sess = ctx.authsession or luci.http.getcookie("sysauth")
206 sess = sess and sess:match("^[A-F0-9]+$")
207 local user = sauth.read(sess)
209 if not util.contains(accs, user) then
211 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
212 if not user or not util.contains(accs, user) then
215 local sid = sess or luci.sys.uniqueid(16)
216 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
218 sauth.write(sid, user)
220 ctx.authsession = sid
223 luci.http.status(403, "Forbidden")
229 if track.setgroup then
230 luci.sys.process.setgroup(track.setgroup)
233 if track.setuser then
234 luci.sys.process.setuser(track.setuser)
237 if c and (c.index or type(c.target) == "function") then
239 ctx.requested = ctx.requested or ctx.dispatched
242 if c and c.index then
243 local tpl = require "luci.template"
245 if util.copcall(tpl.render, "indexer", {}) then
250 if c and type(c.target) == "function" then
251 util.copcall(function()
252 local oldenv = getfenv(c.target)
253 local module = require(c.module)
254 local env = setmetatable({}, {__index=
257 return rawget(tbl, key) or module[key] or oldenv[key]
260 setfenv(c.target, env)
263 c.target(unpack(args))
269 --- Generate the dispatching index using the best possible strategy.
270 function createindex()
271 local path = luci.util.libpath() .. "/controller/"
274 if luci.util.copcall(require, "luci.fastindex") then
275 createindex_fastindex(path, suff)
277 createindex_plain(path, suff)
281 --- Generate the dispatching index using the fastindex C-indexer.
282 -- @param path Controller base directory
283 -- @param suffix Controller file suffix
284 function createindex_fastindex(path, suffix)
288 fi = luci.fastindex.new("index")
289 fi.add(path .. "*" .. suffix)
290 fi.add(path .. "*/*" .. suffix)
294 for k, v in pairs(fi.indexes) do
299 --- Generate the dispatching index using the native file-cache based strategy.
300 -- @param path Controller base directory
301 -- @param suffix Controller file suffix
302 function createindex_plain(path, suffix)
304 local cachedate = fs.mtime(indexcache)
305 if cachedate and cachedate > fs.mtime(path) then
308 sys.process.info("uid") == fs.stat(indexcache, "uid")
309 and fs.stat(indexcache, "mode") == "rw-------",
310 "Fatal: Indexcache is not sane!"
313 index = loadfile(indexcache)()
320 local controllers = util.combine(
321 luci.fs.glob(path .. "*" .. suffix) or {},
322 luci.fs.glob(path .. "*/*" .. suffix) or {}
325 for i,c in ipairs(controllers) do
326 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
327 local mod = require(module)
328 local idx = mod.index
330 if type(idx) == "function" then
336 fs.writefile(indexcache, util.get_bytecode(index))
337 fs.chmod(indexcache, "a-rwx,u+rw")
341 --- Create the dispatching tree from the index.
342 -- Build the index before if it does not exist yet.
343 function createtree()
349 local tree = {nodes={}}
351 ctx.treecache = setmetatable({}, {__mode="v"})
354 -- Load default translation
355 require "luci.i18n".loadc("default")
357 local scope = setmetatable({}, {__index = luci.dispatcher})
359 for k, v in pairs(index) do
368 --- Clone a node of the dispatching tree to another position.
369 -- @param path Virtual path destination
370 -- @param clone Virtual path source
371 -- @param title Destination node title (optional)
372 -- @param order Destination node order value (optional)
373 -- @return Dispatching tree node
374 function assign(path, clone, title, order)
375 local obj = node(unpack(path))
382 setmetatable(obj, {__index = _create_node(clone)})
387 --- Create a new dispatching node and define common parameters.
388 -- @param path Virtual path
389 -- @param target Target function to call when dispatched.
390 -- @param title Destination node title
391 -- @param order Destination node order value (optional)
392 -- @return Dispatching tree node
393 function entry(path, target, title, order)
394 local c = node(unpack(path))
399 c.module = getfenv(2)._NAME
404 --- Fetch or create a new dispatching node.
405 -- @param ... Virtual path
406 -- @return Dispatching tree node
408 local c = _create_node({...})
410 c.module = getfenv(2)._NAME
417 function _create_node(path, cache)
422 cache = cache or context.treecache
423 local name = table.concat(path, ".")
424 local c = cache[name]
427 local last = table.remove(path)
428 c = _create_node(path, cache)
430 local new = {nodes={}, auto=true}
442 --- Create a redirect to another dispatching node.
443 -- @param ... Virtual path destination
447 for _, r in ipairs({...}) do
455 --- Rewrite the first x path values of the request.
456 -- @param n Number of path values to replace
457 -- @param ... Virtual path to replace removed path values with
458 function rewrite(n, ...)
461 local dispatched = util.clone(context.dispatched)
464 table.remove(dispatched, 1)
467 for i, r in ipairs(req) do
468 table.insert(dispatched, i, r)
471 for _, r in ipairs({...}) do
472 dispatched[#dispatched+1] = r
479 --- Create a function-call dispatching target.
480 -- @param name Target function of local controller
481 -- @param ... Additional parameters passed to the function
482 function call(name, ...)
486 return getfenv()[name](unpack(argv), ...)
488 return getfenv()[name](...)
493 --- Create a template render dispatching target.
494 -- @param name Template to be rendered
495 function template(name)
497 require("luci.template")
498 luci.template.render(name)
502 --- Create a CBI model dispatching target.
503 -- @param model CBI model to be rendered
504 function cbi(model, config)
505 config = config or {}
508 require("luci.template")
509 local http = require "luci.http"
511 maps = luci.cbi.load(model, ...)
515 for i, res in ipairs(maps) do
516 if config.autoapply then
517 res.autoapply = config.autoapply
519 local cstate = res:parse()
520 if not state or cstate < state then
525 if config.on_success_to and state and state > 0 then
526 luci.http.redirect(config.on_success_to)
530 if config.state_handler then
531 if not config.state_handler(state, maps) then
536 local pageaction = true
537 http.header("X-CBI-State", state or 0)
538 luci.template.render("cbi/header", {state = state})
539 for i, res in ipairs(maps) do
541 if res.pageaction == false then
545 luci.template.render("cbi/footer", {pageaction=pageaction, state = state, autoapply = config.autoapply})
549 --- Create a CBI form model dispatching target.
550 -- @param model CBI form model tpo be rendered
554 require("luci.template")
555 local http = require "luci.http"
557 maps = luci.cbi.load(model, ...)
561 for i, res in ipairs(maps) do
562 local cstate = res:parse()
563 if not state or cstate < state then
568 http.header("X-CBI-State", state or 0)
569 luci.template.render("header")
570 for i, res in ipairs(maps) do
573 luci.template.render("footer")