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 base64(val)
287         if val then
288                 return (val:match("^[a-zA-Z0-9/+]+=?=?\$") ~= nil) and (math.fmod(#val, 4) == 0)
289         end
290         return false
291 end
293 function string(val)
294         return true             -- Everything qualifies as valid string
295 end
297 function directory(val, seen)
298         local s = fs.stat(val)
299         seen = seen or { }
301         if s and not seen[s.ino] then
302                 seen[s.ino] = true
303                 if s.type == "dir" then
304                         return true
305                 elseif s.type == "lnk" then
307                 end
308         end
310         return false
311 end
313 function file(val, seen)
314         local s = fs.stat(val)
315         seen = seen or { }
317         if s and not seen[s.ino] then
318                 seen[s.ino] = true
319                 if s.type == "reg" then
320                         return true
321                 elseif s.type == "lnk" then
323                 end
324         end
326         return false
327 end
329 function device(val, seen)
330         local s = fs.stat(val)
331         seen = seen or { }
333         if s and not seen[s.ino] then
334                 seen[s.ino] = true
335                 if s.type == "chr" or s.type == "blk" then
336                         return true
337                 elseif s.type == "lnk" then
339                 end
340         end
342         return false
343 end
345 function uciname(val)
346         return (val:match("^[a-zA-Z0-9_]+\$") ~= nil)
347 end
349 function range(val, min, max)
350         val = tonumber(val)
351         min = tonumber(min)
352         max = tonumber(max)
354         if val ~= nil and min ~= nil and max ~= nil then
355                 return ((val >= min) and (val <= max))
356         end
358         return false
359 end
361 function min(val, min)
362         val = tonumber(val)
363         min = tonumber(min)
365         if val ~= nil and min ~= nil then
366                 return (val >= min)
367         end
369         return false
370 end
372 function max(val, max)
373         val = tonumber(val)
374         max = tonumber(max)
376         if val ~= nil and max ~= nil then
377                 return (val <= max)
378         end
380         return false
381 end
383 function rangelength(val, min, max)
384         val = tostring(val)
385         min = tonumber(min)
386         max = tonumber(max)
388         if val ~= nil and min ~= nil and max ~= nil then
389                 return ((#val >= min) and (#val <= max))
390         end
392         return false
393 end
395 function minlength(val, min)
396         val = tostring(val)
397         min = tonumber(min)
399         if val ~= nil and min ~= nil then
400                 return (#val >= min)
401         end
403         return false
404 end
406 function maxlength(val, max)
407         val = tostring(val)
408         max = tonumber(max)
410         if val ~= nil and max ~= nil then
411                 return (#val <= max)
412         end
414         return false
415 end
417 function phonedigit(val)
418         return (val:match("^[0-9\*#!%.]+\$") ~= nil)
419 end
421 function timehhmmss(val)
422         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]\$") ~= nil)
423 end
425 function dateyyyymmdd(val)
426         if val ~= nil then
427                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)\$")
428                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
429                         return false;
430                 end
431                 year = tonumber(yearstr)
432                 month = tonumber(monthstr)
433                 day = tonumber(daystr)
434                 if (year == nil) or (month == nil) or (day == nil) then
435                         return false;
436                 end
438                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
440                 local function is_leap_year(year)
441                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
442                 end
444                 function get_days_in_month(month, year)
445                         if (month == 2) and is_leap_year(year) then
446                                 return 29
447                         else
448                                 return days_in_month[month]
449                         end
450                 end
451                 if (year < 2015) then
452                         return false
453                 end
454                 if ((month == 0) or (month > 12)) then
455                         return false
456                 end
457                 if ((day == 0) or (day > get_days_in_month(month, year))) then
458                         return false
459                 end
460                 return true
461         end
462         return false
463 end