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)