* Replaced luafilesystem with luaposix library
[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