1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Hassfest: Don't allow placeholders in state translations (#161447)

This commit is contained in:
karwosts
2026-01-23 12:26:40 -08:00
committed by GitHub
parent d66c7bf38b
commit 846b139e05

View File

@@ -127,17 +127,18 @@ def translation_key_validator(value: str) -> str:
return value
def translation_value_validator(value: Any) -> str:
def validate_translation_value(value: Any, allow_placeholders=True) -> str:
"""Validate that the value is a valid translation.
- prevents string with HTML
- prevents strings with single quoted placeholders
- prevents strings with placeholders using invalid identifiers
- prevents placeholders where they are not allowed
- prevents combined translations
"""
string_value = cv.string_with_no_html(value)
string_value = string_no_single_quoted_placeholders(string_value)
string_value = validate_placeholders(string_value)
string_value = validate_placeholders(string_value, allow_placeholders)
if RE_COMBINED_REFERENCE.search(string_value):
raise vol.Invalid("the string should not contain combined translations")
if string_value != string_value.strip():
@@ -145,6 +146,20 @@ def translation_value_validator(value: Any) -> str:
return string_value
def translation_value_validator(value: Any) -> str:
"""Validate translation value with default options."""
return validate_translation_value(value)
def custom_translation_value_validator(allow_placeholders=True):
"""Validate translation value with custom options."""
def _validator(value: Any) -> str:
return validate_translation_value(value, allow_placeholders)
return _validator
def string_no_single_quoted_placeholders(value: str) -> str:
"""Validate that the value does not contain placeholders inside single quotes."""
if RE_PLACEHOLDER_IN_SINGLE_QUOTES.search(value):
@@ -154,12 +169,14 @@ def string_no_single_quoted_placeholders(value: str) -> str:
return value
def validate_placeholders(value: str) -> str:
def validate_placeholders(value: str, allow_placeholders: bool) -> str:
"""Validate that placeholders in translations use valid identifiers."""
formatter = string.Formatter()
for _, field_name, _, _ in formatter.parse(value):
if field_name: # skip literal text segments
if not allow_placeholders:
raise vol.Invalid("placeholders are not supported in this value")
if not field_name.isidentifier():
raise vol.Invalid(
"placeholders must be valid identifiers ([a-zA-Z_][a-zA-Z0-9_]*)"
@@ -408,14 +425,16 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
{
vol.Optional("name"): str,
vol.Optional("state"): cv.schema_with_slug_keys(
translation_value_validator,
custom_translation_value_validator(allow_placeholders=False),
slug_validator=translation_key_validator,
),
vol.Optional("state_attributes"): cv.schema_with_slug_keys(
{
vol.Optional("name"): str,
vol.Optional("state"): cv.schema_with_slug_keys(
translation_value_validator,
custom_translation_value_validator(
allow_placeholders=False
),
slug_validator=translation_key_validator,
),
},
@@ -435,14 +454,22 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
{
vol.Optional("name"): translation_value_validator,
vol.Optional("state"): cv.schema_with_slug_keys(
translation_value_validator,
custom_translation_value_validator(
allow_placeholders=False
),
slug_validator=translation_key_validator,
),
vol.Optional("state_attributes"): cv.schema_with_slug_keys(
{
vol.Optional("name"): translation_value_validator,
vol.Optional(
"name"
): custom_translation_value_validator(
allow_placeholders=False
),
vol.Optional("state"): cv.schema_with_slug_keys(
translation_value_validator,
custom_translation_value_validator(
allow_placeholders=False
),
slug_validator=translation_key_validator,
),
},
@@ -450,7 +477,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
),
vol.Optional(
"unit_of_measurement"
): translation_value_validator,
): custom_translation_value_validator(allow_placeholders=False),
},
slug_validator=translation_key_validator,
),