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