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