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