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, ...)
17 for i = 1, select('#', ...), 2 do
18 local f = select(i, ...)
19 local a = select(i+1, ...)
20 if type(f) ~= "function" then
25 elseif f(v, unpack(a)) then
32 _M['and'] = function(v, ...)
34 for i = 1, select('#', ...), 2 do
35 local f = select(i, ...)
36 local a = select(i+1, ...)
37 if type(f) ~= "function" then
42 elseif not f(v, unpack(a)) then
50 return _M['or'](v:gsub("^%s*!%s*", ""), ...)
53 function list(v, subvalidator, subargs)
54 if type(subvalidator) ~= "function" then
58 for token in v:gmatch("%S+") do
59 if not subvalidator(token, unpack(subargs)) then
67 if val == "1" or val == "yes" or val == "on" or val == "true" then
69 elseif val == "0" or val == "no" or val == "off" or val == "false" then
71 elseif val == "" or val == nil then
78 function uinteger(val)
79 local n = tonumber(val)
80 if n ~= nil and math.floor(n) == n and n >= 0 then
88 local n = tonumber(val)
89 if n ~= nil and math.floor(n) == n then
97 local n = tonumber(val)
98 return ( n ~= nil and n >= 0 )
102 return ( tonumber(val) ~= nil )
106 return ip4addr(val) or ip6addr(val)
109 function ip4addr(val)
111 return ip.IPv4(val) and true or false
117 function ip4prefix(val)
119 return ( val and val >= 0 and val <= 32 )
122 function ip6addr(val)
124 return ip.IPv6(val) and true or false
130 function ip6prefix(val)
132 return ( val and val >= 0 and val <= 128 )
136 local ip, mask = val:match("^([^/]+)/([^/]+)$")
138 return ip4addr(ip) and ip4prefix(mask)
142 local ip, mask = val:match("^([^/]+)/([^/]+)$")
144 return ip6addr(ip) and ip6prefix(mask)
148 local ip, mask = val:match("^([^/]+)/([^/]+)$")
150 return ip4addr(ip) and ip4addr(mask)
154 local ip, mask = val:match("^([^/]+)/([^/]+)$")
156 return ip6addr(ip) and ip6addr(mask)
160 return ipmask4(val) or ipmask6(val)
163 function ipmask4(val)
164 return cidr4(val) or ipnet4(val) or ip4addr(val)
167 function ipmask6(val)
168 return cidr6(val) or ipnet6(val) or ip6addr(val)
171 function ip6hostid(val)
172 if val == "eui64" or val == "random" then
175 local addr = ip.IPv6(val)
176 if addr and addr:prefix() == 128 and addr:lower("::1:0:0:0:0") then
186 return ( val and val >= 0 and val <= 65535 )
189 function portrange(val)
190 local p1, p2 = val:match("^(%d+)%-(%d+)$")
191 if p1 and p2 and port(p1) and port(p2) then
198 function macaddr(val)
199 return ip.checkmac(val) and true or false
202 function hostname(val, strict)
203 if val and (#val < 254) and (
204 val:match("^[a-zA-Z_]+$") or
205 (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
206 val:match("[^0-9%.]"))
208 return (not strict or not val:match("^_"))
213 function host(val, ipv4only)
214 return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
217 function network(val)
218 return uciname(val) or host(val)
221 function hostport(val, ipv4only)
222 local h, p = val:match("^([^:]+):([^:]+)$")
223 return not not (h and p and host(h, ipv4only) and port(p))
226 function ip4addrport(val, bracket)
227 local h, p = val:match("^([^:]+):([^:]+)$")
228 return (h and p and ip4addr(h) and port(p))
231 function ip4addrport(val)
232 local h, p = val:match("^([^:]+):([^:]+)$")
233 return (h and p and ip4addr(h) and port(p))
236 function ipaddrport(val, bracket)
237 local h, p = val:match("^([^%[%]:]+):([^:]+)$")
238 if (h and p and ip4addr(h) and port(p)) then
240 elseif (bracket == 1) then
241 h, p = val:match("^%[(.+)%]:([^:]+)$")
242 if (h and p and ip6addr(h) and port(p)) then
246 h, p = val:match("^([^%[%]]+):([^:]+)$")
247 return (h and p and ip6addr(h) and port(p))
252 return (val:match("^[a-fA-F0-9]+$") ~= nil)
254 return (#val >= 8) and (#val <= 63)
259 if val:sub(1, 2) == "s:" then
263 if (#val == 10) or (#val == 26) then
264 return (val:match("^[a-fA-F0-9]+$") ~= nil)
266 return (#val == 5) or (#val == 13)
270 function hexstring(val)
272 return (val:match("^[a-fA-F0-9]+$") ~= nil)
277 function hex(val, maxbytes)
278 maxbytes = tonumber(maxbytes)
279 if val and maxbytes ~= nil then
280 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
287 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
293 return true -- Everything qualifies as valid string
296 function directory(val, seen)
297 local s = fs.stat(val)
300 if s and not seen[s.ino] then
302 if s.type == "dir" then
304 elseif s.type == "lnk" then
305 return directory( fs.readlink(val), seen )
312 function file(val, seen)
313 local s = fs.stat(val)
316 if s and not seen[s.ino] then
318 if s.type == "reg" then
320 elseif s.type == "lnk" then
321 return file( fs.readlink(val), seen )
328 function device(val, seen)
329 local s = fs.stat(val)
332 if s and not seen[s.ino] then
334 if s.type == "chr" or s.type == "blk" then
336 elseif s.type == "lnk" then
337 return device( fs.readlink(val), seen )
344 function uciname(val)
345 return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
348 function range(val, min, max)
353 if val ~= nil and min ~= nil and max ~= nil then
354 return ((val >= min) and (val <= max))
360 function min(val, min)
364 if val ~= nil and min ~= nil then
371 function max(val, max)
375 if val ~= nil and max ~= nil then
382 function rangelength(val, min, max)
387 if val ~= nil and min ~= nil and max ~= nil then
388 return ((#val >= min) and (#val <= max))
394 function minlength(val, min)
398 if val ~= nil and min ~= nil then
405 function maxlength(val, max)
409 if val ~= nil and max ~= nil then
416 function phonedigit(val)
417 return (val:match("^[0-9\*#!%.]+$") ~= nil)
420 function timehhmmss(val)
421 return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
424 function dateyyyymmdd(val)
426 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
427 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
430 year = tonumber(yearstr)
431 month = tonumber(monthstr)
432 day = tonumber(daystr)
433 if (year == nil) or (month == nil) or (day == nil) then
437 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
439 local function is_leap_year(year)
440 return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
443 function get_days_in_month(month, year)
444 if (month == 2) and is_leap_year(year) then
447 return days_in_month[month]
450 if (year < 2015) then
453 if ((month == 0) or (month > 12)) then
456 if ((day == 0) or (day > get_days_in_month(month, year))) then