summaryrefslogtreecommitdiffhomepage
path: root/core/src/dispatcher.lua
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/dispatcher.lua')
-rw-r--r--core/src/dispatcher.lua300
1 files changed, 300 insertions, 0 deletions
diff --git a/core/src/dispatcher.lua b/core/src/dispatcher.lua
new file mode 100644
index 000000000..c60e5dcd1
--- /dev/null
+++ b/core/src/dispatcher.lua
@@ -0,0 +1,300 @@
+--[[
+FFLuCI - Dispatcher
+
+Description:
+The request dispatcher and module dispatcher generators
+
+
+The dispatching process:
+ For a detailed explanation of the dispatching process we assume:
+ You have installed the FFLuCI CGI-Dispatcher in /cgi-bin/ffluci
+
+ To enforce a higher level of security only the CGI-Dispatcher
+ resides inside the web server's document root, everything else
+ stays inside an external directory, we assume this is /lua/ffluci
+ for this explanation.
+
+ All controllers and action are reachable as sub-objects of /cgi-bin/ffluci
+ as if they were virtual folders and files
+ e.g.: /cgi-bin/ffluci/public/info/about
+ /cgi-bin/ffluci/admin/network/interfaces
+ and so on.
+
+ The PATH_INFO variable holds the dispatch path and
+ will be split into three parts: /category/module/action
+
+ Category: This is the category in which modules are stored in
+ By default there are two categories:
+ "public" - which is the default public category
+ "admin" - which is the default protected category
+
+ As FFLuCI itself does not implement authentication
+ you should make sure that "admin" and other sensitive
+ categories are protected by the webserver.
+
+ E.g. for busybox add a line like:
+ /cgi-bin/ffluci/admin:root:$p$root
+ to /etc/httpd.conf to protect the "admin" category
+
+
+ Module: This is the controller which will handle the request further
+ It is always a submodule of ffluci.controller, so a module
+ called "helloworld" will be stored in
+ /lua/ffluci/controller/helloworld.lua
+ You are free to submodule your controllers any further.
+
+ Action: This is action that will be invoked after loading the module.
+ The kind of how the action will be dispatched depends on
+ the module dispatcher that is defined in the controller.
+ See the description of the default module dispatcher down
+ on this page for some examples.
+
+
+ The main dispatcher at first searches for the module by trying to
+ include ffluci.controller.category.module
+ (where "category" is the category name and "module" is the module name)
+ If this fails a 404 status code will be send to the client and FFLuCI exits
+
+ Then the main dispatcher calls the module dispatcher
+ ffluci.controller.category.module.dispatcher with the request object
+ as the only argument. The module dispatcher is then responsible
+ for the further dispatching process.
+
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.dispatcher", package.seeall)
+require("ffluci.http")
+require("ffluci.template")
+require("ffluci.config")
+require("ffluci.sys")
+
+-- Sets privilege for given category
+function assign_privileges(category)
+ local cp = ffluci.config.category_privileges
+ if cp and cp[category] then
+ local u, g = cp[category]:match("([^:]+):([^:]+)")
+ ffluci.sys.process.setuser(u)
+ ffluci.sys.process.setgroup(g)
+ end
+end
+
+
+-- Builds a URL from a triple of category, module and action
+function build_url(category, module, action)
+ category = category or "public"
+ module = module or "index"
+ action = action or "index"
+
+ local pattern = ffluci.http.env.SCRIPT_NAME .. "/%s/%s/%s"
+ return pattern:format(category, module, action)
+end
+
+
+-- Dispatches the "request"
+function dispatch(req)
+ request = req
+ local m = "ffluci.controller." .. request.category .. "." .. request.module
+ local stat, module = pcall(require, m)
+ if not stat then
+ return error404()
+ else
+ module.request = request
+ module.dispatcher = module.dispatcher or dynamic
+ setfenv(module.dispatcher, module)
+ return module.dispatcher(request)
+ end
+end
+
+-- Sends a 404 error code and renders the "error404" template if available
+function error404(message)
+ ffluci.http.status(404, "Not Found")
+ message = message or "Not Found"
+
+ if not pcall(ffluci.template.render, "error404") then
+ ffluci.http.prepare_content("text/plain")
+ print(message)
+ end
+ return false
+end
+
+-- Sends a 500 error code and renders the "error500" template if available
+function error500(message)
+ ffluci.http.status(500, "Internal Server Error")
+
+ if not pcall(ffluci.template.render, "error500", {message=message}) then
+ ffluci.http.prepare_content("text/plain")
+ print(message)
+ end
+ return false
+end
+
+
+-- Dispatches a request depending on the PATH_INFO variable
+function httpdispatch()
+ local pathinfo = ffluci.http.env.PATH_INFO or ""
+ local parts = pathinfo:gmatch("/[%w-]+")
+
+ local sanitize = function(s, default)
+ return s and s:sub(2) or default
+ end
+
+ local cat = sanitize(parts(), "public")
+ local mod = sanitize(parts(), "index")
+ local act = sanitize(parts(), "index")
+
+ assign_privileges(cat)
+ dispatch({category=cat, module=mod, action=act})
+end
+
+
+-- Dispatchers --
+
+
+-- The Action Dispatcher searches the module for any function called
+-- action_"request.action" and calls it
+function action(...)
+ local disp = require("ffluci.dispatcher")
+ if not disp._action(...) then
+ disp.error404()
+ end
+end
+
+-- The CBI dispatcher directly parses and renders the CBI map which is
+-- placed in ffluci/modles/cbi/"request.module"/"request.action"
+function cbi(...)
+ local disp = require("ffluci.dispatcher")
+ if not disp._cbi(...) then
+ disp.error404()
+ end
+end
+
+-- The dynamic dispatcher chains the action, submodule, simpleview and CBI dispatcher
+-- in this particular order. It is the default dispatcher.
+function dynamic(...)
+ local disp = require("ffluci.dispatcher")
+ if not disp._action(...)
+ and not disp._submodule(...)
+ and not disp._simpleview(...)
+ and not disp._cbi(...) then
+ disp.error404()
+ end
+end
+
+-- The Simple View Dispatcher directly renders the template
+-- which is placed in ffluci/views/"request.module"/"request.action"
+function simpleview(...)
+ local disp = require("ffluci.dispatcher")
+ if not disp._simpleview(...) then
+ disp.error404()
+ end
+end
+
+
+-- The submodule dispatcher tries to load a submodule of the controller
+-- and calls its "action"-function
+function submodule(...)
+ local disp = require("ffluci.dispatcher")
+ if not disp._submodule(...) then
+ disp.error404()
+ end
+end
+
+
+-- Internal Dispatcher Functions --
+
+function _action(request)
+ local action = getfenv(2)["action_" .. request.action:gsub("-", "_")]
+ local i18n = require("ffluci.i18n")
+
+ if action then
+ i18n.loadc(request.category .. "_" .. request.module)
+ i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
+ action()
+ return true
+ else
+ return false
+ end
+end
+
+
+function _cbi(request)
+ local disp = require("ffluci.dispatcher")
+ local tmpl = require("ffluci.template")
+ local cbi = require("ffluci.cbi")
+ local i18n = require("ffluci.i18n")
+
+ local path = request.category.."_"..request.module.."/"..request.action
+
+ local stat, map = pcall(cbi.load, path)
+ if stat and map then
+ local stat, err = pcall(map.parse, map)
+ if not stat then
+ disp.error500(err)
+ return true
+ end
+ i18n.loadc(request.category .. "_" .. request.module)
+ i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
+ tmpl.render("cbi/header")
+ map:render()
+ tmpl.render("cbi/footer")
+ return true
+ elseif not stat then
+ disp.error500(map)
+ return true
+ else
+ return false
+ end
+end
+
+
+function _simpleview(request)
+ local i18n = require("ffluci.i18n")
+ local tmpl = require("ffluci.template")
+
+ local path = request.category.."_"..request.module.."/"..request.action
+
+ local stat, t = pcall(tmpl.Template, path)
+ if stat then
+ i18n.loadc(request.category .. "_" .. request.module)
+ i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
+ t:render()
+ return true
+ else
+ return false
+ end
+end
+
+
+function _submodule(request)
+ local i18n = require("ffluci.i18n")
+ local m = "ffluci.controller." .. request.category .. "." ..
+ request.module .. "." .. request.action
+ local stat, module = pcall(require, m)
+
+ if stat and module.action then
+ i18n.loadc(request.category .. "_" .. request.module)
+ i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
+ return pcall(module.action)
+ end
+
+ return false
+end \ No newline at end of file