989eb44021d4e78b9f40a463638e7ad57fd87932
[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)
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                         os.exit(1)
287                 end
288         end
289 end
290
291 -- Reassigns a node to another position
292 function assign(path, clone, title, order)
293         local obj  = node(path)
294         obj.nodes  = nil
295         obj.module = nil
296         
297         obj.title = title
298         obj.order = order
299         
300         local c = context.tree
301         for k, v in ipairs(clone) do
302                 if not c.nodes[v] then
303                         c.nodes[v] = {nodes={}}
304                 end
305
306                 c = c.nodes[v]
307         end
308         
309         setmetatable(obj, {__index = c})
310         
311         return obj
312 end
313
314 -- Shortcut for creating a dispatching node
315 function entry(path, target, title, order)
316         local c = node(path)
317         
318         c.target = target
319         c.title  = title
320         c.order  = order
321         c.module = getfenv(2)._NAME
322
323         return c
324 end
325
326 -- Fetch a dispatching node
327 function node(...)
328         local c = context.tree
329         arg.n = nil
330         if arg[1] then
331                 if type(arg[1]) == "table" then
332                         arg = arg[1]
333                 end
334         end
335
336         for k,v in ipairs(arg) do
337                 if not c.nodes[v] then
338                         c.nodes[v] = {nodes={}}
339                 end
340
341                 c = c.nodes[v]
342         end
343
344         c.module = getfenv(2)._NAME
345         c.path = arg
346
347         return c
348 end
349
350 -- Subdispatchers --
351 function alias(...)
352         local req = arg
353         return function()
354                 dispatch(req)
355         end
356 end
357
358 function rewrite(n, ...)
359         local req = arg
360         return function()
361                 for i=1,n do 
362                         table.remove(context.path, 1)
363                 end
364                 
365                 for i,r in ipairs(req) do
366                         table.insert(context.path, i, r)
367                 end
368                 
369                 dispatch()
370         end
371 end
372
373 function call(name, ...)
374         local argv = {...}
375         return function() return getfenv()[name](unpack(argv)) end
376 end
377
378 function template(name)
379         require("luci.template")
380         return function() luci.template.render(name) end
381 end
382
383 function cbi(model)
384         require("luci.cbi")
385         require("luci.template")
386
387         return function()
388                 local stat, res = luci.util.copcall(luci.cbi.load, model)
389                 if not stat then
390                         error500(res)
391                         return true
392                 end
393
394                 local stat, err = luci.util.copcall(res.parse, res)
395                 if not stat then
396                         error500(err)
397                         return true
398                 end
399
400                 luci.template.render("cbi/header")
401                 res:render()
402                 luci.template.render("cbi/footer")
403         end
404 end