First Commit
[librecmc/package-feed.git] / utils / yunbridge / files / usr / lib / lua / luci / controller / arduino / index.lua
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