* applications/luci-splash: Rewrote luci-splash using an own daemon implementation
[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)
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.sysauth then
145 require("luci.sauth")
146 local def = (type(track.sysauth) == "string") and track.sysauth
147 local accs = def and {track.sysauth} or track.sysauth
148 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
149
150
151 if not luci.util.contains(accs, user) then
152 if not sysauth(def) then
153 return
154 end
155 end
156 end
157
158 if track.setgroup then
159 luci.sys.process.setgroup(track.setgroup)
160 end
161
162 if track.setuser then
163 luci.sys.process.setuser(track.setuser)
164 end
165
166 if c and type(c.target) == "function" then
167 context.dispatched = c
168 stat, mod = luci.util.copcall(require, c.module)
169 if stat then
170 luci.util.updfenv(c.target, mod)
171 end
172
173 stat, err = luci.util.copcall(c.target)
174 if not stat then
175 error500(err)
176 end
177 else
178 error404()
179 end
180 end
181
182 -- Generates the dispatching tree
183 function createindex()
184 local path = luci.sys.libpath() .. "/controller/"
185 local suff = ".lua"
186
187 if luci.util.copcall(require, "luci.fastindex") then
188 createindex_fastindex(path, suff)
189 else
190 createindex_plain(path, suff)
191 end
192 end
193
194 -- Uses fastindex to create the dispatching tree
195 function createindex_fastindex(path, suffix)
196 index = {}
197
198 if not fi then
199 fi = luci.fastindex.new("index")
200 fi.add(path .. "*" .. suffix)
201 fi.add(path .. "*/*" .. suffix)
202 end
203 fi.scan()
204
205 for k, v in pairs(fi.indexes) do
206 index[v[2]] = v[1]
207 end
208 end
209
210 -- Calls the index function of all available controllers
211 -- Fallback for transition purposes / Leave it in as long as it works otherwise throw it away
212 function createindex_plain(path, suffix)
213 index = {}
214
215 local cache = nil
216
217 local controllers = luci.util.combine(
218 luci.fs.glob(path .. "*" .. suffix) or {},
219 luci.fs.glob(path .. "*/*" .. suffix) or {}
220 )
221
222 if indexcache then
223 cache = luci.fs.mtime(indexcache)
224
225 if not cache then
226 luci.fs.mkdir(indexcache)
227 luci.fs.chmod(indexcache, "a=,u=rwx")
228 cache = luci.fs.mtime(indexcache)
229 end
230 end
231
232 for i,c in ipairs(controllers) do
233 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
234 local cachefile
235 local stime
236 local ctime
237
238 if cache then
239 cachefile = indexcache .. "/" .. module
240 stime = luci.fs.mtime(c) or 0
241 ctime = luci.fs.mtime(cachefile) or 0
242 end
243
244 if not cache or stime > ctime then
245 stat, mod = luci.util.copcall(require, module)
246
247 if stat and mod and type(mod.index) == "function" then
248 index[module] = mod.index
249
250 if cache then
251 luci.fs.writefile(cachefile, luci.util.dump(mod.index))
252 end
253 end
254 else
255 index[module] = loadfile(cachefile)
256 end
257 end
258 end
259
260 -- Creates the dispatching tree from the index
261 function createtree()
262 if not index then
263 createindex()
264 end
265
266 context.tree = {nodes={}}
267 require("luci.i18n")
268
269 -- Load default translation
270 luci.i18n.loadc("default")
271
272 local scope = luci.util.clone(_G)
273 for k,v in pairs(luci.dispatcher) do
274 if type(v) == "function" then
275 scope[k] = v
276 end
277 end
278
279 for k, v in pairs(index) do
280 scope._NAME = k
281 setfenv(v, scope)
282
283 local stat, err = luci.util.copcall(v)
284 if not stat then
285 error500("createtree failed: " .. k .. ": " .. err)
286 luci.http.close()
287 os.exit(1)
288 end
289 end
290 end
291
292 -- Reassigns a node to another position
293 function assign(path, clone, title, order)
294 local obj = node(path)
295 obj.nodes = nil
296 obj.module = nil
297
298 obj.title = title
299 obj.order = order
300
301 local c = context.tree
302 for k, v in ipairs(clone) do
303 if not c.nodes[v] then
304 c.nodes[v] = {nodes={}}
305 end
306
307 c = c.nodes[v]
308 end
309
310 setmetatable(obj, {__index = c})
311
312 return obj
313 end
314
315 -- Shortcut for creating a dispatching node
316 function entry(path, target, title, order)
317 local c = node(unpack(path))
318
319 c.target = target
320 c.title = title
321 c.order = order
322 c.module = getfenv(2)._NAME
323
324 return c
325 end
326
327 -- Fetch a dispatching node
328 function node(...)
329 local c = context.tree
330 arg.n = nil
331
332 for k,v in ipairs(arg) do
333 if not c.nodes[v] then
334 c.nodes[v] = {nodes={}}
335 end
336
337 c = c.nodes[v]
338 end
339
340 c.module = getfenv(2)._NAME
341 c.path = arg
342
343 return c
344 end
345
346 -- Subdispatchers --
347 function alias(...)
348 local req = arg
349 return function()
350 dispatch(req)
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(context.path, 1)
359 end
360
361 for i,r in ipairs(req) do
362 table.insert(context.path, i, r)
363 end
364
365 dispatch()
366 end
367 end
368
369 function call(name, ...)
370 local argv = {...}
371 return function() return getfenv()[name](unpack(argv)) end
372 end
373
374 function template(name)
375 require("luci.template")
376 return function() luci.template.render(name) end
377 end
378
379 function cbi(model)
380 require("luci.cbi")
381 require("luci.template")
382
383 return function()
384 local stat, res = luci.util.copcall(luci.cbi.load, model)
385 if not stat then
386 error500(res)
387 return true
388 end
389
390 local stat, err = luci.util.copcall(res.parse, res)
391 if not stat then
392 error500(err)
393 return true
394 end
395
396 luci.template.render("cbi/header")
397 res:render()
398 luci.template.render("cbi/footer")
399 end
400 end