Merge pull request #1024 from stangri/luci-app-vpnbypass
[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 ipmask(val)
136         return ipmask4(val) or ipmask6(val)
137 end
138
139 function ipmask4(val)
140         local ip, mask = val:match("^([^/]+)/([^/]+)$")
141         local bits = tonumber(mask)
142
143         if bits and (bits < 0 or bits > 32) then
144                 return false
145         end
146
147         if not bits and mask and not ip4addr(mask) then
148                 return false
149         end
150
151         return ip4addr(ip or val)
152 end
153
154 function ipmask6(val)
155         local ip, mask = val:match("^([^/]+)/([^/]+)$")
156         local bits = tonumber(mask)
157
158         if bits and (bits < 0 or bits > 128) then
159                 return false
160         end
161
162         if not bits and mask and not ip6addr(mask) then
163                 return false
164         end
165
166         return ip6addr(ip or val)
167 end
168
169 function ip6hostid(val)
170         if val and val:match("^[a-fA-F0-9:]+$") and (#val > 2) then
171                 return (ip6addr("2001:db8:0:0" .. val) or ip6addr("2001:db8:0:0:" .. val))
172         end
173
174         return false
175 end
176
177 function port(val)
178         val = tonumber(val)
179         return ( val and val >= 0 and val <= 65535 )
180 end
181
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
190
191 function macaddr(val)
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, ":" )
197
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
204
205                 return true
206         end
207
208         return false
209 end
210
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
221
222 function host(val, ipv4only)
223         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
224 end
225
226 function network(val)
227         return uciname(val) or host(val)
228 end
229
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
234
235 function ip4addrport(val, bracket)
236         local h, p = val:match("^([^:]+):([^:]+)$")
237         return (h and p and ip4addr(h) and port(p))
238 end
239
240 function ip4addrport(val)
241         local h, p = val:match("^([^:]+):([^:]+)$")
242         return (h and p and ip4addr(h) and port(p))
243 end
244
245 function ipaddrport(val, bracket)
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
258
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
266
267 function wepkey(val)
268         if val:sub(1, 2) == "s:" then
269                 val = val:sub(3)
270         end
271
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
278
279 function hexstring(val)
280         if val then
281                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
282         end
283         return false
284 end
285
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
292
293 function string(val)
294         return true             -- Everything qualifies as valid string
295 end
296
297 function directory(val, seen)
298         local s = fs.stat(val)
299         seen = seen or { }
300
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
306                         return directory( fs.readlink(val), seen )
307                 end
308         end
309
310         return false
311 end
312
313 function file(val, seen)
314         local s = fs.stat(val)
315         seen = seen or { }
316
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
322                         return file( fs.readlink(val), seen )
323                 end
324         end
325
326         return false
327 end
328
329 function device(val, seen)
330         local s = fs.stat(val)
331         seen = seen or { }
332
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
338                         return device( fs.readlink(val), seen )
339                 end
340         end
341
342         return false
343 end
344
345 function uciname(val)
346         return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
347 end
348
349 function range(val, min, max)
350         val = tonumber(val)
351         min = tonumber(min)
352         max = tonumber(max)
353
354         if val ~= nil and min ~= nil and max ~= nil then
355                 return ((val >= min) and (val <= max))
356         end
357
358         return false
359 end
360
361 function min(val, min)
362         val = tonumber(val)
363         min = tonumber(min)
364
365         if val ~= nil and min ~= nil then
366                 return (val >= min)
367         end
368
369         return false
370 end
371
372 function max(val, max)
373         val = tonumber(val)
374         max = tonumber(max)
375
376         if val ~= nil and max ~= nil then
377                 return (val <= max)
378         end
379
380         return false
381 end
382
383 function rangelength(val, min, max)
384         val = tostring(val)
385         min = tonumber(min)
386         max = tonumber(max)
387
388         if val ~= nil and min ~= nil and max ~= nil then
389                 return ((#val >= min) and (#val <= max))
390         end
391
392         return false
393 end
394
395 function minlength(val, min)
396         val = tostring(val)
397         min = tonumber(min)
398
399         if val ~= nil and min ~= nil then
400                 return (#val >= min)
401         end
402
403         return false
404 end
405
406 function maxlength(val, max)
407         val = tostring(val)
408         max = tonumber(max)
409
410         if val ~= nil and max ~= nil then
411                 return (#val <= max)
412         end
413
414         return false
415 end
416
417 function phonedigit(val)
418         return (val:match("^[0-9\*#!%.]+$") ~= nil)
419 end
420
421 function timehhmmss(val)
422         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
423 end
424
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
437
438                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
439
440                 local function is_leap_year(year)
441                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
442                 end
443
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