* libs/web: Added Logout support
[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 -- Fetch a dispatching node
328 function node(...)
329         local c = context.tree
330         arg.n = nil
331
332         for k,v in ipairs(arg) do
333                 if not c.nodes[v] then
334                         c.nodes[v] = {nodes={}}
335                 end
336
337                 c = c.nodes[v]
338         end
339
340         c.module = getfenv(2)._NAME
341         c.path = arg
342
343         return c
344 end
345
346 -- Subdispatchers --
347 function alias(...)
348         local req = arg
349         return function()
350                 dispatch(req)
351         end
352 end
353
354 function rewrite(n, ...)
355         local req = arg
356         return function()
357                 for i=1,n do 
358                         table.remove(context.path, 1)
359                 end
360                 
361                 for i,r in ipairs(req) do
362                         table.insert(context.path, i, r)
363                 end
364                 
365                 dispatch()
366         end
367 end
368
369 function call(name, ...)
370         local argv = {...}
371         return function() return getfenv()[name](unpack(argv)) end
372 end
373
374 function template(name)
375         require("luci.template")
376         return function() luci.template.render(name) end
377 end
378
379 function cbi(model)
380         require("luci.cbi")
381         require("luci.template")
382
383         return function()
384                 local stat, res = luci.util.copcall(luci.cbi.load, model)
385                 if not stat then
386                         error500(res)
387                         return true
388                 end
389
390                 local stat, err = luci.util.copcall(res.parse, res)
391                 if not stat then
392                         error500(err)
393                         return true
394                 end
395
396                 luci.template.render("cbi/header")
397                 res:render()
398                 luci.template.render("cbi/footer")
399         end
400 end