dispatcher.lua 23 KB

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