62b0e0f617b2ef933cdf251b61d9dcf95bb7928c
[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