Skip to content

Commit c1835ac

Browse files
Merge pull request #850 from niccokunzmann/issue-836
Remove quotes from safe characters
2 parents 5fc38a8 + 7e8abb5 commit c1835ac

File tree

4 files changed

+139
-17
lines changed

4 files changed

+139
-17
lines changed

CHANGES.rst

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,42 @@ We use `Semantic Versioning <https://semver.org>`_.
77
- New features increase the **minor** version number.
88
- Minor changes and bug fixes increase the **patch** version number.
99

10-
6.3.0 (2025-05-15)
10+
6.3.1 (unreleased)
1111
------------------
1212

1313
Minor changes:
1414

15-
- Deprecate ``icalendar.UIDGenerator``. See `Issue 816 <https://github.com/collective/icalendar/issues/816>`_.
15+
- ...
16+
17+
Breaking changes:
18+
19+
- ...
1620

1721
New features:
1822

19-
- Add the ``uid`` property to ``Alarm``, ``Event``, ``Calendar``, ``Todo``, and ``Journal`` components. See `Issue 740 <https://github.com/collective/icalendar/issues/740>`_.
23+
- ...
2024

2125
Bug fixes:
2226

23-
- Fix component equality where timezones differ for the datetimes but the times are actually equal. See `Issue 828 <https://github.com/collective/icalendar/issues/828>`_.
24-
- Test that we can add an RRULE as a string. See `Issue 301 <https://github.com/collective/icalendar/issues/301>`_.
25-
- Test that we support dateutil timezones as outlined in `Issue 336 <https://github.com/collective/icalendar/issues/336>`_.
26-
- Build documentation on Read the Docs with the version identifier. See `Issue 826 <https://github.com/collective/icalendar/issues/826>`_.
27+
- Remove forced quoting from parameters with space and single quote. See `Issue 836 <https://github.com/collective/icalendar/issues/836>`_.
2728

28-
6.3.1 (unreleased)
29+
6.3.0 (2025-05-15)
2930
------------------
3031

3132
Minor changes:
3233

33-
- ...
34-
35-
Breaking changes:
36-
37-
- ...
34+
- Deprecate ``icalendar.UIDGenerator``. See `Issue 816 <https://github.com/collective/icalendar/issues/816>`_.
3835

3936
New features:
4037

41-
- ...
38+
- Add the ``uid`` property to ``Alarm``, ``Event``, ``Calendar``, ``Todo``, and ``Journal`` components. See `Issue 740 <https://github.com/collective/icalendar/issues/740>`_.
4239

4340
Bug fixes:
4441

45-
- ...
42+
- Fix component equality where timezones differ for the datetimes but the times are actually equal. See `Issue 828 <https://github.com/collective/icalendar/issues/828>`_.
43+
- Test that we can add an RRULE as a string. See `Issue 301 <https://github.com/collective/icalendar/issues/301>`_.
44+
- Test that we support dateutil timezones as outlined in `Issue 336 <https://github.com/collective/icalendar/issues/336>`_.
45+
- Build documentation on Read the Docs with the version identifier. See `Issue 826 <https://github.com/collective/icalendar/issues/826>`_.
4646

4747
6.2.0 (2025-05-07)
4848
------------------

src/icalendar/parser.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def validate_param_value(value, quoted=True):
129129

130130
# chars presence of which in parameter value will be cause the value
131131
# to be enclosed in double-quotes
132-
QUOTABLE = re.compile("[,;: ’']")
132+
QUOTABLE = re.compile("[,;:]")
133133

134134

135135
def dquote(val, always_quote=False):
@@ -184,7 +184,15 @@ class Parameters(CaselessDict):
184184
"DIR",
185185
"MEMBER",
186186
"SENT-BY",
187+
# Part of X-APPLE-STRUCTURED-LOCATION
188+
"X-ADDRESS",
189+
"X-TITLE",
187190
)
191+
# this is quoted should one of the values be present
192+
quote_also = {
193+
# This is escaped in the RFC
194+
"CN" : " '",
195+
}
188196

189197
def params(self):
190198
"""In RFC 5545 keys are called parameters, so this is to be consitent
@@ -219,7 +227,14 @@ def to_ical(self, sorted=True):
219227

220228
for key, value in items:
221229
upper_key = key.upper()
222-
quoted_value = param_value(value, always_quote=upper_key in self.always_quoted)
230+
check_quoteable_characters = self.quote_also.get(key.upper())
231+
always_quote = (
232+
upper_key in self.always_quoted or (
233+
check_quoteable_characters and
234+
any(c in value for c in check_quoteable_characters)
235+
)
236+
)
237+
quoted_value = param_value(value, always_quote=always_quote)
223238
if isinstance(quoted_value, str):
224239
quoted_value = quoted_value.encode(DEFAULT_ENCODING)
225240
# CaselessDict keys are always unicode
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
BEGIN:VCALENDAR
2+
METHOD:PUBLISH
3+
PRODID:Microsoft Exchange Server 2010
4+
VERSION:2.0
5+
BEGIN:VTIMEZONE
6+
TZID:Eastern Standard Time
7+
BEGIN:STANDARD
8+
DTSTART:16010101T020000
9+
TZOFFSETFROM:-0400
10+
TZOFFSETTO:-0500
11+
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
12+
END:STANDARD
13+
BEGIN:DAYLIGHT
14+
DTSTART:16010101T020000
15+
TZOFFSETFROM:-0500
16+
TZOFFSETTO:-0400
17+
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
18+
END:DAYLIGHT
19+
END:VTIMEZONE
20+
BEGIN:VEVENT
21+
22+
SUMMARY:Anonymous Test Event for TZID
23+
DTSTART;TZID=Eastern Standard Time:20241028T170000
24+
DTEND;TZID=Eastern Standard Time:20241028T180000
25+
DTSTAMP:20250514T023916Z
26+
END:VEVENT
27+
END:VCALENDAR
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Quoting the TZID parameter creates compatibility problems.
2+
3+
See https://github.com/collective/icalendar/issues/836
4+
5+
:rfc:`5545`:
6+
7+
.. code-block:: text
8+
9+
param-value = paramtext / quoted-string
10+
paramtext = *SAFE-CHAR
11+
quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
12+
SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
13+
/ NON-US-ASCII
14+
; Any character except CONTROL, DQUOTE, ";", ":", ","
15+
NON-US-ASCII = UTF8-2 / UTF8-3 / UTF8-4
16+
; UTF8-2, UTF8-3, and UTF8-4 are defined in [RFC3629]
17+
18+
"""
19+
from datetime import datetime, time
20+
from icalendar import Event, Parameters, vDDDTypes, vDatetime, vPeriod, vTime
21+
import pytest
22+
23+
from icalendar.prop import vDDDLists
24+
25+
# All the controls except HTAB
26+
CONTROL = {
27+
i for i in range(256) if 0x00 <= i <= 0x08 or 0x0A <= i <= 0x1F or i == 0x7F
28+
}
29+
30+
# Any character except CONTROL, DQUOTE, ";", ":", ","
31+
SAFE_CHAR = set(range(256)) - CONTROL - set(b'";:,')
32+
33+
param_tzid = pytest.mark.parametrize(
34+
"tzid",
35+
[
36+
"Europe/London",
37+
"Eastern Standard Time",
38+
]
39+
)
40+
41+
@param_tzid
42+
@pytest.mark.parametrize(
43+
"vdt",
44+
[
45+
vDatetime(datetime(2024, 10, 11, 12, 0)),
46+
vTime(time(23, 59, 59)),
47+
vDDDTypes(datetime(2024, 10, 11, 12, 0)),
48+
vPeriod((datetime(2024, 10, 11, 12, 0), datetime(2024, 10, 11, 13, 0))),
49+
vDDDLists([datetime(2024, 10, 11, 12, 0)]),
50+
]
51+
)
52+
def test_parameter_is_not_quoted_when_not_needed(tzid, vdt):
53+
"""Check that serializing the value works without quoting."""
54+
e = Event()
55+
vdt.params["TZID"] = tzid
56+
e["DTSTART"] = vdt
57+
ics = e.to_ical().decode()
58+
print(ics)
59+
assert tzid in ics
60+
assert f'"{tzid}' not in ics
61+
assert f'{tzid}"' not in ics
62+
63+
64+
@pytest.mark.parametrize(
65+
"safe_char",
66+
list(map(chr, sorted(SAFE_CHAR)))
67+
)
68+
def test_safe_char_is_not_escaped(safe_char):
69+
"""Check that paramerter serialization is without quotes for safe chars."""
70+
params = Parameters(tzid=safe_char)
71+
result = params.to_ical().decode()
72+
assert '"' not in result
73+
74+
75+
def test_get_calendar_and_serialize_it_wihtout_quotes(calendars):
76+
"""The example calendar should not contain the timezone with quotes."""
77+
ics = calendars.issue_836_do_not_quote_tzid.to_ical().decode()
78+
assert '"Eastern Standard' not in ics
79+
assert 'Standard Time"' not in ics
80+
assert "Eastern Standard Time" in ics

0 commit comments

Comments
 (0)