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 local fs = require "nixio.fs"
6 local sys = require "luci.sys"
7 local util = require "luci.util"
8 local http = require "luci.http"
9 local nixio = require "nixio", require "nixio.util"
11 module("luci.dispatcher", package.seeall)
12 context = util.threadlocal()
13 uci = require "luci.model.uci"
14 i18n = require "luci.i18n"
24 local function check_fs_depends(fs)
25 local fs = require "nixio.fs"
27 for path, kind in pairs(fs) do
28 if kind == "directory" then
30 for entry in (fs.dir(path) or function() end) do
37 elseif kind == "executable" then
38 if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
41 elseif kind == "file" then
42 if fs.stat(path, "type") ~= "reg" then
51 local function check_uci_depends_options(conf, s, opts)
52 local uci = require "luci.model.uci"
54 if type(opts) == "string" then
55 return (s[".type"] == opts)
56 elseif opts == true then
57 for option, value in pairs(s) do
58 if option:byte(1) ~= 46 then
62 elseif type(opts) == "table" then
63 for option, value in pairs(opts) do
64 local sval = s[option]
65 if type(sval) == "table" then
67 for _, v in ipairs(sval) do
76 elseif value == true then
91 local function check_uci_depends_section(conf, sect)
92 local uci = require "luci.model.uci"
94 for section, options in pairs(sect) do
95 local stype = section:match("^@([A-Za-z0-9_%-]+)$")
98 uci:foreach(conf, stype, function(s)
99 if check_uci_depends_options(conf, s, options) then
108 local s = uci:get_all(conf, section)
109 if not s or not check_uci_depends_options(conf, s, options) then
118 local function check_uci_depends(conf)
119 local uci = require "luci.model.uci"
121 for config, values in pairs(conf) do
122 if values == true then
124 uci:foreach(config, nil, function(s)
131 elseif type(values) == "table" then
132 if not check_uci_depends_section(config, values) then
141 local function check_depends(spec)
142 if type(spec.depends) ~= "table" then
146 if type(spec.depends.fs) == "table" and not check_fs_depends(spec.depends.fs) then
147 local satisfied = false
148 local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
149 for _, alternative in ipairs(alternatives) do
150 if check_fs_depends(alternative) then
155 if not satisfied then
160 if type(spec.depends.uci) == "table" then
161 local satisfied = false
162 local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
163 for _, alternative in ipairs(alternatives) do
164 if check_uci_depends(alternative) then
169 if not satisfied then
177 local function target_to_json(target, module)
180 if target.type == "call" then
184 ["function"] = target.name,
185 ["parameters"] = target.argv
187 elseif target.type == "view" then
190 ["path"] = target.view
192 elseif target.type == "template" then
194 ["type"] = "template",
195 ["path"] = target.view
197 elseif target.type == "cbi" then
200 ["path"] = target.model
202 elseif target.type == "form" then
205 ["path"] = target.model
207 elseif target.type == "firstchild" then
209 ["type"] = "firstchild"
211 elseif target.type == "firstnode" then
213 ["type"] = "firstchild",
216 elseif target.type == "arcombine" then
217 if type(target.targets) == "table" then
219 ["type"] = "arcombine",
221 target_to_json(target.targets[1], module),
222 target_to_json(target.targets[2], module)
226 elseif target.type == "alias" then
229 ["path"] = table.concat(target.req, "/")
231 elseif target.type == "rewrite" then
233 ["type"] = "rewrite",
234 ["path"] = table.concat(target.req, "/"),
235 ["remove"] = target.n
239 if target.post and action then
240 action.post = target.post
246 local function tree_to_json(node, json)
247 local fs = require "nixio.fs"
248 local util = require "luci.util"
250 if type(node.nodes) == "table" then
251 for subname, subnode in pairs(node.nodes) do
253 title = util.striptags(subnode.title),
254 order = subnode.order
265 if subnode.setuser then
266 spec.setuser = subnode.setuser
269 if subnode.setgroup then
270 spec.setgroup = subnode.setgroup
273 if type(subnode.target) == "table" then
274 spec.action = target_to_json(subnode.target, subnode.module)
277 if type(subnode.file_depends) == "table" then
278 for _, v in ipairs(subnode.file_depends) do
279 spec.depends = spec.depends or {}
280 spec.depends.fs = spec.depends.fs or {}
282 local ft = fs.stat(v, "type")
284 spec.depends.fs[v] = "directory"
285 elseif v:match("/s?bin/") then
286 spec.depends.fs[v] = "executable"
288 spec.depends.fs[v] = "file"
293 if type(subnode.uci_depends) == "table" then
294 for k, v in pairs(subnode.uci_depends) do
295 spec.depends = spec.depends or {}
296 spec.depends.uci = spec.depends.uci or {}
297 spec.depends.uci[k] = v
301 if (subnode.sysauth_authenticator ~= nil) or
302 (subnode.sysauth ~= nil and subnode.sysauth ~= false)
304 if subnode.sysauth_authenticator == "htmlauth" then
307 methods = { "cookie:sysauth" }
309 elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
312 methods = { "param:auth", "cookie:sysauth" }
314 elseif subnode.module == "luci.controller.admin.uci" then
317 methods = { "param:sid" }
320 elseif subnode.sysauth == false then
324 for _, v in pairs(spec) do
326 if not spec.action then
330 spec.satisfied = check_depends(spec)
331 json.children = json.children or {}
332 json.children[subname] = tree_to_json(subnode, spec)
342 function build_url(...)
344 local url = { http.getenv("SCRIPT_NAME") or "" }
347 for _, p in ipairs(path) do
348 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
358 return table.concat(url, "")
361 function _ordered_children(node)
362 local name, child, children = nil, nil, {}
364 for name, child in pairs(node.nodes) do
365 children[#children+1] = {
368 order = child.order or 100
372 table.sort(children, function(a, b)
373 if a.order == b.order then
374 return a.name < b.name
376 return a.order < b.order
383 local function dependencies_satisfied(node)
384 if type(node.file_depends) == "table" then
385 for _, file in ipairs(node.file_depends) do
386 local ftype = fs.stat(file, "type")
387 if ftype == "dir" then
389 for e in (fs.dir(file) or function() end) do
395 elseif ftype == nil then
401 if type(node.uci_depends) == "table" then
402 for config, expect_sections in pairs(node.uci_depends) do
403 if type(expect_sections) == "table" then
404 for section, expect_options in pairs(expect_sections) do
405 if type(expect_options) == "table" then
406 for option, expect_value in pairs(expect_options) do
407 local val = uci:get(config, section, option)
408 if expect_value == true and val == nil then
410 elseif type(expect_value) == "string" then
411 if type(val) == "table" then
413 for _, subval in ipairs(val) do
414 if subval == expect_value then
421 elseif val ~= expect_value then
427 local val = uci:get(config, section)
428 if expect_options == true and val == nil then
430 elseif type(expect_options) == "string" and val ~= expect_options then
435 elseif expect_sections == true then
436 if not uci:get_first(config) then
446 function node_visible(node)
449 (not dependencies_satisfied(node)) or
450 (not node.title or #node.title == 0) or
451 (not node.target or node.hidden == true) or
452 (type(node.target) == "table" and node.target.type == "firstchild" and
453 (type(node.nodes) ~= "table" or not next(node.nodes)))
459 function node_childs(node)
463 for _, child in ipairs(_ordered_children(node)) do
464 if node_visible(child.node) then
465 rv[#rv+1] = child.name
473 function error404(message)
474 http.status(404, "Not Found")
475 message = message or "Not Found"
477 local function render()
478 local template = require "luci.template"
479 template.render("error404")
482 if not util.copcall(render) then
483 http.prepare_content("text/plain")
490 function error500(message)
492 if not context.template_header_sent then
493 http.status(500, "Internal Server Error")
494 http.prepare_content("text/plain")
497 require("luci.template")
498 if not util.copcall(luci.template.render, "error500", {message=message}) then
499 http.prepare_content("text/plain")
506 function httpdispatch(request, prefix)
507 http.context.request = request
512 local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
515 for _, node in ipairs(prefix) do
521 for node in pathinfo:gmatch("[^/%z]+") do
525 local stat, err = util.coxpcall(function()
526 dispatch(context.request)
531 --context._disable_memtrace()
534 local function require_post_security(target, args)
535 if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
536 return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
539 if type(target) == "table" then
540 if type(target.post) == "table" then
541 local param_name, required_val, request_val
543 for param_name, required_val in pairs(target.post) do
544 request_val = http.formvalue(param_name)
546 if (type(required_val) == "string" and
547 request_val ~= required_val) or
548 (required_val == true and request_val == nil)
557 return (target.post == true)
563 function test_post_security()
564 if http.getenv("REQUEST_METHOD") ~= "POST" then
565 http.status(405, "Method Not Allowed")
566 http.header("Allow", "POST")
570 if http.formvalue("token") ~= context.authtoken then
571 http.status(403, "Forbidden")
572 luci.template.render("csrftoken")
579 local function session_retrieve(sid, allowed_users)
580 local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
582 if type(sdat) == "table" and
583 type(sdat.values) == "table" and
584 type(sdat.values.token) == "string" and
585 (not allowed_users or
586 util.contains(allowed_users, sdat.values.username))
588 uci:set_session_id(sid)
589 return sid, sdat.values
595 local function session_setup(user, pass, allowed_users)
596 if util.contains(allowed_users, user) then
597 local login = util.ubus("session", "login", {
600 timeout = tonumber(luci.config.sauth.sessiontime)
603 local rp = context.requestpath
604 and table.concat(context.requestpath, "/") or ""
606 if type(login) == "table" and
607 type(login.ubus_rpc_session) == "string"
609 util.ubus("session", "set", {
610 ubus_rpc_session = login.ubus_rpc_session,
611 values = { token = sys.uniqueid(16) }
614 io.stderr:write("luci: accepted login on /%s for %s from %s\n"
615 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
617 return session_retrieve(login.ubus_rpc_session)
620 io.stderr:write("luci: failed login on /%s for %s from %s\n"
621 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
628 local tree = context.tree or createtree()
629 return tree_to_json(tree, {
631 ["type"] = "firstchild",
637 function dispatch(request)
638 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
642 local conf = require "luci.config"
644 "/etc/config/luci seems to be corrupt, unable to find section 'main'")
646 local i18n = require "luci.i18n"
647 local lang = conf.main.lang or "auto"
648 if lang == "auto" then
649 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
650 for aclang in aclang:gmatch("[%w_-]+") do
651 local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
652 if country and culture then
653 local cc = "%s_%s" %{ country, culture:lower() }
654 if conf.languages[cc] then
657 elseif conf.languages[country] then
661 elseif conf.languages[aclang] then
667 if lang == "auto" then
670 i18n.setlanguage(lang)
681 ctx.requestargs = ctx.requestargs or args
686 for i, s in ipairs(request) do
695 util.update(track, c)
703 for j=n+1, #request do
704 args[#args+1] = request[j]
705 freq[#freq+1] = request[j]
709 ctx.requestpath = ctx.requestpath or freq
712 -- Init template engine
713 if (c and c.index) or not track.notemplate then
714 local tpl = require("luci.template")
715 local media = track.mediaurlbase or luci.config.main.mediaurlbase
716 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
718 for name, theme in pairs(luci.config.themes) do
719 if name:sub(1,1) ~= "." and pcall(tpl.Template,
720 "themes/%s/header" % fs.basename(theme)) then
724 assert(media, "No valid theme found")
727 local function _ifattr(cond, key, val, noescape)
729 local env = getfenv(3)
730 local scope = (type(env.self) == "table") and env.self
731 if type(val) == "table" then
732 if not next(val) then
735 val = util.serialize_json(val)
739 val = tostring(val or
740 (type(env[key]) ~= "function" and env[key]) or
741 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
743 if noescape ~= true then
744 val = util.pcdata(val)
747 return string.format(' %s="%s"', tostring(key), val)
753 tpl.context.viewns = setmetatable({
755 include = function(name) tpl.Template(name):render(getfenv(2)) end;
756 translate = i18n.translate;
757 translatef = i18n.translatef;
758 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
759 striptags = util.striptags;
760 pcdata = util.pcdata;
762 theme = fs.basename(media);
763 resource = luci.config.main.resourcebase;
764 ifattr = function(...) return _ifattr(...) end;
765 attr = function(...) return _ifattr(true, ...) end;
767 }, {__index=function(tbl, key)
768 if key == "controller" then
770 elseif key == "REQUEST_URI" then
771 return build_url(unpack(ctx.requestpath))
772 elseif key == "FULL_REQUEST_URI" then
773 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
774 local query = http.getenv("QUERY_STRING")
775 if query and #query > 0 then
779 return table.concat(url, "")
780 elseif key == "token" then
783 return rawget(tbl, key) or _G[key]
788 track.dependent = (track.dependent ~= false)
789 assert(not track.dependent or not track.auto,
790 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
791 "has no parent node so the access to this location has been denied.\n" ..
792 "This is a software bug, please report this message at " ..
793 "https://github.com/openwrt/luci/issues"
796 if track.sysauth and not ctx.authsession then
797 local authen = track.sysauth_authenticator
798 local _, sid, sdat, default_user, allowed_users
800 if type(authen) == "string" and authen ~= "htmlauth" then
801 error500("Unsupported authenticator %q configured" % authen)
805 if type(track.sysauth) == "table" then
806 default_user, allowed_users = nil, track.sysauth
808 default_user, allowed_users = track.sysauth, { track.sysauth }
811 if type(authen) == "function" then
812 _, sid = authen(sys.user.checkpasswd, allowed_users)
814 sid = http.getcookie("sysauth")
817 sid, sdat = session_retrieve(sid, allowed_users)
819 if not (sid and sdat) and authen == "htmlauth" then
820 local user = http.getenv("HTTP_AUTH_USER")
821 local pass = http.getenv("HTTP_AUTH_PASS")
823 if user == nil and pass == nil then
824 user = http.formvalue("luci_username")
825 pass = http.formvalue("luci_password")
828 sid, sdat = session_setup(user, pass, allowed_users)
831 local tmpl = require "luci.template"
835 http.status(403, "Forbidden")
836 http.header("X-LuCI-Login-Required", "yes")
837 tmpl.render(track.sysauth_template or "sysauth", {
838 duser = default_user,
845 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
846 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
848 http.redirect(build_url(unpack(ctx.requestpath)))
851 if not sid or not sdat then
852 http.status(403, "Forbidden")
853 http.header("X-LuCI-Login-Required", "yes")
857 ctx.authsession = sid
858 ctx.authtoken = sdat.token
859 ctx.authuser = sdat.username
862 if track.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
863 luci.http.status(200, "OK")
864 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
865 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
869 if c and require_post_security(c.target, args) then
870 if not test_post_security(c) then
875 if track.setgroup then
876 sys.process.setgroup(track.setgroup)
879 if track.setuser then
880 sys.process.setuser(track.setuser)
885 if type(c.target) == "function" then
887 elseif type(c.target) == "table" then
888 target = c.target.target
892 if c and (c.index or type(target) == "function") then
894 ctx.requested = ctx.requested or ctx.dispatched
897 if c and c.index then
898 local tpl = require "luci.template"
900 if util.copcall(tpl.render, "indexer", {}) then
905 if type(target) == "function" then
906 util.copcall(function()
907 local oldenv = getfenv(target)
908 local module = require(c.module)
909 local env = setmetatable({}, {__index=
912 return rawget(tbl, key) or module[key] or oldenv[key]
919 if type(c.target) == "table" then
920 ok, err = util.copcall(target, c.target, unpack(args))
922 ok, err = util.copcall(target, unpack(args))
925 error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
926 " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
927 "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
931 if not root or not root.target then
932 error404("No root node was registered, this usually happens if no module was installed.\n" ..
933 "Install luci-mod-admin-full and retry. " ..
934 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
936 error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
937 "If this url belongs to an extension, make sure it is properly installed.\n" ..
938 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
943 function createindex()
944 local controllers = { }
945 local base = "%s/controller/" % util.libpath()
948 for path in (fs.glob("%s*.lua" % base) or function() end) do
949 controllers[#controllers+1] = path
952 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
953 controllers[#controllers+1] = path
957 local cachedate = fs.stat(indexcache, "mtime")
960 for _, obj in ipairs(controllers) do
961 local omtime = fs.stat(obj, "mtime")
962 realdate = (omtime and omtime > realdate) and omtime or realdate
965 if cachedate > realdate and sys.process.info("uid") == 0 then
967 sys.process.info("uid") == fs.stat(indexcache, "uid")
968 and fs.stat(indexcache, "modestr") == "rw-------",
969 "Fatal: Indexcache is not sane!"
972 index = loadfile(indexcache)()
980 for _, path in ipairs(controllers) do
981 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
982 local mod = require(modname)
984 "Invalid controller file found\n" ..
985 "The file '" .. path .. "' contains an invalid module line.\n" ..
986 "Please verify whether the module name is set to '" .. modname ..
987 "' - It must correspond to the file path!")
989 local idx = mod.index
990 assert(type(idx) == "function",
991 "Invalid controller file found\n" ..
992 "The file '" .. path .. "' contains no index() function.\n" ..
993 "Please make sure that the controller contains a valid " ..
994 "index function and verify the spelling!")
1000 local f = nixio.open(indexcache, "w", 600)
1001 f:writeall(util.get_bytecode(index))
1006 -- Build the index before if it does not exist yet.
1007 function createtree()
1013 local tree = {nodes={}, inreq=true}
1015 ctx.treecache = setmetatable({}, {__mode="v"})
1018 local scope = setmetatable({}, {__index = luci.dispatcher})
1020 for k, v in pairs(index) do
1029 function assign(path, clone, title, order)
1030 local obj = node(unpack(path))
1037 setmetatable(obj, {__index = _create_node(clone)})
1042 function entry(path, target, title, order)
1043 local c = node(unpack(path))
1048 c.module = getfenv(2)._NAME
1053 -- enabling the node.
1055 return _create_node({...})
1059 local c = _create_node({...})
1061 c.module = getfenv(2)._NAME
1067 function lookup(...)
1068 local i, path = nil, {}
1069 for i = 1, select('#', ...) do
1070 local name, arg = nil, tostring(select(i, ...))
1071 for name in arg:gmatch("[^/]+") do
1072 path[#path+1] = name
1076 for i = #path, 1, -1 do
1077 local node = context.treecache[table.concat(path, ".", 1, i)]
1078 if node and (i == #path or node.leaf) then
1079 return node, build_url(unpack(path))
1084 function _create_node(path)
1089 local name = table.concat(path, ".")
1090 local c = context.treecache[name]
1093 local last = table.remove(path)
1094 local parent = _create_node(path)
1096 c = {nodes={}, auto=true, inreq=true}
1099 for _, n in ipairs(path) do
1100 if context.path[_] ~= n then
1106 c.inreq = c.inreq and (context.path[#path + 1] == last)
1108 parent.nodes[last] = c
1109 context.treecache[name] = c
1115 -- Subdispatchers --
1117 function _find_eligible_node(root, prefix, deep, types, descend)
1118 local children = _ordered_children(root)
1120 if not root.leaf and deep ~= nil then
1121 local sub_path = { unpack(prefix) }
1123 if deep == false then
1128 for _, child in ipairs(children) do
1129 sub_path[#prefix+1] = child.name
1131 local res_path = _find_eligible_node(child.node, sub_path,
1142 (type(root.target) == "table" and
1143 util.contains(types, root.target.type)))
1149 function _find_node(recurse, types)
1150 local path = { unpack(context.path) }
1151 local name = table.concat(path, ".")
1152 local node = context.treecache[name]
1154 path = _find_eligible_node(node, path, recurse, types)
1159 require "luci.template".render("empty_node_placeholder")
1163 function _firstchild()
1164 return _find_node(false, nil)
1167 function firstchild()
1168 return { type = "firstchild", target = _firstchild }
1171 function _firstnode()
1172 return _find_node(true, { "cbi", "form", "template", "arcombine" })
1175 function firstnode()
1176 return { type = "firstnode", target = _firstnode }
1179 function _alias(self, ...)
1180 local req = { unpack(self.req) }
1182 for _, r in ipairs({...}) do
1190 return { type = "alias", target = _alias, req = { ... } }
1193 function _rewrite(self, ...)
1195 local req = { unpack(self.req) }
1196 local dispatched = util.clone(context.dispatched)
1199 table.remove(dispatched, 1)
1202 for i, r in ipairs(req) do
1203 table.insert(dispatched, i, r)
1206 for _, r in ipairs({...}) do
1207 dispatched[#dispatched+1] = r
1210 dispatch(dispatched)
1213 function rewrite(n, ...)
1214 return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
1217 local function _call(self, ...)
1218 local func = getfenv()[self.name]
1220 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
1222 assert(type(func) == "function",
1223 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
1224 'of type "' .. type(func) .. '".')
1226 if #self.argv > 0 then
1227 return func(unpack(self.argv), ...)
1233 function call(name, ...)
1234 return {type = "call", argv = {...}, name = name, target = _call}
1237 function post_on(params, name, ...)
1248 return post_on(true, ...)
1252 local _template = function(self, ...)
1253 require "luci.template".render(self.view)
1256 function template(name)
1257 return {type = "template", view = name, target = _template}
1261 local _view = function(self, ...)
1262 require "luci.template".render("view", { view = self.view })
1266 return {type = "view", view = name, target = _view}
1270 local function _cbi(self, ...)
1271 local cbi = require "luci.cbi"
1272 local tpl = require "luci.template"
1273 local http = require "luci.http"
1275 local config = self.config or {}
1276 local maps = cbi.load(self.model, ...)
1281 for i, res in ipairs(maps) do
1282 if util.instanceof(res, cbi.SimpleForm) then
1283 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1286 io.stderr:write("please change %s to use the form() action instead.\n"
1287 % table.concat(context.request, "/"))
1291 local cstate = res:parse()
1292 if cstate and (not state or cstate < state) then
1297 local function _resolve_path(path)
1298 return type(path) == "table" and build_url(unpack(path)) or path
1301 if config.on_valid_to and state and state > 0 and state < 2 then
1302 http.redirect(_resolve_path(config.on_valid_to))
1306 if config.on_changed_to and state and state > 1 then
1307 http.redirect(_resolve_path(config.on_changed_to))
1311 if config.on_success_to and state and state > 0 then
1312 http.redirect(_resolve_path(config.on_success_to))
1316 if config.state_handler then
1317 if not config.state_handler(state, maps) then
1322 http.header("X-CBI-State", state or 0)
1324 if not config.noheader then
1325 tpl.render("cbi/header", {state = state})
1330 local applymap = false
1331 local pageaction = true
1332 local parsechain = { }
1334 for i, res in ipairs(maps) do
1335 if res.apply_needed and res.parsechain then
1337 for _, c in ipairs(res.parsechain) do
1338 parsechain[#parsechain+1] = c
1343 if res.redirect then
1344 redirect = redirect or res.redirect
1347 if res.pageaction == false then
1352 messages = messages or { }
1353 messages[#messages+1] = res.message
1357 for i, res in ipairs(maps) do
1359 firstmap = (i == 1),
1360 redirect = redirect,
1361 messages = messages,
1362 pageaction = pageaction,
1363 parsechain = parsechain
1367 if not config.nofooter then
1368 tpl.render("cbi/footer", {
1370 pageaction = pageaction,
1371 redirect = redirect,
1373 autoapply = config.autoapply,
1374 trigger_apply = applymap
1379 function cbi(model, config)
1382 post = { ["cbi.submit"] = true },
1390 local function _arcombine(self, ...)
1392 local target = #argv > 0 and self.targets[2] or self.targets[1]
1393 setfenv(target.target, self.env)
1394 target:target(unpack(argv))
1397 function arcombine(trg1, trg2)
1398 return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
1402 local function _form(self, ...)
1403 local cbi = require "luci.cbi"
1404 local tpl = require "luci.template"
1405 local http = require "luci.http"
1407 local maps = luci.cbi.load(self.model, ...)
1411 for i, res in ipairs(maps) do
1412 local cstate = res:parse()
1413 if cstate and (not state or cstate < state) then
1418 http.header("X-CBI-State", state or 0)
1419 tpl.render("header")
1420 for i, res in ipairs(maps) do
1423 tpl.render("footer")
1426 function form(model)
1429 post = { ["cbi.submit"] = true },
1435 translate = i18n.translate
1437 -- This function does not actually translate the given argument but
1438 -- is used by build/i18n-scan.pl to find translatable entries.