Merge pull request #577 from cshore/pull-request-safe-file-upload
[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 port(val)
135         val = tonumber(val)
136         return ( val and val >= 0 and val <= 65535 )
137 end
138
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
147
148 function macaddr(val)
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, ":" )
154
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
161
162                 return true
163         end
164
165         return false
166 end
167
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
178
179 function host(val, ipv4only)
180         return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
181 end
182
183 function network(val)
184         return uciname(val) or host(val)
185 end
186
187 function hostport(val, ipv4only)
188         local h, p = val:match("^([^:]+):([^:]+)$")
189         return not not (h and p and host(h, ipv4only) and port(p))
190 end
191
192 function ip4addrport(val, bracket)
193         local h, p = val:match("^([^:]+):([^:]+)$")
194         return (h and p and ip4addr(h) and port(p))
195 end
196
197 function ip4addrport(val)
198         local h, p = val:match("^([^:]+):([^:]+)$")
199         return (h and p and ip4addr(h) and port(p))
200 end
201
202 function ipaddrport(val, bracket)
203         local h, p = val:match("^([^%[%]:]+):([^:]+)$")
204         if (h and p and ip4addr(h) and port(p)) then
205                 return true
206         elseif (bracket == 1) then
207                 h, p = val:match("^%[(.+)%]:([^:]+)$")
208                 if  (h and p and ip6addr(h) and port(p)) then
209                         return true
210                 end
211         end
212         h, p = val:match("^([^%[%]]+):([^:]+)$")
213         return (h and p and ip6addr(h) and port(p))
214 end
215
216 function wpakey(val)
217         if #val == 64 then
218                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
219         else
220                 return (#val >= 8) and (#val <= 63)
221         end
222 end
223
224 function wepkey(val)
225         if val:sub(1, 2) == "s:" then
226                 val = val:sub(3)
227         end
228
229         if (#val == 10) or (#val == 26) then
230                 return (val:match("^[a-fA-F0-9]+$") ~= nil)
231         else
232                 return (#val == 5) or (#val == 13)
233         end
234 end
235
236 function string(val)
237         return true             -- Everything qualifies as valid string
238 end
239
240 function directory( val, seen )
241         local s = fs.stat(val)
242         seen = seen or { }
243
244         if s and not seen[s.ino] then
245                 seen[s.ino] = true
246                 if s.type == "dir" then
247                         return true
248                 elseif s.type == "lnk" then
249                         return directory( fs.readlink(val), seen )
250                 end
251         end
252
253         return false
254 end
255
256 function file( val, seen )
257         local s = fs.stat(val)
258         seen = seen or { }
259
260         if s and not seen[s.ino] then
261                 seen[s.ino] = true
262                 if s.type == "reg" then
263                         return true
264                 elseif s.type == "lnk" then
265                         return file( fs.readlink(val), seen )
266                 end
267         end
268
269         return false
270 end
271
272 function device( val, seen )
273         local s = fs.stat(val)
274         seen = seen or { }
275
276         if s and not seen[s.ino] then
277                 seen[s.ino] = true
278                 if s.type == "chr" or s.type == "blk" then
279                         return true
280                 elseif s.type == "lnk" then
281                         return device( fs.readlink(val), seen )
282                 end
283         end
284
285         return false
286 end
287
288 function uciname(val)
289         return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
290 end
291
292 function range(val, min, max)
293         val = tonumber(val)
294         min = tonumber(min)
295         max = tonumber(max)
296
297         if val ~= nil and min ~= nil and max ~= nil then
298                 return ((val >= min) and (val <= max))
299         end
300
301         return false
302 end
303
304 function min(val, min)
305         val = tonumber(val)
306         min = tonumber(min)
307
308         if val ~= nil and min ~= nil then
309                 return (val >= min)
310         end
311
312         return false
313 end
314
315 function max(val, max)
316         val = tonumber(val)
317         max = tonumber(max)
318
319         if val ~= nil and max ~= nil then
320                 return (val <= max)
321         end
322
323         return false
324 end
325
326 function rangelength(val, min, max)
327         val = tostring(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 minlength(val, min)
339         val = tostring(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 maxlength(val, max)
350         val = tostring(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 phonedigit(val)
361         return (val:match("^[0-9\*#!%.]+$") ~= nil)
362 end
363
364 function timehhmmss(val)
365         return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
366 end
367
368 function dateyyyymmdd(val)
369         if val ~= nil then
370                 yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
371                 if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
372                         return false;
373                 end
374                 year = tonumber(yearstr)
375                 month = tonumber(monthstr)
376                 day = tonumber(daystr)
377                 if (year == nil) or (month == nil) or (day == nil) then
378                         return false;
379                 end
380
381                 local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
382
383                 local function is_leap_year(year)
384                         return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
385                 end
386
387                 function get_days_in_month(month, year)
388                         if (month == 2) and is_leap_year(year) then
389                                 return 29
390                         else
391                                 return days_in_month[month]
392                         end
393                 end
394                 if (year < 2015) then
395                         return false
396                 end 
397                 if ((month == 0) or (month > 12)) then
398                         return false
399                 end 
400                 if ((day == 0) or (day > get_days_in_month(month, year))) then
401                         return false
402                 end
403                 return true
404         end
405         return false
406 end
407