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 if val and val:match(
200 "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
201 "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
203 local parts = util.split( val, ":" )
206 parts[i] = tonumber( parts[i], 16 )
207 if parts[i] < 0 or parts[i] > 255 then
218 function hostname(val)
219 if val and (#val < 254) and (
220 val:match("^[a-zA-Z_]+$") or
221 (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
222 val:match("[^0-9%.]"))
229 function host(val, ipv4only)
230 return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
233 function network(val)
234 return uciname(val) or host(val)
237 function hostport(val, ipv4only)
238 local h, p = val:match("^([^:]+):([^:]+)$")
239 return not not (h and p and host(h, ipv4only) and port(p))
242 function ip4addrport(val, bracket)
243 local h, p = val:match("^([^:]+):([^:]+)$")
244 return (h and p and ip4addr(h) and port(p))
247 function ip4addrport(val)
248 local h, p = val:match("^([^:]+):([^:]+)$")
249 return (h and p and ip4addr(h) and port(p))
252 function ipaddrport(val, bracket)
253 local h, p = val:match("^([^%[%]:]+):([^:]+)$")
254 if (h and p and ip4addr(h) and port(p)) then
256 elseif (bracket == 1) then
257 h, p = val:match("^%[(.+)%]:([^:]+)$")
258 if (h and p and ip6addr(h) and port(p)) then
262 h, p = val:match("^([^%[%]]+):([^:]+)$")
263 return (h and p and ip6addr(h) and port(p))
268 return (val:match("^[a-fA-F0-9]+$") ~= nil)
270 return (#val >= 8) and (#val <= 63)
275 if val:sub(1, 2) == "s:" then
279 if (#val == 10) or (#val == 26) then
280 return (val:match("^[a-fA-F0-9]+$") ~= nil)
282 return (#val == 5) or (#val == 13)
286 function hexstring(val)
288 return (val:match("^[a-fA-F0-9]+$") ~= nil)
293 function hex(val, maxbytes)
294 maxbytes = tonumber(maxbytes)
295 if val and maxbytes ~= nil then
296 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
303 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
309 return true -- Everything qualifies as valid string
312 function directory(val, seen)
313 local s = fs.stat(val)
316 if s and not seen[s.ino] then
318 if s.type == "dir" then
320 elseif s.type == "lnk" then
321 return directory( fs.readlink(val), seen )
328 function file(val, seen)
329 local s = fs.stat(val)
332 if s and not seen[s.ino] then
334 if s.type == "reg" then
336 elseif s.type == "lnk" then
337 return file( fs.readlink(val), seen )
344 function device(val, seen)
345 local s = fs.stat(val)
348 if s and not seen[s.ino] then
350 if s.type == "chr" or s.type == "blk" then
352 elseif s.type == "lnk" then
353 return device( fs.readlink(val), seen )
360 function uciname(val)
361 return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
364 function range(val, min, max)
369 if val ~= nil and min ~= nil and max ~= nil then
370 return ((val >= min) and (val <= max))
376 function min(val, min)
380 if val ~= nil and min ~= nil then
387 function max(val, max)
391 if val ~= nil and max ~= nil then
398 function rangelength(val, min, max)
403 if val ~= nil and min ~= nil and max ~= nil then
404 return ((#val >= min) and (#val <= max))
410 function minlength(val, min)
414 if val ~= nil and min ~= nil then
421 function maxlength(val, max)
425 if val ~= nil and max ~= nil then
432 function phonedigit(val)
433 return (val:match("^[0-9\*#!%.]+$") ~= nil)
436 function timehhmmss(val)
437 return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
440 function dateyyyymmdd(val)
442 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
443 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
446 year = tonumber(yearstr)
447 month = tonumber(monthstr)
448 day = tonumber(daystr)
449 if (year == nil) or (month == nil) or (day == nil) then
453 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
455 local function is_leap_year(year)
456 return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
459 function get_days_in_month(month, year)
460 if (month == 2) and is_leap_year(year) then
463 return days_in_month[month]
466 if (year < 2015) then
469 if ((month == 0) or (month > 12)) then
472 if ((day == 0) or (day > get_days_in_month(month, year))) then