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