luci-base: expose effective dispatched path to client side environment
[oweals/luci.git] / modules / luci-base / luasrc / dispatcher.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
3 -- Licensed to the public under the Apache License 2.0.
4
5 local fs = require "nixio.fs"
6 local sys = require "luci.sys"
7 local util = require "luci.util"
8 local http = require "luci.http"
9 local nixio = require "nixio", require "nixio.util"
10
11 module("luci.dispatcher", package.seeall)
12 context = util.threadlocal()
13 uci = require "luci.model.uci"
14 i18n = require "luci.i18n"
15 _M.fs = fs
16
17 -- Index table
18 local index = nil
19
20 -- Fastindex
21 local fi
22
23
24 local function check_fs_depends(fs)
25         local fs = require "nixio.fs"
26
27         for path, kind in pairs(fs) do
28                 if kind == "directory" then
29                         local empty = true
30                         for entry in (fs.dir(path) or function() end) do
31                                 empty = false
32                                 break
33                         end
34                         if empty then
35                                 return false
36                         end
37                 elseif kind == "executable" then
38                         if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
39                                 return false
40                         end
41                 elseif kind == "file" then
42                         if fs.stat(path, "type") ~= "reg" then
43                                 return false
44                         end
45                 end
46         end
47
48         return true
49 end
50
51 local function check_uci_depends_options(conf, s, opts)
52         local uci = require "luci.model.uci"
53
54         if type(opts) == "string" then
55                 return (s[".type"] == opts)
56         elseif opts == true then
57                 for option, value in pairs(s) do
58                         if option:byte(1) ~= 46 then
59                                 return true
60                         end
61                 end
62         elseif type(opts) == "table" then
63                 for option, value in pairs(opts) do
64                         local sval = s[option]
65                         if type(sval) == "table" then
66                                 local found = false
67                                 for _, v in ipairs(sval) do
68                                         if v == value then
69                                                 found = true
70                                                 break
71                                         end
72                                 end
73                                 if not found then
74                                         return false
75                                 end
76                         elseif value == true then
77                                 if sval == nil then
78                                         return false
79                                 end
80                         else
81                                 if sval ~= value then
82                                         return false
83                                 end
84                         end
85                 end
86         end
87
88         return true
89 end
90
91 local function check_uci_depends_section(conf, sect)
92         local uci = require "luci.model.uci"
93
94         for section, options in pairs(sect) do
95                 local stype = section:match("^@([A-Za-z0-9_%-]+)$")
96                 if stype then
97                         local found = false
98                         uci:foreach(conf, stype, function(s)
99                                 if check_uci_depends_options(conf, s, options) then
100                                         found = true
101                                         return false
102                                 end
103                         end)
104                         if not found then
105                                 return false
106                         end
107                 else
108                         local s = uci:get_all(conf, section)
109                         if not s or not check_uci_depends_options(conf, s, options) then
110                                 return false
111                         end
112                 end
113         end
114
115         return true
116 end
117
118 local function check_uci_depends(conf)
119         local uci = require "luci.model.uci"
120
121         for config, values in pairs(conf) do
122                 if values == true then
123                         local found = false
124                         uci:foreach(config, nil, function(s)
125                                 found = true
126                                 return false
127                         end)
128                         if not found then
129                                 return false
130                         end
131                 elseif type(values) == "table" then
132                         if not check_uci_depends_section(config, values) then
133                                 return false
134                         end
135                 end
136         end
137
138         return true
139 end
140
141 local function check_depends(spec)
142         if type(spec.depends) ~= "table" then
143                 return true
144         end
145
146         if type(spec.depends.fs) == "table" and not check_fs_depends(spec.depends.fs) then
147                 local satisfied = false
148                 local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
149                 for _, alternative in ipairs(alternatives) do
150                         if check_fs_depends(alternative) then
151                                 satisfied = true
152                                 break
153                         end
154                 end
155                 if not satisfied then
156                         return false
157                 end
158         end
159
160         if type(spec.depends.uci) == "table" then
161                 local satisfied = false
162                 local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
163                 for _, alternative in ipairs(alternatives) do
164                         if check_uci_depends(alternative) then
165                                 satisfied = true
166                                 break
167                         end
168                 end
169                 if not satisfied then
170                         return false
171                 end
172         end
173
174         return true
175 end
176
177 local function target_to_json(target, module)
178         local action
179
180         if target.type == "call" then
181                 action = {
182                         ["type"] = "call",
183                         ["module"] = module,
184                         ["function"] = target.name,
185                         ["parameters"] = target.argv
186                 }
187         elseif target.type == "view" then
188                 action = {
189                         ["type"] = "view",
190                         ["path"] = target.view
191                 }
192         elseif target.type == "template" then
193                 action = {
194                         ["type"] = "template",
195                         ["path"] = target.view
196                 }
197         elseif target.type == "cbi" then
198                 action = {
199                         ["type"] = "cbi",
200                         ["path"] = target.model
201                 }
202         elseif target.type == "form" then
203                 action = {
204                         ["type"] = "form",
205                         ["path"] = target.model
206                 }
207         elseif target.type == "firstchild" then
208                 action = {
209                         ["type"] = "firstchild"
210                 }
211         elseif target.type == "firstnode" then
212                 action = {
213                         ["type"] = "firstchild",
214                         ["recurse"] = true
215                 }
216         elseif target.type == "arcombine" then
217                 if type(target.targets) == "table" then
218                         action = {
219                                 ["type"] = "arcombine",
220                                 ["targets"] = {
221                                         target_to_json(target.targets[1], module),
222                                         target_to_json(target.targets[2], module)
223                                 }
224                         }
225                 end
226         elseif target.type == "alias" then
227                 action = {
228                         ["type"] = "alias",
229                         ["path"] = table.concat(target.req, "/")
230                 }
231         elseif target.type == "rewrite" then
232                 action = {
233                         ["type"] = "rewrite",
234                         ["path"] = table.concat(target.req, "/"),
235                         ["remove"] = target.n
236                 }
237         end
238
239         if target.post and action then
240                 action.post = target.post
241         end
242
243         return action
244 end
245
246 local function tree_to_json(node, json)
247         local fs = require "nixio.fs"
248         local util = require "luci.util"
249
250         if type(node.nodes) == "table" then
251                 for subname, subnode in pairs(node.nodes) do
252                         local spec = {
253                                 title = util.striptags(subnode.title),
254                                 order = subnode.order
255                         }
256
257                         if subnode.leaf then
258                                 spec.wildcard = true
259                         end
260
261                         if subnode.cors then
262                                 spec.cors = true
263                         end
264
265                         if subnode.setuser then
266                                 spec.setuser = subnode.setuser
267                         end
268
269                         if subnode.setgroup then
270                                 spec.setgroup = subnode.setgroup
271                         end
272
273                         if type(subnode.target) == "table" then
274                                 spec.action = target_to_json(subnode.target, subnode.module)
275                         end
276
277                         if type(subnode.file_depends) == "table" then
278                                 for _, v in ipairs(subnode.file_depends) do
279                                         spec.depends = spec.depends or {}
280                                         spec.depends.fs = spec.depends.fs or {}
281
282                                         local ft = fs.stat(v, "type")
283                                         if ft == "dir" then
284                                                 spec.depends.fs[v] = "directory"
285                                         elseif v:match("/s?bin/") then
286                                                 spec.depends.fs[v] = "executable"
287                                         else
288                                                 spec.depends.fs[v] = "file"
289                                         end
290                                 end
291                         end
292
293                         if type(subnode.uci_depends) == "table" then
294                                 for k, v in pairs(subnode.uci_depends) do
295                                         spec.depends = spec.depends or {}
296                                         spec.depends.uci = spec.depends.uci or {}
297                                         spec.depends.uci[k] = v
298                                 end
299                         end
300
301                         if (subnode.sysauth_authenticator ~= nil) or
302                            (subnode.sysauth ~= nil and subnode.sysauth ~= false)
303                         then
304                                 if subnode.sysauth_authenticator == "htmlauth" then
305                                         spec.auth = {
306                                                 login = true,
307                                                 methods = { "cookie:sysauth" }
308                                         }
309                                 elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
310                                         spec.auth = {
311                                                 login = false,
312                                                 methods = { "param:auth", "cookie:sysauth" }
313                                         }
314                                 elseif subnode.module == "luci.controller.admin.uci" then
315                                         spec.auth = {
316                                                 login = false,
317                                                 methods = { "param:sid" }
318                                         }
319                                 end
320                         elseif subnode.sysauth == false then
321                                 spec.auth = {}
322                         end
323
324                         for _, v in pairs(spec) do
325                                 if v ~= nil then
326                                         if not spec.action then
327                                                 spec.title = nil
328                                         end
329
330                                         spec.satisfied = check_depends(spec)
331                                         json.children = json.children or {}
332                                         json.children[subname] = tree_to_json(subnode, spec)
333                                         break
334                                 end
335                         end
336                 end
337         end
338
339         return json
340 end
341
342 function build_url(...)
343         local path = {...}
344         local url = { http.getenv("SCRIPT_NAME") or "" }
345
346         local p
347         for _, p in ipairs(path) do
348                 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
349                         url[#url+1] = "/"
350                         url[#url+1] = p
351                 end
352         end
353
354         if #path == 0 then
355                 url[#url+1] = "/"
356         end
357
358         return table.concat(url, "")
359 end
360
361 function _ordered_children(node)
362         local name, child, children = nil, nil, {}
363
364         for name, child in pairs(node.nodes) do
365                 children[#children+1] = {
366                         name  = name,
367                         node  = child,
368                         order = child.order or 100
369                 }
370         end
371
372         table.sort(children, function(a, b)
373                 if a.order == b.order then
374                         return a.name < b.name
375                 else
376                         return a.order < b.order
377                 end
378         end)
379
380         return children
381 end
382
383 local function dependencies_satisfied(node)
384         if type(node.file_depends) == "table" then
385                 for _, file in ipairs(node.file_depends) do
386                         local ftype = fs.stat(file, "type")
387                         if ftype == "dir" then
388                                 local empty = true
389                                 for e in (fs.dir(file) or function() end) do
390                                         empty = false
391                                 end
392                                 if empty then
393                                         return false
394                                 end
395                         elseif ftype == nil then
396                                 return false
397                         end
398                 end
399         end
400
401         if type(node.uci_depends) == "table" then
402                 for config, expect_sections in pairs(node.uci_depends) do
403                         if type(expect_sections) == "table" then
404                                 for section, expect_options in pairs(expect_sections) do
405                                         if type(expect_options) == "table" then
406                                                 for option, expect_value in pairs(expect_options) do
407                                                         local val = uci:get(config, section, option)
408                                                         if expect_value == true and val == nil then
409                                                                 return false
410                                                         elseif type(expect_value) == "string" then
411                                                                 if type(val) == "table" then
412                                                                         local found = false
413                                                                         for _, subval in ipairs(val) do
414                                                                                 if subval == expect_value then
415                                                                                         found = true
416                                                                                 end
417                                                                         end
418                                                                         if not found then
419                                                                                 return false
420                                                                         end
421                                                                 elseif val ~= expect_value then
422                                                                         return false
423                                                                 end
424                                                         end
425                                                 end
426                                         else
427                                                 local val = uci:get(config, section)
428                                                 if expect_options == true and val == nil then
429                                                         return false
430                                                 elseif type(expect_options) == "string" and val ~= expect_options then
431                                                         return false
432                                                 end
433                                         end
434                                 end
435                         elseif expect_sections == true then
436                                 if not uci:get_first(config) then
437                                         return false
438                                 end
439                         end
440                 end
441         end
442
443         return true
444 end
445
446 function node_visible(node)
447    if node then
448           return not (
449                  (not dependencies_satisfied(node)) or
450                  (not node.title or #node.title == 0) or
451                  (not node.target or node.hidden == true) or
452                  (type(node.target) == "table" and node.target.type == "firstchild" and
453                   (type(node.nodes) ~= "table" or not next(node.nodes)))
454           )
455    end
456    return false
457 end
458
459 function node_childs(node)
460         local rv = { }
461         if node then
462                 local _, child
463                 for _, child in ipairs(_ordered_children(node)) do
464                         if node_visible(child.node) then
465                                 rv[#rv+1] = child.name
466                         end
467                 end
468         end
469         return rv
470 end
471
472
473 function error404(message)
474         http.status(404, "Not Found")
475         message = message or "Not Found"
476
477         local function render()
478                 local template = require "luci.template"
479                 template.render("error404")
480         end
481
482         if not util.copcall(render) then
483                 http.prepare_content("text/plain")
484                 http.write(message)
485         end
486
487         return false
488 end
489
490 function error500(message)
491         util.perror(message)
492         if not context.template_header_sent then
493                 http.status(500, "Internal Server Error")
494                 http.prepare_content("text/plain")
495                 http.write(message)
496         else
497                 require("luci.template")
498                 if not util.copcall(luci.template.render, "error500", {message=message}) then
499                         http.prepare_content("text/plain")
500                         http.write(message)
501                 end
502         end
503         return false
504 end
505
506 function httpdispatch(request, prefix)
507         http.context.request = request
508
509         local r = {}
510         context.request = r
511
512         local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
513
514         if prefix then
515                 for _, node in ipairs(prefix) do
516                         r[#r+1] = node
517                 end
518         end
519
520         local node
521         for node in pathinfo:gmatch("[^/%z]+") do
522                 r[#r+1] = node
523         end
524
525         local stat, err = util.coxpcall(function()
526                 dispatch(context.request)
527         end, error500)
528
529         http.close()
530
531         --context._disable_memtrace()
532 end
533
534 local function require_post_security(target, args)
535         if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
536                 return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
537         end
538
539         if type(target) == "table" then
540                 if type(target.post) == "table" then
541                         local param_name, required_val, request_val
542
543                         for param_name, required_val in pairs(target.post) do
544                                 request_val = http.formvalue(param_name)
545
546                                 if (type(required_val) == "string" and
547                                     request_val ~= required_val) or
548                                    (required_val == true and request_val == nil)
549                                 then
550                                         return false
551                                 end
552                         end
553
554                         return true
555                 end
556
557                 return (target.post == true)
558         end
559
560         return false
561 end
562
563 function test_post_security()
564         if http.getenv("REQUEST_METHOD") ~= "POST" then
565                 http.status(405, "Method Not Allowed")
566                 http.header("Allow", "POST")
567                 return false
568         end
569
570         if http.formvalue("token") ~= context.authtoken then
571                 http.status(403, "Forbidden")
572                 luci.template.render("csrftoken")
573                 return false
574         end
575
576         return true
577 end
578
579 local function session_retrieve(sid, allowed_users)
580         local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
581
582         if type(sdat) == "table" and
583            type(sdat.values) == "table" and
584            type(sdat.values.token) == "string" and
585            (not allowed_users or
586             util.contains(allowed_users, sdat.values.username))
587         then
588                 uci:set_session_id(sid)
589                 return sid, sdat.values
590         end
591
592         return nil, nil
593 end
594
595 local function session_setup(user, pass, allowed_users)
596         if util.contains(allowed_users, user) then
597                 local login = util.ubus("session", "login", {
598                         username = user,
599                         password = pass,
600                         timeout  = tonumber(luci.config.sauth.sessiontime)
601                 })
602
603                 local rp = context.requestpath
604                         and table.concat(context.requestpath, "/") or ""
605
606                 if type(login) == "table" and
607                    type(login.ubus_rpc_session) == "string"
608                 then
609                         util.ubus("session", "set", {
610                                 ubus_rpc_session = login.ubus_rpc_session,
611                                 values = { token = sys.uniqueid(16) }
612                         })
613
614                         io.stderr:write("luci: accepted login on /%s for %s from %s\n"
615                                 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
616
617                         return session_retrieve(login.ubus_rpc_session)
618                 end
619
620                 io.stderr:write("luci: failed login on /%s for %s from %s\n"
621                         %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
622         end
623
624         return nil, nil
625 end
626
627 function menu_json()
628         local tree = context.tree or createtree()
629         return tree_to_json(tree, {
630                 action = {
631                         ["type"] = "firstchild",
632                         ["recurse"] = true
633                 }
634         })
635 end
636
637 function dispatch(request)
638         --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
639         local ctx = context
640         ctx.path = request
641
642         local conf = require "luci.config"
643         assert(conf.main,
644                 "/etc/config/luci seems to be corrupt, unable to find section 'main'")
645
646         local i18n = require "luci.i18n"
647         local lang = conf.main.lang or "auto"
648         if lang == "auto" then
649                 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
650                 for aclang in aclang:gmatch("[%w_-]+") do
651                         local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
652                         if country and culture then
653                                 local cc = "%s_%s" %{ country, culture:lower() }
654                                 if conf.languages[cc] then
655                                         lang = cc
656                                         break
657                                 elseif conf.languages[country] then
658                                         lang = country
659                                         break
660                                 end
661                         elseif conf.languages[aclang] then
662                                 lang = aclang
663                                 break
664                         end
665                 end
666         end
667         if lang == "auto" then
668                 lang = i18n.default
669         end
670         i18n.setlanguage(lang)
671
672         local c = ctx.tree
673         local stat
674         if not c then
675                 c = createtree()
676         end
677
678         local track = {}
679         local args = {}
680         ctx.args = args
681         ctx.requestargs = ctx.requestargs or args
682         local n
683         local preq = {}
684         local freq = {}
685
686         for i, s in ipairs(request) do
687                 preq[#preq+1] = s
688                 freq[#freq+1] = s
689                 c = c.nodes[s]
690                 n = i
691                 if not c then
692                         break
693                 end
694
695                 util.update(track, c)
696
697                 if c.leaf then
698                         break
699                 end
700         end
701
702         if c and c.leaf then
703                 for j=n+1, #request do
704                         args[#args+1] = request[j]
705                         freq[#freq+1] = request[j]
706                 end
707         end
708
709         ctx.requestpath = ctx.requestpath or freq
710         ctx.path = preq
711
712         -- Init template engine
713         if (c and c.index) or not track.notemplate then
714                 local tpl = require("luci.template")
715                 local media = track.mediaurlbase or luci.config.main.mediaurlbase
716                 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
717                         media = nil
718                         for name, theme in pairs(luci.config.themes) do
719                                 if name:sub(1,1) ~= "." and pcall(tpl.Template,
720                                  "themes/%s/header" % fs.basename(theme)) then
721                                         media = theme
722                                 end
723                         end
724                         assert(media, "No valid theme found")
725                 end
726
727                 local function _ifattr(cond, key, val, noescape)
728                         if cond then
729                                 local env = getfenv(3)
730                                 local scope = (type(env.self) == "table") and env.self
731                                 if type(val) == "table" then
732                                         if not next(val) then
733                                                 return ''
734                                         else
735                                                 val = util.serialize_json(val)
736                                         end
737                                 end
738
739                                 val = tostring(val or
740                                         (type(env[key]) ~= "function" and env[key]) or
741                                         (scope and type(scope[key]) ~= "function" and scope[key]) or "")
742
743                                 if noescape ~= true then
744                                         val = util.pcdata(val)
745                                 end
746
747                                 return string.format(' %s="%s"', tostring(key), val)
748                         else
749                                 return ''
750                         end
751                 end
752
753                 tpl.context.viewns = setmetatable({
754                    write       = http.write;
755                    include     = function(name) tpl.Template(name):render(getfenv(2)) end;
756                    translate   = i18n.translate;
757                    translatef  = i18n.translatef;
758                    export      = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
759                    striptags   = util.striptags;
760                    pcdata      = util.pcdata;
761                    media       = media;
762                    theme       = fs.basename(media);
763                    resource    = luci.config.main.resourcebase;
764                    ifattr      = function(...) return _ifattr(...) end;
765                    attr        = function(...) return _ifattr(true, ...) end;
766                    url         = build_url;
767                 }, {__index=function(tbl, key)
768                         if key == "controller" then
769                                 return build_url()
770                         elseif key == "REQUEST_URI" then
771                                 return build_url(unpack(ctx.requestpath))
772                         elseif key == "FULL_REQUEST_URI" then
773                                 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
774                                 local query = http.getenv("QUERY_STRING")
775                                 if query and #query > 0 then
776                                         url[#url+1] = "?"
777                                         url[#url+1] = query
778                                 end
779                                 return table.concat(url, "")
780                         elseif key == "token" then
781                                 return ctx.authtoken
782                         else
783                                 return rawget(tbl, key) or _G[key]
784                         end
785                 end})
786         end
787
788         track.dependent = (track.dependent ~= false)
789         assert(not track.dependent or not track.auto,
790                 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
791                 "has no parent node so the access to this location has been denied.\n" ..
792                 "This is a software bug, please report this message at " ..
793                 "https://github.com/openwrt/luci/issues"
794         )
795
796         if track.sysauth and not ctx.authsession then
797                 local authen = track.sysauth_authenticator
798                 local _, sid, sdat, default_user, allowed_users
799
800                 if type(authen) == "string" and authen ~= "htmlauth" then
801                         error500("Unsupported authenticator %q configured" % authen)
802                         return
803                 end
804
805                 if type(track.sysauth) == "table" then
806                         default_user, allowed_users = nil, track.sysauth
807                 else
808                         default_user, allowed_users = track.sysauth, { track.sysauth }
809                 end
810
811                 if type(authen) == "function" then
812                         _, sid = authen(sys.user.checkpasswd, allowed_users)
813                 else
814                         sid = http.getcookie("sysauth")
815                 end
816
817                 sid, sdat = session_retrieve(sid, allowed_users)
818
819                 if not (sid and sdat) and authen == "htmlauth" then
820                         local user = http.getenv("HTTP_AUTH_USER")
821                         local pass = http.getenv("HTTP_AUTH_PASS")
822
823                         if user == nil and pass == nil then
824                                 user = http.formvalue("luci_username")
825                                 pass = http.formvalue("luci_password")
826                         end
827
828                         sid, sdat = session_setup(user, pass, allowed_users)
829
830                         if not sid then
831                                 local tmpl = require "luci.template"
832
833                                 context.path = {}
834
835                                 http.status(403, "Forbidden")
836                                 http.header("X-LuCI-Login-Required", "yes")
837                                 tmpl.render(track.sysauth_template or "sysauth", {
838                                         duser = default_user,
839                                         fuser = user
840                                 })
841
842                                 return
843                         end
844
845                         http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
846                                 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
847                         })
848                         http.redirect(build_url(unpack(ctx.requestpath)))
849                 end
850
851                 if not sid or not sdat then
852                         http.status(403, "Forbidden")
853                         http.header("X-LuCI-Login-Required", "yes")
854                         return
855                 end
856
857                 ctx.authsession = sid
858                 ctx.authtoken = sdat.token
859                 ctx.authuser = sdat.username
860         end
861
862         if track.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
863                 luci.http.status(200, "OK")
864                 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
865                 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
866                 return
867         end
868
869         if c and require_post_security(c.target, args) then
870                 if not test_post_security(c) then
871                         return
872                 end
873         end
874
875         if track.setgroup then
876                 sys.process.setgroup(track.setgroup)
877         end
878
879         if track.setuser then
880                 sys.process.setuser(track.setuser)
881         end
882
883         local target = nil
884         if c then
885                 if type(c.target) == "function" then
886                         target = c.target
887                 elseif type(c.target) == "table" then
888                         target = c.target.target
889                 end
890         end
891
892         if c and (c.index or type(target) == "function") then
893                 ctx.dispatched = c
894                 ctx.requested = ctx.requested or ctx.dispatched
895         end
896
897         if c and c.index then
898                 local tpl = require "luci.template"
899
900                 if util.copcall(tpl.render, "indexer", {}) then
901                         return true
902                 end
903         end
904
905         if type(target) == "function" then
906                 util.copcall(function()
907                         local oldenv = getfenv(target)
908                         local module = require(c.module)
909                         local env = setmetatable({}, {__index=
910
911                         function(tbl, key)
912                                 return rawget(tbl, key) or module[key] or oldenv[key]
913                         end})
914
915                         setfenv(target, env)
916                 end)
917
918                 local ok, err
919                 if type(c.target) == "table" then
920                         ok, err = util.copcall(target, c.target, unpack(args))
921                 else
922                         ok, err = util.copcall(target, unpack(args))
923                 end
924                 if not ok then
925                         error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
926                                  " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
927                                  "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
928                 end
929         else
930                 local root = node()
931                 if not root or not root.target then
932                         error404("No root node was registered, this usually happens if no module was installed.\n" ..
933                                  "Install luci-mod-admin-full and retry. " ..
934                                  "If the module is already installed, try removing the /tmp/luci-indexcache file.")
935                 else
936                         error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
937                                  "If this url belongs to an extension, make sure it is properly installed.\n" ..
938                                  "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
939                 end
940         end
941 end
942
943 function createindex()
944         local controllers = { }
945         local base = "%s/controller/" % util.libpath()
946         local _, path
947
948         for path in (fs.glob("%s*.lua" % base) or function() end) do
949                 controllers[#controllers+1] = path
950         end
951
952         for path in (fs.glob("%s*/*.lua" % base) or function() end) do
953                 controllers[#controllers+1] = path
954         end
955
956         if indexcache then
957                 local cachedate = fs.stat(indexcache, "mtime")
958                 if cachedate then
959                         local realdate = 0
960                         for _, obj in ipairs(controllers) do
961                                 local omtime = fs.stat(obj, "mtime")
962                                 realdate = (omtime and omtime > realdate) and omtime or realdate
963                         end
964
965                         if cachedate > realdate and sys.process.info("uid") == 0 then
966                                 assert(
967                                         sys.process.info("uid") == fs.stat(indexcache, "uid")
968                                         and fs.stat(indexcache, "modestr") == "rw-------",
969                                         "Fatal: Indexcache is not sane!"
970                                 )
971
972                                 index = loadfile(indexcache)()
973                                 return index
974                         end
975                 end
976         end
977
978         index = {}
979
980         for _, path in ipairs(controllers) do
981                 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
982                 local mod = require(modname)
983                 assert(mod ~= true,
984                        "Invalid controller file found\n" ..
985                        "The file '" .. path .. "' contains an invalid module line.\n" ..
986                        "Please verify whether the module name is set to '" .. modname ..
987                        "' - It must correspond to the file path!")
988
989                 local idx = mod.index
990                 assert(type(idx) == "function",
991                        "Invalid controller file found\n" ..
992                        "The file '" .. path .. "' contains no index() function.\n" ..
993                        "Please make sure that the controller contains a valid " ..
994                        "index function and verify the spelling!")
995
996                 index[modname] = idx
997         end
998
999         if indexcache then
1000                 local f = nixio.open(indexcache, "w", 600)
1001                 f:writeall(util.get_bytecode(index))
1002                 f:close()
1003         end
1004 end
1005
1006 -- Build the index before if it does not exist yet.
1007 function createtree()
1008         if not index then
1009                 createindex()
1010         end
1011
1012         local ctx  = context
1013         local tree = {nodes={}, inreq=true}
1014
1015         ctx.treecache = setmetatable({}, {__mode="v"})
1016         ctx.tree = tree
1017
1018         local scope = setmetatable({}, {__index = luci.dispatcher})
1019
1020         for k, v in pairs(index) do
1021                 scope._NAME = k
1022                 setfenv(v, scope)
1023                 v()
1024         end
1025
1026         return tree
1027 end
1028
1029 function assign(path, clone, title, order)
1030         local obj  = node(unpack(path))
1031         obj.nodes  = nil
1032         obj.module = nil
1033
1034         obj.title = title
1035         obj.order = order
1036
1037         setmetatable(obj, {__index = _create_node(clone)})
1038
1039         return obj
1040 end
1041
1042 function entry(path, target, title, order)
1043         local c = node(unpack(path))
1044
1045         c.target = target
1046         c.title  = title
1047         c.order  = order
1048         c.module = getfenv(2)._NAME
1049
1050         return c
1051 end
1052
1053 -- enabling the node.
1054 function get(...)
1055         return _create_node({...})
1056 end
1057
1058 function node(...)
1059         local c = _create_node({...})
1060
1061         c.module = getfenv(2)._NAME
1062         c.auto = nil
1063
1064         return c
1065 end
1066
1067 function lookup(...)
1068         local i, path = nil, {}
1069         for i = 1, select('#', ...) do
1070                 local name, arg = nil, tostring(select(i, ...))
1071                 for name in arg:gmatch("[^/]+") do
1072                         path[#path+1] = name
1073                 end
1074         end
1075
1076         for i = #path, 1, -1 do
1077                 local node = context.treecache[table.concat(path, ".", 1, i)]
1078                 if node and (i == #path or node.leaf) then
1079                         return node, build_url(unpack(path))
1080                 end
1081         end
1082 end
1083
1084 function _create_node(path)
1085         if #path == 0 then
1086                 return context.tree
1087         end
1088
1089         local name = table.concat(path, ".")
1090         local c = context.treecache[name]
1091
1092         if not c then
1093                 local last = table.remove(path)
1094                 local parent = _create_node(path)
1095
1096                 c = {nodes={}, auto=true, inreq=true}
1097
1098                 local _, n
1099                 for _, n in ipairs(path) do
1100                         if context.path[_] ~= n then
1101                                 c.inreq = false
1102                                 break
1103                         end
1104                 end
1105
1106                 c.inreq = c.inreq and (context.path[#path + 1] == last)
1107
1108                 parent.nodes[last] = c
1109                 context.treecache[name] = c
1110         end
1111
1112         return c
1113 end
1114
1115 -- Subdispatchers --
1116
1117 function _find_eligible_node(root, prefix, deep, types, descend)
1118         local children = _ordered_children(root)
1119
1120         if not root.leaf and deep ~= nil then
1121                 local sub_path = { unpack(prefix) }
1122
1123                 if deep == false then
1124                         deep = nil
1125                 end
1126
1127                 local _, child
1128                 for _, child in ipairs(children) do
1129                         sub_path[#prefix+1] = child.name
1130
1131                         local res_path = _find_eligible_node(child.node, sub_path,
1132                                                              deep, types, true)
1133
1134                         if res_path then
1135                                 return res_path
1136                         end
1137                 end
1138         end
1139
1140         if descend and
1141            (not types or
1142             (type(root.target) == "table" and
1143              util.contains(types, root.target.type)))
1144         then
1145                 return prefix
1146         end
1147 end
1148
1149 function _find_node(recurse, types)
1150         local path = { unpack(context.path) }
1151         local name = table.concat(path, ".")
1152         local node = context.treecache[name]
1153
1154         path = _find_eligible_node(node, path, recurse, types)
1155
1156         if path then
1157                 dispatch(path)
1158         else
1159                 require "luci.template".render("empty_node_placeholder")
1160         end
1161 end
1162
1163 function _firstchild()
1164         return _find_node(false, nil)
1165 end
1166
1167 function firstchild()
1168         return { type = "firstchild", target = _firstchild }
1169 end
1170
1171 function _firstnode()
1172         return _find_node(true, { "cbi", "form", "template", "arcombine" })
1173 end
1174
1175 function firstnode()
1176         return { type = "firstnode", target = _firstnode }
1177 end
1178
1179 function _alias(self, ...)
1180         local req = { unpack(self.req) }
1181
1182         for _, r in ipairs({...}) do
1183                 req[#req+1] = r
1184         end
1185
1186         dispatch(req)
1187 end
1188
1189 function alias(...)
1190         return { type = "alias", target = _alias, req = { ... } }
1191 end
1192
1193 function _rewrite(self, ...)
1194         local n = self.n
1195         local req = { unpack(self.req) }
1196         local dispatched = util.clone(context.dispatched)
1197
1198         for i=1,n do
1199                 table.remove(dispatched, 1)
1200         end
1201
1202         for i, r in ipairs(req) do
1203                 table.insert(dispatched, i, r)
1204         end
1205
1206         for _, r in ipairs({...}) do
1207                 dispatched[#dispatched+1] = r
1208         end
1209
1210         dispatch(dispatched)
1211 end
1212
1213 function rewrite(n, ...)
1214         return { type = "rewrite", target = _rewrite, n = n, req = { ... } }
1215 end
1216
1217 local function _call(self, ...)
1218         local func = getfenv()[self.name]
1219         assert(func ~= nil,
1220                'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
1221
1222         assert(type(func) == "function",
1223                'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
1224                'of type "' .. type(func) .. '".')
1225
1226         if #self.argv > 0 then
1227                 return func(unpack(self.argv), ...)
1228         else
1229                 return func(...)
1230         end
1231 end
1232
1233 function call(name, ...)
1234         return {type = "call", argv = {...}, name = name, target = _call}
1235 end
1236
1237 function post_on(params, name, ...)
1238         return {
1239                 type = "call",
1240                 post = params,
1241                 argv = { ... },
1242                 name = name,
1243                 target = _call
1244         }
1245 end
1246
1247 function post(...)
1248         return post_on(true, ...)
1249 end
1250
1251
1252 local _template = function(self, ...)
1253         require "luci.template".render(self.view)
1254 end
1255
1256 function template(name)
1257         return {type = "template", view = name, target = _template}
1258 end
1259
1260
1261 local _view = function(self, ...)
1262         require "luci.template".render("view", { view = self.view })
1263 end
1264
1265 function view(name)
1266         return {type = "view", view = name, target = _view}
1267 end
1268
1269
1270 local function _cbi(self, ...)
1271         local cbi = require "luci.cbi"
1272         local tpl = require "luci.template"
1273         local http = require "luci.http"
1274
1275         local config = self.config or {}
1276         local maps = cbi.load(self.model, ...)
1277
1278         local state = nil
1279
1280         local i, res
1281         for i, res in ipairs(maps) do
1282                 if util.instanceof(res, cbi.SimpleForm) then
1283                         io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1284                                 % self.model)
1285
1286                         io.stderr:write("please change %s to use the form() action instead.\n"
1287                                 % table.concat(context.request, "/"))
1288                 end
1289
1290                 res.flow = config
1291                 local cstate = res:parse()
1292                 if cstate and (not state or cstate < state) then
1293                         state = cstate
1294                 end
1295         end
1296
1297         local function _resolve_path(path)
1298                 return type(path) == "table" and build_url(unpack(path)) or path
1299         end
1300
1301         if config.on_valid_to and state and state > 0 and state < 2 then
1302                 http.redirect(_resolve_path(config.on_valid_to))
1303                 return
1304         end
1305
1306         if config.on_changed_to and state and state > 1 then
1307                 http.redirect(_resolve_path(config.on_changed_to))
1308                 return
1309         end
1310
1311         if config.on_success_to and state and state > 0 then
1312                 http.redirect(_resolve_path(config.on_success_to))
1313                 return
1314         end
1315
1316         if config.state_handler then
1317                 if not config.state_handler(state, maps) then
1318                         return
1319                 end
1320         end
1321
1322         http.header("X-CBI-State", state or 0)
1323
1324         if not config.noheader then
1325                 tpl.render("cbi/header", {state = state})
1326         end
1327
1328         local redirect
1329         local messages
1330         local applymap   = false
1331         local pageaction = true
1332         local parsechain = { }
1333
1334         for i, res in ipairs(maps) do
1335                 if res.apply_needed and res.parsechain then
1336                         local c
1337                         for _, c in ipairs(res.parsechain) do
1338                                 parsechain[#parsechain+1] = c
1339                         end
1340                         applymap = true
1341                 end
1342
1343                 if res.redirect then
1344                         redirect = redirect or res.redirect
1345                 end
1346
1347                 if res.pageaction == false then
1348                         pageaction = false
1349                 end
1350
1351                 if res.message then
1352                         messages = messages or { }
1353                         messages[#messages+1] = res.message
1354                 end
1355         end
1356
1357         for i, res in ipairs(maps) do
1358                 res:render({
1359                         firstmap   = (i == 1),
1360                         redirect   = redirect,
1361                         messages   = messages,
1362                         pageaction = pageaction,
1363                         parsechain = parsechain
1364                 })
1365         end
1366
1367         if not config.nofooter then
1368                 tpl.render("cbi/footer", {
1369                         flow          = config,
1370                         pageaction    = pageaction,
1371                         redirect      = redirect,
1372                         state         = state,
1373                         autoapply     = config.autoapply,
1374                         trigger_apply = applymap
1375                 })
1376         end
1377 end
1378
1379 function cbi(model, config)
1380         return {
1381                 type = "cbi",
1382                 post = { ["cbi.submit"] = true },
1383                 config = config,
1384                 model = model,
1385                 target = _cbi
1386         }
1387 end
1388
1389
1390 local function _arcombine(self, ...)
1391         local argv = {...}
1392         local target = #argv > 0 and self.targets[2] or self.targets[1]
1393         setfenv(target.target, self.env)
1394         target:target(unpack(argv))
1395 end
1396
1397 function arcombine(trg1, trg2)
1398         return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
1399 end
1400
1401
1402 local function _form(self, ...)
1403         local cbi = require "luci.cbi"
1404         local tpl = require "luci.template"
1405         local http = require "luci.http"
1406
1407         local maps = luci.cbi.load(self.model, ...)
1408         local state = nil
1409
1410         local i, res
1411         for i, res in ipairs(maps) do
1412                 local cstate = res:parse()
1413                 if cstate and (not state or cstate < state) then
1414                         state = cstate
1415                 end
1416         end
1417
1418         http.header("X-CBI-State", state or 0)
1419         tpl.render("header")
1420         for i, res in ipairs(maps) do
1421                 res:render()
1422         end
1423         tpl.render("footer")
1424 end
1425
1426 function form(model)
1427         return {
1428                 type = "form",
1429                 post = { ["cbi.submit"] = true },
1430                 model = model,
1431                 target = _form
1432         }
1433 end
1434
1435 translate = i18n.translate
1436
1437 -- This function does not actually translate the given argument but
1438 -- is used by build/i18n-scan.pl to find translatable entries.
1439 function _(text)
1440         return text
1441 end