diff --git a/homeassistant/components/ambiclimate/strings.json b/homeassistant/components/ambiclimate/strings.json
index 50bc8284b71..26af78f8915 100644
--- a/homeassistant/components/ambiclimate/strings.json
+++ b/homeassistant/components/ambiclimate/strings.json
@@ -3,7 +3,7 @@
"step": {
"auth": {
"title": "Authenticate Ambiclimate",
- "description": "Please follow this [link]({authorization_url}) and Allow access to your Ambiclimate account, then come back and press Submit below.\n(Make sure the specified callback url is {cb_url})"
+ "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback url is {cb_url})"
}
},
"create_entry": {
diff --git a/homeassistant/components/logi_circle/strings.json b/homeassistant/components/logi_circle/strings.json
index 347589c7881..d96f0e041a1 100644
--- a/homeassistant/components/logi_circle/strings.json
+++ b/homeassistant/components/logi_circle/strings.json
@@ -8,7 +8,7 @@
},
"auth": {
"title": "Authenticate with Logi Circle",
- "description": "Please follow the link below and Accept access to your Logi Circle account, then come back and press Submit below.\n\n[Link]({authorization_url})"
+ "description": "Please follow the link below and **Accept** access to your Logi Circle account, then come back and press **Submit** below.\n\n[Link]({authorization_url})"
}
},
"create_entry": {
diff --git a/homeassistant/components/point/strings.json b/homeassistant/components/point/strings.json
index b42c6cef198..194121e8e25 100644
--- a/homeassistant/components/point/strings.json
+++ b/homeassistant/components/point/strings.json
@@ -8,7 +8,7 @@
},
"auth": {
"title": "Authenticate Point",
- "description": "Please follow the link below and Accept access to your Minut account, then come back and press Submit below.\n\n[Link]({authorization_url})"
+ "description": "Please follow the link below and **Accept** access to your Minut account, then come back and press **Submit** below.\n\n[Link]({authorization_url})"
}
},
"create_entry": {
diff --git a/homeassistant/components/starline/strings.json b/homeassistant/components/starline/strings.json
index 919efb2f024..33e28f9a29d 100644
--- a/homeassistant/components/starline/strings.json
+++ b/homeassistant/components/starline/strings.json
@@ -3,7 +3,7 @@
"step": {
"auth_app": {
"title": "Application credentials",
- "description": "Application ID and secret code from StarLine developer account",
+ "description": "Application ID and secret code from [StarLine developer account](https://my.starline.ru/developer)",
"data": {
"app_id": "App ID",
"app_secret": "Secret"
diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py
index 01f737b47da..32121958b03 100644
--- a/homeassistant/helpers/config_validation.py
+++ b/homeassistant/helpers/config_validation.py
@@ -465,6 +465,15 @@ def string(value: Any) -> str:
return str(value)
+def string_with_no_html(value: Any) -> str:
+ """Validate that the value is a string without HTML."""
+ value = string(value)
+ regex = re.compile(r"<[a-z][\s\S]*>")
+ if regex.search(value):
+ raise vol.Invalid("the string should not contain HTML")
+ return str(value)
+
+
def temperature_unit(value: Any) -> str:
"""Validate and transform temperature unit."""
value = str(value).upper()
diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py
index 4c7fc12fbcb..416bfbdb47e 100644
--- a/script/hassfest/translations.py
+++ b/script/hassfest/translations.py
@@ -91,20 +91,20 @@ def gen_data_entry_schema(
"""Generate a data entry schema."""
step_title_class = vol.Required if require_step_title else vol.Optional
schema = {
- vol.Optional("flow_title"): str,
+ vol.Optional("flow_title"): cv.string_with_no_html,
vol.Required("step"): {
str: {
- step_title_class("title"): str,
- vol.Optional("description"): str,
- vol.Optional("data"): {str: str},
+ step_title_class("title"): cv.string_with_no_html,
+ vol.Optional("description"): cv.string_with_no_html,
+ vol.Optional("data"): {str: cv.string_with_no_html},
}
},
- vol.Optional("error"): {str: str},
- vol.Optional("abort"): {str: str},
- vol.Optional("create_entry"): {str: str},
+ vol.Optional("error"): {str: cv.string_with_no_html},
+ vol.Optional("abort"): {str: cv.string_with_no_html},
+ vol.Optional("create_entry"): {str: cv.string_with_no_html},
}
if flow_title == REQUIRED:
- schema[vol.Required("title")] = str
+ schema[vol.Required("title")] = cv.string_with_no_html
elif flow_title == REMOVED:
schema[vol.Optional("title", msg=REMOVED_TITLE_MSG)] = partial(
removed_title_validator, config, integration
@@ -117,7 +117,7 @@ def gen_strings_schema(config: Config, integration: Integration):
"""Generate a strings schema."""
return vol.Schema(
{
- vol.Optional("title"): str,
+ vol.Optional("title"): cv.string_with_no_html,
vol.Optional("config"): gen_data_entry_schema(
config=config,
integration=integration,
@@ -131,10 +131,10 @@ def gen_strings_schema(config: Config, integration: Integration):
require_step_title=False,
),
vol.Optional("device_automation"): {
- vol.Optional("action_type"): {str: str},
- vol.Optional("condition_type"): {str: str},
- vol.Optional("trigger_type"): {str: str},
- vol.Optional("trigger_subtype"): {str: str},
+ vol.Optional("action_type"): {str: cv.string_with_no_html},
+ vol.Optional("condition_type"): {str: cv.string_with_no_html},
+ vol.Optional("trigger_type"): {str: cv.string_with_no_html},
+ vol.Optional("trigger_subtype"): {str: cv.string_with_no_html},
},
vol.Optional("state"): cv.schema_with_slug_keys(
cv.schema_with_slug_keys(str, slug_validator=lowercase_validator),
@@ -203,7 +203,7 @@ def gen_platform_strings_schema(config: Config, integration: Integration):
)
-ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: str}})
+ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: cv.string_with_no_html}})
def validate_translation_file(config: Config, integration: Integration, all_strings):
diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py
index bee463092ff..72eb61bbacb 100644
--- a/tests/helpers/test_config_validation.py
+++ b/tests/helpers/test_config_validation.py
@@ -351,6 +351,26 @@ def test_string():
schema(value)
+def test_string_with_no_html():
+ """Test string with no html validation."""
+ schema = vol.Schema(cv.string_with_no_html)
+
+ with pytest.raises(vol.Invalid):
+ schema("This has HTML in it Link")
+
+ with pytest.raises(vol.Invalid):
+ schema("Bold")
+
+ for value in (
+ True,
+ 3,
+ "Hello",
+ "**Hello**",
+ "This has no HTML [Link](https://home-assistant.io)",
+ ):
+ schema(value)
+
+
def test_temperature_unit():
"""Test temperature unit validation."""
schema = vol.Schema(cv.temperature_unit)