From 877ad38ac3d52cd10e104643da82181fbca7142c Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:12:33 +0100 Subject: [PATCH] Convert image URLs to secure URLs in Xbox integration (#157945) --- homeassistant/components/xbox/browse_media.py | 4 +++- homeassistant/components/xbox/entity.py | 16 ++++++++++------ homeassistant/components/xbox/media_source.py | 3 ++- homeassistant/components/xbox/sensor.py | 5 +++-- .../xbox/snapshots/test_media_source.ambr | 4 ++-- tests/components/xbox/snapshots/test_sensor.ambr | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index f4b8e037314..595dc965eb8 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -19,6 +19,8 @@ from pythonxbox.api.provider.smartglass.models import ( from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType +from .entity import to_https + class MediaTypeDetails(NamedTuple): """Details for media type.""" @@ -151,5 +153,5 @@ def _find_media_image(images: list[Image]) -> str | None: if match := next( (image for image in images if image.image_purpose == purpose), None ): - return f"https:{match.uri}" if match.uri.startswith("/") else match.uri + return to_https(match.uri) return None diff --git a/homeassistant/components/xbox/entity.py b/homeassistant/components/xbox/entity.py index 4aa633a4266..67087fdb82d 100644 --- a/homeassistant/components/xbox/entity.py +++ b/homeassistant/components/xbox/entity.py @@ -151,6 +151,15 @@ def check_deprecated_entity( return False +def to_https(image_url: str) -> str: + """Convert image URLs to secure URLs.""" + + url = URL(image_url) + if url.host == "images-eds.xboxlive.com": + url = url.with_host("images-eds-ssl.xboxlive.com") + return str(url.with_scheme("https")) + + def profile_pic(person: Person, _: Title | None = None) -> str | None: """Return the gamer pic.""" @@ -160,9 +169,4 @@ def profile_pic(person: Person, _: Title | None = None) -> str | None: # to point to the correct image, with the correct domain and certificate. # We need to also remove the 'mode=Padding' query because with it, # it results in an error 400. - url = URL(person.display_pic_raw) - if url.host == "images-eds.xboxlive.com": - url = url.with_host("images-eds-ssl.xboxlive.com").with_scheme("https") - query = dict(url.query) - query.pop("mode", None) - return str(url.with_query(query)) + return str(URL(to_https(person.display_pic_raw)).without_query_params("mode")) diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 76baeee166e..a224fc56eb8 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -22,6 +22,7 @@ from homeassistant.util import dt as dt_util from .binary_sensor import profile_pic from .const import DOMAIN from .coordinator import XboxConfigEntry +from .entity import to_https _LOGGER = logging.getLogger(__name__) @@ -655,6 +656,6 @@ def game_thumbnail(images: list[Image]) -> str | None: (i for i in images if i.type == img_type), None, ): - return match.url + return to_https(match.url) return None diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index 46103bb298c..9fadf325ce5 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -34,6 +34,7 @@ from .entity import ( XboxBaseEntity, XboxBaseEntityDescription, check_deprecated_entity, + to_https, ) PARALLEL_UPDATES = 0 @@ -142,8 +143,8 @@ def title_logo(_: Person, title: Title | None) -> str | None: """Get the game logo.""" return ( - next((i.url for i in title.images if i.type == "Tile"), None) - or next((i.url for i in title.images if i.type == "Logo"), None) + next((to_https(i.url) for i in title.images if i.type == "Tile"), None) + or next((to_https(i.url) for i in title.images if i.type == "Logo"), None) if title and title.images else None ) diff --git a/tests/components/xbox/snapshots/test_media_source.ambr b/tests/components/xbox/snapshots/test_media_source.ambr index 24dc1709c5e..1f3118ccb03 100644 --- a/tests/components/xbox/snapshots/test_media_source.ambr +++ b/tests/components/xbox/snapshots/test_media_source.ambr @@ -577,7 +577,7 @@ 'media_class': , 'media_content_id': 'media-source://xbox/271958441785640/1297287135', 'media_content_type': , - 'thumbnail': 'http://store-images.s-microsoft.com/image/apps.64736.65457035095819016.56f55216-1bb9-40aa-8796-068cf3075fc1.6491fb2f-52e7-4129-bcbd-d23a67117ae0', + 'thumbnail': 'https://store-images.s-microsoft.com/image/apps.64736.65457035095819016.56f55216-1bb9-40aa-8796-068cf3075fc1.6491fb2f-52e7-4129-bcbd-d23a67117ae0', 'title': 'Blue Dragon', }), dict({ @@ -588,7 +588,7 @@ 'media_class': , 'media_content_id': 'media-source://xbox/271958441785640/1560034050', 'media_content_type': , - 'thumbnail': 'http://store-images.s-microsoft.com/image/apps.46246.63309362003335928.4079e21b-b00f-4446-a680-6bf9c0eb0158.c976135a-831a-4cf6-a39b-f01c633567bc', + 'thumbnail': 'https://store-images.s-microsoft.com/image/apps.46246.63309362003335928.4079e21b-b00f-4446-a680-6bf9c0eb0158.c976135a-831a-4cf6-a39b-f01c633567bc', 'title': "Assassin's Creed® Syndicate", }), ]), diff --git a/tests/components/xbox/snapshots/test_sensor.ambr b/tests/components/xbox/snapshots/test_sensor.ambr index 0d68963b42f..f23ce549002 100644 --- a/tests/components/xbox/snapshots/test_sensor.ambr +++ b/tests/components/xbox/snapshots/test_sensor.ambr @@ -790,7 +790,7 @@ 'attributes': ReadOnlyDict({ 'achievements': '2 / 43', 'developer': 'Mistwalker / Artoon', - 'entity_picture': 'http://store-images.s-microsoft.com/image/apps.35072.13670972585585116.70570f0d-17aa-4f97-b692-5412fa183673.25a97451-9369-4f6b-b66b-3427913235eb', + 'entity_picture': 'https://store-images.s-microsoft.com/image/apps.35072.13670972585585116.70570f0d-17aa-4f97-b692-5412fa183673.25a97451-9369-4f6b-b66b-3427913235eb', 'friendly_name': 'GSR Ae Now playing', 'gamerscore': '10 / 1000', 'genres': 'Role Playing',