Prevent invalid data from being passed to zeroconf (#39009)
This commit is contained in:
parent
7c346c2f7c
commit
5a9246468e
2 changed files with 65 additions and 2 deletions
|
@ -55,6 +55,13 @@ HOMEKIT_PROPERTIES = "properties"
|
||||||
HOMEKIT_PAIRED_STATUS_FLAG = "sf"
|
HOMEKIT_PAIRED_STATUS_FLAG = "sf"
|
||||||
HOMEKIT_MODEL = "md"
|
HOMEKIT_MODEL = "md"
|
||||||
|
|
||||||
|
# Property key=value has a max length of 255
|
||||||
|
# so we use 230 to leave space for key=
|
||||||
|
MAX_PROPERTY_VALUE_LEN = 230
|
||||||
|
|
||||||
|
# Dns label max length
|
||||||
|
MAX_NAME_LEN = 63
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.Schema(
|
||||||
|
@ -145,8 +152,10 @@ def setup(hass, config):
|
||||||
hass.helpers.instance_id.async_get(), hass.loop
|
hass.helpers.instance_id.async_get(), hass.loop
|
||||||
).result()
|
).result()
|
||||||
|
|
||||||
|
valid_location_name = _truncate_location_name_to_valid(hass.config.location_name)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"location_name": hass.config.location_name,
|
"location_name": valid_location_name,
|
||||||
"uuid": uuid,
|
"uuid": uuid,
|
||||||
"version": __version__,
|
"version": __version__,
|
||||||
"external_url": "",
|
"external_url": "",
|
||||||
|
@ -178,9 +187,11 @@ def setup(hass, config):
|
||||||
except OSError:
|
except OSError:
|
||||||
host_ip_pton = socket.inet_pton(socket.AF_INET6, host_ip)
|
host_ip_pton = socket.inet_pton(socket.AF_INET6, host_ip)
|
||||||
|
|
||||||
|
_suppress_invalid_properties(params)
|
||||||
|
|
||||||
info = ServiceInfo(
|
info = ServiceInfo(
|
||||||
ZEROCONF_TYPE,
|
ZEROCONF_TYPE,
|
||||||
name=f"{hass.config.location_name}.{ZEROCONF_TYPE}",
|
name=f"{valid_location_name}.{ZEROCONF_TYPE}",
|
||||||
server=f"{uuid}.local.",
|
server=f"{uuid}.local.",
|
||||||
addresses=[host_ip_pton],
|
addresses=[host_ip_pton],
|
||||||
port=hass.http.server_port,
|
port=hass.http.server_port,
|
||||||
|
@ -358,3 +369,33 @@ def info_from_service(service):
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def _suppress_invalid_properties(properties):
|
||||||
|
"""Suppress any properties that will cause zeroconf to fail to startup."""
|
||||||
|
|
||||||
|
for prop, prop_value in properties.items():
|
||||||
|
if not isinstance(prop_value, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(prop_value.encode("utf-8")) > MAX_PROPERTY_VALUE_LEN:
|
||||||
|
_LOGGER.error(
|
||||||
|
"The property '%s' was suppressed because it is longer than the maximum length of %d bytes: %s",
|
||||||
|
prop,
|
||||||
|
MAX_PROPERTY_VALUE_LEN,
|
||||||
|
prop_value,
|
||||||
|
)
|
||||||
|
properties[prop] = ""
|
||||||
|
|
||||||
|
|
||||||
|
def _truncate_location_name_to_valid(location_name):
|
||||||
|
"""Truncate or return the location name usable for zeroconf."""
|
||||||
|
if len(location_name.encode("utf-8")) < MAX_NAME_LEN:
|
||||||
|
return location_name
|
||||||
|
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The location name was truncated because it is longer than the maximum length of %d bytes: %s",
|
||||||
|
MAX_NAME_LEN,
|
||||||
|
location_name,
|
||||||
|
)
|
||||||
|
return location_name.encode("utf-8")[:MAX_NAME_LEN].decode("utf-8", "ignore")
|
||||||
|
|
|
@ -102,6 +102,28 @@ async def test_setup(hass, mock_zeroconf):
|
||||||
assert await hass.components.zeroconf.async_get_instance() is mock_zeroconf
|
assert await hass.components.zeroconf.async_get_instance() is mock_zeroconf
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_with_overly_long_url_and_name(hass, mock_zeroconf, caplog):
|
||||||
|
"""Test we still setup with long urls and names."""
|
||||||
|
with patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
|
||||||
|
) as mock_service_browser, patch(
|
||||||
|
"homeassistant.components.zeroconf.get_url",
|
||||||
|
return_value="https://this.url.is.way.too.long/very/deep/path/that/will/make/us/go/over/the/maximum/string/length/and/would/cause/zeroconf/to/fail/to/startup/because/the/key/and/value/can/only/be/255/bytes/and/this/string/is/a/bit/longer/than/the/maximum/length/that/we/allow/for/a/value",
|
||||||
|
), patch.object(
|
||||||
|
hass.config,
|
||||||
|
"location_name",
|
||||||
|
"\u00dcBER \u00dcber German Umlaut long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string",
|
||||||
|
):
|
||||||
|
mock_zeroconf.get_service_info.side_effect = get_service_info_mock
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
|
assert "https://this.url.is.way.too.long" in caplog.text
|
||||||
|
assert "German Umlaut" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_default_interface(hass, mock_zeroconf):
|
async def test_setup_with_default_interface(hass, mock_zeroconf):
|
||||||
"""Test default interface config."""
|
"""Test default interface config."""
|
||||||
with patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
with patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue