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 and val:match("^[a-fA-F0-9:]+$") and (#val > 2) then
173 return (ip6addr("2001:db8:0:0" .. val) or ip6addr("2001:db8:0:0:" .. val))
181 return ( val and val >= 0 and val <= 65535 )
184 function portrange(val)
185 local p1, p2 = val:match("^(%d+)%-(%d+)$")
186 if p1 and p2 and port(p1) and port(p2) then
193 function macaddr(val)
194 if val and val:match(
195 "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
196 "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
198 local parts = util.split( val, ":" )
201 parts[i] = tonumber( parts[i], 16 )
202 if parts[i] < 0 or parts[i] > 255 then
213 function hostname(val)
214 if val and (#val < 254) and (
215 val:match("^[a-zA-Z_]+$") or
216 (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
217 val:match("[^0-9%.]"))
224 function host(val, ipv4only)
225 return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
228 function network(val)
229 return uciname(val) or host(val)
232 function hostport(val, ipv4only)
233 local h, p = val:match("^([^:]+):([^:]+)$")
234 return not not (h and p and host(h, ipv4only) and port(p))
237 function ip4addrport(val, bracket)
238 local h, p = val:match("^([^:]+):([^:]+)$")
239 return (h and p and ip4addr(h) and port(p))
242 function ip4addrport(val)
243 local h, p = val:match("^([^:]+):([^:]+)$")
244 return (h and p and ip4addr(h) and port(p))
247 function ipaddrport(val, bracket)
248 local h, p = val:match("^([^%[%]:]+):([^:]+)$")
249 if (h and p and ip4addr(h) and port(p)) then
251 elseif (bracket == 1) then
252 h, p = val:match("^%[(.+)%]:([^:]+)$")
253 if (h and p and ip6addr(h) and port(p)) then
257 h, p = val:match("^([^%[%]]+):([^:]+)$")
258 return (h and p and ip6addr(h) and port(p))
263 return (val:match("^[a-fA-F0-9]+$") ~= nil)
265 return (#val >= 8) and (#val <= 63)
270 if val:sub(1, 2) == "s:" then
274 if (#val == 10) or (#val == 26) then
275 return (val:match("^[a-fA-F0-9]+$") ~= nil)
277 return (#val == 5) or (#val == 13)
281 function hexstring(val)
283 return (val:match("^[a-fA-F0-9]+$") ~= nil)
288 function hex(val, maxbytes)
289 maxbytes = tonumber(maxbytes)
290 if val and maxbytes ~= nil then
291 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
298 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
304 return true -- Everything qualifies as valid string
307 function directory(val, seen)
308 local s = fs.stat(val)
311 if s and not seen[s.ino] then
313 if s.type == "dir" then
315 elseif s.type == "lnk" then
316 return directory( fs.readlink(val), seen )
323 function file(val, seen)
324 local s = fs.stat(val)
327 if s and not seen[s.ino] then
329 if s.type == "reg" then
331 elseif s.type == "lnk" then
332 return file( fs.readlink(val), seen )
339 function device(val, seen)
340 local s = fs.stat(val)
343 if s and not seen[s.ino] then
345 if s.type == "chr" or s.type == "blk" then
347 elseif s.type == "lnk" then
348 return device( fs.readlink(val), seen )
355 function uciname(val)
356 return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
359 function range(val, min, max)
364 if val ~= nil and min ~= nil and max ~= nil then
365 return ((val >= min) and (val <= max))
371 function min(val, min)
375 if val ~= nil and min ~= nil then
382 function max(val, max)
386 if val ~= nil and max ~= nil then
393 function rangelength(val, min, max)
398 if val ~= nil and min ~= nil and max ~= nil then
399 return ((#val >= min) and (#val <= max))
405 function minlength(val, min)
409 if val ~= nil and min ~= nil then
416 function maxlength(val, max)
420 if val ~= nil and max ~= nil then
427 function phonedigit(val)
428 return (val:match("^[0-9\*#!%.]+$") ~= nil)
431 function timehhmmss(val)
432 return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
435 function dateyyyymmdd(val)
437 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
438 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
441 year = tonumber(yearstr)
442 month = tonumber(monthstr)
443 day = tonumber(daystr)
444 if (year == nil) or (month == nil) or (day == nil) then
448 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
450 local function is_leap_year(year)
451 return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
454 function get_days_in_month(month, year)
455 if (month == 2) and is_leap_year(year) then
458 return days_in_month[month]
461 if (year < 2015) then
464 if ((month == 0) or (month > 12)) then
467 if ((day == 0) or (day > get_days_in_month(month, year))) then