|
@ -0,0 +1,414 @@ |
|
|
|
|
|
--[[ |
|
|
|
|
|
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 |