[Notifier] Add TelegramOptions::disableMarkdownV2Escaping() - #65643
#65643Closed
nicolas-grekas wants to merge 1 commit into
symfony:8.2symfony/symfony:8.2from
nicolas-grekas:telegram-markdownv2-escaping-opt-outnicolas-grekas/symfony:telegram-markdownv2-escaping-opt-outCopy head branch name to clipboard
Closed
[Notifier] Add TelegramOptions::disableMarkdownV2Escaping()#65643nicolas-grekas wants to merge 1 commit intosymfony:8.2symfony/symfony:8.2from nicolas-grekas:telegram-markdownv2-escaping-opt-outnicolas-grekas/symfony:telegram-markdownv2-escaping-opt-outCopy head branch name to clipboard
TelegramOptions::disableMarkdownV2Escaping()#65643nicolas-grekas wants to merge 1 commit into
symfony:8.2symfony/symfony:8.2from
nicolas-grekas:telegram-markdownv2-escaping-opt-outnicolas-grekas/symfony:telegram-markdownv2-escaping-opt-outCopy head branch name to clipboard
Conversation
Member
Author
|
Closing in favor of #65644, which fixes the escaping itself on 6.4 instead of adding an opt-out. |
nicolas-grekas
added a commit
that referenced
this pull request
Aug 25, 2026
…ansport (nicolas-grekas) This PR was merged into the 6.4 branch. Discussion ---------- [Notifier] Fix escaping of MarkdownV2 markup in TelegramTransport | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #65626 | License | MIT When the parse mode is not set or is `MarkdownV2`, `TelegramTransport` escapes reserved characters in the message text before sending it. The list of characters it escapes was adjusted by hand three times (#41600, #42721, #58636) and still contains three markup markers of MarkdownV2: `~` (strikethrough), `|` (`||spoiler||`) and `>` (block quotation). These formats cannot be used at all. Text that is already escaped by hand is escaped a second time (`1\.2` becomes `1\\.2`), which Telegram rejects. This PR replaces the character list with the rule that Telegram's own parser applies: a reserved character is escaped when Telegram would reject it, and the markup is left alone. - Paired markers are never escaped: `*bold*`, `_italic_`, `__underline__`, `` `code` ``, `~strikethrough~`, `||spoiler||`, `[link](url)`. The first five were already left alone since #58636; `~` and `||` now follow the same rule. - `>` is left alone at the start of a line, where it starts a block quotation, including the `**>` form of expandable quotations. It is escaped anywhere else. - `!` is left alone in front of `[`, where it starts a custom emoji or a date-time entity. It is escaped anywhere else. - A character that is already escaped is kept as it is, so a message written as valid MarkdownV2 is sent unchanged. - `.`, `#`, `+`, `-`, `=`, `{`, `}` and a single `|` are escaped as before. It also fixes the character class of the old pattern: `+-=` was read as a range from `+` to `=`, so digits, `,`, `/`, `:`, `;` and `<` were escaped too. Telegram accepts a backslash in front of any of them, so nothing was visible in the chat, but every digit counted twice towards the 4096-character limit. Behavior change for plain-text senders: a message with an unescaped `~` or `||`, or a line starting with `>`, is now sent as markup, the same way `*` and `_` are since #58636. Plain text is sent as before when these characters are escaped in the message, or with the `HTML` parse mode. Checks run: - `./phpunit src/Symfony/Component/Notifier/Bridge/Telegram` on 6.4: OK, 70 tests, 161 assertions, 1 skipped (pre-existing). - Before the fix, the new case table fails on its first case and the existing escaping test fails on `\~`; both pass after it. - `php-cs-fixer fix --dry-run` on the touched files: clean. Not covered: no call to the real Telegram API was made. The rules follow the MarkdownV2 section of the Bot API documentation. Replaces #65643. Commits ------- 8ce13ef [Notifier] Fix escaping of MarkdownV2 markup in TelegramTransport
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the parse mode is not set or is
MarkdownV2,TelegramTransportescapes the characters. ! # > + - = | { } ~in the message text before sending it. This makes plain text work without any care from the caller. But>,||and~are MarkdownV2 markup (block quotation, spoiler, strikethrough), so these three formats cannot be used at all. Text that is already escaped by hand breaks too:1\.2is turned into1\\.2, which Telegram rejects as an unescaped.after a literal backslash.This PR adds
TelegramOptions::disableMarkdownV2Escaping(bool $bool = true). When it is set, the transport sends the message text exactly as written. The text must then be valid MarkdownV2, including the escaping of the special characters that are not part of the markup. The default behavior is unchanged. The option is consumed by the transport and is not sent to the Telegram API. It has no effect with theHTMLandMarkdownparse modes, which are never escaped.Why an opt-out rather than a change of the escaped set: the set was already reshaped twice as a bug fix (#42721, #58636), and each change broke messages that relied on the previous behavior. Removing
>,|and~from it would make a plaina > bor~5 minfail with a 400 from Telegram after a patch release, and the same change would be needed again for every syntax Telegram adds (block quotations and spoilers were added to the Bot API after the escaping was written). The transport cannot tell markup from plain text; with this option, the caller decides.Alternatives with the existing API, checked before adding the method:
parseMode('markdownv2')in lowercase, reported as a workaround in [Notifier] [Telegram] MARKDOWN_V2 is broken #51330, skips the escaping only because the transport compares the mode case-sensitively. It relies on an implementation detail and on Telegram accepting the value regardless of case.parseMode(TelegramOptions::PARSE_MODE_HTML)supports<blockquote>,<tg-spoiler>and<s>and is never escaped, but it does not help anyone who writes MarkdownV2.Checks run:
./phpunit src/Symfony/Component/Notifier/Bridge/Telegram: OK (101 tests, 221 assertions).TelegramOptionsmethod present, the two transport tests fail on the escaped text and on the option leaking into the request body.php-cs-fixer fix --dry-runon the touched files: clean.Not covered: no call to the real Telegram API was made.
For the documentation: the README of the bridge gets a section in this PR.
notifier.rsthas nothing on Telegram escaping today; it should get the same points: the default escaping and its character set,disableMarkdownV2Escaping(), and the rule that the text must then be valid MarkdownV2.