1
0

dispatcher.lua 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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. module("luci.dispatcher", package.seeall)
  5. local http = _G.L.http
  6. context = setmetatable({}, {
  7. __index = function(t, k)
  8. if k == "request" or k == "requestpath" then
  9. return _G.L.ctx.request_path
  10. elseif k == "requestargs" then
  11. return _G.L.ctx.request_args
  12. else
  13. return _G.L.ctx[k]
  14. end
  15. end
  16. })
  17. uci = require "luci.model.uci"
  18. uci:set_session_id(_G.L.ctx.authsession)
  19. i18n = require "luci.i18n"
  20. i18n.setlanguage(_G.L.dispatcher.lang)
  21. build_url = _G.L.dispatcher.build_url
  22. menu_json = _G.L.dispatcher.menu_json
  23. error404 = _G.L.dispatcher.error404
  24. error500 = _G.L.dispatcher.error500
  25. function is_authenticated(auth)
  26. local session = _G.L.dispatcher.is_authenticated(auth)
  27. if session then
  28. return session.sid, session.data, session.acls
  29. end
  30. end
  31. function assign(path, clone, title, order)
  32. local obj = node(unpack(path))
  33. obj.title = title
  34. obj.order = order
  35. setmetatable(obj, {__index = node(unpack(clone))})
  36. return obj
  37. end
  38. function entry(path, target, title, order)
  39. local c = node(unpack(path))
  40. c.title = title
  41. c.order = order
  42. c.action = target
  43. return c
  44. end
  45. -- enabling the node.
  46. function get(...)
  47. return node(...)
  48. end
  49. function node(...)
  50. local p = table.concat({ ... }, "/")
  51. if not __entries[p] then
  52. __entries[p] = {}
  53. end
  54. return __entries[p]
  55. end
  56. function lookup(...)
  57. local i, path = nil, {}
  58. for i = 1, select('#', ...) do
  59. local name, arg = nil, tostring(select(i, ...))
  60. for name in arg:gmatch("[^/]+") do
  61. path[#path+1] = name
  62. end
  63. end
  64. local node = menu_json()
  65. for i = 1, #path do
  66. node = node.children[path[i]]
  67. if not node then
  68. return nil
  69. elseif node.leaf then
  70. break
  71. end
  72. end
  73. return node, build_url(unpack(path))
  74. end
  75. function process_lua_controller(path)
  76. local base = "/usr/lib/lua/luci/controller/"
  77. local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
  78. local mod = require(modname)
  79. assert(mod ~= true,
  80. "Invalid controller file found\n" ..
  81. "The file '" .. path .. "' contains an invalid module line.\n" ..
  82. "Please verify whether the module name is set to '" .. modname ..
  83. "' - It must correspond to the file path!")
  84. local idx = mod.index
  85. if type(idx) ~= "function" then
  86. return nil
  87. end
  88. local entries = {}
  89. __entries = entries
  90. __controller = modname
  91. setfenv(idx, setmetatable({}, { __index = luci.dispatcher }))()
  92. __entries = nil
  93. __controller = nil
  94. -- fixup gathered node specs
  95. for path, entry in pairs(entries) do
  96. if entry.leaf then
  97. entry.wildcard = true
  98. end
  99. if type(entry.file_depends) == "table" then
  100. for _, v in ipairs(entry.file_depends) do
  101. entry.depends = entry.depends or {}
  102. entry.depends.fs = entry.depends.fs or {}
  103. local ft = fs.stat(v, "type")
  104. if ft == "dir" then
  105. entry.depends.fs[v] = "directory"
  106. elseif v:match("/s?bin/") then
  107. entry.depends.fs[v] = "executable"
  108. else
  109. entry.depends.fs[v] = "file"
  110. end
  111. end
  112. end
  113. if type(entry.uci_depends) == "table" then
  114. for k, v in pairs(entry.uci_depends) do
  115. entry.depends = entry.depends or {}
  116. entry.depends.uci = entry.depends.uci or {}
  117. entry.depends.uci[k] = v
  118. end
  119. end
  120. if type(entry.acl_depends) == "table" then
  121. for _, acl in ipairs(entry.acl_depends) do
  122. entry.depends = entry.depends or {}
  123. entry.depends.acl = entry.depends.acl or {}
  124. entry.depends.acl[#entry.depends.acl + 1] = acl
  125. end
  126. end
  127. if (entry.sysauth_authenticator ~= nil) or
  128. (entry.sysauth ~= nil and entry.sysauth ~= false)
  129. then
  130. if entry.sysauth_authenticator == "htmlauth" then
  131. entry.auth = {
  132. login = true,
  133. methods = { "cookie:sysauth_https", "cookie:sysauth_http" }
  134. }
  135. elseif path == "rpc" and modname == "luci.controller.rpc" then
  136. entry.auth = {
  137. login = false,
  138. methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http", "cookie:sysauth" }
  139. }
  140. elseif modname == "luci.controller.admin.uci" then
  141. entry.auth = {
  142. login = false,
  143. methods = { "param:sid" }
  144. }
  145. end
  146. elseif entry.sysauth == false then
  147. entry.auth = {}
  148. end
  149. if entry.action == nil and type(entry.target) == "table" then
  150. entry.action = entry.target
  151. entry.target = nil
  152. end
  153. entry.leaf = nil
  154. entry.file_depends = nil
  155. entry.uci_depends = nil
  156. entry.acl_depends = nil
  157. entry.sysauth = nil
  158. entry.sysauth_authenticator = nil
  159. end
  160. return entries
  161. end
  162. function invoke_cbi_action(model, config, ...)
  163. local cbi = require "luci.cbi"
  164. local tpl = require "luci.template"
  165. local util = require "luci.util"
  166. if not config then
  167. config = {}
  168. end
  169. local maps = cbi.load(model, ...)
  170. local state = nil
  171. local function has_uci_access(config, level)
  172. local rv = util.ubus("session", "access", {
  173. ubus_rpc_session = context.authsession,
  174. scope = "uci", object = config,
  175. ["function"] = level
  176. })
  177. return (type(rv) == "table" and rv.access == true) or false
  178. end
  179. local i, res
  180. for i, res in ipairs(maps) do
  181. if util.instanceof(res, cbi.SimpleForm) then
  182. io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
  183. % model)
  184. io.stderr:write("please change %s to use the form() action instead.\n"
  185. % table.concat(context.request, "/"))
  186. end
  187. res.flow = config
  188. local cstate = res:parse()
  189. if cstate and (not state or cstate < state) then
  190. state = cstate
  191. end
  192. end
  193. local function _resolve_path(path)
  194. return type(path) == "table" and build_url(unpack(path)) or path
  195. end
  196. if config.on_valid_to and state and state > 0 and state < 2 then
  197. http:redirect(_resolve_path(config.on_valid_to))
  198. return
  199. end
  200. if config.on_changed_to and state and state > 1 then
  201. http:redirect(_resolve_path(config.on_changed_to))
  202. return
  203. end
  204. if config.on_success_to and state and state > 0 then
  205. http:redirect(_resolve_path(config.on_success_to))
  206. return
  207. end
  208. if config.state_handler then
  209. if not config.state_handler(state, maps) then
  210. return
  211. end
  212. end
  213. http:header("X-CBI-State", state or 0)
  214. if not config.noheader then
  215. _G.L.include("cbi/header", {state = state})
  216. end
  217. local redirect
  218. local messages
  219. local applymap = false
  220. local pageaction = true
  221. local parsechain = { }
  222. local writable = false
  223. for i, res in ipairs(maps) do
  224. if res.apply_needed and res.parsechain then
  225. local c
  226. for _, c in ipairs(res.parsechain) do
  227. parsechain[#parsechain+1] = c
  228. end
  229. applymap = true
  230. end
  231. if res.redirect then
  232. redirect = redirect or res.redirect
  233. end
  234. if res.pageaction == false then
  235. pageaction = false
  236. end
  237. if res.message then
  238. messages = messages or { }
  239. messages[#messages+1] = res.message
  240. end
  241. end
  242. for i, res in ipairs(maps) do
  243. local is_readable_map = has_uci_access(res.config, "read")
  244. local is_writable_map = has_uci_access(res.config, "write")
  245. writable = writable or is_writable_map
  246. res:render({
  247. firstmap = (i == 1),
  248. redirect = redirect,
  249. messages = messages,
  250. pageaction = pageaction,
  251. parsechain = parsechain,
  252. readable = is_readable_map,
  253. writable = is_writable_map
  254. })
  255. end
  256. if not config.nofooter then
  257. _G.L.include("cbi/footer", {
  258. flow = config,
  259. pageaction = pageaction,
  260. redirect = redirect,
  261. state = state,
  262. autoapply = config.autoapply,
  263. trigger_apply = applymap,
  264. writable = writable
  265. })
  266. end
  267. end
  268. function invoke_form_action(model, ...)
  269. local cbi = require "luci.cbi"
  270. local tpl = require "luci.template"
  271. local maps = luci.cbi.load(model, ...)
  272. local state = nil
  273. local i, res
  274. for i, res in ipairs(maps) do
  275. local cstate = res:parse()
  276. if cstate and (not state or cstate < state) then
  277. state = cstate
  278. end
  279. end
  280. http:header("X-CBI-State", state or 0)
  281. _G.L.include("header")
  282. for i, res in ipairs(maps) do
  283. res:render()
  284. end
  285. _G.L.include("footer")
  286. end
  287. function render_lua_template(path)
  288. local tpl = require "luci.template"
  289. tpl.render(path, getfenv(1))
  290. end
  291. function test_post_security()
  292. if http:getenv("REQUEST_METHOD") ~= "POST" then
  293. http:status(405, "Method Not Allowed")
  294. http:header("Allow", "POST")
  295. return false
  296. end
  297. if http:formvalue("token") ~= context.authtoken then
  298. http:status(403, "Forbidden")
  299. _G.L.include("csrftoken")
  300. return false
  301. end
  302. return true
  303. end
  304. function call(name, ...)
  305. return {
  306. ["type"] = "call",
  307. ["module"] = __controller,
  308. ["function"] = name,
  309. ["parameters"] = select('#', ...) > 0 and {...} or nil
  310. }
  311. end
  312. function post_on(params, name, ...)
  313. return {
  314. ["type"] = "call",
  315. ["module"] = __controller,
  316. ["function"] = name,
  317. ["parameters"] = select('#', ...) > 0 and {...} or nil,
  318. ["post"] = params
  319. }
  320. end
  321. function post(...)
  322. return post_on(true, ...)
  323. end
  324. function view(name)
  325. return {
  326. ["type"] = "view",
  327. ["path"] = name
  328. }
  329. end
  330. function template(name)
  331. return {
  332. ["type"] = "template",
  333. ["path"] = name
  334. }
  335. end
  336. function cbi(model, config)
  337. return {
  338. ["type"] = "call",
  339. ["module"] = "luci.dispatcher",
  340. ["function"] = "invoke_cbi_action",
  341. ["parameters"] = { model, config or {} },
  342. ["post"] = {
  343. ["cbi.submit"] = true
  344. }
  345. }
  346. end
  347. function form(model)
  348. return {
  349. ["type"] = "call",
  350. ["module"] = "luci.dispatcher",
  351. ["function"] = "invoke_form_action",
  352. ["parameters"] = { model },
  353. ["post"] = {
  354. ["cbi.submit"] = true
  355. }
  356. }
  357. end
  358. function firstchild()
  359. return {
  360. ["type"] = "firstchild"
  361. }
  362. end
  363. function firstnode()
  364. return {
  365. ["type"] = "firstchild",
  366. ["recurse"] = true
  367. }
  368. end
  369. function arcombine(trg1, trg2)
  370. return {
  371. ["type"] = "arcombine",
  372. ["targets"] = { trg1, trg2 } --,
  373. --env = getfenv(),
  374. }
  375. end
  376. function alias(...)
  377. return {
  378. ["type"] = "alias",
  379. ["path"] = table.concat({ ... }, "/")
  380. }
  381. end
  382. function rewrite(n, ...)
  383. return {
  384. ["type"] = "rewrite",
  385. ["path"] = table.concat({ ... }, "/"),
  386. ["remove"] = n
  387. }
  388. end
  389. translate = i18n.translate
  390. -- This function does not actually translate the given argument but
  391. -- is used by build/i18n-scan.pl to find translatable entries.
  392. function _(text)
  393. return text
  394. end