Merge pull request #1709 from dibdot/get_interface-fix
[project/luci.git] / modules / luci-base / luasrc / cbi / datatypes.lua
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.
4
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
10
11
12 module "luci.cbi.datatypes"
13
14
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
31
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
48
49 function neg(v, ...)
50         return _M['or'](v:gsub("^%s*!%s*", ""), ...)
51 end
52
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
65
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
74
75         return false
76 end
77
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
83
84         return false
85 end
86
87 function integer(val)
88         local n = tonumber(val)
89         if n ~= nil and math.floor(n) == n then
90                 return true
91         end
92
93         return false
94 end
95
96 function ufloat(val)
97         local n = tonumber(val)
98         return ( n ~= nil and n >= 0 )
99 end
100
101 function float(val)
102         return ( tonumber(val) ~= nil )
103 end
104
105 function ipaddr(val)
106         return ip4addr(val) or ip6addr(val)
107 end
108
109 function ip4addr(val)
110         if val then
111                 return ip.IPv4(val) and true or false
112         end
113
114         return false
115 end
116
117 function ip4prefix(val)
118         val = tonumber(val)
119         return ( val and val >= 0 and val <= 32 )
120 end
121
122 function ip6addr(val)
123         if val then
124                 return ip.IPv6(val) and true or false
125         end
126
127         return false
128 end
129
130 function ip6prefix(val)
131         val = tonumber(val)
132         return ( val and val >= 0 and val <= 128 )
133 end
134
135 function cidr4(val)
136         local ip, mask = val:match("^([^/]+)/([^/]+)$")
137
138         return ip4addr(ip) and ip4prefix(mask)
139 end
140
141 function cidr6(val)
142         local ip, mask = val:match("^([^/]+)/([^/]+)$")
143
144         return ip6addr(ip) and ip6prefix(mask)
145 end
146
147 function ipnet4(val)
148         local ip, mask = val:match("^([^/]+)/([^/]+)$")
149
150         return ip4addr(ip) and ip4addr(mask)
151 end
152
153 function ipnet6(val)
154         local ip, mask = val:match("^([^/]+)/([^/]+)$")
155
156         return ip6addr(ip) and ip6addr(mask)
157 end
158
159 function ipmask(val)
160         return ipmask4(val) or ipmask6(val)
161 end
162
163 function ipmask4(val)
164         return cidr4(val) or ipnet4(val) or ip4addr(val)
165 end
166
167 function ipmask6(val)
168         return cidr6(val) or ipnet6(val) or ip6addr(val)
169 end
170
171 function ip6hostid(val)
172         if val == "eui64" or val == "random" then
173                 return true
174         else
175                 local addr = ip.IPv6(val)
176                 if addr and addr:prefix() == 128 and addr:lower("::1:0:0:0:0") then
177                         return true
178                 end
179         end
180
181         return false
182 end
183
184 function port(val)
185         val = tonumber(val)
186         return ( val and val >= 0 and val <= 65535 )
187 end
188
189 function portrange(val)
190         local p1, p2 = val:match("^(%d+)%-(%d+)$")
191         if p1 and p2 and port(p1) and port(p2) then
192                 return true
193         else
194                 return port(val)
195         end
196 end
197
198 function macaddr(val)
199         return ip.checkmac(val) and true or false
200 end
201
202 function hostname(val, strict)
203         if val and (#val < 254) and (
204            val:match("^[a-zA-Z_]+$") or
205            (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
206             val:match("[^0-9%.]"))
207         ) then
208                 return (not strict or not val:match("^_"))
209         end
210         return false
211 end
212
213 function host(val, ipv4only)
214         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
215 end
216
217 function network(val)
218         return uciname(val) or host(val)
219 end
220
221 function hostport(val, ipv4only)
222         local h, p = val:match("^([^:]+):([^:]+)$")
223         return not not (h and p and host(h, ipv4only) and port(p))
224 end
225
226 function ip4addrport(val, bracket)
227         local h, p = val:match("^([^:]+):([^:]+)$")
228         return (h and p and ip4addr(h) and port(p))
229 end
230
231 function ip4addrport(val)
232         local h, p = val:match("^([^:]+):([^:]+)$")
233         return (h and p and ip4addr(h) and port(p))
234 end
235
236 function ipaddrport(val, bracket)
237         local h, p = val:match("^([^%[%]:]+):([^:]+)$")
238         if (h and p and ip4addr(h) and port(p)) then
239                 return true
240         elseif (bracket == 1) then
241                 h, p = val:match("^%[(.+)%]:([^:]+)$")
242                 if  (h and p and ip6addr(h) and port(p)) then
243                         return true
244                 end
245         end
246         h, p = val:match("^([^%[%]]+):([^:]+)$")
247         return (h and p and ip6addr(h) and port(p))
248 end
249
250 function wpakey(val)
251         if #val == 64 then
252                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
253         else
254                 return (#val >= 8) and (#val <= 63)
255         end
256 end
257
258 function wepkey(val)
259         if val:sub(1, 2) == "s:" then
260                 val = val:sub(3)
261         end
262
263         if (#val == 10) or (#val == 26) then
264                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
265         else
266                 return (#val == 5) or (#val == 13)
267         end
268 end
269
270 function hexstring(val)
271         if val then
272                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
273         end
274         return false
275 end
276
277 function hex(val, maxbytes)
278         maxbytes = tonumber(maxbytes)
279         if val and maxbytes ~= nil then
280                 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
281         end
282         return false
283 end
284
285 function base64(val)
286         if val then
287                 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
288         end
289         return false
290 end
291
292 function string(val)
293         return true             -- Everything qualifies as valid string
294 end
295
296 function directory(val, seen)
297         local s = fs.stat(val)
298         seen = seen or { }
299
300         if s and not seen[s.ino] then
301                 seen[s.ino] = true
302                 if s.type == "dir" then
303                         return true
304                 elseif s.type == "lnk" then
305                         return directory( fs.readlink(val), seen )
306                 end
307         end
308
309         return false
310 end
311
312 function file(val, seen)
313         local s = fs.stat(val)
314         seen = seen or { }
315
316         if s and not seen[s.ino] then
317                 seen[s.ino] = true
318                 if s.type == "reg" then
319                         return true
320                 elseif s.type == "lnk" then
321                         return file( fs.readlink(val), seen )
322                 end
323         end
324
325         return false
326 end
327
328 function device(val, seen)
329         local s = fs.stat(val)
330         seen = seen or { }
331
332         if s and not seen[s.ino] then
333                 seen[s.ino] = true
334                 if s.type == "chr" or s.type == "blk" then
335                         return true
336                 elseif s.type == "lnk" then
337                         return device( fs.readlink(val), seen )
338                 end
339         end
340
341         return false
342 end
343
344 function uciname(val)
345         return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
346 end
347
348 function range(val, min, max)
349         val = tonumber(val)
350         min = tonumber(min)
351         max = tonumber(max)
352
353         if val ~= nil and min ~= nil and max ~= nil then
354                 return ((val >= min) and (val <= max))
355         end
356
357         return false
358 end
359
360 function min(val, min)
361         val = tonumber(val)
362         min = tonumber(min)
363
364         if val ~= nil and min ~= nil then
365                 return (val >= min)
366         end
367
368         return false
369 end
370
371 function max(val, max)
372         val = tonumber(val)
373         max = tonumber(max)
374
375         if val ~= nil and max ~= nil then
376                 return (val <= max)
377         end
378
379         return false
380 end
381
382 function rangelength(val, min, max)
383         val = tostring(val)
384         min = tonumber(min)
385         max = tonumber(max)
386
387         if val ~= nil and min ~= nil and max ~= nil then
388                 return ((#val >= min) and (#val <= max))
389         end
390
391         return false
392 end
393
394 function minlength(val, min)
395         val = tostring(val)
396         min = tonumber(min)
397
398         if val ~= nil and min ~= nil then
399                 return (#val >= min)
400         end
401
402         return false
403 end
404
405 function maxlength(val, max)
406         val = tostring(val)
407         max = tonumber(max)
408
409         if val ~= nil and max ~= nil then
410                 return (#val <= max)
411         end
412
413         return false
414 end
415
416 function phonedigit(val)
417         return (val:match("^[0-9\*#!%.]+$") ~= nil)
418 end
419
420 function timehhmmss(val)
421         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
422 end
423
424 function dateyyyymmdd(val)
425         if val ~= nil then
426                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
427                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
428                         return false;
429                 end
430                 year = tonumber(yearstr)
431                 month = tonumber(monthstr)
432                 day = tonumber(daystr)
433                 if (year == nil) or (month == nil) or (day == nil) then
434                         return false;
435                 end
436
437                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
438
439                 local function is_leap_year(year)
440                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
441                 end
442
443                 function get_days_in_month(month, year)
444                         if (month == 2) and is_leap_year(year) then
445                                 return 29
446                         else
447                                 return days_in_month[month]
448                         end
449                 end
450                 if (year < 2015) then
451                         return false
452                 end
453                 if ((month == 0) or (month > 12)) then
454                         return false
455                 end
456                 if ((day == 0) or (day > get_days_in_month(month, year))) then
457                         return false
458                 end
459                 return true
460         end
461         return false
462 end