Skip to content

Commit 9b5ac5b

Browse files
authored
Fix WebOS TV image fetch SSL verify failure (home-assistant#85841)
1 parent be899b6 commit 9b5ac5b

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

homeassistant/components/webostv/media_player.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"""Support for interface with an LG webOS Smart TV."""
22
from __future__ import annotations
33

4+
import asyncio
45
from collections.abc import Awaitable, Callable, Coroutine
56
from contextlib import suppress
67
from datetime import timedelta
78
from functools import wraps
9+
from http import HTTPStatus
810
import logging
11+
from ssl import SSLContext
912
from typing import Any, TypeVar, cast
1013

1114
from aiowebostv import WebOsClient, WebOsTvPairError
15+
import async_timeout
1216
from typing_extensions import Concatenate, ParamSpec
1317

1418
from homeassistant import util
@@ -28,6 +32,7 @@
2832
)
2933
from homeassistant.core import HomeAssistant
3034
from homeassistant.exceptions import HomeAssistantError
35+
from homeassistant.helpers.aiohttp_client import async_get_clientsession
3136
from homeassistant.helpers.dispatcher import async_dispatcher_connect
3237
from homeassistant.helpers.entity import DeviceInfo
3338
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -466,3 +471,25 @@ async def async_button(self, button: str) -> None:
466471
async def async_command(self, command: str, **kwargs: Any) -> None:
467472
"""Send a command."""
468473
await self._client.request(command, payload=kwargs.get(ATTR_PAYLOAD))
474+
475+
async def _async_fetch_image(self, url: str) -> tuple[bytes | None, str | None]:
476+
"""Retrieve an image.
477+
478+
webOS uses self-signed certificates, thus we need to use an empty
479+
SSLContext to bypass validation errors if url starts with https.
480+
"""
481+
content = None
482+
ssl_context = None
483+
if url.startswith("https"):
484+
ssl_context = SSLContext()
485+
486+
websession = async_get_clientsession(self.hass)
487+
with suppress(asyncio.TimeoutError), async_timeout.timeout(10):
488+
response = await websession.get(url, ssl=ssl_context)
489+
if response.status == HTTPStatus.OK:
490+
content = await response.read()
491+
492+
if content is None:
493+
_LOGGER.warning("Error retrieving proxied image from %s", url)
494+
495+
return content, None

tests/components/webostv/test_media_player.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""The tests for the LG webOS media player platform."""
22
import asyncio
33
from datetime import timedelta
4+
from http import HTTPStatus
45
from unittest.mock import Mock
56

67
import pytest
@@ -697,3 +698,68 @@ async def test_supported_features_ignore_cache(hass, client):
697698
attrs = hass.states.get(ENTITY_ID).attributes
698699

699700
assert attrs[ATTR_SUPPORTED_FEATURES] == supported
701+
702+
703+
async def test_get_image_http(
704+
hass, client, hass_client_no_auth, aioclient_mock, monkeypatch
705+
):
706+
"""Test get image via http."""
707+
url = "http://something/valid_icon"
708+
monkeypatch.setitem(client.apps[LIVE_TV_APP_ID], "icon", url)
709+
await setup_webostv(hass)
710+
await client.mock_state_update()
711+
712+
attrs = hass.states.get(ENTITY_ID).attributes
713+
assert "entity_picture_local" not in attrs
714+
715+
aioclient_mock.get(url, text="image")
716+
client = await hass_client_no_auth()
717+
718+
resp = await client.get(attrs["entity_picture"])
719+
content = await resp.read()
720+
721+
assert content == b"image"
722+
723+
724+
async def test_get_image_http_error(
725+
hass, client, hass_client_no_auth, aioclient_mock, caplog, monkeypatch
726+
):
727+
"""Test get image via http error."""
728+
url = "http://something/icon_error"
729+
monkeypatch.setitem(client.apps[LIVE_TV_APP_ID], "icon", url)
730+
await setup_webostv(hass)
731+
await client.mock_state_update()
732+
733+
attrs = hass.states.get(ENTITY_ID).attributes
734+
assert "entity_picture_local" not in attrs
735+
736+
aioclient_mock.get(url, exc=asyncio.TimeoutError())
737+
client = await hass_client_no_auth()
738+
739+
resp = await client.get(attrs["entity_picture"])
740+
content = await resp.read()
741+
742+
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
743+
assert f"Error retrieving proxied image from {url}" in caplog.text
744+
assert content == b""
745+
746+
747+
async def test_get_image_https(
748+
hass, client, hass_client_no_auth, aioclient_mock, monkeypatch
749+
):
750+
"""Test get image via http."""
751+
url = "https://something/valid_icon_https"
752+
monkeypatch.setitem(client.apps[LIVE_TV_APP_ID], "icon", url)
753+
await setup_webostv(hass)
754+
await client.mock_state_update()
755+
756+
attrs = hass.states.get(ENTITY_ID).attributes
757+
assert "entity_picture_local" not in attrs
758+
759+
aioclient_mock.get(url, text="https_image")
760+
client = await hass_client_no_auth()
761+
762+
resp = await client.get(attrs["entity_picture"])
763+
content = await resp.read()
764+
765+
assert content == b"https_image"

0 commit comments

Comments
 (0)