* Mördercommit ;-)
[project/luci.git] / core / src / 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.env.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.status(404, "Not Found")
130         message = message or "Not Found"
131         
132         if not pcall(ffluci.template.render, "error404") then
133                 ffluci.http.prepare_content("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.status(500, "Internal Server Error")
142         
143         if not pcall(ffluci.template.render, "error500", {message=message}) then
144                 ffluci.http.prepare_content("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.env.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