You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
12 KiB

  1. --[[
  2. This file is part of YunWebUI.
  3. YunWebUI is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  14. As a special exception, you may use this file as part of a free software
  15. library without restriction. Specifically, if other files instantiate
  16. templates or use macros or inline functions from this file, or you compile
  17. this file and link it with other files to produce an executable, this
  18. file does not by itself cause the resulting executable to be covered by
  19. the GNU General Public License. This exception does not however
  20. invalidate any other reasons why the executable file might be covered by
  21. the GNU General Public License.
  22. Copyright 2013 Arduino LLC (http://www.arduino.cc/)
  23. ]]
  24. module("luci.controller.arduino.index", package.seeall)
  25. local function not_nil_or_empty(value)
  26. return value and value ~= ""
  27. end
  28. local function get_first(cursor, config, type, option)
  29. return cursor:get_first(config, type, option)
  30. end
  31. local function set_first(cursor, config, type, option, value)
  32. cursor:foreach(config, type, function(s)
  33. if s[".type"] == type then
  34. cursor:set(config, s[".name"], option, value)
  35. end
  36. end)
  37. end
  38. local function to_key_value(s)
  39. local parts = luci.util.split(s, ":")
  40. parts[1] = luci.util.trim(parts[1])
  41. parts[2] = luci.util.trim(parts[2])
  42. return parts[1], parts[2]
  43. end
  44. function http_error(code, text)
  45. luci.http.prepare_content("text/plain")
  46. luci.http.status(code)
  47. if text then
  48. luci.http.write(text)
  49. end
  50. end
  51. function index()
  52. function luci.dispatcher.authenticator.arduinoauth(validator, accs, default)
  53. require("luci.controller.arduino.index")
  54. local user = luci.http.formvalue("username")
  55. local pass = luci.http.formvalue("password")
  56. local basic_auth = luci.http.getenv("HTTP_AUTHORIZATION")
  57. if user and validator(user, pass) then
  58. return user
  59. end
  60. if basic_auth and basic_auth ~= "" then
  61. local decoded_basic_auth = nixio.bin.b64decode(string.sub(basic_auth, 7))
  62. user = string.sub(decoded_basic_auth, 0, string.find(decoded_basic_auth, ":") - 1)
  63. pass = string.sub(decoded_basic_auth, string.find(decoded_basic_auth, ":") + 1)
  64. end
  65. if user then
  66. if #pass ~= 64 and validator(user, pass) then
  67. return user
  68. elseif #pass == 64 then
  69. local uci = luci.model.uci.cursor()
  70. uci:load("yunbridge")
  71. local stored_encrypted_pass = uci:get_first("yunbridge", "bridge", "password")
  72. if pass == stored_encrypted_pass then
  73. return user
  74. end
  75. end
  76. end
  77. luci.http.header("WWW-Authenticate", "Basic realm=\"yunbridge\"")
  78. luci.http.status(401)
  79. return false
  80. end
  81. local function make_entry(path, target, title, order)
  82. local page = entry(path, target, title, order)
  83. page.leaf = true
  84. return page
  85. end
  86. -- web panel
  87. local webpanel = entry({ "webpanel" }, alias("webpanel", "go_to_homepage"), _("%s Web Panel") % luci.sys.hostname(), 10)
  88. webpanel.sysauth = "root"
  89. webpanel.sysauth_authenticator = "arduinoauth"
  90. make_entry({ "webpanel", "go_to_homepage" }, call("go_to_homepage"), nil)
  91. --api security level
  92. local uci = luci.model.uci.cursor()
  93. uci:load("yunbridge")
  94. local secure_rest_api = uci:get_first("yunbridge", "bridge", "secure_rest_api")
  95. local rest_api_sysauth = false
  96. if secure_rest_api == "true" then
  97. rest_api_sysauth = webpanel.sysauth
  98. end
  99. --storage api
  100. local data_api = node("data")
  101. data_api.sysauth = rest_api_sysauth
  102. data_api.sysauth_authenticator = webpanel.sysauth_authenticator
  103. make_entry({ "data", "get" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
  104. make_entry({ "data", "put" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
  105. make_entry({ "data", "delete" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
  106. local mailbox_api = node("mailbox")
  107. mailbox_api.sysauth = rest_api_sysauth
  108. mailbox_api.sysauth_authenticator = webpanel.sysauth_authenticator
  109. make_entry({ "mailbox" }, call("build_bridge_mailbox_request"), nil).sysauth = rest_api_sysauth
  110. --plain socket endpoint
  111. local plain_socket_endpoint = make_entry({ "arduino" }, call("board_plain_socket"), nil)
  112. plain_socket_endpoint.sysauth = rest_api_sysauth
  113. plain_socket_endpoint.sysauth_authenticator = webpanel.sysauth_authenticator
  114. end
  115. function go_to_homepage()
  116. luci.http.redirect("/index.html")
  117. end
  118. local function build_bridge_request(command, params)
  119. local bridge_request = {
  120. command = command
  121. }
  122. if command == "raw" then
  123. params = table.concat(params, "/")
  124. if not_nil_or_empty(params) then
  125. bridge_request["data"] = params
  126. end
  127. return bridge_request
  128. end
  129. if command == "get" then
  130. if not_nil_or_empty(params[1]) then
  131. bridge_request["key"] = params[1]
  132. end
  133. return bridge_request
  134. end
  135. if command == "put" and not_nil_or_empty(params[1]) and params[2] then
  136. bridge_request["key"] = params[1]
  137. bridge_request["value"] = params[2]
  138. return bridge_request
  139. end
  140. if command == "delete" and not_nil_or_empty(params[1]) then
  141. bridge_request["key"] = params[1]
  142. return bridge_request
  143. end
  144. return nil
  145. end
  146. local function extract_jsonp_param(query_string)
  147. if not not_nil_or_empty(query_string) then
  148. return nil
  149. end
  150. local qs_parts = string.split(query_string, "&")
  151. for idx, value in ipairs(qs_parts) do
  152. if string.find(value, "jsonp") == 1 or string.find(value, "callback") == 1 then
  153. return string.sub(value, string.find(value, "=") + 1)
  154. end
  155. end
  156. end
  157. local function parts_after(url_part)
  158. local url = luci.http.getenv("PATH_INFO")
  159. local url_after_part = string.find(url, "/", string.find(url, url_part) + 1)
  160. if not url_after_part then
  161. return {}
  162. end
  163. return luci.util.split(string.sub(url, url_after_part + 1), "/")
  164. end
  165. function storage_send_request()
  166. local method = luci.http.getenv("REQUEST_METHOD")
  167. local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
  168. local parts = parts_after("data")
  169. local command = parts[1]
  170. if not command or command == "" then
  171. luci.http.status(404)
  172. return
  173. end
  174. local params = {}
  175. for idx, param in ipairs(parts) do
  176. if idx > 1 and not_nil_or_empty(param) then
  177. table.insert(params, param)
  178. end
  179. end
  180. -- TODO check method?
  181. local bridge_request = build_bridge_request(command, params)
  182. if not bridge_request then
  183. luci.http.status(403)
  184. return
  185. end
  186. local uci = luci.model.uci.cursor()
  187. uci:load("yunbridge")
  188. local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
  189. local sock, code, msg = nixio.connect("127.0.0.1", 5700)
  190. if not sock then
  191. code = code or ""
  192. msg = msg or ""
  193. http_error(500, "nil socket, " .. code .. " " .. msg)
  194. return
  195. end
  196. sock:setopt("socket", "sndtimeo", socket_timeout)
  197. sock:setopt("socket", "rcvtimeo", socket_timeout)
  198. sock:setopt("tcp", "nodelay", 1)
  199. local json = require("luci.json")
  200. sock:write(json.encode(bridge_request))
  201. sock:writeall("\n")
  202. local response_text = {}
  203. while true do
  204. local bytes = sock:recv(4096)
  205. if bytes and #bytes > 0 then
  206. table.insert(response_text, bytes)
  207. end
  208. local json_response = json.decode(table.concat(response_text))
  209. if json_response then
  210. sock:close()
  211. luci.http.status(200)
  212. if jsonp_callback then
  213. luci.http.prepare_content("application/javascript")
  214. luci.http.write(jsonp_callback)
  215. luci.http.write("(")
  216. luci.http.write_json(json_response)
  217. luci.http.write(");")
  218. else
  219. luci.http.prepare_content("application/json")
  220. luci.http.write(json.encode(json_response))
  221. end
  222. return
  223. end
  224. if not bytes or #response_text == 0 then
  225. sock:close()
  226. http_error(500, "Empty response")
  227. return
  228. end
  229. end
  230. sock:close()
  231. end
  232. function board_plain_socket()
  233. local function send_response(response_text, jsonp_callback)
  234. if not response_text then
  235. luci.http.status(500)
  236. return
  237. end
  238. local rows = luci.util.split(response_text, "\r\n")
  239. if #rows == 1 or string.find(rows[1], "Status") ~= 1 then
  240. luci.http.prepare_content("text/plain")
  241. luci.http.status(200)
  242. luci.http.write(response_text)
  243. return
  244. end
  245. local body_start_at_idx = -1
  246. local content_type = "text/plain"
  247. for idx, row in ipairs(rows) do
  248. if row == "" then
  249. body_start_at_idx = idx
  250. break
  251. end
  252. local key, value = to_key_value(row)
  253. if string.lower(key) == "status" then
  254. luci.http.status(tonumber(value))
  255. elseif string.lower(key) == "content-type" then
  256. content_type = value
  257. else
  258. luci.http.header(key, value)
  259. end
  260. end
  261. local response_body = table.concat(rows, "\r\n", body_start_at_idx + 1)
  262. if content_type == "application/json" and jsonp_callback then
  263. local json = require("luci.json")
  264. luci.http.prepare_content("application/javascript")
  265. luci.http.write(jsonp_callback)
  266. luci.http.write("(")
  267. luci.http.write_json(json.decode(response_body))
  268. luci.http.write(");")
  269. else
  270. luci.http.prepare_content(content_type)
  271. luci.http.write(response_body)
  272. end
  273. end
  274. local method = luci.http.getenv("REQUEST_METHOD")
  275. local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
  276. local parts = parts_after("arduino")
  277. local params = {}
  278. for idx, param in ipairs(parts) do
  279. if not_nil_or_empty(param) then
  280. table.insert(params, param)
  281. end
  282. end
  283. if #params == 0 then
  284. luci.http.status(404)
  285. return
  286. end
  287. params = table.concat(params, "/")
  288. local uci = luci.model.uci.cursor()
  289. uci:load("yunbridge")
  290. local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
  291. local sock, code, msg = nixio.connect("127.0.0.1", 5555)
  292. if not sock then
  293. code = code or ""
  294. msg = msg or ""
  295. http_error(500, "Could not connect to YunServer " .. code .. " " .. msg)
  296. return
  297. end
  298. sock:setopt("socket", "sndtimeo", socket_timeout)
  299. sock:setopt("socket", "rcvtimeo", socket_timeout)
  300. sock:setopt("tcp", "nodelay", 1)
  301. sock:write(params)
  302. sock:writeall("\r\n")
  303. local response_text = sock:readall()
  304. sock:close()
  305. send_response(response_text, jsonp_callback)
  306. end
  307. function build_bridge_mailbox_request()
  308. local method = luci.http.getenv("REQUEST_METHOD")
  309. local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
  310. local parts = parts_after("mailbox")
  311. local params = {}
  312. for idx, param in ipairs(parts) do
  313. if not_nil_or_empty(param) then
  314. table.insert(params, param)
  315. end
  316. end
  317. if #params == 0 then
  318. luci.http.status(400)
  319. return
  320. end
  321. local bridge_request = build_bridge_request("raw", params)
  322. if not bridge_request then
  323. luci.http.status(403)
  324. return
  325. end
  326. local uci = luci.model.uci.cursor()
  327. uci:load("yunbridge")
  328. local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
  329. local sock, code, msg = nixio.connect("127.0.0.1", 5700)
  330. if not sock then
  331. code = code or ""
  332. msg = msg or ""
  333. http_error(500, "nil socket, " .. code .. " " .. msg)
  334. return
  335. end
  336. sock:setopt("socket", "sndtimeo", socket_timeout)
  337. sock:setopt("socket", "rcvtimeo", socket_timeout)
  338. sock:setopt("tcp", "nodelay", 1)
  339. local json = require("luci.json")
  340. sock:write(json.encode(bridge_request))
  341. sock:writeall("\n")
  342. sock:close()
  343. luci.http.status(200)
  344. end