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