ef6c6881b4fa2b32d0ba206549c6531996eec7f7
[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         local c = tree
103
104         for s in pathinfo:gmatch("([%w-]+)") do
105                 table.insert(request, s)
106         end
107
108         dispatch()
109 end
110
111 -- Dispatches a request
112 function dispatch()
113         if not built_tree then
114                 createtree()
115         end
116
117         local c = tree
118         local track = {}
119
120         for i, s in ipairs(request) do
121                 c = c.nodes[s]
122                 if not c or c.leaf then
123                         break
124                 end
125
126                 for k, v in pairs(c) do
127                         track[k] = v
128                 end
129         end
130
131         if track.sysauth then
132                 local accs = track.sysauth
133                 accs = (type(accs) == "string") and {accs} or accs
134                 
135                 local function sysauth(user, password)
136                         return (luci.util.contains(accs, user)
137                                 and luci.sys.user.checkpasswd(user, password)) 
138                 end
139                 
140                 if not luci.http.basic_auth(sysauth) then
141                         error401()
142                         return
143                 end
144         end
145
146         if track.i18n then
147                 require("luci.i18n").loadc(track.i18n)
148         end
149
150         if track.setgroup then
151                 luci.sys.process.setgroup(track.setgroup)
152         end
153
154         if track.setuser then
155                 luci.sys.process.setuser(track.setuser)
156         end
157         
158         -- Init template engine
159         local tpl = require("luci.template")
160         tpl.viewns.translate   = function(...) return require("luci.i18n").translate(...) end
161         tpl.viewns.controller  = luci.http.dispatcher()
162         tpl.viewns.uploadctrl  = luci.http.dispatcher_upload()
163         tpl.viewns.media       = luci.config.main.mediaurlbase
164         tpl.viewns.resource    = luci.config.main.resourcebase
165         tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
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         setmetatable(obj, {__index = clone})
307         
308         return obj
309 end
310
311 -- Shortcut for creating a dispatching node
312 function entry(path, target, title, order)
313         local c = node(path)
314         
315         c.target = target
316         c.title  = title
317         c.order  = order
318         c.module = getfenv(2)._NAME
319
320         return c
321 end
322
323 -- Fetch a dispatching node
324 function node(...)
325         local c = tree
326
327         if arg[1] and type(arg[1]) == "table" then
328                 arg = arg[1]
329         end
330
331         for k,v in ipairs(arg) do
332                 if not c.nodes[v] then
333                         c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
334                 end
335
336                 c = c.nodes[v]
337         end
338
339         c.path = arg
340
341         return c
342 end
343
344 -- Subdispatchers --
345 function alias(...)
346         local req = arg
347         return function()
348                 request = req
349                 dispatch()
350         end
351 end
352
353 function rewrite(n, ...)
354         local req = arg
355         return function()
356                 for i=1,n do 
357                         table.remove(request, 1)
358                 end
359                 
360                 for i,r in ipairs(req) do
361                         table.insert(request, i, r)
362                 end
363                 
364                 dispatch()
365         end
366 end
367
368 function call(name)
369         return function() getfenv()[name]() end
370 end
371
372 function template(name)
373         require("luci.template")
374         return function() luci.template.render(name) end
375 end
376
377 function cbi(model)
378         require("luci.cbi")
379         require("luci.template")
380
381         return function()
382                 local stat, res = pcall(luci.cbi.load, model)
383                 if not stat then
384                         error500(res)
385                         return true
386                 end
387
388                 local stat, err = pcall(res.parse, res)
389                 if not stat then
390                         error500(err)
391                         return true
392                 end
393
394                 luci.template.render("cbi/header")
395                 res:render()
396                 luci.template.render("cbi/footer")
397         end
398 end