Skip to content

[Bug] unknown value type is escaped/unescaped instead of kept verbatim, breaking round-trips #1445

Description

@lcampanella98

Describe the bug

When a property's value type is unknown (unrecognized properties, including
X- properties without a VALUE parameter), the value seems to be run through
RFC 5545 TEXT escaping rules instead of being kept as written. As I read
RFC 7265 §5.1/§5.2, an unknown value should be passed through "without
processing."

The problem does not look limited to jCal. In my testing, the value changes in
three places:

  1. Just reading it — the value I get back from from_ical is already
    different from the source, before I write anything out.
  2. iCal → iCal round-trip — iCalendar in, iCalendar out, no jCal involved;
    backslashes are added or removed.
  3. iCal → jCal — the case RFC 7265 §5.1 warns "breaks round-tripping
    values."

I think the jCal case (3) is a clear RFC 7265 violation. I am less sure about
the plain-iCal cases (1, 2).

To reproduce

from icalendar.cal import Component

orig = r"""BEGIN:VEVENT
UID:1
X-MYSTERY:a;b\,c\:d\ne
END:VEVENT
"""
ev = Component.from_ical(orig)

print(repr(str(ev["X-MYSTERY"])))      # just reading it
print(ev.to_ical().decode())           # iCal -> iCal
print(ev.to_jcal())                     # iCal -> jCal

Output:

'a;b,c:d\ne'             # read:  \, -> ,   \: -> :   \n -> real newline
BEGIN:VEVENT
UID:1
X-MYSTERY:a\;b\,c:d\ne   # iCal:  ; -> \;   \, kept   \: -> :
END:VEVENT

['vevent', [['uid', {}, 'text', '1'], ['x-mystery', {}, 'unknown', 'a;b,c:d\ne']], []]
# jCal:  \, -> ,  (kept in iCal above, lost here -> breaks round-tripping, RFC 7265 §5.3)

The source value a;b\,c\:d\ne is preserved by none of the three. The escaped
\, is the clearest case — it survives the iCal → iCal round-trip but loses its
backslash in jCal. Across my tests the affected characters were ,, ;, \,
:, and newlines; others came through unchanged, though I have not tried every
input.

Expected behavior

As I read RFC 7265 §5.1/§5.2 and the §5.3 example, an unknown value should
come through exactly as written, in both directions. For the input above that
would mean:

>>> str(ev["X-MYSTERY"])
'a;b\\,c\\:d\\ne'
>>> print(ev.to_ical().decode())
...
X-MYSTERY:a;b\,c\:d\ne
...
>>> ev.to_jcal()
['vevent', [['uid', {}, 'text', '1'], ['x-mystery', {}, 'unknown', 'a;b\\,c\\:d\\ne']], []]

i.e. no RFC 5545 escaping or unescaping for unknown values either way (only
JSON string escaping, which the JSON layer does on its own).

The RFC 7265 §5.3 example shows the same idea — the decoded jCal value matches
the iCalendar value exactly:

value
iCalendar Stenophylla;Guinea\,Africa
jCal (decoded) Stenophylla;Guinea\,Africa

Environment

  • Operating system: Ubuntu (WSL2)
  • Python version: Python 3.14.5
  • icalendar version: 7.1.2

Additional context

One case I have not worked out: a jCal unknown string could hold a character
(such as a raw line break) that RFC 5545 may not be able to put in a bare
value, so passing it through unchanged could clash with producing valid
iCalendar. The RFC examples do not seem to cover this.

  • I tested it with the latest version (current main).
  • I attached the .ics sample file, or there is no .ics sample file. (Reproducer is inline above.)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions