--[[ 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