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