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
135 function cidr4(val)
136         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
139 end
141 function cidr6(val)
142         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
145 end
147 function ipnet4(val)
148         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
151 end
153 function ipnet6(val)
154         local ip, mask = val:match("^([^/]+)/([^/]+)\$")
157 end
161 end
164         return cidr4(val) or ipnet4(val) or ip4addr(val)
165 end
168         return cidr6(val) or ipnet6(val) or ip6addr(val)
169 end
171 function ip6hostid(val)
172         if val and val:match("^[a-fA-F0-9:]+\$") and (#val > 2) then
174         end
176         return false
177 end
179 function port(val)
180         val = tonumber(val)
181         return ( val and val >= 0 and val <= 65535 )
182 end
184 function portrange(val)
185         local p1, p2 = val:match("^(%d+)%-(%d+)\$")
186         if p1 and p2 and port(p1) and port(p2) then
187                 return true
188         else
189                 return port(val)
190         end
191 end
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]+\$"
197         ) then
198                 local parts = util.split( val, ":" )
200                 for i = 1,6 do
201                         parts[i] = tonumber( parts[i], 16 )
202                         if parts[i] < 0 or parts[i] > 255 then
203                                 return false
204                         end
205                 end
207                 return true
208         end
210         return false
211 end
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%.]"))
218         ) then
219                 return true
220         end
221         return false
222 end
224 function host(val, ipv4only)
225         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
226 end
228 function network(val)
229         return uciname(val) or host(val)
230 end
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))
235 end
238         local h, p = val:match("^([^:]+):([^:]+)\$")
239         return (h and p and ip4addr(h) and port(p))
240 end
243         local h, p = val:match("^([^:]+):([^:]+)\$")
244         return (h and p and ip4addr(h) and port(p))
245 end
248         local h, p = val:match("^([^%[%]:]+):([^:]+)\$")
249         if (h and p and ip4addr(h) and port(p)) then
250                 return true
251         elseif (bracket == 1) then
252                 h, p = val:match("^%[(.+)%]:([^:]+)\$")
253                 if  (h and p and ip6addr(h) and port(p)) then
254                         return true
255                 end
256         end
257         h, p = val:match("^([^%[%]]+):([^:]+)\$")
258         return (h and p and ip6addr(h) and port(p))
259 end
261 function wpakey(val)
262         if #val == 64 then
263                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
264         else
265                 return (#val >= 8) and (#val <= 63)
266         end
267 end
269 function wepkey(val)
270         if val:sub(1, 2) == "s:" then
271                 val = val:sub(3)
272         end
274         if (#val == 10) or (#val == 26) then
275                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
276         else
277                 return (#val == 5) or (#val == 13)
278         end
279 end
281 function hexstring(val)
282         if val then
283                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
284         end
285         return false
286 end
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))
292         end
293         return false
294 end
296 function base64(val)
297         if val then
298                 return (val:match("^[a-zA-Z0-9/+]+=?=?\$") ~= nil) and (math.fmod(#val, 4) == 0)
299         end
300         return false
301 end
303 function string(val)
304         return true             -- Everything qualifies as valid string
305 end
307 function directory(val, seen)
308         local s = fs.stat(val)
309         seen = seen or { }
311         if s and not seen[s.ino] then
312                 seen[s.ino] = true
313                 if s.type == "dir" then
314                         return true
315                 elseif s.type == "lnk" then
317                 end
318         end
320         return false
321 end
323 function file(val, seen)
324         local s = fs.stat(val)
325         seen = seen or { }
327         if s and not seen[s.ino] then
328                 seen[s.ino] = true
329                 if s.type == "reg" then
330                         return true
331                 elseif s.type == "lnk" then
333                 end
334         end
336         return false
337 end
339 function device(val, seen)
340         local s = fs.stat(val)
341         seen = seen or { }
343         if s and not seen[s.ino] then
344                 seen[s.ino] = true
345                 if s.type == "chr" or s.type == "blk" then
346                         return true
347                 elseif s.type == "lnk" then
349                 end
350         end
352         return false
353 end
355 function uciname(val)
356         return (val:match("^[a-zA-Z0-9_]+\$") ~= nil)
357 end
359 function range(val, min, max)
360         val = tonumber(val)
361         min = tonumber(min)
362         max = tonumber(max)
364         if val ~= nil and min ~= nil and max ~= nil then
365                 return ((val >= min) and (val <= max))
366         end
368         return false
369 end
371 function min(val, min)
372         val = tonumber(val)
373         min = tonumber(min)
375         if val ~= nil and min ~= nil then
376                 return (val >= min)
377         end
379         return false
380 end
382 function max(val, max)
383         val = tonumber(val)
384         max = tonumber(max)
386         if val ~= nil and max ~= nil then
387                 return (val <= max)
388         end
390         return false
391 end
393 function rangelength(val, min, max)
394         val = tostring(val)
395         min = tonumber(min)
396         max = tonumber(max)
398         if val ~= nil and min ~= nil and max ~= nil then
399                 return ((#val >= min) and (#val <= max))
400         end
402         return false
403 end
405 function minlength(val, min)
406         val = tostring(val)
407         min = tonumber(min)
409         if val ~= nil and min ~= nil then
410                 return (#val >= min)
411         end
413         return false
414 end
416 function maxlength(val, max)
417         val = tostring(val)
418         max = tonumber(max)
420         if val ~= nil and max ~= nil then
421                 return (#val <= max)
422         end
424         return false
425 end
427 function phonedigit(val)
428         return (val:match("^[0-9\*#!%.]+\$") ~= nil)
429 end
431 function timehhmmss(val)
432         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]\$") ~= nil)
433 end
435 function dateyyyymmdd(val)
436         if val ~= nil then
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
439                         return false;
440                 end
441                 year = tonumber(yearstr)
442                 month = tonumber(monthstr)
443                 day = tonumber(daystr)
444                 if (year == nil) or (month == nil) or (day == nil) then
445                         return false;
446                 end
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))
452                 end
454                 function get_days_in_month(month, year)
455                         if (month == 2) and is_leap_year(year) then
456                                 return 29
457                         else
458                                 return days_in_month[month]
459                         end
460                 end
461                 if (year < 2015) then
462                         return false
463                 end
464                 if ((month == 0) or (month > 12)) then
465                         return false
466                 end
467                 if ((day == 0) or (day > get_days_in_month(month, year))) then
468                         return false
469                 end
470                 return true
471         end
472         return false
473 end