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