mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +01:00
Many changes, mostly a new configuration interface:
* change API access control to use function name suffixes * implement settings interface with baseline configuration * make AP properties configurable * AP SSID can now contain partial MAC address * update more quotation style and documentation * remove captive directory change from network mode switcher.
This commit is contained in:
parent
d3e4812cbf
commit
f988c13dfa
@ -11,7 +11,6 @@ M.DEBUG_PCALLS = true
|
|||||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||||
|
|
||||||
|
|
||||||
-- was: M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
|
||||||
M.apSsid = {
|
M.apSsid = {
|
||||||
default = 'd3d-ap-%%MAC_ADDR_TAIL%%',
|
default = 'd3d-ap-%%MAC_ADDR_TAIL%%',
|
||||||
type = 'string',
|
type = 'string',
|
||||||
@ -20,7 +19,6 @@ M.apSsid = {
|
|||||||
max = 32
|
max = 32
|
||||||
}
|
}
|
||||||
|
|
||||||
-- was: M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
|
||||||
M.apAddress = {
|
M.apAddress = {
|
||||||
default = '192.168.10.1',
|
default = '192.168.10.1',
|
||||||
type = 'string',
|
type = 'string',
|
||||||
@ -28,7 +26,6 @@ M.apAddress = {
|
|||||||
regex = '%d+\.%d+\.%d+\.%d+'
|
regex = '%d+\.%d+\.%d+\.%d+'
|
||||||
}
|
}
|
||||||
|
|
||||||
-- was: M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
|
||||||
M.apNetmask = {
|
M.apNetmask = {
|
||||||
default = '255.255.255.0',
|
default = '255.255.255.0',
|
||||||
type = 'string',
|
type = 'string',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
|
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
|
||||||
|
|
||||||
local config = require('config')
|
local confDefaults = require('conf_defaults')
|
||||||
local u = require('util.utils')
|
local u = require('util.utils')
|
||||||
local l = require('util.logger')
|
local l = require('util.logger')
|
||||||
local wifi = require('network.wlanconfig')
|
local wifi = require('network.wlanconfig')
|
||||||
@ -19,7 +19,7 @@ local function init()
|
|||||||
l:init(l.LEVEL.debug)
|
l:init(l.LEVEL.debug)
|
||||||
l:setStream(io.stderr)
|
l:setStream(io.stderr)
|
||||||
|
|
||||||
if config.DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)")
|
if confDefaults.DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)")
|
||||||
else l:info("Wifibox CGI handler started")
|
else l:info("Wifibox CGI handler started")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ local function init()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function main()
|
local function main()
|
||||||
local rq = RequestClass.new(postData, config.DEBUG_PCALLS)
|
local rq = RequestClass.new(postData, confDefaults.DEBUG_PCALLS)
|
||||||
|
|
||||||
l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "<unknown>")
|
l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "<unknown>")
|
||||||
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. u.dump(rq:getAll()))
|
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. u.dump(rq:getAll()))
|
||||||
@ -48,7 +48,7 @@ end
|
|||||||
l:debug("user agent: " .. rq:getUserAgent())
|
l:debug("user agent: " .. rq:getUserAgent())
|
||||||
end
|
end
|
||||||
|
|
||||||
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then
|
if (not confDefaults.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then
|
||||||
if rq:get('autowifi') ~= nil then
|
if rq:get('autowifi') ~= nil then
|
||||||
setupAutoWifiMode()
|
setupAutoWifiMode()
|
||||||
else
|
else
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
local config = require("config")
|
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
local l = require("util.logger")
|
local l = require("util.logger")
|
||||||
|
local s = require("util.settings")
|
||||||
|
local wifi = require("network.wlanconfig")
|
||||||
local uci = require("uci").cursor()
|
local uci = require("uci").cursor()
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
@ -95,15 +96,16 @@ end
|
|||||||
--[[ Add/remove access point network ]]
|
--[[ Add/remove access point network ]]
|
||||||
function reconf.apnet_add_noreload(dirtyList) reconf.apnet_add(dirtyList, true) end
|
function reconf.apnet_add_noreload(dirtyList) reconf.apnet_add(dirtyList, true) end
|
||||||
function reconf.apnet_add(dirtyList, noReload)
|
function reconf.apnet_add(dirtyList, noReload)
|
||||||
|
local ourSsid = wifi.getSubstitutedSsid(s.get('apSsid'))
|
||||||
local sname = nil
|
local sname = nil
|
||||||
uci:foreach("wireless", "wifi-iface", function(s)
|
uci:foreach("wireless", "wifi-iface", function(s)
|
||||||
if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end
|
if s.ssid == ourSsid then sname = s[".name"]; return false end
|
||||||
end)
|
end)
|
||||||
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
|
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
|
||||||
|
|
||||||
M.uciTableSet("wireless", sname, {
|
M.uciTableSet("wireless", sname, {
|
||||||
network = wifi.NET,
|
network = wifi.NET,
|
||||||
ssid = config.DEFAULT_AP_SSID,
|
ssid = ourSsid,
|
||||||
encryption = "none",
|
encryption = "none",
|
||||||
device = "radio0",
|
device = "radio0",
|
||||||
mode = "ap",
|
mode = "ap",
|
||||||
@ -115,7 +117,7 @@ end
|
|||||||
function reconf.apnet_rm(dirtyList)
|
function reconf.apnet_rm(dirtyList)
|
||||||
local sname = nil
|
local sname = nil
|
||||||
uci:foreach("wireless", "wifi-iface", function(s)
|
uci:foreach("wireless", "wifi-iface", function(s)
|
||||||
if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end
|
if s.ssid == wifi.getSubstitutedSsid(s.get(apSsid)) then sname = s[".name"]; return false end
|
||||||
end)
|
end)
|
||||||
if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end
|
if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end
|
||||||
uci:delete("wireless", sname)
|
uci:delete("wireless", sname)
|
||||||
@ -130,8 +132,8 @@ function reconf.staticaddr_add(dirtyList)
|
|||||||
--NOTE: 'type = "bridge"' should -not- be added as this prevents defining a separate dhcp pool (http://wiki.openwrt.org/doc/recipes/routedap)
|
--NOTE: 'type = "bridge"' should -not- be added as this prevents defining a separate dhcp pool (http://wiki.openwrt.org/doc/recipes/routedap)
|
||||||
M.uciTableSet("network", wifi.NET, {
|
M.uciTableSet("network", wifi.NET, {
|
||||||
proto = "static",
|
proto = "static",
|
||||||
ipaddr = config.DEFAULT_AP_ADDRESS,
|
ipaddr = s.get('apAddress'),
|
||||||
netmask = config.DEFAULT_AP_NETMASK
|
netmask = s.get('apNetmask')
|
||||||
})
|
})
|
||||||
bothBits(dirtyList, "network")
|
bothBits(dirtyList, "network")
|
||||||
end
|
end
|
||||||
@ -178,7 +180,7 @@ end
|
|||||||
|
|
||||||
--[[ Add/remove redirecton of all DNS requests to self ]]
|
--[[ Add/remove redirecton of all DNS requests to self ]]
|
||||||
function reconf.dnsredir_add(dirtyList)
|
function reconf.dnsredir_add(dirtyList)
|
||||||
local redirText = "/#/" .. config.DEFAULT_AP_ADDRESS
|
local redirText = "/#/" .. s.get('apAddress')
|
||||||
local sname = u.getUciSectionName("dhcp", "dnsmasq")
|
local sname = u.getUciSectionName("dhcp", "dnsmasq")
|
||||||
if sname == nil then return l:error("dhcp config does not contain a dnsmasq section") end
|
if sname == nil then return l:error("dhcp config does not contain a dnsmasq section") end
|
||||||
if uci:get("dhcp", sname, "address") ~= nil then return l:debug("DNS address redirection already in place, not re-adding", false) end
|
if uci:get("dhcp", sname, "address") ~= nil then return l:debug("DNS address redirection already in place, not re-adding", false) end
|
||||||
@ -226,7 +228,7 @@ function reconf.natreflect_add(dirtyList)
|
|||||||
proto = "tcp",
|
proto = "tcp",
|
||||||
src_dport = "80",
|
src_dport = "80",
|
||||||
dest_port = "80",
|
dest_port = "80",
|
||||||
dest_ip = config.DEFAULT_AP_ADDRESS,
|
dest_ip = s.get('apAddress'),
|
||||||
target = "DNAT"
|
target = "DNAT"
|
||||||
})
|
})
|
||||||
bothBits(dirtyList, "firewall")
|
bothBits(dirtyList, "firewall")
|
||||||
|
@ -12,6 +12,20 @@ M.NET = "wlan"
|
|||||||
|
|
||||||
local dev, dev_api
|
local dev, dev_api
|
||||||
|
|
||||||
|
-- if a substitution of baseApSsid is requested, cachedApSsid is returned if not nil
|
||||||
|
local cachedApSsid, baseApSsid = nil, nil
|
||||||
|
|
||||||
|
function M.getSubstitutedSsid(unformattedSsid)
|
||||||
|
if unformattedSsid == baseApSsid and cachedApSsid ~= nil then return cachedApSsid end
|
||||||
|
|
||||||
|
local macTail = M.getMacAddress():sub(7)
|
||||||
|
|
||||||
|
baseApSsid = unformattedSsid
|
||||||
|
cachedApSsid = unformattedSsid:gsub('%%%%MAC_ADDR_TAIL%%%%', macTail)
|
||||||
|
|
||||||
|
return cachedApSsid
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Map device mode as reported by iwinfo to device mode as required by UCI
|
--- Map device mode as reported by iwinfo to device mode as required by UCI
|
||||||
-- Note that this function is quite naive.
|
-- Note that this function is quite naive.
|
||||||
|
33
src/rest/api/api_config.lua
Normal file
33
src/rest/api/api_config.lua
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
local log = require('util.logger')
|
||||||
|
local settings = require('util.settings')
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
isApi = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function M._global_GET(request, response)
|
||||||
|
response:setSuccess()
|
||||||
|
--TODO: we need a function to list all configuration keys
|
||||||
|
for k,v in pairs(request:getAll()) do
|
||||||
|
local r,m = settings.get(k)
|
||||||
|
|
||||||
|
if r then response:addData(k, r)
|
||||||
|
else response:addData(k, "could not read key ('" .. m .. "')")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M._global_POST(request, response)
|
||||||
|
response:setSuccess()
|
||||||
|
|
||||||
|
for k,v in pairs(request:getAll()) do
|
||||||
|
local r,m = settings.set(k, v)
|
||||||
|
|
||||||
|
if r then response:addData(k, "ok")
|
||||||
|
else response:addData(k, "could not set key ('" .. m .. "')")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
@ -1,13 +1,14 @@
|
|||||||
local config = require("config")
|
local s = require("util.settings")
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
local l = require("util.logger")
|
local l = require("util.logger")
|
||||||
local netconf = require("network.netconfig")
|
local netconf = require("network.netconfig")
|
||||||
local wifi = require("network.wlanconfig")
|
local wifi = require("network.wlanconfig")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
|
|
||||||
local M = {}
|
local M = {
|
||||||
|
isApi = true
|
||||||
|
}
|
||||||
|
|
||||||
M.isApi = true
|
|
||||||
|
|
||||||
function M._global(request, response)
|
function M._global(request, response)
|
||||||
response:setError("not implemented")
|
response:setError("not implemented")
|
||||||
@ -25,7 +26,7 @@ function M.available(request, response)
|
|||||||
response:setSuccess("")
|
response:setSuccess("")
|
||||||
local netInfoList = {}
|
local netInfoList = {}
|
||||||
for _, se in ipairs(sr) do
|
for _, se in ipairs(sr) do
|
||||||
if noFilter or se.mode ~= "ap" and se.ssid ~= config.DEFAULT_AP_SSID then
|
if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.getSubstitutedSsid(s.get('apSsid')) then
|
||||||
local netInfo = {}
|
local netInfo = {}
|
||||||
|
|
||||||
netInfo["ssid"] = se.ssid
|
netInfo["ssid"] = se.ssid
|
||||||
@ -123,7 +124,8 @@ function M.assoc(request, response)
|
|||||||
end
|
end
|
||||||
|
|
||||||
wifi.activateConfig(argSsid)
|
wifi.activateConfig(argSsid)
|
||||||
netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
|
--netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
|
||||||
|
netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" }
|
||||||
response:setSuccess("wlan associated")
|
response:setSuccess("wlan associated")
|
||||||
response:addData("ssid", argSsid)
|
response:addData("ssid", argSsid)
|
||||||
end
|
end
|
||||||
@ -139,11 +141,13 @@ end
|
|||||||
--UNTESTED
|
--UNTESTED
|
||||||
function M.openap(request, response)
|
function M.openap(request, response)
|
||||||
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
|
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
|
||||||
|
local ssid = wifi.getSubstitutedSsid(s.get('apSsid'))
|
||||||
netconf.switchConfiguration{apnet="add_noreload"}
|
netconf.switchConfiguration{apnet="add_noreload"}
|
||||||
wifi.activateConfig(config.DEFAULT_AP_SSID)
|
wifi.activateConfig(ssid)
|
||||||
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
|
--netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
|
||||||
|
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add" }
|
||||||
response:setSuccess("switched to Access Point mode")
|
response:setSuccess("switched to Access Point mode")
|
||||||
response:addData("ssid", config.DEFAULT_AP_SSID)
|
response:addData("ssid", ssid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
local l = require("util.logger")
|
local l = require("util.logger")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
|
|
||||||
local M = {}
|
local M = {
|
||||||
|
isApi = true
|
||||||
M.isApi = true
|
|
||||||
|
|
||||||
--empty or nil is equivalent to 'ANY', otherwise restrict to specified letters (command-line is always allowed)
|
|
||||||
M._access = {
|
|
||||||
_global = "GET",
|
|
||||||
success = "GET", fail = "GET", error = "GET",
|
|
||||||
read = "GET", write = "POST", readwrite = "ANY", readwrite2 = "",
|
|
||||||
echo = "GET"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -38,9 +30,9 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function M.read(request, response) response:setSuccess("this endpoint can only be accessed through GET request") end
|
function M.read(request, response) response:setSuccess("this endpoint can only be accessed through GET request") end
|
||||||
function M.write(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end
|
function M.write_POST(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end
|
||||||
function M.readwrite(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end
|
function M.readwrite(request, response) response:setSuccess("this endpoint can be accessed both through GET and POST request") end
|
||||||
function M.readwrite2(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end
|
function M.readwrite2(request, response) response:setSuccess("this endpoint can be accessed both through GET and POST request") end
|
||||||
|
|
||||||
|
|
||||||
function M.echo(request, response)
|
function M.echo(request, response)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
local util = require("util.utils") --required for string:split()
|
local util = require('util.utils') -- required for string:split()
|
||||||
local urlcode = require("util.urlcode")
|
local urlcode = require('util.urlcode')
|
||||||
local config = require("config")
|
local confDefaults = require('conf_defaults')
|
||||||
local ResponseClass = require("rest.response")
|
local s = require('util.settings')
|
||||||
|
local ResponseClass = require('rest.response')
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
M.__index = M
|
M.__index = M
|
||||||
|
|
||||||
local GLOBAL_API_FUNCTION_NAME = "_global"
|
local GLOBAL_API_FUNCTION_NAME = '_global'
|
||||||
|
|
||||||
|
|
||||||
--NOTE: requestedApi* contain what was extracted from the request data
|
--NOTE: requestedApi* contain what was extracted from the request data
|
||||||
@ -17,7 +18,6 @@ M.requestedApiFunction = nil
|
|||||||
M.resolvedApiFunction = nil --will contain function address, or nil
|
M.resolvedApiFunction = nil --will contain function address, or nil
|
||||||
M.realApiFunctionName = nil --will contain requested name, or global name, or nil
|
M.realApiFunctionName = nil --will contain requested name, or global name, or nil
|
||||||
M.resolutionError = nil --non-nil means function could not be resolved
|
M.resolutionError = nil --non-nil means function could not be resolved
|
||||||
M.moduleAccessTable = nil
|
|
||||||
|
|
||||||
|
|
||||||
local function kvTableFromUrlEncodedString(encodedText)
|
local function kvTableFromUrlEncodedString(encodedText)
|
||||||
@ -46,26 +46,30 @@ local function kvTableFromArray(argArray)
|
|||||||
return args
|
return args
|
||||||
end
|
end
|
||||||
|
|
||||||
--NOTE: this function ignores empty tokens (e.g. '/a//b/' yields { [1] = a, [2] = b })
|
--- Create an array from the given '/'-separated path.
|
||||||
|
-- Empty path elements are not ignored (e.g. '/a//b' yields { [1] = '', [2] = 'a', [3] = '', [4] = 'b' }).
|
||||||
|
-- @param pathText The path to split.
|
||||||
|
-- @return An array with the path elements.
|
||||||
local function arrayFromPath(pathText)
|
local function arrayFromPath(pathText)
|
||||||
return pathText and pathText:split("/") or {}
|
return pathText and pathText:split('/') or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
--returns true if acceptable is nil or empty or 'ANY' or if it contains requested
|
--- Resolve the given module name.
|
||||||
local function matchRequestMethod(acceptable, requested)
|
-- Modules are searched for in the 'rest.api' path, with their name prefixed by 'api_'.
|
||||||
return acceptable == nil or acceptable == '' or acceptable == 'ANY' or string.find(acceptable, requested)
|
-- e.g. if modname is 'test', then the generated 'require()' path will be 'rest.api.api_test'.
|
||||||
end
|
-- Furthermore, the module must have the table key 'isApi' set to true.
|
||||||
|
-- @param modname The basename of the module to resolve.
|
||||||
|
-- @return Either a module object, or nil on error
|
||||||
--returns either a module object, or nil+errmsg
|
-- @return An message on error, or nil otherwise
|
||||||
|
-- @see resolveApiFunction
|
||||||
local function resolveApiModule(modname)
|
local function resolveApiModule(modname)
|
||||||
if modname == nil then return nil, "missing module name" end
|
if modname == nil then return nil, "missing module name" end
|
||||||
if string.find(modname, "_") == 1 then return nil, "module names starting with '_' are preserved for internal use" end
|
if string.find(modname, '_') == 1 then return nil, "module names starting with '_' are preserved for internal use" end
|
||||||
|
|
||||||
local reqModName = "rest.api.api_" .. modname
|
local reqModName = 'rest.api.api_' .. modname
|
||||||
local ok, modObj
|
local ok, modObj
|
||||||
|
|
||||||
if config.DEBUG_PCALLS then ok, modObj = true, require(reqModName)
|
if confDefaults.DEBUG_PCALLS then ok, modObj = true, require(reqModName)
|
||||||
else ok, modObj = pcall(require, reqModName)
|
else ok, modObj = pcall(require, reqModName)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -76,9 +80,17 @@ local function resolveApiModule(modname)
|
|||||||
return modObj
|
return modObj
|
||||||
end
|
end
|
||||||
|
|
||||||
--returns resultData+nil (usual), or nil+errmsg (unresolvable or inaccessible)
|
--- Resolves a module/function name pair with appropiate access for the given request method.
|
||||||
--resultData contains 'func', 'accessTable' and if found, also 'blankArg'
|
-- First, the function name suffixed with the request method, if not found the plain
|
||||||
local function resolveApiFunction(modname, funcname)
|
-- function name is looked up.
|
||||||
|
-- Returned result data contains a 'func' key and if found, also 'blankArg'.
|
||||||
|
-- @param modname Basename of the module to resolve funcname in.
|
||||||
|
-- @param funcname Basename of the function to resolve.
|
||||||
|
-- @param requestMethod Method by which the request was received.
|
||||||
|
-- @return A table with resultData or nil on error.
|
||||||
|
-- @return A message on error (unresolvable or inaccessible).
|
||||||
|
-- @see resolveApiModule
|
||||||
|
local function resolveApiFunction(modname, funcname, requestMethod)
|
||||||
local resultData = {}
|
local resultData = {}
|
||||||
|
|
||||||
if funcname and string.find(funcname, "_") == 1 then return nil, "function names starting with '_' are preserved for internal use" end
|
if funcname and string.find(funcname, "_") == 1 then return nil, "function names starting with '_' are preserved for internal use" end
|
||||||
@ -90,15 +102,18 @@ local function resolveApiFunction(modname, funcname)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil
|
if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil
|
||||||
local f = mod[funcname]
|
local rqType = requestMethod == 'POST' and 'POST' or 'GET'
|
||||||
|
local fGeneric = mod[funcname]
|
||||||
|
local fWithMethod = mod[funcname .. '_' .. rqType]
|
||||||
local funcNumber = tonumber(funcname)
|
local funcNumber = tonumber(funcname)
|
||||||
|
|
||||||
if (type(f) == "function") then
|
if (type(fWithMethod) == 'function') then
|
||||||
resultData.func = f
|
resultData.func = fWithMethod
|
||||||
resultData.accessTable = mod._access
|
elseif (type(fGeneric) == 'function') then
|
||||||
|
resultData.func = fGeneric
|
||||||
elseif funcNumber ~= nil then
|
elseif funcNumber ~= nil then
|
||||||
resultData.func = mod[GLOBAL_API_FUNCTION_NAME]
|
resultData.func = mod[GLOBAL_API_FUNCTION_NAME .. '_' .. rqType]
|
||||||
resultData.accessTable = mod._access
|
if not resultData.func then resultData.func = mod[GLOBAL_API_FUNCTION_NAME] end
|
||||||
resultData.blankArg = funcNumber
|
resultData.blankArg = funcNumber
|
||||||
else
|
else
|
||||||
return nil, ("function '" .. funcname .. "' does not exist in API module '" .. modname .. "'")
|
return nil, ("function '" .. funcname .. "' does not exist in API module '" .. modname .. "'")
|
||||||
@ -116,27 +131,37 @@ setmetatable(M, {
|
|||||||
|
|
||||||
--This function initializes itself using various environment variables, the arg array and the given postData
|
--This function initializes itself using various environment variables, the arg array and the given postData
|
||||||
--NOTE: if debugging is enabled, commandline arguments 'm' and 'f' override requested module and function
|
--NOTE: if debugging is enabled, commandline arguments 'm' and 'f' override requested module and function
|
||||||
function M.new(postData, debug)
|
function M.new(postData, debugEnabled)
|
||||||
local self = setmetatable({}, M)
|
local self = setmetatable({}, M)
|
||||||
|
|
||||||
--NOTE: is it correct to assume that absence of REQUEST_METHOD indicates command line invocation?
|
--NOTE: is it correct to assume that absence of REQUEST_METHOD indicates command line invocation?
|
||||||
self.requestMethod = os.getenv("REQUEST_METHOD")
|
self.requestMethod = os.getenv('REQUEST_METHOD')
|
||||||
if self.requestMethod ~= nil then
|
if self.requestMethod ~= nil then
|
||||||
self.remoteHost = os.getenv("REMOTE_HOST")
|
self.remoteHost = os.getenv('REMOTE_HOST')
|
||||||
self.remotePort = os.getenv("REMOTE_PORT")
|
self.remotePort = os.getenv('REMOTE_PORT')
|
||||||
self.userAgent = os.getenv("HTTP_USER_AGENT")
|
self.userAgent = os.getenv('HTTP_USER_AGENT')
|
||||||
else
|
else
|
||||||
self.requestMethod = "CMDLINE"
|
self.requestMethod = 'CMDLINE'
|
||||||
end
|
end
|
||||||
|
|
||||||
self.cmdLineArgs = kvTableFromArray(arg)
|
self.cmdLineArgs = kvTableFromArray(arg)
|
||||||
self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING"))
|
self.getArgs = kvTableFromUrlEncodedString(os.getenv('QUERY_STRING'))
|
||||||
self.postArgs = kvTableFromUrlEncodedString(postData)
|
self.postArgs = kvTableFromUrlEncodedString(postData)
|
||||||
self.pathArgs = arrayFromPath(os.getenv("PATH_INFO"))
|
self.pathArgs = arrayFromPath(os.getenv('PATH_INFO'))
|
||||||
|
|
||||||
--override path arguments with command line parameter if debugging is enabled
|
-- override path arguments with command line parameter and allow to emulate GET/POST if debugging is enabled
|
||||||
if debug and self.requestMethod == "CMDLINE" then
|
if debugEnabled and self.requestMethod == 'CMDLINE' then
|
||||||
self.pathArgs = arrayFromPath(self.cmdLineArgs["p"])
|
self.pathArgs = arrayFromPath(self.cmdLineArgs['p'])
|
||||||
|
|
||||||
|
if self.cmdLineArgs['r'] == 'GET' then
|
||||||
|
self.requestMethod = 'GET'
|
||||||
|
self.getArgs = self.cmdLineArgs
|
||||||
|
self.getArgs.p, self.getArgs.r = nil, nil
|
||||||
|
elseif self.cmdLineArgs['r'] == 'POST' then
|
||||||
|
self.requestMethod = 'POST'
|
||||||
|
self.postArgs = self.cmdLineArgs
|
||||||
|
self.postArgs.p, self.postArgs.r = nil, nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
table.remove(self.pathArgs, 1) --drop the first 'empty' field caused by the opening slash of the query string
|
table.remove(self.pathArgs, 1) --drop the first 'empty' field caused by the opening slash of the query string
|
||||||
|
|
||||||
@ -144,16 +169,15 @@ function M.new(postData, debug)
|
|||||||
if #self.pathArgs >= 1 then self.requestedApiModule = self.pathArgs[1] end
|
if #self.pathArgs >= 1 then self.requestedApiModule = self.pathArgs[1] end
|
||||||
if #self.pathArgs >= 2 then self.requestedApiFunction = self.pathArgs[2] end
|
if #self.pathArgs >= 2 then self.requestedApiFunction = self.pathArgs[2] end
|
||||||
|
|
||||||
if self.requestedApiModule == "" then self.requestedApiModule = nil end
|
if self.requestedApiModule == '' then self.requestedApiModule = nil end
|
||||||
if self.requestedApiFunction == "" then self.requestedApiFunction = nil end
|
if self.requestedApiFunction == '' then self.requestedApiFunction = nil end
|
||||||
|
|
||||||
|
|
||||||
-- Perform module/function resolution
|
-- Perform module/function resolution
|
||||||
local rData, errMsg = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction())
|
local rData, errMsg = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction(), self.requestMethod)
|
||||||
|
|
||||||
if rData ~= nil and rData.func ~= nil then --function (possibly the global one) could be resolved
|
if rData ~= nil and rData.func ~= nil then --function (possibly the global one) could be resolved
|
||||||
self.resolvedApiFunction = rData.func
|
self.resolvedApiFunction = rData.func
|
||||||
self.moduleAccessTable = rData.accessTable
|
|
||||||
if rData.blankArg ~= nil then --apparently it was the global one, and we received a 'blank argument'
|
if rData.blankArg ~= nil then --apparently it was the global one, and we received a 'blank argument'
|
||||||
self:setBlankArgument(rData.blankArg)
|
self:setBlankArgument(rData.blankArg)
|
||||||
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
||||||
@ -185,11 +209,11 @@ function M:getRemotePort() return self.remotePort or 0 end
|
|||||||
function M:getUserAgent() return self.userAgent or "" end
|
function M:getUserAgent() return self.userAgent or "" end
|
||||||
|
|
||||||
function M:get(key)
|
function M:get(key)
|
||||||
if self.requestMethod == "GET" then
|
if self.requestMethod == 'GET' then
|
||||||
return self.getArgs[key]
|
return self.getArgs[key]
|
||||||
elseif self.requestMethod == "POST" then
|
elseif self.requestMethod == 'POST' then
|
||||||
return self.postArgs[key]
|
return self.postArgs[key]
|
||||||
elseif self.requestMethod == "CMDLINE" then
|
elseif self.requestMethod == 'CMDLINE' then
|
||||||
return self.cmdLineArgs[key]
|
return self.cmdLineArgs[key]
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
@ -197,11 +221,11 @@ function M:get(key)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M:getAll()
|
function M:getAll()
|
||||||
if self.requestMethod == "GET" then
|
if self.requestMethod == 'GET' then
|
||||||
return self.getArgs
|
return self.getArgs
|
||||||
elseif self.requestMethod == "POST" then
|
elseif self.requestMethod == 'POST' then
|
||||||
return self.postArgs
|
return self.postArgs
|
||||||
elseif self.requestMethod == "CMDLINE" then
|
elseif self.requestMethod == 'CMDLINE' then
|
||||||
return self.cmdLineArgs
|
return self.cmdLineArgs
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
@ -218,16 +242,9 @@ function M:handle()
|
|||||||
local resp = ResponseClass.new(self)
|
local resp = ResponseClass.new(self)
|
||||||
|
|
||||||
if (self.resolvedApiFunction ~= nil) then --we found a function (possible the global function)
|
if (self.resolvedApiFunction ~= nil) then --we found a function (possible the global function)
|
||||||
--check access type
|
|
||||||
local accessText = self.moduleAccessTable[self.realApiFunctionName]
|
|
||||||
if not matchRequestMethod(accessText, self.requestMethod) then
|
|
||||||
resp:setError("function '" .. modname .. "/" .. self.realApiFunctionName .. "' requires different request method ('" .. accessText .. "')")
|
|
||||||
return resp, "incorrect access method (" .. accessText .. " != " .. self.requestMethod .. ")"
|
|
||||||
end
|
|
||||||
|
|
||||||
--invoke the function
|
--invoke the function
|
||||||
local ok, r
|
local ok, r
|
||||||
if config.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp)
|
if confDefaults.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp)
|
||||||
else ok, r = pcall(self.resolvedApiFunction, self, resp)
|
else ok, r = pcall(self.resolvedApiFunction, self, resp)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
local JSON = require("util/JSON")
|
local JSON = require("util/JSON")
|
||||||
local config = require("config")
|
local s = require("util.settings")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
M.__index = M
|
M.__index = M
|
||||||
@ -26,7 +26,7 @@ function M.new(requestObject)
|
|||||||
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
||||||
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
|
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
|
||||||
|
|
||||||
if config.API_INCLUDE_ENDPOINT_INFO == true then
|
if s.API_INCLUDE_ENDPOINT_INFO == true then
|
||||||
self.body["module"] = requestObject:getRequestedApiModule()
|
self.body["module"] = requestObject:getRequestedApiModule()
|
||||||
self.body["function"] = requestObject:getRealApiFunctionName() or ""
|
self.body["function"] = requestObject:getRealApiFunctionName() or ""
|
||||||
end
|
end
|
||||||
|
@ -35,8 +35,8 @@ function M:test_get()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M:test_set()
|
function M:test_set()
|
||||||
local key = 'apAddress'
|
local key, intKey = 'apAddress', 'temperature'
|
||||||
local goodValue, badValue1, badValue2 = '10.0.0.1', '10.00.1', '10.0.0d.1'
|
local intValue, goodValue, badValue1, badValue2 = 340, '10.0.0.1', '10.00.1', '10.0.0d.1'
|
||||||
|
|
||||||
assert(s.get(key) == defaults.apAddress.default)
|
assert(s.get(key) == defaults.apAddress.default)
|
||||||
assert(s.isDefault(key))
|
assert(s.isDefault(key))
|
||||||
@ -53,6 +53,14 @@ function M:test_set()
|
|||||||
|
|
||||||
assert(s.set(key, nil))
|
assert(s.set(key, nil))
|
||||||
assert(s.isDefault(key))
|
assert(s.isDefault(key))
|
||||||
|
|
||||||
|
-- test with value of int type
|
||||||
|
assert(s.get(intKey) == defaults.temperature.default)
|
||||||
|
assert(s.isDefault(intKey))
|
||||||
|
|
||||||
|
assert(s.set(intKey, intValue))
|
||||||
|
assert(s.get(intKey) == intValue)
|
||||||
|
assert(not s.isDefault(intKey))
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:test_setNonExistent()
|
function M:test_setNonExistent()
|
||||||
|
@ -98,6 +98,7 @@ function M:test_symlink()
|
|||||||
assert(false, 'not implemented')
|
assert(false, 'not implemented')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- this should test for the following condition: 'Could not rename /www to /www-regular(/www: Invalid cross-device link)'
|
||||||
function M:test_symlinkInRoot()
|
function M:test_symlinkInRoot()
|
||||||
assert(false, 'not implemented')
|
assert(false, 'not implemented')
|
||||||
end
|
end
|
||||||
|
39
src/test/test_wlanconfig.lua
Normal file
39
src/test/test_wlanconfig.lua
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
local wlanconfig = require("network.wlanconfig")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
_is_test = true,
|
||||||
|
_skip = {},
|
||||||
|
_wifibox_only = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
local function captureCommandOutput(cmd)
|
||||||
|
local f = assert(io.popen(cmd, 'r'))
|
||||||
|
return assert(f:read('*all'))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M._setup()
|
||||||
|
wlanconfig.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.test_getMacAddress()
|
||||||
|
local reportedMac = wlanconfig.getMacAddress()
|
||||||
|
local output = captureCommandOutput('ifconfig wlan0')
|
||||||
|
local actualMac = output:match('HWaddr (%w%w:%w%w:%w%w:%w%w:%w%w:%w%w)'):gsub(':', ''):upper()
|
||||||
|
|
||||||
|
assert(reportedMac == actualMac)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.test_getSubstitutedSsid()
|
||||||
|
local mac = wlanconfig.getMacAddress()
|
||||||
|
local macTail = mac:sub(7)
|
||||||
|
|
||||||
|
local expected1 = 'pre' .. macTail .. 'post'
|
||||||
|
local expected2 = 'pre' .. macTail .. 'post-cache-test'
|
||||||
|
|
||||||
|
assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1)
|
||||||
|
assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post-cache-test') == expected2)
|
||||||
|
assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1)
|
||||||
|
assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
@ -40,12 +40,15 @@ local function isValid(value, baseTable)
|
|||||||
|
|
||||||
if type == 'bool' then
|
if type == 'bool' then
|
||||||
return isboolean(value) or nil,"invalid bool value"
|
return isboolean(value) or nil,"invalid bool value"
|
||||||
|
|
||||||
elseif type == 'int' or type == 'float' then
|
elseif type == 'int' or type == 'float' then
|
||||||
local ok = isnumber(value)
|
local numValue = tonumber(value)
|
||||||
ok = ok and (type == 'float' or math.floor(value) == value)
|
local ok = numValue and true or false
|
||||||
if min then ok = ok and value >= min end
|
ok = ok and (type == 'float' or math.floor(numValue) == numValue)
|
||||||
if max then ok = ok and value <= max end
|
if min then ok = ok and numValue >= min end
|
||||||
return ok or nil,"invalid int/float value"
|
if max then ok = ok and numValue <= max end
|
||||||
|
return ok or nil,"invalid int/float value or out of range"
|
||||||
|
|
||||||
elseif type == 'string' then
|
elseif type == 'string' then
|
||||||
local ok = true
|
local ok = true
|
||||||
if min then ok = ok and value:len() >= min end
|
if min then ok = ok and value:len() >= min end
|
||||||
@ -69,7 +72,7 @@ function M.get(key)
|
|||||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||||
|
|
||||||
local v = base.default
|
local v = base.default
|
||||||
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key))
|
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key), base.type)
|
||||||
|
|
||||||
return uciV or v
|
return uciV or v
|
||||||
end
|
end
|
||||||
@ -95,7 +98,7 @@ function M.set(key, value)
|
|||||||
|
|
||||||
local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
||||||
|
|
||||||
if fromUciValue(current) == value then return true end
|
if fromUciValue(current, base.type) == value then return true end
|
||||||
|
|
||||||
if value ~= nil then
|
if value ~= nil then
|
||||||
local valid,m = isValid(value, base)
|
local valid,m = isValid(value, base)
|
||||||
|
Loading…
Reference in New Issue
Block a user