682511db2ffa080d20e1ef1659f25ad6ec9d5be1
[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.dependent then
145                 local stat, err = pcall(assert, not track.auto)
146                 if not stat then
147                         error500(err)
148                         return
149                 end
150         end
151         
152         if track.sysauth then
153                 require("luci.sauth")
154                 local def  = (type(track.sysauth) == "string") and track.sysauth
155                 local accs = def and {track.sysauth} or track.sysauth
156                 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
157                 
158                 
159                 if not luci.util.contains(accs, user) then
160                         if not sysauth(def) then
161                                 return
162                         end
163                 end
164         end
165
166         if track.setgroup then
167                 luci.sys.process.setgroup(track.setgroup)
168         end
169
170         if track.setuser then
171                 luci.sys.process.setuser(track.setuser)
172         end
173
174         if c and type(c.target) == "function" then
175                 context.dispatched = c
176                 stat, mod = luci.util.copcall(require, c.module)
177                 if stat then
178                         luci.util.updfenv(c.target, mod)
179                 end
180                 
181                 stat, err = luci.util.copcall(c.target)
182                 if not stat then
183                         error500(err)
184                 end
185         else
186                 error404()
187         end
188 end
189
190 -- Generates the dispatching tree
191 function createindex()
192         local path = luci.sys.libpath() .. "/controller/"
193         local suff = ".lua"
194         
195         if luci.util.copcall(require, "luci.fastindex") then
196                 createindex_fastindex(path, suff)
197         else
198                 createindex_plain(path, suff)
199         end
200 end
201
202 -- Uses fastindex to create the dispatching tree
203 function createindex_fastindex(path, suffix)
204         index = {}
205                 
206         if not fi then
207                 fi = luci.fastindex.new("index")
208                 fi.add(path .. "*" .. suffix)
209                 fi.add(path .. "*/*" .. suffix)
210         end
211         fi.scan()
212         
213         for k, v in pairs(fi.indexes) do
214                 index[v[2]] = v[1]
215         end
216 end
217
218 -- Calls the index function of all available controllers
219 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
220 function createindex_plain(path, suffix)
221         index = {}
222
223         local cache = nil 
224         
225         local controllers = luci.util.combine(
226                 luci.fs.glob(path .. "*" .. suffix) or {},
227                 luci.fs.glob(path .. "*/*" .. suffix) or {}
228         )
229         
230         if indexcache then
231                 cache = luci.fs.mtime(indexcache)
232                 
233                 if not cache then
234                         luci.fs.mkdir(indexcache)
235                         luci.fs.chmod(indexcache, "a=,u=rwx")
236                         cache = luci.fs.mtime(indexcache)
237                 end
238         end
239
240         for i,c in ipairs(controllers) do
241                 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
242                 local cachefile
243                 local stime
244                 local ctime
245                 
246                 if cache then
247                         cachefile = indexcache .. "/" .. module
248                         stime = luci.fs.mtime(c) or 0
249                         ctime = luci.fs.mtime(cachefile) or 0
250                 end
251                 
252                 if not cache or stime > ctime then 
253                         stat, mod = luci.util.copcall(require, module)
254         
255                         if stat and mod and type(mod.index) == "function" then
256                                 index[module] = mod.index
257                                 
258                                 if cache then
259                                         luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
260                                 end
261                         end
262                 else
263                         index[module] = loadfile(cachefile)
264                 end
265         end
266 end
267
268 -- Creates the dispatching tree from the index
269 function createtree()
270         if not index then
271                 createindex()
272         end
273         
274         context.tree = {nodes={}}
275         require("luci.i18n")
276                 
277         -- Load default translation
278         luci.i18n.loadc("default")
279         
280         local scope = luci.util.clone(_G)
281         for k,v in pairs(luci.dispatcher) do
282                 if type(v) == "function" then
283                         scope[k] = v
284                 end
285         end
286
287         for k, v in pairs(index) do
288                 scope._NAME = k
289                 setfenv(v, scope)
290
291                 local stat, err = luci.util.copcall(v)
292                 if not stat then
293                         error500("createtree failed: " .. k .. ": " .. err)
294                         luci.http.close()
295                         os.exit(1)
296                 end
297         end
298 end
299
300 -- Reassigns a node to another position
301 function assign(path, clone, title, order)
302         local obj  = node(unpack(path))
303         obj.nodes  = nil
304         obj.module = nil
305         
306         obj.title = title
307         obj.order = order
308         
309         local c = context.tree
310         for k, v in ipairs(clone) do
311                 if not c.nodes[v] then
312                         c.nodes[v] = {nodes={}}
313                 end
314
315                 c = c.nodes[v]
316         end
317         
318         setmetatable(obj, {__index = c})
319         
320         return obj
321 end
322
323 -- Shortcut for creating a dispatching node
324 function entry(path, target, title, order)
325         local c = node(unpack(path))
326         
327         c.target = target
328         c.title  = title
329         c.order  = order
330         c.module = getfenv(2)._NAME
331
332         return c
333 end
334
335 -- Fetch a dispatching node
336 function node(...)
337         local c = context.tree
338         arg.n = nil
339
340         for k,v in ipairs(arg) do
341                 if not c.nodes[v] then
342                         c.nodes[v] = {nodes={}, auto=true}
343                 end
344
345                 c = c.nodes[v]
346         end
347
348         c.module = getfenv(2)._NAME
349         c.path = arg
350         c.auto = nil
351
352         return c
353 end
354
355 -- Subdispatchers --
356 function alias(...)
357         local req = arg
358         return function()
359                 dispatch(req)
360         end
361 end
362
363 function rewrite(n, ...)
364         local req = arg
365         return function()
366                 for i=1,n do 
367                         table.remove(context.path, 1)
368                 end
369                 
370                 for i,r in ipairs(req) do
371                         table.insert(context.path, i, r)
372                 end
373                 
374                 dispatch()
375         end
376 end
377
378 function call(name, ...)
379         local argv = {...}
380         return function() return getfenv()[name](unpack(argv)) end
381 end
382
383 function template(name)
384         require("luci.template")
385         return function() luci.template.render(name) end
386 end
387
388 function cbi(model)
389         require("luci.cbi")
390         require("luci.template")
391
392         return function()
393                 local stat, maps = luci.util.copcall(luci.cbi.load, model)
394                 if not stat then
395                         error500(maps)
396                         return true
397                 end
398
399                 for i, res in ipairs(maps) do
400                         local stat, err = luci.util.copcall(res.parse, res)
401                         if not stat then
402                                 error500(err)
403                                 return true
404                         end
405                 end
406
407                 luci.template.render("cbi/header")
408                 for i, res in ipairs(maps) do
409                         res:render()
410                 end
411                 luci.template.render("cbi/footer")
412         end
413 end