* Added native basic authentication support
[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.http")
28 require("luci.sys")
29 require("luci.fs")
30
31 -- Dirty OpenWRT fix
32 if (os.time() < luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua")) then
33 os.execute('date -s '..os.date('%m%d%H%M%Y', luci.fs.mtime(luci.sys.libpath() .. "/dispatcher.lua"))..' > /dev/null 2>&1')
34 end
35
36 -- Local dispatch database
37 local tree = {nodes={}}
38
39 -- Index table
40 local index = {}
41
42 -- Global request object
43 request = {}
44
45 -- Active dispatched node
46 dispatched = nil
47
48 -- Status fields
49 built_index = false
50 built_tree = false
51
52 -- Fastindex
53 local fi
54
55
56 -- Builds a URL
57 function build_url(...)
58 return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
59 end
60
61 -- Prints an error message or renders the "error401" template if available
62 function error401(message)
63 message = message or "Unauthorized"
64
65 require("luci.template")
66 if not pcall(luci.template.render, "error401") then
67 luci.http.prepare_content("text/plain")
68 print(message)
69 end
70 return false
71 end
72
73 -- Sends a 404 error code and renders the "error404" template if available
74 function error404(message)
75 luci.http.status(404, "Not Found")
76 message = message or "Not Found"
77
78 require("luci.template")
79 if not pcall(luci.template.render, "error404") then
80 luci.http.prepare_content("text/plain")
81 print(message)
82 end
83 return false
84 end
85
86 -- Sends a 500 error code and renders the "error500" template if available
87 function error500(message)
88 luci.http.status(500, "Internal Server Error")
89
90 require("luci.template")
91 if not pcall(luci.template.render, "error500", {message=message}) then
92 luci.http.prepare_content("text/plain")
93 print(message)
94 end
95 return false
96 end
97
98 -- Creates a request object for dispatching
99 function httpdispatch()
100 local pathinfo = luci.http.env.PATH_INFO or ""
101 local c = tree
102
103 for s in pathinfo:gmatch("([%w-]+)") do
104 table.insert(request, s)
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.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
165
166
167 if c and type(c.target) == "function" then
168 dispatched = c
169 stat, mod = pcall(require, c.module)
170 if stat then
171 luci.util.updfenv(c.target, mod)
172 end
173
174 stat, err = pcall(c.target)
175 if not stat then
176 error500(err)
177 end
178 else
179 error404()
180 end
181 end
182
183 -- Generates the dispatching tree
184 function createindex()
185 index = {}
186 local path = luci.sys.libpath() .. "/controller/"
187 local suff = ".lua"
188
189 if pcall(require, "luci.fastindex") then
190 createindex_fastindex(path, suff)
191 else
192 createindex_plain(path, suff)
193 end
194
195 built_index = true
196 end
197
198 -- Uses fastindex to create the dispatching tree
199 function createindex_fastindex(path, suffix)
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 if built_index then
216 return
217 end
218
219 local cache = nil
220
221 local controllers = luci.util.combine(
222 luci.fs.glob(path .. "*" .. suffix) or {},
223 luci.fs.glob(path .. "*/*" .. suffix) or {}
224 )
225
226 if indexcache then
227 cache = luci.fs.mtime(indexcache)
228
229 if not cache then
230 luci.fs.mkdir(indexcache)
231 luci.fs.chmod(indexcache, "a=,u=rwx")
232 cache = luci.fs.mtime(indexcache)
233 end
234 end
235
236 for i,c in ipairs(controllers) do
237 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
238 local cachefile
239 local stime
240 local ctime
241
242 if cache then
243 cachefile = indexcache .. "/" .. module
244 stime = luci.fs.mtime(c) or 0
245 ctime = luci.fs.mtime(cachefile) or 0
246 end
247
248 if not cache or stime > ctime then
249 stat, mod = pcall(require, module)
250
251 if stat and mod and type(mod.index) == "function" then
252 index[module] = mod.index
253
254 if cache then
255 luci.fs.writefile(cachefile, luci.util.dump(mod.index))
256 end
257 end
258 else
259 index[module] = loadfile(cachefile)
260 end
261 end
262 end
263
264 -- Creates the dispatching tree from the index
265 function createtree()
266 if not built_index then
267 createindex()
268 end
269
270 require("luci.i18n")
271
272 -- Load default translation
273 luci.i18n.loadc("default")
274
275 local scope = luci.util.clone(_G)
276 for k,v in pairs(_M) do
277 if type(v) == "function" then
278 scope[k] = v
279 end
280 end
281
282 for k, v in pairs(index) do
283 scope._NAME = k
284 setfenv(v, scope)
285
286 local stat, err = pcall(v)
287 if not stat then
288 error500(err)
289 os.exit(1)
290 end
291 end
292
293 built_tree = true
294 end
295
296 -- Shortcut for creating a dispatching node
297 function entry(path, target, title, order, add)
298 add = add or {}
299
300 local c = node(path)
301 c.target = target
302 c.title = title
303 c.order = order
304 c.module = getfenv(2)._NAME
305
306 for k,v in pairs(add) do
307 c[k] = v
308 end
309
310 return c
311 end
312
313 -- Fetch a dispatching node
314 function node(...)
315 local c = tree
316
317 if arg[1] and type(arg[1]) == "table" then
318 arg = arg[1]
319 end
320
321 for k,v in ipairs(arg) do
322 if not c.nodes[v] then
323 c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
324 end
325
326 c = c.nodes[v]
327 end
328
329 return c
330 end
331
332 -- Subdispatchers --
333 function alias(...)
334 local req = arg
335 return function()
336 request = req
337 dispatch()
338 end
339 end
340
341 function rewrite(n, ...)
342 local req = arg
343 return function()
344 for i=1,n do
345 table.remove(request, 1)
346 end
347
348 for i,r in ipairs(req) do
349 table.insert(request, i, r)
350 end
351
352 dispatch()
353 end
354 end
355
356 function call(name)
357 return function() getfenv()[name]() end
358 end
359
360 function template(name)
361 require("luci.template")
362 return function() luci.template.render(name) end
363 end
364
365 function cbi(model)
366 require("luci.cbi")
367 require("luci.template")
368
369 return function()
370 local stat, res = pcall(luci.cbi.load, model)
371 if not stat then
372 error500(res)
373 return true
374 end
375
376 local stat, err = pcall(res.parse, res)
377 if not stat then
378 error500(err)
379 return true
380 end
381
382 luci.template.render("cbi/header")
383 res:render()
384 luci.template.render("cbi/footer")
385 end
386 end