diff options
Diffstat (limited to 'modules/luci-base/luasrc')
31 files changed, 651 insertions, 412 deletions
diff --git a/modules/luci-base/luasrc/cbi.lua b/modules/luci-base/luasrc/cbi.lua index f3d4618b65..2c1bb4d226 100644 --- a/modules/luci-base/luasrc/cbi.lua +++ b/modules/luci-base/luasrc/cbi.lua @@ -38,7 +38,7 @@ function load(cbimap, ...) require("luci.config") require("luci.util") - local upldir = "/lib/uci/upload/" + local upldir = "/etc/luci-uploads/" local cbidir = luci.util.libpath() .. "/model/cbi/" local func, err @@ -367,63 +367,64 @@ end -- Use optimized UCI writing function Map.parse(self, readinput, ...) - self.readinput = (readinput ~= false) - self:_run_hooks("on_parse") - if self:formvalue("cbi.skip") then self.state = FORM_SKIP + elseif not self.save then + self.state = FORM_INVALID + elseif not self:submitstate() then + self.state = FORM_NODATA + end + + -- Back out early to prevent unauthorized changes on the subsequent parse + if self.state ~= nil then return self:state_handler(self.state) end + self.readinput = (readinput ~= false) + self:_run_hooks("on_parse") + Node.parse(self, ...) - if self.save then - self:_run_hooks("on_save", "on_before_save") + self:_run_hooks("on_save", "on_before_save") + for i, config in ipairs(self.parsechain) do + self.uci:save(config) + end + self:_run_hooks("on_after_save") + if (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then + self:_run_hooks("on_before_commit") for i, config in ipairs(self.parsechain) do - self.uci:save(config) - end - self:_run_hooks("on_after_save") - if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then - self:_run_hooks("on_before_commit") - for i, config in ipairs(self.parsechain) do - self.uci:commit(config) - - -- Refresh data because commit changes section names - self.uci:load(config) - end - self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") - if self.apply_on_parse then - self.uci:apply(self.parsechain) - self:_run_hooks("on_apply", "on_after_apply") - else - -- This is evaluated by the dispatcher and delegated to the - -- template which in turn fires XHR to perform the actual - -- apply actions. - self.apply_needed = true - end - - -- Reparse sections - Node.parse(self, true) + self.uci:commit(config) + -- Refresh data because commit changes section names + self.uci:load(config) end - for i, config in ipairs(self.parsechain) do - self.uci:unload(config) - end - if type(self.commit_handler) == "function" then - self:commit_handler(self:submitstate()) + self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") + if self.apply_on_parse then + self.uci:apply(self.parsechain) + self:_run_hooks("on_apply", "on_after_apply") + else + -- This is evaluated by the dispatcher and delegated to the + -- template which in turn fires XHR to perform the actual + -- apply actions. + self.apply_needed = true end + + -- Reparse sections + Node.parse(self, true) + end + for i, config in ipairs(self.parsechain) do + self.uci:unload(config) + end + if type(self.commit_handler) == "function" then + self:commit_handler(self:submitstate()) end - if self:submitstate() then - if not self.save then - self.state = FORM_INVALID - elseif self.proceed then - self.state = FORM_PROCEED - else - self.state = self.changed and FORM_CHANGED or FORM_VALID - end + if self.proceed then + self.state = FORM_PROCEED + elseif self.changed then + self.state = FORM_CHANGED else - self.state = FORM_NODATA + self.state = FORM_VALID end return self:state_handler(self.state) @@ -1470,6 +1471,7 @@ function Value.__init__(self, ...) self.template = "cbi/value" self.keylist = {} self.vallist = {} + self.readonly = nil end function Value.reset_values(self) @@ -1483,6 +1485,10 @@ function Value.value(self, key, val) table.insert(self.vallist, tostring(val)) end +function Value.parse(self, section, novld) + if self.readonly then return end + AbstractValue.parse(self, section, novld) +end -- DummyValue - This does nothing except being there DummyValue = class(AbstractValue) @@ -1527,17 +1533,25 @@ function Flag.__init__(self, ...) end -- A flag can only have two states: set or unset -function Flag.parse(self, section) +function Flag.parse(self, section, novld) local fexists = self.map:formvalue( FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option) if fexists then local fvalue = self:formvalue(section) and self.enabled or self.disabled local cvalue = self:cfgvalue(section) - if fvalue ~= self.default or (not self.optional and not self.rmempty) then - self:write(section, fvalue) - else + local val_err + fvalue, val_err = self:validate(fvalue, section) + if not fvalue then + if not novld then + self:add_error(section, "invalid", val_err) + end + return + end + if fvalue == self.default and (self.optional or self.rmempty) then self:remove(section) + else + self:write(section, fvalue) end if (fvalue ~= cvalue) then self.section.changed = true end else @@ -1549,7 +1563,9 @@ end function Flag.cfgvalue(self, section) return AbstractValue.cfgvalue(self, section) or self.default end - +function Flag.validate(self, value) + return value +end --[[ ListValue - A one-line value predefined in a list @@ -1795,6 +1811,7 @@ function Button.__init__(self, ...) self.template = "cbi/button" self.inputstyle = nil self.rmempty = true + self.unsafeupload = false end @@ -1811,9 +1828,15 @@ function FileUpload.__init__(self, ...) end function FileUpload.formcreated(self, section) - return AbstractValue.formcreated(self, section) or - self.map:formvalue("cbi.rlf."..section.."."..self.option) or - self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + if self.unsafeupload then + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbi.rlf."..section.."."..self.option) or + self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") or + self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + else + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + end end function FileUpload.cfgvalue(self, section) @@ -1824,27 +1847,50 @@ function FileUpload.cfgvalue(self, section) return nil end +-- If we have a new value, use it +-- otherwise use old value +-- deletion should be managed by a separate button object +-- unless self.unsafeupload is set in which case if the user +-- choose to remove the old file we do so. +-- Also, allow to specify (via textbox) a file already on router function FileUpload.formvalue(self, section) local val = AbstractValue.formvalue(self, section) if val then - if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and - not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") - then + if self.unsafeupload then + if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and + not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + then + return val + end + fs.unlink(val) + self.value = nil + return nil + elseif val ~= "" then return val - end - fs.unlink(val) - self.value = nil + end end - return nil + val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + if val == "" then + val = nil + end + if not self.unsafeupload then + if not val then + val = self.map:formvalue("cbi.rlf."..section.."."..self.option) + end + end + return val end function FileUpload.remove(self, section) - local val = AbstractValue.formvalue(self, section) - if val and fs.access(val) then fs.unlink(val) end - return AbstractValue.remove(self, section) + if self.unsafeupload then + local val = AbstractValue.formvalue(self, section) + if val and fs.access(val) then fs.unlink(val) end + return AbstractValue.remove(self, section) + else + return nil + end end - FileBrowser = class(AbstractValue) function FileBrowser.__init__(self, ...) diff --git a/modules/luci-base/luasrc/cbi/datatypes.lua b/modules/luci-base/luasrc/cbi/datatypes.lua index ebd7e594f7..626ad91c75 100644 --- a/modules/luci-base/luasrc/cbi/datatypes.lua +++ b/modules/luci-base/luasrc/cbi/datatypes.lua @@ -176,14 +176,43 @@ function hostname(val) return false end -function host(val) - return hostname(val) or ipaddr(val) +function host(val, ipv4only) + return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val)) end function network(val) return uciname(val) or host(val) end +function hostport(val, ipv4only) + local h, p = val:match("^([^:]+):([^:]+)$") + return not not (h and p and host(h, ipv4only) and port(p)) +end + +function ip4addrport(val, bracket) + local h, p = val:match("^([^:]+):([^:]+)$") + return (h and p and ip4addr(h) and port(p)) +end + +function ip4addrport(val) + local h, p = val:match("^([^:]+):([^:]+)$") + return (h and p and ip4addr(h) and port(p)) +end + +function ipaddrport(val, bracket) + local h, p = val:match("^([^%[%]:]+):([^:]+)$") + if (h and p and ip4addr(h) and port(p)) then + return true + elseif (bracket == 1) then + h, p = val:match("^%[(.+)%]:([^:]+)$") + if (h and p and ip6addr(h) and port(p)) then + return true + end + end + h, p = val:match("^([^%[%]]+):([^:]+)$") + return (h and p and ip6addr(h) and port(p)) +end + function wpakey(val) if #val == 64 then return (val:match("^[a-fA-F0-9]+$") ~= nil) @@ -331,3 +360,48 @@ end function phonedigit(val) return (val:match("^[0-9\*#!%.]+$") ~= nil) end + +function timehhmmss(val) + return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil) +end + +function dateyyyymmdd(val) + if val ~= nil then + yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$") + if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then + return false; + end + year = tonumber(yearstr) + month = tonumber(monthstr) + day = tonumber(daystr) + if (year == nil) or (month == nil) or (day == nil) then + return false; + end + + local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + + local function is_leap_year(year) + return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0)) + end + + function get_days_in_month(month, year) + if (month == 2) and is_leap_year(year) then + return 29 + else + return days_in_month[month] + end + end + if (year < 2015) then + return false + end + if ((month == 0) or (month > 12)) then + return false + end + if ((day == 0) or (day > get_days_in_month(month, year))) then + return false + end + return true + end + return false +end + diff --git a/modules/luci-base/luasrc/controller/admin/servicectl.lua b/modules/luci-base/luasrc/controller/admin/servicectl.lua index 5b855cb24b..1d73eb4ecc 100644 --- a/modules/luci-base/luasrc/controller/admin/servicectl.lua +++ b/modules/luci-base/luasrc/controller/admin/servicectl.lua @@ -6,7 +6,7 @@ module("luci.controller.admin.servicectl", package.seeall) function index() entry({"servicectl"}, alias("servicectl", "status")).sysauth = "root" entry({"servicectl", "status"}, call("action_status")).leaf = true - entry({"servicectl", "restart"}, call("action_restart")).leaf = true + entry({"servicectl", "restart"}, post("action_restart")).leaf = true end function action_status() diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua index 8b8d1fa349..cd5d77a12b 100644 --- a/modules/luci-base/luasrc/dispatcher.lua +++ b/modules/luci-base/luasrc/dispatcher.lua @@ -1,4 +1,5 @@ -- 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" @@ -26,14 +27,6 @@ function build_url(...) local path = {...} local url = { http.getenv("SCRIPT_NAME") or "" } - local k, v - for k, v in pairs(context.urltoken) do - url[#url+1] = "/;" - url[#url+1] = http.urlencode(k) - url[#url+1] = "=" - url[#url+1] = http.urlencode(v) - end - local p for _, p in ipairs(path) do if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then @@ -42,6 +35,10 @@ function build_url(...) end end + if #path == 0 then + url[#url+1] = "/" + end + return table.concat(url, "") end @@ -112,24 +109,11 @@ function authenticator.htmlauth(validator, accs, default) return user end - if context.urltoken.stok then - context.urltoken.stok = nil - - local cookie = 'sysauth=%s; expires=%s; path=%s/' %{ - http.getcookie('sysauth') or 'x', - 'Thu, 01 Jan 1970 01:00:00 GMT', - build_url() - } - - http.header("Set-Cookie", cookie) - http.redirect(build_url()) - else - require("luci.i18n") - require("luci.template") - context.path = {} - http.status(403, "Forbidden") - luci.template.render("sysauth", {duser=default, fuser=user}) - end + require("luci.i18n") + require("luci.template") + context.path = {} + http.status(403, "Forbidden") + luci.template.render("sysauth", {duser=default, fuser=user}) return false @@ -140,7 +124,6 @@ function httpdispatch(request, prefix) local r = {} context.request = r - context.urltoken = {} local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) @@ -150,18 +133,8 @@ function httpdispatch(request, prefix) end end - local tokensok = true for node in pathinfo:gmatch("[^/]+") do - local tkey, tval - if tokensok then - tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)") - end - if tkey then - context.urltoken[tkey] = tval - else - tokensok = false - r[#r+1] = node - end + r[#r+1] = node end local stat, err = util.coxpcall(function() @@ -173,6 +146,48 @@ function httpdispatch(request, prefix) --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 or request_val == "")) + 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 + function dispatch(request) --context._disable_memtrace = require "luci.debug".trap_memtrace("l") local ctx = context @@ -206,7 +221,6 @@ function dispatch(request) ctx.args = args ctx.requestargs = ctx.requestargs or args local n - local token = ctx.urltoken local preq = {} local freq = {} @@ -284,11 +298,14 @@ function dispatch(request) resource = luci.config.main.resourcebase; ifattr = function(...) return _ifattr(...) end; attr = function(...) return _ifattr(true, ...) end; + url = build_url; }, {__index=function(table, key) if key == "controller" then return build_url() elseif key == "REQUEST_URI" then return build_url(unpack(ctx.requestpath)) + elseif key == "token" then + return ctx.authtoken else return rawget(table, key) or _G[key] end @@ -311,20 +328,17 @@ function dispatch(request) local def = (type(track.sysauth) == "string") and track.sysauth local accs = def and {track.sysauth} or track.sysauth local sess = ctx.authsession - local verifytoken = false if not sess then sess = http.getcookie("sysauth") sess = sess and sess:match("^[a-f0-9]*$") - verifytoken = true end local sdat = (util.ubus("session", "get", { ubus_rpc_session = sess }) or { }).values - local user + local user, token if sdat then - if not verifytoken or ctx.urltoken.stok == sdat.token then - user = sdat.user - end + user = sdat.user + token = sdat.token else local eu = http.getenv("HTTP_AUTH_USER") local ep = http.getenv("HTTP_AUTH_PASS") @@ -357,12 +371,10 @@ function dispatch(request) end if sess and token then - http.header("Set-Cookie", 'sysauth=%s; path=%s/' %{ - sess, build_url() - }) + http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sess, build_url() }) - ctx.urltoken.stok = token ctx.authsession = sess + ctx.authtoken = token ctx.authuser = user http.redirect(build_url(unpack(ctx.requestpath))) @@ -374,10 +386,17 @@ function dispatch(request) end else ctx.authsession = sess + ctx.authtoken = token ctx.authuser = user end 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 @@ -703,6 +722,20 @@ 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) @@ -814,7 +847,13 @@ local function _cbi(self, ...) end function cbi(model, config) - return {type = "cbi", config = config, model = model, target = _cbi} + return { + type = "cbi", + post = { ["cbi.submit"] = "1" }, + config = config, + model = model, + target = _cbi + } end @@ -854,7 +893,12 @@ local function _form(self, ...) end function form(model) - return {type = "cbi", model = model, target = _form} + return { + type = "cbi", + post = { ["cbi.submit"] = "1" }, + model = model, + target = _form + } end translate = i18n.translate diff --git a/modules/luci-base/luasrc/http.lua b/modules/luci-base/luasrc/http.lua index a92d8affb6..4b35731727 100644 --- a/modules/luci-base/luasrc/http.lua +++ b/modules/luci-base/luasrc/http.lua @@ -208,6 +208,7 @@ function splice(fd, size) end function redirect(url) + if url == "" then url = "/" end status(302, "Found") header("Location", url) close() diff --git a/modules/luci-base/luasrc/http.luadoc b/modules/luci-base/luasrc/http.luadoc index 4e31216a1e..8a325db21a 100644 --- a/modules/luci-base/luasrc/http.luadoc +++ b/modules/luci-base/luasrc/http.luadoc @@ -1,8 +1,7 @@ ---[[ LuCI Web Framework high-level HTTP functions. - -module "luci.http" ]] +module "luci.http" ---[[ Close the HTTP-Connection. diff --git a/modules/luci-base/luasrc/http/protocol.lua b/modules/luci-base/luasrc/http/protocol.lua index 61d7b802fe..0cb62aeec9 100644 --- a/modules/luci-base/luasrc/http/protocol.lua +++ b/modules/luci-base/luasrc/http/protocol.lua @@ -72,7 +72,7 @@ function urlencode( str ) if type(str) == "string" then str = str:gsub( - "([^a-zA-Z0-9$_%-%.!*'(),])", + "([^a-zA-Z0-9$_%-%.%~])", __chrenc ) end @@ -559,14 +559,23 @@ function parse_message_body( src, msg, filecb ) -- If we have a file callback then feed it if type(filecb) == "function" then - sink = filecb - + local meta = { + name = "raw", + encoding = msg.env.CONTENT_TYPE + } + sink = function( chunk ) + if chunk then + return filecb(meta, chunk, false) + else + return filecb(meta, nil, true) + end + end -- ... else append to .content else msg.content = "" msg.content_length = 0 - sink = function( chunk, err ) + sink = function( chunk ) if chunk then if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then msg.content = msg.content .. chunk diff --git a/modules/luci-base/luasrc/model/ipkg.lua b/modules/luci-base/luasrc/model/ipkg.lua index 2e26bd7a16..e653b03465 100644 --- a/modules/luci-base/luasrc/model/ipkg.lua +++ b/modules/luci-base/luasrc/model/ipkg.lua @@ -127,22 +127,29 @@ local function _list(action, pat, cb) (pat and (" '%s'" % pat:gsub("'", "")) or "")) if fd then - local name, version, desc + local name, version, sz, desc while true do local line = fd:read("*l") if not line then break end - name, version, desc = line:match("^(.-) %- (.-) %- (.+)") + name, version, sz, desc = line:match("^(.-) %- (.-) %- (.-) %- (.+)") if not name then - name, version = line:match("^(.-) %- (.+)") + name, version, sz = line:match("^(.-) %- (.-) %- (.+)") desc = "" end - cb(name, version, desc) + if name and version then + if #version > 26 then + version = version:sub(1,21) .. ".." .. version:sub(-3,-1) + end + + cb(name, version, sz, desc) + end name = nil version = nil + sz = nil desc = nil end @@ -151,15 +158,15 @@ local function _list(action, pat, cb) end function list_all(pat, cb) - _list("list", pat, cb) + _list("list --size", pat, cb) end function list_installed(pat, cb) - _list("list_installed", pat, cb) + _list("list_installed --size", pat, cb) end function find(pat, cb) - _list("find", pat, cb) + _list("find --size", pat, cb) end @@ -233,4 +240,3 @@ function compare_versions(ver1, comp, ver2) -- all equal and not compare greater or lower then true return not (comp == "<" or comp == ">") end - diff --git a/modules/luci-base/luasrc/model/ipkg.luadoc b/modules/luci-base/luasrc/model/ipkg.luadoc index 0dbab7a68f..4e1548dda6 100644 --- a/modules/luci-base/luasrc/model/ipkg.luadoc +++ b/modules/luci-base/luasrc/model/ipkg.luadoc @@ -1,8 +1,7 @@ ---[[ LuCI OPKG call abstraction library - -module "luci.model.ipkg" ]] +module "luci.model.ipkg" ---[[ Return information about installed and available packages. diff --git a/modules/luci-base/luasrc/model/network.lua b/modules/luci-base/luasrc/model/network.lua index 20e1032760..81fc416fed 100644 --- a/modules/luci-base/luasrc/model/network.lua +++ b/modules/luci-base/luasrc/model/network.lua @@ -1,8 +1,8 @@ -- Copyright 2009-2015 Jo-Philipp Wich <jow@openwrt.org> -- Licensed to the public under the Apache License 2.0. -local type, next, pairs, ipairs, loadfile, table - = type, next, pairs, ipairs, loadfile, table +local type, next, pairs, ipairs, loadfile, table, select + = type, next, pairs, ipairs, loadfile, table, select local tonumber, tostring, math = tonumber, tostring, math @@ -16,6 +16,7 @@ local utl = require "luci.util" local dsp = require "luci.dispatcher" local uci = require "luci.model.uci" local lng = require "luci.i18n" +local jsc = require "luci.jsonc" module "luci.model.network" @@ -31,10 +32,10 @@ local _protocols = { } local _interfaces, _bridge, _switch, _tunnel local _ubusnetcache, _ubusdevcache, _ubuswificache -local _uci_real, _uci_state +local _uci function _filter(c, s, o, r) - local val = _uci_real:get(c, s, o) + local val = _uci:get(c, s, o) if val then local l = { } if type(val) == "string" then @@ -44,9 +45,9 @@ function _filter(c, s, o, r) end end if #l > 0 then - _uci_real:set(c, s, o, table.concat(l, " ")) + _uci:set(c, s, o, table.concat(l, " ")) else - _uci_real:delete(c, s, o) + _uci:delete(c, s, o) end elseif type(val) == "table" then for _, val in ipairs(val) do @@ -55,16 +56,16 @@ function _filter(c, s, o, r) end end if #l > 0 then - _uci_real:set(c, s, o, l) + _uci:set(c, s, o, l) else - _uci_real:delete(c, s, o) + _uci:delete(c, s, o) end end end end function _append(c, s, o, a) - local val = _uci_real:get(c, s, o) or "" + local val = _uci:get(c, s, o) or "" if type(val) == "string" then local l = { } for val in val:gmatch("%S+") do @@ -73,7 +74,7 @@ function _append(c, s, o, a) end end l[#l+1] = a - _uci_real:set(c, s, o, table.concat(l, " ")) + _uci:set(c, s, o, table.concat(l, " ")) elseif type(val) == "table" then local l = { } for _, val in ipairs(val) do @@ -82,7 +83,7 @@ function _append(c, s, o, a) end end l[#l+1] = a - _uci_real:set(c, s, o, l) + _uci:set(c, s, o, l) end end @@ -95,15 +96,15 @@ function _stror(s1, s2) end function _get(c, s, o) - return _uci_real:get(c, s, o) + return _uci:get(c, s, o) end function _set(c, s, o, v) if v ~= nil then if type(v) == "boolean" then v = v and "1" or "0" end - return _uci_real:set(c, s, o, v) + return _uci:set(c, s, o, v) else - return _uci_real:delete(c, s, o) + return _uci:delete(c, s, o) end end @@ -127,7 +128,7 @@ function _wifi_state(key, val, field) for radio, radiostate in pairs(_ubuswificache) do for ifc, ifcstate in pairs(radiostate.interfaces) do if ifcstate.section and ifcstate.section:sub(1, 1) == '@' then - local s = _uci_real:get_all('wireless.%s' % ifcstate.section) + local s = _uci:get_all('wireless.%s' % ifcstate.section) if s then ifcstate.section = s['.name'] end @@ -153,7 +154,7 @@ function _wifi_lookup(ifn) local num = 0 ifnidx = tonumber(ifnidx) - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device == radio then num = num + 1 @@ -166,20 +167,9 @@ function _wifi_lookup(ifn) return sid - -- looks like wifi, try to locate the section via state vars + -- looks like wifi, try to locate the section via ubus state elseif _wifi_iface(ifn) then - local sid = _wifi_state("ifname", ifn, "section") - if not sid then - _uci_state:foreach("wireless", "wifi-iface", - function(s) - if s.ifname == ifn then - sid = s['.name'] - return false - end - end) - end - - return sid + return _wifi_state("ifname", ifn, "section") end end @@ -205,8 +195,7 @@ end function init(cursor) - _uci_real = cursor or _uci_real or uci.cursor() - _uci_state = _uci_real:substate() + _uci = cursor or _uci or uci.cursor() _interfaces = { } _bridge = { } @@ -281,13 +270,13 @@ function init(cursor) end function save(self, ...) - _uci_real:save(...) - _uci_real:load(...) + _uci:save(...) + _uci:load(...) end function commit(self, ...) - _uci_real:commit(...) - _uci_real:load(...) + _uci:commit(...) + _uci:load(...) end function ifnameof(self, x) @@ -345,7 +334,7 @@ end function add_network(self, n, options) local oldnet = self:get_network(n) if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then - if _uci_real:section("network", "interface", n, options) then + if _uci:section("network", "interface", n, options) then return network(n) end elseif oldnet and oldnet:is_empty() then @@ -360,7 +349,7 @@ function add_network(self, n, options) end function get_network(self, n) - if n and _uci_real:get("network", n) == "interface" then + if n and _uci:get("network", n) == "interface" then return network(n) end end @@ -369,7 +358,7 @@ function get_networks(self) local nets = { } local nls = { } - _uci_real:foreach("network", "interface", + _uci:foreach("network", "interface", function(s) nls[s['.name']] = network(s['.name']) end) @@ -383,18 +372,18 @@ function get_networks(self) end function del_network(self, n) - local r = _uci_real:delete("network", n) + local r = _uci:delete("network", n) if r then - _uci_real:delete_all("network", "alias", + _uci:delete_all("network", "alias", function(s) return (s.interface == n) end) - _uci_real:delete_all("network", "route", + _uci:delete_all("network", "route", function(s) return (s.interface == n) end) - _uci_real:delete_all("network", "route6", + _uci:delete_all("network", "route6", function(s) return (s.interface == n) end) - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) local net local rest = { } @@ -404,10 +393,10 @@ function del_network(self, n) end end if #rest > 0 then - _uci_real:set("wireless", s['.name'], "network", + _uci:set("wireless", s['.name'], "network", table.concat(rest, " ")) else - _uci_real:delete("wireless", s['.name'], "network") + _uci:delete("wireless", s['.name'], "network") end end) end @@ -417,31 +406,31 @@ end function rename_network(self, old, new) local r if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then - r = _uci_real:section("network", "interface", new, _uci_real:get_all("network", old)) + r = _uci:section("network", "interface", new, _uci:get_all("network", old)) if r then - _uci_real:foreach("network", "alias", + _uci:foreach("network", "alias", function(s) if s.interface == old then - _uci_real:set("network", s['.name'], "interface", new) + _uci:set("network", s['.name'], "interface", new) end end) - _uci_real:foreach("network", "route", + _uci:foreach("network", "route", function(s) if s.interface == old then - _uci_real:set("network", s['.name'], "interface", new) + _uci:set("network", s['.name'], "interface", new) end end) - _uci_real:foreach("network", "route6", + _uci:foreach("network", "route6", function(s) if s.interface == old then - _uci_real:set("network", s['.name'], "interface", new) + _uci:set("network", s['.name'], "interface", new) end end) - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) local net local list = { } @@ -453,12 +442,12 @@ function rename_network(self, old, new) end end if #list > 0 then - _uci_real:set("wireless", s['.name'], "network", + _uci:set("wireless", s['.name'], "network", table.concat(list, " ")) end end) - _uci_real:delete("network", old) + _uci:delete("network", old) end end return r or false @@ -470,7 +459,7 @@ function get_interface(self, i) else local ifc local num = { } - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device then num[s.device] = num[s.device] and num[s.device] + 1 or 1 @@ -485,6 +474,21 @@ function get_interface(self, i) end end +local function swdev_from_board_json() + local boardinfo = jsc.parse(nfs.readfile("/etc/board.json") or "") + if type(boardinfo) == "table" and type(boardinfo.network) == "table" then + local net, val + for net, val in pairs(boardinfo.network) do + if type(val) == "table" and type(val.ifname) == "string" and + val.create_vlan == true + then + return val.ifname + end + end + end + return nil +end + function get_interfaces(self) local iface local ifaces = { } @@ -493,7 +497,7 @@ function get_interfaces(self) local baseof = { } -- find normal interfaces - _uci_real:foreach("network", "interface", + _uci:foreach("network", "interface", function(s) for iface in utl.imatch(s.ifname) do if not _iface_ignore(iface) and not _wifi_iface(iface) then @@ -510,7 +514,7 @@ function get_interfaces(self) end -- find vlan interfaces - _uci_real:foreach("network", "switch_vlan", + _uci:foreach("network", "switch_vlan", function(s) if not s.device then return @@ -526,7 +530,7 @@ function get_interfaces(self) end end if not base or not base:match("^eth%d") then - base = "eth0" + base = swdev_from_board_json() or "eth0" end else base = s.device @@ -551,7 +555,7 @@ function get_interfaces(self) -- find wifi interfaces local num = { } local wfs = { } - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device then num[s.device] = num[s.device] and num[s.device] + 1 or 1 @@ -572,7 +576,7 @@ function ignore_interface(self, x) end function get_wifidev(self, dev) - if _uci_real:get("wireless", dev) == "wifi-device" then + if _uci:get("wireless", dev) == "wifi-device" then return wifidev(dev) end end @@ -581,7 +585,7 @@ function get_wifidevs(self) local devs = { } local wfd = { } - _uci_real:foreach("wireless", "wifi-device", + _uci:foreach("wireless", "wifi-device", function(s) wfd[#wfd+1] = s['.name'] end) local dev @@ -601,9 +605,9 @@ end function add_wifinet(self, net, options) if type(options) == "table" and options.device and - _uci_real:get("wireless", options.device) == "wifi-device" + _uci:get("wireless", options.device) == "wifi-device" then - local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + local wnet = _uci:section("wireless", "wifi-iface", nil, options) return wifinet(wnet) end end @@ -611,7 +615,7 @@ end function del_wifinet(self, net) local wnet = _wifi_lookup(net) if wnet then - _uci_real:delete("wireless", wnet) + _uci:delete("wireless", wnet) return true end return false @@ -684,7 +688,7 @@ end function network(name, proto) if name then - local p = proto or _uci_real:get("network", name, "proto") + local p = proto or _uci:get("network", name, "proto") local c = p and _protocols[p] or protocol return c(name) end @@ -695,7 +699,7 @@ function protocol.__init__(self, name) end function protocol._get(self, opt) - local v = _uci_real:get("network", self.sid, opt) + local v = _uci:get("network", self.sid, opt) if type(v) == "table" then return table.concat(v, " ") end @@ -730,7 +734,7 @@ function protocol.ifname(self) end if not ifname then local num = { } - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device then num[s.device] = num[s.device] @@ -779,17 +783,21 @@ function protocol.uptime(self) end function protocol.expires(self) - local a = tonumber(_uci_state:get("network", self.sid, "lease_acquired")) - local l = tonumber(_uci_state:get("network", self.sid, "lease_lifetime")) - if a and l then - l = l - (nxo.sysinfo().uptime - a) - return l > 0 and l or 0 + local u = self:_ubus("uptime") + local d = self:_ubus("data") + + if type(u) == "number" and type(d) == "table" and + type(d.leasetime) == "number" + then + local r = (d.leasetime - (u % d.leasetime)) + return r > 0 and r or 0 end + return -1 end function protocol.metric(self) - return tonumber(_uci_state:get("network", self.sid, "metric")) or 0 + return self:_ubus("metric") or 0 end function protocol.ipaddr(self) @@ -797,6 +805,20 @@ function protocol.ipaddr(self) return addrs and #addrs > 0 and addrs[1].address end +function protocol.ipaddrs(self) + local addrs = self:_ubus("ipv4-address") + local rv = { } + + if type(addrs) == "table" then + local n, addr + for n, addr in ipairs(addrs) do + rv[#rv+1] = "%s/%d" %{ addr.address, addr.mask } + end + end + + return rv +end + function protocol.netmask(self) local addrs = self:_ubus("ipv4-address") return addrs and #addrs > 0 and @@ -835,6 +857,28 @@ function protocol.ip6addr(self) end end +function protocol.ip6addrs(self) + local addrs = self:_ubus("ipv6-address") + local rv = { } + local n, addr + + if type(addrs) == "table" then + for n, addr in ipairs(addrs) do + rv[#rv+1] = "%s/%d" %{ addr.address, addr.mask } + end + end + + addrs = self:_ubus("ipv6-prefix-assignment") + + if type(addrs) == "table" then + for n, addr in ipairs(addrs) do + rv[#rv+1] = "%s1/%d" %{ addr.address, addr.mask } + end + end + + return rv +end + function protocol.gw6addr(self) local _, route for _, route in ipairs(self:_ubus("route") or { }) do @@ -885,7 +929,7 @@ function protocol.is_empty(self) rv = false end - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) local n for n in utl.imatch(s.network) do @@ -937,12 +981,12 @@ function protocol.get_interface(self) else local ifn = nil local num = { } - for ifn in utl.imatch(_uci_real:get("network", self.sid, "ifname")) do + for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do ifn = ifn:match("^[^:/]+") return ifn and interface(ifn, self) end ifn = nil - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device then num[s.device] = num[s.device] and num[s.device] + 1 or 1 @@ -977,7 +1021,7 @@ function protocol.get_interfaces(self) local num = { } local wfs = { } - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device then num[s.device] = num[s.device] and num[s.device] + 1 or 1 @@ -1020,7 +1064,7 @@ function protocol.contains_interface(self, ifname) local wif = _wifi_lookup(ifname) if wif then local n - for n in utl.imatch(_uci_real:get("wireless", wif, "network")) do + for n in utl.imatch(_uci:get("wireless", wif, "network")) do if n == self.sid then return true end @@ -1066,7 +1110,8 @@ function interface.name(self) end function interface.mac(self) - return (self:_ubus("macaddr") or "00:00:00:00:00:00"):upper() + local mac = self:_ubus("macaddr") + return mac and mac:upper() end function interface.ipaddrs(self) @@ -1278,22 +1323,11 @@ function wifidev.is_up(self) return (_ubuswificache[self.sid].up == true) end - local up = false - _uci_state:foreach("wireless", "wifi-iface", - function(s) - if s.device == self.sid then - if s.up == "1" then - up = true - return false - end - end - end) - - return up + return false end function wifidev.get_wifinet(self, net) - if _uci_real:get("wireless", net) == "wifi-iface" then + if _uci:get("wireless", net) == "wifi-iface" then return wifinet(net) else local wnet = _wifi_lookup(net) @@ -1306,7 +1340,7 @@ end function wifidev.get_wifinets(self) local nets = { } - _uci_real:foreach("wireless", "wifi-iface", + _uci:foreach("wireless", "wifi-iface", function(s) if s.device == self.sid then nets[#nets+1] = wifinet(s['.name']) @@ -1320,7 +1354,7 @@ function wifidev.add_wifinet(self, options) options = options or { } options.device = self.sid - local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + local wnet = _uci:section("wireless", "wifi-iface", nil, options) if wnet then return wifinet(wnet, options) end @@ -1329,12 +1363,12 @@ end function wifidev.del_wifinet(self, net) if utl.instanceof(net, wifinet) then net = net.sid - elseif _uci_real:get("wireless", net) ~= "wifi-iface" then + elseif _uci:get("wireless", net) ~= "wifi-iface" then net = _wifi_lookup(net) end - if net and _uci_real:get("wireless", net, "device") == self.sid then - _uci_real:delete("wireless", net) + if net and _uci:get("wireless", net, "device") == self.sid then + _uci:delete("wireless", net) return true end @@ -1347,26 +1381,58 @@ wifinet = utl.class() function wifinet.__init__(self, net, data) self.sid = net + local n = 0 local num = { } - local netid - _uci_real:foreach("wireless", "wifi-iface", + local netid, sid + _uci:foreach("wireless", "wifi-iface", function(s) + n = n + 1 if s.device then num[s.device] = num[s.device] and num[s.device] + 1 or 1 if s['.name'] == self.sid then + sid = "@wifi-iface[%d]" % n netid = "%s.network%d" %{ s.device, num[s.device] } return false end end end) + if sid then + local _, k, r, i + for k, r in pairs(_ubuswificache) do + if type(r) == "table" and + type(r.interfaces) == "table" + then + for _, i in ipairs(r.interfaces) do + if type(i) == "table" and i.section == sid then + self._ubusdata = { + radio = k, + dev = r, + net = i + } + end + end + end + end + end + local dev = _wifi_state("section", self.sid, "ifname") or netid self.netid = netid self.wdev = dev self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { } - self.iwdata = data or _uci_state:get_all("wireless", self.sid) or - _uci_real:get_all("wireless", self.sid) or { } +end + +function wifinet.ubus(self, ...) + local n, v = self._ubusdata + for n = 1, select('#', ...) do + if type(v) == "table" then + v = v[select(n, ...)] + else + return nil + end + end + return v end function wifinet.get(self, opt) @@ -1378,19 +1444,23 @@ function wifinet.set(self, opt, val) end function wifinet.mode(self) - return _uci_state:get("wireless", self.sid, "mode") or "ap" + return self:ubus("net", "config", "mode") or self:get("mode") or "ap" end function wifinet.ssid(self) - return _uci_state:get("wireless", self.sid, "ssid") + return self:ubus("net", "config", "ssid") or self:get("ssid") end function wifinet.bssid(self) - return _uci_state:get("wireless", self.sid, "bssid") + return self:ubus("net", "config", "bssid") or self:get("bssid") end function wifinet.network(self) - return _uci_state:get("wifinet", self.sid, "network") + local net, networks = nil, { } + for net in utl.imatch(self:ubus("net", "config", "network") or self:get("network")) do + networks[#networks+1] = net + end + return networks end function wifinet.id(self) @@ -1402,7 +1472,7 @@ function wifinet.name(self) end function wifinet.ifname(self) - local ifname = self.iwinfo.ifname + local ifname = self:ubus("net", "ifname") or self.iwinfo.ifname if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then ifname = self.wdev end @@ -1410,9 +1480,8 @@ function wifinet.ifname(self) end function wifinet.get_device(self) - if self.iwdata.device then - return wifidev(self.iwdata.device) - end + local dev = self:ubus("radio") or self:get("device") + return dev and wifidev(dev) or nil end function wifinet.is_up(self) @@ -1421,7 +1490,7 @@ function wifinet.is_up(self) end function wifinet.active_mode(self) - local m = _stror(self.iwdata.mode, self.iwinfo.mode) or "ap" + local m = self.iwinfo.mode or self:ubus("net", "config", "mode") or self:get("mode") or "ap" if m == "ap" then m = "Master" elseif m == "sta" then m = "Client" @@ -1438,11 +1507,11 @@ function wifinet.active_mode_i18n(self) end function wifinet.active_ssid(self) - return _stror(self.iwdata.ssid, self.iwinfo.ssid) + return self.iwinfo.ssid or self:ubus("net", "config", "ssid") or self:get("ssid") end function wifinet.active_bssid(self) - return _stror(self.iwdata.bssid, self.iwinfo.bssid) or "00:00:00:00:00:00" + return self.iwinfo.bssid or self:ubus("net", "config", "bssid") or self:get("bssid") end function wifinet.active_encryption(self) @@ -1469,8 +1538,8 @@ function wifinet.bitrate(self) end function wifinet.channel(self) - return self.iwinfo.channel or - tonumber(_uci_state:get("wireless", self.iwdata.device, "channel")) + return self.iwinfo.channel or self:ubus("dev", "config", "channel") or + tonumber(self:get("channel")) end function wifinet.signal(self) @@ -1482,7 +1551,7 @@ function wifinet.noise(self) end function wifinet.country(self) - return self.iwinfo.country or "00" + return self.iwinfo.country or self:ubus("dev", "config", "country") or "00" end function wifinet.txpower(self) @@ -1548,8 +1617,8 @@ end function wifinet.get_networks(self) local nets = { } local net - for net in utl.imatch(tostring(self.iwdata.network)) do - if _uci_real:get("network", net) == "interface" then + for net in utl.imatch(self:ubus("net", "config", "network") or self:get("network")) do + if _uci:get("network", net) == "interface" then nets[#nets+1] = network(net) end end diff --git a/modules/luci-base/luasrc/model/uci.luadoc b/modules/luci-base/luasrc/model/uci.luadoc index 1c208669d1..7591b68b02 100644 --- a/modules/luci-base/luasrc/model/uci.luadoc +++ b/modules/luci-base/luasrc/model/uci.luadoc @@ -8,8 +8,8 @@ Cursor.commit the data to the actual config files. LuCI then needs to Cursor.apply the changes so deamons etc. are reloaded. @cstyle instance -module "luci.model.uci" ]] +module "luci.model.uci" ---[[ Create a new UCI-Cursor. @@ -235,12 +235,18 @@ Saves changes made to a config to make them committable. ---[[ Set a value or create a named section. +When invoked with three arguments `config`, `sectionname`, `sectiontype`, +then a named section of the given type is created. + +When invoked with four arguments `config`, `sectionname`, `optionname` and +`optionvalue` then the value of the specified option is set to the given value. + @class function @name Cursor.set @param config UCI config @param section UCI section name @param option UCI option or UCI section type -@param value UCI value or nil if you want to create a section +@param value UCI value or nothing if you want to create a section @return Boolean whether operation succeeded ]] diff --git a/modules/luci-base/luasrc/sys.lua b/modules/luci-base/luasrc/sys.lua index 3977da3eda..31c3e12095 100644 --- a/modules/luci-base/luasrc/sys.lua +++ b/modules/luci-base/luasrc/sys.lua @@ -293,47 +293,51 @@ function net.ipv6_hints(callback) end function net.conntrack(callback) - local connt = {} - if fs.access("/proc/net/nf_conntrack", "r") then - for line in io.lines("/proc/net/nf_conntrack") do - line = line:match "^(.-( [^ =]+=).-)%2" - local entry, flags = _parse_mixed_record(line, " +") - if flags[6] ~= "TIME_WAIT" then - entry.layer3 = flags[1] - entry.layer4 = flags[3] - for i=1, #entry do - entry[i] = nil - end + local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack") + if not ok or not nfct then + return nil + end - if callback then - callback(entry) - else - connt[#connt+1] = entry + local line, connt = nil, (not callback) and { } + for line in nfct do + local fam, l3, l4, timeout, state, tuples = + line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +([A-Z_]+) +(.+)$") + + if fam and l3 and l4 and timeout and state and tuples and + state ~= "TIME_WAIT" + then + l4 = nixio.getprotobynumber(l4) + + local entry = { + bytes = 0, + packets = 0, + layer3 = fam, + layer4 = l4 and l4.name or "unknown", + timeout = tonumber(timeout, 10) + } + + local key, val + for key, val in tuples:gmatch("(%w+)=(%S+)") do + if key == "bytes" or key == "packets" then + entry[key] = entry[key] + tonumber(val, 10) + elseif key == "src" or key == "dst" or key == "sport" or key == "dport" then + if entry[key] == nil then + entry[key] = val + end + elseif val then + entry[key] = val end end - end - elseif fs.access("/proc/net/ip_conntrack", "r") then - for line in io.lines("/proc/net/ip_conntrack") do - line = line:match "^(.-( [^ =]+=).-)%2" - local entry, flags = _parse_mixed_record(line, " +") - if flags[4] ~= "TIME_WAIT" then - entry.layer3 = "ipv4" - entry.layer4 = flags[1] - for i=1, #entry do - entry[i] = nil - end - if callback then - callback(entry) - else - connt[#connt+1] = entry - end + if callback then + callback(entry) + else + connt[#connt+1] = entry end end - else - return nil end - return connt + + return callback and true or connt end function net.devices() @@ -581,30 +585,20 @@ function wifi.getiwinfo(ifname) local stat, iwinfo = pcall(require, "iwinfo") if ifname then - local c = 0 - local u = uci.cursor_state() local d, n = ifname:match("^(%w+)%.network(%d+)") - if d and n then + local wstate = luci.util.ubus("network.wireless", "status") or { } + + d = d or ifname + n = n and tonumber(n) or 1 + + if type(wstate[d]) == "table" and + type(wstate[d].interfaces) == "table" and + type(wstate[d].interfaces[n]) == "table" and + type(wstate[d].interfaces[n].ifname) == "string" + then + ifname = wstate[d].interfaces[n].ifname + else ifname = d - n = tonumber(n) - u:foreach("wireless", "wifi-iface", - function(s) - if s.device == d then - c = c + 1 - if c == n then - ifname = s.ifname or s.device - return false - end - end - end) - elseif u:get("wireless", ifname) == "wifi-device" then - u:foreach("wireless", "wifi-iface", - function(s) - if s.device == ifname and s.ifname then - ifname = s.ifname - return false - end - end) end local t = stat and iwinfo.type(ifname) @@ -665,28 +659,3 @@ end function init.stop(name) return (init_action("stop", name) == 0) end - - --- Internal functions - -function _parse_mixed_record(cnt, delimiter) - delimiter = delimiter or " " - local data = {} - local flags = {} - - for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do - for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do - local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*') - - if k then - if x == "" then - table.insert(flags, k) - else - data[k] = v - end - end - end - end - - return data, flags -end diff --git a/modules/luci-base/luasrc/sys.luadoc b/modules/luci-base/luasrc/sys.luadoc index 72a16a1ab0..54be34958a 100644 --- a/modules/luci-base/luasrc/sys.luadoc +++ b/modules/luci-base/luasrc/sys.luadoc @@ -1,8 +1,7 @@ ---[[ LuCI Linux and POSIX system utilities. - -module "luci.sys" ]] +module "luci.sys" ---[[ Execute a given shell command and return the error code diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua index b604f6b65e..ff13b1ccd2 100644 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua +++ b/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua @@ -89,7 +89,7 @@ TZ = { { 'America/Cancun', 'EST5' }, { 'America/Caracas', 'VET4:30' }, { 'America/Cayenne', 'GFT3' }, - { 'America/Cayman', 'EST5' }, + { 'America/Cayman', 'EST5EDT,M3.2.0,M11.1.0' }, { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, { 'America/Costa Rica', 'CST6' }, @@ -105,6 +105,7 @@ TZ = { { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, { 'America/Eirunepe', 'ACT5' }, { 'America/El Salvador', 'CST6' }, + { 'America/Fort Nelson', 'MST7' }, { 'America/Fortaleza', 'BRT3' }, { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, { 'America/Godthab', 'WGT3WGST,M3.5.0/-2,M10.5.0/-1' }, @@ -151,7 +152,7 @@ TZ = { { 'America/Miquelon', 'PMST3PMDT,M3.2.0,M11.1.0' }, { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Montevideo', 'UYT3UYST,M10.1.0,M3.2.0' }, + { 'America/Montevideo', 'UYT3' }, { 'America/Montserrat', 'AST4' }, { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, @@ -266,7 +267,7 @@ TZ = { { 'Asia/Oral', 'ORAT-5' }, { 'Asia/Phnom Penh', 'ICT-7' }, { 'Asia/Pontianak', 'WIB-7' }, - { 'Asia/Pyongyang', 'KST-9' }, + { 'Asia/Pyongyang', 'KST-8:30' }, { 'Asia/Qatar', 'AST-3' }, { 'Asia/Qyzylorda', 'QYZT-6' }, { 'Asia/Rangoon', 'MMT-6:30' }, @@ -322,7 +323,7 @@ TZ = { { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, { 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Chisinau', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' }, { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, { 'Europe/Dublin', 'GMT0IST,M3.5.0/1,M10.5.0' }, { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, @@ -388,7 +389,7 @@ TZ = { { 'Pacific/Efate', 'VUT-11' }, { 'Pacific/Enderbury', 'PHOT-13' }, { 'Pacific/Fakaofo', 'TKT-13' }, - { 'Pacific/Fiji', 'FJT-12FJST,M11.1.0,M1.3.4/75' }, + { 'Pacific/Fiji', 'FJT-12FJST,M11.1.0,M1.3.0/3' }, { 'Pacific/Funafuti', 'TVT-12' }, { 'Pacific/Galapagos', 'GALT6' }, { 'Pacific/Gambier', 'GAMT9' }, @@ -404,7 +405,7 @@ TZ = { { 'Pacific/Midway', 'SST11' }, { 'Pacific/Nauru', 'NRT-12' }, { 'Pacific/Niue', 'NUT11' }, - { 'Pacific/Norfolk', 'NFT-11:30' }, + { 'Pacific/Norfolk', 'NFT-11' }, { 'Pacific/Noumea', 'NCT-11' }, { 'Pacific/Pago Pago', 'SST11' }, { 'Pacific/Palau', 'PWT-9' }, diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua index 24429ac36b..53c8adbc69 100644 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua +++ b/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua @@ -41,7 +41,6 @@ OFFSET = { pmst = -10800, -- PMST pmdt = -7200, -- PMDT uyt = -10800, -- UYT - uyst = -7200, -- UYST fnt = -7200, -- FNT srt = -10800, -- SRT clt = -10800, -- CLT @@ -95,7 +94,7 @@ OFFSET = { novt = 21600, -- NOVT omst = 21600, -- OMST orat = 18000, -- ORAT - kst = 32400, -- KST + kst = 30600, -- KST qyzt = 21600, -- QYZT mmt = 23400, -- MMT sakt = 36000, -- SAKT @@ -153,7 +152,7 @@ OFFSET = { sst = -39600, -- SST nrt = 43200, -- NRT nut = -39600, -- NUT - nft = 41400, -- NFT + nft = 39600, -- NFT nct = 39600, -- NCT pwt = 32400, -- PWT pont = 39600, -- PONT diff --git a/modules/luci-base/luasrc/util.lua b/modules/luci-base/luasrc/util.lua index dcf8230b3e..5bf0beb6c2 100644 --- a/modules/luci-base/luasrc/util.lua +++ b/modules/luci-base/luasrc/util.lua @@ -9,6 +9,7 @@ local ldebug = require "luci.debug" local string = require "string" local coroutine = require "coroutine" local tparser = require "luci.template.parser" +local json = require "luci.jsonc" local _ubus = require "ubus" local _ubus_connection = nil @@ -150,6 +151,28 @@ function striptags(value) return value and tparser.striptags(tostring(value)) end +-- for bash, ash and similar shells single-quoted strings are taken +-- literally except for single quotes (which terminate the string) +-- (and the exception noted below for dash (-) at the start of a +-- command line parameter). +function shellsqescape(value) + local res + res, _ = string.gsub(res, "'", "'\\''") + return res +end + +-- bash, ash and other similar shells interpret a dash (-) at the start +-- of a command-line parameters as an option indicator regardless of +-- whether it is inside a single-quoted string. It must be backlash +-- escaped to resolve this. This requires in some funky special-case +-- handling. It may actually be a property of the getopt function +-- rather than the shell proper. +function shellstartsqescape(value) + res, _ = string.gsub(value, "^\-", "\\-") + res, _ = string.gsub(res, "^-", "\-") + return shellsqescape(value) +end + -- containing the resulting substrings. The optional max parameter specifies -- the number of bytes to process, regardless of the actual length of the given -- string. The optional last parameter, regex, specifies whether the separator @@ -600,55 +623,11 @@ function ubus(object, method, data) end function serialize_json(x, cb) - local rv, push = nil, cb - if not push then - rv = { } - push = function(tok) rv[#rv+1] = tok end - end - - if x == nil then - push("null") - elseif type(x) == "table" then - -- test if table is array like - local k, v - local n1, n2 = 0, 0 - for k in pairs(x) do n1 = n1 + 1 end - for k in ipairs(x) do n2 = n2 + 1 end - - if n1 == n2 and n1 > 0 then - push("[") - for k = 1, n2 do - if k > 1 then - push(",") - end - serialize_json(x[k], push) - end - push("]") - else - push("{") - for k, v in pairs(x) do - push("%q:" % tostring(k)) - serialize_json(v, push) - if next(x, k) then - push(",") - end - end - push("}") - end - elseif type(x) == "number" or type(x) == "boolean" then - if (x ~= x) then - -- NaN is the only value that doesn't equal to itself. - push("Number.NaN") - else - push(tostring(x)) - end + local js = json.stringify(x) + if type(cb) == "function" then + cb(js) else - push('"%s"' % tostring(x):gsub('["%z\1-\31\\]', - function(c) return '\\u%04x' % c:byte(1) end)) - end - - if not cb then - return table.concat(rv, "") + return js end end diff --git a/modules/luci-base/luasrc/util.luadoc b/modules/luci-base/luasrc/util.luadoc index 1c09b7a9ab..805eeb7f8e 100644 --- a/modules/luci-base/luasrc/util.luadoc +++ b/modules/luci-base/luasrc/util.luadoc @@ -1,8 +1,7 @@ ---[[ LuCI utility functions. - -module "luci.util" ]] +module "luci.util" ---[[ Create a Class object (Python-style object model). @@ -182,7 +181,8 @@ Checks whether the given table contains the given value. @name contains @param table Table value @param value Value to search within the given table -@return Boolean indicating whether the given value occurs within table +@return number indicating the first index at which the given value occurs +-- within table or false. ]] ---[[ diff --git a/modules/luci-base/luasrc/view/cbi/apply_xhr.htm b/modules/luci-base/luasrc/view/cbi/apply_xhr.htm index 1814c9393b..daa57c1db7 100644 --- a/modules/luci-base/luasrc/view/cbi/apply_xhr.htm +++ b/modules/luci-base/luasrc/view/cbi/apply_xhr.htm @@ -4,10 +4,10 @@ <script type="text/javascript">//<![CDATA[ var apply_xhr = new XHR(); - apply_xhr.get('<%=luci.dispatcher.build_url("servicectl", "restart", table.concat(configs, ","))%>', null, + apply_xhr.post('<%=url('servicectl/restart', table.concat(configs, ","))%>', { token: '<%=token%>' }, function() { var checkfinish = function() { - apply_xhr.get('<%=luci.dispatcher.build_url("servicectl", "status")%>', null, + apply_xhr.get('<%=url('servicectl/status')%>', null, function(x) { if( x.responseText == 'finish' ) { diff --git a/modules/luci-base/luasrc/view/cbi/browser.htm b/modules/luci-base/luasrc/view/cbi/browser.htm index e4a4077d55..a18120141d 100644 --- a/modules/luci-base/luasrc/view/cbi/browser.htm +++ b/modules/luci-base/luasrc/view/cbi/browser.htm @@ -2,6 +2,6 @@ <%+cbi/valueheader%> <input class="cbi-input-text" type="text"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> /> <script type="text/javascript"> -cbi_browser_init('<%=cbid%>', '<%=resource%>', '<%=luci.dispatcher.build_url("admin", "filebrowser")%>'<%=self.default_path and ", '"..self.default_path.."'"%>); +cbi_browser_init('<%=cbid%>', '<%=resource%>', '<%=url('admin/filebrowser')%>'<%=self.default_path and ", '"..self.default_path.."'"%>); </script> <%+cbi/valuefooter%> diff --git a/modules/luci-base/luasrc/view/cbi/dynlist.htm b/modules/luci-base/luasrc/view/cbi/dynlist.htm index fd626a4ecf..e936c0c39f 100644 --- a/modules/luci-base/luasrc/view/cbi/dynlist.htm +++ b/modules/luci-base/luasrc/view/cbi/dynlist.htm @@ -15,7 +15,9 @@ <script type="text/javascript"> cbi_dynlist_init( '<%=cbid%>', '<%=resource%>', '<%=self.datatype%>', - <%=tostring(self.optional or self.rmempty)%> + <%=tostring(self.optional or self.rmempty)%>, + '<%=url('admin/filebrowser')%>', + '<%=self.default_path and self.default_path%>' <%- if #self.keylist > 0 then -%>, [{ <%- for i, k in ipairs(self.keylist) do -%> <%-=string.format("%q", k) .. ":" .. string.format("%q", self.vallist[i])-%> diff --git a/modules/luci-base/luasrc/view/cbi/error.htm b/modules/luci-base/luasrc/view/cbi/error.htm index 2acb96924e..75ec1082aa 100644 --- a/modules/luci-base/luasrc/view/cbi/error.htm +++ b/modules/luci-base/luasrc/view/cbi/error.htm @@ -1,5 +1,5 @@ <div class="cbi-map" id="cbi-<%=self.config%>"> - <% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %> + <% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %> <% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %> <p class="alert-message danger"> diff --git a/modules/luci-base/luasrc/view/cbi/header.htm b/modules/luci-base/luasrc/view/cbi/header.htm index 2bddaba61a..302df1d2fd 100644 --- a/modules/luci-base/luasrc/view/cbi/header.htm +++ b/modules/luci-base/luasrc/view/cbi/header.htm @@ -2,6 +2,7 @@ <form method="post" name="cbi" action="<%=REQUEST_URI%>" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, '<%:Some fields are invalid, cannot save values!%>')"> <div> <script type="text/javascript" src="<%=resource%>/cbi.js"></script> + <input type="hidden" name="token" value="<%=token%>" /> <input type="hidden" name="cbi.submit" value="1" /> <input type="submit" value="<%:Save%>" class="hidden" /> </div> diff --git a/modules/luci-base/luasrc/view/cbi/map.htm b/modules/luci-base/luasrc/view/cbi/map.htm index 053220d185..e90c3f589f 100644 --- a/modules/luci-base/luasrc/view/cbi/map.htm +++ b/modules/luci-base/luasrc/view/cbi/map.htm @@ -5,7 +5,7 @@ <%-+cbi/apply_xhr-%> <div class="cbi-map" id="cbi-<%=self.config%>"> - <% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %> + <% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %> <% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %> <%- if firstmap and applymap then cbi_apply_xhr(self.config, parsechain, redirect) end -%> <%- self:render_children() %> diff --git a/modules/luci-base/luasrc/view/cbi/mvalue.htm b/modules/luci-base/luasrc/view/cbi/mvalue.htm index 6a0b3881d0..5d092610ed 100644 --- a/modules/luci-base/luasrc/view/cbi/mvalue.htm +++ b/modules/luci-base/luasrc/view/cbi/mvalue.htm @@ -12,7 +12,7 @@ c = c + 1 %> <input class="cbi-input-checkbox" type="checkbox" onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= attr("id", cbid..c) .. attr("name", cbid) .. attr("value", key) .. ifattr(luci.util.contains(v, key), "checked", "checked") %> /> - <label<%= attr("for", cbid..c) %>><%=self.vallist[i]%></label><br /> + <label<%= attr("for", cbid..c) %>><%=self.vallist[i]%></label><% if not self.oneline then %><br /><% else %> <% end %> <% if c == self.size then c = 0 %><br /> <% end end %> <% end %> diff --git a/modules/luci-base/luasrc/view/cbi/simpleform.htm b/modules/luci-base/luasrc/view/cbi/simpleform.htm index 5216cd50f1..78f5c5a544 100644 --- a/modules/luci-base/luasrc/view/cbi/simpleform.htm +++ b/modules/luci-base/luasrc/view/cbi/simpleform.htm @@ -2,11 +2,12 @@ <form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>"> <div> <script type="text/javascript" src="<%=resource%>/cbi.js"></script> + <input type="hidden" name="token" value="<%=token%>" /> <input type="hidden" name="cbi.submit" value="1" /> </div> <% end %> <div class="cbi-map" id="cbi-<%=self.config%>"> - <% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %> + <% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %> <% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %> <% self:render_children() %> <br /> diff --git a/modules/luci-base/luasrc/view/cbi/upload.htm b/modules/luci-base/luasrc/view/cbi/upload.htm index 7770934111..157f3b36fa 100644 --- a/modules/luci-base/luasrc/view/cbi/upload.htm +++ b/modules/luci-base/luasrc/view/cbi/upload.htm @@ -6,9 +6,19 @@ <%+cbi/valueheader%> <% if s then %> <%:Uploaded File%> (<%=t.byte_format(s.size)%>) - <input type="hidden"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> /> - <input class="cbi-button cbi-input-image" type="image" value="<%:Replace entry%>" name="cbi.rlf.<%=section .. "." .. self.option%>" alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> - <% else %> + <% if self.unsafeupload then %> + <input type="hidden"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> /> + <input class="cbi-button cbi-input-image" type="image" value="<%:Replace entry%>" name="cbi.rlf.<%=section .. "." .. self.option%>" alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> + <% end %> + <% end %> + + <% if not self.unsafeupload then %> + <input type="hidden"<%= attr("value", v) .. attr("name", "cbi.rlf." .. section .. "." .. self.option) .. attr("id", "cbi.rlf." .. section .. "." .. self.option) %> /> + <% end %> + + <% if (not s) or (s and not self.unsafeupload) then %> <input class="cbi-input-file" type="file"<%= attr("name", cbid) .. attr("id", cbid) %> /> <% end %> + <input type="text" class="cbi-input-text" onchange="cbi_d_update(this.id)"<%= + attr("name", cbid .. ".textbox") .. attr("id", cbid .. ".textbox") .. attr("value", luci.cbi.AbstractValue.cfgvalue(self, section) or self.default) .. ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder") .. ifattr(self.readonly, "readonly") .. ifattr(self.maxlength, "maxlength") %> /> <%+cbi/valuefooter%> diff --git a/modules/luci-base/luasrc/view/cbi/value.htm b/modules/luci-base/luasrc/view/cbi/value.htm index d1a7bea5c6..c43dab5f4b 100644 --- a/modules/luci-base/luasrc/view/cbi/value.htm +++ b/modules/luci-base/luasrc/view/cbi/value.htm @@ -1,7 +1,8 @@ <%+cbi/valueheader%> <input type="<%=self.password and 'password" class="cbi-input-password' or 'text" class="cbi-input-text' %>" onchange="cbi_d_update(this.id)"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) .. - ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder") + ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder") .. + ifattr(self.readonly, "readonly") .. ifattr(self.maxlength, "maxlength") %> /> <% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %> <% if #self.keylist > 0 or self.datatype then -%> @@ -28,7 +29,7 @@ <%- end -%>'); <%- end %> <% if self.datatype then -%> - cbi_validate_field('<%=cbid%>', <%=tostring((self.optional or self.rmempty) == true)%>, '<%=self.datatype:gsub("'", "\\'")%>'); + cbi_validate_field('<%=cbid%>', <%=tostring((self.optional or self.rmempty) == true)%>, '<%=self.datatype:gsub("\\", "\\\\"):gsub("'", "\\'")%>'); <%- end %> //]]></script> <% end -%> diff --git a/modules/luci-base/luasrc/view/csrftoken.htm b/modules/luci-base/luasrc/view/csrftoken.htm new file mode 100644 index 0000000000..57ac03f3bf --- /dev/null +++ b/modules/luci-base/luasrc/view/csrftoken.htm @@ -0,0 +1,24 @@ +<%# + Copyright 2015 Jo-Philipp Wich <jow@openwrt.org> + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +<h2 name="content"><%:Form token mismatch%></h2> +<br /> + +<p class="alert-message"><%:The submitted security token is invalid or already expired!%></p> + +<p><%: + In order to prevent unauthorized access to the system, your request has + been blocked. Click "Continue »" below to return to the previous page. +%></p> + +<hr /> + +<p class="right"> + <strong><a href="#" onclick="window.history.back();">Continue »</a></strong> +</p> + +<%+footer%> diff --git a/modules/luci-base/luasrc/view/error404.htm b/modules/luci-base/luasrc/view/error404.htm index c2be29ed58..bc74226830 100644 --- a/modules/luci-base/luasrc/view/error404.htm +++ b/modules/luci-base/luasrc/view/error404.htm @@ -5,7 +5,7 @@ -%> <%+header%> -<h2><a id="content" name="content">404 <%:Not Found%></a></h2> +<h2 name="content">404 <%:Not Found%></h2> <p><%:Sorry, the object you requested was not found.%></p> <tt><%:Unable to dispatch%>: <%=luci.http.request.env.PATH_INFO%></tt> <%+footer%> diff --git a/modules/luci-base/luasrc/view/error500.htm b/modules/luci-base/luasrc/view/error500.htm index 8fb18ed076..34a52cda84 100644 --- a/modules/luci-base/luasrc/view/error500.htm +++ b/modules/luci-base/luasrc/view/error500.htm @@ -5,7 +5,7 @@ -%> <%+header%> -<h2><a id="content" name="content">500 <%:Internal Server Error%></a></h2> +<h2 name="content">500 <%:Internal Server Error%></h2> <p><%:Sorry, the server encountered an unexpected error.%></p> <pre class="error500"><%=message%></pre> <%+footer%> diff --git a/modules/luci-base/luasrc/view/sysauth.htm b/modules/luci-base/luasrc/view/sysauth.htm index 7f0f0a622b..e207504911 100644 --- a/modules/luci-base/luasrc/view/sysauth.htm +++ b/modules/luci-base/luasrc/view/sysauth.htm @@ -8,7 +8,7 @@ <form method="post" action="<%=pcdata(luci.http.getenv("REQUEST_URI"))%>"> <div class="cbi-map"> - <h2><a id="content" name="content"><%:Authorization Required%></a></h2> + <h2 name="content"><%:Authorization Required%></h2> <div class="cbi-map-descr"> <%:Please enter your username and password.%> <%- if fuser then %> |