* General code cleanup
[project/luci.git] / core / src / ffluci / dispatcher.lua
1 --[[
2 FFLuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7
8 The dispatching process:
9 For a detailed explanation of the dispatching process we assume:
10 You have installed the FFLuCI CGI-Dispatcher in /cgi-bin/ffluci
11
12 To enforce a higher level of security only the CGI-Dispatcher
13 resides inside the web server's document root, everything else
14 stays inside an external directory, we assume this is /lua/ffluci
15 for this explanation.
16
17 All controllers and action are reachable as sub-objects of /cgi-bin/ffluci
18 as if they were virtual folders and files
19 e.g.: /cgi-bin/ffluci/public/info/about
20 /cgi-bin/ffluci/admin/network/interfaces
21 and so on.
22
23 The PATH_INFO variable holds the dispatch path and
24 will be split into three parts: /category/module/action
25
26 Category: This is the category in which modules are stored in
27 By default there are two categories:
28 "public" - which is the default public category
29 "admin" - which is the default protected category
30
31 As FFLuCI itself does not implement authentication
32 you should make sure that "admin" and other sensitive
33 categories are protected by the webserver.
34
35 E.g. for busybox add a line like:
36 /cgi-bin/ffluci/admin:root:$p$root
37 to /etc/httpd.conf to protect the "admin" category
38
39
40 Module: This is the controller which will handle the request further
41 It is always a submodule of ffluci.controller, so a module
42 called "helloworld" will be stored in
43 /lua/ffluci/controller/helloworld.lua
44 You are free to submodule your controllers any further.
45
46 Action: This is action that will be invoked after loading the module.
47 The kind of how the action will be dispatched depends on
48 the module dispatcher that is defined in the controller.
49 See the description of the default module dispatcher down
50 on this page for some examples.
51
52
53 The main dispatcher at first searches for the module by trying to
54 include ffluci.controller.category.module
55 (where "category" is the category name and "module" is the module name)
56 If this fails a 404 status code will be send to the client and FFLuCI exits
57
58 Then the main dispatcher calls the module dispatcher
59 ffluci.controller.category.module.dispatcher with the request object
60 as the only argument. The module dispatcher is then responsible
61 for the further dispatching process.
62
63
64 FileId:
65 $Id$
66
67 License:
68 Copyright 2008 Steven Barth <steven@midlink.org>
69
70 Licensed under the Apache License, Version 2.0 (the "License");
71 you may not use this file except in compliance with the License.
72 You may obtain a copy of the License at
73
74 http://www.apache.org/licenses/LICENSE-2.0
75
76 Unless required by applicable law or agreed to in writing, software
77 distributed under the License is distributed on an "AS IS" BASIS,
78 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
79 See the License for the specific language governing permissions and
80 limitations under the License.
81
82 ]]--
83
84 module("ffluci.dispatcher", package.seeall)
85 require("ffluci.http")
86 require("ffluci.template")
87 require("ffluci.config")
88 require("ffluci.sys")
89
90 -- Sets privilege for given category
91 function assign_privileges(category)
92 local cp = ffluci.config.category_privileges
93 if cp and cp[category] then
94 local u, g = cp[category]:match("([^:]+):([^:]+)")
95 ffluci.sys.process.setuser(u)
96 ffluci.sys.process.setgroup(g)
97 end
98 end
99
100
101 -- Builds a URL from a triple of category, module and action
102 function build_url(category, module, action)
103 category = category or "public"
104 module = module or "index"
105 action = action or "index"
106
107 local pattern = ffluci.http.get_script_name() .. "/%s/%s/%s"
108 return pattern:format(category, module, action)
109 end
110
111
112 -- Dispatches the "request"
113 function dispatch(req)
114 request = req
115 local m = "ffluci.controller." .. request.category .. "." .. request.module
116 local stat, module = pcall(require, m)
117 if not stat then
118 return error404()
119 else
120 module.request = request
121 module.dispatcher = module.dispatcher or dynamic
122 setfenv(module.dispatcher, module)
123 return module.dispatcher(request)
124 end
125 end
126
127 -- Sends a 404 error code and renders the "error404" template if available
128 function error404(message)
129 ffluci.http.set_status(404, "Not Found")
130 message = message or "Not Found"
131
132 if not pcall(ffluci.template.render, "error404") then
133 ffluci.http.set_content_type("text/plain")
134 print(message)
135 end
136 return false
137 end
138
139 -- Sends a 500 error code and renders the "error500" template if available
140 function error500(message)
141 ffluci.http.set_status(500, "Internal Server Error")
142
143 if not pcall(ffluci.template.render, "error500", {message=message}) then
144 ffluci.http.set_content_type("text/plain")
145 print(message)
146 end
147 return false
148 end
149
150
151 -- Dispatches a request depending on the PATH_INFO variable
152 function httpdispatch()
153 local pathinfo = ffluci.http.get_path_info() or ""
154 local parts = pathinfo:gmatch("/[%w-]+")
155
156 local sanitize = function(s, default)
157 return s and s:sub(2) or default
158 end
159
160 local cat = sanitize(parts(), "public")
161 local mod = sanitize(parts(), "index")
162 local act = sanitize(parts(), "index")
163
164 assign_privileges(cat)
165 dispatch({category=cat, module=mod, action=act})
166 end
167
168
169 -- Dispatchers --
170
171
172 -- The Action Dispatcher searches the module for any function called
173 -- action_"request.action" and calls it
174 function action(...)
175 local disp = require("ffluci.dispatcher")
176 if not disp._action(...) then
177 disp.error404()
178 end
179 end
180
181 -- The CBI dispatcher directly parses and renders the CBI map which is
182 -- placed in ffluci/modles/cbi/"request.module"/"request.action"
183 function cbi(...)
184 local disp = require("ffluci.dispatcher")
185 if not disp._cbi(...) then
186 disp.error404()
187 end
188 end
189
190 -- The dynamic dispatcher chains the action, submodule, simpleview and CBI dispatcher
191 -- in this particular order. It is the default dispatcher.
192 function dynamic(...)
193 local disp = require("ffluci.dispatcher")
194 if not disp._action(...)
195 and not disp._submodule(...)
196 and not disp._simpleview(...)
197 and not disp._cbi(...) then
198 disp.error404()
199 end
200 end
201
202 -- The Simple View Dispatcher directly renders the template
203 -- which is placed in ffluci/views/"request.module"/"request.action"
204 function simpleview(...)
205 local disp = require("ffluci.dispatcher")
206 if not disp._simpleview(...) then
207 disp.error404()
208 end
209 end
210
211
212 -- The submodule dispatcher tries to load a submodule of the controller
213 -- and calls its "action"-function
214 function submodule(...)
215 local disp = require("ffluci.dispatcher")
216 if not disp._submodule(...) then
217 disp.error404()
218 end
219 end
220
221
222 -- Internal Dispatcher Functions --
223
224 function _action(request)
225 local action = getfenv(2)["action_" .. request.action:gsub("-", "_")]
226 local i18n = require("ffluci.i18n")
227
228 if action then
229 i18n.loadc(request.category .. "_" .. request.module)
230 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
231 action()
232 return true
233 else
234 return false
235 end
236 end
237
238
239 function _cbi(request)
240 local disp = require("ffluci.dispatcher")
241 local tmpl = require("ffluci.template")
242 local cbi = require("ffluci.cbi")
243 local i18n = require("ffluci.i18n")
244
245 local path = request.category.."_"..request.module.."/"..request.action
246
247 local stat, map = pcall(cbi.load, path)
248 if stat and map then
249 local stat, err = pcall(map.parse, map)
250 if not stat then
251 disp.error500(err)
252 return true
253 end
254 i18n.loadc(request.category .. "_" .. request.module)
255 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
256 tmpl.render("cbi/header")
257 map:render()
258 tmpl.render("cbi/footer")
259 return true
260 elseif not stat then
261 disp.error500(map)
262 return true
263 else
264 return false
265 end
266 end
267
268
269 function _simpleview(request)
270 local i18n = require("ffluci.i18n")
271 local tmpl = require("ffluci.template")
272
273 local path = request.category.."_"..request.module.."/"..request.action
274
275 local stat, t = pcall(tmpl.Template, path)
276 if stat then
277 i18n.loadc(request.category .. "_" .. request.module)
278 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
279 t:render()
280 return true
281 else
282 return false
283 end
284 end
285
286
287 function _submodule(request)
288 local i18n = require("ffluci.i18n")
289 local m = "ffluci.controller." .. request.category .. "." ..
290 request.module .. "." .. request.action
291 local stat, module = pcall(require, m)
292
293 if stat and module.action then
294 i18n.loadc(request.category .. "_" .. request.module)
295 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
296 return pcall(module.action)
297 end
298
299 return false
300 end