From 0f6af6e00da8c5edee69333bb83db6c711183a80 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 1 Dec 2019 20:06:43 +0100 Subject: [PATCH] luci-base: dispatcher.lua: introduce dispatch tree JSON conversion Introduce a new method menu_json() which converts the current dispatch tree into JSON structure. Signed-off-by: Jo-Philipp Wich (cherry picked from commit 852d24061d236048a8c2c886787eede2586b10b4) --- modules/luci-base/luasrc/dispatcher.lua | 377 ++++++++++++++++++++++-- 1 file changed, 355 insertions(+), 22 deletions(-) diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua index 17228ac32f..d7b722a07c 100644 --- a/modules/luci-base/luasrc/dispatcher.lua +++ b/modules/luci-base/luasrc/dispatcher.lua @@ -21,6 +21,324 @@ local index = nil 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 "" } @@ -306,6 +624,16 @@ local function session_setup(user, pass, allowed_users) 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 @@ -848,38 +1176,43 @@ function firstnode() return { type = "firstnode", target = _firstnode } end -function alias(...) - local req = {...} - return function(...) - for _, r in ipairs({...}) do - req[#req+1] = r - end +function _alias(self, ...) + local req = { unpack(self.req) } - dispatch(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] @@ -1092,7 +1425,7 @@ end function form(model) return { - type = "cbi", + type = "form", post = { ["cbi.submit"] = true }, model = model, target = _form -- 2.30.2