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 return cidr4(val) or cidr6(val)
140 local ip, mask = val:match("^([^/]+)/([^/]+)$")
142 return ip4addr(ip) and ip4prefix(mask)
146 local ip, mask = val:match("^([^/]+)/([^/]+)$")
148 return ip6addr(ip) and ip6prefix(mask)
152 local ip, mask = val:match("^([^/]+)/([^/]+)$")
154 return ip4addr(ip) and ip4addr(mask)
158 local ip, mask = val:match("^([^/]+)/([^/]+)$")
160 return ip6addr(ip) and ip6addr(mask)
164 return ipmask4(val) or ipmask6(val)
167 function ipmask4(val)
168 return cidr4(val) or ipnet4(val) or ip4addr(val)
171 function ipmask6(val)
172 return cidr6(val) or ipnet6(val) or ip6addr(val)
175 function ip6hostid(val)
176 if val == "eui64" or val == "random" then
179 local addr = ip.IPv6(val)
180 if addr and addr:prefix() == 128 and addr:lower("::1:0:0:0:0") then
190 return ( val and val >= 0 and val <= 65535 )
193 function portrange(val)
194 local p1, p2 = val:match("^(%d+)%-(%d+)$")
195 if p1 and p2 and port(p1) and port(p2) then
202 function macaddr(val)
203 return ip.checkmac(val) and true or false
206 function hostname(val, strict)
207 if val and (#val < 254) and (
208 val:match("^[a-zA-Z_]+$") or
209 (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
210 val:match("[^0-9%.]"))
212 return (not strict or not val:match("^_"))
217 function host(val, ipv4only)
218 return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
221 function network(val)
222 return uciname(val) or host(val)
225 function hostport(val, ipv4only)
226 local h, p = val:match("^([^:]+):([^:]+)$")
227 return not not (h and p and host(h, ipv4only) and port(p))
230 function ip4addrport(val, bracket)
231 local h, p = val:match("^([^:]+):([^:]+)$")
232 return (h and p and ip4addr(h) and port(p))
235 function ip4addrport(val)
236 local h, p = val:match("^([^:]+):([^:]+)$")
237 return (h and p and ip4addr(h) and port(p))
240 function ipaddrport(val, bracket)
241 local h, p = val:match("^([^%[%]:]+):([^:]+)$")
242 if (h and p and ip4addr(h) and port(p)) then
244 elseif (bracket == 1) then
245 h, p = val:match("^%[(.+)%]:([^:]+)$")
246 if (h and p and ip6addr(h) and port(p)) then
250 h, p = val:match("^([^%[%]]+):([^:]+)$")
251 return (h and p and ip6addr(h) and port(p))
256 return (val:match("^[a-fA-F0-9]+$") ~= nil)
258 return (#val >= 8) and (#val <= 63)
263 if val:sub(1, 2) == "s:" then
267 if (#val == 10) or (#val == 26) then
268 return (val:match("^[a-fA-F0-9]+$") ~= nil)
270 return (#val == 5) or (#val == 13)
274 function hexstring(val)
276 return (val:match("^[a-fA-F0-9]+$") ~= nil)
281 function hex(val, maxbytes)
282 maxbytes = tonumber(maxbytes)
283 if val and maxbytes ~= nil then
284 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
291 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
297 return true -- Everything qualifies as valid string
300 function directory(val, seen)
301 local s = fs.stat(val)
304 if s and not seen[s.ino] then
306 if s.type == "dir" then
308 elseif s.type == "lnk" then
309 return directory( fs.readlink(val), seen )
316 function file(val, seen)
317 local s = fs.stat(val)
320 if s and not seen[s.ino] then
322 if s.type == "reg" then
324 elseif s.type == "lnk" then
325 return file( fs.readlink(val), seen )
332 function device(val, seen)
333 local s = fs.stat(val)
336 if s and not seen[s.ino] then
338 if s.type == "chr" or s.type == "blk" then
340 elseif s.type == "lnk" then
341 return device( fs.readlink(val), seen )
348 function uciname(val)
349 return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
352 function range(val, min, max)
357 if val ~= nil and min ~= nil and max ~= nil then
358 return ((val >= min) and (val <= max))
364 function min(val, min)
368 if val ~= nil and min ~= nil then
375 function max(val, max)
379 if val ~= nil and max ~= nil then
386 function rangelength(val, min, max)
391 if val ~= nil and min ~= nil and max ~= nil then
392 return ((#val >= min) and (#val <= max))
398 function minlength(val, min)
402 if val ~= nil and min ~= nil then
409 function maxlength(val, max)
413 if val ~= nil and max ~= nil then
420 function phonedigit(val)
421 return (val:match("^[0-9%*#!%.]+$") ~= nil)
424 function timehhmmss(val)
425 return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
428 function dateyyyymmdd(val)
430 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
431 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
434 year = tonumber(yearstr)
435 month = tonumber(monthstr)
436 day = tonumber(daystr)
437 if (year == nil) or (month == nil) or (day == nil) then
441 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
443 local function is_leap_year(year)
444 return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
447 function get_days_in_month(month, year)
448 if (month == 2) and is_leap_year(year) then
451 return days_in_month[month]
454 if (year < 2015) then
457 if ((month == 0) or (month > 12)) then
460 if ((day == 0) or (day > get_days_in_month(month, year))) then