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) )
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))
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
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')
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 _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
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')
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'))
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
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))
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'))
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')
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
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)
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
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
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
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
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))
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))
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)
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): """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())
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)