Skip to content

Sanitize RFC 5545 control characters in TEXT on output (#1712) - #1741

Open
Dmao233 wants to merge 7 commits into
collective:mainfrom
Dmao233:cursor/fix-text-nul-sanitize-output-7468
Open

Sanitize RFC 5545 control characters in TEXT on output (#1712)#1741
Dmao233 wants to merge 7 commits into
collective:mainfrom
Dmao233:cursor/fix-text-nul-sanitize-output-7468

Conversation

@Dmao233

@Dmao233 Dmao233 commented Aug 30, 2026

Copy link
Copy Markdown

Linked issue

Description

#1723 rejects control characters on parse. That still lets round-trip emit NUL, and it fails callers on junk files. nicco asked to keep parse open and emit valid TEXT. This strips leftover CONTROLs on output. Parameter NULs still go into errors.

Checklist

  • I added a change log entry, following the instructions in all subsections under Change log requirements.
  • I followed icalendar's Artificial intelligence policy and disclosed my Responsible AI use in my commit messages, if applicable.
  • I added or updated tests, if applicable.
  • I ran and ensured all tests pass locally by following Run tests.
  • I added or edited documentation as necessary, both as docstrings to be rendered in the API documentation and narrative documentation, following the Style guide.

Additional information

I used AI to help draft the patch. I read the issue thread and checked the tests, including the reporter repro.

@github-actions github-actions Bot added the ai-suspicion This contribution is possibly created with lots of AI help without enough human understanding. label Aug 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request did not pass quality checks and AI use is suspected. Please review Contribute and make any necessary amendments.

@github-actions

Copy link
Copy Markdown
Contributor

Profile summary:

GitHub user: Dmao233
🟡 Some concerns found with user's profile.
🟡 Some concerns found with recent PR activity.
🟢 No concerns found with recent issue activity.

For a more detailed report, run `gh-profiler Dmao233`.
Full profile
GitHub user: Dmao233
🟡 Some concerns found with user's profile.
   🟡 Account age: 2 years
   🟢 Profile information:
        name: CenFangyu
        blog: https://x.com/cenfangyu17
        twitter: https://x.com/cenfangyu17
      Empty fields: company, location, email, bio

🟡 Some concerns found with recent PR activity.
   66 PRs opened in the last 21 days.
      4 opened against repos the user owns.
      0 opened against repos in publicly associated orgs.
      62 opened against external repos.

   🟡 27 of 62 external PRs closed without merging in the last 21 days.

🟢 No concerns found with recent issue activity.
   3 new issues opened in the last 21 days.
      0 opened in repos the user owns.
      0 opened in repos in publicly associated orgs.
      3 opened in external repos.

   🟢 0 external issues closed as NOT_PLANNED.
   🟢 0 external issues opened with the same title.

@read-the-docs-community

read-the-docs-community Bot commented Aug 30, 2026

Copy link
Copy Markdown

Documentation build overview

📚 icalendar | 🛠️ Build #34322977 | 📁 Comparing cada72d against latest (d157305)

  🔍 Preview build  

3 files changed
± genindex.html
± reference/api/icalendar.prop.text.html
± _modules/icalendar/prop/text.html

cursor Bot pushed a commit to Dmao233/icalendar that referenced this pull request Aug 30, 2026
CIFuzz failed after parse stayed open: dateutil.tzical raised
ValueError: unsupported property: RRULE, which was not ignored.

AI use: Cursor Grok 4.6
Prompt: Add newly-exposed dateutil parse errors to the fuzzer ignore list after CIFuzz failed on collective#1741.
Parse stays open; leftover CONTROLs are stripped on serialize so NUL cannot reach ICS output.

AI use: Cursor Grok 4.6
Prompt: Fix NUL in TEXT values by sanitizing on output per collective#1712.
CIFuzz reached dateutil after parse stayed open; ignore those expected ValueErrors.

AI use: Cursor Grok 4.6
Prompt: Add dateutil parse errors to the fuzzer ignore list after CI failed.
@cursor
cursor Bot force-pushed the cursor/fix-text-nul-sanitize-output-7468 branch from e81edf6 to e3cf05a Compare August 30, 2026 16:30
@Dmao233

Dmao233 commented Aug 30, 2026

Copy link
Copy Markdown
Author

Quality bot failed on description length, missing checklist boxes, commit author not matching, and a first commit over 500 chars.

Description is back on the template. Commits rewritten as me: d242400f, e3cf05ae.

It also flags the username Dmao233 for consecutive digits. I cannot change that.

@stevepiercy

Copy link
Copy Markdown
Member

@Dmao233 there's no need to repeat anything from the comments made by automation or in pushed commits. Maintainers receive an email notification for each comment and pushed commit, as well as view the results of CI.

We need to do a better job of clarifying that in our contributing documentation, as many new contributors don't understand that AI is incredibly noisy and states the obvious.

And there's no need to reply to this comment, as that would be yet more noise, but you can use reactions. Thank you!

@stevepiercy

Copy link
Copy Markdown
Member

@Dmao233 the CI fuzzing check fails. We don't review pull requests until CI checks pass. Would you please take care? Thank you!

A malformed RRULE key reached to_ical() with a raw LF after parse stayed open. from_parts now strips leftover CR/LF so serialize cannot hit the Contentline assert.

AI use: Cursor Grok 4.6
Prompt: Stop CIFuzz AssertionError on unescaped newlines in content lines for collective#1741.
from_parts must keep failing loudly (issue collective#1445). CIFuzz hit that assert on a malformed RRULE; treat only that message as an expected parse/serialize error.

AI use: Cursor Grok 4.6
Prompt: Revert silent newline stripping and ignore only the Contentline raw-newline assert in the fuzzer.
@Dmao233

Dmao233 commented Aug 31, 2026

Copy link
Copy Markdown
Author

Fuzzing is green now.

@angatha angatha left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution. I have some suggestions and questions.

Comment thread src/icalendar/tests/fuzzed/__init__.py Outdated
"mandatory TZOFFSETFROM", # dateutil tzical
"no timezones defined", # dateutil tzical
"more than one timezone available", # dateutil tzical
"Unsupported DTSTART param", # dateutil tzical

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did thouse became a problem now?

# control character except the horizontal tab may appear in a TEXT value.
# The line feed, ``\x0a``, is additionally accepted here because it is the
# result of the escaped sequences ``\N`` and ``\n``.
UNSAFE_TEXT_CHARS = re.compile(r"[\x00-\x08\x0b-\x1f\x7f]")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. Please keep it.

_UNSAFE_TEXT_CHARS = re.compile(r"[\x00-\x08\x0b-\x1f\x7f]")


def _escape_char(text: str | bytes) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method name and description does not suggest character filtering. Additionally there is _unescape_char in string.py, which should do the inverse. I would prefere another method for that, or you move this to vText.__init__. Then it would also catch to_jcal.

Comment thread src/icalendar/parser/string.py Outdated
# CONTROL except HTAB (see :rfc:`5545#section-3.1`). After the line-break
# escapes below, any remaining CONTROL other than HTAB is stripped so
# serialized output never contains a raw control character.
_UNSAFE_TEXT_CHARS = re.compile(r"[\x00-\x08\x0b-\x1f\x7f]")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rever this and use the constant defined in text.py

Comment thread src/icalendar/tests/fuzzed/__init__.py Outdated
Comment on lines +79 to +84
# Contentline.__new__ fails loudly on a raw LF (issue #1445). That is
# expected for non-TEXT values such as a malformed RRULE; do not treat
# it as a fuzzer crash. Other asserts still propagate.
if _CONTENTLINE_NEWLINE_ASSERT in str(e):
return -1
raise

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this now a problem?

FORBIDDEN_VALUES = [
*FORBIDDEN_CONTROL_CHARS,
"A\x00B", # NUL from the issue report
"a\rb", # lone carriage return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Dmao233 and others added 3 commits September 1, 2026 01:21
Keep the public UNSAFE_TEXT_CHARS name. Filter in vText.__new__ so to_ical and to_jcal stay clean; leave _escape_char as an escaper. Drop unproven fuzzer ignores.

AI use: Cursor Grok 4.6
Prompt: Address angatha review on collective#1741: restore UNSAFE_TEXT_CHARS and move filtering out of _escape_char.
Use a raw docstring for _strip_unsafe_text_chars. Tighten the two remaining fuzzer ignore comments to the proven CIFuzz exceptions.

AI use: Cursor Grok 4.6
Prompt: Fix ruff D301 and confirm remaining collective#1741 review items.
)

to_unicode leaves UUID/int/None unchanged; Event.new and vUid.new pass those into vText. Coerce to str so construction matches the old str.__new__ behavior.

AI use: Cursor Grok 4.6
Prompt: Fix ruff D301 and the 3.14 UUID/int/None AttributeError from vText sanitization.

Co-authored-by: CenFangyu <Dmao233@users.noreply.github.com>
@Dmao233

Dmao233 commented Sep 1, 2026

Copy link
Copy Markdown
Author

Addressed in 278d267 and cada72d.

  • Kept public UNSAFE_TEXT_CHARS; filter moved to vText.__new__ (covers to_ical and to_jcal). _escape_char is escape-only again.
  • Restored the control-char cases that still apply under sanitize-on-construct.
  • Fuzzer: only kept proven ignores (unsupported property, Contentline raw-newline assert for [Bug] unknown value type is escaped/unescaped instead of kept verbatim, breaking round-trips #1445). Dropped the broad dateutil ones.
  • Also coerce non-str TEXT inputs before strip so vUid.new / Event.new (UUID) keep working on 3.14.

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

Labels

ai-suspicion This contribution is possibly created with lots of AI help without enough human understanding.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] NUL bytes in TEXT property values pass through to serialized output

3 participants