Example #1
0
 def _set_challenge_response(self):
     # 5.2 4-8.
     self._request.ws_challenge = self._get_challenge()
     # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
     self._request.ws_challenge_md5 = util.md5_hash(self._request.ws_challenge).digest()
     self._logger.debug("Challenge: %r (%s)", self._request.ws_challenge, util.hexify(self._request.ws_challenge))
     self._logger.debug(
         "Challenge response: %r (%s)", self._request.ws_challenge_md5, util.hexify(self._request.ws_challenge_md5)
     )
Example #2
0
 def _set_challenge_response(self):
     # 5.2 4-8.
     self._request.ws_challenge = self._get_challenge()
     # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
     self._request.ws_challenge_md5 = util.md5_hash(
         self._request.ws_challenge).digest()
     self._logger.debug('Challenge: %r (%s)', self._request.ws_challenge,
                        util.hexify(self._request.ws_challenge))
     self._logger.debug('Challenge response: %r (%s)',
                        self._request.ws_challenge_md5,
                        util.hexify(self._request.ws_challenge_md5))
Example #3
0
 def _set_accept(self):
     # Key validation, response generation.
     key = self._get_key()
     accept = compute_accept(key)
     self._logger.debug('%s: %r (%s)', common.SEC_WEBSOCKET_ACCEPT_HEADER,
                        accept, util.hexify(base64.b64decode(accept)))
     self._request._accept = accept
Example #4
0
    def _get_key(self):
        key = get_mandatory_header(self._request,
                                   common.SEC_WEBSOCKET_KEY_HEADER)

        decoded_key = self._validate_key(key)

        self._logger.debug('%s: %r (%s)', common.SEC_WEBSOCKET_KEY_HEADER, key,
                           util.hexify(decoded_key))

        return key.encode('UTF-8')
Example #5
0
    def _get_key(self):
        key = get_mandatory_header(self._request,
                                   common.SEC_WEBSOCKET_KEY_HEADER)

        decoded_key = self._validate_key(key)

        self._logger.debug('Sec-WebSocket-Key: %r (%s)', key,
                           util.hexify(decoded_key))

        return key
Example #6
0
    def _get_key(self):
        key = get_mandatory_header(
            self._request, common.SEC_WEBSOCKET_KEY_HEADER)

        decoded_key = self._validate_key(key)

        self._logger.debug('Sec-WebSocket-Key: %r (%s)',
                           key, util.hexify(decoded_key))

        return key
def web_socket_transfer_data(request):
    # Wait for a close frame sent from the client.
    close_frame = request.ws_stream.receive_bytes(6)

    msgutil.send_message(request, 'Client should ignore this message')

    # Send only first two bytes of the received frame. The remaining four bytes
    # are "masking key", which changes every time the test runs.

    message = "close_frame[:2]='%s'" % util.hexify(close_frame[:2])
    data = struct.pack('!H', 1000) + message.encode('UTF-8')

    request.connection.write(stream.create_close_frame(data))

    # Tell pywebsocket we have sent a close frame to the client, so it can close
    # the connection.
    request.server_terminated = True
Example #8
0
    def do_handshake(self):
        check_header_lines(self._request, _MANDATORY_HEADERS)
        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._get_origin()
        self._check_version()
        self._set_protocol()
        self._set_extensions()

        key = self._get_key()
        (accept, accept_binary) = compute_accept(key)
        self._logger.debug('Sec-WebSocket-Accept: %r (%s)', accept,
                           util.hexify(accept_binary))

        self._logger.debug('IETF HyBi 07 protocol')
        self._request.ws_version = common.VERSION_HYBI07
        stream_options = StreamOptions()
        stream_options.deflate = self._request.ws_deflate
        self._request.ws_stream = Stream(self._request, stream_options)

        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        self._dispatcher.do_extra_handshake(self._request)

        if self._request.ws_requested_protocols is not None:
            if self._request.ws_protocol is None:
                raise HandshakeError(
                    'do_extra_handshake must choose one subprotocol from '
                    'ws_requested_protocols and set it to ws_protocol')

            # TODO(tyoshino): Validate selected subprotocol value.

            self._logger.debug('Subprotocol accepted: %r',
                               self._request.ws_protocol)
        else:
            if self._request.ws_protocol is not None:
                raise HandshakeError(
                    'ws_protocol must be None when the client didn\'t request '
                    'any subprotocol')

        self._send_handshake(accept)

        self._logger.debug('Sent opening handshake response')
Example #9
0
 def test_hexify(self):
     self.assertEqual('61 7a 41 5a 30 39 20 09 0d 0a 00 ff',
                      util.hexify(b'azAZ09 \t\r\n\x00\xff'))
Example #10
0
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(
            self._request,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        # This handshake must be based on latest hybi. We are responsible to
        # fallback to HTTP on handshake failure as latest hybi handshake
        # specifies.
        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            (accept, accept_binary) = compute_accept(key)
            self._logger.debug(
                '%s: %r (%s)',
                common.SEC_WEBSOCKET_ACCEPT_HEADER,
                accept,
                util.hexify(accept_binary))

            self._logger.debug('Protocol version is RFC 6455')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # List of extra headers. The extra handshake handler may add header
            # data as name/value pairs to this list and pywebsocket appends
            # them to the WebSocket handshake.
            self._request.extra_headers = []

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)
            processors = filter(lambda processor: processor is not None,
                                self._request.ws_extension_processors)

            # Ask each processor if there are extensions on the request which
            # cannot co-exist. When processor decided other processors cannot
            # co-exist with it, the processor marks them (or itself) as
            # "inactive". The first extension processor has the right to
            # make the final call.
            for processor in reversed(processors):
                if processor.is_active():
                    processor.check_consistency_with_other_processors(
                        processors)
            processors = filter(lambda processor: processor.is_active(),
                                processors)

            accepted_extensions = []

            # We need to take into account of mux extension here.
            # If mux extension exists:
            # - Remove processors of extensions for logical channel,
            #   which are processors located before the mux processor
            # - Pass extension requests for logical channel to mux processor
            # - Attach the mux processor to the request. It will be referred
            #   by dispatcher to see whether the dispatcher should use mux
            #   handler or not.
            mux_index = -1
            for i, processor in enumerate(processors):
                if processor.name() == common.MUX_EXTENSION:
                    mux_index = i
                    break
            if mux_index >= 0:
                logical_channel_extensions = []
                for processor in processors[:mux_index]:
                    logical_channel_extensions.append(processor.request())
                    processor.set_active(False)
                self._request.mux_processor = processors[mux_index]
                self._request.mux_processor.set_extensions(
                    logical_channel_extensions)
                processors = filter(lambda processor: processor.is_active(),
                                    processors)

            stream_options = StreamOptions()

            for processor in processors:
                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                accepted_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

            if len(accepted_extensions) > 0:
                self._request.ws_extensions = accepted_extensions
                self._logger.debug(
                    'Extensions accepted: %r',
                    map(common.ExtensionParameter.name, accepted_extensions))
            else:
                self._request.ws_extensions = None

            self._request.ws_stream = self._create_stream(stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol)

                self._logger.debug(
                    'Subprotocol accepted: %r',
                    self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException, e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #11
0
    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        request_line = _build_method_line(self._options.resource)
        self._logger.debug('Client\'s opening handshake Request-Line: %r',
                           request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(
            _format_host_header(self._options.server_host,
                                self._options.server_port,
                                self._options.use_tls))
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)
        if self._options.origin is not None:
            if self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
                fields.append(
                    _origin_header(common.SEC_WEBSOCKET_ORIGIN_HEADER,
                                   self._options.origin))
            else:
                fields.append(
                    _origin_header(common.ORIGIN_HEADER, self._options.origin))

        # TODO: adapt original library code
        # original code uses os.urandom which is not available on digi gateways
        # get 16 bytes out of the int range 33..126 (readable ascii codes)
        random_int_list = random.sample(range(33, 127), 16)
        original_key = ''.join(chr(i) for i in random_int_list)

        self._key = base64.b64encode(original_key)

        self._logger.debug('%s: %r (%s)', common.SEC_WEBSOCKET_KEY_HEADER,
                           self._key, util.hexify(original_key))
        fields.append('%s: %s\r\n' %
                      (common.SEC_WEBSOCKET_KEY_HEADER, self._key))

        if self._options.version_header > 0:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          self._options.version_header))
        elif self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
            fields.append(
                '%s: %d\r\n' %
                (common.SEC_WEBSOCKET_VERSION_HEADER, common.VERSION_HYBI08))
        else:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          common.VERSION_HYBI_LATEST))

        extensions_to_request = []

        if self._options.deflate_stream:
            extensions_to_request.append(
                common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION))

        if self._options.deflate_frame:
            extensions_to_request.append(
                common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION))

        if len(extensions_to_request) != 0:
            fields.append('%s: %s\r\n' %
                          (common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
                           common.format_extensions(extensions_to_request)))

        for field in fields:
            self._socket.sendall(field)

        self._socket.sendall('\r\n')

        self._logger.debug('Sent client\'s opening handshake headers: %r',
                           fields)
        self._logger.debug('Start reading Status-Line')

        status_line = ''
        while True:
            ch = _receive_bytes(self._socket, 1)
            status_line += ch
            if ch == '\n':
                break

        m = re.match('HTTP/\\d+\.\\d+ (\\d\\d\\d) .*\r\n', status_line)
        if m is None:
            raise ClientHandshakeError('Wrong status line format: %r' %
                                       status_line)
        status_code = m.group(1)
        if status_code != '101':
            self._logger.debug(
                'Unexpected status code %s with following '
                'headers: %r', status_code, self._read_fields())
            raise ClientHandshakeError(
                'Expected HTTP status code 101 but found %r' % status_code)

        self._logger.debug('Received valid Status-Line')
        self._logger.debug('Start reading headers until we see an empty line')

        fields = self._read_fields()

        ch = _receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise ClientHandshakeError(
                'Expected LF but found %r while reading value %r for header '
                'name %r' % (ch, value, name))

        self._logger.debug('Received an empty line')
        self._logger.debug('Server\'s opening handshake headers: %r', fields)

        _validate_mandatory_header(fields, common.UPGRADE_HEADER,
                                   common.WEBSOCKET_UPGRADE_TYPE, False)

        _validate_mandatory_header(fields, common.CONNECTION_HEADER,
                                   common.UPGRADE_CONNECTION_TYPE, False)

        accept = _get_mandatory_header(fields,
                                       common.SEC_WEBSOCKET_ACCEPT_HEADER)

        # Validate
        try:
            binary_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeError('Illegal value for header %s: %r' %
                                 (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))
Example #12
0
 def test_hexify(self):
     self.assertEqual('61 7a 41 5a 30 39 20 09 0d 0a 00 ff',
                      util.hexify('azAZ09 \t\r\n\x00\xff'))
Example #13
0
class Handshaker(object):
    """This class performs WebSocket handshake."""
    def __init__(self, request, dispatcher):
        """Construct an instance.

        Args:
            request: mod_python request.
            dispatcher: Dispatcher (dispatch.Dispatcher).

        Handshaker will add attributes such as ws_resource during handshake.
        """

        self._logger = util.get_class_logger(self)

        self._request = request
        self._dispatcher = dispatcher

    def do_handshake(self):
        check_request_line(self._request)

        validate_mandatory_header(self._request, common.UPGRADE_HEADER,
                                  common.WEBSOCKET_UPGRADE_TYPE)

        connection = get_mandatory_header(self._request,
                                          common.CONNECTION_HEADER)

        try:
            connection_tokens = parse_token_list(connection)
        except HandshakeError, e:
            raise HandshakeError('Failed to parse %s: %s' %
                                 (common.CONNECTION_HEADER, e))

        connection_is_valid = False
        for token in connection_tokens:
            if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower():
                connection_is_valid = True
                break
        if not connection_is_valid:
            raise HandshakeError(
                '%s header doesn\'t contain "%s"' %
                (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._get_origin()
        self._check_version()
        self._set_protocol()
        self._set_extensions()

        key = self._get_key()
        (accept, accept_binary) = compute_accept(key)
        self._logger.debug('Sec-WebSocket-Accept: %r (%s)', accept,
                           util.hexify(accept_binary))

        self._logger.debug('IETF HyBi 07 protocol')
        self._request.ws_version = common.VERSION_HYBI07
        stream_options = StreamOptions()
        stream_options.deflate = self._request.ws_deflate
        self._request.ws_stream = Stream(self._request, stream_options)

        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        self._dispatcher.do_extra_handshake(self._request)

        if self._request.ws_requested_protocols is not None:
            if self._request.ws_protocol is None:
                raise HandshakeError(
                    'do_extra_handshake must choose one subprotocol from '
                    'ws_requested_protocols and set it to ws_protocol')

            # TODO(tyoshino): Validate selected subprotocol value.

            self._logger.debug('Subprotocol accepted: %r',
                               self._request.ws_protocol)
        else:
            if self._request.ws_protocol is not None:
                raise HandshakeError(
                    'ws_protocol must be None when the client didn\'t request '
                    'any subprotocol')

        self._send_handshake(accept)

        self._logger.debug('Sent opening handshake response')
Example #14
0
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(
            self._request,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        # This handshake must be based on latest hybi. We are responsible to
        # fallback to HTTP on handshake failure as latest hybi handshake
        # specifies.
        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            (accept, accept_binary) = compute_accept(key)
            self._logger.debug(
                '%s: %r (%s)',
                common.SEC_WEBSOCKET_ACCEPT_HEADER,
                accept,
                util.hexify(accept_binary))

            self._logger.debug('Protocol version is RFC 6455')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)
            processors = filter(lambda processor: processor is not None,
                                self._request.ws_extension_processors)

            accepted_extensions = []

            # We need to take care of mux extension here. Extensions that
            # are placed before mux should be applied to logical channels.
            mux_index = -1
            for i, processor in enumerate(processors):
                if processor.name() == common.MUX_EXTENSION:
                    mux_index = i
                    break
            if mux_index >= 0:
                mux_processor = processors[mux_index]
                logical_channel_processors = processors[:mux_index]
                processors = processors[mux_index+1:]

                for processor in logical_channel_processors:
                    extension_response = processor.get_extension_response()
                    if extension_response is None:
                        # Rejected.
                        continue
                    accepted_extensions.append(extension_response)
                # Pass a shallow copy of accepted_extensions as extensions for
                # logical channels.
                mux_response = mux_processor.get_extension_response(
                    self._request, accepted_extensions[:])
                if mux_response is not None:
                    accepted_extensions.append(mux_response)

            stream_options = StreamOptions()

            # When there is mux extension, here, |processors| contain only
            # prosessors for extensions placed after mux.
            for processor in processors:

                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                accepted_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

            if len(accepted_extensions) > 0:
                self._request.ws_extensions = accepted_extensions
                self._logger.debug(
                    'Extensions accepted: %r',
                    map(common.ExtensionParameter.name, accepted_extensions))
            else:
                self._request.ws_extensions = None

            self._request.ws_stream = self._create_stream(stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol, hixie=False)

                self._logger.debug(
                    'Subprotocol accepted: %r',
                    self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException, e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #15
0
    def handshake(self, socket):
        """Handshake WebSocket.

        Raises:
            Exception: handshake failed.
        """

        self._socket = socket

        request_line = _method_line(self._options.resource)
        self._logger.debug('Opening handshake Request-Line: %r', request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)

        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))

        if self._options.version is 8:
            fields.append(_sec_origin_header(self._options.origin))
        else:
            fields.append(_origin_header(self._options.origin))

        original_key = os.urandom(16)
        key = base64.b64encode(original_key)
        self._logger.debug(
            'Sec-WebSocket-Key: %s (%s)', key, util.hexify(original_key))
        fields.append('Sec-WebSocket-Key: %s\r\n' % key)

        fields.append('Sec-WebSocket-Version: %d\r\n' % self._options.version)

        # Setting up extensions.
        if len(self._options.extensions) > 0:
            fields.append('Sec-WebSocket-Extensions: %s\r\n' %
                          ', '.join(self._options.extensions))

        self._logger.debug('Opening handshake request headers: %r', fields)

        for field in fields:
            self._socket.sendall(field)
        self._socket.sendall('\r\n')

        self._logger.info('Sent opening handshake request')

        field = ''
        while True:
            ch = receive_bytes(self._socket, 1)
            field += ch
            if ch == '\n':
                break

        self._logger.debug('Opening handshake Response-Line: %r', field)

        if len(field) < 7 or not field.endswith('\r\n'):
            raise Exception('Wrong status line: %r' % field)
        m = re.match('[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise Exception(
                'No HTTP status code found in status line: %r' % field)
        code = m.group(1)
        if not re.match('[0-9][0-9][0-9]', code):
            raise Exception(
                'HTTP status code %r is not three digit in status line: %r' %
                (code, field))
        if code != '101':
            raise HttpStatusException(
                'Expected HTTP status code 101 but found %r in status line: '
                '%r' % (code, field), int(code))
        fields = _read_fields(self._socket)
        ch = receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise Exception('Expected LF but found: %r' % ch)

        self._logger.debug('Opening handshake response headers: %r', fields)

        # Check /fields/
        if len(fields['upgrade']) != 1:
            raise Exception(
                'Multiple Upgrade headers found: %s' % fields['upgrade'])
        if len(fields['connection']) != 1:
            raise Exception(
                'Multiple Connection headers found: %s' % fields['connection'])
        if fields['upgrade'][0] != 'websocket':
            raise Exception(
                'Unexpected Upgrade header value: %s' % fields['upgrade'][0])
        if fields['connection'][0].lower() != 'upgrade':
            raise Exception(
                'Unexpected Connection header value: %s' %
                fields['connection'][0])

        if len(fields['sec-websocket-accept']) != 1:
            raise Exception(
                'Multiple Sec-WebSocket-Accept headers found: %s' %
                fields['sec-websocket-accept'])

        accept = fields['sec-websocket-accept'][0]

        # Validate
        try:
            decoded_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeException(
                'Illegal value for header Sec-WebSocket-Accept: ' + accept)
Example #16
0
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(
            self._request,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        # This handshake must be based on latest hybi. We are responsible to
        # fallback to HTTP on handshake failure as latest hybi handshake
        # specifies.
        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            (accept, accept_binary) = compute_accept(key)
            self._logger.debug(
                '%s: %r (%s)',
                common.SEC_WEBSOCKET_ACCEPT_HEADER,
                accept,
                util.hexify(accept_binary))

            self._logger.debug('Protocol version is RFC 6455')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)

            stream_options = StreamOptions()

            self._request.ws_extensions = None
            for processor in self._request.ws_extension_processors:
                if processor is None:
                    # Some processors may be removed by extra handshake
                    # handler.
                    continue

                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                if self._request.ws_extensions is None:
                    self._request.ws_extensions = []
                self._request.ws_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

            if self._request.ws_extensions is not None:
                self._logger.debug(
                    'Extensions accepted: %r',
                    map(common.ExtensionParameter.name,
                        self._request.ws_extensions))

            self._request.ws_stream = Stream(self._request, stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol, hixie=False)

                self._logger.debug(
                    'Subprotocol accepted: %r',
                    self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException, e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #17
0
    def handshake(self, socket):
        """Handshake WebSocket.

        Raises:
            Exception: handshake failed.
        """

        self._socket = socket

        request_line = _method_line(self._options.resource)
        self._logger.debug('Opening handshake Request-Line: %r', request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)

        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))

        if self._options.version is 8:
            fields.append(_sec_origin_header(self._options.origin))
        else:
            fields.append(_origin_header(self._options.origin))

        original_key = os.urandom(16)
        key = base64.b64encode(original_key)
        self._logger.debug(
            'Sec-WebSocket-Key: %s (%s)', key, util.hexify(original_key))
        fields.append('Sec-WebSocket-Key: %s\r\n' % key)

        fields.append('Sec-WebSocket-Version: %d\r\n' % self._options.version)

        # Setting up extensions.
        if len(self._options.extensions) > 0:
            fields.append('Sec-WebSocket-Extensions: %s\r\n' %
                          ', '.join(self._options.extensions))

        self._logger.debug('Opening handshake request headers: %r', fields)

        for field in fields:
            self._socket.sendall(field)
        self._socket.sendall('\r\n')

        self._logger.info('Sent opening handshake request')

        field = ''
        while True:
            ch = receive_bytes(self._socket, 1)
            field += ch
            if ch == '\n':
                break

        self._logger.debug('Opening handshake Response-Line: %r', field)

        if len(field) < 7 or not field.endswith('\r\n'):
            raise Exception('Wrong status line: %r' % field)
        m = re.match('[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise Exception(
                'No HTTP status code found in status line: %r' % field)
        code = m.group(1)
        if not re.match('[0-9][0-9][0-9]', code):
            raise Exception(
                'HTTP status code %r is not three digit in status line: %r' %
                (code, field))
        if code != '101':
            raise HttpStatusException(
                'Expected HTTP status code 101 but found %r in status line: '
                '%r' % (code, field), int(code))
        fields = _read_fields(self._socket)
        ch = receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise Exception('Expected LF but found: %r' % ch)

        self._logger.debug('Opening handshake response headers: %r', fields)

        # Check /fields/
        if len(fields['upgrade']) != 1:
            raise Exception(
                'Multiple Upgrade headers found: %s' % fields['upgrade'])
        if len(fields['connection']) != 1:
            raise Exception(
                'Multiple Connection headers found: %s' % fields['connection'])
        if fields['upgrade'][0] != 'websocket':
            raise Exception(
                'Unexpected Upgrade header value: %s' % fields['upgrade'][0])
        if fields['connection'][0].lower() != 'upgrade':
            raise Exception(
                'Unexpected Connection header value: %s' %
                fields['connection'][0])

        if len(fields['sec-websocket-accept']) != 1:
            raise Exception(
                'Multiple Sec-WebSocket-Accept headers found: %s' %
                fields['sec-websocket-accept'])

        accept = fields['sec-websocket-accept'][0]

        # Validate
        try:
            decoded_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeException(
                'Illegal value for header Sec-WebSocket-Accept: ' + accept)
Example #18
0
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(self._request, common.UPGRADE_HEADER,
                                  common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            accept = compute_accept(key)
            self._logger.debug('%s: %r (%s)',
                               common.SEC_WEBSOCKET_ACCEPT_HEADER, accept,
                               util.hexify(base64.b64decode(accept)))

            self._logger.debug('Protocol version is RFC 6455')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # List of extra headers. The extra handshake handler may add header
            # data as name/value pairs to this list and pywebsocket appends
            # them to the WebSocket handshake.
            self._request.extra_headers = []

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)
            processors = [
                processor
                for processor in self._request.ws_extension_processors
                if processor is not None
            ]

            # Ask each processor if there are extensions on the request which
            # cannot co-exist. When processor decided other processors cannot
            # co-exist with it, the processor marks them (or itself) as
            # "inactive". The first extension processor has the right to
            # make the final call.
            for processor in reversed(processors):
                if processor.is_active():
                    processor.check_consistency_with_other_processors(
                        processors)
            processors = [
                processor for processor in processors if processor.is_active()
            ]

            accepted_extensions = []

            stream_options = StreamOptions()

            for index, processor in enumerate(processors):
                if not processor.is_active():
                    continue

                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                accepted_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

                # Inactivate all of the following compression extensions.
                for j in range(index + 1, len(processors)):
                    processors[j].set_active(False)

            if len(accepted_extensions) > 0:
                self._request.ws_extensions = accepted_extensions
                self._logger.debug(
                    'Extensions accepted: %r',
                    list(
                        map(common.ExtensionParameter.name,
                            accepted_extensions)))
            else:
                self._request.ws_extensions = None

            self._request.ws_stream = self._create_stream(stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol)

                self._logger.debug('Subprotocol accepted: %r',
                                   self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException as e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #19
0
File: hybi.py Project: GhostQ/GitSB
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(self._request, common.UPGRADE_HEADER,
                                  common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        # This handshake must be based on latest hybi. We are responsible to
        # fallback to HTTP on handshake failure as latest hybi handshake
        # specifies.
        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            (accept, accept_binary) = compute_accept(key)
            self._logger.debug('%s: %r (%s)',
                               common.SEC_WEBSOCKET_ACCEPT_HEADER, accept,
                               util.hexify(accept_binary))

            self._logger.debug('Protocol version is RFC 6455')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # List of extra headers. The extra handshake handler may add header
            # data as name/value pairs to this list and pywebsocket appends
            # them to the WebSocket handshake.
            self._request.extra_headers = []

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)
            processors = filter(lambda processor: processor is not None,
                                self._request.ws_extension_processors)

            # Ask each processor if there are extensions on the request which
            # cannot co-exist. When processor decided other processors cannot
            # co-exist with it, the processor marks them (or itself) as
            # "inactive". The first extension processor has the right to
            # make the final call.
            for processor in reversed(processors):
                if processor.is_active():
                    processor.check_consistency_with_other_processors(
                        processors)
            processors = filter(lambda processor: processor.is_active(),
                                processors)

            accepted_extensions = []

            # We need to take into account of mux extension here.
            # If mux extension exists:
            # - Remove processors of extensions for logical channel,
            #   which are processors located before the mux processor
            # - Pass extension requests for logical channel to mux processor
            # - Attach the mux processor to the request. It will be referred
            #   by dispatcher to see whether the dispatcher should use mux
            #   handler or not.
            mux_index = -1
            for i, processor in enumerate(processors):
                if processor.name() == common.MUX_EXTENSION:
                    mux_index = i
                    break
            if mux_index >= 0:
                logical_channel_extensions = []
                for processor in processors[:mux_index]:
                    logical_channel_extensions.append(processor.request())
                    processor.set_active(False)
                self._request.mux_processor = processors[mux_index]
                self._request.mux_processor.set_extensions(
                    logical_channel_extensions)
                processors = filter(lambda processor: processor.is_active(),
                                    processors)

            stream_options = StreamOptions()

            for index, processor in enumerate(processors):
                if not processor.is_active():
                    continue

                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                accepted_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

                if not is_compression_extension(processor.name()):
                    continue

                # Inactivate all of the following compression extensions.
                for j in xrange(index + 1, len(processors)):
                    if is_compression_extension(processors[j].name()):
                        processors[j].set_active(False)

            if len(accepted_extensions) > 0:
                self._request.ws_extensions = accepted_extensions
                self._logger.debug(
                    'Extensions accepted: %r',
                    map(common.ExtensionParameter.name, accepted_extensions))
            else:
                self._request.ws_extensions = None

            self._request.ws_stream = self._create_stream(stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol)

                self._logger.debug('Subprotocol accepted: %r',
                                   self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException, e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #20
0
    def do_handshake(self):
        self._request.ws_close_code = None
        self._request.ws_close_reason = None

        # Parsing.

        check_request_line(self._request)

        validate_mandatory_header(self._request, common.UPGRADE_HEADER,
                                  common.WEBSOCKET_UPGRADE_TYPE)

        self._validate_connection_header()

        self._request.ws_resource = self._request.uri

        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)

        self._request.ws_version = self._check_version()

        # This handshake must be based on latest hybi. We are responsible to
        # fallback to HTTP on handshake failure as latest hybi handshake
        # specifies.
        try:
            self._get_origin()
            self._set_protocol()
            self._parse_extensions()

            # Key validation, response generation.

            key = self._get_key()
            (accept, accept_binary) = compute_accept(key)
            self._logger.debug('%s: %r (%s)',
                               common.SEC_WEBSOCKET_ACCEPT_HEADER, accept,
                               util.hexify(accept_binary))

            self._logger.debug('IETF HyBi protocol')

            # Setup extension processors.

            processors = []
            if self._request.ws_requested_extensions is not None:
                for extension_request in self._request.ws_requested_extensions:
                    processor = get_extension_processor(extension_request)
                    # Unknown extension requests are just ignored.
                    if processor is not None:
                        processors.append(processor)
            self._request.ws_extension_processors = processors

            # Extra handshake handler may modify/remove processors.
            self._dispatcher.do_extra_handshake(self._request)

            stream_options = StreamOptions()

            self._request.ws_extensions = None
            for processor in self._request.ws_extension_processors:
                if processor is None:
                    # Some processors may be removed by extra handshake
                    # handler.
                    continue

                extension_response = processor.get_extension_response()
                if extension_response is None:
                    # Rejected.
                    continue

                if self._request.ws_extensions is None:
                    self._request.ws_extensions = []
                self._request.ws_extensions.append(extension_response)

                processor.setup_stream_options(stream_options)

            if self._request.ws_extensions is not None:
                self._logger.debug(
                    'Extensions accepted: %r',
                    map(common.ExtensionParameter.name,
                        self._request.ws_extensions))

            self._request.ws_stream = Stream(self._request, stream_options)

            if self._request.ws_requested_protocols is not None:
                if self._request.ws_protocol is None:
                    raise HandshakeException(
                        'do_extra_handshake must choose one subprotocol from '
                        'ws_requested_protocols and set it to ws_protocol')
                validate_subprotocol(self._request.ws_protocol, hixie=False)

                self._logger.debug('Subprotocol accepted: %r',
                                   self._request.ws_protocol)
            else:
                if self._request.ws_protocol is not None:
                    raise HandshakeException(
                        'ws_protocol must be None when the client didn\'t '
                        'request any subprotocol')

            self._send_handshake(accept)
        except HandshakeException, e:
            if not e.status:
                # Fallback to 400 bad request by default.
                e.status = common.HTTP_STATUS_BAD_REQUEST
            raise e
Example #21
0
    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        request_line = _build_method_line(self._options.resource)
        self._logger.debug('Client\'s opening handshake Request-Line: %r',
                           request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)
        if self._options.origin is not None:
            if self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
                fields.append(_origin_header(
                    common.SEC_WEBSOCKET_ORIGIN_HEADER,
                    self._options.origin))
            else:
                fields.append(_origin_header(common.ORIGIN_HEADER,
                                             self._options.origin))

        original_key = os.urandom(16)
        self._key = base64.b64encode(original_key)
        self._logger.debug(
            '%s: %r (%s)',
            common.SEC_WEBSOCKET_KEY_HEADER,
            self._key,
            util.hexify(original_key))
        fields.append(
            '%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY_HEADER, self._key))

        if self._options.version_header > 0:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          self._options.version_header))
        elif self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          common.VERSION_HYBI08))
        else:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          common.VERSION_HYBI_LATEST))

        extensions_to_request = []

        if self._options.deflate_frame:
            extensions_to_request.append(
                common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION))

        if self._options.use_permessage_deflate:
            extension = common.ExtensionParameter(
                    common.PERMESSAGE_DEFLATE_EXTENSION)
            # Accept the client_max_window_bits extension parameter by default.
            extension.add_parameter(
                    PerMessageDeflateExtensionProcessor.
                            _CLIENT_MAX_WINDOW_BITS_PARAM,
                    None)
            extensions_to_request.append(extension)

        if len(extensions_to_request) != 0:
            fields.append(
                '%s: %s\r\n' %
                (common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
                 common.format_extensions(extensions_to_request)))

        for field in fields:
            self._socket.sendall(field)

        self._socket.sendall('\r\n')

        self._logger.debug('Sent client\'s opening handshake headers: %r',
                           fields)
        self._logger.debug('Start reading Status-Line')

        status_line = ''
        while True:
            ch = _receive_bytes(self._socket, 1)
            status_line += ch
            if ch == '\n':
                break

        m = re.match('HTTP/\\d+\.\\d+ (\\d\\d\\d) .*\r\n', status_line)
        if m is None:
            raise ClientHandshakeError(
                'Wrong status line format: %r' % status_line)
        status_code = m.group(1)
        if status_code != '101':
            self._logger.debug('Unexpected status code %s with following '
                               'headers: %r', status_code, self._read_fields())
            raise ClientHandshakeError(
                'Expected HTTP status code 101 but found %r' % status_code)

        self._logger.debug('Received valid Status-Line')
        self._logger.debug('Start reading headers until we see an empty line')

        fields = self._read_fields()

        ch = _receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise ClientHandshakeError(
                'Expected LF but found %r while reading value %r for header '
                'name %r' % (ch, value, name))

        self._logger.debug('Received an empty line')
        self._logger.debug('Server\'s opening handshake headers: %r', fields)

        _validate_mandatory_header(
            fields,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE,
            False)

        _validate_mandatory_header(
            fields,
            common.CONNECTION_HEADER,
            common.UPGRADE_CONNECTION_TYPE,
            False)

        accept = _get_mandatory_header(
            fields, common.SEC_WEBSOCKET_ACCEPT_HEADER)

        # Validate
        try:
            binary_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeError(
                'Illegal value for header %s: %r' %
                (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))
Example #22
0
    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        request_line = _build_method_line(self._options.resource)
        self._logger.debug("Client's opening handshake Request-Line: %r", request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_format_host_header(self._options.server_host, self._options.server_port, self._options.use_tls))
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)
        if self._options.origin is not None:
            if self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
                fields.append(_origin_header(common.SEC_WEBSOCKET_ORIGIN_HEADER, self._options.origin))
            else:
                fields.append(_origin_header(common.ORIGIN_HEADER, self._options.origin))

        original_key = os.urandom(16)
        self._key = base64.b64encode(original_key)
        self._logger.debug("%s: %r (%s)", common.SEC_WEBSOCKET_KEY_HEADER, self._key, util.hexify(original_key))
        fields.append("%s: %s\r\n" % (common.SEC_WEBSOCKET_KEY_HEADER, self._key))

        if self._options.version_header > 0:
            fields.append("%s: %d\r\n" % (common.SEC_WEBSOCKET_VERSION_HEADER, self._options.version_header))
        elif self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
            fields.append("%s: %d\r\n" % (common.SEC_WEBSOCKET_VERSION_HEADER, common.VERSION_HYBI08))
        else:
            fields.append("%s: %d\r\n" % (common.SEC_WEBSOCKET_VERSION_HEADER, common.VERSION_HYBI_LATEST))

        extensions_to_request = []

        if self._options.deflate_stream:
            extensions_to_request.append(common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION))

        if self._options.deflate_frame:
            extensions_to_request.append(common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION))

        if len(extensions_to_request) != 0:
            fields.append(
                "%s: %s\r\n" % (common.SEC_WEBSOCKET_EXTENSIONS_HEADER, format_extensions(extensions_to_request))
            )

        for field in fields:
            self._socket.sendall(field)

        self._socket.sendall("\r\n")

        self._logger.debug("Sent client's opening handshake headers: %r", fields)
        self._logger.debug("Start reading Status-Line")

        status_line = ""
        while True:
            ch = _receive_bytes(self._socket, 1)
            status_line += ch
            if ch == "\n":
                break

        m = re.match("HTTP/\\d+\.\\d+ (\\d\\d\\d) .*\r\n", status_line)
        if m is None:
            raise ClientHandshakeError("Wrong status line format: %r" % status_line)
        status_code = m.group(1)
        if status_code != "101":
            self._logger.debug(
                "Unexpected status code %s with following " "headers: %r", status_code, self._read_fields()
            )
            raise ClientHandshakeError("Expected HTTP status code 101 but found %r" % status_code)

        self._logger.debug("Received valid Status-Line")
        self._logger.debug("Start reading headers until we see an empty line")

        fields = self._read_fields()

        ch = _receive_bytes(self._socket, 1)
        if ch != "\n":  # 0x0A
            raise ClientHandshakeError(
                "Expected LF but found %r while reading value %r for header " "name %r" % (ch, value, name)
            )

        self._logger.debug("Received an empty line")
        self._logger.debug("Server's opening handshake headers: %r", fields)

        _validate_mandatory_header(fields, common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE, False)

        _validate_mandatory_header(fields, common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE, False)

        accept = _get_mandatory_header(fields, common.SEC_WEBSOCKET_ACCEPT_HEADER)

        # Validate
        try:
            binary_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeError("Illegal value for header %s: %r" % (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))
Example #23
0
class ClientHandshakeProcessor(ClientHandshakeBase):
    """WebSocket opening handshake processor for
    draft-ietf-hybi-thewebsocketprotocol-06 and later.
    """

    def __init__(self, socket, options):
        super(ClientHandshakeProcessor, self).__init__()

        self._socket = socket
        self._options = options

        self._logger = util.get_class_logger(self)

    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        request_line = _build_method_line(self._options.resource)
        self._logger.debug('Client\'s opening handshake Request-Line: %r',
                           request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)
        if self._options.origin is not None:
            if self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
                fields.append(_origin_header(
                    common.SEC_WEBSOCKET_ORIGIN_HEADER,
                    self._options.origin))
            else:
                fields.append(_origin_header(common.ORIGIN_HEADER,
                                             self._options.origin))

        original_key = os.urandom(16)
        self._key = base64.b64encode(original_key)
        self._logger.debug(
            '%s: %r (%s)',
            common.SEC_WEBSOCKET_KEY_HEADER,
            self._key,
            util.hexify(original_key))
        fields.append(
            '%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY_HEADER, self._key))

        if self._options.version_header > 0:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          self._options.version_header))
        elif self._options.protocol_version == _PROTOCOL_VERSION_HYBI08:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          common.VERSION_HYBI08))
        else:
            fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER,
                                          common.VERSION_HYBI_LATEST))

        extensions_to_request = []

        if self._options.deflate_frame:
            extensions_to_request.append(
                common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION))

        if self._options.use_permessage_deflate:
            extension = common.ExtensionParameter(
                    common.PERMESSAGE_DEFLATE_EXTENSION)
            # Accept the client_max_window_bits extension parameter by default.
            extension.add_parameter(
                    PerMessageDeflateExtensionProcessor.
                            _CLIENT_MAX_WINDOW_BITS_PARAM,
                    None)
            extensions_to_request.append(extension)

        if len(extensions_to_request) != 0:
            fields.append(
                '%s: %s\r\n' %
                (common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
                 common.format_extensions(extensions_to_request)))

        for field in fields:
            self._socket.sendall(field)

        self._socket.sendall('\r\n')

        self._logger.debug('Sent client\'s opening handshake headers: %r',
                           fields)
        self._logger.debug('Start reading Status-Line')

        status_line = ''
        while True:
            ch = _receive_bytes(self._socket, 1)
            status_line += ch
            if ch == '\n':
                break

        m = re.match('HTTP/\\d+\.\\d+ (\\d\\d\\d) .*\r\n', status_line)
        if m is None:
            raise ClientHandshakeError(
                'Wrong status line format: %r' % status_line)
        status_code = m.group(1)
        if status_code != '101':
            self._logger.debug('Unexpected status code %s with following '
                               'headers: %r', status_code, self._read_fields())
            raise ClientHandshakeError(
                'Expected HTTP status code 101 but found %r' % status_code)

        self._logger.debug('Received valid Status-Line')
        self._logger.debug('Start reading headers until we see an empty line')

        fields = self._read_fields()

        ch = _receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise ClientHandshakeError(
                'Expected LF but found %r while reading value %r for header '
                'name %r' % (ch, value, name))

        self._logger.debug('Received an empty line')
        self._logger.debug('Server\'s opening handshake headers: %r', fields)

        _validate_mandatory_header(
            fields,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE,
            False)

        _validate_mandatory_header(
            fields,
            common.CONNECTION_HEADER,
            common.UPGRADE_CONNECTION_TYPE,
            False)

        accept = _get_mandatory_header(
            fields, common.SEC_WEBSOCKET_ACCEPT_HEADER)

        # Validate
        try:
            binary_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeError(
                'Illegal value for header %s: %r' %
                (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))

        if len(binary_accept) != 20:
            raise ClientHandshakeError(
                'Decoded value of %s is not 20-byte long' %
                common.SEC_WEBSOCKET_ACCEPT_HEADER)

        self._logger.debug(
            'Response for challenge : %r (%s)',
            accept, util.hexify(binary_accept))

        binary_expected_accept = util.sha1_hash(
            self._key + common.WEBSOCKET_ACCEPT_UUID).digest()
        expected_accept = base64.b64encode(binary_expected_accept)

        self._logger.debug(
            'Expected response for challenge: %r (%s)',
            expected_accept, util.hexify(binary_expected_accept))

        if accept != expected_accept:
            raise ClientHandshakeError(
                'Invalid %s header: %r (expected: %s)' %
                (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept, expected_accept))

        deflate_frame_accepted = False
        permessage_deflate_accepted = False

        extensions_header = fields.get(
            common.SEC_WEBSOCKET_EXTENSIONS_HEADER.lower())
        accepted_extensions = []
        if extensions_header is not None and len(extensions_header) != 0:
            accepted_extensions = common.parse_extensions(extensions_header[0])

        # TODO(bashi): Support the new style perframe compression extension.
        for extension in accepted_extensions:
            extension_name = extension.name()
            if (extension_name == common.DEFLATE_FRAME_EXTENSION and
                self._options.deflate_frame):
                deflate_frame_accepted = True
                processor = DeflateFrameExtensionProcessor(extension)
                unused_extension_response = processor.get_extension_response()
                self._options.deflate_frame = processor
                continue
            elif (extension_name == common.PERMESSAGE_DEFLATE_EXTENSION and
                  self._options.use_permessage_deflate):
                permessage_deflate_accepted = True

                framer = _get_permessage_deflate_framer(extension)
                framer.set_compress_outgoing_enabled(True)
                self._options.use_permessage_deflate = framer
                continue

            raise ClientHandshakeError(
                'Unexpected extension %r' % extension_name)

        if (self._options.deflate_frame and not deflate_frame_accepted):
            raise ClientHandshakeError(
                'Requested %s, but the server rejected it' %
                common.DEFLATE_FRAME_EXTENSION)

        if (self._options.use_permessage_deflate and
            not permessage_deflate_accepted):
            raise ClientHandshakeError(
                    'Requested %s, but the server rejected it' %
                    common.PERMESSAGE_DEFLATE_EXTENSION)
Example #24
0
    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        # 4.1 5. send request line.
        self._socket.sendall(_build_method_line(self._options.resource))
        # 4.1 6. Let /fields/ be an empty list of strings.
        fields = []
        # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/.
        fields.append(_UPGRADE_HEADER_HIXIE75)
        # 4.1 8. Add the string "Connection: Upgrade" to /fields/.
        fields.append(_CONNECTION_HEADER)
        # 4.1 9-12. Add Host: field to /fields/.
        fields.append(_format_host_header(self._options.server_host, self._options.server_port, self._options.use_tls))
        # 4.1 13. Add Origin: field to /fields/.
        if not self._options.origin:
            raise ClientHandshakeError("Specify the origin of the connection by --origin flag")
        fields.append(_origin_header(common.ORIGIN_HEADER, self._options.origin))
        # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/.
        # TODO: 4.1 15 Add cookie headers to /fields/.

        # 4.1 16-23. Add Sec-WebSocket-Key<n> to /fields/.
        self._number1, key1 = self._generate_sec_websocket_key()
        self._logger.debug("Number1: %d", self._number1)
        fields.append("%s: %s\r\n" % (common.SEC_WEBSOCKET_KEY1_HEADER, key1))
        self._number2, key2 = self._generate_sec_websocket_key()
        self._logger.debug("Number2: %d", self._number2)
        fields.append("%s: %s\r\n" % (common.SEC_WEBSOCKET_KEY2_HEADER, key2))

        fields.append("%s: 0\r\n" % common.SEC_WEBSOCKET_DRAFT_HEADER)

        # 4.1 24. For each string in /fields/, in a random order: send the
        # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE
        # RETURN U+000A LINE FEED character pair (CRLF).
        random.shuffle(fields)
        for field in fields:
            self._socket.sendall(field)
        # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED
        # character pair (CRLF).
        self._socket.sendall("\r\n")
        # 4.1 26. let /key3/ be a string consisting of eight random bytes (or
        # equivalently, a random 64 bit integer encoded in a big-endian order).
        self._key3 = self._generate_key3()
        # 4.1 27. send /key3/ to the server.
        self._socket.sendall(self._key3)
        self._logger.debug("Key3: %r (%s)", self._key3, util.hexify(self._key3))

        self._logger.info("Sent handshake")

        # 4.1 28. Read bytes from the server until either the connection
        # closes, or a 0x0A byte is read. let /field/ be these bytes, including
        # the 0x0A bytes.
        field = ""
        while True:
            ch = _receive_bytes(self._socket, 1)
            field += ch
            if ch == "\n":
                break
        # if /field/ is not at least seven bytes long, or if the last
        # two bytes aren't 0x0D and 0x0A respectively, or if it does not
        # contain at least two 0x20 bytes, then fail the WebSocket connection
        # and abort these steps.
        if len(field) < 7 or not field.endswith("\r\n"):
            raise ClientHandshakeError("Wrong status line: %r" % field)
        m = re.match("[^ ]* ([^ ]*) .*", field)
        if m is None:
            raise ClientHandshakeError("No HTTP status code found in status line: %r" % field)
        # 4.1 29. let /code/ be the substring of /field/ that starts from the
        # byte after the first 0x20 byte, and ends with the byte before the
        # second 0x20 byte.
        code = m.group(1)
        # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in
        # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket
        # connection and abort these steps.
        if not re.match("[0-9][0-9][0-9]", code):
            raise ClientHandshakeError("HTTP status code %r is not three digit in status line: %r" % (code, field))
        # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the
        # next step.
        if code != "101":
            raise ClientHandshakeError(
                "Expected HTTP status code 101 but found %r in status line: " "%r" % (code, field)
            )
        # 4.1 32-39. read fields into /fields/
        fields = self._read_fields()
        # 4.1 40. _Fields processing_
        # read a byte from server
        ch = _receive_bytes(self._socket, 1)
        if ch != "\n":  # 0x0A
            raise ClientHandshakeError("Expected LF but found %r" % ch)
        # 4.1 41. check /fields/
        # TODO(ukai): protocol
        # if the entry's name is "upgrade"
        #  if the value is not exactly equal to the string "WebSocket",
        #  then fail the WebSocket connection and abort these steps.
        _validate_mandatory_header(fields, common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75, True)
        # if the entry's name is "connection"
        #  if the value, converted to ASCII lowercase, is not exactly equal
        #  to the string "upgrade", then fail the WebSocket connection and
        #  abort these steps.
        _validate_mandatory_header(fields, common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE, False)

        origin = _get_mandatory_header(fields, common.SEC_WEBSOCKET_ORIGIN_HEADER)

        location = _get_mandatory_header(fields, common.SEC_WEBSOCKET_LOCATION_HEADER)

        # TODO(ukai): check origin, location, cookie, ..

        # 4.1 42. let /challenge/ be the concatenation of /number_1/,
        # expressed as a big endian 32 bit integer, /number_2/, expressed
        # as big endian 32 bit integer, and the eight bytes of /key_3/ in the
        # order they were sent on the wire.
        challenge = struct.pack("!I", self._number1)
        challenge += struct.pack("!I", self._number2)
        challenge += self._key3

        self._logger.debug("Challenge: %r (%s)", challenge, util.hexify(challenge))

        # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a
        # big-endian 128 bit string.
        expected = util.md5_hash(challenge).digest()
        self._logger.debug("Expected challenge response: %r (%s)", expected, util.hexify(expected))

        # 4.1 44. read sixteen bytes from the server.
        # let /reply/ be those bytes.
        reply = _receive_bytes(self._socket, 16)
        self._logger.debug("Actual challenge response: %r (%s)", reply, util.hexify(reply))

        # 4.1 45. if /reply/ does not exactly equal /expected/, then fail
        # the WebSocket connection and abort these steps.
        if expected != reply:
            raise ClientHandshakeError("Bad challenge response: %r (expected) != %r (actual)" % (expected, reply))
Example #25
0
    def handshake(self):
        """Performs opening handshake on the specified socket.

        Raises:
            ClientHandshakeError: handshake failed.
        """

        # 4.1 5. send request line.
        self._socket.sendall(_build_method_line(self._options.resource))
        # 4.1 6. Let /fields/ be an empty list of strings.
        fields = []
        # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/.
        fields.append(_UPGRADE_HEADER_HIXIE75)
        # 4.1 8. Add the string "Connection: Upgrade" to /fields/.
        fields.append(_CONNECTION_HEADER)
        # 4.1 9-12. Add Host: field to /fields/.
        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))
        # 4.1 13. Add Origin: field to /fields/.
        if not self._options.origin:
            raise ClientHandshakeError(
                'Specify the origin of the connection by --origin flag')
        fields.append(_origin_header(common.ORIGIN_HEADER,
                                     self._options.origin))
        # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/.
        # TODO: 4.1 15 Add cookie headers to /fields/.

        # 4.1 16-23. Add Sec-WebSocket-Key<n> to /fields/.
        self._number1, key1 = self._generate_sec_websocket_key()
        self._logger.debug('Number1: %d', self._number1)
        fields.append('%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY1_HEADER, key1))
        self._number2, key2 = self._generate_sec_websocket_key()
        self._logger.debug('Number2: %d', self._number2)
        fields.append('%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY2_HEADER, key2))

        fields.append('%s: 0\r\n' % common.SEC_WEBSOCKET_DRAFT_HEADER)

        # 4.1 24. For each string in /fields/, in a random order: send the
        # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE
        # RETURN U+000A LINE FEED character pair (CRLF).
        random.shuffle(fields)
        for field in fields:
            self._socket.sendall(field)
        # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED
        # character pair (CRLF).
        self._socket.sendall('\r\n')
        # 4.1 26. let /key3/ be a string consisting of eight random bytes (or
        # equivalently, a random 64 bit integer encoded in a big-endian order).
        self._key3 = self._generate_key3()
        # 4.1 27. send /key3/ to the server.
        self._socket.sendall(self._key3)
        self._logger.debug(
            'Key3: %r (%s)', self._key3, util.hexify(self._key3))

        self._logger.info('Sent handshake')

        # 4.1 28. Read bytes from the server until either the connection
        # closes, or a 0x0A byte is read. let /field/ be these bytes, including
        # the 0x0A bytes.
        field = ''
        while True:
            ch = _receive_bytes(self._socket, 1)
            field += ch
            if ch == '\n':
                break
        # if /field/ is not at least seven bytes long, or if the last
        # two bytes aren't 0x0D and 0x0A respectively, or if it does not
        # contain at least two 0x20 bytes, then fail the WebSocket connection
        # and abort these steps.
        if len(field) < 7 or not field.endswith('\r\n'):
            raise ClientHandshakeError('Wrong status line: %r' % field)
        m = re.match('[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise ClientHandshakeError(
                'No HTTP status code found in status line: %r' % field)
        # 4.1 29. let /code/ be the substring of /field/ that starts from the
        # byte after the first 0x20 byte, and ends with the byte before the
        # second 0x20 byte.
        code = m.group(1)
        # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in
        # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket
        # connection and abort these steps.
        if not re.match('[0-9][0-9][0-9]', code):
            raise ClientHandshakeError(
                'HTTP status code %r is not three digit in status line: %r' %
                (code, field))
        # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the
        # next step.
        if code != '101':
            raise ClientHandshakeError(
                'Expected HTTP status code 101 but found %r in status line: '
                '%r' % (code, field))
        # 4.1 32-39. read fields into /fields/
        fields = self._read_fields()
        # 4.1 40. _Fields processing_
        # read a byte from server
        ch = _receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise ClientHandshakeError('Expected LF but found %r' % ch)
        # 4.1 41. check /fields/
        # TODO(ukai): protocol
        # if the entry's name is "upgrade"
        #  if the value is not exactly equal to the string "WebSocket",
        #  then fail the WebSocket connection and abort these steps.
        _validate_mandatory_header(
            fields,
            common.UPGRADE_HEADER,
            common.WEBSOCKET_UPGRADE_TYPE_HIXIE75,
            True)
        # if the entry's name is "connection"
        #  if the value, converted to ASCII lowercase, is not exactly equal
        #  to the string "upgrade", then fail the WebSocket connection and
        #  abort these steps.
        _validate_mandatory_header(
            fields,
            common.CONNECTION_HEADER,
            common.UPGRADE_CONNECTION_TYPE,
            False)

        origin = _get_mandatory_header(
            fields, common.SEC_WEBSOCKET_ORIGIN_HEADER)

        location = _get_mandatory_header(
            fields, common.SEC_WEBSOCKET_LOCATION_HEADER)

        # TODO(ukai): check origin, location, cookie, ..

        # 4.1 42. let /challenge/ be the concatenation of /number_1/,
        # expressed as a big endian 32 bit integer, /number_2/, expressed
        # as big endian 32 bit integer, and the eight bytes of /key_3/ in the
        # order they were sent on the wire.
        challenge = struct.pack('!I', self._number1)
        challenge += struct.pack('!I', self._number2)
        challenge += self._key3

        self._logger.debug(
            'Challenge: %r (%s)', challenge, util.hexify(challenge))

        # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a
        # big-endian 128 bit string.
        expected = util.md5_hash(challenge).digest()
        self._logger.debug(
            'Expected challenge response: %r (%s)',
            expected, util.hexify(expected))

        # 4.1 44. read sixteen bytes from the server.
        # let /reply/ be those bytes.
        reply = _receive_bytes(self._socket, 16)
        self._logger.debug(
            'Actual challenge response: %r (%s)', reply, util.hexify(reply))

        # 4.1 45. if /reply/ does not exactly equal /expected/, then fail
        # the WebSocket connection and abort these steps.
        if expected != reply:
            raise ClientHandshakeError(
                'Bad challenge response: %r (expected) != %r (actual)' %
                (expected, reply))
    def handshake(self, socket):
        """Handshake WebSocket.

        Raises:
            Exception: handshake failed.
        """

        self._socket = socket

        request_line = _method_line(self._options.resource)
        self._logger.debug('Opening handshake Request-Line: %r', request_line)
        self._socket.sendall(request_line.encode('UTF-8'))

        fields = []
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)

        fields.append(
            _format_host_header(self._options.server_host,
                                self._options.server_port,
                                self._options.use_tls))

        if self._options.version is 8:
            fields.append(_sec_origin_header(self._options.origin))
        else:
            fields.append(_origin_header(self._options.origin))

        original_key = os.urandom(16)
        key = base64.b64encode(original_key)
        self._logger.debug('Sec-WebSocket-Key: %s (%s)', key,
                           util.hexify(original_key))
        fields.append(u'Sec-WebSocket-Key: %s\r\n' % key.decode('UTF-8'))

        fields.append(u'Sec-WebSocket-Version: %d\r\n' % self._options.version)

        # Setting up extensions.
        if len(self._options.extensions) > 0:
            fields.append(u'Sec-WebSocket-Extensions: %s\r\n' %
                          ', '.join(self._options.extensions))

        self._logger.debug('Opening handshake request headers: %r', fields)

        for field in fields:
            self._socket.sendall(field.encode('UTF-8'))
        self._socket.sendall(b'\r\n')

        self._logger.info('Sent opening handshake request')

        field = b''
        while True:
            ch = receive_bytes(self._socket, 1)
            field += ch
            if ch == b'\n':
                break

        self._logger.debug('Opening handshake Response-Line: %r', field)

        # Will raise a UnicodeDecodeError when the decode fails
        if len(field) < 7 or not field.endswith(b'\r\n'):
            raise Exception('Wrong status line: %s' % field.decode('Latin-1'))
        m = re.match(b'[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise Exception('No HTTP status code found in status line: %s' %
                            field.decode('Latin-1'))
        code = m.group(1)
        if not re.match(b'[0-9][0-9][0-9]$', code):
            raise Exception(
                'HTTP status code %s is not three digit in status line: %s' %
                (code.decode('Latin-1'), field.decode('Latin-1')))
        if code != b'101':
            raise HttpStatusException(
                'Expected HTTP status code 101 but found %s in status line: '
                '%r' % (code.decode('Latin-1'), field.decode('Latin-1')),
                int(code))
        fields = _read_fields(self._socket)
        ch = receive_bytes(self._socket, 1)
        if ch != b'\n':  # 0x0A
            raise Exception('Expected LF but found: %r' % ch)

        self._logger.debug('Opening handshake response headers: %r', fields)

        # Check /fields/
        if len(fields['upgrade']) != 1:
            raise Exception('Multiple Upgrade headers found: %s' %
                            fields['upgrade'])
        if len(fields['connection']) != 1:
            raise Exception('Multiple Connection headers found: %s' %
                            fields['connection'])
        if fields['upgrade'][0] != 'websocket':
            raise Exception('Unexpected Upgrade header value: %s' %
                            fields['upgrade'][0])
        if fields['connection'][0].lower() != 'upgrade':
            raise Exception('Unexpected Connection header value: %s' %
                            fields['connection'][0])

        if len(fields['sec-websocket-accept']) != 1:
            raise Exception('Multiple Sec-WebSocket-Accept headers found: %s' %
                            fields['sec-websocket-accept'])

        accept = fields['sec-websocket-accept'][0]

        # Validate
        try:
            decoded_accept = base64.b64decode(accept)
        except TypeError as e:
            raise HandshakeException(
                'Illegal value for header Sec-WebSocket-Accept: ' + accept)

        if len(decoded_accept) != 20:
            raise HandshakeException(
                'Decoded value of Sec-WebSocket-Accept is not 20-byte long')

        self._logger.debug('Actual Sec-WebSocket-Accept: %r (%s)', accept,
                           util.hexify(decoded_accept))

        original_expected_accept = sha1(key + WEBSOCKET_ACCEPT_UUID).digest()
        expected_accept = base64.b64encode(original_expected_accept)

        self._logger.debug('Expected Sec-WebSocket-Accept: %r (%s)',
                           expected_accept,
                           util.hexify(original_expected_accept))

        if accept != expected_accept.decode('UTF-8'):
            raise Exception(
                'Invalid Sec-WebSocket-Accept header: %r (expected) != %r '
                '(actual)' % (accept, expected_accept))

        server_extensions_header = fields.get('sec-websocket-extensions')
        accepted_extensions = []
        if server_extensions_header is not None:
            accepted_extensions = common.parse_extensions(
                ', '.join(server_extensions_header))

        # Scan accepted extension list to check if there is any unrecognized
        # extensions or extensions we didn't request in it. Then, for
        # extensions we request, parse them and store parameters. They will be
        # used later by each extension.
        for extension in accepted_extensions:
            if extension.name() == _PERMESSAGE_DEFLATE_EXTENSION:
                checker = self._options.check_permessage_deflate
                if checker:
                    checker(extension)
                    continue

            raise Exception('Received unrecognized extension: %s' %
                            extension.name())
Example #27
0
    def handshake(self, socket):
        """Handshake WebSocket.

        Raises:
            Exception: handshake failed.
        """

        self._socket = socket

        # 4.1 5. send request line.
        request_line = _method_line(self._options.resource)
        self._logger.debug('Opening handshake Request-Line: %r', request_line)
        self._socket.sendall(request_line)
        # 4.1 6. Let /fields/ be an empty list of strings.
        fields = []
        # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/.
        fields.append(_UPGRADE_HEADER_HIXIE75)
        # 4.1 8. Add the string "Connection: Upgrade" to /fields/.
        fields.append(_CONNECTION_HEADER)
        # 4.1 9-12. Add Host: field to /fields/.
        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))
        # 4.1 13. Add Origin: field to /fields/.
        fields.append(_origin_header(self._options.origin))
        # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/.
        # TODO: 4.1 15 Add cookie headers to /fields/.

        # 4.1 16-23. Add Sec-WebSocket-Key<n> to /fields/.
        self._number1, key1 = self._generate_sec_websocket_key()
        self._logger.debug('Number1: %d', self._number1)
        fields.append('Sec-WebSocket-Key1: %s\r\n' % key1)
        self._number2, key2 = self._generate_sec_websocket_key()
        self._logger.debug('Number2: %d', self._number1)
        fields.append('Sec-WebSocket-Key2: %s\r\n' % key2)

        fields.append('Sec-WebSocket-Draft: %s\r\n' % self._draft_field)

        # 4.1 24. For each string in /fields/, in a random order: send the
        # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE
        # RETURN U+000A LINE FEED character pair (CRLF).
        random.shuffle(fields)

        self._logger.debug('Opening handshake request headers: %r', fields)
        for field in fields:
            self._socket.sendall(field)

        # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED
        # character pair (CRLF).
        self._socket.sendall('\r\n')
        # 4.1 26. let /key3/ be a string consisting of eight random bytes (or
        # equivalently, a random 64 bit integer encoded in a big-endian order).
        self._key3 = self._generate_key3()
        # 4.1 27. send /key3/ to the server.
        self._socket.sendall(self._key3)
        self._logger.debug(
            'Key3: %r (%s)', self._key3, util.hexify(self._key3))

        self._logger.info('Sent opening handshake request')

        # 4.1 28. Read bytes from the server until either the connection
        # closes, or a 0x0A byte is read. let /field/ be these bytes, including
        # the 0x0A bytes.
        field = ''
        while True:
            ch = receive_bytes(self._socket, 1)
            field += ch
            if ch == '\n':
                break

        self._logger.debug('Opening handshake Response-Line: %r', field)

        # if /field/ is not at least seven bytes long, or if the last
        # two bytes aren't 0x0D and 0x0A respectively, or if it does not
        # contain at least two 0x20 bytes, then fail the WebSocket connection
        # and abort these steps.
        if len(field) < 7 or not field.endswith('\r\n'):
            raise Exception('Wrong status line: %r' % field)
        m = re.match('[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise Exception('No code found in status line: %r' % field)
        # 4.1 29. let /code/ be the substring of /field/ that starts from the
        # byte after the first 0x20 byte, and ends with the byte before the
        # second 0x20 byte.
        code = m.group(1)
        # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in
        # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket
        # connection and abort these steps.
        if not re.match('[0-9][0-9][0-9]', code):
            raise Exception(
                'HTTP status code %r is not three digit in status line: %r' %
                (code, field))
        # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the
        # next step.
        if code != '101':
            raise HttpStatusException(
                'Expected HTTP status code 101 but found %r in status line: '
                '%r' % (code, field), int(code))
        # 4.1 32-39. read fields into /fields/
        fields = _read_fields(self._socket)

        self._logger.debug('Opening handshake response headers: %r', fields)

        # 4.1 40. _Fields processing_
        # read a byte from server
        ch = receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise Exception('Expected LF but found %r' % ch)
        # 4.1 41. check /fields/
        if len(fields['upgrade']) != 1:
            raise Exception(
                'Multiple Upgrade headers found: %s' % fields['upgrade'])
        if len(fields['connection']) != 1:
            raise Exception(
                'Multiple Connection headers found: %s' % fields['connection'])
        if len(fields['sec-websocket-origin']) != 1:
            raise Exception(
                'Multiple Sec-WebSocket-Origin headers found: %s' %
                fields['sec-sebsocket-origin'])
        if len(fields['sec-websocket-location']) != 1:
            raise Exception(
                'Multiple Sec-WebSocket-Location headers found: %s' %
                fields['sec-sebsocket-location'])
        # TODO(ukai): protocol
        # if the entry's name is "upgrade"
        #  if the value is not exactly equal to the string "WebSocket",
        #  then fail the WebSocket connection and abort these steps.
        if fields['upgrade'][0] != 'WebSocket':
            raise Exception(
                'Unexpected Upgrade header value: %s' % fields['upgrade'][0])
        # if the entry's name is "connection"
        #  if the value, converted to ASCII lowercase, is not exactly equal
        #  to the string "upgrade", then fail the WebSocket connection and
        #  abort these steps.
        if fields['connection'][0].lower() != 'upgrade':
            raise Exception(
                'Unexpected Connection header value: %s' %
                fields['connection'][0])
        # TODO(ukai): check origin, location, cookie, ..

        # 4.1 42. let /challenge/ be the concatenation of /number_1/,
        # expressed as a big endian 32 bit integer, /number_2/, expressed
        # as big endian 32 bit integer, and the eight bytes of /key_3/ in the
        # order they were sent on the wire.
        challenge = struct.pack('!I', self._number1)
        challenge += struct.pack('!I', self._number2)
        challenge += self._key3

        self._logger.debug(
            'Challenge: %r (%s)', challenge, util.hexify(challenge))

        # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a
        # big-endian 128 bit string.
        expected = util.md5_hash(challenge).digest()
        self._logger.debug(
            'Expected challenge response: %r (%s)',
            expected, util.hexify(expected))

        # 4.1 44. read sixteen bytes from the server.
        # let /reply/ be those bytes.
        reply = receive_bytes(self._socket, 16)
        self._logger.debug(
            'Actual challenge response: %r (%s)', reply, util.hexify(reply))

        # 4.1 45. if /reply/ does not exactly equal /expected/, then fail
        # the WebSocket connection and abort these steps.
        if expected != reply:
            raise Exception(
                'Bad challenge response: %r (expected) != %r (actual)' %
                (expected, reply))
class WebSocketHandshake(object):
    """Opening handshake processor for the WebSocket protocol (RFC 6455)."""

    def __init__(self, options):
        self._logger = util.get_class_logger(self)

        self._options = options

    def handshake(self, socket):
        """Handshake WebSocket.

        Raises:
            Exception: handshake failed.
        """

        self._socket = socket

        request_line = _method_line(self._options.resource)
        self._logger.debug('Opening handshake Request-Line: %r', request_line)
        self._socket.sendall(request_line)

        fields = []
        fields.append(_UPGRADE_HEADER)
        fields.append(_CONNECTION_HEADER)

        fields.append(_format_host_header(
            self._options.server_host,
            self._options.server_port,
            self._options.use_tls))

        if self._options.version is 8:
            fields.append(_sec_origin_header(self._options.origin))
        else:
            fields.append(_origin_header(self._options.origin))

        original_key = os.urandom(16)
        key = base64.b64encode(original_key)
        self._logger.debug(
            'Sec-WebSocket-Key: %s (%s)', key, util.hexify(original_key))
        fields.append('Sec-WebSocket-Key: %s\r\n' % key)

        fields.append('Sec-WebSocket-Version: %d\r\n' % self._options.version)

        # Setting up extensions.
        if len(self._options.extensions) > 0:
            fields.append('Sec-WebSocket-Extensions: %s\r\n' %
                          ', '.join(self._options.extensions))

        self._logger.debug('Opening handshake request headers: %r', fields)

        for field in fields:
            self._socket.sendall(field)
        self._socket.sendall('\r\n')

        self._logger.info('Sent opening handshake request')

        field = ''
        while True:
            ch = receive_bytes(self._socket, 1)
            field += ch
            if ch == '\n':
                break

        self._logger.debug('Opening handshake Response-Line: %r', field)

        if len(field) < 7 or not field.endswith('\r\n'):
            raise Exception('Wrong status line: %r' % field)
        m = re.match('[^ ]* ([^ ]*) .*', field)
        if m is None:
            raise Exception(
                'No HTTP status code found in status line: %r' % field)
        code = m.group(1)
        if not re.match('[0-9][0-9][0-9]', code):
            raise Exception(
                'HTTP status code %r is not three digit in status line: %r' %
                (code, field))
        if code != '101':
            raise HttpStatusException(
                'Expected HTTP status code 101 but found %r in status line: '
                '%r' % (code, field), int(code))
        fields = _read_fields(self._socket)
        ch = receive_bytes(self._socket, 1)
        if ch != '\n':  # 0x0A
            raise Exception('Expected LF but found: %r' % ch)

        self._logger.debug('Opening handshake response headers: %r', fields)

        # Check /fields/
        if len(fields['upgrade']) != 1:
            raise Exception(
                'Multiple Upgrade headers found: %s' % fields['upgrade'])
        if len(fields['connection']) != 1:
            raise Exception(
                'Multiple Connection headers found: %s' % fields['connection'])
        if fields['upgrade'][0] != 'websocket':
            raise Exception(
                'Unexpected Upgrade header value: %s' % fields['upgrade'][0])
        if fields['connection'][0].lower() != 'upgrade':
            raise Exception(
                'Unexpected Connection header value: %s' %
                fields['connection'][0])

        if len(fields['sec-websocket-accept']) != 1:
            raise Exception(
                'Multiple Sec-WebSocket-Accept headers found: %s' %
                fields['sec-websocket-accept'])

        accept = fields['sec-websocket-accept'][0]

        # Validate
        try:
            decoded_accept = base64.b64decode(accept)
        except TypeError, e:
            raise HandshakeException(
                'Illegal value for header Sec-WebSocket-Accept: ' + accept)

        if len(decoded_accept) != 20:
            raise HandshakeException(
                'Decoded value of Sec-WebSocket-Accept is not 20-byte long')

        self._logger.debug('Actual Sec-WebSocket-Accept: %r (%s)',
                           accept, util.hexify(decoded_accept))

        original_expected_accept = util.sha1_hash(
            key + WEBSOCKET_ACCEPT_UUID).digest()
        expected_accept = base64.b64encode(original_expected_accept)

        self._logger.debug('Expected Sec-WebSocket-Accept: %r (%s)',
                           expected_accept,
                           util.hexify(original_expected_accept))

        if accept != expected_accept:
            raise Exception(
                'Invalid Sec-WebSocket-Accept header: %r (expected) != %r '
                '(actual)' % (accept, expected_accept))

        server_extensions_header = fields.get('sec-websocket-extensions')
        if (server_extensions_header is None or
            len(server_extensions_header) != 1):
            accepted_extensions = []
        else:
            accepted_extensions = server_extensions_header[0].split(',')
            # TODO(tyoshino): Follow the ABNF in the spec.
            accepted_extensions = [s.strip() for s in accepted_extensions]

        # Scan accepted extension list to check if there is any unrecognized
        # extensions or extensions we didn't request in it. Then, for
        # extensions we request, parse them and store parameters. They will be
        # used later by each extension.
        deflate_stream_accepted = False
        deflate_frame_accepted = False
        mux_accepted = False
        for extension in accepted_extensions:
            if extension == '':
                continue
            if extension == _DEFLATE_STREAM_EXTENSION:
                if self._options.use_deflate_stream:
                    deflate_stream_accepted = True
                    continue
            if extension == _DEFLATE_FRAME_EXTENSION:
                if self._options.use_deflate_frame:
                    deflate_frame_accepted = True
                    continue
            if extension == _MUX_EXTENSION:
                if self._options.use_mux:
                    mux_accepted = True
                    continue

            raise Exception(
                'Received unrecognized extension: %s' % extension)

        # Let all extensions check the response for extension request.

        if self._options.use_deflate_stream and not deflate_stream_accepted:
            raise Exception('%s extension not accepted' %
                            _DEFLATE_STREAM_EXTENSION)

        if (self._options.use_deflate_frame and
            not deflate_frame_accepted):
            raise Exception('%s extension not accepted' %
                            _DEFLATE_FRAME_EXTENSION)

        if self._options.use_mux and not mux_accepted:
            raise Exception('%s extension not accepted' % _MUX_EXTENSION)