1 -- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
2 -- Copyright 2017 Dan Luedtke <mail@danrl.com>
3 -- Licensed to the public under the Apache License 2.0.
5 local fs = require "nixio.fs"
6 local ip = require "luci.ip"
7 local math = require "math"
8 local util = require "luci.util"
9 local tonumber, tostring, type, unpack, select = tonumber, tostring, type, unpack, select
12 module "luci.cbi.datatypes"
15 _M['or'] = function(v, ...)
16 local i, n = 1, select('#', ...)
18 local f = select(i, ...)
19 if type(f) ~= "function" then
22 if type(f) == "number" then
30 local a = select(i-1, ...)
31 if f(v, unpack(a)) then
39 _M['and'] = function(v, ...)
40 local i, n = 1, select('#', ...)
42 local f = select(i, ...)
43 if type(f) ~= "function" then
46 if type(f) == "number" then
55 local a = select(i-1, ...)
56 if not f(v, unpack(a)) then
65 return _M['or'](v:gsub("^%s*!%s*", ""), ...)
68 function list(v, subvalidator, subargs)
69 if type(subvalidator) ~= "function" then
73 for token in v:gmatch("%S+") do
74 if not subvalidator(token, unpack(subargs)) then
82 if val == "1" or val == "yes" or val == "on" or val == "true" then
84 elseif val == "0" or val == "no" or val == "off" or val == "false" then
86 elseif val == "" or val == nil then
93 function uinteger(val)
94 local n = tonumber(val)
95 if n ~= nil and math.floor(n) == n and n >= 0 then
102 function integer(val)
103 local n = tonumber(val)
104 if n ~= nil and math.floor(n) == n then
112 local n = tonumber(val)
113 return ( n ~= nil and n >= 0 )
117 return ( tonumber(val) ~= nil )
121 return ip4addr(val) or ip6addr(val)
124 function ip4addr(val)
126 return ip.IPv4(val) and true or false
132 function ip4prefix(val)
134 return ( val and val >= 0 and val <= 32 )
137 function ip6addr(val)
139 return ip.IPv6(val) and true or false
145 function ip6prefix(val)
147 return ( val and val >= 0 and val <= 128 )
151 return cidr4(val) or cidr6(val)
155 local ip, mask = val:match("^([^/]+)/([^/]+)$")
157 return ip4addr(ip) and ip4prefix(mask)
161 local ip, mask = val:match("^([^/]+)/([^/]+)$")
163 return ip6addr(ip) and ip6prefix(mask)
167 local ip, mask = val:match("^([^/]+)/([^/]+)$")
169 return ip4addr(ip) and ip4addr(mask)
173 local ip, mask = val:match("^([^/]+)/([^/]+)$")
175 return ip6addr(ip) and ip6addr(mask)
179 return ipmask4(val) or ipmask6(val)
182 function ipmask4(val)
183 return cidr4(val) or ipnet4(val) or ip4addr(val)
186 function ipmask6(val)
187 return cidr6(val) or ipnet6(val) or ip6addr(val)
190 function ip6hostid(val)
191 if val == "eui64" or val == "random" then
194 local addr = ip.IPv6(val)
195 if addr and addr:prefix() == 128 and addr:lower("::1:0:0:0:0") then
205 return ( val and val >= 0 and val <= 65535 )
208 function portrange(val)
209 local p1, p2 = val:match("^(%d+)%-(%d+)$")
210 if p1 and p2 and port(p1) and port(p2) then
217 function macaddr(val)
218 return ip.checkmac(val) and true or false
221 function hostname(val, strict)
222 if val and (#val < 254) and (
223 val:match("^[a-zA-Z_]+$") or
224 (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
225 val:match("[^0-9%.]"))
227 return (not strict or not val:match("^_"))
232 function host(val, ipv4only)
233 return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
236 function network(val)
237 return uciname(val) or host(val)
240 function hostport(val, ipv4only)
241 local h, p = val:match("^([^:]+):([^:]+)$")
242 return not not (h and p and host(h, ipv4only) and port(p))
245 function ip4addrport(val, bracket)
246 local h, p = val:match("^([^:]+):([^:]+)$")
247 return (h and p and ip4addr(h) and port(p))
250 function ip4addrport(val)
251 local h, p = val:match("^([^:]+):([^:]+)$")
252 return (h and p and ip4addr(h) and port(p))
255 function ipaddrport(val, bracket)
256 local h, p = val:match("^([^%[%]:]+):([^:]+)$")
257 if (h and p and ip4addr(h) and port(p)) then
259 elseif (bracket == 1) then
260 h, p = val:match("^%[(.+)%]:([^:]+)$")
261 if (h and p and ip6addr(h) and port(p)) then
265 h, p = val:match("^([^%[%]]+):([^:]+)$")
266 return (h and p and ip6addr(h) and port(p))
271 return (val:match("^[a-fA-F0-9]+$") ~= nil)
273 return (#val >= 8) and (#val <= 63)
278 if val:sub(1, 2) == "s:" then
282 if (#val == 10) or (#val == 26) then
283 return (val:match("^[a-fA-F0-9]+$") ~= nil)
285 return (#val == 5) or (#val == 13)
289 function hexstring(val)
291 return (val:match("^[a-fA-F0-9]+$") ~= nil)
296 function hex(val, maxbytes)
297 maxbytes = tonumber(maxbytes)
298 if val and maxbytes ~= nil then
299 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
306 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
312 return true -- Everything qualifies as valid string
315 function directory(val, seen)
316 local s = fs.stat(val)
319 if s and not seen[s.ino] then
321 if s.type == "dir" then
323 elseif s.type == "lnk" then
324 return directory( fs.readlink(val), seen )
331 function file(val, seen)
332 local s = fs.stat(val)
335 if s and not seen[s.ino] then
337 if s.type == "reg" then
339 elseif s.type == "lnk" then
340 return file( fs.readlink(val), seen )
347 function device(val, seen)
348 local s = fs.stat(val)
351 if s and not seen[s.ino] then
353 if s.type == "chr" or s.type == "blk" then
355 elseif s.type == "lnk" then
356 return device( fs.readlink(val), seen )
363 function uciname(val)
364 return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
367 function range(val, min, max)
372 if val ~= nil and min ~= nil and max ~= nil then
373 return ((val >= min) and (val <= max))
379 function min(val, min)
383 if val ~= nil and min ~= nil then
390 function max(val, max)
394 if val ~= nil and max ~= nil then
401 function rangelength(val, min, max)
406 if val ~= nil and min ~= nil and max ~= nil then
407 return ((#val >= min) and (#val <= max))
413 function minlength(val, min)
417 if val ~= nil and min ~= nil then
424 function maxlength(val, max)
428 if val ~= nil and max ~= nil then
435 function phonedigit(val)
436 return (val:match("^[0-9%*#!%.]+$") ~= nil)
439 function timehhmmss(val)
440 return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
443 function dateyyyymmdd(val)
445 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
446 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
449 year = tonumber(yearstr)
450 month = tonumber(monthstr)
451 day = tonumber(daystr)
452 if (year == nil) or (month == nil) or (day == nil) then
456 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
458 local function is_leap_year(year)
459 return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
462 function get_days_in_month(month, year)
463 if (month == 2) and is_leap_year(year) then
466 return days_in_month[month]
469 if (year < 2015) then
472 if ((month == 0) or (month > 12)) then
475 if ((day == 0) or (day > get_days_in_month(month, year))) then