1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
3 -- Licensed to the public under the Apache License 2.0.
5 module("luci.dispatcher", package.seeall)
9 context = setmetatable({}, {
10 __index = function(t, k)
11 if k == "request" or k == "requestpath" then
12 return _G.L.ctx.request_path
13 elseif k == "requestargs" then
14 return _G.L.ctx.request_args
21 uci = require "luci.model.uci"
22 uci:set_session_id(_G.L.ctx.authsession)
24 i18n = require "luci.i18n"
25 i18n.setlanguage(_G.L.dispatcher.lang)
27 build_url = _G.L.dispatcher.build_url
28 menu_json = _G.L.dispatcher.menu_json
29 error404 = _G.L.dispatcher.error404
30 error500 = _G.L.dispatcher.error500
32 function is_authenticated(auth)
33 local session = _G.L.dispatcher.is_authenticated(auth)
35 return session.sid, session.data, session.acls
39 function assign(path, clone, title, order)
40 local obj = node(unpack(path))
45 setmetatable(obj, {__index = node(unpack(clone))})
50 function entry(path, target, title, order)
51 local c = node(unpack(path))
66 local p = table.concat({ ... }, "/")
68 if not __entries[p] then
76 local i, path = nil, {}
77 for i = 1, select('#', ...) do
78 local name, arg = nil, tostring(select(i, ...))
79 for name in arg:gmatch("[^/]+") do
84 local node = menu_json()
86 node = node.children[path[i]]
95 return node, build_url(unpack(path))
99 function process_lua_controller(path)
100 local base = "/usr/lib/lua/luci/controller/"
101 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
102 local mod = require(modname)
104 "Invalid controller file found\n" ..
105 "The file '" .. path .. "' contains an invalid module line.\n" ..
106 "Please verify whether the module name is set to '" .. modname ..
107 "' - It must correspond to the file path!")
109 local idx = mod.index
110 if type(idx) ~= "function" then
117 __controller = modname
119 setfenv(idx, setmetatable({}, { __index = luci.dispatcher }))()
124 -- fixup gathered node specs
125 for path, entry in pairs(entries) do
127 entry.wildcard = true
130 if type(entry.file_depends) == "table" then
131 for _, v in ipairs(entry.file_depends) do
132 entry.depends = entry.depends or {}
133 entry.depends.fs = entry.depends.fs or {}
135 local ft = fs.stat(v, "type")
137 entry.depends.fs[v] = "directory"
138 elseif v:match("/s?bin/") then
139 entry.depends.fs[v] = "executable"
141 entry.depends.fs[v] = "file"
146 if type(entry.uci_depends) == "table" then
147 for k, v in pairs(entry.uci_depends) do
148 entry.depends = entry.depends or {}
149 entry.depends.uci = entry.depends.uci or {}
150 entry.depends.uci[k] = v
154 if type(entry.acl_depends) == "table" then
155 for _, acl in ipairs(entry.acl_depends) do
156 entry.depends = entry.depends or {}
157 entry.depends.acl = entry.depends.acl or {}
158 entry.depends.acl[#entry.depends.acl + 1] = acl
162 if (entry.sysauth_authenticator ~= nil) or
163 (entry.sysauth ~= nil and entry.sysauth ~= false)
165 if entry.sysauth_authenticator == "htmlauth" then
168 methods = { "cookie:sysauth_https", "cookie:sysauth_http" }
170 elseif path == "rpc" and modname == "luci.controller.rpc" then
173 methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http", "cookie:sysauth" }
175 elseif modname == "luci.controller.admin.uci" then
178 methods = { "param:sid" }
181 elseif entry.sysauth == false then
185 if entry.action == nil and type(entry.target) == "table" then
186 entry.action = entry.target
192 entry.file_depends = nil
193 entry.uci_depends = nil
194 entry.acl_depends = nil
197 entry.sysauth_authenticator = nil
203 function invoke_cbi_action(model, config, ...)
204 local cbi = require "luci.cbi"
205 local tpl = require "luci.template"
206 local util = require "luci.util"
212 local maps = cbi.load(model, ...)
216 local function has_uci_access(config, level)
217 local rv = util.ubus("session", "access", {
218 ubus_rpc_session = context.authsession,
219 scope = "uci", object = config,
223 return (type(rv) == "table" and rv.access == true) or false
227 for i, res in ipairs(maps) do
228 if util.instanceof(res, cbi.SimpleForm) then
229 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
232 io.stderr:write("please change %s to use the form() action instead.\n"
233 % table.concat(context.request, "/"))
237 local cstate = res:parse()
238 if cstate and (not state or cstate < state) then
243 local function _resolve_path(path)
244 return type(path) == "table" and build_url(unpack(path)) or path
247 if config.on_valid_to and state and state > 0 and state < 2 then
248 http:redirect(_resolve_path(config.on_valid_to))
252 if config.on_changed_to and state and state > 1 then
253 http:redirect(_resolve_path(config.on_changed_to))
257 if config.on_success_to and state and state > 0 then
258 http:redirect(_resolve_path(config.on_success_to))
262 if config.state_handler then
263 if not config.state_handler(state, maps) then
268 http:header("X-CBI-State", state or 0)
270 if not config.noheader then
271 _G.L.include("cbi/header", {state = state})
276 local applymap = false
277 local pageaction = true
278 local parsechain = { }
279 local writable = false
281 for i, res in ipairs(maps) do
282 if res.apply_needed and res.parsechain then
284 for _, c in ipairs(res.parsechain) do
285 parsechain[#parsechain+1] = c
291 redirect = redirect or res.redirect
294 if res.pageaction == false then
299 messages = messages or { }
300 messages[#messages+1] = res.message
304 for i, res in ipairs(maps) do
305 local is_readable_map = has_uci_access(res.config, "read")
306 local is_writable_map = has_uci_access(res.config, "write")
308 writable = writable or is_writable_map
314 pageaction = pageaction,
315 parsechain = parsechain,
316 readable = is_readable_map,
317 writable = is_writable_map
321 if not config.nofooter then
322 _G.L.include("cbi/footer", {
324 pageaction = pageaction,
327 autoapply = config.autoapply,
328 trigger_apply = applymap,
334 function invoke_form_action(model, ...)
335 local cbi = require "luci.cbi"
336 local tpl = require "luci.template"
338 local maps = luci.cbi.load(model, ...)
342 for i, res in ipairs(maps) do
343 local cstate = res:parse()
344 if cstate and (not state or cstate < state) then
349 http:header("X-CBI-State", state or 0)
350 _G.L.include("header")
351 for i, res in ipairs(maps) do
354 _G.L.include("footer")
357 function render_lua_template(path)
358 local tpl = require "luci.template"
360 tpl.render(path, getfenv(1))
363 function test_post_security()
364 if http:getenv("REQUEST_METHOD") ~= "POST" then
365 http:status(405, "Method Not Allowed")
366 http:header("Allow", "POST")
370 if http:formvalue("token") ~= context.authtoken then
371 http:status(403, "Forbidden")
372 _G.L.include("csrftoken")
380 function call(name, ...)
383 ["module"] = __controller,
385 ["parameters"] = select('#', ...) > 0 and {...} or nil
389 function post(name, ...)
392 ["module"] = __controller,
394 ["parameters"] = select('#', ...) > 0 and {...} or nil,
406 function template(name)
408 ["type"] = "template",
413 function cbi(model, config)
416 ["module"] = "luci.dispatcher",
417 ["function"] = "invoke_cbi_action",
418 ["parameters"] = { model, config or {} },
420 ["cbi.submit"] = true
428 ["module"] = "luci.dispatcher",
429 ["function"] = "invoke_form_action",
430 ["parameters"] = { model },
432 ["cbi.submit"] = true
437 function firstchild()
439 ["type"] = "firstchild"
445 ["type"] = "firstchild",
450 function arcombine(trg1, trg2)
452 ["type"] = "arcombine",
453 ["targets"] = { trg1, trg2 } --,
461 ["path"] = table.concat({ ... }, "/")
465 function rewrite(n, ...)
467 ["type"] = "rewrite",
468 ["path"] = table.concat({ ... }, "/"),
474 translate = i18n.translate
476 -- This function does not actually translate the given argument but
477 -- is used by build/i18n-scan.pl to find translatable entries.