Skip to content

Commit f63135a

Browse files
committed
Reject non-ASCII case equivalents in country and currency codes
With ignore_case=True, country_code() and currency() compared value.upper() against the ISO lists. str.upper() maps letters such as dotless i (U+0131), long s (U+017F) and the fi ligature (U+FB01) onto ASCII, so "ıt", "ıta", "fiN" and "uſd" were accepted as IT, ITA, FIN and USD. Require ASCII input before case-insensitive matching.
1 parent 22786cc commit f63135a

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

‎src/validators/country.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ def country_code(value: str, /, *, iso_format: str = "auto", ignore_case: bool =
299299
if not value:
300300
return False
301301

302-
if not (1 < len(value) < 4):
302+
# str.upper() would map e.g. dotless i and long s onto ASCII letters
303+
if not (1 < len(value) < 4) or not value.isascii():
303304
return False
304305

305306
if iso_format == "auto" and (iso_format := _get_code_type(value)) == "invalid":
@@ -349,7 +350,8 @@ def currency(value: str, /, *, skip_symbols: bool = True, ignore_case: bool = Fa
349350
if not skip_symbols and value in _currency_symbols:
350351
return True
351352

352-
if len(value) != 3:
353+
# str.upper() would map e.g. dotless i and long s onto ASCII letters
354+
if len(value) != 3 or not value.isascii():
353355
return False
354356

355357
return value.upper() in _currency_iso4217 if ignore_case else value in _currency_iso4217

‎tests/test_country.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ def test_returns_failed_validation_on_invalid_country_code(value: str, iso_forma
5555
assert isinstance(country_code(value, iso_format=iso_format), ValidationError)
5656

5757

58+
@pytest.mark.parametrize(
59+
("value", "iso_format"),
60+
[
61+
("\u0131t", "auto"),
62+
("\u017fe", "alpha2"),
63+
("\u0131ta", "auto"),
64+
("\ufb01N", "alpha3"),
65+
],
66+
)
67+
def test_returns_failed_validation_on_non_ascii_case_equivalent_country_code(
68+
value: str, iso_format: str
69+
):
70+
"""Test returns failed validation when Unicode case mapping yields an ASCII code."""
71+
assert isinstance(country_code(value, iso_format=iso_format, ignore_case=True), ValidationError)
72+
73+
5874
@pytest.mark.parametrize(
5975
("value", "skip_symbols", "ignore_case"),
6076
[("$", False, False), ("⃀", False, False), ("৳", False, False), ("uSd", True, True)],
@@ -73,6 +89,8 @@ def test_returns_true_on_valid_currency(value: str, skip_symbols: bool, ignore_c
7389
("⃀৳", False, False),
7490
("uSd", True, False),
7591
("Bucks", True, True),
92+
("u\u017fd", True, True),
93+
("\u0131ls", True, True),
7694
],
7795
)
7896
def test_returns_failed_validation_invalid_currency(

0 commit comments

Comments
 (0)