From 9cc87b2c2b8a1f66baea1ef624a2b9370eccbe13 Mon Sep 17 00:00:00 2001 From: ShipItChief Date: Sun, 19 Apr 2026 10:07:06 +0000 Subject: [PATCH] fix(parser): handle bytes input in escape_char using to_unicode escape_char was annotated to accept str | bytes but used str.replace() operations directly, causing TypeError when bytes input contained characters that needed escaping (comma, semicolon, backslash, newline). Use to_unicode() to convert bytes to str before applying replacements, matching the approach used in unescape_char. This also corrects the return type from str | bytes to str. Adds test_escape_char_bytes verifying all escape patterns work with bytes input. Closes #1226 --- CHANGES.rst | 1 + src/icalendar/parser/string.py | 6 +++--- src/icalendar/tests/test_parsing.py | 13 ++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 745f16d9b..20c210195 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -42,6 +42,7 @@ New features Bug fixes ~~~~~~~~~ +- Fixed :func:`~icalendar.parser.string.escape_char` to accept :class:`bytes` input by converting it to :class:`str` with :func:`~icalendar.parser_tools.to_unicode` before applying replacements. :issue:`1226` - ... Documentation diff --git a/src/icalendar/parser/string.py b/src/icalendar/parser/string.py index 209503831..9242c09c7 100644 --- a/src/icalendar/parser/string.py +++ b/src/icalendar/parser/string.py @@ -2,10 +2,10 @@ import re -from icalendar.parser_tools import DEFAULT_ENCODING +from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode -def escape_char(text: str | bytes) -> str | bytes: +def escape_char(text: str | bytes) -> str: r"""Format value according to iCalendar TEXT escaping rules. Escapes special characters in text values according to :rfc:`5545#section-3.3.11` @@ -29,7 +29,7 @@ def escape_char(text: str | bytes) -> str | bytes: 6. ``"\n"`` -> ``r"\n"`` (transform a newline character to a literal, or raw, newline character) """ - assert isinstance(text, (str, bytes)) + text = to_unicode(text) # NOTE: ORDER MATTERS! return ( text.replace(r"\N", "\n") diff --git a/src/icalendar/tests/test_parsing.py b/src/icalendar/tests/test_parsing.py index 3a73badc3..e2283fe10 100644 --- a/src/icalendar/tests/test_parsing.py +++ b/src/icalendar/tests/test_parsing.py @@ -9,7 +9,7 @@ from icalendar.cal.calendar import Calendar from icalendar.cal.component_factory import ComponentFactory from icalendar.cal.event import Event -from icalendar.parser import Contentline, Parameters, unescape_char +from icalendar.parser import Contentline, Parameters, unescape_char, escape_char @pytest.mark.parametrize( @@ -280,3 +280,14 @@ def test_create_a_component(): my_component_class = factory.get_component_class("My-Component") assert my_component_class.name == "MY-COMPONENT" assert my_component_class.__name__ == "MyComponent" + + +def test_escape_char_bytes(): + """Test that escape_char accepts bytes input and returns str.""" + assert escape_char(b"hello,world") == "hello\\,world" + assert escape_char(b"hello;world") == "hello\\;world" + assert escape_char(b"hello\\world") == "hello\\\\world" + assert escape_char(b"hello\nworld") == "hello\\nworld" + assert escape_char(b"hello\r\nworld") == "hello\\nworld" + assert escape_char(b"hello\\Nworld") == "hello\\nworld" + assert escape_char("hello,world") == "hello\\,world"