summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-base/luasrc
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2019-12-04 19:19:45 +0100
committerJo-Philipp Wich <jo@mein.io>2019-12-16 18:07:17 +0100
commit184ea6230076bf4dd3abf6669bde17f87a051146 (patch)
treeeb4c0b7049e6375ee1ad73a67bb86541ff43939f /modules/luci-base/luasrc
parent1ec6e72106218e11871dd8ee563f7673c2d1f7dc (diff)
luci-base: dispatcher.lua: refactor dispatch logic
Refactor the dispatch logic to operate on the internal JSON representation of the menu tree. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules/luci-base/luasrc')
-rw-r--r--modules/luci-base/luasrc/dispatcher.lua598
1 files changed, 218 insertions, 380 deletions
diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua
index 05d3399ef..103215549 100644
--- a/modules/luci-base/luasrc/dispatcher.lua
+++ b/modules/luci-base/luasrc/dispatcher.lua
@@ -17,10 +17,6 @@ _M.fs = fs
-- Index table
local index = nil
--- Fastindex
-local fi
-
-
local function check_fs_depends(fs)
local fs = require "nixio.fs"
@@ -321,18 +317,13 @@ local function tree_to_json(node, json)
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
+ 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)
end
end
@@ -358,117 +349,6 @@ function build_url(...)
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
- (type(node.nodes) ~= "table" or not next(node.nodes)))
- )
- end
- return false
-end
-
-function node_childs(node)
- local rv = { }
- if node then
- local _, child
- for _, child in ipairs(_ordered_children(node)) do
- if node_visible(child.node) then
- rv[#rv+1] = child.name
- end
- end
- end
- return rv
-end
-
function error404(message)
http.status(404, "Not Found")
@@ -658,6 +538,77 @@ local function session_setup(user, pass, allowed_users)
return nil, nil
end
+local function check_authentication(method)
+ local auth_type, auth_param = method:match("^(%w+):(.+)$")
+ local sid, sdat
+
+ if auth_type == "cookie" then
+ sid = http.getcookie(auth_param)
+ elseif auth_type == "param" then
+ sid = http.formvalue(auth_param)
+ end
+
+ return session_retrieve(sid)
+end
+
+local function get_children(node)
+ local children = {}
+
+ if not node.wildcard and type(node.children) == "table" then
+ for name, child in pairs(node.children) do
+ children[#children+1] = {
+ name = name,
+ node = child,
+ order = child.order or 1000
+ }
+ 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)
+ end
+
+ return children
+end
+
+local function find_subnode(root, prefix, recurse, descended)
+ local children = get_children(root)
+
+ if #children > 0 and (not descended or recurse) then
+ local sub_path = { unpack(prefix) }
+
+ if recurse == false then
+ recurse = nil
+ end
+
+ for _, child in ipairs(children) do
+ sub_path[#prefix+1] = child.name
+
+ local res_path = find_subnode(child.node, sub_path, recurse, true)
+
+ if res_path then
+ return res_path
+ end
+ end
+ end
+
+ if descended then
+ if not recurse or
+ root.action.type == "cbi" or
+ root.action.type == "form" or
+ root.action.type == "view" or
+ root.action.type == "template" or
+ root.action.type == "arcombine"
+ then
+ return prefix
+ end
+ end
+end
+
function menu_json()
local tree = context.tree or createtree()
return tree_to_json(tree, {
@@ -749,85 +700,65 @@ end
function dispatch(request)
--context._disable_memtrace = require "luci.debug".trap_memtrace("l")
local ctx = context
- ctx.path = request
- local c = ctx.tree
- local stat
- if not c then
- c = createtree()
- end
+ local auth, cors, suid, sgid
+ local menu = menu_json()
+ local page = menu
- local track = {}
- local args = {}
- ctx.args = args
- ctx.requestargs = ctx.requestargs or args
- local n
- local preq = {}
- local freq = {}
+ local requested_path_full = {}
+ local requested_path_node = {}
+ local requested_path_args = {}
for i, s in ipairs(request) do
- preq[#preq+1] = s
- freq[#freq+1] = s
- c = c.nodes[s]
- n = i
- if not c then
+ if type(page.children) ~= "table" or not page.children[s] then
+ page = nil
break
end
- util.update(track, c)
-
- if c.leaf then
+ if not page.children[s].satisfied then
+ page = nil
break
end
- end
- if c and c.leaf then
- for j=n+1, #request do
- args[#args+1] = request[j]
- freq[#freq+1] = request[j]
- end
- end
+ page = page.children[s]
+ auth = page.auth or auth
+ cors = page.cors or cors
+ suid = page.setuser or suid
+ sgid = page.setgroup or sgid
- ctx.requestpath = ctx.requestpath or freq
- ctx.path = preq
+ requested_path_full[i] = s
+ requested_path_node[i] = s
- -- Init template engine
- if (c and c.index) or not track.notemplate then
- init_template_engine(ctx)
+ if page.wildcard then
+ for j = i + 1, #request do
+ requested_path_args[j - i] = request[j]
+ requested_path_full[j] = request[j]
+ end
+ break
+ end
end
- track.dependent = (track.dependent ~= false)
- assert(not track.dependent or not track.auto,
- "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
- "has no parent node so the access to this location has been denied.\n" ..
- "This is a software bug, please report this message at " ..
- "https://github.com/openwrt/luci/issues"
- )
+ local tpl = init_template_engine(ctx)
- if track.sysauth and not ctx.authsession then
- local authen = track.sysauth_authenticator
- local _, sid, sdat, default_user, allowed_users
+ ctx.args = requested_path_args
+ ctx.path = requested_path_node
+ ctx.dispatched = page
- if type(authen) == "string" and authen ~= "htmlauth" then
- error500("Unsupported authenticator %q configured" % authen)
- return
- end
+ ctx.requestpath = ctx.requestpath or requested_path_full
+ ctx.requestargs = ctx.requestargs or requested_path_args
+ ctx.requested = ctx.requested or page
- if type(track.sysauth) == "table" then
- default_user, allowed_users = nil, track.sysauth
- else
- default_user, allowed_users = track.sysauth, { track.sysauth }
- end
+ if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
+ local sid, sdat
+ for _, method in ipairs(auth.methods) do
+ sid, sdat = check_authentication(method)
- if type(authen) == "function" then
- _, sid = authen(sys.user.checkpasswd, allowed_users)
- else
- sid = http.getcookie("sysauth")
+ if sid and sdat then
+ break
+ end
end
- sid, sdat = session_retrieve(sid, allowed_users)
-
- if not (sid and sdat) and authen == "htmlauth" then
+ if not (sid and sdat) and auth.login then
local user = http.getenv("HTTP_AUTH_USER")
local pass = http.getenv("HTTP_AUTH_PASS")
@@ -836,27 +767,23 @@ function dispatch(request)
pass = http.formvalue("luci_password")
end
- sid, sdat = session_setup(user, pass, allowed_users)
+ sid, sdat = session_setup(user, pass, { "root" })
if not sid then
- local tmpl = require "luci.template"
-
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
+ return tpl.render("sysauth", { duser = "root", fuser = user })
end
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)))
+ return
end
if not sid or not sdat then
@@ -870,81 +797,117 @@ function dispatch(request)
ctx.authuser = sdat.username
end
- if track.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
+ local action = (page and type(page.action) == "table") and page.action or {}
+
+ if action.type == "arcombine" then
+ action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
+ end
+
+ if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
luci.http.status(200, "OK")
luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
return
end
- if c and require_post_security(c.target, args) then
- if not test_post_security(c) then
+ if require_post_security(action) then
+ if not test_post_security() then
return
end
end
- if track.setgroup then
- sys.process.setgroup(track.setgroup)
+ if sgid then
+ sys.process.setgroup(sgid)
end
- if track.setuser then
- sys.process.setuser(track.setuser)
+ if suid then
+ sys.process.setuser(suid)
end
- local target = nil
- if c then
- if type(c.target) == "function" then
- target = c.target
- elseif type(c.target) == "table" then
- target = c.target.target
+ if action.type == "view" then
+ tpl.render("view", { view = action.path })
+
+ elseif action.type == "call" then
+ local ok, mod = util.copcall(require, action.module)
+ if not ok then
+ error500(mod)
+ return
end
- end
- if c and (c.index or type(target) == "function") then
- ctx.dispatched = c
- ctx.requested = ctx.requested or ctx.dispatched
- end
+ local func = mod[action["function"]]
- if c and c.index then
- local tpl = require "luci.template"
+ assert(func ~= nil,
+ 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
- if util.copcall(tpl.render, "indexer", {}) then
- return true
+ assert(type(func) == "function",
+ 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
+ 'of type "' .. type(func) .. '".')
+
+ local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
+ for _, s in ipairs(requested_path_args) do
+ argv[#argv + 1] = s
end
- end
- if type(target) == "function" then
- util.copcall(function()
- local oldenv = getfenv(target)
- local module = require(c.module)
- local env = setmetatable({}, {__index=
+ local ok, err = util.copcall(func, unpack(argv))
+ if not ok then
+ error500(err)
+ end
- function(tbl, key)
- return rawget(tbl, key) or module[key] or oldenv[key]
- end})
+ elseif action.type == "firstchild" then
+ local sub_request = find_subnode(page, requested_path_full, action.recurse)
+ if sub_request then
+ dispatch(sub_request)
+ else
+ tpl.render("empty_node_placeholder", getfenv(1))
+ end
- setfenv(target, env)
- end)
+ elseif action.type == "alias" then
+ local sub_request = {}
+ for name in action.path:gmatch("[^/]+") do
+ sub_request[#sub_request + 1] = name
+ end
- local ok, err
- if type(c.target) == "table" then
- ok, err = util.copcall(target, c.target, unpack(args))
- else
- ok, err = util.copcall(target, unpack(args))
+ for _, s in ipairs(requested_path_args) do
+ sub_request[#sub_request + 1] = s
end
- 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)"))
+
+ dispatch(sub_request)
+
+ elseif action.type == "rewrite" then
+ local sub_request = { unpack(request) }
+ for i = 1, action.remove do
+ table.remove(sub_request, 1)
end
+
+ local n = 1
+ for s in action.path:gmatch("[^/]+") do
+ table.insert(sub_request, n, s)
+ n = n + 1
+ end
+
+ for _, s in ipairs(requested_path_args) do
+ sub_request[#sub_request + 1] = s
+ end
+
+ dispatch(sub_request)
+
+ elseif action.type == "template" then
+ tpl.render(action.path, getfenv(1))
+
+ elseif action.type == "cbi" then
+ _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
+
+ elseif action.type == "form" then
+ _form({ model = action.path }, unpack(requested_path_args))
+
else
- local root = node()
- if not root or not root.target then
+ local root = find_subnode(menu, {}, true)
+ if not root then
error404("No root node was registered, this usually happens if no module was installed.\n" ..
"Install luci-mod-admin-full and retry. " ..
"If the module is already installed, try removing the /tmp/luci-indexcache file.")
else
- error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
+ error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
"If this url belongs to an extension, make sure it is properly installed.\n" ..
"If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
end
@@ -1106,16 +1069,6 @@ function _create_node(path)
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
@@ -1125,124 +1078,24 @@ end
-- Subdispatchers --
-function _find_eligible_node(root, prefix, deep, types, descend)
- local children = _ordered_children(root)
-
- if not root.leaf and deep ~= nil then
- local sub_path = { unpack(prefix) }
-
- 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 }
-end
-
-function _firstnode()
- return _find_node(true, { "cbi", "form", "template", "arcombine" })
+ return { type = "firstchild" }
end
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)
+ return { type = "firstnode" }
end
function alias(...)
- return { type = "alias", target = _alias, req = { ... } }
-end
-
-function _rewrite(self, ...)
- local n = self.n
- local req = { unpack(self.req) }
- local dispatched = util.clone(context.dispatched)
-
- for i=1,n do
- table.remove(dispatched, 1)
- end
-
- for i, r in ipairs(req) do
- table.insert(dispatched, i, r)
- end
-
- for _, r in ipairs({...}) do
- dispatched[#dispatched+1] = r
- end
-
- dispatch(dispatched)
+ return { type = "alias", req = { ... } }
end
function rewrite(n, ...)
- return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
-end
-
-local function _call(self, ...)
- local func = getfenv()[self.name]
- assert(func ~= nil,
- 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
-
- assert(type(func) == "function",
- 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
- 'of type "' .. type(func) .. '".')
-
- if #self.argv > 0 then
- return func(unpack(self.argv), ...)
- else
- return func(...)
- end
+ return { type = "rewrite", n = n, req = { ... } }
end
function call(name, ...)
- return {type = "call", argv = {...}, name = name, target = _call}
+ return { type = "call", argv = {...}, name = name }
end
function post_on(params, name, ...)
@@ -1250,8 +1103,7 @@ function post_on(params, name, ...)
type = "call",
post = params,
argv = { ... },
- name = name,
- target = _call
+ name = name
}
end
@@ -1260,25 +1112,16 @@ function post(...)
end
-local _template = function(self, ...)
- require "luci.template".render(self.view)
-end
-
function template(name)
- return {type = "template", view = name, target = _template}
-end
-
-
-local _view = function(self, ...)
- require "luci.template".render("view", { view = self.view })
+ return { type = "template", view = name }
end
function view(name)
- return {type = "view", view = name, target = _view}
+ return { type = "view", view = name }
end
-local function _cbi(self, ...)
+function _cbi(self, ...)
local cbi = require "luci.cbi"
local tpl = require "luci.template"
local http = require "luci.http"
@@ -1392,25 +1235,21 @@ function cbi(model, config)
type = "cbi",
post = { ["cbi.submit"] = true },
config = config,
- model = model,
- target = _cbi
+ model = model
}
end
-local function _arcombine(self, ...)
- local argv = {...}
- local target = #argv > 0 and self.targets[2] or self.targets[1]
- setfenv(target.target, self.env)
- target:target(unpack(argv))
-end
-
function arcombine(trg1, trg2)
- return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
+ return {
+ type = "arcombine",
+ env = getfenv(),
+ targets = {trg1, trg2}
+ }
end
-local function _form(self, ...)
+function _form(self, ...)
local cbi = require "luci.cbi"
local tpl = require "luci.template"
local http = require "luci.http"
@@ -1438,8 +1277,7 @@ function form(model)
return {
type = "form",
post = { ["cbi.submit"] = true },
- model = model,
- target = _form
+ model = model
}
end