Anonymous avatar Anonymous committed 6fcf6c7

Detect hybi-07 handshakes and check them for validity

Detection is handled in handle_one_response, which dispatches to the
appropriate handshake implementation for the version detected. Version 75/76
requests are now handled by _handle_one_legacy_response, which is unmodified.

Comments (0)

Files changed (2)

geventwebsocket/handler.py

+import base64
 import re
 import struct
 from hashlib import md5
         super(WebSocketHandler, self).__init__(*args, **kwargs)
 
     def handle_one_response(self, call_wsgi_app=True):
+        if self.environ.get("HTTP_ORIGIN"):
+            self._handle_one_legacy_response()
+        elif self.environ.get("HTTP_SEC_WEBSOCKET_VERSION"):
+            version = int(self.environ.get("HTTP_SEC_WEBSOCKET_VERSION"))
+            if version is 7:
+                if not self._handle_one_version7_response():
+                    return
+            else:
+                return
+        else:
+            # not a valid websocket request
+            return super(WebSocketHandler, self).handle_one_response()
+
+        if call_wsgi_app:
+            return self.application(self.environ, self.start_response)
+        else:
+            return
+
+    def _close_connection(self, reason=None):
+        # based on gevent/pywsgi.py
+        # see http://pypi.python.org/pypi/gevent#downloads
+
+        if reason:
+            print "Closing the connection because %s!" % reason
+        if self.socket is not None:
+            try:
+                self.socket._sock.close()
+                self.socket.close()
+            except socket.error:
+                pass
+
+    def _handle_one_version7_response(self):
+        environ = self.environ
+
+        protocol, version = self.request_version.split("/")
+        # check client handshake for validity
+        if not environ.get("REQUEST_METHOD") == "GET":
+            # 5.2.1 (1)
+            self._close_connection()
+            return False
+        elif not protocol == "HTTP":
+            # 5.2.1 (1)
+            self._close_connection()
+            return False
+        elif float(version) < 1.1:
+            # 5.2.1 (1)
+            self._close_connection()
+            return False
+        elif not environ.get("HTTP_HOST") == environ.get("SERVER_NAME"):
+            # 5.2.1 (2)
+            self._close_connection()
+            return False
+        elif not environ.get("HTTP_SEC_WEBSOCKET_KEY"):
+            # 5.2.1 (3)
+            self._close_connection()
+            return False
+        elif len(base64.b64decode(environ.get("HTTP_SEC_WEBSOCKET_KEY"))) != 16:
+            # 5.2.1 (3)
+            self._close_connection()
+            return False
+        #TODO: provide a way to specify how to handle Sec-WebSocket-Origin if present
+
+    def _handle_one_legacy_response(self):
         # In case the client doesn't want to initialize a WebSocket connection
         # we will proceed with the default PyWSGI functionality.
         if self.environ.get("HTTP_CONNECTION") != "Upgrade" or \
         else:
             raise Exception("Version not supported")
 
-        if call_wsgi_app:
-            return self.application(self.environ, self.start_response)
-        else:
-            return
-
     def accept_upgrade(self):
         """
         Returns True if request is allowed to be upgraded.

tests/test__websocket.py

 from gevent import monkey
 monkey.patch_all(thread=False)
 
+import base64
 import sys
 import greentest
 import gevent
         return socket.create_connection(('127.0.0.1', self.port))
 
 
+"""
 class TestWebSocket(TestCase):
     message = "\x00Hello world\xff"
 
                'Unexpected message: %r (expected %r)\n%s' % (message, self.message, self)
 
         fd.close()
+"""
+
+class TestWebSocketVersion7(TestCase):
+    def application(self, environ, start_response):
+        if environ['PATH_INFO'] == "/echo":
+            try:
+                ws = environ['wsgi.websocket']
+            except KeyError:
+                print ">>> In TestWebSocketVersion7.application!\n\n"
+                start_response("400 Bad Request", [])
+                return []
+
+            while True:
+                message = ws.wait()
+                if message is None:
+                    break
+                ws.send(message)
+
+            return []
+
+    def test_bad_handshake_method(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "POST /echo HTTP/1.1\r\n" \
+        "Host: localhost\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd, code=101, reason="Web Socket Protocol Handshake")
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with bad method"
+        fd.close()
+
+    def test_bad_handshake_version(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "GET /echo HTTP/1.0\r\n" \
+        "Host: localhost\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd)
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with bad version"
+        fd.close()
+
+    def test_bad_handshake_host(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "GET /echo HTTP/1.1\r\n" \
+        "Host: example.com\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd)
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with bad Host"
+        fd.close()
+
+    def test_bad_handshake_no_key(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "GET /echo HTTP/1.1\r\n" \
+        "Host: localhost\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd)
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with no Sec-WebSocket-Key"
+        fd.close()
+
+    def test_bad_handshake_short_key(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "GET /echo HTTP/1.1\r\n" \
+        "Host: localhost\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Key: " + base64.b64encode('too short') + "\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd)
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with key that is too short"
+        fd.close()
+
+    def test_bad_handshake_long_key(self):
+        fd = self.connect().makefile(bufsize=1)
+        closed = False
+        headers = "" \
+        "GET /echo HTTP/1.1\r\n" \
+        "Host: localhost\r\n" \
+        "Upgrade: WebSocket\r\n" \
+        "Connection: Upgrade\r\n" \
+        "Sec-WebSocket-Key: " + base64.b64encode('too long. too long. too long') + "\r\n" \
+        "Sec-WebSocket-Origin: http://localhost\r\n" \
+        "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+        "Sec-WebSocket-Version: 7\r\n" \
+        "\r\n"
+
+        fd.write(headers)
+        try:
+            response = read_http(fd)
+        except ConnectionClosed:
+            closed = True
+
+        assert closed, "Failed to abort connection with key that is too long"
+        fd.close()
+
+    """
+    def test_handshake(self):
+        fd = self.connect().makefile(bufsize=1)
+        headers = "" \
+            "GET /echo HTTP/1.1\r\n" \
+            "Host: localhost\r\n" \
+            "Upgrade: WebSocket\r\n" \
+            "Connection: Upgrade\r\n" \
+            "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" \
+            "Sec-WebSocket-Origin: http://localhost\r\n" \
+            "Sec-WebSocket-Protocol: chat, superchat\r\n" \
+            "Sec-WebSocket-Version: 7\r\n" \
+            "\r\n"
+
+        fd.write(headers)
+        response = read_http(fd, code=101, reason="Web Socket Protocol Handshake")
+
+        fd.write(self.message)
+        message = fd.read(len(self.message))
+        assert message == self.message, \
+               'Unexpected message: %r (expected %r)\n%s' % (message, self.message, self)
+
+        fd.close()
+    """
 
 if __name__ == '__main__':
     greentest.main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.