df23aaf13591b730bca7c309aaee0cdc49f2e4fd
[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