3 from __future__
import annotations
5 from collections.abc
import Callable
6 from contextlib
import suppress
7 from ipaddress
import ip_address
9 from aiohttp
import hdrs
10 from hass_nabucasa
import remote
19 from .hassio
import is_hassio
21 TYPE_URL_INTERNAL =
"internal_url"
22 TYPE_URL_EXTERNAL =
"external_url"
23 SUPERVISOR_NETWORK_HOST =
"homeassistant"
27 """An URL to the Home Assistant instance is not available."""
32 """Test if the current request is internal."""
35 hass, allow_external=
False, allow_cloud=
False, require_current_request=
True
37 except NoURLAvailableError:
44 hass: HomeAssistant, *, allow_ssl: bool =
False
46 """Get URL for home assistant within supervisor network."""
47 if hass.config.api
is None or not is_hassio(hass):
51 if hass.config.api.use_ssl:
61 host=SUPERVISOR_NETWORK_HOST,
62 port=hass.config.api.port,
68 """Return if the URL points at this Home Assistant instance."""
69 parsed = yarl.URL(url)
71 if not parsed.is_absolute():
74 if parsed.is_default_port():
75 parsed = parsed.with_port(
None)
77 def host_ip() -> str | None:
78 if hass.config.api
is None or is_loopback(ip_address(hass.config.api.local_ip)):
83 scheme=
"http", host=hass.config.api.local_ip, port=hass.config.api.port
87 def cloud_url() -> str | None:
90 except NoURLAvailableError:
93 potential_base_factory: Callable[[], str |
None]
94 for potential_base_factory
in (
95 lambda: hass.config.internal_url,
96 lambda: hass.config.external_url,
101 potential_base = potential_base_factory()
103 if potential_base
is None:
109 parsed.scheme == potential_parsed.scheme
110 and parsed.authority == potential_parsed.authority
121 require_current_request: bool =
False,
122 require_ssl: bool =
False,
123 require_standard_port: bool =
False,
124 require_cloud: bool =
False,
125 allow_internal: bool =
True,
126 allow_external: bool =
True,
127 allow_cloud: bool =
True,
128 allow_ip: bool |
None =
None,
129 prefer_external: bool |
None =
None,
130 prefer_cloud: bool =
False,
132 """Get a URL to this instance."""
133 if require_current_request
and http.current_request.get()
is None:
134 raise NoURLAvailableError
136 if prefer_external
is None:
137 prefer_external = hass.config.api
is not None and hass.config.api.use_ssl
140 allow_ip = hass.config.api
is None or not hass.config.api.use_ssl
142 order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL]
147 for url_type
in order:
148 if allow_internal
and url_type == TYPE_URL_INTERNAL
and not require_cloud:
149 with suppress(NoURLAvailableError):
153 require_current_request=require_current_request,
154 require_ssl=require_ssl,
155 require_standard_port=require_standard_port,
158 if require_cloud
or (allow_external
and url_type == TYPE_URL_EXTERNAL):
159 with suppress(NoURLAvailableError):
162 allow_cloud=allow_cloud,
164 prefer_cloud=prefer_cloud,
165 require_current_request=require_current_request,
166 require_ssl=require_ssl,
167 require_standard_port=require_standard_port,
168 require_cloud=require_cloud,
171 raise NoURLAvailableError
177 require_current_request
178 and request_host
is not None
179 and hass.config.api
is not None
181 scheme =
"https" if hass.config.api.use_ssl
else "http"
182 current_url = yarl.URL.build(
183 scheme=scheme, host=request_host, port=hass.config.api.port
186 known_hostnames = [
"localhost"]
193 known_hostnames.extend(
194 [host_info[
"hostname"], f
"{host_info['hostname']}.local"]
204 or request_host
in known_hostnames
206 and (
not require_ssl
or current_url.scheme ==
"https")
207 and (
not require_standard_port
or current_url.is_default_port())
212 raise NoURLAvailableError
216 """Get the host address of the current request."""
217 if (request := http.current_request.get())
is None:
218 raise NoURLAvailableError
221 host = request.headers.get(hdrs.HOST)
227 return (host.partition(
"[")[2]).partition(
"]")[0]
229 host = host.partition(
":")[0]
237 allow_ip: bool =
True,
238 require_current_request: bool =
False,
239 require_ssl: bool =
False,
240 require_standard_port: bool =
False,
242 """Get internal URL of this instance."""
243 if hass.config.internal_url:
244 internal_url = yarl.URL(hass.config.internal_url)
247 and (
not require_ssl
or internal_url.scheme ==
"https")
248 and (
not require_standard_port
or internal_url.is_default_port())
254 if allow_ip
and not (
255 require_ssl
or hass.config.api
is None or hass.config.api.use_ssl
257 ip_url = yarl.URL.build(
258 scheme=
"http", host=hass.config.api.local_ip, port=hass.config.api.port
264 and (
not require_standard_port
or ip_url.is_default_port())
268 raise NoURLAvailableError
275 allow_cloud: bool =
True,
276 allow_ip: bool =
True,
277 prefer_cloud: bool =
False,
278 require_current_request: bool =
False,
279 require_ssl: bool =
False,
280 require_standard_port: bool =
False,
281 require_cloud: bool =
False,
283 """Get external URL of this instance."""
285 return _get_cloud_url(hass, require_current_request=require_current_request)
287 if prefer_cloud
and allow_cloud:
288 with suppress(NoURLAvailableError):
291 if hass.config.external_url:
292 external_url = yarl.URL(hass.config.external_url)
298 and (
not require_standard_port
or external_url.is_default_port())
302 external_url.scheme ==
"https"
310 with suppress(NoURLAvailableError):
311 return _get_cloud_url(hass, require_current_request=require_current_request)
313 raise NoURLAvailableError
317 def _get_cloud_url(hass: HomeAssistant, require_current_request: bool =
False) -> str:
318 """Get external Home Assistant Cloud URL of this instance."""
319 if "cloud" in hass.config.components:
329 except CloudNotAvailable
as err:
330 raise NoURLAvailableError
from err
335 raise NoURLAvailableError
339 """Return True if the current connection is a nabucasa cloud connection."""
341 if "cloud" not in hass.config.components:
344 return remote.is_cloud_request.get()
str async_remote_ui_url(HomeAssistant hass)
dict[str, Any]|None get_host_info(HomeAssistant hass)
bool is_hassio(HomeAssistant hass)
bool is_internal_request(HomeAssistant hass)
str|None get_supervisor_network_url(HomeAssistant hass, *bool allow_ssl=False)
bool is_cloud_connection(HomeAssistant hass)
str get_url(HomeAssistant hass, *bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False, bool require_cloud=False, bool allow_internal=True, bool allow_external=True, bool allow_cloud=True, bool|None allow_ip=None, bool|None prefer_external=None, bool prefer_cloud=False)
str|None _get_request_host()
str _get_internal_url(HomeAssistant hass, *bool allow_ip=True, bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False)
str _get_external_url(HomeAssistant hass, *bool allow_cloud=True, bool allow_ip=True, bool prefer_cloud=False, bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False, bool require_cloud=False)
bool is_hass_url(HomeAssistant hass, str url)
str _get_cloud_url(HomeAssistant hass, bool require_current_request=False)
bool is_loopback(IPv4Address|IPv6Address address)
str normalize_url(str address)
bool is_ip_address(str address)