Skip to content

ext/mbstring: Don't emit surrogates when encoding to UTF-8 - #23959

Closed
nicolas-grekas wants to merge 1 commit into
php:PHP-8.4from
nicolas-grekas:mbstring-utf8-surrogates
Closed

nicolas-grekas wants to merge 1 commit into
php:PHP-8.4from
nicolas-grekas:mbstring-utf8-surrogates

Conversation

@nicolas-grekas

Copy link
Copy Markdown
Contributor

mb_convert_encoding("\0\0\xD8\0", 'UTF-8', 'UCS-4') returns "\xED\xA0\x80", which isn't valid UTF-8. Since 4427b2e (PHP 8.3), mbstring also flags its UTF-8 output as valid, so mb_check_encoding(), mb_scrub(), preg_match('//u') and others accept that string while they reject the same bytes written as a literal. The same happens with UCS-2 and HTML-ENTITIES input, mb_convert_variables(), mb_decode_numericentity() and mb_decode_mimeheader().

The UTF-8 encoder now replaces surrogates with the substitute character, as it already does for codepoints above U+10FFFF. The internal mode used by mb_strpos() and friends keeps encoding them, so that different surrogates in a UCS-2 string still don't match each other.

Found while working on #23957.

@alexdowad

Copy link
Copy Markdown
Contributor

Thanks for the great catch! This looks right to me.
@youkidearitai Anything I'm missing?

@youkidearitai youkidearitai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, Thanks!
Please add also NEWS entry.

The UTF-8 encoder turned surrogate codepoints into 3-byte sequences like
"\xED\xA0\x80", which are not valid UTF-8. Such codepoints come from
UCS-2, UCS-4 or HTML-ENTITIES input, or from mb_decode_numericentity().
Since mbstring marks the UTF-8 strings it returns as valid UTF-8,
functions that trust that flag, like mb_check_encoding(), mb_scrub() or
preg_match(), accepted them too.

Surrogates are now replaced by the substitute character, as codepoints
above U+10FFFF already are. The internal mode used by mb_strpos() and
similar functions still encodes them, so that different surrogates don't
match each other.
@nicolas-grekas
nicolas-grekas force-pushed the mbstring-utf8-surrogates branch from 550f4c4 to 89246d2 Compare September 28, 2026 08:33
@nicolas-grekas

Copy link
Copy Markdown
Contributor Author

Please add also NEWS entry.

done thanks for the suggestion

@alexdowad

Copy link
Copy Markdown
Contributor

@nicolas-grekas Do you want to go ahead and merge? Or should I merge this for you?

@nicolas-grekas

Copy link
Copy Markdown
Contributor Author

Please merge, I'm not sure I can.

@alexdowad

Copy link
Copy Markdown
Contributor

OK, pulled .patch file, applied to PHP-8.4 with git am, merged down into PHP-8.5, then down into PHP-8.6, then down into master.

Thank you very much for the good fix, @nicolas-grekas!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants