* luci/statistics: moving the graph controller to admin caused the images to be gener...
[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.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
166
167
168 if c and type(c.target) == "function" then
169 dispatched = c
170 stat, mod = pcall(require, c.module)
171 if stat then
172 luci.util.updfenv(c.target, mod)
173 end
174
175 stat, err = pcall(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 index = {}
187 local path = luci.sys.libpath() .. "/controller/"
188 local suff = ".lua"
189
190 if pcall(require, "luci.fastindex") then
191 createindex_fastindex(path, suff)
192 else
193 createindex_plain(path, suff)
194 end
195
196 built_index = true
197 end
198
199 -- Uses fastindex to create the dispatching tree
200 function createindex_fastindex(path, suffix)
201 if not fi then
202 fi = luci.fastindex.new("index")
203 fi.add(path .. "*" .. suffix)
204 fi.add(path .. "*/*" .. suffix)
205 end
206 fi.scan()
207
208 for k, v in pairs(fi.indexes) do
209 index[v[2]] = v[1]
210 end
211 end
212
213 -- Calls the index function of all available controllers
214 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
215 function createindex_plain(path, suffix)
216 if built_index then
217 return
218 end
219
220 local cache = nil
221
222 local controllers = luci.util.combine(
223 luci.fs.glob(path .. "*" .. suffix) or {},
224 luci.fs.glob(path .. "*/*" .. suffix) or {}
225 )
226
227 if indexcache then
228 cache = luci.fs.mtime(indexcache)
229
230 if not cache then
231 luci.fs.mkdir(indexcache)
232 luci.fs.chmod(indexcache, "a=,u=rwx")
233 cache = luci.fs.mtime(indexcache)
234 end
235 end
236
237 for i,c in ipairs(controllers) do
238 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
239 local cachefile
240 local stime
241 local ctime
242
243 if cache then
244 cachefile = indexcache .. "/" .. module
245 stime = luci.fs.mtime(c) or 0
246 ctime = luci.fs.mtime(cachefile) or 0
247 end
248
249 if not cache or stime > ctime then
250 stat, mod = pcall(require, module)
251
252 if stat and mod and type(mod.index) == "function" then
253 index[module] = mod.index
254
255 if cache then
256 luci.fs.writefile(cachefile, luci.util.dump(mod.index))
257 end
258 end
259 else
260 index[module] = loadfile(cachefile)
261 end
262 end
263 end
264
265 -- Creates the dispatching tree from the index
266 function createtree()
267 if not built_index then
268 createindex()
269 end
270
271 require("luci.i18n")
272
273 -- Load default translation
274 luci.i18n.loadc("default")
275
276 local scope = luci.util.clone(_G)
277 for k,v in pairs(_M) do
278 if type(v) == "function" then
279 scope[k] = v
280 end
281 end
282
283 for k, v in pairs(index) do
284 scope._NAME = k
285 setfenv(v, scope)
286
287 local stat, err = pcall(v)
288 if not stat then
289 error500(err)
290 os.exit(1)
291 end
292 end
293
294 built_tree = true
295 end
296
297 -- Reassigns a node to another position
298 function assign(path, clone, title, order)
299 local obj = node(path)
300 obj.nodes = nil
301 obj.module = nil
302
303 obj.title = title
304 obj.order = order
305
306 setmetatable(obj, {__index = clone})
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 = tree
326
327 if arg[1] and type(arg[1]) == "table" then
328 arg = arg[1]
329 end
330
331 for k,v in ipairs(arg) do
332 if not c.nodes[v] then
333 c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
334 end
335
336 c = c.nodes[v]
337 end
338
339 c.path = arg
340
341 return c
342 end
343
344 -- Subdispatchers --
345 function alias(...)
346 local req = arg
347 return function()
348 request = req
349 dispatch()
350 end
351 end
352
353 function rewrite(n, ...)
354 local req = arg
355 return function()
356 for i=1,n do
357 table.remove(request, 1)
358 end
359
360 for i,r in ipairs(req) do
361 table.insert(request, i, r)
362 end
363
364 dispatch()
365 end
366 end
367
368 function call(name)
369 return function() getfenv()[name]() end
370 end
371
372 function template(name)
373 require("luci.template")
374 return function() luci.template.render(name) end
375 end
376
377 function cbi(model)
378 require("luci.cbi")
379 require("luci.template")
380
381 return function()
382 local stat, res = pcall(luci.cbi.load, model)
383 if not stat then
384 error500(res)
385 return true
386 end
387
388 local stat, err = pcall(res.parse, res)
389 if not stat then
390 error500(err)
391 return true
392 end
393
394 luci.template.render("cbi/header")
395 res:render()
396 luci.template.render("cbi/footer")
397 end
398 end