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)
303 local controllers = util.combine(
304 luci.fs.glob(path .. "*" .. suffix) or {},
305 luci.fs.glob(path .. "*/*" .. suffix) or {}
309 local cachedate = fs.mtime(indexcache)
312 for _, obj in ipairs(controllers) do
313 local omtime = fs.mtime(path .. "/" .. obj)
314 realdate = (omtime and omtime > realdate) and omtime or realdate
317 if cachedate > realdate then
319 sys.process.info("uid") == fs.stat(indexcache, "uid")
320 and fs.stat(indexcache, "mode") == "rw-------",
321 "Fatal: Indexcache is not sane!"
324 index = loadfile(indexcache)()
332 for i,c in ipairs(controllers) do
333 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
334 local mod = require(module)
335 local idx = mod.index
337 if type(idx) == "function" then
343 fs.writefile(indexcache, util.get_bytecode(index))
344 fs.chmod(indexcache, "a-rwx,u+rw")
348 --- Create the dispatching tree from the index.
349 -- Build the index before if it does not exist yet.
350 function createtree()
356 local tree = {nodes={}}
358 ctx.treecache = setmetatable({}, {__mode="v"})
361 -- Load default translation
362 require "luci.i18n".loadc("default")
364 local scope = setmetatable({}, {__index = luci.dispatcher})
366 for k, v in pairs(index) do
375 --- Clone a node of the dispatching tree to another position.
376 -- @param path Virtual path destination
377 -- @param clone Virtual path source
378 -- @param title Destination node title (optional)
379 -- @param order Destination node order value (optional)
380 -- @return Dispatching tree node
381 function assign(path, clone, title, order)
382 local obj = node(unpack(path))
389 setmetatable(obj, {__index = _create_node(clone)})
394 --- Create a new dispatching node and define common parameters.
395 -- @param path Virtual path
396 -- @param target Target function to call when dispatched.
397 -- @param title Destination node title
398 -- @param order Destination node order value (optional)
399 -- @return Dispatching tree node
400 function entry(path, target, title, order)
401 local c = node(unpack(path))
406 c.module = getfenv(2)._NAME
411 --- Fetch or create a new dispatching node.
412 -- @param ... Virtual path
413 -- @return Dispatching tree node
415 local c = _create_node({...})
417 c.module = getfenv(2)._NAME
424 function _create_node(path, cache)
429 cache = cache or context.treecache
430 local name = table.concat(path, ".")
431 local c = cache[name]
434 local last = table.remove(path)
435 c = _create_node(path, cache)
437 local new = {nodes={}, auto=true}
449 --- Create a redirect to another dispatching node.
450 -- @param ... Virtual path destination
454 for _, r in ipairs({...}) do
462 --- Rewrite the first x path values of the request.
463 -- @param n Number of path values to replace
464 -- @param ... Virtual path to replace removed path values with
465 function rewrite(n, ...)
468 local dispatched = util.clone(context.dispatched)
471 table.remove(dispatched, 1)
474 for i, r in ipairs(req) do
475 table.insert(dispatched, i, r)
478 for _, r in ipairs({...}) do
479 dispatched[#dispatched+1] = r
486 --- Create a function-call dispatching target.
487 -- @param name Target function of local controller
488 -- @param ... Additional parameters passed to the function
489 function call(name, ...)
493 return getfenv()[name](unpack(argv), ...)
495 return getfenv()[name](...)
500 --- Create a template render dispatching target.
501 -- @param name Template to be rendered
502 function template(name)
504 require("luci.template")
505 luci.template.render(name)
509 --- Create a CBI model dispatching target.
510 -- @param model CBI model to be rendered
511 function cbi(model, config)
512 config = config or {}
515 require("luci.template")
516 local http = require "luci.http"
518 maps = luci.cbi.load(model, ...)
522 for i, res in ipairs(maps) do
523 if config.autoapply then
524 res.autoapply = config.autoapply
526 local cstate = res:parse()
527 if not state or cstate < state then
532 if config.on_success_to and state and state > 0 then
533 luci.http.redirect(config.on_success_to)
537 if config.state_handler then
538 if not config.state_handler(state, maps) then
543 local pageaction = true
544 http.header("X-CBI-State", state or 0)
545 luci.template.render("cbi/header", {state = state})
546 for i, res in ipairs(maps) do
548 if res.pageaction == false then
552 luci.template.render("cbi/footer", {pageaction=pageaction, state = state, autoapply = config.autoapply})
556 --- Create a CBI form model dispatching target.
557 -- @param model CBI form model tpo be rendered
561 require("luci.template")
562 local http = require "luci.http"
564 maps = luci.cbi.load(model, ...)
568 for i, res in ipairs(maps) do
569 local cstate = res:parse()
570 if not state or cstate < state then
575 http.header("X-CBI-State", state or 0)
576 luci.template.render("header")
577 for i, res in ipairs(maps) do
580 luci.template.render("footer")