local fi
+local function check_fs_depends(fs)
+ local fs = require "nixio.fs"
+
+ for path, kind in pairs(fs) do
+ if kind == "directory" then
+ local empty = true
+ for entry in (fs.dir(path) or function() end) do
+ empty = false
+ break
+ end
+ if empty then
+ return false
+ end
+ elseif kind == "executable" then
+ if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
+ return false
+ end
+ elseif kind == "file" then
+ if fs.stat(path, "type") ~= "reg" then
+ return false
+ end
+ end
+ end
+
+ return true
+end
+
+local function check_uci_depends_options(conf, s, opts)
+ local uci = require "luci.model.uci"
+
+ if type(opts) == "string" then
+ return (s[".type"] == opts)
+ elseif opts == true then
+ for option, value in pairs(s) do
+ if option:byte(1) ~= 46 then
+ return true
+ end
+ end
+ elseif type(opts) == "table" then
+ for option, value in pairs(opts) do
+ local sval = s[option]
+ if type(sval) == "table" then
+ local found = false
+ for _, v in ipairs(sval) do
+ if v == value then
+ found = true
+ break
+ end
+ end
+ if not found then
+ return false
+ end
+ elseif value == true then
+ if sval == nil then
+ return false
+ end
+ else
+ if sval ~= value then
+ return false
+ end
+ end
+ end
+ end
+
+ return true
+end
+
+local function check_uci_depends_section(conf, sect)
+ local uci = require "luci.model.uci"
+
+ for section, options in pairs(sect) do
+ local stype = section:match("^@([A-Za-z0-9_%-]+)$")
+ if stype then
+ local found = false
+ uci:foreach(conf, stype, function(s)
+ if check_uci_depends_options(conf, s, options) then
+ found = true
+ return false
+ end
+ end)
+ if not found then
+ return false
+ end
+ else
+ local s = uci:get_all(conf, section)
+ if not s or not check_uci_depends_options(conf, s, options) then
+ return false
+ end
+ end
+ end
+
+ return true
+end
+
+local function check_uci_depends(conf)
+ local uci = require "luci.model.uci"
+
+ for config, values in pairs(conf) do
+ if values == true then
+ local found = false
+ uci:foreach(config, nil, function(s)
+ found = true
+ return false
+ end)
+ if not found then
+ return false
+ end
+ elseif type(values) == "table" then
+ if not check_uci_depends_section(config, values) then
+ return false
+ end
+ end
+ end
+
+ return true
+end
+
+local function check_depends(spec)
+ if type(spec.depends) ~= "table" then
+ return true
+ end
+
+ if type(spec.depends.fs) == "table" and not check_fs_depends(spec.depends.fs) then
+ local satisfied = false
+ local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
+ for _, alternative in ipairs(alternatives) do
+ if check_fs_depends(alternative) then
+ satisfied = true
+ break
+ end
+ end
+ if not satisfied then
+ return false
+ end
+ end
+
+ if type(spec.depends.uci) == "table" then
+ local satisfied = false
+ local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
+ for _, alternative in ipairs(alternatives) do
+ if check_uci_depends(alternative) then
+ satisfied = true
+ break
+ end
+ end
+ if not satisfied then
+ return false
+ end
+ end
+
+ return true
+end
+
+local function target_to_json(target, module)
+ local action
+
+ if target.type == "call" then
+ action = {
+ ["type"] = "call",
+ ["module"] = module,
+ ["function"] = target.name,
+ ["parameters"] = target.argv
+ }
+ elseif target.type == "view" then
+ action = {
+ ["type"] = "view",
+ ["path"] = target.view
+ }
+ elseif target.type == "template" then
+ action = {
+ ["type"] = "template",
+ ["path"] = target.view
+ }
+ elseif target.type == "cbi" then
+ action = {
+ ["type"] = "cbi",
+ ["path"] = target.model
+ }
+ elseif target.type == "form" then
+ action = {
+ ["type"] = "form",
+ ["path"] = target.model
+ }
+ elseif target.type == "firstchild" then
+ action = {
+ ["type"] = "firstchild"
+ }
+ elseif target.type == "firstnode" then
+ action = {
+ ["type"] = "firstchild",
+ ["recurse"] = true
+ }
+ elseif target.type == "arcombine" then
+ if type(target.targets) == "table" then
+ action = {
+ ["type"] = "arcombine",
+ ["targets"] = {
+ target_to_json(target.targets[1], module),
+ target_to_json(target.targets[2], module)
+ }
+ }
+ end
+ elseif target.type == "alias" then
+ action = {
+ ["type"] = "alias",
+ ["path"] = table.concat(target.req, "/")
+ }
+ elseif target.type == "rewrite" then
+ action = {
+ ["type"] = "rewrite",
+ ["path"] = table.concat(target.req, "/"),
+ ["remove"] = target.n
+ }
+ end
+
+ if target.post and action then
+ action.post = target.post
+ end
+
+ return action
+end
+
+local function tree_to_json(node, json)
+ local fs = require "nixio.fs"
+ local util = require "luci.util"
+
+ if type(node.nodes) == "table" then
+ for subname, subnode in pairs(node.nodes) do
+ local spec = {
+ title = util.striptags(subnode.title),
+ order = subnode.order
+ }
+
+ if subnode.leaf then
+ spec.wildcard = true
+ end
+
+ if subnode.cors then
+ spec.cors = true
+ end
+
+ if subnode.setuser then
+ spec.setuser = subnode.setuser
+ end
+
+ if subnode.setgroup then
+ spec.setgroup = subnode.setgroup
+ end
+
+ if type(subnode.target) == "table" then
+ spec.action = target_to_json(subnode.target, subnode.module)
+ end
+
+ if type(subnode.file_depends) == "table" then
+ for _, v in ipairs(subnode.file_depends) do
+ spec.depends = spec.depends or {}
+ spec.depends.fs = spec.depends.fs or {}
+
+ local ft = fs.stat(v, "type")
+ if ft == "dir" then
+ spec.depends.fs[v] = "directory"
+ elseif v:match("/s?bin/") then
+ spec.depends.fs[v] = "executable"
+ else
+ spec.depends.fs[v] = "file"
+ end
+ end
+ end
+
+ if type(subnode.uci_depends) == "table" then
+ for k, v in pairs(subnode.uci_depends) do
+ spec.depends = spec.depends or {}
+ spec.depends.uci = spec.depends.uci or {}
+ spec.depends.uci[k] = v
+ end
+ end
+
+ if (subnode.sysauth_authenticator ~= nil) or
+ (subnode.sysauth ~= nil and subnode.sysauth ~= false)
+ then
+ if subnode.sysauth_authenticator == "htmlauth" then
+ spec.auth = {
+ login = true,
+ methods = { "cookie:sysauth" }
+ }
+ elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
+ spec.auth = {
+ login = false,
+ methods = { "param:auth", "cookie:sysauth" }
+ }
+ elseif subnode.module == "luci.controller.admin.uci" then
+ spec.auth = {
+ login = false,
+ methods = { "param:sid" }
+ }
+ end
+ elseif subnode.sysauth == false then
+ spec.auth = {}
+ end
+
+ for _, v in pairs(spec) do
+ if v ~= nil then
+ if not spec.action then
+ spec.title = nil
+ end
+
+ spec.satisfied = check_depends(spec)
+ json.children = json.children or {}
+ json.children[subname] = tree_to_json(subnode, spec)
+ break
+ end
+ end
+ end
+ end
+
+ return json
+end
+
function build_url(...)
local path = {...}
local url = { http.getenv("SCRIPT_NAME") or "" }
return table.concat(url, "")
end
+function _ordered_children(node)
+ local name, child, children = nil, nil, {}
+
+ for name, child in pairs(node.nodes) do
+ children[#children+1] = {
+ name = name,
+ node = child,
+ order = child.order or 100
+ }
+ end
+
+ table.sort(children, function(a, b)
+ if a.order == b.order then
+ return a.name < b.name
+ else
+ return a.order < b.order
+ end
+ end)
+
+ return children
+end
+
+local function dependencies_satisfied(node)
+ if type(node.file_depends) == "table" then
+ for _, file in ipairs(node.file_depends) do
+ local ftype = fs.stat(file, "type")
+ if ftype == "dir" then
+ local empty = true
+ for e in (fs.dir(file) or function() end) do
+ empty = false
+ end
+ if empty then
+ return false
+ end
+ elseif ftype == nil then
+ return false
+ end
+ end
+ end
+
+ if type(node.uci_depends) == "table" then
+ for config, expect_sections in pairs(node.uci_depends) do
+ if type(expect_sections) == "table" then
+ for section, expect_options in pairs(expect_sections) do
+ if type(expect_options) == "table" then
+ for option, expect_value in pairs(expect_options) do
+ local val = uci:get(config, section, option)
+ if expect_value == true and val == nil then
+ return false
+ elseif type(expect_value) == "string" then
+ if type(val) == "table" then
+ local found = false
+ for _, subval in ipairs(val) do
+ if subval == expect_value then
+ found = true
+ end
+ end
+ if not found then
+ return false
+ end
+ elseif val ~= expect_value then
+ return false
+ end
+ end
+ end
+ else
+ local val = uci:get(config, section)
+ if expect_options == true and val == nil then
+ return false
+ elseif type(expect_options) == "string" and val ~= expect_options then
+ return false
+ end
+ end
+ end
+ elseif expect_sections == true then
+ if not uci:get_first(config) then
+ return false
+ end
+ end
+ end
+ end
+
+ return true
+end
+
function node_visible(node)
if node then
return not (
+ (not dependencies_satisfied(node)) or
(not node.title or #node.title == 0) or
(not node.target or node.hidden == true) or
(type(node.target) == "table" and node.target.type == "firstchild" and
function node_childs(node)
local rv = { }
if node then
- local k, v
- for k, v in util.spairs(node.nodes,
- function(a, b)
- return (node.nodes[a].order or 100)
- < (node.nodes[b].order or 100)
- end)
- do
- if node_visible(v) then
- rv[#rv+1] = k
+ local _, child
+ for _, child in ipairs(_ordered_children(node)) do
+ if node_visible(child.node) then
+ rv[#rv+1] = child.name
end
end
end
--context._disable_memtrace()
end
-local function require_post_security(target)
+local function require_post_security(target, args)
+ if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
+ return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
+ end
+
if type(target) == "table" then
if type(target.post) == "table" then
local param_name, required_val, request_val
return nil, nil
end
+function menu_json()
+ local tree = context.tree or createtree()
+ return tree_to_json(tree, {
+ action = {
+ ["type"] = "firstchild",
+ ["recurse"] = true
+ }
+ })
+end
+
function dispatch(request)
--context._disable_memtrace = require "luci.debug".trap_memtrace("l")
local ctx = context
ctx.requestpath = ctx.requestpath or freq
ctx.path = preq
- if track.i18n then
- i18n.loadc(track.i18n)
- end
-
-- Init template engine
if (c and c.index) or not track.notemplate then
local tpl = require("luci.template")
assert(media, "No valid theme found")
end
- local function _ifattr(cond, key, val)
+ local function _ifattr(cond, key, val, noescape)
if cond then
local env = getfenv(3)
local scope = (type(env.self) == "table") and env.self
val = util.serialize_json(val)
end
end
- return string.format(
- ' %s="%s"', tostring(key),
- util.pcdata(tostring( val
- or (type(env[key]) ~= "function" and env[key])
- or (scope and type(scope[key]) ~= "function" and scope[key])
- or "" ))
- )
+
+ val = tostring(val or
+ (type(env[key]) ~= "function" and env[key]) or
+ (scope and type(scope[key]) ~= "function" and scope[key]) or "")
+
+ if noescape ~= true then
+ val = util.pcdata(val)
+ end
+
+ return string.format(' %s="%s"', tostring(key), val)
else
return ''
end
elseif key == "REQUEST_URI" then
return build_url(unpack(ctx.requestpath))
elseif key == "FULL_REQUEST_URI" then
- local url = { http.getenv("SCRIPT_NAME"), http.getenv("PATH_INFO") }
+ local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
local query = http.getenv("QUERY_STRING")
if query and #query > 0 then
url[#url+1] = "?"
context.path = {}
http.status(403, "Forbidden")
+ http.header("X-LuCI-Login-Required", "yes")
tmpl.render(track.sysauth_template or "sysauth", {
duser = default_user,
fuser = user
return
end
- http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sid, build_url() })
+ http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
+ sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
+ })
http.redirect(build_url(unpack(ctx.requestpath)))
end
if not sid or not sdat then
http.status(403, "Forbidden")
+ http.header("X-LuCI-Login-Required", "yes")
return
end
return
end
- if c and require_post_security(c.target) then
+ if c and require_post_security(c.target, args) then
if not test_post_security(c) then
return
end
else
ok, err = util.copcall(target, unpack(args))
end
- assert(ok,
- "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
- " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
- "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
+ if not ok then
+ error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
+ " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
+ "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
+ end
else
local root = node()
if not root or not root.target then
local ctx = context
local tree = {nodes={}, inreq=true}
- local modi = {}
ctx.treecache = setmetatable({}, {__mode="v"})
ctx.tree = tree
- ctx.modifiers = modi
-
- -- Load default translation
- require "luci.i18n".loadc("base")
local scope = setmetatable({}, {__index = luci.dispatcher})
v()
end
- local function modisort(a,b)
- return modi[a].order < modi[b].order
- end
-
- for _, v in util.spairs(modi, modisort) do
- scope._NAME = v.module
- setfenv(v.func, scope)
- v.func()
- end
-
return tree
end
-function modifier(func, order)
- context.modifiers[#context.modifiers+1] = {
- func = func,
- order = order or 0,
- module
- = getfenv(2)._NAME
- }
-end
-
function assign(path, clone, title, order)
local obj = node(unpack(path))
obj.nodes = nil
local last = table.remove(path)
local parent = _create_node(path)
- c = {nodes={}, auto=true}
- -- the node is "in request" if the request path matches
- -- at least up to the length of the node path
- if parent.inreq and context.path[#path+1] == last then
- c.inreq = true
+ c = {nodes={}, auto=true, inreq=true}
+
+ local _, n
+ for _, n in ipairs(path) do
+ if context.path[_] ~= n then
+ c.inreq = false
+ break
+ end
end
+
+ c.inreq = c.inreq and (context.path[#path + 1] == last)
+
parent.nodes[last] = c
context.treecache[name] = c
end
+
return c
end
-- Subdispatchers --
-function _firstchild()
- local path = { unpack(context.path) }
- local name = table.concat(path, ".")
- local node = context.treecache[name]
-
- local lowest
- if node and node.nodes and next(node.nodes) then
- local k, v
- for k, v in pairs(node.nodes) do
- if not lowest or
- (v.order or 100) < (node.nodes[lowest].order or 100)
- then
- lowest = k
- end
- end
- end
+function _find_eligible_node(root, prefix, deep, types, descend)
+ local children = _ordered_children(root)
- assert(lowest ~= nil,
- "The requested node contains no childs, unable to redispatch")
+ if not root.leaf and deep ~= nil then
+ local sub_path = { unpack(prefix) }
- path[#path+1] = lowest
- dispatch(path)
+ if deep == false then
+ deep = nil
+ end
+
+ local _, child
+ for _, child in ipairs(children) do
+ sub_path[#prefix+1] = child.name
+
+ local res_path = _find_eligible_node(child.node, sub_path,
+ deep, types, true)
+
+ if res_path then
+ return res_path
+ end
+ end
+ end
+
+ if descend and
+ (not types or
+ (type(root.target) == "table" and
+ util.contains(types, root.target.type)))
+ then
+ return prefix
+ end
+end
+
+function _find_node(recurse, types)
+ local path = { unpack(context.path) }
+ local name = table.concat(path, ".")
+ local node = context.treecache[name]
+
+ path = _find_eligible_node(node, path, recurse, types)
+
+ if path then
+ dispatch(path)
+ else
+ require "luci.template".render("empty_node_placeholder")
+ end
+end
+
+function _firstchild()
+ return _find_node(false, nil)
end
function firstchild()
- return { type = "firstchild", target = _firstchild }
+ return { type = "firstchild", target = _firstchild }
end
-function alias(...)
- local req = {...}
- return function(...)
- for _, r in ipairs({...}) do
- req[#req+1] = r
- end
+function _firstnode()
+ return _find_node(true, { "cbi", "form", "template", "arcombine" })
+end
- dispatch(req)
+function firstnode()
+ return { type = "firstnode", target = _firstnode }
+end
+
+function _alias(self, ...)
+ local req = { unpack(self.req) }
+
+ for _, r in ipairs({...}) do
+ req[#req+1] = r
end
+
+ dispatch(req)
end
-function rewrite(n, ...)
- local req = {...}
- return function(...)
- local dispatched = util.clone(context.dispatched)
+function alias(...)
+ return { type = "alias", target = _alias, req = { ... } }
+end
- for i=1,n do
- table.remove(dispatched, 1)
- end
+function _rewrite(self, ...)
+ local n = self.n
+ local req = { unpack(self.req) }
+ local dispatched = util.clone(context.dispatched)
- for i, r in ipairs(req) do
- table.insert(dispatched, i, r)
- end
+ for i=1,n do
+ table.remove(dispatched, 1)
+ end
- for _, r in ipairs({...}) do
- dispatched[#dispatched+1] = r
- end
+ for i, r in ipairs(req) do
+ table.insert(dispatched, i, r)
+ end
- dispatch(dispatched)
+ for _, r in ipairs({...}) do
+ dispatched[#dispatched+1] = r
end
+
+ dispatch(dispatched)
end
+function rewrite(n, ...)
+ return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
+end
local function _call(self, ...)
local func = getfenv()[self.name]
end
+local _view = function(self, ...)
+ require "luci.template".render("view", { view = self.view })
+end
+
+function view(name)
+ return {type = "view", view = name, target = _view}
+end
+
+
local function _cbi(self, ...)
local cbi = require "luci.cbi"
local tpl = require "luci.template"
for i, res in ipairs(maps) do
res:render({
firstmap = (i == 1),
- applymap = applymap,
redirect = redirect,
messages = messages,
pageaction = pageaction,
if not config.nofooter then
tpl.render("cbi/footer", {
- flow = config,
- pageaction = pageaction,
- redirect = redirect,
- state = state,
- autoapply = config.autoapply
+ flow = config,
+ pageaction = pageaction,
+ redirect = redirect,
+ state = state,
+ autoapply = config.autoapply,
+ trigger_apply = applymap
})
end
end
function form(model)
return {
- type = "cbi",
+ type = "form",
post = { ["cbi.submit"] = true },
model = model,
target = _form