mirror of
				https://github.com/Ysurac/openmptcprouter-feeds.git
				synced 2025-03-09 15:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1003 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1003 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- 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 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
 | |
| 
 | |
| -- Fastindex
 | |
| local fi
 | |
| 
 | |
| 
 | |
| 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 node_visible(node)
 | |
|    if node then
 | |
| 	  return not (
 | |
| 		 (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 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
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	return rv
 | |
| 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")
 | |
| 	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
 | |
| 
 | |
| 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
 | |
| 
 | |
| 	local stat, err = util.coxpcall(function()
 | |
| 		dispatch(context.request)
 | |
| 	end, error500)
 | |
| 
 | |
| 	http.close()
 | |
| 
 | |
| 	--context._disable_memtrace()
 | |
| end
 | |
| 
 | |
| local function require_post_security(target)
 | |
| 	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 })
 | |
| 
 | |
| 	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
 | |
| 	end
 | |
| 
 | |
| 	return nil, nil
 | |
| end
 | |
| 
 | |
| local function session_setup(user, pass, allowed_users)
 | |
| 	if util.contains(allowed_users, user) then
 | |
| 		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) }
 | |
| 			})
 | |
| 
 | |
| 			io.stderr:write("luci: accepted login on /%s for %s from %s\n"
 | |
| 				%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
 | |
| 
 | |
| 			return session_retrieve(login.ubus_rpc_session)
 | |
| 		end
 | |
| 
 | |
| 		io.stderr:write("luci: failed login on /%s for %s from %s\n"
 | |
| 			%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
 | |
| 	end
 | |
| 
 | |
| 	return nil, nil
 | |
| end
 | |
| 
 | |
| function dispatch(request)
 | |
| 	--context._disable_memtrace = require "luci.debug".trap_memtrace("l")
 | |
| 	local ctx = context
 | |
| 	ctx.path = request
 | |
| 
 | |
| 	local conf = require "luci.config"
 | |
| 	assert(conf.main,
 | |
| 		"/etc/config/luci seems to be corrupt, unable to find section 'main'")
 | |
| 
 | |
| 	local i18n = require "luci.i18n"
 | |
| 	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)
 | |
| 
 | |
| 	local c = ctx.tree
 | |
| 	local stat
 | |
| 	if not c then
 | |
| 		c = createtree()
 | |
| 	end
 | |
| 
 | |
| 	local track = {}
 | |
| 	local args = {}
 | |
| 	ctx.args = args
 | |
| 	ctx.requestargs = ctx.requestargs or args
 | |
| 	local n
 | |
| 	local preq = {}
 | |
| 	local freq = {}
 | |
| 
 | |
| 	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
 | |
| 			break
 | |
| 		end
 | |
| 
 | |
| 		util.update(track, c)
 | |
| 
 | |
| 		if c.leaf then
 | |
| 			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
 | |
| 
 | |
| 	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")
 | |
| 		local media = track.mediaurlbase or 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)
 | |
| 			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
 | |
| 				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 "" ))
 | |
| 				)
 | |
| 			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   = util.striptags;
 | |
| 		   pcdata      = util.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})
 | |
| 	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"
 | |
| 	)
 | |
| 
 | |
| 	if track.sysauth and not ctx.authsession then
 | |
| 		local authen = track.sysauth_authenticator
 | |
| 		local _, sid, sdat, default_user, allowed_users
 | |
| 
 | |
| 		if type(authen) == "string" and authen ~= "htmlauth" then
 | |
| 			error500("Unsupported authenticator %q configured" % authen)
 | |
| 			return
 | |
| 		end
 | |
| 
 | |
| 		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(authen) == "function" then
 | |
| 			_, sid = authen(sys.user.checkpasswd, allowed_users)
 | |
| 		else
 | |
| 			sid = http.getcookie("sysauth")
 | |
| 		end
 | |
| 
 | |
| 		sid, sdat = session_retrieve(sid, allowed_users)
 | |
| 
 | |
| 		if not (sid and sdat) and authen == "htmlauth" 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
 | |
| 
 | |
| 			sid, sdat = session_setup(user, pass, allowed_users)
 | |
| 
 | |
| 			if not sid then
 | |
| 				local tmpl = require "luci.template"
 | |
| 
 | |
| 				context.path = {}
 | |
| 
 | |
| 				http.status(403, "Forbidden")
 | |
| 				tmpl.render(track.sysauth_template or "sysauth", {
 | |
| 					duser = default_user,
 | |
| 					fuser = user
 | |
| 				})
 | |
| 
 | |
| 				return
 | |
| 			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)))
 | |
| 		end
 | |
| 
 | |
| 		if not sid or not sdat then
 | |
| 			http.status(403, "Forbidden")
 | |
| 			return
 | |
| 		end
 | |
| 
 | |
| 		ctx.authsession = sid
 | |
| 		ctx.authtoken = sdat.token
 | |
| 		ctx.authuser = sdat.username
 | |
| 	end
 | |
| 
 | |
| 	if track.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) then
 | |
| 		if not test_post_security(c) then
 | |
| 			return
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if track.setgroup then
 | |
| 		sys.process.setgroup(track.setgroup)
 | |
| 	end
 | |
| 
 | |
| 	if track.setuser then
 | |
| 		sys.process.setuser(track.setuser)
 | |
| 	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
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if c and (c.index or type(target) == "function") then
 | |
| 		ctx.dispatched = c
 | |
| 		ctx.requested = ctx.requested or ctx.dispatched
 | |
| 	end
 | |
| 
 | |
| 	if c and c.index then
 | |
| 		local tpl = require "luci.template"
 | |
| 
 | |
| 		if util.copcall(tpl.render, "indexer", {}) then
 | |
| 			return true
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if type(target) == "function" then
 | |
| 		util.copcall(function()
 | |
| 			local oldenv = getfenv(target)
 | |
| 			local module = require(c.module)
 | |
| 			local env = setmetatable({}, {__index=
 | |
| 
 | |
| 			function(tbl, key)
 | |
| 				return rawget(tbl, key) or module[key] or oldenv[key]
 | |
| 			end})
 | |
| 
 | |
| 			setfenv(target, env)
 | |
| 		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))
 | |
| 		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)"))
 | |
| 		end
 | |
| 	else
 | |
| 		local root = node()
 | |
| 		if not root or not root.target 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" ..
 | |
| 			         "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
 | |
| 
 | |
| 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
 | |
| 
 | |
| 	if indexcache then
 | |
| 		local cachedate = fs.stat(indexcache, "mtime")
 | |
| 		if cachedate then
 | |
| 			local realdate = 0
 | |
| 			for _, obj in ipairs(controllers) do
 | |
| 				local omtime = fs.stat(obj, "mtime")
 | |
| 				realdate = (omtime and omtime > realdate) and omtime or realdate
 | |
| 			end
 | |
| 
 | |
| 			if cachedate > realdate and sys.process.info("uid") == 0 then
 | |
| 				assert(
 | |
| 					sys.process.info("uid") == fs.stat(indexcache, "uid")
 | |
| 					and fs.stat(indexcache, "modestr") == "rw-------",
 | |
| 					"Fatal: Indexcache is not sane!"
 | |
| 				)
 | |
| 
 | |
| 				index = loadfile(indexcache)()
 | |
| 				return index
 | |
| 			end
 | |
| 		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
 | |
| 		assert(type(idx) == "function",
 | |
| 		       "Invalid controller file found\n" ..
 | |
| 		       "The file '" .. path .. "' contains no index() function.\n" ..
 | |
| 		       "Please make sure that the controller contains a valid " ..
 | |
| 		       "index function and verify the spelling!")
 | |
| 
 | |
| 		index[modname] = idx
 | |
| 	end
 | |
| 
 | |
| 	if indexcache then
 | |
| 		local f = nixio.open(indexcache, "w", 600)
 | |
| 		f:writeall(util.get_bytecode(index))
 | |
| 		f:close()
 | |
| 	end
 | |
| 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}
 | |
| 	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})
 | |
| 
 | |
| 	for k, v in pairs(index) do
 | |
| 		scope._NAME = k
 | |
| 		setfenv(v, scope)
 | |
| 		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
 | |
| 	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}
 | |
| 
 | |
| 		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
 | |
| 
 | |
|    assert(lowest ~= nil,
 | |
| 		  "The requested node contains no childs, unable to redispatch")
 | |
| 
 | |
|    path[#path+1] = lowest
 | |
|    dispatch(path)
 | |
| end
 | |
| 
 | |
| function firstchild()
 | |
|    return { type = "firstchild", target = _firstchild }
 | |
| end
 | |
| 
 | |
| function alias(...)
 | |
| 	local req = {...}
 | |
| 	return function(...)
 | |
| 		for _, r in ipairs({...}) do
 | |
| 			req[#req+1] = r
 | |
| 		end
 | |
| 
 | |
| 		dispatch(req)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function rewrite(n, ...)
 | |
| 	local req = {...}
 | |
| 	return function(...)
 | |
| 		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)
 | |
| 	end
 | |
| 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
 | |
| end
 | |
| 
 | |
| function call(name, ...)
 | |
| 	return {type = "call", argv = {...}, name = name, target = _call}
 | |
| end
 | |
| 
 | |
| function post_on(params, name, ...)
 | |
| 	return {
 | |
| 		type = "call",
 | |
| 		post = params,
 | |
| 		argv = { ... },
 | |
| 		name = name,
 | |
| 		target = _call
 | |
| 	}
 | |
| end
 | |
| 
 | |
| function post(...)
 | |
| 	return post_on(true, ...)
 | |
| end
 | |
| 
 | |
| 
 | |
| local _template = function(self, ...)
 | |
| 	require "luci.template".render(self.view)
 | |
| end
 | |
| 
 | |
| function template(name)
 | |
| 	return {type = "template", view = name, target = _template}
 | |
| end
 | |
| 
 | |
| 
 | |
| local function _cbi(self, ...)
 | |
| 	local cbi = require "luci.cbi"
 | |
| 	local tpl = require "luci.template"
 | |
| 	local http = require "luci.http"
 | |
| 
 | |
| 	local config = self.config or {}
 | |
| 	local maps = cbi.load(self.model, ...)
 | |
| 
 | |
| 	local state = nil
 | |
| 
 | |
| 	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 = { }
 | |
| 
 | |
| 	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
 | |
| 		res:render({
 | |
| 			firstmap   = (i == 1),
 | |
| 			redirect   = redirect,
 | |
| 			messages   = messages,
 | |
| 			pageaction = pageaction,
 | |
| 			parsechain = parsechain
 | |
| 		})
 | |
| 	end
 | |
| 
 | |
| 	if not config.nofooter then
 | |
| 		tpl.render("cbi/footer", {
 | |
| 			flow          = config,
 | |
| 			pageaction    = pageaction,
 | |
| 			redirect      = redirect,
 | |
| 			state         = state,
 | |
| 			autoapply     = config.autoapply,
 | |
| 			trigger_apply = applymap
 | |
| 		})
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function cbi(model, config)
 | |
| 	return {
 | |
| 		type = "cbi",
 | |
| 		post = { ["cbi.submit"] = true },
 | |
| 		config = config,
 | |
| 		model = model,
 | |
| 		target = _cbi
 | |
| 	}
 | |
| 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}}
 | |
| end
 | |
| 
 | |
| 
 | |
| local 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 = "cbi",
 | |
| 		post = { ["cbi.submit"] = true },
 | |
| 		model = model,
 | |
| 		target = _form
 | |
| 	}
 | |
| 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
 |