dispatcher.lua 21 KB

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