dispatcher.lua 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530
  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 xml = require "luci.xml"
  8. local http = require "luci.http"
  9. local nixio = require "nixio", require "nixio.util"
  10. module("luci.dispatcher", package.seeall)
  11. context = util.threadlocal()
  12. uci = require "luci.model.uci"
  13. i18n = require "luci.i18n"
  14. _M.fs = fs
  15. -- Index table
  16. local index = nil
  17. local function check_fs_depends(spec)
  18. local fs = require "nixio.fs"
  19. for path, kind in pairs(spec) do
  20. if kind == "directory" then
  21. local empty = true
  22. for entry in (fs.dir(path) or function() end) do
  23. empty = false
  24. break
  25. end
  26. if empty then
  27. return false
  28. end
  29. elseif kind == "executable" then
  30. if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
  31. return false
  32. end
  33. elseif kind == "file" then
  34. if fs.stat(path, "type") ~= "reg" then
  35. return false
  36. end
  37. end
  38. end
  39. return true
  40. end
  41. local function check_uci_depends_options(conf, s, opts)
  42. local uci = require "luci.model.uci"
  43. if type(opts) == "string" then
  44. return (s[".type"] == opts)
  45. elseif opts == true then
  46. for option, value in pairs(s) do
  47. if option:byte(1) ~= 46 then
  48. return true
  49. end
  50. end
  51. elseif type(opts) == "table" then
  52. for option, value in pairs(opts) do
  53. local sval = s[option]
  54. if type(sval) == "table" then
  55. local found = false
  56. for _, v in ipairs(sval) do
  57. if v == value then
  58. found = true
  59. break
  60. end
  61. end
  62. if not found then
  63. return false
  64. end
  65. elseif value == true then
  66. if sval == nil then
  67. return false
  68. end
  69. else
  70. if sval ~= value then
  71. return false
  72. end
  73. end
  74. end
  75. end
  76. return true
  77. end
  78. local function check_uci_depends_section(conf, sect)
  79. local uci = require "luci.model.uci"
  80. for section, options in pairs(sect) do
  81. local stype = section:match("^@([A-Za-z0-9_%-]+)$")
  82. if stype then
  83. local found = false
  84. uci:foreach(conf, stype, function(s)
  85. if check_uci_depends_options(conf, s, options) then
  86. found = true
  87. return false
  88. end
  89. end)
  90. if not found then
  91. return false
  92. end
  93. else
  94. local s = uci:get_all(conf, section)
  95. if not s or not check_uci_depends_options(conf, s, options) then
  96. return false
  97. end
  98. end
  99. end
  100. return true
  101. end
  102. local function check_uci_depends(conf)
  103. local uci = require "luci.model.uci"
  104. for config, values in pairs(conf) do
  105. if values == true then
  106. local found = false
  107. uci:foreach(config, nil, function(s)
  108. found = true
  109. return false
  110. end)
  111. if not found then
  112. return false
  113. end
  114. elseif type(values) == "table" then
  115. if not check_uci_depends_section(config, values) then
  116. return false
  117. end
  118. end
  119. end
  120. return true
  121. end
  122. local function check_acl_depends(require_groups, groups)
  123. if type(require_groups) == "table" and #require_groups > 0 then
  124. local writable = false
  125. for _, group in ipairs(require_groups) do
  126. local read = false
  127. local write = false
  128. if type(groups) == "table" and type(groups[group]) == "table" then
  129. for _, perm in ipairs(groups[group]) do
  130. if perm == "read" then
  131. read = true
  132. elseif perm == "write" then
  133. write = true
  134. end
  135. end
  136. end
  137. if not read and not write then
  138. return nil
  139. elseif write then
  140. writable = true
  141. end
  142. end
  143. return writable
  144. end
  145. return true
  146. end
  147. local function check_depends(spec)
  148. if type(spec.depends) ~= "table" then
  149. return true
  150. end
  151. if type(spec.depends.fs) == "table" then
  152. local satisfied = false
  153. local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
  154. for _, alternative in ipairs(alternatives) do
  155. if check_fs_depends(alternative) then
  156. satisfied = true
  157. break
  158. end
  159. end
  160. if not satisfied then
  161. return false
  162. end
  163. end
  164. if type(spec.depends.uci) == "table" then
  165. local satisfied = false
  166. local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
  167. for _, alternative in ipairs(alternatives) do
  168. if check_uci_depends(alternative) then
  169. satisfied = true
  170. break
  171. end
  172. end
  173. if not satisfied then
  174. return false
  175. end
  176. end
  177. return true
  178. end
  179. local function target_to_json(target, module)
  180. local action
  181. if target.type == "call" then
  182. action = {
  183. ["type"] = "call",
  184. ["module"] = module,
  185. ["function"] = target.name,
  186. ["parameters"] = target.argv
  187. }
  188. elseif target.type == "view" then
  189. action = {
  190. ["type"] = "view",
  191. ["path"] = target.view
  192. }
  193. elseif target.type == "template" then
  194. action = {
  195. ["type"] = "template",
  196. ["path"] = target.view
  197. }
  198. elseif target.type == "cbi" then
  199. action = {
  200. ["type"] = "cbi",
  201. ["path"] = target.model,
  202. ["config"] = target.config
  203. }
  204. elseif target.type == "form" then
  205. action = {
  206. ["type"] = "form",
  207. ["path"] = target.model
  208. }
  209. elseif target.type == "firstchild" then
  210. action = {
  211. ["type"] = "firstchild"
  212. }
  213. elseif target.type == "firstnode" then
  214. action = {
  215. ["type"] = "firstchild",
  216. ["recurse"] = true
  217. }
  218. elseif target.type == "arcombine" then
  219. if type(target.targets) == "table" then
  220. action = {
  221. ["type"] = "arcombine",
  222. ["targets"] = {
  223. target_to_json(target.targets[1], module),
  224. target_to_json(target.targets[2], module)
  225. }
  226. }
  227. end
  228. elseif target.type == "alias" then
  229. action = {
  230. ["type"] = "alias",
  231. ["path"] = table.concat(target.req, "/")
  232. }
  233. elseif target.type == "rewrite" then
  234. action = {
  235. ["type"] = "rewrite",
  236. ["path"] = table.concat(target.req, "/"),
  237. ["remove"] = target.n
  238. }
  239. end
  240. if target.post and action then
  241. action.post = target.post
  242. end
  243. return action
  244. end
  245. local function tree_to_json(node, json)
  246. local fs = require "nixio.fs"
  247. local util = require "luci.util"
  248. if type(node.nodes) == "table" then
  249. for subname, subnode in pairs(node.nodes) do
  250. local spec = {
  251. title = xml.striptags(subnode.title),
  252. order = subnode.order
  253. }
  254. if subnode.leaf then
  255. spec.wildcard = true
  256. end
  257. if subnode.cors then
  258. spec.cors = true
  259. end
  260. if subnode.setuser then
  261. spec.setuser = subnode.setuser
  262. end
  263. if subnode.setgroup then
  264. spec.setgroup = subnode.setgroup
  265. end
  266. if type(subnode.target) == "table" then
  267. spec.action = target_to_json(subnode.target, subnode.module)
  268. end
  269. if type(subnode.file_depends) == "table" then
  270. for _, v in ipairs(subnode.file_depends) do
  271. spec.depends = spec.depends or {}
  272. spec.depends.fs = spec.depends.fs or {}
  273. local ft = fs.stat(v, "type")
  274. if ft == "dir" then
  275. spec.depends.fs[v] = "directory"
  276. elseif v:match("/s?bin/") then
  277. spec.depends.fs[v] = "executable"
  278. else
  279. spec.depends.fs[v] = "file"
  280. end
  281. end
  282. end
  283. if type(subnode.uci_depends) == "table" then
  284. for k, v in pairs(subnode.uci_depends) do
  285. spec.depends = spec.depends or {}
  286. spec.depends.uci = spec.depends.uci or {}
  287. spec.depends.uci[k] = v
  288. end
  289. end
  290. if type(subnode.acl_depends) == "table" then
  291. for _, acl in ipairs(subnode.acl_depends) do
  292. spec.depends = spec.depends or {}
  293. spec.depends.acl = spec.depends.acl or {}
  294. spec.depends.acl[#spec.depends.acl + 1] = acl
  295. end
  296. end
  297. if (subnode.sysauth_authenticator ~= nil) or
  298. (subnode.sysauth ~= nil and subnode.sysauth ~= false)
  299. then
  300. if subnode.sysauth_authenticator == "htmlauth" then
  301. spec.auth = {
  302. login = true,
  303. methods = { "cookie:sysauth" }
  304. }
  305. elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
  306. spec.auth = {
  307. login = false,
  308. methods = { "query:auth", "cookie:sysauth" }
  309. }
  310. elseif subnode.module == "luci.controller.admin.uci" then
  311. spec.auth = {
  312. login = false,
  313. methods = { "param:sid" }
  314. }
  315. end
  316. elseif subnode.sysauth == false then
  317. spec.auth = {}
  318. end
  319. if not spec.action then
  320. spec.title = nil
  321. end
  322. spec.satisfied = check_depends(spec)
  323. json.children = json.children or {}
  324. json.children[subname] = tree_to_json(subnode, spec)
  325. end
  326. end
  327. return json
  328. end
  329. function build_url(...)
  330. local path = {...}
  331. local url = { http.getenv("SCRIPT_NAME") or "" }
  332. local p
  333. for _, p in ipairs(path) do
  334. if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
  335. url[#url+1] = "/"
  336. url[#url+1] = p
  337. end
  338. end
  339. if #path == 0 then
  340. url[#url+1] = "/"
  341. end
  342. return table.concat(url, "")
  343. end
  344. function error404(message)
  345. http.status(404, "Not Found")
  346. message = message or "Not Found"
  347. local function render()
  348. local template = require "luci.template"
  349. template.render("error404", {message=message})
  350. end
  351. if not util.copcall(render) then
  352. http.prepare_content("text/plain")
  353. http.write(message)
  354. end
  355. return false
  356. end
  357. function error500(message)
  358. util.perror(message)
  359. if not context.template_header_sent then
  360. http.status(500, "Internal Server Error")
  361. http.prepare_content("text/plain")
  362. http.write(message)
  363. else
  364. require("luci.template")
  365. if not util.copcall(luci.template.render, "error500", {message=message}) then
  366. http.prepare_content("text/plain")
  367. http.write(message)
  368. end
  369. end
  370. return false
  371. end
  372. local function determine_request_language()
  373. local conf = require "luci.config"
  374. assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'")
  375. local lang = conf.main.lang or "auto"
  376. if lang == "auto" then
  377. local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
  378. for aclang in aclang:gmatch("[%w_-]+") do
  379. local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
  380. if country and culture then
  381. local cc = "%s_%s" %{ country, culture:lower() }
  382. if conf.languages[cc] then
  383. lang = cc
  384. break
  385. elseif conf.languages[country] then
  386. lang = country
  387. break
  388. end
  389. elseif conf.languages[aclang] then
  390. lang = aclang
  391. break
  392. end
  393. end
  394. end
  395. if lang == "auto" then
  396. lang = i18n.default
  397. end
  398. i18n.setlanguage(lang)
  399. end
  400. function httpdispatch(request, prefix)
  401. http.context.request = request
  402. local r = {}
  403. context.request = r
  404. local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
  405. if prefix then
  406. for _, node in ipairs(prefix) do
  407. r[#r+1] = node
  408. end
  409. end
  410. local node
  411. for node in pathinfo:gmatch("[^/%z]+") do
  412. r[#r+1] = node
  413. end
  414. determine_request_language()
  415. local stat, err = util.coxpcall(function()
  416. dispatch(context.request)
  417. end, error500)
  418. http.close()
  419. --context._disable_memtrace()
  420. end
  421. local function require_post_security(target, args)
  422. if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
  423. return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
  424. end
  425. if type(target) == "table" then
  426. if type(target.post) == "table" then
  427. local param_name, required_val, request_val
  428. for param_name, required_val in pairs(target.post) do
  429. request_val = http.formvalue(param_name)
  430. if (type(required_val) == "string" and
  431. request_val ~= required_val) or
  432. (required_val == true and request_val == nil)
  433. then
  434. return false
  435. end
  436. end
  437. return true
  438. end
  439. return (target.post == true)
  440. end
  441. return false
  442. end
  443. function test_post_security()
  444. if http.getenv("REQUEST_METHOD") ~= "POST" then
  445. http.status(405, "Method Not Allowed")
  446. http.header("Allow", "POST")
  447. return false
  448. end
  449. if http.formvalue("token") ~= context.authtoken then
  450. http.status(403, "Forbidden")
  451. luci.template.render("csrftoken")
  452. return false
  453. end
  454. return true
  455. end
  456. local function session_retrieve(sid, allowed_users)
  457. local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
  458. local sacl = util.ubus("session", "access", { ubus_rpc_session = sid })
  459. if type(sdat) == "table" and
  460. type(sdat.values) == "table" and
  461. type(sdat.values.token) == "string" and
  462. (not allowed_users or
  463. util.contains(allowed_users, sdat.values.username))
  464. then
  465. uci:set_session_id(sid)
  466. return sid, sdat.values, type(sacl) == "table" and sacl or {}
  467. end
  468. return nil, nil, nil
  469. end
  470. local function session_setup(user, pass)
  471. local login = util.ubus("session", "login", {
  472. username = user,
  473. password = pass,
  474. timeout = tonumber(luci.config.sauth.sessiontime)
  475. })
  476. local rp = context.requestpath
  477. and table.concat(context.requestpath, "/") or ""
  478. if type(login) == "table" and
  479. type(login.ubus_rpc_session) == "string"
  480. then
  481. util.ubus("session", "set", {
  482. ubus_rpc_session = login.ubus_rpc_session,
  483. values = { token = sys.uniqueid(16) }
  484. })
  485. nixio.syslog("info", tostring("luci: accepted login on /%s for %s from %s\n"
  486. %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" }))
  487. return session_retrieve(login.ubus_rpc_session)
  488. end
  489. nixio.syslog("info", tostring("luci: failed login on /%s for %s from %s\n"
  490. %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" }))
  491. end
  492. local function check_authentication(method)
  493. local auth_type, auth_param = method:match("^(%w+):(.+)$")
  494. local sid, sdat
  495. if auth_type == "cookie" then
  496. sid = http.getcookie(auth_param)
  497. elseif auth_type == "param" then
  498. sid = http.formvalue(auth_param)
  499. elseif auth_type == "query" then
  500. sid = http.formvalue(auth_param, true)
  501. end
  502. return session_retrieve(sid)
  503. end
  504. local function get_children(node)
  505. local children = {}
  506. if not node.wildcard and type(node.children) == "table" then
  507. for name, child in pairs(node.children) do
  508. children[#children+1] = {
  509. name = name,
  510. node = child,
  511. order = child.order or 1000
  512. }
  513. end
  514. table.sort(children, function(a, b)
  515. if a.order == b.order then
  516. return a.name < b.name
  517. else
  518. return a.order < b.order
  519. end
  520. end)
  521. end
  522. return children
  523. end
  524. local function find_subnode(root, prefix, recurse, descended)
  525. local children = get_children(root)
  526. if #children > 0 and (not descended or recurse) then
  527. local sub_path = { unpack(prefix) }
  528. if recurse == false then
  529. recurse = nil
  530. end
  531. for _, child in ipairs(children) do
  532. sub_path[#prefix+1] = child.name
  533. local res_path = find_subnode(child.node, sub_path, recurse, true)
  534. if res_path then
  535. return res_path
  536. end
  537. end
  538. end
  539. if descended then
  540. if not recurse or
  541. root.action.type == "cbi" or
  542. root.action.type == "form" or
  543. root.action.type == "view" or
  544. root.action.type == "template" or
  545. root.action.type == "arcombine"
  546. then
  547. return prefix
  548. end
  549. end
  550. end
  551. local function merge_trees(node_a, node_b)
  552. for k, v in pairs(node_b) do
  553. if k == "children" then
  554. node_a.children = node_a.children or {}
  555. for name, spec in pairs(v) do
  556. node_a.children[name] = merge_trees(node_a.children[name] or {}, spec)
  557. end
  558. else
  559. node_a[k] = v
  560. end
  561. end
  562. if type(node_a.action) == "table" and
  563. node_a.action.type == "firstchild" and
  564. node_a.children == nil
  565. then
  566. node_a.satisfied = false
  567. end
  568. return node_a
  569. end
  570. local function apply_tree_acls(node, acl)
  571. if type(node.children) == "table" then
  572. for _, child in pairs(node.children) do
  573. apply_tree_acls(child, acl)
  574. end
  575. end
  576. local perm
  577. if type(node.depends) == "table" then
  578. perm = check_acl_depends(node.depends.acl, acl["access-group"])
  579. else
  580. perm = true
  581. end
  582. if perm == nil then
  583. node.satisfied = false
  584. elseif perm == false then
  585. node.readonly = true
  586. end
  587. end
  588. function menu_json(acl)
  589. local tree = context.tree or createtree()
  590. local lua_tree = tree_to_json(tree, {
  591. action = {
  592. ["type"] = "firstchild",
  593. ["recurse"] = true
  594. }
  595. })
  596. local json_tree = createtree_json()
  597. local menu_tree = merge_trees(lua_tree, json_tree)
  598. if acl then
  599. apply_tree_acls(menu_tree, acl)
  600. end
  601. return menu_tree
  602. end
  603. local function init_template_engine(ctx)
  604. local tpl = require "luci.template"
  605. local media = luci.config.main.mediaurlbase
  606. if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
  607. media = nil
  608. for name, theme in pairs(luci.config.themes) do
  609. if name:sub(1,1) ~= "." and pcall(tpl.Template,
  610. "themes/%s/header" % fs.basename(theme)) then
  611. media = theme
  612. end
  613. end
  614. assert(media, "No valid theme found")
  615. end
  616. local function _ifattr(cond, key, val, noescape)
  617. if cond then
  618. local env = getfenv(3)
  619. local scope = (type(env.self) == "table") and env.self
  620. if type(val) == "table" then
  621. if not next(val) then
  622. return ''
  623. else
  624. val = util.serialize_json(val)
  625. end
  626. end
  627. val = tostring(val or
  628. (type(env[key]) ~= "function" and env[key]) or
  629. (scope and type(scope[key]) ~= "function" and scope[key]) or "")
  630. if noescape ~= true then
  631. val = xml.pcdata(val)
  632. end
  633. return string.format(' %s="%s"', tostring(key), val)
  634. else
  635. return ''
  636. end
  637. end
  638. tpl.context.viewns = setmetatable({
  639. write = http.write;
  640. include = function(name) tpl.Template(name):render(getfenv(2)) end;
  641. translate = i18n.translate;
  642. translatef = i18n.translatef;
  643. export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
  644. striptags = xml.striptags;
  645. pcdata = xml.pcdata;
  646. media = media;
  647. theme = fs.basename(media);
  648. resource = luci.config.main.resourcebase;
  649. ifattr = function(...) return _ifattr(...) end;
  650. attr = function(...) return _ifattr(true, ...) end;
  651. url = build_url;
  652. }, {__index=function(tbl, key)
  653. if key == "controller" then
  654. return build_url()
  655. elseif key == "REQUEST_URI" then
  656. return build_url(unpack(ctx.requestpath))
  657. elseif key == "FULL_REQUEST_URI" then
  658. local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
  659. local query = http.getenv("QUERY_STRING")
  660. if query and #query > 0 then
  661. url[#url+1] = "?"
  662. url[#url+1] = query
  663. end
  664. return table.concat(url, "")
  665. elseif key == "token" then
  666. return ctx.authtoken
  667. else
  668. return rawget(tbl, key) or _G[key]
  669. end
  670. end})
  671. return tpl
  672. end
  673. function dispatch(request)
  674. --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
  675. local ctx = context
  676. local auth, cors, suid, sgid
  677. local menu = menu_json()
  678. local page = menu
  679. local requested_path_full = {}
  680. local requested_path_node = {}
  681. local requested_path_args = {}
  682. local required_path_acls = {}
  683. for i, s in ipairs(request) do
  684. if type(page.children) ~= "table" or not page.children[s] then
  685. page = nil
  686. break
  687. end
  688. if not page.children[s].satisfied then
  689. page = nil
  690. break
  691. end
  692. page = page.children[s]
  693. auth = page.auth or auth
  694. cors = page.cors or cors
  695. suid = page.setuser or suid
  696. sgid = page.setgroup or sgid
  697. if type(page.depends) == "table" and type(page.depends.acl) == "table" then
  698. for _, group in ipairs(page.depends.acl) do
  699. local found = false
  700. for _, item in ipairs(required_path_acls) do
  701. if item == group then
  702. found = true
  703. break
  704. end
  705. end
  706. if not found then
  707. required_path_acls[#required_path_acls + 1] = group
  708. end
  709. end
  710. end
  711. requested_path_full[i] = s
  712. requested_path_node[i] = s
  713. if page.wildcard then
  714. for j = i + 1, #request do
  715. requested_path_args[j - i] = request[j]
  716. requested_path_full[j] = request[j]
  717. end
  718. break
  719. end
  720. end
  721. local tpl = init_template_engine(ctx)
  722. ctx.args = requested_path_args
  723. ctx.path = requested_path_node
  724. ctx.dispatched = page
  725. ctx.requestpath = ctx.requestpath or requested_path_full
  726. ctx.requestargs = ctx.requestargs or requested_path_args
  727. ctx.requested = ctx.requested or page
  728. if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
  729. local sid, sdat, sacl
  730. for _, method in ipairs(auth.methods) do
  731. sid, sdat, sacl = check_authentication(method)
  732. if sid and sdat and sacl then
  733. break
  734. end
  735. end
  736. if not (sid and sdat and sacl) and auth.login then
  737. local user = http.getenv("HTTP_AUTH_USER")
  738. local pass = http.getenv("HTTP_AUTH_PASS")
  739. if user == nil and pass == nil then
  740. user = http.formvalue("luci_username")
  741. pass = http.formvalue("luci_password")
  742. end
  743. if user and pass then
  744. sid, sdat, sacl = session_setup(user, pass)
  745. end
  746. if not sid then
  747. context.path = {}
  748. http.status(403, "Forbidden")
  749. http.header("X-LuCI-Login-Required", "yes")
  750. local scope = { duser = "root", fuser = user }
  751. local ok, res = util.copcall(tpl.render_string, [[<% include("themes/" .. theme .. "/sysauth") %>]], scope)
  752. if ok then
  753. return res
  754. end
  755. return tpl.render("sysauth", scope)
  756. end
  757. http.header("Set-Cookie", 'sysauth=%s; path=%s; SameSite=Strict; HttpOnly%s' %{
  758. sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
  759. })
  760. http.redirect(build_url(unpack(ctx.requestpath)))
  761. return
  762. end
  763. if not sid or not sdat or not sacl then
  764. http.status(403, "Forbidden")
  765. http.header("X-LuCI-Login-Required", "yes")
  766. return
  767. end
  768. ctx.authsession = sid
  769. ctx.authtoken = sdat.token
  770. ctx.authuser = sdat.username
  771. ctx.authacl = sacl
  772. end
  773. if #required_path_acls > 0 then
  774. local perm = check_acl_depends(required_path_acls, ctx.authacl and ctx.authacl["access-group"])
  775. if perm == nil then
  776. http.status(403, "Forbidden")
  777. return
  778. end
  779. page.readonly = not perm
  780. end
  781. local action = (page and type(page.action) == "table") and page.action or {}
  782. if action.type == "arcombine" then
  783. action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
  784. end
  785. if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
  786. luci.http.status(200, "OK")
  787. luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
  788. luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
  789. return
  790. end
  791. if require_post_security(action) then
  792. if not test_post_security() then
  793. return
  794. end
  795. end
  796. if sgid then
  797. sys.process.setgroup(sgid)
  798. end
  799. if suid then
  800. sys.process.setuser(suid)
  801. end
  802. if action.type == "view" then
  803. tpl.render("view", { view = action.path })
  804. elseif action.type == "call" then
  805. local ok, mod = util.copcall(require, action.module)
  806. if not ok then
  807. error500(mod)
  808. return
  809. end
  810. local func = mod[action["function"]]
  811. assert(func ~= nil,
  812. 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
  813. assert(type(func) == "function",
  814. 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
  815. 'of type "' .. type(func) .. '".')
  816. local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
  817. for _, s in ipairs(requested_path_args) do
  818. argv[#argv + 1] = s
  819. end
  820. local ok, err = util.copcall(func, unpack(argv))
  821. if not ok then
  822. error500(err)
  823. end
  824. elseif action.type == "firstchild" then
  825. local sub_request = find_subnode(page, requested_path_full, action.recurse)
  826. if sub_request then
  827. dispatch(sub_request)
  828. else
  829. tpl.render("empty_node_placeholder", getfenv(1))
  830. end
  831. elseif action.type == "alias" then
  832. local sub_request = {}
  833. for name in action.path:gmatch("[^/]+") do
  834. sub_request[#sub_request + 1] = name
  835. end
  836. for _, s in ipairs(requested_path_args) do
  837. sub_request[#sub_request + 1] = s
  838. end
  839. dispatch(sub_request)
  840. elseif action.type == "rewrite" then
  841. local sub_request = { unpack(request) }
  842. for i = 1, action.remove do
  843. table.remove(sub_request, 1)
  844. end
  845. local n = 1
  846. for s in action.path:gmatch("[^/]+") do
  847. table.insert(sub_request, n, s)
  848. n = n + 1
  849. end
  850. for _, s in ipairs(requested_path_args) do
  851. sub_request[#sub_request + 1] = s
  852. end
  853. dispatch(sub_request)
  854. elseif action.type == "template" then
  855. tpl.render(action.path, getfenv(1))
  856. elseif action.type == "cbi" then
  857. _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
  858. elseif action.type == "form" then
  859. _form({ model = action.path }, unpack(requested_path_args))
  860. else
  861. local root = find_subnode(menu, {}, true)
  862. if not root then
  863. error404("No root node was registered, this usually happens if no module was installed.\n" ..
  864. "Install luci-mod-admin-full and retry. " ..
  865. "If the module is already installed, try removing the /tmp/luci-indexcache file.")
  866. else
  867. error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
  868. "If this url belongs to an extension, make sure it is properly installed.\n" ..
  869. "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
  870. end
  871. end
  872. end
  873. local function hash_filelist(files)
  874. local fprint = {}
  875. local n = 0
  876. for i, file in ipairs(files) do
  877. local st = fs.stat(file)
  878. if st then
  879. fprint[n + 1] = '%x' % st.ino
  880. fprint[n + 2] = '%x' % st.mtime
  881. fprint[n + 3] = '%x' % st.size
  882. n = n + 3
  883. end
  884. end
  885. return nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
  886. end
  887. local function read_cachefile(file, reader)
  888. local euid = sys.process.info("uid")
  889. local fuid = fs.stat(file, "uid")
  890. local mode = fs.stat(file, "modestr")
  891. if euid ~= fuid or mode ~= "rw-------" then
  892. return nil
  893. end
  894. return reader(file)
  895. end
  896. function createindex()
  897. local controllers = { }
  898. local base = "%s/controller/" % util.libpath()
  899. local _, path
  900. for path in (fs.glob("%s*.lua" % base) or function() end) do
  901. controllers[#controllers+1] = path
  902. end
  903. for path in (fs.glob("%s*/*.lua" % base) or function() end) do
  904. controllers[#controllers+1] = path
  905. end
  906. local cachefile
  907. if indexcache then
  908. cachefile = "%s.%s.lua" %{ indexcache, hash_filelist(controllers) }
  909. local res = read_cachefile(cachefile, function(path) return loadfile(path)() end)
  910. if res then
  911. index = res
  912. return res
  913. end
  914. for file in (fs.glob("%s.*.lua" % indexcache) or function() end) do
  915. fs.unlink(file)
  916. end
  917. end
  918. index = {}
  919. for _, path in ipairs(controllers) do
  920. local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
  921. local mod = require(modname)
  922. assert(mod ~= true,
  923. "Invalid controller file found\n" ..
  924. "The file '" .. path .. "' contains an invalid module line.\n" ..
  925. "Please verify whether the module name is set to '" .. modname ..
  926. "' - It must correspond to the file path!")
  927. local idx = mod.index
  928. if type(idx) == "function" then
  929. index[modname] = idx
  930. end
  931. end
  932. if cachefile then
  933. local f = nixio.open(cachefile, "w", 600)
  934. f:writeall(util.get_bytecode(index))
  935. f:close()
  936. end
  937. end
  938. function createtree_json()
  939. local json = require "luci.jsonc"
  940. local tree = {}
  941. local schema = {
  942. action = "table",
  943. auth = "table",
  944. cors = "boolean",
  945. depends = "table",
  946. order = "number",
  947. setgroup = "string",
  948. setuser = "string",
  949. title = "string",
  950. wildcard = "boolean"
  951. }
  952. local files = {}
  953. local cachefile
  954. for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
  955. files[#files+1] = file
  956. end
  957. if indexcache then
  958. cachefile = "%s.%s.json" %{ indexcache, hash_filelist(files) }
  959. local res = read_cachefile(cachefile, function(path) return json.parse(fs.readfile(path) or "") end)
  960. if res then
  961. return res
  962. end
  963. for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
  964. fs.unlink(file)
  965. end
  966. end
  967. for _, file in ipairs(files) do
  968. local data = json.parse(fs.readfile(file) or "")
  969. if type(data) == "table" then
  970. for path, spec in pairs(data) do
  971. if type(spec) == "table" then
  972. local node = tree
  973. for s in path:gmatch("[^/]+") do
  974. if s == "*" then
  975. node.wildcard = true
  976. break
  977. end
  978. node.children = node.children or {}
  979. node.children[s] = node.children[s] or {}
  980. node = node.children[s]
  981. end
  982. if node ~= tree then
  983. for k, t in pairs(schema) do
  984. if type(spec[k]) == t then
  985. node[k] = spec[k]
  986. end
  987. end
  988. node.satisfied = check_depends(spec)
  989. end
  990. end
  991. end
  992. end
  993. end
  994. if cachefile then
  995. local f = nixio.open(cachefile, "w", 600)
  996. f:writeall(json.stringify(tree))
  997. f:close()
  998. end
  999. return tree
  1000. end
  1001. -- Build the index before if it does not exist yet.
  1002. function createtree()
  1003. if not index then
  1004. createindex()
  1005. end
  1006. local ctx = context
  1007. local tree = {nodes={}, inreq=true}
  1008. ctx.treecache = setmetatable({}, {__mode="v"})
  1009. ctx.tree = tree
  1010. local scope = setmetatable({}, {__index = luci.dispatcher})
  1011. for k, v in pairs(index) do
  1012. scope._NAME = k
  1013. setfenv(v, scope)
  1014. v()
  1015. end
  1016. return tree
  1017. end
  1018. function assign(path, clone, title, order)
  1019. local obj = node(unpack(path))
  1020. obj.nodes = nil
  1021. obj.module = nil
  1022. obj.title = title
  1023. obj.order = order
  1024. setmetatable(obj, {__index = _create_node(clone)})
  1025. return obj
  1026. end
  1027. function entry(path, target, title, order)
  1028. local c = node(unpack(path))
  1029. c.target = target
  1030. c.title = title
  1031. c.order = order
  1032. c.module = getfenv(2)._NAME
  1033. return c
  1034. end
  1035. -- enabling the node.
  1036. function get(...)
  1037. return _create_node({...})
  1038. end
  1039. function node(...)
  1040. local c = _create_node({...})
  1041. c.module = getfenv(2)._NAME
  1042. c.auto = nil
  1043. return c
  1044. end
  1045. function lookup(...)
  1046. local i, path = nil, {}
  1047. for i = 1, select('#', ...) do
  1048. local name, arg = nil, tostring(select(i, ...))
  1049. for name in arg:gmatch("[^/]+") do
  1050. path[#path+1] = name
  1051. end
  1052. end
  1053. for i = #path, 1, -1 do
  1054. local node = context.treecache[table.concat(path, ".", 1, i)]
  1055. if node and (i == #path or node.leaf) then
  1056. return node, build_url(unpack(path))
  1057. end
  1058. end
  1059. end
  1060. function _create_node(path)
  1061. if #path == 0 then
  1062. return context.tree
  1063. end
  1064. local name = table.concat(path, ".")
  1065. local c = context.treecache[name]
  1066. if not c then
  1067. local last = table.remove(path)
  1068. local parent = _create_node(path)
  1069. c = {nodes={}, auto=true, inreq=true}
  1070. parent.nodes[last] = c
  1071. context.treecache[name] = c
  1072. end
  1073. return c
  1074. end
  1075. -- Subdispatchers --
  1076. function firstchild()
  1077. return { type = "firstchild" }
  1078. end
  1079. function firstnode()
  1080. return { type = "firstnode" }
  1081. end
  1082. function alias(...)
  1083. return { type = "alias", req = { ... } }
  1084. end
  1085. function rewrite(n, ...)
  1086. return { type = "rewrite", n = n, req = { ... } }
  1087. end
  1088. function call(name, ...)
  1089. return { type = "call", argv = {...}, name = name }
  1090. end
  1091. function post_on(params, name, ...)
  1092. return {
  1093. type = "call",
  1094. post = params,
  1095. argv = { ... },
  1096. name = name
  1097. }
  1098. end
  1099. function post(...)
  1100. return post_on(true, ...)
  1101. end
  1102. function template(name)
  1103. return { type = "template", view = name }
  1104. end
  1105. function view(name)
  1106. return { type = "view", view = name }
  1107. end
  1108. function _cbi(self, ...)
  1109. local cbi = require "luci.cbi"
  1110. local tpl = require "luci.template"
  1111. local http = require "luci.http"
  1112. local util = require "luci.util"
  1113. local config = self.config or {}
  1114. local maps = cbi.load(self.model, ...)
  1115. local state = nil
  1116. local function has_uci_access(config, level)
  1117. local rv = util.ubus("session", "access", {
  1118. ubus_rpc_session = context.authsession,
  1119. scope = "uci", object = config,
  1120. ["function"] = level
  1121. })
  1122. return (type(rv) == "table" and rv.access == true) or false
  1123. end
  1124. local i, res
  1125. for i, res in ipairs(maps) do
  1126. if util.instanceof(res, cbi.SimpleForm) then
  1127. io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
  1128. % self.model)
  1129. io.stderr:write("please change %s to use the form() action instead.\n"
  1130. % table.concat(context.request, "/"))
  1131. end
  1132. res.flow = config
  1133. local cstate = res:parse()
  1134. if cstate and (not state or cstate < state) then
  1135. state = cstate
  1136. end
  1137. end
  1138. local function _resolve_path(path)
  1139. return type(path) == "table" and build_url(unpack(path)) or path
  1140. end
  1141. if config.on_valid_to and state and state > 0 and state < 2 then
  1142. http.redirect(_resolve_path(config.on_valid_to))
  1143. return
  1144. end
  1145. if config.on_changed_to and state and state > 1 then
  1146. http.redirect(_resolve_path(config.on_changed_to))
  1147. return
  1148. end
  1149. if config.on_success_to and state and state > 0 then
  1150. http.redirect(_resolve_path(config.on_success_to))
  1151. return
  1152. end
  1153. if config.state_handler then
  1154. if not config.state_handler(state, maps) then
  1155. return
  1156. end
  1157. end
  1158. http.header("X-CBI-State", state or 0)
  1159. if not config.noheader then
  1160. tpl.render("cbi/header", {state = state})
  1161. end
  1162. local redirect
  1163. local messages
  1164. local applymap = false
  1165. local pageaction = true
  1166. local parsechain = { }
  1167. local writable = false
  1168. for i, res in ipairs(maps) do
  1169. if res.apply_needed and res.parsechain then
  1170. local c
  1171. for _, c in ipairs(res.parsechain) do
  1172. parsechain[#parsechain+1] = c
  1173. end
  1174. applymap = true
  1175. end
  1176. if res.redirect then
  1177. redirect = redirect or res.redirect
  1178. end
  1179. if res.pageaction == false then
  1180. pageaction = false
  1181. end
  1182. if res.message then
  1183. messages = messages or { }
  1184. messages[#messages+1] = res.message
  1185. end
  1186. end
  1187. for i, res in ipairs(maps) do
  1188. local is_readable_map = has_uci_access(res.config, "read")
  1189. local is_writable_map = has_uci_access(res.config, "write")
  1190. writable = writable or is_writable_map
  1191. res:render({
  1192. firstmap = (i == 1),
  1193. redirect = redirect,
  1194. messages = messages,
  1195. pageaction = pageaction,
  1196. parsechain = parsechain,
  1197. readable = is_readable_map,
  1198. writable = is_writable_map
  1199. })
  1200. end
  1201. if not config.nofooter then
  1202. tpl.render("cbi/footer", {
  1203. flow = config,
  1204. pageaction = pageaction,
  1205. redirect = redirect,
  1206. state = state,
  1207. autoapply = config.autoapply,
  1208. trigger_apply = applymap,
  1209. writable = writable
  1210. })
  1211. end
  1212. end
  1213. function cbi(model, config)
  1214. return {
  1215. type = "cbi",
  1216. post = { ["cbi.submit"] = true },
  1217. config = config,
  1218. model = model
  1219. }
  1220. end
  1221. function arcombine(trg1, trg2)
  1222. return {
  1223. type = "arcombine",
  1224. env = getfenv(),
  1225. targets = {trg1, trg2}
  1226. }
  1227. end
  1228. function _form(self, ...)
  1229. local cbi = require "luci.cbi"
  1230. local tpl = require "luci.template"
  1231. local http = require "luci.http"
  1232. local maps = luci.cbi.load(self.model, ...)
  1233. local state = nil
  1234. local i, res
  1235. for i, res in ipairs(maps) do
  1236. local cstate = res:parse()
  1237. if cstate and (not state or cstate < state) then
  1238. state = cstate
  1239. end
  1240. end
  1241. http.header("X-CBI-State", state or 0)
  1242. tpl.render("header")
  1243. for i, res in ipairs(maps) do
  1244. res:render()
  1245. end
  1246. tpl.render("footer")
  1247. end
  1248. function form(model)
  1249. return {
  1250. type = "form",
  1251. post = { ["cbi.submit"] = true },
  1252. model = model
  1253. }
  1254. end
  1255. translate = i18n.translate
  1256. -- This function does not actually translate the given argument but
  1257. -- is used by build/i18n-scan.pl to find translatable entries.
  1258. function _(text)
  1259. return text
  1260. end