luci-base: properly handle undefined IPv6 local-address information
[project/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 function build_url(...)
25         local path = {...}
26         local url = { http.getenv("SCRIPT_NAME") or "" }
27
28         local p
29         for _, p in ipairs(path) do
30                 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
31                         url[#url+1] = "/"
32                         url[#url+1] = p
33                 end
34         end
35
36         if #path == 0 then
37                 url[#url+1] = "/"
38         end
39
40         return table.concat(url, "")
41 end
42
43 function node_visible(node)
44    if node then
45           return not (
46                  (not node.title or #node.title == 0) or
47                  (not node.target or node.hidden == true) or
48                  (type(node.target) == "table" and node.target.type == "firstchild" and
49                   (type(node.nodes) ~= "table" or not next(node.nodes)))
50           )
51    end
52    return false
53 end
54
55 function node_childs(node)
56         local rv = { }
57         if node then
58                 local k, v
59                 for k, v in util.spairs(node.nodes,
60                         function(a, b)
61                                 return (node.nodes[a].order or 100)
62                                      < (node.nodes[b].order or 100)
63                         end)
64                 do
65                         if node_visible(v) then
66                                 rv[#rv+1] = k
67                         end
68                 end
69         end
70         return rv
71 end
72
73
74 function error404(message)
75         http.status(404, "Not Found")
76         message = message or "Not Found"
77
78         require("luci.template")
79         if not util.copcall(luci.template.render, "error404") then
80                 http.prepare_content("text/plain")
81                 http.write(message)
82         end
83         return false
84 end
85
86 function error500(message)
87         util.perror(message)
88         if not context.template_header_sent then
89                 http.status(500, "Internal Server Error")
90                 http.prepare_content("text/plain")
91                 http.write(message)
92         else
93                 require("luci.template")
94                 if not util.copcall(luci.template.render, "error500", {message=message}) then
95                         http.prepare_content("text/plain")
96                         http.write(message)
97                 end
98         end
99         return false
100 end
101
102 function httpdispatch(request, prefix)
103         http.context.request = request
104
105         local r = {}
106         context.request = r
107
108         local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
109
110         if prefix then
111                 for _, node in ipairs(prefix) do
112                         r[#r+1] = node
113                 end
114         end
115
116         for node in pathinfo:gmatch("[^/]+") do
117                 r[#r+1] = node
118         end
119
120         local stat, err = util.coxpcall(function()
121                 dispatch(context.request)
122         end, error500)
123
124         http.close()
125
126         --context._disable_memtrace()
127 end
128
129 local function require_post_security(target)
130         if type(target) == "table" then
131                 if type(target.post) == "table" then
132                         local param_name, required_val, request_val
133
134                         for param_name, required_val in pairs(target.post) do
135                                 request_val = http.formvalue(param_name)
136
137                                 if (type(required_val) == "string" and
138                                     request_val ~= required_val) or
139                                    (required_val == true and
140                                     (request_val == nil or request_val == ""))
141                                 then
142                                         return false
143                                 end
144                         end
145
146                         return true
147                 end
148
149                 return (target.post == true)
150         end
151
152         return false
153 end
154
155 function test_post_security()
156         if http.getenv("REQUEST_METHOD") ~= "POST" then
157                 http.status(405, "Method Not Allowed")
158                 http.header("Allow", "POST")
159                 return false
160         end
161
162         if http.formvalue("token") ~= context.authtoken then
163                 http.status(403, "Forbidden")
164                 luci.template.render("csrftoken")
165                 return false
166         end
167
168         return true
169 end
170
171 local function session_retrieve(sid, allowed_users)
172         local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
173
174         if type(sdat) == "table" and
175            type(sdat.values) == "table" and
176            type(sdat.values.token) == "string" and
177            (not allowed_users or
178             util.contains(allowed_users, sdat.values.username))
179         then
180                 return sid, sdat.values
181         end
182
183         return nil, nil
184 end
185
186 local function session_setup(user, pass, allowed_users)
187         if util.contains(allowed_users, user) then
188                 local login = util.ubus("session", "login", {
189                         username = user,
190                         password = pass,
191                         timeout  = tonumber(luci.config.sauth.sessiontime)
192                 })
193
194                 local rp = context.requestpath
195                         and table.concat(context.requestpath, "/") or ""
196
197                 if type(login) == "table" and
198                    type(login.ubus_rpc_session) == "string"
199                 then
200                         util.ubus("session", "set", {
201                                 ubus_rpc_session = login.ubus_rpc_session,
202                                 values = { token = sys.uniqueid(16) }
203                         })
204
205                         io.stderr:write("luci: accepted login on /%s for %s from %s\n"
206                                 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
207
208                         return session_retrieve(login.ubus_rpc_session)
209                 end
210
211                 io.stderr:write("luci: failed login on /%s for %s from %s\n"
212                         %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
213         end
214
215         return nil, nil
216 end
217
218 function dispatch(request)
219         --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
220         local ctx = context
221         ctx.path = request
222
223         local conf = require "luci.config"
224         assert(conf.main,
225                 "/etc/config/luci seems to be corrupt, unable to find section 'main'")
226
227         local i18n = require "luci.i18n"
228         local lang = conf.main.lang or "auto"
229         if lang == "auto" then
230                 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
231                 for aclang in aclang:gmatch("[%w_-]+") do
232                         local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
233                         if country and culture then
234                                 local cc = "%s_%s" %{ country, culture:lower() }
235                                 if conf.languages[cc] then
236                                         lang = cc
237                                         break
238                                 elseif conf.languages[country] then
239                                         lang = country
240                                         break
241                                 end
242                         elseif conf.languages[aclang] then
243                                 lang = aclang
244                                 break
245                         end
246                 end
247         end
248         if lang == "auto" then
249                 lang = i18n.default
250         end
251         i18n.setlanguage(lang)
252
253         local c = ctx.tree
254         local stat
255         if not c then
256                 c = createtree()
257         end
258
259         local track = {}
260         local args = {}
261         ctx.args = args
262         ctx.requestargs = ctx.requestargs or args
263         local n
264         local preq = {}
265         local freq = {}
266
267         for i, s in ipairs(request) do
268                 preq[#preq+1] = s
269                 freq[#freq+1] = s
270                 c = c.nodes[s]
271                 n = i
272                 if not c then
273                         break
274                 end
275
276                 util.update(track, c)
277
278                 if c.leaf then
279                         break
280                 end
281         end
282
283         if c and c.leaf then
284                 for j=n+1, #request do
285                         args[#args+1] = request[j]
286                         freq[#freq+1] = request[j]
287                 end
288         end
289
290         ctx.requestpath = ctx.requestpath or freq
291         ctx.path = preq
292
293         if track.i18n then
294                 i18n.loadc(track.i18n)
295         end
296
297         -- Init template engine
298         if (c and c.index) or not track.notemplate then
299                 local tpl = require("luci.template")
300                 local media = track.mediaurlbase or luci.config.main.mediaurlbase
301                 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
302                         media = nil
303                         for name, theme in pairs(luci.config.themes) do
304                                 if name:sub(1,1) ~= "." and pcall(tpl.Template,
305                                  "themes/%s/header" % fs.basename(theme)) then
306                                         media = theme
307                                 end
308                         end
309                         assert(media, "No valid theme found")
310                 end
311
312                 local function _ifattr(cond, key, val)
313                         if cond then
314                                 local env = getfenv(3)
315                                 local scope = (type(env.self) == "table") and env.self
316                                 if type(val) == "table" then
317                                         if not next(val) then
318                                                 return ''
319                                         else
320                                                 val = util.serialize_json(val)
321                                         end
322                                 end
323                                 return string.format(
324                                         ' %s="%s"', tostring(key),
325                                         util.pcdata(tostring( val
326                                          or (type(env[key]) ~= "function" and env[key])
327                                          or (scope and type(scope[key]) ~= "function" and scope[key])
328                                          or "" ))
329                                 )
330                         else
331                                 return ''
332                         end
333                 end
334
335                 tpl.context.viewns = setmetatable({
336                    write       = http.write;
337                    include     = function(name) tpl.Template(name):render(getfenv(2)) end;
338                    translate   = i18n.translate;
339                    translatef  = i18n.translatef;
340                    export      = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
341                    striptags   = util.striptags;
342                    pcdata      = util.pcdata;
343                    media       = media;
344                    theme       = fs.basename(media);
345                    resource    = luci.config.main.resourcebase;
346                    ifattr      = function(...) return _ifattr(...) end;
347                    attr        = function(...) return _ifattr(true, ...) end;
348                    url         = build_url;
349                 }, {__index=function(table, key)
350                         if key == "controller" then
351                                 return build_url()
352                         elseif key == "REQUEST_URI" then
353                                 return build_url(unpack(ctx.requestpath))
354                         elseif key == "token" then
355                                 return ctx.authtoken
356                         else
357                                 return rawget(table, key) or _G[key]
358                         end
359                 end})
360         end
361
362         track.dependent = (track.dependent ~= false)
363         assert(not track.dependent or not track.auto,
364                 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
365                 "has no parent node so the access to this location has been denied.\n" ..
366                 "This is a software bug, please report this message at " ..
367                 "https://github.com/openwrt/luci/issues"
368         )
369
370         if track.sysauth and not ctx.authsession then
371                 local authen = track.sysauth_authenticator
372                 local _, sid, sdat, default_user, allowed_users
373
374                 if type(authen) == "string" and authen ~= "htmlauth" then
375                         error500("Unsupported authenticator %q configured" % authen)
376                         return
377                 end
378
379                 if type(track.sysauth) == "table" then
380                         default_user, allowed_users = nil, track.sysauth
381                 else
382                         default_user, allowed_users = track.sysauth, { track.sysauth }
383                 end
384
385                 if type(authen) == "function" then
386                         _, sid = authen(sys.user.checkpasswd, allowed_users)
387                 else
388                         sid = http.getcookie("sysauth")
389                 end
390
391                 sid, sdat = session_retrieve(sid, allowed_users)
392
393                 if not (sid and sdat) and authen == "htmlauth" then
394                         local user = http.getenv("HTTP_AUTH_USER")
395                         local pass = http.getenv("HTTP_AUTH_PASS")
396
397                         if user == nil and pass == nil then
398                                 user = http.formvalue("luci_username")
399                                 pass = http.formvalue("luci_password")
400                         end
401
402                         sid, sdat = session_setup(user, pass, allowed_users)
403
404                         if not sid then
405                                 local tmpl = require "luci.template"
406
407                                 context.path = {}
408
409                                 http.status(403, "Forbidden")
410                                 tmpl.render(track.sysauth_template or "sysauth", {
411                                         duser = default_user,
412                                         fuser = user
413                                 })
414
415                                 return
416                         end
417
418                         http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sid, build_url() })
419                         http.redirect(build_url(unpack(ctx.requestpath)))
420                 end
421
422                 if not sid or not sdat then
423                         http.status(403, "Forbidden")
424                         return
425                 end
426
427                 ctx.authsession = sid
428                 ctx.authtoken = sdat.token
429                 ctx.authuser = sdat.username
430         end
431
432         if c and require_post_security(c.target) then
433                 if not test_post_security(c) then
434                         return
435                 end
436         end
437
438         if track.setgroup then
439                 sys.process.setgroup(track.setgroup)
440         end
441
442         if track.setuser then
443                 sys.process.setuser(track.setuser)
444         end
445
446         local target = nil
447         if c then
448                 if type(c.target) == "function" then
449                         target = c.target
450                 elseif type(c.target) == "table" then
451                         target = c.target.target
452                 end
453         end
454
455         if c and (c.index or type(target) == "function") then
456                 ctx.dispatched = c
457                 ctx.requested = ctx.requested or ctx.dispatched
458         end
459
460         if c and c.index then
461                 local tpl = require "luci.template"
462
463                 if util.copcall(tpl.render, "indexer", {}) then
464                         return true
465                 end
466         end
467
468         if type(target) == "function" then
469                 util.copcall(function()
470                         local oldenv = getfenv(target)
471                         local module = require(c.module)
472                         local env = setmetatable({}, {__index=
473
474                         function(tbl, key)
475                                 return rawget(tbl, key) or module[key] or oldenv[key]
476                         end})
477
478                         setfenv(target, env)
479                 end)
480
481                 local ok, err
482                 if type(c.target) == "table" then
483                         ok, err = util.copcall(target, c.target, unpack(args))
484                 else
485                         ok, err = util.copcall(target, unpack(args))
486                 end
487                 assert(ok,
488                        "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
489                        " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
490                        "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
491         else
492                 local root = node()
493                 if not root or not root.target then
494                         error404("No root node was registered, this usually happens if no module was installed.\n" ..
495                                  "Install luci-mod-admin-full and retry. " ..
496                                  "If the module is already installed, try removing the /tmp/luci-indexcache file.")
497                 else
498                         error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
499                                  "If this url belongs to an extension, make sure it is properly installed.\n" ..
500                                  "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
501                 end
502         end
503 end
504
505 function createindex()
506         local controllers = { }
507         local base = "%s/controller/" % util.libpath()
508         local _, path
509
510         for path in (fs.glob("%s*.lua" % base) or function() end) do
511                 controllers[#controllers+1] = path
512         end
513
514         for path in (fs.glob("%s*/*.lua" % base) or function() end) do
515                 controllers[#controllers+1] = path
516         end
517
518         if indexcache then
519                 local cachedate = fs.stat(indexcache, "mtime")
520                 if cachedate then
521                         local realdate = 0
522                         for _, obj in ipairs(controllers) do
523                                 local omtime = fs.stat(obj, "mtime")
524                                 realdate = (omtime and omtime > realdate) and omtime or realdate
525                         end
526
527                         if cachedate > realdate and sys.process.info("uid") == 0 then
528                                 assert(
529                                         sys.process.info("uid") == fs.stat(indexcache, "uid")
530                                         and fs.stat(indexcache, "modestr") == "rw-------",
531                                         "Fatal: Indexcache is not sane!"
532                                 )
533
534                                 index = loadfile(indexcache)()
535                                 return index
536                         end
537                 end
538         end
539
540         index = {}
541
542         for _, path in ipairs(controllers) do
543                 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
544                 local mod = require(modname)
545                 assert(mod ~= true,
546                        "Invalid controller file found\n" ..
547                        "The file '" .. path .. "' contains an invalid module line.\n" ..
548                        "Please verify whether the module name is set to '" .. modname ..
549                        "' - It must correspond to the file path!")
550
551                 local idx = mod.index
552                 assert(type(idx) == "function",
553                        "Invalid controller file found\n" ..
554                        "The file '" .. path .. "' contains no index() function.\n" ..
555                        "Please make sure that the controller contains a valid " ..
556                        "index function and verify the spelling!")
557
558                 index[modname] = idx
559         end
560
561         if indexcache then
562                 local f = nixio.open(indexcache, "w", 600)
563                 f:writeall(util.get_bytecode(index))
564                 f:close()
565         end
566 end
567
568 -- Build the index before if it does not exist yet.
569 function createtree()
570         if not index then
571                 createindex()
572         end
573
574         local ctx  = context
575         local tree = {nodes={}, inreq=true}
576         local modi = {}
577
578         ctx.treecache = setmetatable({}, {__mode="v"})
579         ctx.tree = tree
580         ctx.modifiers = modi
581
582         -- Load default translation
583         require "luci.i18n".loadc("base")
584
585         local scope = setmetatable({}, {__index = luci.dispatcher})
586
587         for k, v in pairs(index) do
588                 scope._NAME = k
589                 setfenv(v, scope)
590                 v()
591         end
592
593         local function modisort(a,b)
594                 return modi[a].order < modi[b].order
595         end
596
597         for _, v in util.spairs(modi, modisort) do
598                 scope._NAME = v.module
599                 setfenv(v.func, scope)
600                 v.func()
601         end
602
603         return tree
604 end
605
606 function modifier(func, order)
607         context.modifiers[#context.modifiers+1] = {
608                 func = func,
609                 order = order or 0,
610                 module
611                         = getfenv(2)._NAME
612         }
613 end
614
615 function assign(path, clone, title, order)
616         local obj  = node(unpack(path))
617         obj.nodes  = nil
618         obj.module = nil
619
620         obj.title = title
621         obj.order = order
622
623         setmetatable(obj, {__index = _create_node(clone)})
624
625         return obj
626 end
627
628 function entry(path, target, title, order)
629         local c = node(unpack(path))
630
631         c.target = target
632         c.title  = title
633         c.order  = order
634         c.module = getfenv(2)._NAME
635
636         return c
637 end
638
639 -- enabling the node.
640 function get(...)
641         return _create_node({...})
642 end
643
644 function node(...)
645         local c = _create_node({...})
646
647         c.module = getfenv(2)._NAME
648         c.auto = nil
649
650         return c
651 end
652
653 function _create_node(path)
654         if #path == 0 then
655                 return context.tree
656         end
657
658         local name = table.concat(path, ".")
659         local c = context.treecache[name]
660
661         if not c then
662                 local last = table.remove(path)
663                 local parent = _create_node(path)
664
665                 c = {nodes={}, auto=true}
666                 -- the node is "in request" if the request path matches
667                 -- at least up to the length of the node path
668                 if parent.inreq and context.path[#path+1] == last then
669                   c.inreq = true
670                 end
671                 parent.nodes[last] = c
672                 context.treecache[name] = c
673         end
674         return c
675 end
676
677 -- Subdispatchers --
678
679 function _firstchild()
680    local path = { unpack(context.path) }
681    local name = table.concat(path, ".")
682    local node = context.treecache[name]
683
684    local lowest
685    if node and node.nodes and next(node.nodes) then
686           local k, v
687           for k, v in pairs(node.nodes) do
688                  if not lowest or
689                         (v.order or 100) < (node.nodes[lowest].order or 100)
690                  then
691                         lowest = k
692                  end
693           end
694    end
695
696    assert(lowest ~= nil,
697                   "The requested node contains no childs, unable to redispatch")
698
699    path[#path+1] = lowest
700    dispatch(path)
701 end
702
703 function firstchild()
704    return { type = "firstchild", target = _firstchild }
705 end
706
707 function alias(...)
708         local req = {...}
709         return function(...)
710                 for _, r in ipairs({...}) do
711                         req[#req+1] = r
712                 end
713
714                 dispatch(req)
715         end
716 end
717
718 function rewrite(n, ...)
719         local req = {...}
720         return function(...)
721                 local dispatched = util.clone(context.dispatched)
722
723                 for i=1,n do
724                         table.remove(dispatched, 1)
725                 end
726
727                 for i, r in ipairs(req) do
728                         table.insert(dispatched, i, r)
729                 end
730
731                 for _, r in ipairs({...}) do
732                         dispatched[#dispatched+1] = r
733                 end
734
735                 dispatch(dispatched)
736         end
737 end
738
739
740 local function _call(self, ...)
741         local func = getfenv()[self.name]
742         assert(func ~= nil,
743                'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
744
745         assert(type(func) == "function",
746                'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
747                'of type "' .. type(func) .. '".')
748
749         if #self.argv > 0 then
750                 return func(unpack(self.argv), ...)
751         else
752                 return func(...)
753         end
754 end
755
756 function call(name, ...)
757         return {type = "call", argv = {...}, name = name, target = _call}
758 end
759
760 function post_on(params, name, ...)
761         return {
762                 type = "call",
763                 post = params,
764                 argv = { ... },
765                 name = name,
766                 target = _call
767         }
768 end
769
770 function post(...)
771         return post_on(true, ...)
772 end
773
774
775 local _template = function(self, ...)
776         require "luci.template".render(self.view)
777 end
778
779 function template(name)
780         return {type = "template", view = name, target = _template}
781 end
782
783
784 local function _cbi(self, ...)
785         local cbi = require "luci.cbi"
786         local tpl = require "luci.template"
787         local http = require "luci.http"
788
789         local config = self.config or {}
790         local maps = cbi.load(self.model, ...)
791
792         local state = nil
793
794         for i, res in ipairs(maps) do
795                 res.flow = config
796                 local cstate = res:parse()
797                 if cstate and (not state or cstate < state) then
798                         state = cstate
799                 end
800         end
801
802         local function _resolve_path(path)
803                 return type(path) == "table" and build_url(unpack(path)) or path
804         end
805
806         if config.on_valid_to and state and state > 0 and state < 2 then
807                 http.redirect(_resolve_path(config.on_valid_to))
808                 return
809         end
810
811         if config.on_changed_to and state and state > 1 then
812                 http.redirect(_resolve_path(config.on_changed_to))
813                 return
814         end
815
816         if config.on_success_to and state and state > 0 then
817                 http.redirect(_resolve_path(config.on_success_to))
818                 return
819         end
820
821         if config.state_handler then
822                 if not config.state_handler(state, maps) then
823                         return
824                 end
825         end
826
827         http.header("X-CBI-State", state or 0)
828
829         if not config.noheader then
830                 tpl.render("cbi/header", {state = state})
831         end
832
833         local redirect
834         local messages
835         local applymap   = false
836         local pageaction = true
837         local parsechain = { }
838
839         for i, res in ipairs(maps) do
840                 if res.apply_needed and res.parsechain then
841                         local c
842                         for _, c in ipairs(res.parsechain) do
843                                 parsechain[#parsechain+1] = c
844                         end
845                         applymap = true
846                 end
847
848                 if res.redirect then
849                         redirect = redirect or res.redirect
850                 end
851
852                 if res.pageaction == false then
853                         pageaction = false
854                 end
855
856                 if res.message then
857                         messages = messages or { }
858                         messages[#messages+1] = res.message
859                 end
860         end
861
862         for i, res in ipairs(maps) do
863                 res:render({
864                         firstmap   = (i == 1),
865                         applymap   = applymap,
866                         redirect   = redirect,
867                         messages   = messages,
868                         pageaction = pageaction,
869                         parsechain = parsechain
870                 })
871         end
872
873         if not config.nofooter then
874                 tpl.render("cbi/footer", {
875                         flow       = config,
876                         pageaction = pageaction,
877                         redirect   = redirect,
878                         state      = state,
879                         autoapply  = config.autoapply
880                 })
881         end
882 end
883
884 function cbi(model, config)
885         return {
886                 type = "cbi",
887                 post = { ["cbi.submit"] = "1" },
888                 config = config,
889                 model = model,
890                 target = _cbi
891         }
892 end
893
894
895 local function _arcombine(self, ...)
896         local argv = {...}
897         local target = #argv > 0 and self.targets[2] or self.targets[1]
898         setfenv(target.target, self.env)
899         target:target(unpack(argv))
900 end
901
902 function arcombine(trg1, trg2)
903         return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
904 end
905
906
907 local function _form(self, ...)
908         local cbi = require "luci.cbi"
909         local tpl = require "luci.template"
910         local http = require "luci.http"
911
912         local maps = luci.cbi.load(self.model, ...)
913         local state = nil
914
915         for i, res in ipairs(maps) do
916                 local cstate = res:parse()
917                 if cstate and (not state or cstate < state) then
918                         state = cstate
919                 end
920         end
921
922         http.header("X-CBI-State", state or 0)
923         tpl.render("header")
924         for i, res in ipairs(maps) do
925                 res:render()
926         end
927         tpl.render("footer")
928 end
929
930 function form(model)
931         return {
932                 type = "cbi",
933                 post = { ["cbi.submit"] = "1" },
934                 model = model,
935                 target = _form
936         }
937 end
938
939 translate = i18n.translate
940
941 -- This function does not actually translate the given argument but
942 -- is used by build/i18n-scan.pl to find translatable entries.
943 function _(text)
944         return text
945 end