1 -- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
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
11 module "luci.cbi.datatypes"
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
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
48 function neg(v, ...)
49         return _M['or'](v:gsub("^%s*!%s*", ""), ...)
50 end
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
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
74         return false
75 end
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
83         return false
84 end
86 function integer(val)
87         local n = tonumber(val)
88         if n ~= nil and math.floor(n) == n then
89                 return true
90         end
92         return false
93 end
95 function ufloat(val)
96         local n = tonumber(val)
97         return ( n ~= nil and n >= 0 )
98 end
100 function float(val)
101         return ( tonumber(val) ~= nil )
102 end
106 end
109         if val then
110                 return ip.IPv4(val) and true or false
111         end
113         return false
114 end
116 function ip4prefix(val)
117         val = tonumber(val)
118         return ( val and val >= 0 and val <= 32 )
119 end
122         if val then
123                 return ip.IPv6(val) and true or false
124         end
126         return false
127 end
129 function ip6prefix(val)
130         val = tonumber(val)
131         return ( val and val >= 0 and val <= 128 )
132 end
134 function port(val)
135         val = tonumber(val)
136         return ( val and val >= 0 and val <= 65535 )
137 end
139 function portrange(val)
140         local p1, p2 = val:match("^(%d+)%-(%d+)\$")
141         if p1 and p2 and port(p1) and port(p2) then
142                 return true
143         else
144                 return port(val)
145         end
146 end
149         if val and val:match(
150                 "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
151                  "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+\$"
152         ) then
153                 local parts = util.split( val, ":" )
155                 for i = 1,6 do
156                         parts[i] = tonumber( parts[i], 16 )
157                         if parts[i] < 0 or parts[i] > 255 then
158                                 return false
159                         end
160                 end
162                 return true
163         end
165         return false
166 end
168 function hostname(val)
169         if val and (#val < 254) and (
170            val:match("^[a-zA-Z_]+\$") or
171            (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]\$") and
172             val:match("[^0-9%.]"))
173         ) then
174                 return true
175         end
176         return false
177 end
179 function host(val)
181 end
183 function network(val)
184         return uciname(val) or host(val)
185 end
187 function hostport(val)
188         local h, p = val:match("^([^:]+):([^:]+)\$")
189         return not not (h and p and host(h) and port(p))
190 end
193         local h, p = val:match("^([^:]+):([^:]+)\$")
194         return not not (h and p and ipaddr(h) and port(p))
195 end
197 function wpakey(val)
198         if #val == 64 then
199                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
200         else
201                 return (#val >= 8) and (#val <= 63)
202         end
203 end
205 function wepkey(val)
206         if val:sub(1, 2) == "s:" then
207                 val = val:sub(3)
208         end
210         if (#val == 10) or (#val == 26) then
211                 return (val:match("^[a-fA-F0-9]+\$") ~= nil)
212         else
213                 return (#val == 5) or (#val == 13)
214         end
215 end
217 function string(val)
218         return true             -- Everything qualifies as valid string
219 end
221 function directory( val, seen )
222         local s = fs.stat(val)
223         seen = seen or { }
225         if s and not seen[s.ino] then
226                 seen[s.ino] = true
227                 if s.type == "dir" then
228                         return true
229                 elseif s.type == "lnk" then
231                 end
232         end
234         return false
235 end
237 function file( val, seen )
238         local s = fs.stat(val)
239         seen = seen or { }
241         if s and not seen[s.ino] then
242                 seen[s.ino] = true
243                 if s.type == "reg" then
244                         return true
245                 elseif s.type == "lnk" then
247                 end
248         end
250         return false
251 end
253 function device( val, seen )
254         local s = fs.stat(val)
255         seen = seen or { }
257         if s and not seen[s.ino] then
258                 seen[s.ino] = true
259                 if s.type == "chr" or s.type == "blk" then
260                         return true
261                 elseif s.type == "lnk" then
263                 end
264         end
266         return false
267 end
269 function uciname(val)
270         return (val:match("^[a-zA-Z0-9_]+\$") ~= nil)
271 end
273 function range(val, min, max)
274         val = tonumber(val)
275         min = tonumber(min)
276         max = tonumber(max)
278         if val ~= nil and min ~= nil and max ~= nil then
279                 return ((val >= min) and (val <= max))
280         end
282         return false
283 end
285 function min(val, min)
286         val = tonumber(val)
287         min = tonumber(min)
289         if val ~= nil and min ~= nil then
290                 return (val >= min)
291         end
293         return false
294 end
296 function max(val, max)
297         val = tonumber(val)
298         max = tonumber(max)
300         if val ~= nil and max ~= nil then
301                 return (val <= max)
302         end
304         return false
305 end
307 function rangelength(val, min, max)
308         val = tostring(val)
309         min = tonumber(min)
310         max = tonumber(max)
312         if val ~= nil and min ~= nil and max ~= nil then
313                 return ((#val >= min) and (#val <= max))
314         end
316         return false
317 end
319 function minlength(val, min)
320         val = tostring(val)
321         min = tonumber(min)
323         if val ~= nil and min ~= nil then
324                 return (#val >= min)
325         end
327         return false
328 end
330 function maxlength(val, max)
331         val = tostring(val)
332         max = tonumber(max)
334         if val ~= nil and max ~= nil then
335                 return (#val <= max)
336         end
338         return false
339 end
341 function phonedigit(val)
342         return (val:match("^[0-9\*#!%.]+\$") ~= nil)
343 end
345 function timehhmmss(val)
346         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]\$") ~= nil)
347 end
349 function dateyyyymmdd(val)
350         if val ~= nil then
351                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)\$")
352                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
353                         return false;
354                 end
355                 year = tonumber(yearstr)
356                 month = tonumber(monthstr)
357                 day = tonumber(daystr)
358                 if (year == nil) or (month == nil) or (day == nil) then
359                         return false;
360                 end
362                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
364                 local function is_leap_year(year)
365                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
366                 end
368                 function get_days_in_month(month, year)
369                         if (month == 2) and is_leap_year(year) then
370                                 return 29
371                         else
372                                 return days_in_month[month]
373                         end
374                 end
375                 if (year < 2015) then
376                         return false
377                 end
378                 if ((month == 0) or (month > 12)) then
379                         return false
380                 end
381                 if ((day == 0) or (day > get_days_in_month(month, year))) then
382                         return false
383                 end
384                 return true
385         end
386         return false
387 end