* Moved Freifunk status pages to admin-core and linked them in Freifunk
[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 -- Reassigns a node to another position
297 function assign(path, clone, title, order)
298         local obj  = node(path)
299         obj.nodes  = nil
300         obj.module = nil
301         
302         obj.title = title
303         obj.order = order
304         
305         setmetatable(obj, {__index = clone})
306         
307         return obj
308 end
309
310 -- Shortcut for creating a dispatching node
311 function entry(path, target, title, order)
312         local c = node(path)
313         
314         c.target = target
315         c.title  = title
316         c.order  = order
317         c.module = getfenv(2)._NAME
318
319         return c
320 end
321
322 -- Fetch a dispatching node
323 function node(...)
324         local c = tree
325
326         if arg[1] and type(arg[1]) == "table" then
327                 arg = arg[1]
328         end
329
330         for k,v in ipairs(arg) do
331                 if not c.nodes[v] then
332                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
333                 end
334
335                 c = c.nodes[v]
336         end
337
338         return c
339 end
340
341 -- Subdispatchers --
342 function alias(...)
343         local req = arg
344         return function()
345                 request = req
346                 dispatch()
347         end
348 end
349
350 function rewrite(n, ...)
351         local req = arg
352         return function()
353                 for i=1,n do 
354                         table.remove(request, 1)
355                 end
356                 
357                 for i,r in ipairs(req) do
358                         table.insert(request, i, r)
359                 end
360                 
361                 dispatch()
362         end
363 end
364
365 function call(name)
366         return function() getfenv()[name]() end
367 end
368
369 function template(name)
370         require("luci.template")
371         return function() luci.template.render(name) end
372 end
373
374 function cbi(model)
375         require("luci.cbi")
376         require("luci.template")
377
378         return function()
379                 local stat, res = pcall(luci.cbi.load, model)
380                 if not stat then
381                         error500(res)
382                         return true
383                 end
384
385                 local stat, err = pcall(res.parse, res)
386                 if not stat then
387                         error500(err)
388                         return true
389                 end
390
391                 luci.template.render("cbi/header")
392                 res:render()
393                 luci.template.render("cbi/footer")
394         end
395 end