def test_requests_have_host_header(self): """ The tado internet bridge will fail if a secure session request doesn't have a Host header. https://github.com/home-assistant/home-assistant/issues/16971 https://github.com/jlusiardi/homekit_python/pull/130 """ session = mock.Mock() session.pairing_data = { 'AccessoryIP': '192.168.1.2', 'AccessoryPort': 8080, } secure_http = SecureHttp(session) with mock.patch.object(secure_http, '_handle_request') as handle_req: secure_http.get('/characteristics') assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][ 0].decode() secure_http.post('/characteristics', b'') assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][ 0].decode() secure_http.put('/characteristics', b'') assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][ 0].decode()
class IpSession(object): def __init__(self, pairing_data): """ :param pairing_data: :raises AccessoryNotFoundError: if the device can not be found via zeroconf """ logging.debug('init session') connected = False if 'AccessoryIP' in pairing_data and 'AccessoryPort' in pairing_data: # if it is known, try it accessory_ip = pairing_data['AccessoryIP'] accessory_port = pairing_data['AccessoryPort'] conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port) try: conn.connect() write_fun = create_ip_pair_verify_write(conn) c2a_key, a2c_key = get_session_keys(conn, pairing_data, write_fun) connected = True except Exception: connected = False if not connected: # no connection yet, so ip / port might have changed and we need to fall back to slow zeroconf lookup device_id = pairing_data['AccessoryPairingID'] connection_data = find_device_ip_and_port(device_id) if connection_data is None: raise AccessoryNotFoundError('Device {id} not found'.format( id=pairing_data['AccessoryPairingID'])) conn = HomeKitHTTPConnection(connection_data['ip'], port=connection_data['port']) pairing_data['AccessoryIP'] = connection_data['ip'] pairing_data['AccessoryPort'] = connection_data['port'] write_fun = create_ip_pair_verify_write(conn) c2a_key, a2c_key = get_session_keys(conn, pairing_data, write_fun) logging.debug('session established') self.sock = conn.sock self.c2a_key = c2a_key self.a2c_key = a2c_key self.pairing_data = pairing_data self.sec_http = SecureHttp(self) def close(self): """ Close the session. This closes the socket. """ try: self.sock.close() except OSError: # If we get an OSError its probably because the socket is already closed pass self.sock = None def get_from_pairing_data(self, key): if key not in self.pairing_data: return None return self.pairing_data[key] def set_in_pairing_data(self, key, value): self.pairing_data[key] = value def get(self, url): """ Perform HTTP get via the encrypted session. :param url: The url to request :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.get(url) def put(self, url, body, content_type=HttpContentTypes.JSON): """ Perform HTTP put via the encrypted session. :param url: The url to request :param body: the body of the put request :param content_type: the content of the content-type header :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.put(url, body, content_type) def post(self, url, body, content_type=HttpContentTypes.JSON): """ Perform HTTP post via the encrypted session. :param url: The url to request :param body: the body of the post request :param content_type: the content of the content-type header :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.post(url, body, content_type)
class IpSession(object): def __init__(self, pairing_data): """ :param pairing_data: :raises AccessoryNotFoundError: if the device can not be found via zeroconf """ logging.debug('init session') connected = False self.pairing_data = pairing_data if 'AccessoryIP' in pairing_data and 'AccessoryPort' in pairing_data: # if it is known, try it accessory_ip = pairing_data['AccessoryIP'] accessory_port = pairing_data['AccessoryPort'] connected = self._connect(accessory_ip, accessory_port) if not connected: # no connection yet, so ip / port might have changed and we need to fall back to slow zeroconf lookup device_id = pairing_data['AccessoryPairingID'] connection_data = find_device_ip_and_port(device_id) # update pairing data with the IP/port we elaborated above, perhaps next time they are valid pairing_data['AccessoryIP'] = connection_data['ip'] pairing_data['AccessoryPort'] = connection_data['port'] if connection_data is None: raise AccessoryNotFoundError('Device {id} not found'.format( id=pairing_data['AccessoryPairingID'])) if not self._connect(connection_data['ip'], connection_data['port']): return logging.debug('session established') self.sec_http = SecureHttp(self) def _connect(self, accessory_ip, accessory_port): conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port) try: conn.connect() write_fun = create_ip_pair_verify_write(conn) state_machine = get_session_keys(self.pairing_data) request, expected = state_machine.send(None) while True: try: response = write_fun(request, expected) request, expected = state_machine.send(response) except StopIteration as result: self.c2a_key, self.a2c_key = result.value self.sock = conn.sock return True except OSError as e: logging.debug("Failed to connect to accessory: %s", e.strerror) except Exception: logging.exception("Failed to connect to accessory") return False def close(self): """ Close the session. This closes the socket. """ try: self.sock.close() except OSError: # If we get an OSError its probably because the socket is already closed pass self.sock = None def get_from_pairing_data(self, key): if key not in self.pairing_data: return None return self.pairing_data[key] def set_in_pairing_data(self, key, value): self.pairing_data[key] = value def get(self, url): """ Perform HTTP get via the encrypted session. :param url: The url to request :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.get(url) def put(self, url, body, content_type=HttpContentTypes.JSON): """ Perform HTTP put via the encrypted session. :param url: The url to request :param body: the body of the put request :param content_type: the content of the content-type header :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.put(url, body, content_type) def post(self, url, body, content_type=HttpContentTypes.JSON): """ Perform HTTP post via the encrypted session. :param url: The url to request :param body: the body of the post request :param content_type: the content of the content-type header :return: a homekit.http_impl.HttpResponse object """ return self.sec_http.post(url, body, content_type)