def _get_deprecated_base_url( hass: HomeAssistant, *, internal: bool = False, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Work with the deprecated `base_url`, used as fallback.""" if hass.config.api is None or not hass.config.api.deprecated_base_url: raise NoURLAvailableError base_url = yarl.URL(hass.config.api.deprecated_base_url) # Rules that apply to both internal and external if ((allow_ip or not is_ip_address(str(base_url.host))) and (not require_current_request or base_url.host == _get_request_host()) and (not require_ssl or base_url.scheme == "https") and (not require_standard_port or base_url.is_default_port())): # Check to ensure an internal URL if internal and (str(base_url.host).endswith(".local") or (is_ip_address(str(base_url.host)) and not is_loopback(ip_address(base_url.host)) and is_private(ip_address(base_url.host)))): return normalize_url(str(base_url)) # Check to ensure an external URL (a little) if (not internal and not str(base_url.host).endswith(".local") and not (is_ip_address(str(base_url.host)) and is_local(ip_address(str(base_url.host))))): return normalize_url(str(base_url)) raise NoURLAvailableError
def _get_internal_url( hass: HomeAssistant, *, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get internal URL of this instance.""" if hass.config.internal_url: internal_url = yarl.URL(hass.config.internal_url) if ( (not require_current_request or internal_url.host == _get_request_host()) and (not require_ssl or internal_url.scheme == "https") and (not require_standard_port or internal_url.is_default_port()) and (allow_ip or not is_ip_address(str(internal_url.host))) ): return normalize_url(str(internal_url)) # Fallback to detected local IP if allow_ip and not ( require_ssl or hass.config.api is None or hass.config.api.use_ssl ): ip_url = yarl.URL.build( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) if ( not is_loopback(ip_address(ip_url.host)) and (not require_current_request or ip_url.host == _get_request_host()) and (not require_standard_port or ip_url.is_default_port()) ): return normalize_url(str(ip_url)) raise NoURLAvailableError
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(hass) if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ((allow_ip or not is_ip_address(str(external_url.host))) and (not require_current_request or external_url.host == _get_request_host()) and (not require_standard_port or external_url.is_default_port()) and (not require_ssl or (external_url.scheme == "https" and not is_ip_address(str(external_url.host))))): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url( hass, require_current_request=require_current_request) raise NoURLAvailableError
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ( (allow_ip or not is_ip_address(str(external_url.host))) and ( not require_current_request or external_url.host == _get_request_host() ) and (not require_standard_port or external_url.is_default_port()) and ( not require_ssl or ( external_url.scheme == "https" and not is_ip_address(str(external_url.host)) ) ) ): return normalize_url(str(external_url)) try: return _get_deprecated_base_url( hass, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_cloud: try: return _get_cloud_url(hass, require_current_request=require_current_request) except NoURLAvailableError: pass # get ais url remote_access = hass.states.get("input_boolean.ais_remote_access").state if remote_access == "on": return hass.states.get("camera.remote_access").state else: return "http://" + hass.states.get("sensor.internal_ip_address").state + ":8180" raise NoURLAvailableError
def valid_base_url(value: str) -> str: """Validate base url, return value.""" url = yarl.URL(cv.url(value)) if url.path != "/": raise vol.Invalid("Path should be empty") return normalize_url(value)
def _get_cloud_url(hass: HomeAssistant, require_current_request: bool = False) -> str: """Get external Home Assistant Cloud URL of this instance.""" if "cloud" in hass.config.components: try: cloud_url = yarl.URL(cast(str, hass.components.cloud.async_remote_ui_url())) except hass.components.cloud.CloudNotAvailable as err: raise NoURLAvailableError from err if not require_current_request or cloud_url.host == _get_request_host(): return normalize_url(str(cloud_url)) raise NoURLAvailableError
def is_hass_url(hass: HomeAssistant, url: str) -> bool: """Return if the URL points at this Home Assistant instance.""" parsed = yarl.URL(normalize_url(url)) def host_ip() -> str | None: if hass.config.api is None or is_loopback(ip_address(hass.config.api.local_ip)): return None return str( yarl.URL.build( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) ) def cloud_url() -> str | None: try: return _get_cloud_url(hass) except NoURLAvailableError: return None for potential_base_factory in ( lambda: hass.config.internal_url, lambda: hass.config.external_url, cloud_url, host_ip, ): potential_base = potential_base_factory() if potential_base is None: continue potential_parsed = yarl.URL(normalize_url(potential_base)) if ( parsed.scheme == potential_parsed.scheme and parsed.authority == potential_parsed.authority ): return True return False
def test_normalize_url(): """Test the normalizing of URLs.""" assert network_util.normalize_url( "http://example.com") == "http://example.com" assert network_util.normalize_url( "https://example.com") == "https://example.com" assert network_util.normalize_url( "https://example.com/") == "https://example.com" assert (network_util.normalize_url("https://example.com:443") == "https://example.com") assert network_util.normalize_url( "http://example.com:80") == "http://example.com" assert (network_util.normalize_url("https://example.com:80") == "https://example.com:80") assert (network_util.normalize_url("http://example.com:443") == "http://example.com:443") assert (network_util.normalize_url("https://example.com:443/test/") == "https://example.com/test") assert network_util.normalize_url("/test/") == "/test"
def is_hass_url(hass: HomeAssistant, url: str) -> bool: """Return if the URL points at this Home Assistant instance.""" parsed = yarl.URL(url) if not parsed.is_absolute(): return False if parsed.is_default_port(): parsed = parsed.with_port(None) def host_ip() -> str | None: if hass.config.api is None or is_loopback(ip_address(hass.config.api.local_ip)): return None return str( yarl.URL.build( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) ) def cloud_url() -> str | None: try: return _get_cloud_url(hass) except NoURLAvailableError: return None potential_base_factory: Callable[[], str | None] for potential_base_factory in ( lambda: hass.config.internal_url, lambda: hass.config.external_url, cloud_url, host_ip, lambda: get_supervisor_network_url(hass, allow_ssl=True), ): potential_base = potential_base_factory() if potential_base is None: continue potential_parsed = yarl.URL(normalize_url(potential_base)) if ( parsed.scheme == potential_parsed.scheme and parsed.authority == potential_parsed.authority ): return True return False
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ( (allow_ip or not is_ip_address(str(external_url.host))) and (not require_standard_port or external_url.is_default_port()) and ( not require_ssl or ( external_url.scheme == "https" and not is_ip_address(str(external_url.host)) ) ) ): return normalize_url(str(external_url)) try: return _get_deprecated_base_url( hass, allow_ip=allow_ip, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass raise NoURLAvailableError
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(hass) if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ((allow_ip or not is_ip_address(str(external_url.host))) and (not require_current_request or external_url.host == _get_request_host()) and (not require_standard_port or external_url.is_default_port()) and (not require_ssl or (external_url.scheme == "https" and not is_ip_address(str(external_url.host))))): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url( hass, require_current_request=require_current_request) # get ais url remote_access = hass.states.get("input_boolean.ais_remote_access").state if remote_access == "on": import homeassistant.components.ais_dom.ais_global as ais_global return "https://" + ais_global.get_sercure_android_id_dom( ) + ".paczka.pro" else: return "http://" + hass.config.api.local_ip + ":" + str( hass.config.api.port) raise NoURLAvailableError
def get_url( hass: HomeAssistant, *, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, allow_internal: bool = True, allow_external: bool = True, allow_cloud: bool = True, allow_ip: bool = True, prefer_external: bool = False, prefer_cloud: bool = False, ) -> str: """Get a URL to this instance.""" if require_current_request and http.current_request.get() is None: raise NoURLAvailableError order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL] if prefer_external: order.reverse() # Try finding an URL in the order specified for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: try: return _get_internal_url( hass, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_external and url_type == TYPE_URL_EXTERNAL: try: return _get_external_url( hass, allow_cloud=allow_cloud, allow_ip=allow_ip, prefer_cloud=prefer_cloud, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently request_host = _get_request_host() if ( require_current_request and request_host is not None and hass.config.api is not None ): scheme = "https" if hass.config.api.use_ssl else "http" current_url = yarl.URL.build( scheme=scheme, host=request_host, port=hass.config.api.port ) known_hostnames = ["localhost"] if hass.components.hassio.is_hassio(): host_info = hass.components.hassio.get_host_info() known_hostnames.extend( [host_info["hostname"], f"{host_info['hostname']}.local"] ) if ( ( ( allow_ip and is_ip_address(request_host) and is_loopback(ip_address(request_host)) ) or request_host in known_hostnames ) and (not require_ssl or current_url.scheme == "https") and (not require_standard_port or current_url.is_default_port()) ): return normalize_url(str(current_url)) # We have to be honest now, we have no viable option available raise NoURLAvailableError
def test_valid_base_url(value): """Test we validate base urls.""" assert tts.valid_base_url(value) == normalize_url(value) # Test we strip trailing `/` assert tts.valid_base_url(value + "/") == normalize_url(value)