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