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 local function determine_request_language()
507 local conf = require "luci.config"
508 assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'")
510 local lang = conf.main.lang or "auto"
511 if lang == "auto" then
512 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
513 for aclang in aclang:gmatch("[%w_-]+") do
514 local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
515 if country and culture then
516 local cc = "%s_%s" %{ country, culture:lower() }
517 if conf.languages[cc] then
520 elseif conf.languages[country] then
524 elseif conf.languages[aclang] then
531 if lang == "auto" then
535 i18n.setlanguage(lang)
538 function httpdispatch(request, prefix)
539 http.context.request = request
544 local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
547 for _, node in ipairs(prefix) do
553 for node in pathinfo:gmatch("[^/%z]+") do
557 determine_request_language()
559 local stat, err = util.coxpcall(function()
560 dispatch(context.request)
565 --context._disable_memtrace()
568 local function require_post_security(target, args)
569 if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
570 return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
573 if type(target) == "table" then
574 if type(target.post) == "table" then
575 local param_name, required_val, request_val
577 for param_name, required_val in pairs(target.post) do
578 request_val = http.formvalue(param_name)
580 if (type(required_val) == "string" and
581 request_val ~= required_val) or
582 (required_val == true and request_val == nil)
591 return (target.post == true)
597 function test_post_security()
598 if http.getenv("REQUEST_METHOD") ~= "POST" then
599 http.status(405, "Method Not Allowed")
600 http.header("Allow", "POST")
604 if http.formvalue("token") ~= context.authtoken then
605 http.status(403, "Forbidden")
606 luci.template.render("csrftoken")
613 local function session_retrieve(sid, allowed_users)
614 local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
616 if type(sdat) == "table" and
617 type(sdat.values) == "table" and
618 type(sdat.values.token) == "string" and
619 (not allowed_users or
620 util.contains(allowed_users, sdat.values.username))
622 uci:set_session_id(sid)
623 return sid, sdat.values
629 local function session_setup(user, pass, allowed_users)
630 if util.contains(allowed_users, user) then
631 local login = util.ubus("session", "login", {
634 timeout = tonumber(luci.config.sauth.sessiontime)
637 local rp = context.requestpath
638 and table.concat(context.requestpath, "/") or ""
640 if type(login) == "table" and
641 type(login.ubus_rpc_session) == "string"
643 util.ubus("session", "set", {
644 ubus_rpc_session = login.ubus_rpc_session,
645 values = { token = sys.uniqueid(16) }
648 io.stderr:write("luci: accepted login on /%s for %s from %s\n"
649 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
651 return session_retrieve(login.ubus_rpc_session)
654 io.stderr:write("luci: failed login on /%s for %s from %s\n"
655 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
662 local tree = context.tree or createtree()
663 return tree_to_json(tree, {
665 ["type"] = "firstchild",
671 function dispatch(request)
672 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
685 ctx.requestargs = ctx.requestargs or args
690 for i, s in ipairs(request) do
699 util.update(track, c)
707 for j=n+1, #request do
708 args[#args+1] = request[j]
709 freq[#freq+1] = request[j]
713 ctx.requestpath = ctx.requestpath or freq
716 -- Init template engine
717 if (c and c.index) or not track.notemplate then
718 local tpl = require("luci.template")
719 local media = track.mediaurlbase or luci.config.main.mediaurlbase
720 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
722 for name, theme in pairs(luci.config.themes) do
723 if name:sub(1,1) ~= "." and pcall(tpl.Template,
724 "themes/%s/header" % fs.basename(theme)) then
728 assert(media, "No valid theme found")
731 local function _ifattr(cond, key, val, noescape)
733 local env = getfenv(3)
734 local scope = (type(env.self) == "table") and env.self
735 if type(val) == "table" then
736 if not next(val) then
739 val = util.serialize_json(val)
743 val = tostring(val or
744 (type(env[key]) ~= "function" and env[key]) or
745 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
747 if noescape ~= true then
748 val = util.pcdata(val)
751 return string.format(' %s="%s"', tostring(key), val)
757 tpl.context.viewns = setmetatable({
759 include = function(name) tpl.Template(name):render(getfenv(2)) end;
760 translate = i18n.translate;
761 translatef = i18n.translatef;
762 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
763 striptags = util.striptags;
764 pcdata = util.pcdata;
766 theme = fs.basename(media);
767 resource = luci.config.main.resourcebase;
768 ifattr = function(...) return _ifattr(...) end;
769 attr = function(...) return _ifattr(true, ...) end;
771 }, {__index=function(tbl, key)
772 if key == "controller" then
774 elseif key == "REQUEST_URI" then
775 return build_url(unpack(ctx.requestpath))
776 elseif key == "FULL_REQUEST_URI" then
777 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
778 local query = http.getenv("QUERY_STRING")
779 if query and #query > 0 then
783 return table.concat(url, "")
784 elseif key == "token" then
787 return rawget(tbl, key) or _G[key]
792 track.dependent = (track.dependent ~= false)
793 assert(not track.dependent or not track.auto,
794 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
795 "has no parent node so the access to this location has been denied.\n" ..
796 "This is a software bug, please report this message at " ..
797 "https://github.com/openwrt/luci/issues"
800 if track.sysauth and not ctx.authsession then
801 local authen = track.sysauth_authenticator
802 local _, sid, sdat, default_user, allowed_users
804 if type(authen) == "string" and authen ~= "htmlauth" then
805 error500("Unsupported authenticator %q configured" % authen)
809 if type(track.sysauth) == "table" then
810 default_user, allowed_users = nil, track.sysauth
812 default_user, allowed_users = track.sysauth, { track.sysauth }
815 if type(authen) == "function" then
816 _, sid = authen(sys.user.checkpasswd, allowed_users)
818 sid = http.getcookie("sysauth")
821 sid, sdat = session_retrieve(sid, allowed_users)
823 if not (sid and sdat) and authen == "htmlauth" then
824 local user = http.getenv("HTTP_AUTH_USER")
825 local pass = http.getenv("HTTP_AUTH_PASS")
827 if user == nil and pass == nil then
828 user = http.formvalue("luci_username")
829 pass = http.formvalue("luci_password")
832 sid, sdat = session_setup(user, pass, allowed_users)
835 local tmpl = require "luci.template"
839 http.status(403, "Forbidden")
840 http.header("X-LuCI-Login-Required", "yes")
841 tmpl.render(track.sysauth_template or "sysauth", {
842 duser = default_user,
849 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
850 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
852 http.redirect(build_url(unpack(ctx.requestpath)))
855 if not sid or not sdat then
856 http.status(403, "Forbidden")
857 http.header("X-LuCI-Login-Required", "yes")
861 ctx.authsession = sid
862 ctx.authtoken = sdat.token
863 ctx.authuser = sdat.username
866 if track.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
867 luci.http.status(200, "OK")
868 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
869 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
873 if c and require_post_security(c.target, args) then
874 if not test_post_security(c) then
879 if track.setgroup then
880 sys.process.setgroup(track.setgroup)
883 if track.setuser then
884 sys.process.setuser(track.setuser)
889 if type(c.target) == "function" then
891 elseif type(c.target) == "table" then
892 target = c.target.target
896 if c and (c.index or type(target) == "function") then
898 ctx.requested = ctx.requested or ctx.dispatched
901 if c and c.index then
902 local tpl = require "luci.template"
904 if util.copcall(tpl.render, "indexer", {}) then
909 if type(target) == "function" then
910 util.copcall(function()
911 local oldenv = getfenv(target)
912 local module = require(c.module)
913 local env = setmetatable({}, {__index=
916 return rawget(tbl, key) or module[key] or oldenv[key]
923 if type(c.target) == "table" then
924 ok, err = util.copcall(target, c.target, unpack(args))
926 ok, err = util.copcall(target, unpack(args))
929 error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
930 " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
931 "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
935 if not root or not root.target then
936 error404("No root node was registered, this usually happens if no module was installed.\n" ..
937 "Install luci-mod-admin-full and retry. " ..
938 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
940 error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
941 "If this url belongs to an extension, make sure it is properly installed.\n" ..
942 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
947 function createindex()
948 local controllers = { }
949 local base = "%s/controller/" % util.libpath()
952 for path in (fs.glob("%s*.lua" % base) or function() end) do
953 controllers[#controllers+1] = path
956 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
957 controllers[#controllers+1] = path
961 local cachedate = fs.stat(indexcache, "mtime")
964 for _, obj in ipairs(controllers) do
965 local omtime = fs.stat(obj, "mtime")
966 realdate = (omtime and omtime > realdate) and omtime or realdate
969 if cachedate > realdate and sys.process.info("uid") == 0 then
971 sys.process.info("uid") == fs.stat(indexcache, "uid")
972 and fs.stat(indexcache, "modestr") == "rw-------",
973 "Fatal: Indexcache is not sane!"
976 index = loadfile(indexcache)()
984 for _, path in ipairs(controllers) do
985 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
986 local mod = require(modname)
988 "Invalid controller file found\n" ..
989 "The file '" .. path .. "' contains an invalid module line.\n" ..
990 "Please verify whether the module name is set to '" .. modname ..
991 "' - It must correspond to the file path!")
993 local idx = mod.index
994 assert(type(idx) == "function",
995 "Invalid controller file found\n" ..
996 "The file '" .. path .. "' contains no index() function.\n" ..
997 "Please make sure that the controller contains a valid " ..
998 "index function and verify the spelling!")
1000 index[modname] = idx
1004 local f = nixio.open(indexcache, "w", 600)
1005 f:writeall(util.get_bytecode(index))
1010 -- Build the index before if it does not exist yet.
1011 function createtree()
1017 local tree = {nodes={}, inreq=true}
1019 ctx.treecache = setmetatable({}, {__mode="v"})
1022 local scope = setmetatable({}, {__index = luci.dispatcher})
1024 for k, v in pairs(index) do
1033 function assign(path, clone, title, order)
1034 local obj = node(unpack(path))
1041 setmetatable(obj, {__index = _create_node(clone)})
1046 function entry(path, target, title, order)
1047 local c = node(unpack(path))
1052 c.module = getfenv(2)._NAME
1057 -- enabling the node.
1059 return _create_node({...})
1063 local c = _create_node({...})
1065 c.module = getfenv(2)._NAME
1071 function lookup(...)
1072 local i, path = nil, {}
1073 for i = 1, select('#', ...) do
1074 local name, arg = nil, tostring(select(i, ...))
1075 for name in arg:gmatch("[^/]+") do
1076 path[#path+1] = name
1080 for i = #path, 1, -1 do
1081 local node = context.treecache[table.concat(path, ".", 1, i)]
1082 if node and (i == #path or node.leaf) then
1083 return node, build_url(unpack(path))
1088 function _create_node(path)
1093 local name = table.concat(path, ".")
1094 local c = context.treecache[name]
1097 local last = table.remove(path)
1098 local parent = _create_node(path)
1100 c = {nodes={}, auto=true, inreq=true}
1103 for _, n in ipairs(path) do
1104 if context.path[_] ~= n then
1110 c.inreq = c.inreq and (context.path[#path + 1] == last)
1112 parent.nodes[last] = c
1113 context.treecache[name] = c
1119 -- Subdispatchers --
1121 function _find_eligible_node(root, prefix, deep, types, descend)
1122 local children = _ordered_children(root)
1124 if not root.leaf and deep ~= nil then
1125 local sub_path = { unpack(prefix) }
1127 if deep == false then
1132 for _, child in ipairs(children) do
1133 sub_path[#prefix+1] = child.name
1135 local res_path = _find_eligible_node(child.node, sub_path,
1146 (type(root.target) == "table" and
1147 util.contains(types, root.target.type)))
1153 function _find_node(recurse, types)
1154 local path = { unpack(context.path) }
1155 local name = table.concat(path, ".")
1156 local node = context.treecache[name]
1158 path = _find_eligible_node(node, path, recurse, types)
1163 require "luci.template".render("empty_node_placeholder")
1167 function _firstchild()
1168 return _find_node(false, nil)
1171 function firstchild()
1172 return { type = "firstchild", target = _firstchild }
1175 function _firstnode()
1176 return _find_node(true, { "cbi", "form", "template", "arcombine" })
1179 function firstnode()
1180 return { type = "firstnode", target = _firstnode }
1183 function _alias(self, ...)
1184 local req = { unpack(self.req) }
1186 for _, r in ipairs({...}) do
1194 return { type = "alias", target = _alias, req = { ... } }
1197 function _rewrite(self, ...)
1199 local req = { unpack(self.req) }
1200 local dispatched = util.clone(context.dispatched)
1203 table.remove(dispatched, 1)
1206 for i, r in ipairs(req) do
1207 table.insert(dispatched, i, r)
1210 for _, r in ipairs({...}) do
1211 dispatched[#dispatched+1] = r
1214 dispatch(dispatched)
1217 function rewrite(n, ...)
1218 return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
1221 local function _call(self, ...)
1222 local func = getfenv()[self.name]
1224 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
1226 assert(type(func) == "function",
1227 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
1228 'of type "' .. type(func) .. '".')
1230 if #self.argv > 0 then
1231 return func(unpack(self.argv), ...)
1237 function call(name, ...)
1238 return {type = "call", argv = {...}, name = name, target = _call}
1241 function post_on(params, name, ...)
1252 return post_on(true, ...)
1256 local _template = function(self, ...)
1257 require "luci.template".render(self.view)
1260 function template(name)
1261 return {type = "template", view = name, target = _template}
1265 local _view = function(self, ...)
1266 require "luci.template".render("view", { view = self.view })
1270 return {type = "view", view = name, target = _view}
1274 local function _cbi(self, ...)
1275 local cbi = require "luci.cbi"
1276 local tpl = require "luci.template"
1277 local http = require "luci.http"
1279 local config = self.config or {}
1280 local maps = cbi.load(self.model, ...)
1285 for i, res in ipairs(maps) do
1286 if util.instanceof(res, cbi.SimpleForm) then
1287 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1290 io.stderr:write("please change %s to use the form() action instead.\n"
1291 % table.concat(context.request, "/"))
1295 local cstate = res:parse()
1296 if cstate and (not state or cstate < state) then
1301 local function _resolve_path(path)
1302 return type(path) == "table" and build_url(unpack(path)) or path
1305 if config.on_valid_to and state and state > 0 and state < 2 then
1306 http.redirect(_resolve_path(config.on_valid_to))
1310 if config.on_changed_to and state and state > 1 then
1311 http.redirect(_resolve_path(config.on_changed_to))
1315 if config.on_success_to and state and state > 0 then
1316 http.redirect(_resolve_path(config.on_success_to))
1320 if config.state_handler then
1321 if not config.state_handler(state, maps) then
1326 http.header("X-CBI-State", state or 0)
1328 if not config.noheader then
1329 tpl.render("cbi/header", {state = state})
1334 local applymap = false
1335 local pageaction = true
1336 local parsechain = { }
1338 for i, res in ipairs(maps) do
1339 if res.apply_needed and res.parsechain then
1341 for _, c in ipairs(res.parsechain) do
1342 parsechain[#parsechain+1] = c
1347 if res.redirect then
1348 redirect = redirect or res.redirect
1351 if res.pageaction == false then
1356 messages = messages or { }
1357 messages[#messages+1] = res.message
1361 for i, res in ipairs(maps) do
1363 firstmap = (i == 1),
1364 redirect = redirect,
1365 messages = messages,
1366 pageaction = pageaction,
1367 parsechain = parsechain
1371 if not config.nofooter then
1372 tpl.render("cbi/footer", {
1374 pageaction = pageaction,
1375 redirect = redirect,
1377 autoapply = config.autoapply,
1378 trigger_apply = applymap
1383 function cbi(model, config)
1386 post = { ["cbi.submit"] = true },
1394 local function _arcombine(self, ...)
1396 local target = #argv > 0 and self.targets[2] or self.targets[1]
1397 setfenv(target.target, self.env)
1398 target:target(unpack(argv))
1401 function arcombine(trg1, trg2)
1402 return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
1406 local function _form(self, ...)
1407 local cbi = require "luci.cbi"
1408 local tpl = require "luci.template"
1409 local http = require "luci.http"
1411 local maps = luci.cbi.load(self.model, ...)
1415 for i, res in ipairs(maps) do
1416 local cstate = res:parse()
1417 if cstate and (not state or cstate < state) then
1422 http.header("X-CBI-State", state or 0)
1423 tpl.render("header")
1424 for i, res in ipairs(maps) do
1427 tpl.render("footer")
1430 function form(model)
1433 post = { ["cbi.submit"] = true },
1439 translate = i18n.translate
1441 -- This function does not actually translate the given argument but
1442 -- is used by build/i18n-scan.pl to find translatable entries.