From 80e2cdcd8d83f71f34fbfc96776f744c41303f95 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 5 Nov 2019 14:26:35 +0100 Subject: [PATCH] refact: using flask templating --- pwnagotchi/ui/display.py | 5 +- pwnagotchi/ui/web/__init__.py | 12 +++ pwnagotchi/ui/{web.py => web/handler.py} | 95 +++++++----------------- pwnagotchi/ui/web/server.py | 43 +++++++++++ 4 files changed, 83 insertions(+), 72 deletions(-) create mode 100644 pwnagotchi/ui/web/__init__.py rename pwnagotchi/ui/{web.py => web/handler.py} (63%) create mode 100644 pwnagotchi/ui/web/server.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 7d077a0..8abb1d7 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -5,6 +5,7 @@ import threading import pwnagotchi.plugins as plugins import pwnagotchi.ui.hw as hw import pwnagotchi.ui.web as web +from pwnagotchi.ui.web.server import Server from pwnagotchi.ui.view import View @@ -15,7 +16,7 @@ class Display(View): self._enabled = config['enabled'] self._rotation = config['rotation'] - self._webui = web.Server(config) + self._webui = Server(config) self.init_display() @@ -41,7 +42,7 @@ class Display(View): def is_waveshare27inch(self): return self._implementation.name == 'waveshare27inch' - + def is_waveshare29inch(self): return self._implementation.name == 'waveshare29inch' diff --git a/pwnagotchi/ui/web/__init__.py b/pwnagotchi/ui/web/__init__.py new file mode 100644 index 0000000..fd43206 --- /dev/null +++ b/pwnagotchi/ui/web/__init__.py @@ -0,0 +1,12 @@ +from threading import Lock + +frame_path = '/root/pwnagotchi.png' +frame_format = 'PNG' +frame_ctype = 'image/png' +frame_lock = Lock() + + +def update_frame(img): + global frame_lock, frame_path, frame_format + with frame_lock: + img.save(frame_path, format=frame_format) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web/handler.py similarity index 63% rename from pwnagotchi/ui/web.py rename to pwnagotchi/ui/web/handler.py index c7396ff..a04cc54 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web/handler.py @@ -1,6 +1,3 @@ -import _thread -import secrets -from threading import Lock import logging import os @@ -9,27 +6,14 @@ logging.getLogger('werkzeug').setLevel(logging.ERROR) os.environ['WERKZEUG_RUN_MAIN'] = 'true' import pwnagotchi +import pwnagotchi.ui.web as web from pwnagotchi.agent import Agent from pwnagotchi import plugins -from flask import Flask + from flask import send_file from flask import request from flask import abort from flask import render_template_string -from flask_cors import CORS -from flask_wtf.csrf import CSRFProtect - -frame_path = '/root/pwnagotchi.png' -frame_format = 'PNG' -frame_ctype = 'image/png' -frame_lock = Lock() - - -def update_frame(img): - global frame_lock, frame_path, frame_format - with frame_lock: - img.save(frame_path, format=frame_format) - STYLE = """ .block { @@ -59,26 +43,27 @@ window.onload = function() { function updateImage() { image.src = image.src.split("?")[0] + "?" + new Date().getTime(); } - setInterval(updateImage, %d); + setInterval(updateImage, 1000); } """ INDEX = """<html> <head> - <title>%s</title> + <title>{{ title }}</title> <style>""" + STYLE + """</style> </head> <body> - <div style="position: absolute; top:0; left:0; width:100%%;" class="pixelated"> - <img src="/ui" id="ui" style="width:100%%;"/> + <div style="position: absolute; top:0; left:0; width:100%;" class="pixelated"> + <img src="/ui" id="ui" style="width:100%;"/> <br/> <hr/> <form style="display:inline;" method="POST" action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');"> <input style="display:inline;" type="submit" class="block" value="Shutdown"/> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> </form> - <form style="display:inline;" method="POST" action="/restart" onsubmit="return confirm('This will restart the service in %s mode, continue?');"> - <input style="display:inline;" type="submit" class="block" value="Restart in %s mode"/> + <form style="display:inline;" method="POST" action="/restart" onsubmit="return confirm('This will restart the service in {{ other_mode }} mode, continue?');"> + <input style="display:inline;" type="submit" class="block" value="Restart in {{ other_mode }} mode"/> + <input type="hidden" name="mode" value="{{ other_mode }}"/> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> </form> </div> @@ -89,18 +74,18 @@ INDEX = """<html> STATUS_PAGE = """<html> <head> - <title>%s</title> + <title>{{ title }}</title> <style>""" + STYLE + """</style> </head> <body> - <div style="position: absolute; top:0; left:0; width:100%%;"> - %s + <div style="position: absolute; top:0; left:0; width:100%;"> + {{ message }} </div> </body> </html>""" -class RequestHandler: +class Handler: def __init__(self, app): self._app = app self._app.add_url_rule('/', 'index', self.index) @@ -115,12 +100,8 @@ class RequestHandler: self._app.add_url_rule('/plugins/<name>/<path:subpath>', 'plugins', self.plugins, methods=['GET', 'POST']) def index(self): - other_mode = 'AUTO' if Agent.INSTANCE.mode == 'manual' else 'MANU' - return render_template_string(INDEX % ( - pwnagotchi.name(), - other_mode, - other_mode, - 1000)) + return render_template_string(INDEX, title=pwnagotchi.name(), + other_mode='AUTO' if Agent.INSTANCE.mode == 'manual' else 'MANU') def plugins(self, name, subpath): if name is None: @@ -142,49 +123,23 @@ class RequestHandler: # serve a message and shuts down the unit def shutdown(self): try: - return render_template_string(STATUS_PAGE % (pwnagotchi.name(), 'Shutting down ...')) + return render_template_string(STATUS_PAGE, title=pwnagotchi.name(), message='Shutting down ...') finally: pwnagotchi.shutdown() # serve a message and restart the unit in the other mode def restart(self): - other_mode = 'AUTO' if Agent.INSTANCE.mode == 'manual' else 'MANU' + mode = request.form['mode'] + if mode not in ('AUTO', 'MANU'): + mode = 'MANU' + try: - return render_template_string(STATUS_PAGE % (pwnagotchi.name(), 'Restart in %s mode ...' % other_mode)) + return render_template_string(STATUS_PAGE, title=pwnagotchi.name(), + message='Restart in %s mode ...' % mode) finally: - pwnagotchi.restart(other_mode) + pwnagotchi.restart(mode) # serve the PNG file with the display image def ui(self): - global frame_lock, frame_path - with frame_lock: - return send_file(frame_path, mimetype='image/png') - - -class Server: - def __init__(self, config): - self._enabled = config['video']['enabled'] - self._port = config['video']['port'] - self._address = config['video']['address'] - self._origin = None - - if 'origin' in config['video']: - self._origin = config['video']['origin'] - - if self._enabled: - _thread.start_new_thread(self._http_serve, ()) - - def _http_serve(self): - if self._address is not None: - app = Flask(__name__) - app.secret_key = secrets.token_urlsafe(256) - - if self._origin: - CORS(app, resources={r"*": {"origins": self._origin}}) - - CSRFProtect(app) - RequestHandler(app) - - app.run(host=self._address, port=self._port, debug=False) - else: - logging.info("could not get ip of usb0, video server not starting") + with web.frame_lock: + return send_file(web.frame_path, mimetype='image/png') diff --git a/pwnagotchi/ui/web/server.py b/pwnagotchi/ui/web/server.py new file mode 100644 index 0000000..5bc1fef --- /dev/null +++ b/pwnagotchi/ui/web/server.py @@ -0,0 +1,43 @@ +import _thread +import secrets +import logging +import os + +# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server +logging.getLogger('werkzeug').setLevel(logging.ERROR) +os.environ['WERKZEUG_RUN_MAIN'] = 'true' + +from flask import Flask +from flask_cors import CORS +from flask_wtf.csrf import CSRFProtect + +from pwnagotchi.ui.web.handler import Handler + + +class Server: + def __init__(self, config): + self._enabled = config['video']['enabled'] + self._port = config['video']['port'] + self._address = config['video']['address'] + self._origin = None + + if 'origin' in config['video']: + self._origin = config['video']['origin'] + + if self._enabled: + _thread.start_new_thread(self._http_serve, ()) + + def _http_serve(self): + if self._address is not None: + app = Flask(__name__) + app.secret_key = secrets.token_urlsafe(256) + + if self._origin: + CORS(app, resources={r"*": {"origins": self._origin}}) + + CSRFProtect(app) + Handler(app) + + app.run(host=self._address, port=self._port, debug=False) + else: + logging.info("could not get ip of usb0, video server not starting")