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