* libs/httpd: Fixed a problem with empty sources in filehandler
[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(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 if arg[1] then
332 if type(arg[1]) == "table" then
333 arg = arg[1]
334 end
335 end
336
337 for k,v in ipairs(arg) do
338 if not c.nodes[v] then
339 c.nodes[v] = {nodes={}}
340 end
341
342 c = c.nodes[v]
343 end
344
345 c.module = getfenv(2)._NAME
346 c.path = arg
347
348 return c
349 end
350
351 -- Subdispatchers --
352 function alias(...)
353 local req = arg
354 return function()
355 dispatch(req)
356 end
357 end
358
359 function rewrite(n, ...)
360 local req = arg
361 return function()
362 for i=1,n do
363 table.remove(context.path, 1)
364 end
365
366 for i,r in ipairs(req) do
367 table.insert(context.path, i, r)
368 end
369
370 dispatch()
371 end
372 end
373
374 function call(name, ...)
375 local argv = {...}
376 return function() return getfenv()[name](unpack(argv)) end
377 end
378
379 function template(name)
380 require("luci.template")
381 return function() luci.template.render(name) end
382 end
383
384 function cbi(model)
385 require("luci.cbi")
386 require("luci.template")
387
388 return function()
389 local stat, res = luci.util.copcall(luci.cbi.load, model)
390 if not stat then
391 error500(res)
392 return true
393 end
394
395 local stat, err = luci.util.copcall(res.parse, res)
396 if not stat then
397 error500(err)
398 return true
399 end
400
401 luci.template.render("cbi/header")
402 res:render()
403 luci.template.render("cbi/footer")
404 end
405 end