Merge pull request #1502 from dibdot/adblock
[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 and val:match("^[a-fA-F0-9:]+$") and (#val > 2) then
173                 return (ip6addr("2001:db8:0:0" .. val) or ip6addr("2001:db8:0:0:" .. val))
174         end
175
176         return false
177 end
178
179 function port(val)
180         val = tonumber(val)
181         return ( val and val >= 0 and val <= 65535 )
182 end
183
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
192
193 function macaddr(val)
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, ":" )
199
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
206
207                 return true
208         end
209
210         return false
211 end
212
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
223
224 function host(val, ipv4only)
225         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
226 end
227
228 function network(val)
229         return uciname(val) or host(val)
230 end
231
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
236
237 function ip4addrport(val, bracket)
238         local h, p = val:match("^([^:]+):([^:]+)$")
239         return (h and p and ip4addr(h) and port(p))
240 end
241
242 function ip4addrport(val)
243         local h, p = val:match("^([^:]+):([^:]+)$")
244         return (h and p and ip4addr(h) and port(p))
245 end
246
247 function ipaddrport(val, bracket)
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
260
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
268
269 function wepkey(val)
270         if val:sub(1, 2) == "s:" then
271                 val = val:sub(3)
272         end
273
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
280
281 function hexstring(val)
282         if val then
283                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
284         end
285         return false
286 end
287
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
295
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
302
303 function string(val)
304         return true             -- Everything qualifies as valid string
305 end
306
307 function directory(val, seen)
308         local s = fs.stat(val)
309         seen = seen or { }
310
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
316                         return directory( fs.readlink(val), seen )
317                 end
318         end
319
320         return false
321 end
322
323 function file(val, seen)
324         local s = fs.stat(val)
325         seen = seen or { }
326
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
332                         return file( fs.readlink(val), seen )
333                 end
334         end
335
336         return false
337 end
338
339 function device(val, seen)
340         local s = fs.stat(val)
341         seen = seen or { }
342
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
348                         return device( fs.readlink(val), seen )
349                 end
350         end
351
352         return false
353 end
354
355 function uciname(val)
356         return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
357 end
358
359 function range(val, min, max)
360         val = tonumber(val)
361         min = tonumber(min)
362         max = tonumber(max)
363
364         if val ~= nil and min ~= nil and max ~= nil then
365                 return ((val >= min) and (val <= max))
366         end
367
368         return false
369 end
370
371 function min(val, min)
372         val = tonumber(val)
373         min = tonumber(min)
374
375         if val ~= nil and min ~= nil then
376                 return (val >= min)
377         end
378
379         return false
380 end
381
382 function max(val, max)
383         val = tonumber(val)
384         max = tonumber(max)
385
386         if val ~= nil and max ~= nil then
387                 return (val <= max)
388         end
389
390         return false
391 end
392
393 function rangelength(val, min, max)
394         val = tostring(val)
395         min = tonumber(min)
396         max = tonumber(max)
397
398         if val ~= nil and min ~= nil and max ~= nil then
399                 return ((#val >= min) and (#val <= max))
400         end
401
402         return false
403 end
404
405 function minlength(val, min)
406         val = tostring(val)
407         min = tonumber(min)
408
409         if val ~= nil and min ~= nil then
410                 return (#val >= min)
411         end
412
413         return false
414 end
415
416 function maxlength(val, max)
417         val = tostring(val)
418         max = tonumber(max)
419
420         if val ~= nil and max ~= nil then
421                 return (#val <= max)
422         end
423
424         return false
425 end
426
427 function phonedigit(val)
428         return (val:match("^[0-9\*#!%.]+$") ~= nil)
429 end
430
431 function timehhmmss(val)
432         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
433 end
434
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
447
448                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
449
450                 local function is_leap_year(year)
451                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
452                 end
453
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