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