* Added native basic authentication support
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26 module("luci.dispatcher", package.seeall)
27 require("luci.http")
28 require("luci.sys")
29 require("luci.fs")
30
31 -- Dirty OpenWRT fix
32 if (os.time() < luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua")) then
33         os.execute('date -s '..os.date('%m%d%H%M%Y', luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua"))..' > /dev/null 2>&1')
34 end
35
36 -- Local dispatch database
37 local tree = {nodes={}}
38
39 -- Index table
40 local index = {}
41
42 -- Global request object
43 request = {}
44
45 -- Active dispatched node
46 dispatched = nil
47
48 -- Status fields
49 built_index = false
50 built_tree  = false
51
52 -- Fastindex
53 local fi
54
55
56 -- Builds a URL
57 function build_url(...)
58         return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
59 end
60
61 -- Prints an error message or renders the "error401" template if available
62 function error401(message)
63         message = message or "Unauthorized"
64
65         require("luci.template")
66         if not pcall(luci.template.render, "error401") then
67                 luci.http.prepare_content("text/plain")
68                 print(message)
69         end
70         return false
71 end
72
73 -- Sends a 404 error code and renders the "error404" template if available
74 function error404(message)
75         luci.http.status(404, "Not Found")
76         message = message or "Not Found"
77
78         require("luci.template")
79         if not pcall(luci.template.render, "error404") then
80                 luci.http.prepare_content("text/plain")
81                 print(message)
82         end
83         return false
84 end
85
86 -- Sends a 500 error code and renders the "error500" template if available
87 function error500(message)
88         luci.http.status(500, "Internal Server Error")
89
90         require("luci.template")
91         if not pcall(luci.template.render, "error500", {message=message}) then
92                 luci.http.prepare_content("text/plain")
93                 print(message)
94         end
95         return false
96 end
97
98 -- Creates a request object for dispatching
99 function httpdispatch()
100         local pathinfo = luci.http.env.PATH_INFO or ""
101         local c = tree
102
103         for s in pathinfo:gmatch("([%w-]+)") do
104                 table.insert(request, s)
105         end
106
107         dispatch()
108 end
109
110 -- Dispatches a request
111 function dispatch()
112         if not built_tree then
113                 createtree()
114         end
115
116         local c = tree
117         local track = {}
118
119         for i, s in ipairs(request) do
120                 c = c.nodes[s]
121                 if not c or c.leaf then
122                         break
123                 end
124
125                 for k, v in pairs(c) do
126                         track[k] = v
127                 end
128         end
129
130         if track.sysauth then
131                 local accs = track.sysauth
132                 accs = (type(accs) == "string") and {accs} or accs
133                 
134                 local function sysauth(user, password)
135                         return (luci.util.contains(accs, user)
136                                 and luci.sys.user.checkpasswd(user, password)) 
137                 end
138                 
139                 if not luci.http.basic_auth(sysauth) then
140                         error401()
141                         return
142                 end
143         end
144
145         if track.i18n then
146                 require("luci.i18n").loadc(track.i18n)
147         end
148
149         if track.setgroup then
150                 luci.sys.process.setgroup(track.setgroup)
151         end
152
153         if track.setuser then
154                 luci.sys.process.setuser(track.setuser)
155         end
156         
157         -- Init template engine
158         local tpl = require("luci.template")
159         tpl.viewns.translate   = function(...) return require("luci.i18n").translate(...) end
160         tpl.viewns.controller  = luci.http.dispatcher()
161         tpl.viewns.uploadctrl  = luci.http.dispatcher_upload()
162         tpl.viewns.media       = luci.config.main.mediaurlbase
163         tpl.viewns.resource    = luci.config.main.resourcebase
164         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
165         
166
167         if c and type(c.target) == "function" then
168                 dispatched = c
169                 stat, mod = pcall(require, c.module)
170                 if stat then
171                         luci.util.updfenv(c.target, mod)
172                 end
173                 
174                 stat, err = pcall(c.target)
175                 if not stat then
176                         error500(err)
177                 end
178         else
179                 error404()
180         end
181 end
182
183 -- Generates the dispatching tree
184 function createindex()
185         index = {}
186         local path = luci.sys.libpath() .. "/controller/"
187         local suff = ".lua"
188         
189         if pcall(require, "luci.fastindex") then
190                 createindex_fastindex(path, suff)
191         else
192                 createindex_plain(path, suff)
193         end
194         
195         built_index = true
196 end
197
198 -- Uses fastindex to create the dispatching tree
199 function createindex_fastindex(path, suffix)    
200         if not fi then
201                 fi = luci.fastindex.new("index")
202                 fi.add(path .. "*" .. suffix)
203                 fi.add(path .. "*/*" .. suffix)
204         end
205         fi.scan()
206         
207         for k, v in pairs(fi.indexes) do
208                 index[v[2]] = v[1]
209         end
210 end
211
212 -- Calls the index function of all available controllers
213 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
214 function createindex_plain(path, suffix)
215         if built_index then
216                 return
217         end
218
219         local cache = nil 
220         
221         local controllers = luci.util.combine(
222                 luci.fs.glob(path .. "*" .. suffix) or {},
223                 luci.fs.glob(path .. "*/*" .. suffix) or {}
224         )
225         
226         if indexcache then
227                 cache = luci.fs.mtime(indexcache)
228                 
229                 if not cache then
230                         luci.fs.mkdir(indexcache)
231                         luci.fs.chmod(indexcache, "a=,u=rwx")
232                         cache = luci.fs.mtime(indexcache)
233                 end
234         end
235
236         for i,c in ipairs(controllers) do
237                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
238                 local cachefile
239                 local stime
240                 local ctime
241                 
242                 if cache then
243                         cachefile = indexcache .. "/" .. module
244                         stime = luci.fs.mtime(c) or 0
245                         ctime = luci.fs.mtime(cachefile) or 0
246                 end
247                 
248                 if not cache or stime > ctime then 
249                         stat, mod = pcall(require, module)
250         
251                         if stat and mod and type(mod.index) == "function" then
252                                 index[module] = mod.index
253                                 
254                                 if cache then
255                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
256                                 end
257                         end
258                 else
259                         index[module] = loadfile(cachefile)
260                 end
261         end
262 end
263
264 -- Creates the dispatching tree from the index
265 function createtree()
266         if not built_index then
267                 createindex()
268         end
269         
270         require("luci.i18n")
271                 
272         -- Load default translation
273         luci.i18n.loadc("default")
274         
275         local scope = luci.util.clone(_G)
276         for k,v in pairs(_M) do
277                 if type(v) == "function" then
278                         scope[k] = v
279                 end
280         end
281
282         for k, v in pairs(index) do
283                 scope._NAME = k
284                 setfenv(v, scope)
285                 
286                 local stat, err = pcall(v)
287                 if not stat then
288                         error500(err)   
289                         os.exit(1)
290                 end
291         end
292         
293         built_tree = true
294 end
295
296 -- Shortcut for creating a dispatching node
297 function entry(path, target, title, order, add)
298         add = add or {}
299
300         local c = node(path)
301         c.target = target
302         c.title  = title
303         c.order  = order
304         c.module = getfenv(2)._NAME
305
306         for k,v in pairs(add) do
307                 c[k] = v
308         end
309
310         return c
311 end
312
313 -- Fetch a dispatching node
314 function node(...)
315         local c = tree
316
317         if arg[1] and type(arg[1]) == "table" then
318                 arg = arg[1]
319         end
320
321         for k,v in ipairs(arg) do
322                 if not c.nodes[v] then
323                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
324                 end
325
326                 c = c.nodes[v]
327         end
328
329         return c
330 end
331
332 -- Subdispatchers --
333 function alias(...)
334         local req = arg
335         return function()
336                 request = req
337                 dispatch()
338         end
339 end
340
341 function rewrite(n, ...)
342         local req = arg
343         return function()
344                 for i=1,n do 
345                         table.remove(request, 1)
346                 end
347                 
348                 for i,r in ipairs(req) do
349                         table.insert(request, i, r)
350                 end
351                 
352                 dispatch()
353         end
354 end
355
356 function call(name)
357         return function() getfenv()[name]() end
358 end
359
360 function template(name)
361         require("luci.template")
362         return function() luci.template.render(name) end
363 end
364
365 function cbi(model)
366         require("luci.cbi")
367         require("luci.template")
368
369         return function()
370                 local stat, res = pcall(luci.cbi.load, model)
371                 if not stat then
372                         error500(res)
373                         return true
374                 end
375
376                 local stat, err = pcall(res.parse, res)
377                 if not stat then
378                         error500(err)
379                         return true
380                 end
381
382                 luci.template.render("cbi/header")
383                 res:render()
384                 luci.template.render("cbi/footer")
385         end
386 end