* libs/web: Switched from HTTP-Basic-Auth to Session-Auth
[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 os.exit(1)
287 end
288 end
289 end
290
291 -- Reassigns a node to another position
292 function assign(path, clone, title, order)
293 local obj = node(path)
294 obj.nodes = nil
295 obj.module = nil
296
297 obj.title = title
298 obj.order = order
299
300 local c = context.tree
301 for k, v in ipairs(clone) do
302 if not c.nodes[v] then
303 c.nodes[v] = {nodes={}}
304 end
305
306 c = c.nodes[v]
307 end
308
309 setmetatable(obj, {__index = c})
310
311 return obj
312 end
313
314 -- Shortcut for creating a dispatching node
315 function entry(path, target, title, order)
316 local c = node(path)
317
318 c.target = target
319 c.title = title
320 c.order = order
321 c.module = getfenv(2)._NAME
322
323 return c
324 end
325
326 -- Fetch a dispatching node
327 function node(...)
328 local c = context.tree
329 arg.n = nil
330 if arg[1] then
331 if type(arg[1]) == "table" then
332 arg = arg[1]
333 end
334 end
335
336 for k,v in ipairs(arg) do
337 if not c.nodes[v] then
338 c.nodes[v] = {nodes={}}
339 end
340
341 c = c.nodes[v]
342 end
343
344 c.module = getfenv(2)._NAME
345 c.path = arg
346
347 return c
348 end
349
350 -- Subdispatchers --
351 function alias(...)
352 local req = arg
353 return function()
354 dispatch(req)
355 end
356 end
357
358 function rewrite(n, ...)
359 local req = arg
360 return function()
361 for i=1,n do
362 table.remove(context.path, 1)
363 end
364
365 for i,r in ipairs(req) do
366 table.insert(context.path, i, r)
367 end
368
369 dispatch()
370 end
371 end
372
373 function call(name, ...)
374 local argv = {...}
375 return function() return getfenv()[name](unpack(argv)) end
376 end
377
378 function template(name)
379 require("luci.template")
380 return function() luci.template.render(name) end
381 end
382
383 function cbi(model)
384 require("luci.cbi")
385 require("luci.template")
386
387 return function()
388 local stat, res = luci.util.copcall(luci.cbi.load, model)
389 if not stat then
390 error500(res)
391 return true
392 end
393
394 local stat, err = luci.util.copcall(res.parse, res)
395 if not stat then
396 error500(err)
397 return true
398 end
399
400 luci.template.render("cbi/header")
401 res:render()
402 luci.template.render("cbi/footer")
403 end
404 end