diff options
Diffstat (limited to 'modules/luci-base/luasrc/dispatcher.lua')
-rw-r--r-- | modules/luci-base/luasrc/dispatcher.lua | 1564 |
1 files changed, 0 insertions, 1564 deletions
diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua deleted file mode 100644 index a3726fb1c1..0000000000 --- a/modules/luci-base/luasrc/dispatcher.lua +++ /dev/null @@ -1,1564 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local sys = require "luci.sys" -local util = require "luci.util" -local xml = require "luci.xml" -local http = require "luci.http" -local nixio = require "nixio", require "nixio.util" - -module("luci.dispatcher", package.seeall) -context = util.threadlocal() -uci = require "luci.model.uci" -i18n = require "luci.i18n" -_M.fs = fs - --- Index table -local index = nil - -local function check_fs_depends(spec) - local fs = require "nixio.fs" - - for path, kind in pairs(spec) 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 - elseif kind == "absent" then - if fs.stat(path, "type") 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_acl_depends(require_groups, groups) - if type(require_groups) == "table" and #require_groups > 0 then - local writable = false - - for _, group in ipairs(require_groups) do - local read = false - local write = false - if type(groups) == "table" and type(groups[group]) == "table" then - for _, perm in ipairs(groups[group]) do - if perm == "read" then - read = true - elseif perm == "write" then - write = true - end - end - end - if not read and not write then - return nil - elseif write then - writable = true - end - end - - return writable - end - - return true -end - -local function check_depends(spec) - if type(spec.depends) ~= "table" then - return true - end - - if type(spec.depends.fs) == "table" 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, - ["config"] = target.config - } - 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 = xml.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 type(subnode.acl_depends) == "table" then - for _, acl in ipairs(subnode.acl_depends) do - spec.depends = spec.depends or {} - spec.depends.acl = spec.depends.acl or {} - spec.depends.acl[#spec.depends.acl + 1] = acl - 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_https", "cookie:sysauth_http" } - } - elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then - spec.auth = { - login = false, - methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subnode.module == "luci.controller.admin.uci" then - spec.auth = { - login = false, - methods = { "param:sid" } - } - end - elseif subnode.sysauth == false then - spec.auth = {} - 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 - - return json -end - -function build_url(...) - local path = {...} - local url = { http.getenv("SCRIPT_NAME") or "" } - - local p - for _, p in ipairs(path) do - if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then - url[#url+1] = "/" - url[#url+1] = p - end - end - - if #path == 0 then - url[#url+1] = "/" - end - - return table.concat(url, "") -end - - -function error404(message) - http.status(404, "Not Found") - message = message or "Not Found" - - local function render() - local template = require "luci.template" - template.render("error404", {message=message}) - end - - if not util.copcall(render) then - http.prepare_content("text/plain") - http.write(message) - end - - return false -end - -function error500(message) - util.perror(message) - if not context.template_header_sent then - http.status(500, "Internal Server Error") - http.prepare_content("text/plain") - http.write(message) - else - require("luci.template") - if not util.copcall(luci.template.render, "error500", {message=message}) then - http.prepare_content("text/plain") - http.write(message) - end - end - return false -end - -local function determine_request_language() - local conf = require "luci.config" - assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'") - - local lang = conf.main.lang or "auto" - if lang == "auto" then - local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" - for aclang in aclang:gmatch("[%w_-]+") do - local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$") - if country and culture then - local cc = "%s_%s" %{ country, culture:lower() } - if conf.languages[cc] then - lang = cc - break - elseif conf.languages[country] then - lang = country - break - end - elseif conf.languages[aclang] then - lang = aclang - break - end - end - end - - if lang == "auto" then - lang = i18n.default - end - - i18n.setlanguage(lang) -end - -function httpdispatch(request, prefix) - http.context.request = request - - local r = {} - context.request = r - - local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) - - if prefix then - for _, node in ipairs(prefix) do - r[#r+1] = node - end - end - - local node - for node in pathinfo:gmatch("[^/%z]+") do - r[#r+1] = node - end - - determine_request_language() - - local stat, err = util.coxpcall(function() - dispatch(context.request) - end, error500) - - http.close() - - --context._disable_memtrace() -end - -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 - - for param_name, required_val in pairs(target.post) do - request_val = http.formvalue(param_name) - - if (type(required_val) == "string" and - request_val ~= required_val) or - (required_val == true and request_val == nil) - then - return false - end - end - - return true - end - - return (target.post == true) - end - - return false -end - -function test_post_security() - if http.getenv("REQUEST_METHOD") ~= "POST" then - http.status(405, "Method Not Allowed") - http.header("Allow", "POST") - return false - end - - if http.formvalue("token") ~= context.authtoken then - http.status(403, "Forbidden") - luci.template.render("csrftoken") - return false - end - - return true -end - -local function session_retrieve(sid, allowed_users) - local sdat = util.ubus("session", "get", { ubus_rpc_session = sid }) - local sacl = util.ubus("session", "access", { ubus_rpc_session = sid }) - - if type(sdat) == "table" and - type(sdat.values) == "table" and - type(sdat.values.token) == "string" and - (not allowed_users or - util.contains(allowed_users, sdat.values.username)) - then - uci:set_session_id(sid) - return sid, sdat.values, type(sacl) == "table" and sacl or {} - end - - return nil, nil, nil -end - -local function session_setup(user, pass) - local login = util.ubus("session", "login", { - username = user, - password = pass, - timeout = tonumber(luci.config.sauth.sessiontime) - }) - - local rp = context.requestpath - and table.concat(context.requestpath, "/") or "" - - if type(login) == "table" and - type(login.ubus_rpc_session) == "string" - then - util.ubus("session", "set", { - ubus_rpc_session = login.ubus_rpc_session, - values = { token = sys.uniqueid(16) } - }) - nixio.syslog("info", tostring("luci: accepted login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) - - return session_retrieve(login.ubus_rpc_session) - end - nixio.syslog("info", tostring("luci: failed login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) -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) - elseif auth_type == "query" then - sid = http.formvalue(auth_param, true) - end - - return session_retrieve(sid) -end - -local function merge_trees(node_a, node_b) - for k, v in pairs(node_b) do - if k == "children" then - node_a.children = node_a.children or {} - - for name, spec in pairs(v) do - node_a.children[name] = merge_trees(node_a.children[name] or {}, spec) - end - else - node_a[k] = v - end - end - - if type(node_a.action) == "table" and - node_a.action.type == "firstchild" and - node_a.children == nil - then - node_a.satisfied = false - end - - return node_a -end - -local function apply_tree_acls(node, acl) - if type(node.children) == "table" then - for _, child in pairs(node.children) do - apply_tree_acls(child, acl) - end - end - - local perm - if type(node.depends) == "table" then - perm = check_acl_depends(node.depends.acl, acl["access-group"]) - else - perm = true - end - - if perm == nil then - node.satisfied = false - elseif perm == false then - node.readonly = true - end -end - -function menu_json(acl) - local tree = context.tree or createtree() - local lua_tree = tree_to_json(tree, { - action = { - ["type"] = "firstchild", - ["recurse"] = true - } - }) - - local json_tree = createtree_json() - local menu_tree = merge_trees(lua_tree, json_tree) - - if acl then - apply_tree_acls(menu_tree, acl) - end - - return menu_tree -end - -local function init_template_engine(ctx) - local tpl = require "luci.template" - local media = luci.config.main.mediaurlbase - - if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then - media = nil - for name, theme in pairs(luci.config.themes) do - if name:sub(1,1) ~= "." and pcall(tpl.Template, - "themes/%s/header" % fs.basename(theme)) then - media = theme - end - end - assert(media, "No valid theme found") - end - - local function _ifattr(cond, key, val, noescape) - if cond then - local env = getfenv(3) - local scope = (type(env.self) == "table") and env.self - if type(val) == "table" then - if not next(val) then - return '' - else - val = util.serialize_json(val) - end - end - - 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 = xml.pcdata(val) - end - - return string.format(' %s="%s"', tostring(key), val) - else - return '' - end - end - - tpl.context.viewns = setmetatable({ - write = http.write; - include = function(name) tpl.Template(name):render(getfenv(2)) end; - translate = i18n.translate; - translatef = i18n.translatef; - export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; - striptags = xml.striptags; - pcdata = xml.pcdata; - media = media; - theme = fs.basename(media); - resource = luci.config.main.resourcebase; - ifattr = function(...) return _ifattr(...) end; - attr = function(...) return _ifattr(true, ...) end; - url = build_url; - }, {__index=function(tbl, key) - if key == "controller" then - return build_url() - elseif key == "REQUEST_URI" then - return build_url(unpack(ctx.requestpath)) - elseif key == "FULL_REQUEST_URI" then - 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] = "?" - url[#url+1] = query - end - return table.concat(url, "") - elseif key == "token" then - return ctx.authtoken - else - return rawget(tbl, key) or _G[key] - end - end}) - - return tpl -end - -function is_authenticated(auth) - if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then - local sid, sdat, sacl - for _, method in ipairs(auth.methods) do - sid, sdat, sacl = check_authentication(method) - - if sid and sdat and sacl then - return sid, sdat, sacl - end - end - end -end - -local function ctx_append(ctx, name, node) - ctx.path = ctx.path or {} - ctx.path[#ctx.path + 1] = name - - ctx.acls = ctx.acls or {} - - local acls = (type(node.depends) == "table" and type(node.depends.acl) == "table") and node.depends.acl or {} - for _, acl in ipairs(acls) do - ctx.acls[_] = acl - end - - ctx.auth = node.auth or ctx.auth - ctx.cors = node.cors or ctx.cors - ctx.suid = node.setuser or ctx.suid - ctx.sgid = node.setgroup or ctx.sgid - - return ctx -end - -local function node_weight(node) - local weight = node.order or 9999 - - if weight > 9999 then - weight = 9999 - end - - if type(node.auth) == "table" and node.auth.login then - weight = weight + 10000 - end - - return weight -end - -local function resolve_firstchild(node, sacl, login_allowed, ctx) - local candidate = nil - local candidate_ctx = nil - - for name, child in pairs(node.children) do - if child.satisfied then - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - local cacl = (type(child.depends) == "table") and child.depends.acl or nil - local login = login_allowed or (type(child.auth) == "table" and child.auth.login) - if login or check_acl_depends(cacl, sacl and sacl["access-group"]) ~= nil then - if child.title and type(child.action) == "table" then - local child_ctx = ctx_append(util.clone(ctx, true), name, child) - if child.action.type == "firstchild" then - if not candidate or node_weight(candidate) > node_weight(child) then - local have_grandchild = resolve_firstchild(child, sacl, login, child_ctx) - if have_grandchild then - candidate = child - candidate_ctx = child_ctx - end - end - elseif not child.firstchild_ineligible then - if not candidate or node_weight(candidate) > node_weight(child) then - candidate = child - candidate_ctx = child_ctx - end - end - end - end - end - end - - if candidate then - for k, v in pairs(candidate_ctx) do - ctx[k] = v - end - - return true - end - - return false -end - -local function resolve_page(tree, request_path) - local node = tree - local sacl = nil - local login = false - local ctx = {} - - for i, s in ipairs(request_path) do - node = node.children and node.children[s] - - if not node or not node.satisfied then - break - end - - ctx_append(ctx, s, node) - - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - if not login and type(node.auth) == "table" and node.auth.login then - login = true - end - - if node.wildcard then - ctx.request_args = {} - ctx.request_path = util.clone(ctx.path, true) - - for j = i + 1, #request_path do - ctx.request_path[j] = request_path[j] - ctx.request_args[j - i] = request_path[j] - end - - break - end - end - - if node and type(node.action) == "table" and node.action.type == "firstchild" then - resolve_firstchild(node, sacl, login, ctx) - end - - ctx.acls = ctx.acls or {} - ctx.path = ctx.path or {} - ctx.request_args = ctx.request_args or {} - ctx.request_path = ctx.request_path or util.clone(request_path, true) - - node = tree - - for _, s in ipairs(ctx.path or {}) do - node = node.children[s] - assert(node, "Internal node resolve error") - end - - return node, ctx -end - -function dispatch(request) - --context._disable_memtrace = require "luci.debug".trap_memtrace("l") - local ctx = context - - local auth, cors, suid, sgid - local menu = menu_json() - local page, lookup_ctx = resolve_page(menu, request) - local action = (page and type(page.action) == "table") and page.action or {} - - local tpl = init_template_engine(ctx) - - ctx.args = lookup_ctx.request_args - ctx.path = lookup_ctx.path - ctx.dispatched = page - - ctx.requestpath = ctx.requestpath or lookup_ctx.request_path - ctx.requestargs = ctx.requestargs or lookup_ctx.request_args - ctx.requested = ctx.requested or page - - if type(lookup_ctx.auth) == "table" and next(lookup_ctx.auth) then - local sid, sdat, sacl = is_authenticated(lookup_ctx.auth) - - if not (sid and sdat and sacl) and lookup_ctx.auth.login then - local user = http.getenv("HTTP_AUTH_USER") - local pass = http.getenv("HTTP_AUTH_PASS") - - if user == nil and pass == nil then - user = http.formvalue("luci_username") - pass = http.formvalue("luci_password") - end - - if user and pass then - sid, sdat, sacl = session_setup(user, pass) - end - - if not sid then - context.path = {} - - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - - local scope = { duser = "root", fuser = user } - local ok, res = util.copcall(tpl.render_string, [[<% include("themes/" .. theme .. "/sysauth") %>]], scope) - if ok then - return res - end - return tpl.render("sysauth", scope) - end - - http.header("Set-Cookie", 'sysauth_%s=%s; path=%s; SameSite=Strict; HttpOnly%s' %{ - http.getenv("HTTPS") == "on" and "https" or "http", - 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 or not sacl then - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - return - end - - ctx.authsession = sid - ctx.authtoken = sdat.token - ctx.authuser = sdat.username - ctx.authacl = sacl - end - - if #lookup_ctx.acls > 0 then - local perm = check_acl_depends(lookup_ctx.acls, ctx.authacl and ctx.authacl["access-group"]) - if perm == nil then - http.status(403, "Forbidden") - return - end - - if page then - page.readonly = not perm - end - end - - if action.type == "arcombine" then - action = (#lookup_ctx.request_args > 0) and action.targets[2] or action.targets[1] - end - - if lookup_ctx.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 require_post_security(action) then - if not test_post_security() then - return - end - end - - if lookup_ctx.sgid then - sys.process.setgroup(lookup_ctx.sgid) - end - - if lookup_ctx.suid then - sys.process.setuser(lookup_ctx.suid) - end - - 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 - - local func = mod[action["function"]] - - assert(func ~= nil, - 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?') - - 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(lookup_ctx.request_args) do - argv[#argv + 1] = s - end - - local ok, err = util.copcall(func, unpack(argv)) - if not ok then - error500(err) - end - - --elseif action.type == "firstchild" then - -- tpl.render("empty_node_placeholder", getfenv(1)) - - elseif action.type == "alias" then - local sub_request = {} - for name in action.path:gmatch("[^/]+") do - sub_request[#sub_request + 1] = name - end - - for _, s in ipairs(lookup_ctx.request_args) do - sub_request[#sub_request + 1] = s - end - - 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(lookup_ctx.request_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(lookup_ctx.request_args)) - - elseif action.type == "form" then - _form({ model = action.path }, unpack(lookup_ctx.request_args)) - - else - if not menu.children 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(lookup_ctx.request_path, "/") .. "'.\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 - end -end - -local function hash_filelist(files) - local fprint = {} - local n = 0 - - for i, file in ipairs(files) do - local st = fs.stat(file) - if st then - fprint[n + 1] = '%x' % st.ino - fprint[n + 2] = '%x' % st.mtime - fprint[n + 3] = '%x' % st.size - n = n + 3 - end - end - - return nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".") -end - -local function read_cachefile(file, reader) - local euid = sys.process.info("uid") - local fuid = fs.stat(file, "uid") - local mode = fs.stat(file, "modestr") - - if euid ~= fuid or mode ~= "rw-------" then - return nil - end - - return reader(file) -end - -function createindex() - local controllers = { } - local base = "%s/controller/" % util.libpath() - local _, path - - for path in (fs.glob("%s*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - for path in (fs.glob("%s*/*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - local cachefile - - if indexcache then - cachefile = "%s.%s.lua" %{ indexcache, hash_filelist(controllers) } - - local res = read_cachefile(cachefile, function(path) return loadfile(path)() end) - if res then - index = res - return res - end - - for file in (fs.glob("%s.*.lua" % indexcache) or function() end) do - fs.unlink(file) - end - end - - index = {} - - for _, path in ipairs(controllers) do - local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") - local mod = require(modname) - assert(mod ~= true, - "Invalid controller file found\n" .. - "The file '" .. path .. "' contains an invalid module line.\n" .. - "Please verify whether the module name is set to '" .. modname .. - "' - It must correspond to the file path!") - - local idx = mod.index - if type(idx) == "function" then - index[modname] = idx - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(util.get_bytecode(index)) - f:close() - end -end - -function createtree_json() - local json = require "luci.jsonc" - local tree = {} - - local schema = { - action = "table", - auth = "table", - cors = "boolean", - depends = "table", - order = "number", - setgroup = "string", - setuser = "string", - title = "string", - wildcard = "boolean", - firstchild_ineligible = "boolean" - } - - local files = {} - local cachefile - - for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do - files[#files+1] = file - end - - if indexcache then - cachefile = "%s.%s.json" %{ indexcache, hash_filelist(files) } - - local res = read_cachefile(cachefile, function(path) return json.parse(fs.readfile(path) or "") end) - if res then - return res - end - - for file in (fs.glob("%s.*.json" % indexcache) or function() end) do - fs.unlink(file) - end - end - - for _, file in ipairs(files) do - local data = json.parse(fs.readfile(file) or "") - if type(data) == "table" then - for path, spec in pairs(data) do - if type(spec) == "table" then - local node = tree - - for s in path:gmatch("[^/]+") do - if s == "*" then - node.wildcard = true - break - end - - node.children = node.children or {} - node.children[s] = node.children[s] or {} - node = node.children[s] - end - - if node ~= tree then - for k, t in pairs(schema) do - if type(spec[k]) == t then - node[k] = spec[k] - end - end - - node.satisfied = check_depends(spec) - end - end - end - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(json.stringify(tree)) - f:close() - end - - return tree -end - --- Build the index before if it does not exist yet. -function createtree() - if not index then - createindex() - end - - local ctx = context - local tree = {nodes={}, inreq=true} - - ctx.treecache = setmetatable({}, {__mode="v"}) - ctx.tree = tree - - local scope = setmetatable({}, {__index = luci.dispatcher}) - - for k, v in pairs(index) do - scope._NAME = k - setfenv(v, scope) - v() - end - - return tree -end - -function assign(path, clone, title, order) - local obj = node(unpack(path)) - obj.nodes = nil - obj.module = nil - - obj.title = title - obj.order = order - - setmetatable(obj, {__index = _create_node(clone)}) - - return obj -end - -function entry(path, target, title, order) - local c = node(unpack(path)) - - c.target = target - c.title = title - c.order = order - c.module = getfenv(2)._NAME - - return c -end - --- enabling the node. -function get(...) - return _create_node({...}) -end - -function node(...) - local c = _create_node({...}) - - c.module = getfenv(2)._NAME - c.auto = nil - - return c -end - -function lookup(...) - local i, path = nil, {} - for i = 1, select('#', ...) do - local name, arg = nil, tostring(select(i, ...)) - for name in arg:gmatch("[^/]+") do - path[#path+1] = name - end - end - - for i = #path, 1, -1 do - local node = context.treecache[table.concat(path, ".", 1, i)] - if node and (i == #path or node.leaf) then - return node, build_url(unpack(path)) - end - end -end - -function _create_node(path) - if #path == 0 then - return context.tree - end - - local name = table.concat(path, ".") - local c = context.treecache[name] - - if not c then - local last = table.remove(path) - local parent = _create_node(path) - - c = {nodes={}, auto=true, inreq=true} - - parent.nodes[last] = c - context.treecache[name] = c - end - - return c -end - --- Subdispatchers -- - -function firstchild() - return { type = "firstchild" } -end - -function firstnode() - return { type = "firstnode" } -end - -function alias(...) - return { type = "alias", req = { ... } } -end - -function rewrite(n, ...) - return { type = "rewrite", n = n, req = { ... } } -end - -function call(name, ...) - return { type = "call", argv = {...}, name = name } -end - -function post_on(params, name, ...) - return { - type = "call", - post = params, - argv = { ... }, - name = name - } -end - -function post(...) - return post_on(true, ...) -end - - -function template(name) - return { type = "template", view = name } -end - -function view(name) - return { type = "view", view = name } -end - - -function _cbi(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - local util = require "luci.util" - - local config = self.config or {} - local maps = cbi.load(self.model, ...) - - local state = nil - - local function has_uci_access(config, level) - local rv = util.ubus("session", "access", { - ubus_rpc_session = context.authsession, - scope = "uci", object = config, - ["function"] = level - }) - - return (type(rv) == "table" and rv.access == true) or false - end - - local i, res - for i, res in ipairs(maps) do - if util.instanceof(res, cbi.SimpleForm) then - io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" - % self.model) - - io.stderr:write("please change %s to use the form() action instead.\n" - % table.concat(context.request, "/")) - end - - res.flow = config - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - local function _resolve_path(path) - return type(path) == "table" and build_url(unpack(path)) or path - end - - if config.on_valid_to and state and state > 0 and state < 2 then - http.redirect(_resolve_path(config.on_valid_to)) - return - end - - if config.on_changed_to and state and state > 1 then - http.redirect(_resolve_path(config.on_changed_to)) - return - end - - if config.on_success_to and state and state > 0 then - http.redirect(_resolve_path(config.on_success_to)) - return - end - - if config.state_handler then - if not config.state_handler(state, maps) then - return - end - end - - http.header("X-CBI-State", state or 0) - - if not config.noheader then - tpl.render("cbi/header", {state = state}) - end - - local redirect - local messages - local applymap = false - local pageaction = true - local parsechain = { } - local writable = false - - for i, res in ipairs(maps) do - if res.apply_needed and res.parsechain then - local c - for _, c in ipairs(res.parsechain) do - parsechain[#parsechain+1] = c - end - applymap = true - end - - if res.redirect then - redirect = redirect or res.redirect - end - - if res.pageaction == false then - pageaction = false - end - - if res.message then - messages = messages or { } - messages[#messages+1] = res.message - end - end - - for i, res in ipairs(maps) do - local is_readable_map = has_uci_access(res.config, "read") - local is_writable_map = has_uci_access(res.config, "write") - - writable = writable or is_writable_map - - res:render({ - firstmap = (i == 1), - redirect = redirect, - messages = messages, - pageaction = pageaction, - parsechain = parsechain, - readable = is_readable_map, - writable = is_writable_map - }) - end - - if not config.nofooter then - tpl.render("cbi/footer", { - flow = config, - pageaction = pageaction, - redirect = redirect, - state = state, - autoapply = config.autoapply, - trigger_apply = applymap, - writable = writable - }) - end -end - -function cbi(model, config) - return { - type = "cbi", - post = { ["cbi.submit"] = true }, - config = config, - model = model - } -end - - -function arcombine(trg1, trg2) - return { - type = "arcombine", - env = getfenv(), - targets = {trg1, trg2} - } -end - - -function _form(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - - local maps = luci.cbi.load(self.model, ...) - local state = nil - - local i, res - for i, res in ipairs(maps) do - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - http.header("X-CBI-State", state or 0) - tpl.render("header") - for i, res in ipairs(maps) do - res:render() - end - tpl.render("footer") -end - -function form(model) - return { - type = "form", - post = { ["cbi.submit"] = true }, - model = model - } -end - -translate = i18n.translate - --- This function does not actually translate the given argument but --- is used by build/i18n-scan.pl to find translatable entries. -function _(text) - return text -end |