libs/cbi: Added magic ;-)
[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
27 --- LuCI web dispatcher.
28 module("luci.dispatcher", package.seeall)
29 require("luci.init")
30 require("luci.http")
31 require("luci.sys")
32 require("luci.fs")
33
34 context = luci.util.threadlocal()
35
36 -- Index table
37 local index = nil
38
39 -- Fastindex
40 local fi
41
42
43 --- Build the URL relative to the server webroot from given virtual path.
44 -- @param ... Virtual path
45 -- @return Relative URL
46 function build_url(...)
47 return luci.http.getenv("SCRIPT_NAME") .. "/" .. table.concat(arg, "/")
48 end
49
50 --- Send a 404 error code and render the "error404" template if available.
51 -- @param message Custom error message (optional)
52 -- @return false
53 function error404(message)
54 luci.http.status(404, "Not Found")
55 message = message or "Not Found"
56
57 require("luci.template")
58 if not luci.util.copcall(luci.template.render, "error404") then
59 luci.http.prepare_content("text/plain")
60 luci.http.write(message)
61 end
62 return false
63 end
64
65 --- Send a 500 error code and render the "error500" template if available.
66 -- @param message Custom error message (optional)#
67 -- @return false
68 function error500(message)
69 luci.http.status(500, "Internal Server Error")
70
71 require("luci.template")
72 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
73 luci.http.prepare_content("text/plain")
74 luci.http.write(message)
75 end
76 return false
77 end
78
79 --- Render and evaluate the system authentication login form.
80 -- @param default Default username
81 -- @return Authentication status
82 function sysauth(default)
83 local user = luci.http.formvalue("username")
84 local pass = luci.http.formvalue("password")
85
86 if user and luci.sys.user.checkpasswd(user, pass) then
87 local sid = luci.sys.uniqueid(16)
88 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
89 luci.sauth.write(sid, user)
90 return true
91 else
92 require("luci.i18n")
93 require("luci.template")
94 context.path = {}
95 luci.template.render("sysauth", {duser=default, fuser=user})
96 return false
97 end
98 end
99
100 --- Dispatch an HTTP request.
101 -- @param request LuCI HTTP Request object
102 function httpdispatch(request)
103 luci.http.context.request = request
104 context.request = {}
105 local pathinfo = request:getenv("PATH_INFO") or ""
106
107 for node in pathinfo:gmatch("[^/]+") do
108 table.insert(context.request, node)
109 end
110
111 dispatch(context.request)
112 luci.http.close()
113 end
114
115 --- Dispatches a LuCI virtual path.
116 -- @param request Virtual path
117 function dispatch(request)
118 context.path = request
119
120 require("luci.i18n")
121 luci.i18n.setlanguage(require("luci.config").main.lang)
122
123 if not context.tree then
124 createtree()
125 end
126
127 local c = context.tree
128 local track = {}
129 local args = {}
130 local n
131
132 for i, s in ipairs(request) do
133 c = c.nodes[s]
134 n = i
135 if not c or c.leaf then
136 break
137 end
138
139 for k, v in pairs(c) do
140 track[k] = v
141 end
142 end
143
144 if c and c.leaf then
145 for j=n+1, #request do
146 table.insert(args, request[j])
147 end
148 end
149
150 if track.i18n then
151 require("luci.i18n").loadc(track.i18n)
152 end
153
154 -- Init template engine
155 local tpl = require("luci.template")
156 local viewns = {}
157 tpl.context.viewns = viewns
158 viewns.write = luci.http.write
159 viewns.translate = function(...) return require("luci.i18n").translate(...) end
160 viewns.controller = luci.http.getenv("SCRIPT_NAME")
161 viewns.media = luci.config.main.mediaurlbase
162 viewns.resource = luci.config.main.resourcebase
163 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
164
165 if track.dependent then
166 local stat, err = pcall(assert, not track.auto)
167 if not stat then
168 error500(err)
169 return
170 end
171 end
172
173 if track.sysauth then
174 require("luci.sauth")
175 local def = (type(track.sysauth) == "string") and track.sysauth
176 local accs = def and {track.sysauth} or track.sysauth
177 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
178
179
180 if not luci.util.contains(accs, user) then
181 if not sysauth(def) then
182 return
183 end
184 end
185 end
186
187 if track.setgroup then
188 luci.sys.process.setgroup(track.setgroup)
189 end
190
191 if track.setuser then
192 luci.sys.process.setuser(track.setuser)
193 end
194
195 if c and type(c.target) == "function" then
196 context.dispatched = c
197 stat, mod = luci.util.copcall(require, c.module)
198 if stat then
199 luci.util.updfenv(c.target, mod)
200 end
201
202 stat, err = luci.util.copcall(c.target, unpack(args))
203 if not stat then
204 error500(err)
205 end
206 else
207 error404()
208 end
209 end
210
211 --- Generate the dispatching index using the best possible strategy.
212 function createindex()
213 local path = luci.util.libpath() .. "/controller/"
214 local suff = ".lua"
215
216 if luci.util.copcall(require, "luci.fastindex") then
217 createindex_fastindex(path, suff)
218 else
219 createindex_plain(path, suff)
220 end
221 end
222
223 --- Generate the dispatching index using the fastindex C-indexer.
224 -- @param path Controller base directory
225 -- @param suffix Controller file suffix
226 function createindex_fastindex(path, suffix)
227 index = {}
228
229 if not fi then
230 fi = luci.fastindex.new("index")
231 fi.add(path .. "*" .. suffix)
232 fi.add(path .. "*/*" .. suffix)
233 end
234 fi.scan()
235
236 for k, v in pairs(fi.indexes) do
237 index[v[2]] = v[1]
238 end
239 end
240
241 --- Generate the dispatching index using the native file-cache based strategy.
242 -- @param path Controller base directory
243 -- @param suffix Controller file suffix
244 function createindex_plain(path, suffix)
245 index = {}
246
247 local cache = nil
248
249 local controllers = luci.util.combine(
250 luci.fs.glob(path .. "*" .. suffix) or {},
251 luci.fs.glob(path .. "*/*" .. suffix) or {}
252 )
253
254 if indexcache then
255 cache = luci.fs.mtime(indexcache)
256
257 if not cache then
258 luci.fs.mkdir(indexcache)
259 luci.fs.chmod(indexcache, "a=,u=rwx")
260 cache = luci.fs.mtime(indexcache)
261 end
262 end
263
264 for i,c in ipairs(controllers) do
265 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
266 local cachefile
267 local stime
268 local ctime
269
270 if cache then
271 cachefile = indexcache .. "/" .. module
272 stime = luci.fs.mtime(c) or 0
273 ctime = luci.fs.mtime(cachefile) or 0
274 end
275
276 if not cache or stime > ctime then
277 stat, mod = luci.util.copcall(require, module)
278
279 if stat and mod and type(mod.index) == "function" then
280 index[module] = mod.index
281
282 if cache then
283 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
284 end
285 end
286 else
287 index[module] = loadfile(cachefile)
288 end
289 end
290 end
291
292 --- Create the dispatching tree from the index.
293 -- Build the index before if it does not exist yet.
294 function createtree()
295 if not index then
296 createindex()
297 end
298
299 context.tree = {nodes={}}
300 require("luci.i18n")
301
302 -- Load default translation
303 luci.i18n.loadc("default")
304
305 local scope = luci.util.clone(_G)
306 for k,v in pairs(luci.dispatcher) do
307 if type(v) == "function" then
308 scope[k] = v
309 end
310 end
311
312 for k, v in pairs(index) do
313 scope._NAME = k
314 setfenv(v, scope)
315
316 local stat, err = luci.util.copcall(v)
317 if not stat then
318 error500("createtree failed: " .. k .. ": " .. err)
319 luci.http.close()
320 os.exit(1)
321 end
322 end
323 end
324
325 --- Clone a node of the dispatching tree to another position.
326 -- @param path Virtual path destination
327 -- @param clone Virtual path source
328 -- @param title Destination node title (optional)
329 -- @param order Destination node order value (optional)
330 -- @return Dispatching tree node
331 function assign(path, clone, title, order)
332 local obj = node(unpack(path))
333 obj.nodes = nil
334 obj.module = nil
335
336 obj.title = title
337 obj.order = order
338
339 local c = context.tree
340 for k, v in ipairs(clone) do
341 if not c.nodes[v] then
342 c.nodes[v] = {nodes={}}
343 end
344
345 c = c.nodes[v]
346 end
347
348 setmetatable(obj, {__index = c})
349
350 return obj
351 end
352
353 --- Create a new dispatching node and define common parameters.
354 -- @param path Virtual path
355 -- @param target Target function to call when dispatched.
356 -- @param title Destination node title
357 -- @param order Destination node order value (optional)
358 -- @return Dispatching tree node
359 function entry(path, target, title, order)
360 local c = node(unpack(path))
361
362 c.target = target
363 c.title = title
364 c.order = order
365 c.module = getfenv(2)._NAME
366
367 return c
368 end
369
370 --- Fetch or create a new dispatching node.
371 -- @param ... Virtual path
372 -- @return Dispatching tree node
373 function node(...)
374 local c = context.tree
375 arg.n = nil
376
377 for k,v in ipairs(arg) do
378 if not c.nodes[v] then
379 c.nodes[v] = {nodes={}, auto=true}
380 end
381
382 c = c.nodes[v]
383 end
384
385 c.module = getfenv(2)._NAME
386 c.path = arg
387 c.auto = nil
388
389 return c
390 end
391
392 -- Subdispatchers --
393
394 --- Create a redirect to another dispatching node.
395 -- @param ... Virtual path destination
396 function alias(...)
397 local req = arg
398 return function()
399 dispatch(req)
400 end
401 end
402
403 --- Rewrite the first x path values of the request.
404 -- @param n Number of path values to replace
405 -- @param ... Virtual path to replace removed path values with
406 function rewrite(n, ...)
407 local req = arg
408 return function()
409 for i=1,n do
410 table.remove(context.path, 1)
411 end
412
413 for i,r in ipairs(req) do
414 table.insert(context.path, i, r)
415 end
416
417 dispatch()
418 end
419 end
420
421 --- Create a function-call dispatching target.
422 -- @param name Target function of local controller
423 -- @param ... Additional parameters passed to the function
424 function call(name, ...)
425 local argv = {...}
426 return function() return getfenv()[name](unpack(argv)) end
427 end
428
429 --- Create a template render dispatching target.
430 -- @param name Template to be rendered
431 function template(name)
432 require("luci.template")
433 return function() luci.template.render(name) end
434 end
435
436 --- Create a CBI model dispatching target.
437 -- @param model CBI model tpo be rendered
438 function cbi(model)
439 require("luci.cbi")
440 require("luci.template")
441
442 return function(...)
443 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
444 if not stat then
445 error500(maps)
446 return true
447 end
448
449 for i, res in ipairs(maps) do
450 local stat, err = luci.util.copcall(res.parse, res)
451 if not stat then
452 error500(err)
453 return true
454 end
455 end
456
457 luci.template.render("cbi/header")
458 for i, res in ipairs(maps) do
459 res:render()
460 end
461 luci.template.render("cbi/footer")
462 end
463 end