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 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))
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))