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