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