* Cleaned up and rewrote dispatchers
[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
91 local tree = {}
92
93 -- Sets privilege for given category
94 function assign_privileges(category)
95         local cp = ffluci.config.category_privileges
96         if cp and cp[category] then
97                 local u, g = cp[category]:match("([^:]+):([^:]+)")
98                 ffluci.sys.process.setuser(u)
99                 ffluci.sys.process.setgroup(g)
100         end
101 end
102
103 -- Dispatches the "request"
104 function dispatch(req)
105         request = req
106         local m = "ffluci.controller." .. request.category .. "." .. request.module
107         local stat, module = pcall(require, m)
108         if not stat then
109                 return error404()
110         else
111                 module.request = request
112                 module.dispatcher = module.dispatcher or dynamic
113                 setfenv(module.dispatcher, module)
114                 return module.dispatcher(request)
115         end     
116 end
117
118 -- Sends a 404 error code and renders the "error404" template if available
119 function error404(message)
120         message = message or "Not Found"
121         
122         if not pcall(ffluci.template.render, "error404") then
123                 ffluci.http.textheader()
124                 print(message)
125         end
126         return false    
127 end
128
129 -- Sends a 500 error code and renders the "error500" template if available
130 function error500(message)
131         ffluci.http.status(500, "Internal Server Error")
132         
133         if not pcall(ffluci.template.render, "error500", {message=message}) then
134                 ffluci.http.textheader()
135                 print(message)
136         end
137         return false    
138 end
139
140
141 -- Dispatches a request depending on the PATH_INFO variable
142 function httpdispatch()
143         local pathinfo = os.getenv("PATH_INFO") or ""
144         local parts = pathinfo:gmatch("/[%w-]+")
145         
146         local sanitize = function(s, default)
147                 return s and s:sub(2) or default
148         end
149         
150         local cat = sanitize(parts(), "public")
151         local mod = sanitize(parts(), "index")
152         local act = sanitize(parts(), "index")
153         
154         assign_privileges(cat)
155         dispatch({category=cat, module=mod, action=act})
156 end
157
158
159 -- Dispatchers --
160
161
162 -- The Action Dispatcher searches the module for any function called
163 -- action_"request.action" and calls it
164 function action(...)
165         local disp = require("ffluci.dispatcher")
166         if not disp._action(...) then
167                 disp.error404()
168         end     
169 end
170
171 -- The CBI dispatcher directly parses and renders the CBI map which is
172 -- placed in ffluci/modles/cbi/"request.module"/"request.action" 
173 function cbi(...)
174         local disp = require("ffluci.dispatcher")
175         if not disp._cbi(...) then
176                 disp.error404()
177         end
178 end
179
180 -- The dynamic dispatcher chains the action, submodule, simpleview and CBI dispatcher
181 -- in this particular order. It is the default dispatcher.
182 function dynamic(...)
183         local disp = require("ffluci.dispatcher")
184         if  not disp._action(...)
185         and not disp._submodule(...)
186         and not disp._simpleview(...)
187         and not disp._cbi(...) then
188                 disp.error404()
189         end
190 end
191
192 -- The Simple View Dispatcher directly renders the template
193 -- which is placed in ffluci/views/"request.module"/"request.action" 
194 function simpleview(...)
195         local disp = require("ffluci.dispatcher")
196         if not disp._simpleview(...) then
197                 disp.error404()
198         end
199 end
200
201
202 -- The submodule dispatcher tries to load a submodule of the controller
203 -- and calls its "action"-function
204 function submodule(...)
205         local disp = require("ffluci.dispatcher")
206         if not disp._submodule(...) then
207                 disp.error404()
208         end
209 end
210
211
212 -- Internal Dispatcher Functions --
213
214 function _action(request)
215         local action = getfenv()["action_" .. request.action:gsub("-", "_")]
216         local i18n = require("ffluci.i18n")
217         
218         if action then
219                 i18n.loadc(request.category .. "_" .. request.module)
220                 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
221                 action()
222                 return true
223         else
224                 return false
225         end
226 end
227
228
229 function _cbi(request)
230         local disp = require("ffluci.dispatcher")
231         local tmpl = require("ffluci.template")
232         local cbi  = require("ffluci.cbi")
233         local i18n = require("ffluci.i18n")
234         
235         local path = request.category.."_"..request.module.."/"..request.action
236         
237         local stat, map = pcall(cbi.load, path)
238         if stat and map then
239                 local stat, err = pcall(map.parse, map)
240                 if not stat then
241                         disp.error500(err)
242                         return true
243                 end
244                 i18n.loadc(request.category .. "_" .. request.module)
245                 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
246                 tmpl.render("cbi/header")
247                 map:render()
248                 tmpl.render("cbi/footer")
249                 return true
250         elseif not stat then
251                 disp.error500(map)
252                 return true
253         else
254                 return false
255         end
256 end
257
258
259 function _simpleview(request)
260         local i18n = require("ffluci.i18n")
261         local tmpl = require("ffluci.template")
262         
263         local path = request.category.."_"..request.module.."/"..request.action
264         
265         local stat, t = pcall(tmpl.Template, path)
266         if stat then
267                 i18n.loadc(request.category .. "_" .. request.module)
268                 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
269                 t:render()
270                 return true
271         else
272                 return false
273         end
274 end
275
276
277 function _submodule(request)
278         local i18n = require("ffluci.i18n")
279         local m = "ffluci.controller." .. request.category .. "." ..
280          request.module .. "." .. request.action
281         local stat, module = pcall(require, m)
282         
283         if stat and module.action then 
284                 i18n.loadc(request.category .. "_" .. request.module)
285                 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
286                 return pcall(module.action)
287         end
288         
289         return false
290 end