From e75cb4f5ba4030bffb18eec9ca039678b5a508f4 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 26 Apr 2010 22:27:18 +0000 Subject: [PATCH] libs/cbi: drop UVL, introduce server side datatype validation --- libs/cbi/luasrc/cbi.lua | 161 ++------------------------ libs/cbi/luasrc/cbi/datatypes.lua | 186 ++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 150 deletions(-) create mode 100644 libs/cbi/luasrc/cbi/datatypes.lua diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index e687164c25..a18d37d605 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -29,12 +29,12 @@ module("luci.cbi", package.seeall) require("luci.template") local util = require("luci.util") require("luci.http") -require("luci.uvl") --local event = require "luci.sys.event" local fs = require("nixio.fs") local uci = require("luci.model.uci") +local datatypes = require("luci.cbi.datatypes") local class = util.class local instanceof = util.instanceof @@ -150,64 +150,6 @@ function load(cbimap, ...) return maps 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 = class() @@ -317,9 +259,6 @@ function Map.__init__(self, config, ...) if not self.uci:load(self.config) then error("Unable to read UCI data: " .. self.config) end - - self.validator = luci.uvl.UVL() - self.scheme = self.validator:get_scheme(self.config) end function Map.formvalue(self, key) @@ -827,20 +766,6 @@ end -- Appends a new 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 local obj = class(self.map, self, option, ...) self:append(obj) @@ -1060,16 +985,6 @@ function NamedSection.__init__(self, map, section, stype, ...) -- Defaults 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.section = section end @@ -1097,10 +1012,6 @@ function NamedSection.parse(self, novld) AbstractSection.parse_dynamic(self, s) if self.map:submitstate() then Node.parse(self, s) - - if not novld and not self.override_scheme and self.map.scheme then - _uvl_validate_section(self, s) - end end AbstractSection.parse_optionals(self, s) @@ -1122,19 +1033,9 @@ TypedSection = class(AbstractSection) function TypedSection.__init__(self, map, type, ...) AbstractSection.__init__(self, map, type, ...) - self.template = "cbi/tsection" + self.template = "cbi/tsection" self.deps = {} 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 -- Return all matching UCI sections for this TypedSection @@ -1175,10 +1076,6 @@ function TypedSection.parse(self, novld) AbstractSection.parse_dynamic(self, k) if self.map:submitstate() then 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 AbstractSection.parse_optionals(self, k) end @@ -1290,29 +1187,6 @@ function AbstractValue.__init__(self, map, section, option, ...) end 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" end @@ -1447,7 +1321,8 @@ end -- Return the UCI value of this object 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 return nil elseif not self.cast or self.cast == type(value) then @@ -1463,7 +1338,13 @@ end -- Validate the form 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 AbstractValue.transform = AbstractValue.validate @@ -1580,26 +1461,6 @@ function ListValue.__init__(self, ...) self.widget = "select" 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, ...) if luci.util.contains(self.keylist, key) then return diff --git a/libs/cbi/luasrc/cbi/datatypes.lua b/libs/cbi/luasrc/cbi/datatypes.lua new file mode 100644 index 0000000000..097f1d92b3 --- /dev/null +++ b/libs/cbi/luasrc/cbi/datatypes.lua @@ -0,0 +1,186 @@ +--[[ + +LuCI - Configuration Bind Interface - Datatype Tests +(c) 2010 Jo-Philipp Wich + +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 -- 2.30.2