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