libs/cbi: drop UVL, introduce server side datatype validation
authorJo-Philipp Wich <jow@openwrt.org>
Mon, 26 Apr 2010 22:27:18 +0000 (22:27 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Mon, 26 Apr 2010 22:27:18 +0000 (22:27 +0000)
libs/cbi/luasrc/cbi.lua
libs/cbi/luasrc/cbi/datatypes.lua [new file with mode: 0644]

index e687164..a18d37d 100644 (file)
@@ -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 (file)
index 0000000..097f1d9
--- /dev/null
@@ -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