1
0

dispatcher.lua 20 KB

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