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 local function init_template_engine(ctx)
672 local tpl = require "luci.template"
673 local media = luci.config.main.mediaurlbase
675 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
677 for name, theme in pairs(luci.config.themes) do
678 if name:sub(1,1) ~= "." and pcall(tpl.Template,
679 "themes/%s/header" % fs.basename(theme)) then
683 assert(media, "No valid theme found")
686 local function _ifattr(cond, key, val, noescape)
688 local env = getfenv(3)
689 local scope = (type(env.self) == "table") and env.self
690 if type(val) == "table" then
691 if not next(val) then
694 val = util.serialize_json(val)
698 val = tostring(val or
699 (type(env[key]) ~= "function" and env[key]) or
700 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
702 if noescape ~= true then
703 val = util.pcdata(val)
706 return string.format(' %s="%s"', tostring(key), val)
712 tpl.context.viewns = setmetatable({
714 include = function(name) tpl.Template(name):render(getfenv(2)) end;
715 translate = i18n.translate;
716 translatef = i18n.translatef;
717 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
718 striptags = util.striptags;
719 pcdata = util.pcdata;
721 theme = fs.basename(media);
722 resource = luci.config.main.resourcebase;
723 ifattr = function(...) return _ifattr(...) end;
724 attr = function(...) return _ifattr(true, ...) end;
726 }, {__index=function(tbl, key)
727 if key == "controller" then
729 elseif key == "REQUEST_URI" then
730 return build_url(unpack(ctx.requestpath))
731 elseif key == "FULL_REQUEST_URI" then
732 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
733 local query = http.getenv("QUERY_STRING")
734 if query and #query > 0 then
738 return table.concat(url, "")
739 elseif key == "token" then
742 return rawget(tbl, key) or _G[key]
749 function dispatch(request)
750 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
763 ctx.requestargs = ctx.requestargs or args
768 for i, s in ipairs(request) do
777 util.update(track, c)
785 for j=n+1, #request do
786 args[#args+1] = request[j]
787 freq[#freq+1] = request[j]
791 ctx.requestpath = ctx.requestpath or freq
794 -- Init template engine
795 if (c and c.index) or not track.notemplate then
796 init_template_engine(ctx)
799 track.dependent = (track.dependent ~= false)
800 assert(not track.dependent or not track.auto,
801 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
802 "has no parent node so the access to this location has been denied.\n" ..
803 "This is a software bug, please report this message at " ..
804 "https://github.com/openwrt/luci/issues"
807 if track.sysauth and not ctx.authsession then
808 local authen = track.sysauth_authenticator
809 local _, sid, sdat, default_user, allowed_users
811 if type(authen) == "string" and authen ~= "htmlauth" then
812 error500("Unsupported authenticator %q configured" % authen)
816 if type(track.sysauth) == "table" then
817 default_user, allowed_users = nil, track.sysauth
819 default_user, allowed_users = track.sysauth, { track.sysauth }
822 if type(authen) == "function" then
823 _, sid = authen(sys.user.checkpasswd, allowed_users)
825 sid = http.getcookie("sysauth")
828 sid, sdat = session_retrieve(sid, allowed_users)
830 if not (sid and sdat) and authen == "htmlauth" then
831 local user = http.getenv("HTTP_AUTH_USER")
832 local pass = http.getenv("HTTP_AUTH_PASS")
834 if user == nil and pass == nil then
835 user = http.formvalue("luci_username")
836 pass = http.formvalue("luci_password")
839 sid, sdat = session_setup(user, pass, allowed_users)
842 local tmpl = require "luci.template"
846 http.status(403, "Forbidden")
847 http.header("X-LuCI-Login-Required", "yes")
848 tmpl.render(track.sysauth_template or "sysauth", {
849 duser = default_user,
856 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
857 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
859 http.redirect(build_url(unpack(ctx.requestpath)))
862 if not sid or not sdat then
863 http.status(403, "Forbidden")
864 http.header("X-LuCI-Login-Required", "yes")
868 ctx.authsession = sid
869 ctx.authtoken = sdat.token
870 ctx.authuser = sdat.username
873 if track.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
874 luci.http.status(200, "OK")
875 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
876 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
880 if c and require_post_security(c.target, args) then
881 if not test_post_security(c) then
886 if track.setgroup then
887 sys.process.setgroup(track.setgroup)
890 if track.setuser then
891 sys.process.setuser(track.setuser)
896 if type(c.target) == "function" then
898 elseif type(c.target) == "table" then
899 target = c.target.target
903 if c and (c.index or type(target) == "function") then
905 ctx.requested = ctx.requested or ctx.dispatched
908 if c and c.index then
909 local tpl = require "luci.template"
911 if util.copcall(tpl.render, "indexer", {}) then
916 if type(target) == "function" then
917 util.copcall(function()
918 local oldenv = getfenv(target)
919 local module = require(c.module)
920 local env = setmetatable({}, {__index=
923 return rawget(tbl, key) or module[key] or oldenv[key]
930 if type(c.target) == "table" then
931 ok, err = util.copcall(target, c.target, unpack(args))
933 ok, err = util.copcall(target, unpack(args))
936 error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
937 " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
938 "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
942 if not root or not root.target then
943 error404("No root node was registered, this usually happens if no module was installed.\n" ..
944 "Install luci-mod-admin-full and retry. " ..
945 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
947 error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
948 "If this url belongs to an extension, make sure it is properly installed.\n" ..
949 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
954 function createindex()
955 local controllers = { }
956 local base = "%s/controller/" % util.libpath()
959 for path in (fs.glob("%s*.lua" % base) or function() end) do
960 controllers[#controllers+1] = path
963 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
964 controllers[#controllers+1] = path
968 local cachedate = fs.stat(indexcache, "mtime")
971 for _, obj in ipairs(controllers) do
972 local omtime = fs.stat(obj, "mtime")
973 realdate = (omtime and omtime > realdate) and omtime or realdate
976 if cachedate > realdate and sys.process.info("uid") == 0 then
978 sys.process.info("uid") == fs.stat(indexcache, "uid")
979 and fs.stat(indexcache, "modestr") == "rw-------",
980 "Fatal: Indexcache is not sane!"
983 index = loadfile(indexcache)()
991 for _, path in ipairs(controllers) do
992 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
993 local mod = require(modname)
995 "Invalid controller file found\n" ..
996 "The file '" .. path .. "' contains an invalid module line.\n" ..
997 "Please verify whether the module name is set to '" .. modname ..
998 "' - It must correspond to the file path!")
1000 local idx = mod.index
1001 assert(type(idx) == "function",
1002 "Invalid controller file found\n" ..
1003 "The file '" .. path .. "' contains no index() function.\n" ..
1004 "Please make sure that the controller contains a valid " ..
1005 "index function and verify the spelling!")
1007 index[modname] = idx
1011 local f = nixio.open(indexcache, "w", 600)
1012 f:writeall(util.get_bytecode(index))
1017 -- Build the index before if it does not exist yet.
1018 function createtree()
1024 local tree = {nodes={}, inreq=true}
1026 ctx.treecache = setmetatable({}, {__mode="v"})
1029 local scope = setmetatable({}, {__index = luci.dispatcher})
1031 for k, v in pairs(index) do
1040 function assign(path, clone, title, order)
1041 local obj = node(unpack(path))
1048 setmetatable(obj, {__index = _create_node(clone)})
1053 function entry(path, target, title, order)
1054 local c = node(unpack(path))
1059 c.module = getfenv(2)._NAME
1064 -- enabling the node.
1066 return _create_node({...})
1070 local c = _create_node({...})
1072 c.module = getfenv(2)._NAME
1078 function lookup(...)
1079 local i, path = nil, {}
1080 for i = 1, select('#', ...) do
1081 local name, arg = nil, tostring(select(i, ...))
1082 for name in arg:gmatch("[^/]+") do
1083 path[#path+1] = name
1087 for i = #path, 1, -1 do
1088 local node = context.treecache[table.concat(path, ".", 1, i)]
1089 if node and (i == #path or node.leaf) then
1090 return node, build_url(unpack(path))
1095 function _create_node(path)
1100 local name = table.concat(path, ".")
1101 local c = context.treecache[name]
1104 local last = table.remove(path)
1105 local parent = _create_node(path)
1107 c = {nodes={}, auto=true, inreq=true}
1110 for _, n in ipairs(path) do
1111 if context.path[_] ~= n then
1117 c.inreq = c.inreq and (context.path[#path + 1] == last)
1119 parent.nodes[last] = c
1120 context.treecache[name] = c
1126 -- Subdispatchers --
1128 function _find_eligible_node(root, prefix, deep, types, descend)
1129 local children = _ordered_children(root)
1131 if not root.leaf and deep ~= nil then
1132 local sub_path = { unpack(prefix) }
1134 if deep == false then
1139 for _, child in ipairs(children) do
1140 sub_path[#prefix+1] = child.name
1142 local res_path = _find_eligible_node(child.node, sub_path,
1153 (type(root.target) == "table" and
1154 util.contains(types, root.target.type)))
1160 function _find_node(recurse, types)
1161 local path = { unpack(context.path) }
1162 local name = table.concat(path, ".")
1163 local node = context.treecache[name]
1165 path = _find_eligible_node(node, path, recurse, types)
1170 require "luci.template".render("empty_node_placeholder")
1174 function _firstchild()
1175 return _find_node(false, nil)
1178 function firstchild()
1179 return { type = "firstchild", target = _firstchild }
1182 function _firstnode()
1183 return _find_node(true, { "cbi", "form", "template", "arcombine" })
1186 function firstnode()
1187 return { type = "firstnode", target = _firstnode }
1190 function _alias(self, ...)
1191 local req = { unpack(self.req) }
1193 for _, r in ipairs({...}) do
1201 return { type = "alias", target = _alias, req = { ... } }
1204 function _rewrite(self, ...)
1206 local req = { unpack(self.req) }
1207 local dispatched = util.clone(context.dispatched)
1210 table.remove(dispatched, 1)
1213 for i, r in ipairs(req) do
1214 table.insert(dispatched, i, r)
1217 for _, r in ipairs({...}) do
1218 dispatched[#dispatched+1] = r
1221 dispatch(dispatched)
1224 function rewrite(n, ...)
1225 return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
1228 local function _call(self, ...)
1229 local func = getfenv()[self.name]
1231 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
1233 assert(type(func) == "function",
1234 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
1235 'of type "' .. type(func) .. '".')
1237 if #self.argv > 0 then
1238 return func(unpack(self.argv), ...)
1244 function call(name, ...)
1245 return {type = "call", argv = {...}, name = name, target = _call}
1248 function post_on(params, name, ...)
1259 return post_on(true, ...)
1263 local _template = function(self, ...)
1264 require "luci.template".render(self.view)
1267 function template(name)
1268 return {type = "template", view = name, target = _template}
1272 local _view = function(self, ...)
1273 require "luci.template".render("view", { view = self.view })
1277 return {type = "view", view = name, target = _view}
1281 local function _cbi(self, ...)
1282 local cbi = require "luci.cbi"
1283 local tpl = require "luci.template"
1284 local http = require "luci.http"
1286 local config = self.config or {}
1287 local maps = cbi.load(self.model, ...)
1292 for i, res in ipairs(maps) do
1293 if util.instanceof(res, cbi.SimpleForm) then
1294 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1297 io.stderr:write("please change %s to use the form() action instead.\n"
1298 % table.concat(context.request, "/"))
1302 local cstate = res:parse()
1303 if cstate and (not state or cstate < state) then
1308 local function _resolve_path(path)
1309 return type(path) == "table" and build_url(unpack(path)) or path
1312 if config.on_valid_to and state and state > 0 and state < 2 then
1313 http.redirect(_resolve_path(config.on_valid_to))
1317 if config.on_changed_to and state and state > 1 then
1318 http.redirect(_resolve_path(config.on_changed_to))
1322 if config.on_success_to and state and state > 0 then
1323 http.redirect(_resolve_path(config.on_success_to))
1327 if config.state_handler then
1328 if not config.state_handler(state, maps) then
1333 http.header("X-CBI-State", state or 0)
1335 if not config.noheader then
1336 tpl.render("cbi/header", {state = state})
1341 local applymap = false
1342 local pageaction = true
1343 local parsechain = { }
1345 for i, res in ipairs(maps) do
1346 if res.apply_needed and res.parsechain then
1348 for _, c in ipairs(res.parsechain) do
1349 parsechain[#parsechain+1] = c
1354 if res.redirect then
1355 redirect = redirect or res.redirect
1358 if res.pageaction == false then
1363 messages = messages or { }
1364 messages[#messages+1] = res.message
1368 for i, res in ipairs(maps) do
1370 firstmap = (i == 1),
1371 redirect = redirect,
1372 messages = messages,
1373 pageaction = pageaction,
1374 parsechain = parsechain
1378 if not config.nofooter then
1379 tpl.render("cbi/footer", {
1381 pageaction = pageaction,
1382 redirect = redirect,
1384 autoapply = config.autoapply,
1385 trigger_apply = applymap
1390 function cbi(model, config)
1393 post = { ["cbi.submit"] = true },
1401 local function _arcombine(self, ...)
1403 local target = #argv > 0 and self.targets[2] or self.targets[1]
1404 setfenv(target.target, self.env)
1405 target:target(unpack(argv))
1408 function arcombine(trg1, trg2)
1409 return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
1413 local function _form(self, ...)
1414 local cbi = require "luci.cbi"
1415 local tpl = require "luci.template"
1416 local http = require "luci.http"
1418 local maps = luci.cbi.load(self.model, ...)
1422 for i, res in ipairs(maps) do
1423 local cstate = res:parse()
1424 if cstate and (not state or cstate < state) then
1429 http.header("X-CBI-State", state or 0)
1430 tpl.render("header")
1431 for i, res in ipairs(maps) do
1434 tpl.render("footer")
1437 function form(model)
1440 post = { ["cbi.submit"] = true },
1446 translate = i18n.translate
1448 -- This function does not actually translate the given argument but
1449 -- is used by build/i18n-scan.pl to find translatable entries.