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