Merge branch 'menu'
[project/luci.git] / core / src / dispatcher.lua
index 0a66ccd..84c665e 100644 (file)
@@ -4,63 +4,6 @@ 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$
 
@@ -80,48 +23,24 @@ 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")
+require("ffluci.fs")
 
--- 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
+-- Local dispatch database
+local tree = {nodes={}}
 
+-- Global request object
+request = {}
 
--- 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.dispatcher() .. "/%s/%s/%s"
-       return pattern:format(category, module, action)
-end
+-- Active dispatched node
+dispatched = nil
 
 
--- 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     
+-- Builds a URL
+function build_url(...)
+       return ffluci.http.dispatcher() .. "/" .. table.concat(arg, "/") 
 end
 
 -- Sends a 404 error code and renders the "error404" template if available
@@ -129,6 +48,7 @@ function error404(message)
        ffluci.http.status(404, "Not Found")
        message = message or "Not Found"
        
+       require("ffluci.template")
        if not pcall(ffluci.template.render, "error404") then
                ffluci.http.prepare_content("text/plain")
                print(message)
@@ -140,6 +60,7 @@ end
 function error500(message)
        ffluci.http.status(500, "Internal Server Error")
        
+       require("ffluci.template")
        if not pcall(ffluci.template.render, "error500", {message=message}) then
                ffluci.http.prepare_content("text/plain")
                print(message)
@@ -147,154 +68,124 @@ function error500(message)
        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 c = tree
        
-       local sanitize = function(s, default)
-               return s and s:sub(2) or default
+       for s in pathinfo:gmatch("/([%w-]+)") do
+               table.insert(request, s)
        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     
+       dispatch()
 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()
+function dispatch()
+       local c = tree
+       local track = {}
+       
+       for i, s in ipairs(request) do
+               c = c.nodes[s]
+               if not c then
+                       break
+               end     
+                               
+               for k, v in pairs(c) do
+                       track[k] = v
+               end
        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()
+       
+       
+       if track.i18n then
+               require("ffluci.i18n").loadc(track.i18n)
        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()
+       
+       if track.setuser then
+               ffluci.sys.process.setuser(track.setuser)
        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()
+       
+       if track.setgroup then
+               ffluci.sys.process.setgroup(track.setgroup)
        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
+       
+       if c and type(c.target) == "function" then
+               dispatched = c
+               
+               stat, err = pcall(c.target)
+               if not stat then
+                       error500(err)
+               end
        else
-               return false
+               error404()
        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
+-- Calls the index function of all available controllers
+function createindex()
+       local root = ffluci.sys.libpath() .. "/controller/"
+       local suff = ".lua"
+       for i,c in ipairs(ffluci.fs.glob(root .. "*/*" .. suff)) do
+               c = "ffluci.controller." .. c:sub(#root+1, #c-#suff):gsub("/", ".", 1)
+               stat, mod = pcall(require, c)
        
-       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
+               if stat and mod and type(mod.index) == "function" then
+                       ffluci.util.updfenv(mod.index, ffluci.dispatcher)
+                       pcall(mod.index)
                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")
+-- Fetch a dispatching node
+function node(...)
+       local c = tree
        
-       local path = request.category.."_"..request.module.."/"..request.action
+       for k,v in ipairs(arg) do
+               if not c.nodes[v] then
+                       c.nodes[v] = {nodes={}}
+               end
+               
+               c = c.nodes[v]
+       end
        
-       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
+       return c
+end
+
+-- Subdispatchers --
+function alias(...)
+       local req = arg
+       return function()
+               request = req
+               dispatch()
        end
 end
 
+function template(name)
+       require("ffluci.template")
+       return function() ffluci.template.render(name) 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)
+function cbi(model)
+       require("ffluci.cbi")
+       require("ffluci.template")
        
-       if stat and module.action then 
-               i18n.loadc(request.category .. "_" .. request.module)
-               i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
-               return pcall(module.action)
+       return function()
+               local stat, res = pcall(ffluci.cbi.load, model)
+               if not stat then
+                       error500(res)
+                       return true
+               end
+               
+               local stat, err = pcall(res.parse, res)
+               if not stat then
+                       error500(err)
+                       return true
+               end
+               
+               ffluci.template.render("cbi/header")
+               res:render()
+               ffluci.template.render("cbi/footer")
        end
-       
-       return false
 end
\ No newline at end of file