2 This file is part of YunWebUI.
4 YunWebUI is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 As a special exception, you may use this file as part of a free software
19 library without restriction. Specifically, if other files instantiate
20 templates or use macros or inline functions from this file, or you compile
21 this file and link it with other files to produce an executable, this
22 file does not by itself cause the resulting executable to be covered by
23 the GNU General Public License. This exception does not however
24 invalidate any other reasons why the executable file might be covered by
25 the GNU General Public License.
27 Copyright 2013 Arduino LLC (http://www.arduino.cc/)
30 module("luci.controller.arduino.index", package.seeall)
32 local function not_nil_or_empty(value)
33 return value and value ~= ""
36 local function get_first(cursor, config, type, option)
37 return cursor:get_first(config, type, option)
40 local function set_first(cursor, config, type, option, value)
41 cursor:foreach(config, type, function(s)
42 if s[".type"] == type then
43 cursor:set(config, s[".name"], option, value)
49 local function to_key_value(s)
50 local parts = luci.util.split(s, ":")
51 parts[1] = luci.util.trim(parts[1])
52 parts[2] = luci.util.trim(parts[2])
53 return parts[1], parts[2]
56 function http_error(code, text)
57 luci.http.prepare_content("text/plain")
58 luci.http.status(code)
65 function luci.dispatcher.authenticator.arduinoauth(validator, accs, default)
66 require("luci.controller.arduino.index")
68 local user = luci.http.formvalue("username")
69 local pass = luci.http.formvalue("password")
70 local basic_auth = luci.http.getenv("HTTP_AUTHORIZATION")
72 if user and validator(user, pass) then
76 if basic_auth and basic_auth ~= "" then
77 local decoded_basic_auth = nixio.bin.b64decode(string.sub(basic_auth, 7))
78 user = string.sub(decoded_basic_auth, 0, string.find(decoded_basic_auth, ":") - 1)
79 pass = string.sub(decoded_basic_auth, string.find(decoded_basic_auth, ":") + 1)
83 if #pass ~= 64 and validator(user, pass) then
85 elseif #pass == 64 then
86 local uci = luci.model.uci.cursor()
88 local stored_encrypted_pass = uci:get_first("yunbridge", "bridge", "password")
89 if pass == stored_encrypted_pass then
95 luci.http.header("WWW-Authenticate", "Basic realm=\"yunbridge\"")
101 local function make_entry(path, target, title, order)
102 local page = entry(path, target, title, order)
108 local webpanel = entry({ "webpanel" }, alias("webpanel", "go_to_homepage"), _("%s Web Panel") % luci.sys.hostname(), 10)
109 webpanel.sysauth = "root"
110 webpanel.sysauth_authenticator = "arduinoauth"
112 make_entry({ "webpanel", "go_to_homepage" }, call("go_to_homepage"), nil)
115 local uci = luci.model.uci.cursor()
116 uci:load("yunbridge")
117 local secure_rest_api = uci:get_first("yunbridge", "bridge", "secure_rest_api")
118 local rest_api_sysauth = false
119 if secure_rest_api == "true" then
120 rest_api_sysauth = webpanel.sysauth
124 local data_api = node("data")
125 data_api.sysauth = rest_api_sysauth
126 data_api.sysauth_authenticator = webpanel.sysauth_authenticator
127 make_entry({ "data", "get" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
128 make_entry({ "data", "put" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
129 make_entry({ "data", "delete" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
130 local mailbox_api = node("mailbox")
131 mailbox_api.sysauth = rest_api_sysauth
132 mailbox_api.sysauth_authenticator = webpanel.sysauth_authenticator
133 make_entry({ "mailbox" }, call("build_bridge_mailbox_request"), nil).sysauth = rest_api_sysauth
135 --plain socket endpoint
136 local plain_socket_endpoint = make_entry({ "arduino" }, call("board_plain_socket"), nil)
137 plain_socket_endpoint.sysauth = rest_api_sysauth
138 plain_socket_endpoint.sysauth_authenticator = webpanel.sysauth_authenticator
141 function go_to_homepage()
142 luci.http.redirect("/index.html")
145 local function build_bridge_request(command, params)
147 local bridge_request = {
151 if command == "raw" then
152 params = table.concat(params, "/")
153 if not_nil_or_empty(params) then
154 bridge_request["data"] = params
156 return bridge_request
159 if command == "get" then
160 if not_nil_or_empty(params[1]) then
161 bridge_request["key"] = params[1]
163 return bridge_request
166 if command == "put" and not_nil_or_empty(params[1]) and params[2] then
167 bridge_request["key"] = params[1]
168 bridge_request["value"] = params[2]
169 return bridge_request
172 if command == "delete" and not_nil_or_empty(params[1]) then
173 bridge_request["key"] = params[1]
174 return bridge_request
180 local function extract_jsonp_param(query_string)
181 if not not_nil_or_empty(query_string) then
185 local qs_parts = string.split(query_string, "&")
186 for idx, value in ipairs(qs_parts) do
187 if string.find(value, "jsonp") == 1 or string.find(value, "callback") == 1 then
188 return string.sub(value, string.find(value, "=") + 1)
193 local function parts_after(url_part)
194 local url = luci.http.getenv("PATH_INFO")
195 local url_after_part = string.find(url, "/", string.find(url, url_part) + 1)
196 if not url_after_part then
199 return luci.util.split(string.sub(url, url_after_part + 1), "/")
202 function storage_send_request()
203 local method = luci.http.getenv("REQUEST_METHOD")
204 local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
205 local parts = parts_after("data")
206 local command = parts[1]
207 if not command or command == "" then
208 luci.http.status(404)
212 for idx, param in ipairs(parts) do
213 if idx > 1 and not_nil_or_empty(param) then
214 table.insert(params, param)
218 -- TODO check method?
219 local bridge_request = build_bridge_request(command, params)
220 if not bridge_request then
221 luci.http.status(403)
225 local uci = luci.model.uci.cursor()
226 uci:load("yunbridge")
227 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
229 local sock, code, msg = nixio.connect("127.0.0.1", 5700)
233 http_error(500, "nil socket, " .. code .. " " .. msg)
237 sock:setopt("socket", "sndtimeo", socket_timeout)
238 sock:setopt("socket", "rcvtimeo", socket_timeout)
239 sock:setopt("tcp", "nodelay", 1)
241 local json = require("luci.json")
243 sock:write(json.encode(bridge_request))
246 local response_text = {}
248 local bytes = sock:recv(4096)
249 if bytes and #bytes > 0 then
250 table.insert(response_text, bytes)
253 local json_response = json.decode(table.concat(response_text))
254 if json_response then
256 luci.http.status(200)
257 if jsonp_callback then
258 luci.http.prepare_content("application/javascript")
259 luci.http.write(jsonp_callback)
261 luci.http.write_json(json_response)
262 luci.http.write(");")
264 luci.http.prepare_content("application/json")
265 luci.http.write(json.encode(json_response))
270 if not bytes or #response_text == 0 then
272 http_error(500, "Empty response")
280 function board_plain_socket()
281 local function send_response(response_text, jsonp_callback)
282 if not response_text then
283 luci.http.status(500)
287 local rows = luci.util.split(response_text, "\r\n")
288 if #rows == 1 or string.find(rows[1], "Status") ~= 1 then
289 luci.http.prepare_content("text/plain")
290 luci.http.status(200)
291 luci.http.write(response_text)
295 local body_start_at_idx = -1
296 local content_type = "text/plain"
297 for idx, row in ipairs(rows) do
299 body_start_at_idx = idx
303 local key, value = to_key_value(row)
304 if string.lower(key) == "status" then
305 luci.http.status(tonumber(value))
306 elseif string.lower(key) == "content-type" then
309 luci.http.header(key, value)
313 local response_body = table.concat(rows, "\r\n", body_start_at_idx + 1)
314 if content_type == "application/json" and jsonp_callback then
315 local json = require("luci.json")
316 luci.http.prepare_content("application/javascript")
317 luci.http.write(jsonp_callback)
319 luci.http.write_json(json.decode(response_body))
320 luci.http.write(");")
322 luci.http.prepare_content(content_type)
323 luci.http.write(response_body)
327 local method = luci.http.getenv("REQUEST_METHOD")
328 local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
329 local parts = parts_after("arduino")
331 for idx, param in ipairs(parts) do
332 if not_nil_or_empty(param) then
333 table.insert(params, param)
338 luci.http.status(404)
342 params = table.concat(params, "/")
344 local uci = luci.model.uci.cursor()
345 uci:load("yunbridge")
346 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
348 local sock, code, msg = nixio.connect("127.0.0.1", 5555)
352 http_error(500, "Could not connect to YunServer " .. code .. " " .. msg)
356 sock:setopt("socket", "sndtimeo", socket_timeout)
357 sock:setopt("socket", "rcvtimeo", socket_timeout)
358 sock:setopt("tcp", "nodelay", 1)
361 sock:writeall("\r\n")
363 local response_text = sock:readall()
366 send_response(response_text, jsonp_callback)
369 function build_bridge_mailbox_request()
370 local method = luci.http.getenv("REQUEST_METHOD")
371 local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
372 local parts = parts_after("mailbox")
374 for idx, param in ipairs(parts) do
375 if not_nil_or_empty(param) then
376 table.insert(params, param)
381 luci.http.status(400)
385 local bridge_request = build_bridge_request("raw", params)
386 if not bridge_request then
387 luci.http.status(403)
391 local uci = luci.model.uci.cursor()
392 uci:load("yunbridge")
393 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
395 local sock, code, msg = nixio.connect("127.0.0.1", 5700)
399 http_error(500, "nil socket, " .. code .. " " .. msg)
403 sock:setopt("socket", "sndtimeo", socket_timeout)
404 sock:setopt("socket", "rcvtimeo", socket_timeout)
405 sock:setopt("tcp", "nodelay", 1)
407 local json = require("luci.json")
409 sock:write(json.encode(bridge_request))
413 luci.http.status(200)