1import logging
2import time
3import cgi
4import socket
5import sys
6import gevent
7import urllib
8
9from gevent.pywsgi import WSGIServer
10from gevent.pywsgi import WSGIHandler
11from geventwebsocket.handler import WebSocketHandler
12
13from .UiRequest import UiRequest
14from Site import SiteManager
15from Config import config
16from Debug import Debug
17import importlib
18
19
20# Skip websocket handler if not necessary
21class UiWSGIHandler(WSGIHandler):
22
23    def __init__(self, *args, **kwargs):
24        self.server = args[2]
25        super(UiWSGIHandler, self).__init__(*args, **kwargs)
26        self.args = args
27        self.kwargs = kwargs
28
29    def run_application(self):
30        if "HTTP_UPGRADE" in self.environ:  # Websocket request
31            try:
32                ws_handler = WebSocketHandler(*self.args, **self.kwargs)
33                ws_handler.__dict__ = self.__dict__  # Match class variables
34                ws_handler.run_application()
35            except Exception as err:
36                logging.error("UiWSGIHandler websocket error: %s" % Debug.formatException(err))
37                if config.debug:  # Allow websocket errors to appear on /Debug
38                    import main
39                    main.DebugHook.handleError()
40        else:  # Standard HTTP request
41            try:
42                super(UiWSGIHandler, self).run_application()
43            except Exception as err:
44                logging.error("UiWSGIHandler error: %s" % Debug.formatException(err))
45                if config.debug:  # Allow websocket errors to appear on /Debug
46                    import main
47                    main.DebugHook.handleError()
48
49    def handle(self):
50        # Save socket to be able to close them properly on exit
51        self.server.sockets[self.client_address] = self.socket
52        super(UiWSGIHandler, self).handle()
53        del self.server.sockets[self.client_address]
54
55
56class UiServer:
57
58    def __init__(self):
59        self.ip = config.ui_ip
60        self.port = config.ui_port
61        self.running = False
62        if self.ip == "*":
63            self.ip = "0.0.0.0"  # Bind all
64        if config.ui_host:
65            self.allowed_hosts = set(config.ui_host)
66        elif config.ui_ip == "127.0.0.1":
67            # IP Addresses are inherently allowed as they are immune to DNS
68            # rebinding attacks.
69            self.allowed_hosts = set(["zero", "localhost:%s" % config.ui_port])
70            # "URI producers and normalizers should omit the port component and
71            # its ':' delimiter if port is empty or if its value would be the
72            # same as that of the scheme's default."
73            # Source: https://tools.ietf.org/html/rfc3986#section-3.2.3
74            # As a result, we need to support portless hosts if port 80 is in
75            # use.
76            if config.ui_port == 80:
77                self.allowed_hosts.update(["localhost"])
78        else:
79            self.allowed_hosts = set([])
80        self.allowed_ws_origins = set()
81        self.allow_trans_proxy = config.ui_trans_proxy
82
83        self.wrapper_nonces = []
84        self.add_nonces = []
85        self.websockets = []
86        self.site_manager = SiteManager.site_manager
87        self.sites = SiteManager.site_manager.list()
88        self.log = logging.getLogger(__name__)
89
90    # After WebUI started
91    def afterStarted(self):
92        from util import Platform
93        Platform.setMaxfilesopened(config.max_files_opened)
94
95    # Handle WSGI request
96    def handleRequest(self, env, start_response):
97        path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8")
98        if env.get("QUERY_STRING"):
99            get = dict(urllib.parse.parse_qsl(env['QUERY_STRING']))
100        else:
101            get = {}
102        ui_request = UiRequest(self, get, env, start_response)
103        if config.debug:  # Let the exception catched by werkezung
104            return ui_request.route(path)
105        else:  # Catch and display the error
106            try:
107                return ui_request.route(path)
108            except Exception as err:
109                logging.debug("UiRequest error: %s" % Debug.formatException(err))
110                return ui_request.error500("Err: %s" % Debug.formatException(err))
111
112    # Reload the UiRequest class to prevent restarts in debug mode
113    def reload(self):
114        global UiRequest
115        import imp
116        import sys
117        importlib.reload(sys.modules["User.UserManager"])
118        importlib.reload(sys.modules["Ui.UiWebsocket"])
119        UiRequest = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest
120        # UiRequest.reload()
121
122    # Bind and run the server
123    def start(self):
124        self.running = True
125        handler = self.handleRequest
126
127        if config.debug:
128            # Auto reload UiRequest on change
129            from Debug import DebugReloader
130            DebugReloader.watcher.addCallback(self.reload)
131
132            # Werkzeug Debugger
133            try:
134                from werkzeug.debug import DebuggedApplication
135                handler = DebuggedApplication(self.handleRequest, evalex=True)
136            except Exception as err:
137                self.log.info("%s: For debugging please enable the port option: DEBUG=on" % err)
138                from Debug import DebugReloader
139        self.log.write = lambda msg: self.log.debug(msg.strip())  # For Wsgi access.log
140        self.log.info("--------------------------------------")
141        if ":" in config.ui_ip:
142            self.log.info("Web interface: http://[%s]:%s/" % (config.ui_ip, config.ui_port))
143        else:
144            self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port))
145        self.log.info("--------------------------------------")
146
147        if config.open_browser and config.open_browser != "False":
148            logging.info("Opening browser: %s...", config.open_browser)
149            import webbrowser
150            try:
151                if config.open_browser == "default_browser":
152                    browser = webbrowser.get()
153                else:
154                    browser = webbrowser.get(config.open_browser)
155                url = "http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage)
156                gevent.spawn_later(0.3, browser.open, url, new=2)
157            except Exception as err:
158                print("Error starting browser: %s" % err)
159
160        self.server = WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log)
161        self.server.sockets = {}
162        self.afterStarted()
163        try:
164            self.server.serve_forever()
165        except Exception as err:
166            self.log.error("Web interface bind error, must be running already, exiting.... %s" % err)
167            import main
168            main.file_server.stop()
169        self.log.debug("Stopped.")
170
171    def stop(self):
172        self.log.debug("Stopping...")
173        # Close WS sockets
174        if "clients" in dir(self.server):
175            for client in list(self.server.clients.values()):
176                client.ws.close()
177        # Close http sockets
178        sock_closed = 0
179        for sock in list(self.server.sockets.values()):
180            try:
181                sock.send(b"bye")
182                sock.shutdown(socket.SHUT_RDWR)
183                # sock._sock.close()
184                # sock.close()
185                sock_closed += 1
186            except Exception as err:
187                self.log.debug("Http connection close error: %s" % err)
188        self.log.debug("Socket closed: %s" % sock_closed)
189        time.sleep(0.1)
190        if config.debug:
191            from Debug import DebugReloader
192            DebugReloader.watcher.stop()
193
194        self.server.socket.close()
195        self.server.stop()
196        self.running = False
197        time.sleep(1)
198
199    def updateWebsocket(self, **kwargs):
200        for ws in self.websockets:
201            ws.event("serverChanged", kwargs)
202