1 -- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
2 -- Copyright 2017 Dan Luedtke <mail@danrl.com>
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
17         for i = 1, select('#', ...), 2 do
18                 local f = select(i, ...)
19                 local a = select(i+1, ...)
20                 if type(f) ~= "function" then
21                         if f == v then
22                                 return true
23                         end
24                         i = i - 1
25                 elseif f(v, unpack(a)) then
26                         return true
27                 end
28         end
29         return false
30 end
32 _M['and'] = function(v, ...)
33         local i
34         for i = 1, select('#', ...), 2 do
35                 local f = select(i, ...)
36                 local a = select(i+1, ...)
37                 if type(f) ~= "function" then
38                         if f ~= v then
39                                 return false
40                         end
41                         i = i - 1
42                 elseif not f(v, unpack(a)) then
43                         return false
44                 end
45         end
46         return true
47 end
49 function neg(v, ...)
50         return _M['or'](v:gsub("^%s*!%s*", ""), ...)
51 end
53 function list(v, subvalidator, subargs)
54         if type(subvalidator) ~= "function" then
55                 return false
56         end
57         local token
58         for token in v:gmatch("%S+") do
59                 if not subvalidator(token, unpack(subargs)) then
60                         return false
61                 end
62         end
63         return true
64 end
66 function bool(val)
67         if val == "1" or val == "yes" or val == "on" or val == "true" then
68                 return true
69         elseif val == "0" or val == "no" or val == "off" or val == "false" then
70                 return true
71         elseif val == "" or val == nil then
72                 return true
73         end
75         return false
76 end
78 function uinteger(val)
79         local n = tonumber(val)
80         if n ~= nil and math.floor(n) == n and n >= 0 then
81                 return true
82         end
84         return false
85 end
87 function integer(val)
88         local n = tonumber(val)
89         if n ~= nil and math.floor(n) == n then
90                 return true
91         end
93         return false
94 end
96 function ufloat(val)
97         local n = tonumber(val)
98         return ( n ~= nil and n >= 0 )
99 end
101 function float(val)
102         return ( tonumber(val) ~= nil )
103 end
107 end
110         if val then
111                 return ip.IPv4(val) and true or false
112         end
114         return false
115 end
117 function ip4prefix(val)
118         val = tonumber(val)
119         return ( val and val >= 0 and val <= 32 )
120 end
123         if val then
124                 return ip.IPv6(val) and true or false
125         end
127         return false
128 end
130 function ip6prefix(val)
131         val = tonumber(val)
132         return ( val and val >= 0 and val <= 128 )
133 end
137 end
140         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
143         if bits and (bits < 0 or bits > 32) then
144                 return false
145         end
148                 return false
149         end
152 end
155         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
158         if bits and (bits < 0 or bits > 128) then
159                 return false
160         end
163                 return false
164         end
167 end
169 function ip6hostid(val)
170         if val and val:match("^[a-fA-F0-9:]+\$") and (#val > 2) then
172         end
174         return false
175 end
177 function port(val)
178         val = tonumber(val)
179         return ( val and val >= 0 and val <= 65535 )
180 end
182 function portrange(val)
183         local p1, p2 = val:match("^(%d+)%-(%d+)\$")
184         if p1 and p2 and port(p1) and port(p2) then
185                 return true
186         else
187                 return port(val)
188         end
189 end
192         if val and val:match(
193                 "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
194                  "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+\$"
195         ) then
196                 local parts = util.split( val, ":" )
198                 for i = 1,6 do
199                         parts[i] = tonumber( parts[i], 16 )
200                         if parts[i] < 0 or parts[i] > 255 then
201                                 return false
202                         end
203                 end
205                 return true
206         end
208         return false
209 end
211 function hostname(val)
212         if val and (#val < 254) and (
213            val:match("^[a-zA-Z_]+\$") or
214            (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]\$") and
215             val:match("[^0-9%.]"))
216         ) then
217                 return true
218         end
219         return false
220 end
222 function host(val, ipv4only)
223         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
224 end
226 function network(val)
227         return uciname(val) or host(val)
228 end
230 function hostport(val, ipv4only)
231         local h, p = val:match("^([^:]+):([^:]+)\$")
232         return not not (h and p and host(h, ipv4only) and port(p))
233 end
236         local h, p = val:match("^([^:]+):([^:]+)\$")
237         return (h and p and ip4addr(h) and port(p))
238 end
241         local h, p = val:match("^([^:]+):([^:]+)\$")
242         return (h and p and ip4addr(h) and port(p))
243 end
246         local h, p = val:match("^([^%[%]:]+):([^:]+)\$")
247         if (h and p and ip4addr(h) and port(p)) then
248                 return true
249         elseif (bracket == 1) then
250                 h, p = val:match("^%[(.+)%]:([^:]+)\$")
251                 if  (h and p and ip6addr(h) and port(p)) then
252                         return true
253                 end
254         end
255         h, p = val:match("^([^%[%]]+):([^:]+)\$")
256         return (h and p and ip6addr(h) and port(p))
257 end
259 function wpakey(val)
260         if #val == 64 then
261                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
262         else
263                 return (#val >= 8) and (#val <= 63)
264         end
265 end
267 function wepkey(val)
268         if val:sub(1, 2) == "s:" then
269                 val = val:sub(3)
270         end
272         if (#val == 10) or (#val == 26) then
273                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
274         else
275                 return (#val == 5) or (#val == 13)
276         end
277 end
279 function hexstring(val)
280         if val then
281                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
282         end
283         return false
284 end
286 function hex(val, maxbytes)
287         maxbytes = tonumber(maxbytes)
288         if val and maxbytes ~= nil then
289                 return ((val:match("^0x[a-fA-F0-9]+\$") ~= nil) and (#val <= 2 + maxbytes * 2))
290         end
291         return false
292 end
294 function base64(val)
295         if val then
296                 return (val:match("^[a-zA-Z0-9/+]+=?=?\$") ~= nil) and (math.fmod(#val, 4) == 0)
297         end
298         return false
299 end
301 function string(val)
302         return true             -- Everything qualifies as valid string
303 end
305 function directory(val, seen)
306         local s = fs.stat(val)
307         seen = seen or { }
309         if s and not seen[s.ino] then
310                 seen[s.ino] = true
311                 if s.type == "dir" then
312                         return true
313                 elseif s.type == "lnk" then
315                 end
316         end
318         return false
319 end
321 function file(val, seen)
322         local s = fs.stat(val)
323         seen = seen or { }
325         if s and not seen[s.ino] then
326                 seen[s.ino] = true
327                 if s.type == "reg" then
328                         return true
329                 elseif s.type == "lnk" then
331                 end
332         end
334         return false
335 end
337 function device(val, seen)
338         local s = fs.stat(val)
339         seen = seen or { }
341         if s and not seen[s.ino] then
342                 seen[s.ino] = true
343                 if s.type == "chr" or s.type == "blk" then
344                         return true
345                 elseif s.type == "lnk" then
347                 end
348         end
350         return false
351 end
353 function uciname(val)
354         return (val:match("^[a-zA-Z0-9_]+\$") ~= nil)
355 end
357 function range(val, min, max)
358         val = tonumber(val)
359         min = tonumber(min)
360         max = tonumber(max)
362         if val ~= nil and min ~= nil and max ~= nil then
363                 return ((val >= min) and (val <= max))
364         end
366         return false
367 end
369 function min(val, min)
370         val = tonumber(val)
371         min = tonumber(min)
373         if val ~= nil and min ~= nil then
374                 return (val >= min)
375         end
377         return false
378 end
380 function max(val, max)
381         val = tonumber(val)
382         max = tonumber(max)
384         if val ~= nil and max ~= nil then
385                 return (val <= max)
386         end
388         return false
389 end
391 function rangelength(val, min, max)
392         val = tostring(val)
393         min = tonumber(min)
394         max = tonumber(max)
396         if val ~= nil and min ~= nil and max ~= nil then
397                 return ((#val >= min) and (#val <= max))
398         end
400         return false
401 end
403 function minlength(val, min)
404         val = tostring(val)
405         min = tonumber(min)
407         if val ~= nil and min ~= nil then
408                 return (#val >= min)
409         end
411         return false
412 end
414 function maxlength(val, max)
415         val = tostring(val)
416         max = tonumber(max)
418         if val ~= nil and max ~= nil then
419                 return (#val <= max)
420         end
422         return false
423 end
425 function phonedigit(val)
426         return (val:match("^[0-9\*#!%.]+\$") ~= nil)
427 end
429 function timehhmmss(val)
430         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]\$") ~= nil)
431 end
433 function dateyyyymmdd(val)
434         if val ~= nil then
435                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)\$")
436                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
437                         return false;
438                 end
439                 year = tonumber(yearstr)
440                 month = tonumber(monthstr)
441                 day = tonumber(daystr)
442                 if (year == nil) or (month == nil) or (day == nil) then
443                         return false;
444                 end
446                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
448                 local function is_leap_year(year)
449                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
450                 end
452                 function get_days_in_month(month, year)
453                         if (month == 2) and is_leap_year(year) then
454                                 return 29
455                         else
456                                 return days_in_month[month]
457                         end
458                 end
459                 if (year < 2015) then
460                         return false
461                 end
462                 if ((month == 0) or (month > 12)) then
463                         return false
464                 end
465                 if ((day == 0) or (day > get_days_in_month(month, year))) then
466                         return false
467                 end
468                 return true
469         end
470         return false
471 end