756f3cc7d50f1f4dabf50dcc735666bb6a141518
[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 context = luci.util.threadlocal()
33
34 -- Index table
35 local index = nil
36
37 -- Fastindex
38 local fi
39
40
41 -- Builds a URL
42 function build_url(...)
43         return luci.http.getenv("SCRIPT_NAME") .. "/" .. table.concat(arg, "/")
44 end
45
46 -- Sends a 404 error code and renders the "error404" template if available
47 function error404(message)
48         luci.http.status(404, "Not Found")
49         message = message or "Not Found"
50
51         require("luci.template")
52         if not luci.util.copcall(luci.template.render, "error404") then
53                 luci.http.prepare_content("text/plain")
54                 luci.http.write(message)
55         end
56         return false
57 end
58
59 -- Sends a 500 error code and renders the "error500" template if available
60 function error500(message)
61         luci.http.status(500, "Internal Server Error")
62
63         require("luci.template")
64         if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
65                 luci.http.prepare_content("text/plain")
66                 luci.http.write(message)
67         end
68         return false
69 end
70
71 -- Renders an authorization form
72 function sysauth(default)
73         local user = luci.http.formvalue("username")
74         local pass = luci.http.formvalue("password")
75         
76         if user and luci.sys.user.checkpasswd(user, pass) then
77                 local sid = luci.sys.uniqueid(16)
78                 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
79                 luci.sauth.write(sid, user)
80                 return true
81         else
82                 require("luci.i18n")
83                 require("luci.template")
84                 context.path = {}
85                 luci.template.render("sysauth", {duser=default, fuser=user})
86                 return false
87         end
88 end
89
90 -- Creates a request object for dispatching
91 function httpdispatch(request)
92         luci.http.context.request = request
93         context.request = {}
94         local pathinfo = request:getenv("PATH_INFO") or ""
95
96         for node in pathinfo:gmatch("[^/]+") do
97                 table.insert(context.request, node)
98         end
99
100         dispatch(context.request)
101         luci.http.close()
102 end
103
104 -- Dispatches a request
105 function dispatch(request)
106         context.path = request
107         
108         require("luci.i18n")
109         luci.i18n.setlanguage(require("luci.config").main.lang)
110         
111         if not context.tree then
112                 createtree()
113         end
114         
115         local c = context.tree
116         local track = {}
117
118         for i, s in ipairs(request) do
119                 c = c.nodes[s]
120                 if not c or c.leaf then
121                         break
122                 end
123
124                 for k, v in pairs(c) do
125                         track[k] = v
126                 end
127         end
128
129         if track.i18n then
130                 require("luci.i18n").loadc(track.i18n)
131         end
132         
133         -- Init template engine
134         local tpl = require("luci.template")
135         local viewns = {}
136         tpl.context.viewns = viewns
137         viewns.write       = luci.http.write
138         viewns.translate   = function(...) return require("luci.i18n").translate(...) end
139         viewns.controller  = luci.http.getenv("SCRIPT_NAME")
140         viewns.media       = luci.config.main.mediaurlbase
141         viewns.resource    = luci.config.main.resourcebase
142         viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
143         
144         if track.sysauth then
145                 require("luci.sauth")
146                 local def  = (type(track.sysauth) == "string") and track.sysauth
147                 local accs = def and {track.sysauth} or track.sysauth
148                 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
149                 
150                 
151                 if not luci.util.contains(accs, user) then
152                         if not sysauth(def) then
153                                 return
154                         end
155                 end
156         end
157
158         if track.setgroup then
159                 luci.sys.process.setgroup(track.setgroup)
160         end
161
162         if track.setuser then
163                 luci.sys.process.setuser(track.setuser)
164         end
165
166         if c and type(c.target) == "function" then
167                 context.dispatched = c
168                 stat, mod = luci.util.copcall(require, c.module)
169                 if stat then
170                         luci.util.updfenv(c.target, mod)
171                 end
172                 
173                 stat, err = luci.util.copcall(c.target)
174                 if not stat then
175                         error500(err)
176                 end
177         else
178                 error404()
179         end
180 end
181
182 -- Generates the dispatching tree
183 function createindex()
184         local path = luci.sys.libpath() .. "/controller/"
185         local suff = ".lua"
186         
187         if luci.util.copcall(require, "luci.fastindex") then
188                 createindex_fastindex(path, suff)
189         else
190                 createindex_plain(path, suff)
191         end
192 end
193
194 -- Uses fastindex to create the dispatching tree
195 function createindex_fastindex(path, suffix)
196         index = {}
197                 
198         if not fi then
199                 fi = luci.fastindex.new("index")
200                 fi.add(path .. "*" .. suffix)
201                 fi.add(path .. "*/*" .. suffix)
202         end
203         fi.scan()
204         
205         for k, v in pairs(fi.indexes) do
206                 index[v[2]] = v[1]
207         end
208 end
209
210 -- Calls the index function of all available controllers
211 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
212 function createindex_plain(path, suffix)
213         index = {}
214
215         local cache = nil 
216         
217         local controllers = luci.util.combine(
218                 luci.fs.glob(path .. "*" .. suffix) or {},
219                 luci.fs.glob(path .. "*/*" .. suffix) or {}
220         )
221         
222         if indexcache then
223                 cache = luci.fs.mtime(indexcache)
224                 
225                 if not cache then
226                         luci.fs.mkdir(indexcache)
227                         luci.fs.chmod(indexcache, "a=,u=rwx")
228                         cache = luci.fs.mtime(indexcache)
229                 end
230         end
231
232         for i,c in ipairs(controllers) do
233                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
234                 local cachefile
235                 local stime
236                 local ctime
237                 
238                 if cache then
239                         cachefile = indexcache .. "/" .. module
240                         stime = luci.fs.mtime(c) or 0
241                         ctime = luci.fs.mtime(cachefile) or 0
242                 end
243                 
244                 if not cache or stime > ctime then 
245                         stat, mod = luci.util.copcall(require, module)
246         
247                         if stat and mod and type(mod.index) == "function" then
248                                 index[module] = mod.index
249                                 
250                                 if cache then
251                                         luci.fs.writefile(cachefile, luci.util.dump(mod.index))
252                                 end
253                         end
254                 else
255                         index[module] = loadfile(cachefile)
256                 end
257         end
258 end
259
260 -- Creates the dispatching tree from the index
261 function createtree()
262         if not index then
263                 createindex()
264         end
265         
266         context.tree = {nodes={}}
267         require("luci.i18n")
268                 
269         -- Load default translation
270         luci.i18n.loadc("default")
271         
272         local scope = luci.util.clone(_G)
273         for k,v in pairs(luci.dispatcher) do
274                 if type(v) == "function" then
275                         scope[k] = v
276                 end
277         end
278
279         for k, v in pairs(index) do
280                 scope._NAME = k
281                 setfenv(v, scope)
282
283                 local stat, err = luci.util.copcall(v)
284                 if not stat then
285                         error500("createtree failed: " .. k .. ": " .. err)
286                         luci.http.close()
287                         os.exit(1)
288                 end
289         end
290 end
291
292 -- Reassigns a node to another position
293 function assign(path, clone, title, order)
294         local obj  = node(unpack(path))
295         obj.nodes  = nil
296         obj.module = nil
297         
298         obj.title = title
299         obj.order = order
300         
301         local c = context.tree
302         for k, v in ipairs(clone) do
303                 if not c.nodes[v] then
304                         c.nodes[v] = {nodes={}}
305                 end
306
307                 c = c.nodes[v]
308         end
309         
310         setmetatable(obj, {__index = c})
311         
312         return obj
313 end
314
315 -- Shortcut for creating a dispatching node
316 function entry(path, target, title, order)
317         local c = node(unpack(path))
318         
319         c.target = target
320         c.title  = title
321         c.order  = order
322         c.module = getfenv(2)._NAME
323
324         return c
325 end
326
327 -- Checks whether a node exists
328 function registered(...)
329         local c = context.tree
330
331         for k,v in ipairs(arg) do
332                 if not c.nodes[v] then
333                         return false
334                 end
335
336                 c = c.nodes[v]
337         end
338         return true
339 end
340
341 -- Fetch a dispatching node
342 function node(...)
343         local c = context.tree
344         arg.n = nil
345
346         for k,v in ipairs(arg) do
347                 if not c.nodes[v] then
348                         c.nodes[v] = {nodes={}}
349                 end
350
351                 c = c.nodes[v]
352         end
353
354         c.module = getfenv(2)._NAME
355         c.path = arg
356
357         return c
358 end
359
360 -- Subdispatchers --
361 function alias(...)
362         local req = arg
363         return function()
364                 dispatch(req)
365         end
366 end
367
368 function rewrite(n, ...)
369         local req = arg
370         return function()
371                 for i=1,n do 
372                         table.remove(context.path, 1)
373                 end
374                 
375                 for i,r in ipairs(req) do
376                         table.insert(context.path, i, r)
377                 end
378                 
379                 dispatch()
380         end
381 end
382
383 function call(name, ...)
384         local argv = {...}
385         return function() return getfenv()[name](unpack(argv)) end
386 end
387
388 function template(name)
389         require("luci.template")
390         return function() luci.template.render(name) end
391 end
392
393 function cbi(model)
394         require("luci.cbi")
395         require("luci.template")
396
397         return function()
398                 local stat, maps = luci.util.copcall(luci.cbi.load, model)
399                 if not stat then
400                         error500(maps)
401                         return true
402                 end
403
404                 for i, res in ipairs(maps) do
405                         local stat, err = luci.util.copcall(res.parse, res)
406                         if not stat then
407                                 error500(err)
408                                 return true
409                         end
410                 end
411
412                 luci.template.render("cbi/header")
413                 for i, res in ipairs(maps) do
414                         res:render()
415                 end
416                 luci.template.render("cbi/footer")
417         end
418 end