a7e02f350a3d5b7b74c02b0ea1163f358e8c301f
[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         if val and val:match(
200                 "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
201                  "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
202         ) then
203                 local parts = util.split( val, ":" )
204
205                 for i = 1,6 do
206                         parts[i] = tonumber( parts[i], 16 )
207                         if parts[i] < 0 or parts[i] > 255 then
208                                 return false
209                         end
210                 end
211
212                 return true
213         end
214
215         return false
216 end
217
218 function hostname(val)
219         if val and (#val < 254) and (
220            val:match("^[a-zA-Z_]+$") or
221            (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
222             val:match("[^0-9%.]"))
223         ) then
224                 return true
225         end
226         return false
227 end
228
229 function host(val, ipv4only)
230         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
231 end
232
233 function network(val)
234         return uciname(val) or host(val)
235 end
236
237 function hostport(val, ipv4only)
238         local h, p = val:match("^([^:]+):([^:]+)$")
239         return not not (h and p and host(h, ipv4only) and port(p))
240 end
241
242 function ip4addrport(val, bracket)
243         local h, p = val:match("^([^:]+):([^:]+)$")
244         return (h and p and ip4addr(h) and port(p))
245 end
246
247 function ip4addrport(val)
248         local h, p = val:match("^([^:]+):([^:]+)$")
249         return (h and p and ip4addr(h) and port(p))
250 end
251
252 function ipaddrport(val, bracket)
253         local h, p = val:match("^([^%[%]:]+):([^:]+)$")
254         if (h and p and ip4addr(h) and port(p)) then
255                 return true
256         elseif (bracket == 1) then
257                 h, p = val:match("^%[(.+)%]:([^:]+)$")
258                 if  (h and p and ip6addr(h) and port(p)) then
259                         return true
260                 end
261         end
262         h, p = val:match("^([^%[%]]+):([^:]+)$")
263         return (h and p and ip6addr(h) and port(p))
264 end
265
266 function wpakey(val)
267         if #val == 64 then
268                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
269         else
270                 return (#val >= 8) and (#val <= 63)
271         end
272 end
273
274 function wepkey(val)
275         if val:sub(1, 2) == "s:" then
276                 val = val:sub(3)
277         end
278
279         if (#val == 10) or (#val == 26) then
280                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
281         else
282                 return (#val == 5) or (#val == 13)
283         end
284 end
285
286 function hexstring(val)
287         if val then
288                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
289         end
290         return false
291 end
292
293 function hex(val, maxbytes)
294         maxbytes = tonumber(maxbytes)
295         if val and maxbytes ~= nil then
296                 return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
297         end
298         return false
299 end
300
301 function base64(val)
302         if val then
303                 return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
304         end
305         return false
306 end
307
308 function string(val)
309         return true             -- Everything qualifies as valid string
310 end
311
312 function directory(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 == "dir" then
319                         return true
320                 elseif s.type == "lnk" then
321                         return directory( fs.readlink(val), seen )
322                 end
323         end
324
325         return false
326 end
327
328 function file(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 == "reg" then
335                         return true
336                 elseif s.type == "lnk" then
337                         return file( fs.readlink(val), seen )
338                 end
339         end
340
341         return false
342 end
343
344 function device(val, seen)
345         local s = fs.stat(val)
346         seen = seen or { }
347
348         if s and not seen[s.ino] then
349                 seen[s.ino] = true
350                 if s.type == "chr" or s.type == "blk" then
351                         return true
352                 elseif s.type == "lnk" then
353                         return device( fs.readlink(val), seen )
354                 end
355         end
356
357         return false
358 end
359
360 function uciname(val)
361         return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
362 end
363
364 function range(val, min, max)
365         val = tonumber(val)
366         min = tonumber(min)
367         max = tonumber(max)
368
369         if val ~= nil and min ~= nil and max ~= nil then
370                 return ((val >= min) and (val <= max))
371         end
372
373         return false
374 end
375
376 function min(val, min)
377         val = tonumber(val)
378         min = tonumber(min)
379
380         if val ~= nil and min ~= nil then
381                 return (val >= min)
382         end
383
384         return false
385 end
386
387 function max(val, max)
388         val = tonumber(val)
389         max = tonumber(max)
390
391         if val ~= nil and max ~= nil then
392                 return (val <= max)
393         end
394
395         return false
396 end
397
398 function rangelength(val, min, max)
399         val = tostring(val)
400         min = tonumber(min)
401         max = tonumber(max)
402
403         if val ~= nil and min ~= nil and max ~= nil then
404                 return ((#val >= min) and (#val <= max))
405         end
406
407         return false
408 end
409
410 function minlength(val, min)
411         val = tostring(val)
412         min = tonumber(min)
413
414         if val ~= nil and min ~= nil then
415                 return (#val >= min)
416         end
417
418         return false
419 end
420
421 function maxlength(val, max)
422         val = tostring(val)
423         max = tonumber(max)
424
425         if val ~= nil and max ~= nil then
426                 return (#val <= max)
427         end
428
429         return false
430 end
431
432 function phonedigit(val)
433         return (val:match("^[0-9\*#!%.]+$") ~= nil)
434 end
435
436 function timehhmmss(val)
437         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
438 end
439
440 function dateyyyymmdd(val)
441         if val ~= nil then
442                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
443                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
444                         return false;
445                 end
446                 year = tonumber(yearstr)
447                 month = tonumber(monthstr)
448                 day = tonumber(daystr)
449                 if (year == nil) or (month == nil) or (day == nil) then
450                         return false;
451                 end
452
453                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
454
455                 local function is_leap_year(year)
456                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
457                 end
458
459                 function get_days_in_month(month, year)
460                         if (month == 2) and is_leap_year(year) then
461                                 return 29
462                         else
463                                 return days_in_month[month]
464                         end
465                 end
466                 if (year < 2015) then
467                         return false
468                 end
469                 if ((month == 0) or (month > 12)) then
470                         return false
471                 end
472                 if ((day == 0) or (day > get_days_in_month(month, year))) then
473                         return false
474                 end
475                 return true
476         end
477         return false
478 end