openwrt-packages/utils/yunbridge/files/usr/lib/lua/luci/controller/arduino/index.lua

415 lines
12 KiB
Lua

--[[
This file is part of YunWebUI.
YunWebUI is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, you may use this file as part of a free software
library without restriction. Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
Copyright 2013 Arduino LLC (http://www.arduino.cc/)
]]
module("luci.controller.arduino.index", package.seeall)
local function not_nil_or_empty(value)
return value and value ~= ""
end
local function get_first(cursor, config, type, option)
return cursor:get_first(config, type, option)
end
local function set_first(cursor, config, type, option, value)
cursor:foreach(config, type, function(s)
if s[".type"] == type then
cursor:set(config, s[".name"], option, value)
end
end)
end
local function to_key_value(s)
local parts = luci.util.split(s, ":")
parts[1] = luci.util.trim(parts[1])
parts[2] = luci.util.trim(parts[2])
return parts[1], parts[2]
end
function http_error(code, text)
luci.http.prepare_content("text/plain")
luci.http.status(code)
if text then
luci.http.write(text)
end
end
function index()
function luci.dispatcher.authenticator.arduinoauth(validator, accs, default)
require("luci.controller.arduino.index")
local user = luci.http.formvalue("username")
local pass = luci.http.formvalue("password")
local basic_auth = luci.http.getenv("HTTP_AUTHORIZATION")
if user and validator(user, pass) then
return user
end
if basic_auth and basic_auth ~= "" then
local decoded_basic_auth = nixio.bin.b64decode(string.sub(basic_auth, 7))
user = string.sub(decoded_basic_auth, 0, string.find(decoded_basic_auth, ":") - 1)
pass = string.sub(decoded_basic_auth, string.find(decoded_basic_auth, ":") + 1)
end
if user then
if #pass ~= 64 and validator(user, pass) then
return user
elseif #pass == 64 then
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local stored_encrypted_pass = uci:get_first("yunbridge", "bridge", "password")
if pass == stored_encrypted_pass then
return user
end
end
end
luci.http.header("WWW-Authenticate", "Basic realm=\"yunbridge\"")
luci.http.status(401)
return false
end
local function make_entry(path, target, title, order)
local page = entry(path, target, title, order)
page.leaf = true
return page
end
-- web panel
local webpanel = entry({ "webpanel" }, alias("webpanel", "go_to_homepage"), _("%s Web Panel") % luci.sys.hostname(), 10)
webpanel.sysauth = "root"
webpanel.sysauth_authenticator = "arduinoauth"
make_entry({ "webpanel", "go_to_homepage" }, call("go_to_homepage"), nil)
--api security level
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local secure_rest_api = uci:get_first("yunbridge", "bridge", "secure_rest_api")
local rest_api_sysauth = false
if secure_rest_api == "true" then
rest_api_sysauth = webpanel.sysauth
end
--storage api
local data_api = node("data")
data_api.sysauth = rest_api_sysauth
data_api.sysauth_authenticator = webpanel.sysauth_authenticator
make_entry({ "data", "get" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
make_entry({ "data", "put" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
make_entry({ "data", "delete" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
local mailbox_api = node("mailbox")
mailbox_api.sysauth = rest_api_sysauth
mailbox_api.sysauth_authenticator = webpanel.sysauth_authenticator
make_entry({ "mailbox" }, call("build_bridge_mailbox_request"), nil).sysauth = rest_api_sysauth
--plain socket endpoint
local plain_socket_endpoint = make_entry({ "arduino" }, call("board_plain_socket"), nil)
plain_socket_endpoint.sysauth = rest_api_sysauth
plain_socket_endpoint.sysauth_authenticator = webpanel.sysauth_authenticator
end
function go_to_homepage()
luci.http.redirect("/index.html")
end
local function build_bridge_request(command, params)
local bridge_request = {
command = command
}
if command == "raw" then
params = table.concat(params, "/")
if not_nil_or_empty(params) then
bridge_request["data"] = params
end
return bridge_request
end
if command == "get" then
if not_nil_or_empty(params[1]) then
bridge_request["key"] = params[1]
end
return bridge_request
end
if command == "put" and not_nil_or_empty(params[1]) and params[2] then
bridge_request["key"] = params[1]
bridge_request["value"] = params[2]
return bridge_request
end
if command == "delete" and not_nil_or_empty(params[1]) then
bridge_request["key"] = params[1]
return bridge_request
end
return nil
end
local function extract_jsonp_param(query_string)
if not not_nil_or_empty(query_string) then
return nil
end
local qs_parts = string.split(query_string, "&")
for idx, value in ipairs(qs_parts) do
if string.find(value, "jsonp") == 1 or string.find(value, "callback") == 1 then
return string.sub(value, string.find(value, "=") + 1)
end
end
end
local function parts_after(url_part)
local url = luci.http.getenv("PATH_INFO")
local url_after_part = string.find(url, "/", string.find(url, url_part) + 1)
if not url_after_part then
return {}
end
return luci.util.split(string.sub(url, url_after_part + 1), "/")
end
function storage_send_request()
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("data")
local command = parts[1]
if not command or command == "" then
luci.http.status(404)
return
end
local params = {}
for idx, param in ipairs(parts) do
if idx > 1 and not_nil_or_empty(param) then
table.insert(params, param)
end
end
-- TODO check method?
local bridge_request = build_bridge_request(command, params)
if not bridge_request then
luci.http.status(403)
return
end
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5700)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "nil socket, " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
local json = require("luci.json")
sock:write(json.encode(bridge_request))
sock:writeall("\n")
local response_text = {}
while true do
local bytes = sock:recv(4096)
if bytes and #bytes > 0 then
table.insert(response_text, bytes)
end
local json_response = json.decode(table.concat(response_text))
if json_response then
sock:close()
luci.http.status(200)
if jsonp_callback then
luci.http.prepare_content("application/javascript")
luci.http.write(jsonp_callback)
luci.http.write("(")
luci.http.write_json(json_response)
luci.http.write(");")
else
luci.http.prepare_content("application/json")
luci.http.write(json.encode(json_response))
end
return
end
if not bytes or #response_text == 0 then
sock:close()
http_error(500, "Empty response")
return
end
end
sock:close()
end
function board_plain_socket()
local function send_response(response_text, jsonp_callback)
if not response_text then
luci.http.status(500)
return
end
local rows = luci.util.split(response_text, "\r\n")
if #rows == 1 or string.find(rows[1], "Status") ~= 1 then
luci.http.prepare_content("text/plain")
luci.http.status(200)
luci.http.write(response_text)
return
end
local body_start_at_idx = -1
local content_type = "text/plain"
for idx, row in ipairs(rows) do
if row == "" then
body_start_at_idx = idx
break
end
local key, value = to_key_value(row)
if string.lower(key) == "status" then
luci.http.status(tonumber(value))
elseif string.lower(key) == "content-type" then
content_type = value
else
luci.http.header(key, value)
end
end
local response_body = table.concat(rows, "\r\n", body_start_at_idx + 1)
if content_type == "application/json" and jsonp_callback then
local json = require("luci.json")
luci.http.prepare_content("application/javascript")
luci.http.write(jsonp_callback)
luci.http.write("(")
luci.http.write_json(json.decode(response_body))
luci.http.write(");")
else
luci.http.prepare_content(content_type)
luci.http.write(response_body)
end
end
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("arduino")
local params = {}
for idx, param in ipairs(parts) do
if not_nil_or_empty(param) then
table.insert(params, param)
end
end
if #params == 0 then
luci.http.status(404)
return
end
params = table.concat(params, "/")
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5555)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "Could not connect to YunServer " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
sock:write(params)
sock:writeall("\r\n")
local response_text = sock:readall()
sock:close()
send_response(response_text, jsonp_callback)
end
function build_bridge_mailbox_request()
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("mailbox")
local params = {}
for idx, param in ipairs(parts) do
if not_nil_or_empty(param) then
table.insert(params, param)
end
end
if #params == 0 then
luci.http.status(400)
return
end
local bridge_request = build_bridge_request("raw", params)
if not bridge_request then
luci.http.status(403)
return
end
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5700)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "nil socket, " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
local json = require("luci.json")
sock:write(json.encode(bridge_request))
sock:writeall("\n")
sock:close()
luci.http.status(200)
end