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