libs/web: Added luci.web.dispatcher.registered
[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.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(unpack(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 -- Checks whether a node exists
328 function registered(...)
329 local c = context.tree
330
331 for k,v in ipairs(arg) do
332 if not c.nodes[v] then
333 return false
334 end
335
336 c = c.nodes[v]
337 end
338 return true
339 end
340
341 -- Fetch a dispatching node
342 function node(...)
343 local c = context.tree
344 arg.n = nil
345
346 for k,v in ipairs(arg) do
347 if not c.nodes[v] then
348 c.nodes[v] = {nodes={}}
349 end
350
351 c = c.nodes[v]
352 end
353
354 c.module = getfenv(2)._NAME
355 c.path = arg
356
357 return c
358 end
359
360 -- Subdispatchers --
361 function alias(...)
362 local req = arg
363 return function()
364 dispatch(req)
365 end
366 end
367
368 function rewrite(n, ...)
369 local req = arg
370 return function()
371 for i=1,n do
372 table.remove(context.path, 1)
373 end
374
375 for i,r in ipairs(req) do
376 table.insert(context.path, i, r)
377 end
378
379 dispatch()
380 end
381 end
382
383 function call(name, ...)
384 local argv = {...}
385 return function() return getfenv()[name](unpack(argv)) end
386 end
387
388 function template(name)
389 require("luci.template")
390 return function() luci.template.render(name) end
391 end
392
393 function cbi(model)
394 require("luci.cbi")
395 require("luci.template")
396
397 return function()
398 local stat, maps = luci.util.copcall(luci.cbi.load, model)
399 if not stat then
400 error500(maps)
401 return true
402 end
403
404 for i, res in ipairs(maps) do
405 local stat, err = luci.util.copcall(res.parse, res)
406 if not stat then
407 error500(err)
408 return true
409 end
410 end
411
412 luci.template.render("cbi/header")
413 for i, res in ipairs(maps) do
414 res:render()
415 end
416 luci.template.render("cbi/footer")
417 end
418 end