index.lua (12089B)
1 --[[ 2 This file is part of YunWebUI. 3 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. 8 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. 13 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 17 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. 26 27 Copyright 2013 Arduino LLC (http://www.arduino.cc/) 28 ]] 29 30 module("luci.controller.arduino.index", package.seeall) 31 32 local function not_nil_or_empty(value) 33 return value and value ~= "" 34 end 35 36 local function get_first(cursor, config, type, option) 37 return cursor:get_first(config, type, option) 38 end 39 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) 44 end 45 end) 46 end 47 48 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] 54 end 55 56 function http_error(code, text) 57 luci.http.prepare_content("text/plain") 58 luci.http.status(code) 59 if text then 60 luci.http.write(text) 61 end 62 end 63 64 function index() 65 function luci.dispatcher.authenticator.arduinoauth(validator, accs, default) 66 require("luci.controller.arduino.index") 67 68 local user = luci.http.formvalue("username") 69 local pass = luci.http.formvalue("password") 70 local basic_auth = luci.http.getenv("HTTP_AUTHORIZATION") 71 72 if user and validator(user, pass) then 73 return user 74 end 75 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) 80 end 81 82 if user then 83 if #pass ~= 64 and validator(user, pass) then 84 return user 85 elseif #pass == 64 then 86 local uci = luci.model.uci.cursor() 87 uci:load("yunbridge") 88 local stored_encrypted_pass = uci:get_first("yunbridge", "bridge", "password") 89 if pass == stored_encrypted_pass then 90 return user 91 end 92 end 93 end 94 95 luci.http.header("WWW-Authenticate", "Basic realm=\"yunbridge\"") 96 luci.http.status(401) 97 98 return false 99 end 100 101 local function make_entry(path, target, title, order) 102 local page = entry(path, target, title, order) 103 page.leaf = true 104 return page 105 end 106 107 -- web panel 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" 111 112 make_entry({ "webpanel", "go_to_homepage" }, call("go_to_homepage"), nil) 113 114 --api security level 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 121 end 122 123 --storage api 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 134 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 139 end 140 141 function go_to_homepage() 142 luci.http.redirect("/index.html") 143 end 144 145 local function build_bridge_request(command, params) 146 147 local bridge_request = { 148 command = command 149 } 150 151 if command == "raw" then 152 params = table.concat(params, "/") 153 if not_nil_or_empty(params) then 154 bridge_request["data"] = params 155 end 156 return bridge_request 157 end 158 159 if command == "get" then 160 if not_nil_or_empty(params[1]) then 161 bridge_request["key"] = params[1] 162 end 163 return bridge_request 164 end 165 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 170 end 171 172 if command == "delete" and not_nil_or_empty(params[1]) then 173 bridge_request["key"] = params[1] 174 return bridge_request 175 end 176 177 return nil 178 end 179 180 local function extract_jsonp_param(query_string) 181 if not not_nil_or_empty(query_string) then 182 return nil 183 end 184 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) 189 end 190 end 191 end 192 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 197 return {} 198 end 199 return luci.util.split(string.sub(url, url_after_part + 1), "/") 200 end 201 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) 209 return 210 end 211 local params = {} 212 for idx, param in ipairs(parts) do 213 if idx > 1 and not_nil_or_empty(param) then 214 table.insert(params, param) 215 end 216 end 217 218 -- TODO check method? 219 local bridge_request = build_bridge_request(command, params) 220 if not bridge_request then 221 luci.http.status(403) 222 return 223 end 224 225 local uci = luci.model.uci.cursor() 226 uci:load("yunbridge") 227 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5) 228 229 local sock, code, msg = nixio.connect("127.0.0.1", 5700) 230 if not sock then 231 code = code or "" 232 msg = msg or "" 233 http_error(500, "nil socket, " .. code .. " " .. msg) 234 return 235 end 236 237 sock:setopt("socket", "sndtimeo", socket_timeout) 238 sock:setopt("socket", "rcvtimeo", socket_timeout) 239 sock:setopt("tcp", "nodelay", 1) 240 241 local json = require("luci.json") 242 243 sock:write(json.encode(bridge_request)) 244 sock:writeall("\n") 245 246 local response_text = {} 247 while true do 248 local bytes = sock:recv(4096) 249 if bytes and #bytes > 0 then 250 table.insert(response_text, bytes) 251 end 252 253 local json_response = json.decode(table.concat(response_text)) 254 if json_response then 255 sock:close() 256 luci.http.status(200) 257 if jsonp_callback then 258 luci.http.prepare_content("application/javascript") 259 luci.http.write(jsonp_callback) 260 luci.http.write("(") 261 luci.http.write_json(json_response) 262 luci.http.write(");") 263 else 264 luci.http.prepare_content("application/json") 265 luci.http.write(json.encode(json_response)) 266 end 267 return 268 end 269 270 if not bytes or #response_text == 0 then 271 sock:close() 272 http_error(500, "Empty response") 273 return 274 end 275 end 276 277 sock:close() 278 end 279 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) 284 return 285 end 286 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) 292 return 293 end 294 295 local body_start_at_idx = -1 296 local content_type = "text/plain" 297 for idx, row in ipairs(rows) do 298 if row == "" then 299 body_start_at_idx = idx 300 break 301 end 302 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 307 content_type = value 308 else 309 luci.http.header(key, value) 310 end 311 end 312 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) 318 luci.http.write("(") 319 luci.http.write_json(json.decode(response_body)) 320 luci.http.write(");") 321 else 322 luci.http.prepare_content(content_type) 323 luci.http.write(response_body) 324 end 325 end 326 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") 330 local params = {} 331 for idx, param in ipairs(parts) do 332 if not_nil_or_empty(param) then 333 table.insert(params, param) 334 end 335 end 336 337 if #params == 0 then 338 luci.http.status(404) 339 return 340 end 341 342 params = table.concat(params, "/") 343 344 local uci = luci.model.uci.cursor() 345 uci:load("yunbridge") 346 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5) 347 348 local sock, code, msg = nixio.connect("127.0.0.1", 5555) 349 if not sock then 350 code = code or "" 351 msg = msg or "" 352 http_error(500, "Could not connect to YunServer " .. code .. " " .. msg) 353 return 354 end 355 356 sock:setopt("socket", "sndtimeo", socket_timeout) 357 sock:setopt("socket", "rcvtimeo", socket_timeout) 358 sock:setopt("tcp", "nodelay", 1) 359 360 sock:write(params) 361 sock:writeall("\r\n") 362 363 local response_text = sock:readall() 364 sock:close() 365 366 send_response(response_text, jsonp_callback) 367 end 368 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") 373 local params = {} 374 for idx, param in ipairs(parts) do 375 if not_nil_or_empty(param) then 376 table.insert(params, param) 377 end 378 end 379 380 if #params == 0 then 381 luci.http.status(400) 382 return 383 end 384 385 local bridge_request = build_bridge_request("raw", params) 386 if not bridge_request then 387 luci.http.status(403) 388 return 389 end 390 391 local uci = luci.model.uci.cursor() 392 uci:load("yunbridge") 393 local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5) 394 395 local sock, code, msg = nixio.connect("127.0.0.1", 5700) 396 if not sock then 397 code = code or "" 398 msg = msg or "" 399 http_error(500, "nil socket, " .. code .. " " .. msg) 400 return 401 end 402 403 sock:setopt("socket", "sndtimeo", socket_timeout) 404 sock:setopt("socket", "rcvtimeo", socket_timeout) 405 sock:setopt("tcp", "nodelay", 1) 406 407 local json = require("luci.json") 408 409 sock:write(json.encode(bridge_request)) 410 sock:writeall("\n") 411 sock:close() 412 413 luci.http.status(200) 414 end