summaryrefslogtreecommitdiffstats
path: root/module/lib/mod_pywebsocket/handshake/draft75.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/lib/mod_pywebsocket/handshake/draft75.py')
-rw-r--r--module/lib/mod_pywebsocket/handshake/draft75.py190
1 files changed, 190 insertions, 0 deletions
diff --git a/module/lib/mod_pywebsocket/handshake/draft75.py b/module/lib/mod_pywebsocket/handshake/draft75.py
new file mode 100644
index 000000000..802a31c9a
--- /dev/null
+++ b/module/lib/mod_pywebsocket/handshake/draft75.py
@@ -0,0 +1,190 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
+
+
+# Note: request.connection.write is used in this module, even though mod_python
+# document says that it should be used only in connection handlers.
+# Unfortunately, we have no other options. For example, request.write is not
+# suitable because it doesn't allow direct raw bytes writing.
+
+
+import logging
+import re
+
+from mod_pywebsocket import common
+from mod_pywebsocket.stream import StreamHixie75
+from mod_pywebsocket import util
+from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import build_location
+from mod_pywebsocket.handshake._base import validate_subprotocol
+
+
+_MANDATORY_HEADERS = [
+ # key, expected value or None
+ ['Upgrade', 'WebSocket'],
+ ['Connection', 'Upgrade'],
+ ['Host', None],
+ ['Origin', None],
+]
+
+_FIRST_FIVE_LINES = map(re.compile, [
+ r'^GET /[\S]* HTTP/1.1\r\n$',
+ r'^Upgrade: WebSocket\r\n$',
+ r'^Connection: Upgrade\r\n$',
+ r'^Host: [\S]+\r\n$',
+ r'^Origin: [\S]+\r\n$',
+])
+
+_SIXTH_AND_LATER = re.compile(
+ r'^'
+ r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
+ r'(Cookie: [^\r]*\r\n)*'
+ r'(Cookie2: [^\r]*\r\n)?'
+ r'(Cookie: [^\r]*\r\n)*'
+ r'\r\n')
+
+
+class Handshaker(object):
+ """This class performs WebSocket handshake."""
+
+ def __init__(self, request, dispatcher, strict=False):
+ """Construct an instance.
+
+ Args:
+ request: mod_python request.
+ dispatcher: Dispatcher (dispatch.Dispatcher).
+ strict: Strictly check handshake request. Default: False.
+ If True, request.connection must provide get_memorized_lines
+ method.
+
+ Handshaker will add attributes such as ws_resource in performing
+ handshake.
+ """
+
+ self._logger = util.get_class_logger(self)
+
+ self._request = request
+ self._dispatcher = dispatcher
+ self._strict = strict
+
+ def do_handshake(self):
+ """Perform WebSocket Handshake.
+
+ On _request, we set
+ ws_resource, ws_origin, ws_location, ws_protocol
+ ws_challenge_md5: WebSocket handshake information.
+ ws_stream: Frame generation/parsing class.
+ ws_version: Protocol version.
+ """
+
+ self._check_header_lines()
+ self._set_resource()
+ self._set_origin()
+ self._set_location()
+ self._set_subprotocol()
+ self._set_protocol_version()
+
+ self._dispatcher.do_extra_handshake(self._request)
+
+ self._send_handshake()
+
+ self._logger.debug('Sent opening handshake response')
+
+ def _set_resource(self):
+ self._request.ws_resource = self._request.uri
+
+ def _set_origin(self):
+ self._request.ws_origin = self._request.headers_in['Origin']
+
+ def _set_location(self):
+ self._request.ws_location = build_location(self._request)
+
+ def _set_subprotocol(self):
+ subprotocol = self._request.headers_in.get('WebSocket-Protocol')
+ if subprotocol is not None:
+ validate_subprotocol(subprotocol, hixie=True)
+ self._request.ws_protocol = subprotocol
+
+ def _set_protocol_version(self):
+ self._logger.debug('IETF Hixie 75 protocol')
+ self._request.ws_version = common.VERSION_HIXIE75
+ self._request.ws_stream = StreamHixie75(self._request)
+
+ def _sendall(self, data):
+ self._request.connection.write(data)
+
+ def _send_handshake(self):
+ self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
+ self._sendall('Upgrade: WebSocket\r\n')
+ self._sendall('Connection: Upgrade\r\n')
+ self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
+ self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
+ if self._request.ws_protocol:
+ self._sendall(
+ 'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
+ self._sendall('\r\n')
+
+ def _check_header_lines(self):
+ for key, expected_value in _MANDATORY_HEADERS:
+ actual_value = self._request.headers_in.get(key)
+ if not actual_value:
+ raise HandshakeException('Header %s is not defined' % key)
+ if expected_value:
+ if actual_value != expected_value:
+ raise HandshakeException(
+ 'Expected %r for header %s but found %r' %
+ (expected_value, key, actual_value))
+ if self._strict:
+ try:
+ lines = self._request.connection.get_memorized_lines()
+ except AttributeError, e:
+ raise AttributeError(
+ 'Strict handshake is specified but the connection '
+ 'doesn\'t provide get_memorized_lines()')
+ self._check_first_lines(lines)
+
+ def _check_first_lines(self, lines):
+ if len(lines) < len(_FIRST_FIVE_LINES):
+ raise HandshakeException('Too few header lines: %d' % len(lines))
+ for line, regexp in zip(lines, _FIRST_FIVE_LINES):
+ if not regexp.search(line):
+ raise HandshakeException(
+ 'Unexpected header: %r doesn\'t match %r'
+ % (line, regexp.pattern))
+ sixth_and_later = ''.join(lines[5:])
+ if not _SIXTH_AND_LATER.search(sixth_and_later):
+ raise HandshakeException(
+ 'Unexpected header: %r doesn\'t match %r'
+ % (sixth_and_later, _SIXTH_AND_LATER.pattern))
+
+
+# vi:sts=4 sw=4 et