Skip to content

Commit 8354571

Browse files
authored
count characters not bytes for UTF8String SIZE constraints (#14934)
1 parent bd45692 commit 8354571

3 files changed

Lines changed: 40 additions & 2 deletions

File tree

src/rust/src/declarative_asn1/decode.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ fn decode_pystr<'a>(
6565
annotation: &Annotation,
6666
) -> ParseResult<pyo3::Bound<'a, pyo3::types::PyString>> {
6767
let value = read_value::<asn1::Utf8String<'a>>(parser, &annotation.encoding)?;
68-
check_size_constraint(&annotation.size, value.as_str().len(), "UTF8String")?;
68+
check_size_constraint(
69+
&annotation.size,
70+
value.as_str().chars().count(),
71+
"UTF8String",
72+
)?;
6973
Ok(pyo3::types::PyString::new(py, value.as_str()))
7074
}
7175

src/rust/src/declarative_asn1/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
197197
Type::PyStr() => {
198198
let val: pyo3::pybacked::PyBackedStr = value.extract()?;
199199
let asn1_string: asn1::Utf8String<'_> = asn1::Utf8String::new(&val);
200-
check_size_constraint(&annotation.size, val.len(), "UTF8String")?;
200+
check_size_constraint(&annotation.size, val.chars().count(), "UTF8String")?;
201201
Ok(write_value(writer, &asn1_string, encoding)?)
202202
}
203203
Type::PrintableString() => {

tests/hazmat/asn1/test_serialization.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,40 @@ class Example:
14681468
]
14691469
)
14701470

1471+
def test_ok_string_size_counts_characters(self) -> None:
1472+
# "é€" is two characters but five UTF-8 bytes, so a SIZE constraint
1473+
# measured in characters must accept it under max=2.
1474+
@asn1.sequence
1475+
@_comparable_dataclass
1476+
class Example:
1477+
a: Annotated[str, asn1.Size(min=1, max=2)]
1478+
1479+
assert_roundtrips(
1480+
[
1481+
(
1482+
Example(a="é€"),
1483+
b"\x30\x07\x0c\x05\xc3\xa9\xe2\x82\xac",
1484+
)
1485+
]
1486+
)
1487+
1488+
def test_fail_string_size_counts_characters(self) -> None:
1489+
# "é€" is two characters; SIZE(min=3) must reject it even though it
1490+
# is five UTF-8 bytes.
1491+
@asn1.sequence
1492+
@_comparable_dataclass
1493+
class Example:
1494+
a: Annotated[str, asn1.Size(min=3, max=10)]
1495+
1496+
with pytest.raises(
1497+
ValueError,
1498+
match=re.escape("UTF8String has size 2, expected size in [3, 10]"),
1499+
):
1500+
asn1.decode_der(
1501+
Example,
1502+
b"\x30\x07\x0c\x05\xc3\xa9\xe2\x82\xac",
1503+
)
1504+
14711505
def test_ok_string_size_restriction_no_max(self) -> None:
14721506
@asn1.sequence
14731507
@_comparable_dataclass

0 commit comments

Comments
 (0)