025ae382d0b78385af6cdea349b5224732416688
[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         return c
340 end
341
342 -- Subdispatchers --
343 function alias(...)
344         local req = arg
345         return function()
346                 request = req
347                 dispatch()
348         end
349 end
350
351 function rewrite(n, ...)
352         local req = arg
353         return function()
354                 for i=1,n do 
355                         table.remove(request, 1)
356                 end
357                 
358                 for i,r in ipairs(req) do
359                         table.insert(request, i, r)
360                 end
361                 
362                 dispatch()
363         end
364 end
365
366 function call(name)
367         return function() getfenv()[name]() end
368 end
369
370 function template(name)
371         require("luci.template")
372         return function() luci.template.render(name) end
373 end
374
375 function cbi(model)
376         require("luci.cbi")
377         require("luci.template")
378
379         return function()
380                 local stat, res = pcall(luci.cbi.load, model)
381                 if not stat then
382                         error500(res)
383                         return true
384                 end
385
386                 local stat, err = pcall(res.parse, res)
387                 if not stat then
388                         error500(err)
389                         return true
390                 end
391
392                 luci.template.render("cbi/header")
393                 res:render()
394                 luci.template.render("cbi/footer")
395         end
396 end