diff --git a/homeassistant/components/rest/const.py b/homeassistant/components/rest/const.py index 0bf0ea9743d..8fb08f766fa 100644 --- a/homeassistant/components/rest/const.py +++ b/homeassistant/components/rest/const.py @@ -26,3 +26,10 @@ REST = "rest" REST_DATA = "rest_data" METHODS = ["POST", "GET"] + +XML_MIME_TYPES = ( + "application/rss+xml", + "application/xhtml+xml", + "application/xml", + "text/xml", +) diff --git a/homeassistant/components/rest/data.py b/homeassistant/components/rest/data.py index 95086f68d70..1f331651165 100644 --- a/homeassistant/components/rest/data.py +++ b/homeassistant/components/rest/data.py @@ -3,14 +3,19 @@ from __future__ import annotations import logging import ssl +from xml.parsers.expat import ExpatError import httpx +import xmltodict from homeassistant.core import HomeAssistant from homeassistant.helpers import template from homeassistant.helpers.httpx_client import create_async_httpx_client +from homeassistant.helpers.json import json_dumps from homeassistant.util.ssl import SSLCipherList +from .const import XML_MIME_TYPES + DEFAULT_TIMEOUT = 10 _LOGGER = logging.getLogger(__name__) @@ -59,6 +64,26 @@ class RestData: """Set url.""" self._resource = url + def data_without_xml(self) -> str | None: + """If the data is an XML string, convert it to a JSON string.""" + _LOGGER.debug("Data fetched from resource: %s", self.data) + if ( + (value := self.data) is not None + # If the http request failed, headers will be None + and (headers := self.headers) is not None + and (content_type := headers.get("content-type")) + and content_type.startswith(XML_MIME_TYPES) + ): + try: + value = json_dumps(xmltodict.parse(value)) + except ExpatError: + _LOGGER.warning( + "REST xml result could not be parsed and converted to JSON" + ) + else: + _LOGGER.debug("JSON converted from XML: %s", self.data) + return value + async def async_update(self, log_errors: bool = True) -> None: """Get the latest data from REST service with provided method.""" if not self._async_client: diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 6fc0b69d1fd..18d0b6c7e76 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -3,11 +3,9 @@ from __future__ import annotations import logging import ssl -from xml.parsers.expat import ExpatError from jsonpath import jsonpath import voluptuous as vol -import xmltodict from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, @@ -26,7 +24,6 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.json import json_dumps from homeassistant.helpers.template_entity import TemplateSensor from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -127,26 +124,7 @@ class RestSensor(RestEntity, TemplateSensor): def _update_from_rest_data(self) -> None: """Update state from the rest data.""" - value = self.rest.data - _LOGGER.debug("Data fetched from resource: %s", value) - if self.rest.headers is not None: - # If the http request failed, headers will be None - content_type = self.rest.headers.get("content-type") - - if content_type and ( - content_type.startswith("text/xml") - or content_type.startswith("application/xml") - or content_type.startswith("application/xhtml+xml") - or content_type.startswith("application/rss+xml") - ): - try: - value = json_dumps(xmltodict.parse(value)) - _LOGGER.debug("JSON converted from XML: %s", value) - except ExpatError: - _LOGGER.warning( - "REST xml result could not be parsed and converted to JSON" - ) - _LOGGER.debug("Erroneous XML: %s", value) + value = self.rest.data_without_xml() if self._json_attrs: self._attr_extra_state_attributes = {} diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index fd595ef07a6..a7674937ab8 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -899,7 +899,7 @@ async def test_update_with_xml_convert_bad_xml( state = hass.states.get("sensor.foo") assert state.state == STATE_UNKNOWN - assert "Erroneous XML" in caplog.text + assert "REST xml result could not be parsed" in caplog.text assert "Empty reply" in caplog.text @@ -936,7 +936,7 @@ async def test_update_with_failed_get( state = hass.states.get("sensor.foo") assert state.state == STATE_UNKNOWN - assert "Erroneous XML" in caplog.text + assert "REST xml result could not be parsed" in caplog.text assert "Empty reply" in caplog.text