Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions pychromecast/dial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

from dataclasses import dataclass
from http import HTTPStatus
import json
import logging
import socket
Expand Down Expand Up @@ -74,6 +75,28 @@ def _get_host_from_zc_service_info(
return (host, port)


def _urlopen(url: str, timeout: float, context: ssl.SSLContext | None) -> Any:
"""Help open an URL."""
headers = {"content-type": "application/json"}
try:
req = urllib.request.Request(url, headers=headers)
return urllib.request.urlopen(req, timeout=timeout, context=context)
except urllib.error.HTTPError as err:
if err.code != HTTPStatus.FORBIDDEN:
raise
# We may be blocked because we're connecting to a hostname specified directly
# instead of to an IP address resolved by mDNS, cast devices will reject the
# request when the address is set to the hostname. Do another attempt with an
# empty host header.
# Note: This is a simplified approach to not have to deal with name resolution
# in pychromecast. If devices reject the empty host header we need to do name
# resolution and instead set the host header to the string version of the IP.
_LOGGER.debug("Failed to fetch %s, retrying with empty host header", url)
headers["host"] = ""
req = urllib.request.Request(url, headers=headers)
return urllib.request.urlopen(req, timeout=timeout, context=context)


def _get_status(
services: set[HostServiceInfo | MDNSServiceInfo],
zconf: zeroconf.Zeroconf | None,
Expand All @@ -90,8 +113,6 @@ def _get_status(
_LOGGER.debug("Resolved service %s to %s", service, host)
break

headers = {"content-type": "application/json"}

if secure:
url = FORMAT_BASE_URL_HTTPS.format(host) + path
else:
Expand All @@ -101,8 +122,7 @@ def _get_status(
if secure and not has_context:
context = get_ssl_context()

req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=timeout, context=context) as response:
with _urlopen(url, timeout, context) as response:
data = response.read()
return (host, json.loads(data.decode("utf-8")))

Expand Down Expand Up @@ -246,7 +266,8 @@ def get_device_info( # pylint: disable=too-many-locals
multizone_supported,
)

except (urllib.error.HTTPError, urllib.error.URLError, OSError, ValueError):
except (urllib.error.HTTPError, urllib.error.URLError, OSError, ValueError) as err:
_LOGGER.debug("Failed to get device info for %s: %s (%s)", host, err, type(err))
return None


Expand Down Expand Up @@ -305,7 +326,10 @@ def get_multizone_status(

return MultizoneStatus(dynamic_groups, groups)

except (urllib.error.HTTPError, urllib.error.URLError, OSError, ValueError):
except (urllib.error.HTTPError, urllib.error.URLError, OSError, ValueError) as err:
_LOGGER.debug(
"Failed to get multizone status for %s: %s (%s)", host, err, type(err)
)
return None


Expand Down