99ba9adca3a425024b929f4ebed4b52f895f8349
[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.init")
28 require("luci.http")
29 require("luci.sys")
30 require("luci.fs")
31
32 -- Dirty OpenWRT fix
33 if (os.time() < luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua")) then
34         os.execute('date -s '..os.date('%m%d%H%M%Y', luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua"))..' > /dev/null 2>&1')
35 end
36
37 -- Local dispatch database
38 local tree = {nodes={}}
39
40 -- Index table
41 local index = {}
42
43 -- Global request object
44 request = {}
45
46 -- Active dispatched node
47 dispatched = nil
48
49 -- Status fields
50 built_index = false
51 built_tree  = false
52
53 -- Fastindex
54 local fi
55
56
57 -- Builds a URL
58 function build_url(...)
59         return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
60 end
61
62 -- Prints an error message or renders the "error401" template if available
63 function error401(message)
64         message = message or "Unauthorized"
65
66         require("luci.template")
67         if not pcall(luci.template.render, "error401") then
68                 luci.http.prepare_content("text/plain")
69                 print(message)
70         end
71         return false
72 end
73
74 -- Sends a 404 error code and renders the "error404" template if available
75 function error404(message)
76         luci.http.status(404, "Not Found")
77         message = message or "Not Found"
78
79         require("luci.template")
80         if not pcall(luci.template.render, "error404") then
81                 luci.http.prepare_content("text/plain")
82                 print(message)
83         end
84         return false
85 end
86
87 -- Sends a 500 error code and renders the "error500" template if available
88 function error500(message)
89         luci.http.status(500, "Internal Server Error")
90
91         require("luci.template")
92         if not pcall(luci.template.render, "error500", {message=message}) then
93                 luci.http.prepare_content("text/plain")
94                 print(message)
95         end
96         return false
97 end
98
99 -- Creates a request object for dispatching
100 function httpdispatch()
101         local pathinfo = luci.http.env.PATH_INFO or ""
102
103         for node in pathinfo:gmatch("[^/]+") do
104                 table.insert(request, node)
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.uci         = require("luci.model.uci").config
165         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. (luci.http.env.PATH_INFO or "")
166         
167
168         if c and type(c.target) == "function" then
169                 dispatched = c
170                 stat, mod = pcall(require, c.module)
171                 if stat then
172                         luci.util.updfenv(c.target, mod)
173                 end
174                 
175                 stat, err = pcall(c.target)
176                 if not stat then
177                         error500(err)
178                 end
179         else
180                 error404()
181         end
182 end
183
184 -- Generates the dispatching tree
185 function createindex()
186         index = {}
187         local path = luci.sys.libpath() .. "/controller/"
188         local suff = ".lua"
189         
190         if pcall(require, "luci.fastindex") then
191                 createindex_fastindex(path, suff)
192         else
193                 createindex_plain(path, suff)
194         end
195         
196         built_index = true
197 end
198
199 -- Uses fastindex to create the dispatching tree
200 function createindex_fastindex(path, suffix)    
201         if not fi then
202                 fi = luci.fastindex.new("index")
203                 fi.add(path .. "*" .. suffix)
204                 fi.add(path .. "*/*" .. suffix)
205         end
206         fi.scan()
207         
208         for k, v in pairs(fi.indexes) do
209                 index[v[2]] = v[1]
210         end
211 end
212
213 -- Calls the index function of all available controllers
214 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
215 function createindex_plain(path, suffix)
216         if built_index then
217                 return
218         end
219
220         local cache = nil 
221         
222         local controllers = luci.util.combine(
223                 luci.fs.glob(path .. "*" .. suffix) or {},
224                 luci.fs.glob(path .. "*/*" .. suffix) or {}
225         )
226         
227         if indexcache then
228                 cache = luci.fs.mtime(indexcache)
229                 
230                 if not cache then
231                         luci.fs.mkdir(indexcache)
232                         luci.fs.chmod(indexcache, "a=,u=rwx")
233                         cache = luci.fs.mtime(indexcache)
234                 end
235         end
236
237         for i,c in ipairs(controllers) do
238                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
239                 local cachefile
240                 local stime
241                 local ctime
242                 
243                 if cache then
244                         cachefile = indexcache .. "/" .. module
245                         stime = luci.fs.mtime(c) or 0
246                         ctime = luci.fs.mtime(cachefile) or 0
247                 end
248                 
249                 if not cache or stime > ctime then 
250                         stat, mod = pcall(require, module)
251         
252                         if stat and mod and type(mod.index) == "function" then
253                                 index[module] = mod.index
254                                 
255                                 if cache then
256                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
257                                 end
258                         end
259                 else
260                         index[module] = loadfile(cachefile)
261                 end
262         end
263 end
264
265 -- Creates the dispatching tree from the index
266 function createtree()
267         if not built_index then
268                 createindex()
269         end
270         
271         require("luci.i18n")
272                 
273         -- Load default translation
274         luci.i18n.loadc("default")
275         
276         local scope = luci.util.clone(_G)
277         for k,v in pairs(_M) do
278                 if type(v) == "function" then
279                         scope[k] = v
280                 end
281         end
282
283         for k, v in pairs(index) do
284                 scope._NAME = k
285                 setfenv(v, scope)
286                 
287                 local stat, err = pcall(v)
288                 if not stat then
289                         error500(err)   
290                         os.exit(1)
291                 end
292         end
293         
294         built_tree = true
295 end
296
297 -- Reassigns a node to another position
298 function assign(path, clone, title, order)
299         local obj  = node(path)
300         obj.nodes  = nil
301         obj.module = nil
302         
303         obj.title = title
304         obj.order = order
305         
306         local c = tree
307         for k, v in ipairs(clone) do
308                 if not c.nodes[v] then
309                         c.nodes[v] = {nodes={}}
310                 end
311
312                 c = c.nodes[v]
313         end
314         
315         setmetatable(obj, {__index = c})
316         
317         return obj
318 end
319
320 -- Shortcut for creating a dispatching node
321 function entry(path, target, title, order)
322         local c = node(path)
323         
324         c.target = target
325         c.title  = title
326         c.order  = order
327         c.module = getfenv(2)._NAME
328
329         return c
330 end
331
332 -- Fetch a dispatching node
333 function node(...)
334         local c = tree
335
336         arg.n = nil
337         if arg[1] then
338                 if type(arg[1]) == "table" then
339                         arg = arg[1]
340                 end
341         end
342
343         for k,v in ipairs(arg) do
344                 if not c.nodes[v] then
345                         c.nodes[v] = {nodes={}}
346                 end
347
348                 c = c.nodes[v]
349         end
350
351         c.module = getfenv(2)._NAME
352         c.path = arg
353
354         return c
355 end
356
357 -- Subdispatchers --
358 function alias(...)
359         local req = arg
360         return function()
361                 request = req
362                 dispatch()
363         end
364 end
365
366 function rewrite(n, ...)
367         local req = arg
368         return function()
369                 for i=1,n do 
370                         table.remove(request, 1)
371                 end
372                 
373                 for i,r in ipairs(req) do
374                         table.insert(request, i, r)
375                 end
376                 
377                 dispatch()
378         end
379 end
380
381 function call(name)
382         return function() getfenv()[name]() end
383 end
384
385 function template(name)
386         require("luci.template")
387         return function() luci.template.render(name) end
388 end
389
390 function cbi(model)
391         require("luci.cbi")
392         require("luci.template")
393
394         return function()
395                 local stat, res = pcall(luci.cbi.load, model)
396                 if not stat then
397                         error500(res)
398                         return true
399                 end
400
401                 local stat, err = pcall(res.parse, res)
402                 if not stat then
403                         error500(err)
404                         return true
405                 end
406
407                 luci.template.render("cbi/header")
408                 res:render()
409                 luci.template.render("cbi/footer")
410         end
411 end