libs/cbi: drop UVL, introduce server side datatype validation
This commit is contained in:
parent
ee36ef4170
commit
e75cb4f5ba
2 changed files with 197 additions and 150 deletions
|
@ -29,12 +29,12 @@ module("luci.cbi", package.seeall)
|
||||||
require("luci.template")
|
require("luci.template")
|
||||||
local util = require("luci.util")
|
local util = require("luci.util")
|
||||||
require("luci.http")
|
require("luci.http")
|
||||||
require("luci.uvl")
|
|
||||||
|
|
||||||
|
|
||||||
--local event = require "luci.sys.event"
|
--local event = require "luci.sys.event"
|
||||||
local fs = require("nixio.fs")
|
local fs = require("nixio.fs")
|
||||||
local uci = require("luci.model.uci")
|
local uci = require("luci.model.uci")
|
||||||
|
local datatypes = require("luci.cbi.datatypes")
|
||||||
local class = util.class
|
local class = util.class
|
||||||
local instanceof = util.instanceof
|
local instanceof = util.instanceof
|
||||||
|
|
||||||
|
@ -150,64 +150,6 @@ function load(cbimap, ...)
|
||||||
return maps
|
return maps
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _uvl_validate_section(node, name)
|
|
||||||
local co = node.map:get()
|
|
||||||
|
|
||||||
luci.uvl.STRICT_UNKNOWN_OPTIONS = false
|
|
||||||
luci.uvl.STRICT_UNKNOWN_SECTIONS = false
|
|
||||||
|
|
||||||
local function tag_fields(e)
|
|
||||||
if e.option and node.fields[e.option] then
|
|
||||||
if node.fields[e.option].error then
|
|
||||||
node.fields[e.option].error[name] = e
|
|
||||||
else
|
|
||||||
node.fields[e.option].error = { [name] = e }
|
|
||||||
end
|
|
||||||
elseif e.childs then
|
|
||||||
for _, c in ipairs(e.childs) do tag_fields(c) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tag_section(e)
|
|
||||||
local s = { }
|
|
||||||
for _, c in ipairs(e.childs or { e }) do
|
|
||||||
if c.childs and not c:is('DEPENDENCY') then
|
|
||||||
table.insert( s, c.childs[1]:string() )
|
|
||||||
else
|
|
||||||
table.insert( s, c:string() )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #s > 0 then
|
|
||||||
if node.error then
|
|
||||||
node.error[name] = s
|
|
||||||
else
|
|
||||||
node.error = { [name] = s }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local stat, err = node.map.validator:validate_section(node.config, name, co)
|
|
||||||
if err then
|
|
||||||
node.map.save = false
|
|
||||||
tag_fields(err)
|
|
||||||
tag_section(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _uvl_strip_remote_dependencies(deps)
|
|
||||||
local clean = {}
|
|
||||||
|
|
||||||
for k, v in pairs(deps) do
|
|
||||||
k = k:gsub("%$config%.%$section%.", "")
|
|
||||||
if k:match("^[%w_]+$") and type(v) == "string" then
|
|
||||||
clean[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return clean
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Node pseudo abstract class
|
-- Node pseudo abstract class
|
||||||
Node = class()
|
Node = class()
|
||||||
|
@ -317,9 +259,6 @@ function Map.__init__(self, config, ...)
|
||||||
if not self.uci:load(self.config) then
|
if not self.uci:load(self.config) then
|
||||||
error("Unable to read UCI data: " .. self.config)
|
error("Unable to read UCI data: " .. self.config)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.validator = luci.uvl.UVL()
|
|
||||||
self.scheme = self.validator:get_scheme(self.config)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Map.formvalue(self, key)
|
function Map.formvalue(self, key)
|
||||||
|
@ -827,20 +766,6 @@ end
|
||||||
|
|
||||||
-- Appends a new option
|
-- Appends a new option
|
||||||
function AbstractSection.option(self, class, option, ...)
|
function AbstractSection.option(self, class, option, ...)
|
||||||
-- Autodetect from UVL
|
|
||||||
if class == true and self.map:get_scheme(self.sectiontype, option) then
|
|
||||||
local vs = self.map:get_scheme(self.sectiontype, option)
|
|
||||||
if vs.type == "boolean" then
|
|
||||||
class = Flag
|
|
||||||
elseif vs.type == "list" then
|
|
||||||
class = DynamicList
|
|
||||||
elseif vs.type == "enum" or vs.type == "reference" then
|
|
||||||
class = ListValue
|
|
||||||
else
|
|
||||||
class = Value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if instanceof(class, AbstractValue) then
|
if instanceof(class, AbstractValue) then
|
||||||
local obj = class(self.map, self, option, ...)
|
local obj = class(self.map, self, option, ...)
|
||||||
self:append(obj)
|
self:append(obj)
|
||||||
|
@ -1060,16 +985,6 @@ function NamedSection.__init__(self, map, section, stype, ...)
|
||||||
|
|
||||||
-- Defaults
|
-- Defaults
|
||||||
self.addremove = false
|
self.addremove = false
|
||||||
|
|
||||||
-- Use defaults from UVL
|
|
||||||
if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
|
|
||||||
local vs = self.map:get_scheme(self.sectiontype)
|
|
||||||
self.addremove = not vs.unique and not vs.required
|
|
||||||
self.dynamic = vs.dynamic
|
|
||||||
self.title = self.title or vs.title
|
|
||||||
self.description = self.description or vs.descr
|
|
||||||
end
|
|
||||||
|
|
||||||
self.template = "cbi/nsection"
|
self.template = "cbi/nsection"
|
||||||
self.section = section
|
self.section = section
|
||||||
end
|
end
|
||||||
|
@ -1097,10 +1012,6 @@ function NamedSection.parse(self, novld)
|
||||||
AbstractSection.parse_dynamic(self, s)
|
AbstractSection.parse_dynamic(self, s)
|
||||||
if self.map:submitstate() then
|
if self.map:submitstate() then
|
||||||
Node.parse(self, s)
|
Node.parse(self, s)
|
||||||
|
|
||||||
if not novld and not self.override_scheme and self.map.scheme then
|
|
||||||
_uvl_validate_section(self, s)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
AbstractSection.parse_optionals(self, s)
|
AbstractSection.parse_optionals(self, s)
|
||||||
|
|
||||||
|
@ -1122,19 +1033,9 @@ TypedSection = class(AbstractSection)
|
||||||
function TypedSection.__init__(self, map, type, ...)
|
function TypedSection.__init__(self, map, type, ...)
|
||||||
AbstractSection.__init__(self, map, type, ...)
|
AbstractSection.__init__(self, map, type, ...)
|
||||||
|
|
||||||
self.template = "cbi/tsection"
|
self.template = "cbi/tsection"
|
||||||
self.deps = {}
|
self.deps = {}
|
||||||
self.anonymous = false
|
self.anonymous = false
|
||||||
|
|
||||||
-- Use defaults from UVL
|
|
||||||
if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
|
|
||||||
local vs = self.map:get_scheme(self.sectiontype)
|
|
||||||
self.addremove = not vs.unique and not vs.required
|
|
||||||
self.dynamic = vs.dynamic
|
|
||||||
self.anonymous = not vs.named
|
|
||||||
self.title = self.title or vs.title
|
|
||||||
self.description = self.description or vs.descr
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Return all matching UCI sections for this TypedSection
|
-- Return all matching UCI sections for this TypedSection
|
||||||
|
@ -1175,10 +1076,6 @@ function TypedSection.parse(self, novld)
|
||||||
AbstractSection.parse_dynamic(self, k)
|
AbstractSection.parse_dynamic(self, k)
|
||||||
if self.map:submitstate() then
|
if self.map:submitstate() then
|
||||||
Node.parse(self, k, novld)
|
Node.parse(self, k, novld)
|
||||||
|
|
||||||
if not novld and not self.override_scheme and self.map.scheme then
|
|
||||||
_uvl_validate_section(self, k)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
AbstractSection.parse_optionals(self, k)
|
AbstractSection.parse_optionals(self, k)
|
||||||
end
|
end
|
||||||
|
@ -1290,29 +1187,6 @@ function AbstractValue.__init__(self, map, section, option, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function AbstractValue.prepare(self)
|
function AbstractValue.prepare(self)
|
||||||
-- Use defaults from UVL
|
|
||||||
if not self.override_scheme
|
|
||||||
and self.map:get_scheme(self.section.sectiontype, self.option) then
|
|
||||||
local vs = self.map:get_scheme(self.section.sectiontype, self.option)
|
|
||||||
if self.cast == nil then
|
|
||||||
self.cast = (vs.type == "list") and "list" or "string"
|
|
||||||
end
|
|
||||||
self.title = self.title or vs.title
|
|
||||||
self.description = self.description or vs.descr
|
|
||||||
if self.default == nil then
|
|
||||||
self.default = vs.default
|
|
||||||
end
|
|
||||||
|
|
||||||
if vs.depends and not self.override_dependencies then
|
|
||||||
for i, deps in ipairs(vs.depends) do
|
|
||||||
deps = _uvl_strip_remote_dependencies(deps)
|
|
||||||
if next(deps) then
|
|
||||||
self:depends(deps)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.cast = self.cast or "string"
|
self.cast = self.cast or "string"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1447,7 +1321,8 @@ end
|
||||||
|
|
||||||
-- Return the UCI value of this object
|
-- Return the UCI value of this object
|
||||||
function AbstractValue.cfgvalue(self, section)
|
function AbstractValue.cfgvalue(self, section)
|
||||||
local value = self.map:get(section, self.option)
|
local value = (self.error and self.error[section] == "invalid")
|
||||||
|
and self:formvalue(section) or self.map:get(section, self.option)
|
||||||
if not value then
|
if not value then
|
||||||
return nil
|
return nil
|
||||||
elseif not self.cast or self.cast == type(value) then
|
elseif not self.cast or self.cast == type(value) then
|
||||||
|
@ -1463,7 +1338,13 @@ end
|
||||||
|
|
||||||
-- Validate the form value
|
-- Validate the form value
|
||||||
function AbstractValue.validate(self, value)
|
function AbstractValue.validate(self, value)
|
||||||
return value
|
if self.datatype and value and datatypes[self.datatype] then
|
||||||
|
if datatypes[self.datatype](value) then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
AbstractValue.transform = AbstractValue.validate
|
AbstractValue.transform = AbstractValue.validate
|
||||||
|
@ -1580,26 +1461,6 @@ function ListValue.__init__(self, ...)
|
||||||
self.widget = "select"
|
self.widget = "select"
|
||||||
end
|
end
|
||||||
|
|
||||||
function ListValue.prepare(self, ...)
|
|
||||||
AbstractValue.prepare(self, ...)
|
|
||||||
if not self.override_scheme
|
|
||||||
and self.map:get_scheme(self.section.sectiontype, self.option) then
|
|
||||||
local vs = self.map:get_scheme(self.section.sectiontype, self.option)
|
|
||||||
if self.value and vs.valuelist and not self.override_values then
|
|
||||||
for k, v in ipairs(vs.valuelist) do
|
|
||||||
local deps = {}
|
|
||||||
if not self.override_dependencies
|
|
||||||
and vs.enum_depends and vs.enum_depends[v.value] then
|
|
||||||
for i, dep in ipairs(vs.enum_depends[v.value]) do
|
|
||||||
table.insert(deps, _uvl_strip_remote_dependencies(dep))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:value(v.value, v.title or v.value, unpack(deps))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListValue.value(self, key, val, ...)
|
function ListValue.value(self, key, val, ...)
|
||||||
if luci.util.contains(self.keylist, key) then
|
if luci.util.contains(self.keylist, key) then
|
||||||
return
|
return
|
||||||
|
|
186
libs/cbi/luasrc/cbi/datatypes.lua
Normal file
186
libs/cbi/luasrc/cbi/datatypes.lua
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
--[[
|
||||||
|
|
||||||
|
LuCI - Configuration Bind Interface - Datatype Tests
|
||||||
|
(c) 2010 Jo-Philipp Wich <xm@subsignal.org>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local ip = require "luci.ip"
|
||||||
|
local math = require "math"
|
||||||
|
local util = require "luci.util"
|
||||||
|
|
||||||
|
local tonumber = tonumber
|
||||||
|
|
||||||
|
module "luci.cbi.datatypes"
|
||||||
|
|
||||||
|
|
||||||
|
function bool( val )
|
||||||
|
if val == "1" or val == "yes" or val == "on" or val == "true" then
|
||||||
|
return true
|
||||||
|
elseif val == "0" or val == "no" or val == "off" or val == "false" then
|
||||||
|
return true
|
||||||
|
elseif val == "" or val == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function uint( val )
|
||||||
|
local n = tonumber(val)
|
||||||
|
if n ~= nil and math.floor(n) == n and n >= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function int( val )
|
||||||
|
local n = tonumber(val)
|
||||||
|
if n ~= nil and math.floor(n) == n then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function float( val )
|
||||||
|
return ( tonumber(val) ~= nil )
|
||||||
|
end
|
||||||
|
|
||||||
|
function ipaddr( val )
|
||||||
|
return ip4addr(val) or ip6addr(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip4addr( val )
|
||||||
|
if val then
|
||||||
|
return ip.IPv4(val) and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip4prefix( val )
|
||||||
|
val = tonumber(val)
|
||||||
|
return ( val and val >= 0 and val <= 32 )
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip6addr( val )
|
||||||
|
if val then
|
||||||
|
return ip.IPv6(val) and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip6prefix( val )
|
||||||
|
val = tonumber(val)
|
||||||
|
return ( val and val >= 0 and val <= 128 )
|
||||||
|
end
|
||||||
|
|
||||||
|
function port( val )
|
||||||
|
val = tonumber(val)
|
||||||
|
return ( val and val >= 1 and val <= 65535 )
|
||||||
|
end
|
||||||
|
|
||||||
|
function portrange( val )
|
||||||
|
local p1, p2 = val:match("^(%d+)%-(%d+)$")
|
||||||
|
if p1 and p2 and port(p1) and port(p2) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return port(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function macaddr( val )
|
||||||
|
if val and val:match(
|
||||||
|
"^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
|
||||||
|
"[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
|
||||||
|
) then
|
||||||
|
local parts = util.split( val, ":" )
|
||||||
|
|
||||||
|
for i = 1,6 do
|
||||||
|
parts[i] = tonumber( parts[i], 16 )
|
||||||
|
if parts[i] < 0 or parts[i] > 255 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function hostname( val )
|
||||||
|
if val and val:match("[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*") then
|
||||||
|
return true -- XXX: ToDo: need better solution
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function host( val )
|
||||||
|
return hostname(val) or ipaddr(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string( val )
|
||||||
|
return true -- Everything qualifies as valid string
|
||||||
|
end
|
||||||
|
|
||||||
|
function directory( val, seen )
|
||||||
|
local s = fs.stat( val )
|
||||||
|
seen = seen or { }
|
||||||
|
|
||||||
|
if s and not seen[s.ino] then
|
||||||
|
seen[s.ino] = true
|
||||||
|
if s.type == "dir" then
|
||||||
|
return true
|
||||||
|
elseif s.type == "lnk" then
|
||||||
|
return directory( fs.readlink(val), seen )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function file( val, seen )
|
||||||
|
local s = fs.stat( val )
|
||||||
|
seen = seen or { }
|
||||||
|
|
||||||
|
if s and not seen[s.ino] then
|
||||||
|
seen[s.ino] = true
|
||||||
|
if s.type == "reg" then
|
||||||
|
return true
|
||||||
|
elseif s.type == "lnk" then
|
||||||
|
return file( fs.readlink(val), seen )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function device( val, seen )
|
||||||
|
local s = fs.stat( val )
|
||||||
|
seen = seen or { }
|
||||||
|
|
||||||
|
if s and not seen[s.ino] then
|
||||||
|
seen[s.ino] = true
|
||||||
|
if s.type == "chr" or s.type == "blk" then
|
||||||
|
return true
|
||||||
|
elseif s.type == "lnk" then
|
||||||
|
return device( fs.readlink(val), seen )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
Loading…
Reference in a new issue