Skip to content

Fix convert.quoted-printable-decode of lowercase hex digits#22870

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/qp-decode-lowercase-hex
Open

Fix convert.quoted-printable-decode of lowercase hex digits#22870
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/qp-decode-lowercase-hex

Conversation

@iliaal

@iliaal iliaal commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The convert.quoted-printable-decode stream filter accepts lowercase hex digits (isxdigit() passes a-f) but its nibble math only handles uppercase, so a hex escape with a lowercase digit decodes to the wrong byte. quoted_printable_decode() decodes lowercase correctly, so the filter and the function disagree.

$fp = fopen('php://temp', 'r+');
fwrite($fp, '=cd'); rewind($fp);
stream_filter_append($fp, 'convert.quoted-printable-decode', STREAM_FILTER_READ);
var_dump(bin2hex(stream_get_contents($fp))); // "ed", should be "cd"

The stream filter's nibble decoder accepts lowercase a-f via isxdigit()
but decodes them as (*ps - 0x37), correct only for uppercase A-F. Bytes
whose hex spelling uses a lowercase digit are silently corrupted (=cd
decodes to 0xed, =0a to 0x2a). Decode lowercase with (*ps - 0x57).
@iliaal
iliaal force-pushed the fix/qp-decode-lowercase-hex branch from 0db9b02 to f28afa8 Compare July 23, 2026 19:17
@arnaud-lb

arnaud-lb commented Jul 24, 2026

Copy link
Copy Markdown
Member

The filter documentation refers to quoted_printable_decode(), which says that it follows RFC 2045. The RFC explicitly disallows lowercase letters:

The digits of the hexadecimal alphabet, for this purpose, are "0123456789ABCDEF". Uppercase letters must be used; lowercase letters are not allowed.
https://datatracker.ietf.org/doc/html/rfc2045#section-6.7

Since decoding lowercase digits has never worked properly, and since fixing invalid inputs often lead to bugs or security issues, I would be in favor of changing quoted_printable_decode() and the filter so that they fail when the input uses lowercase letters.

Edit: I see now that only the filter doesn't decode lowercase hex letter properly. In that case I would suggestion to only change the filter, and to propose a deprecation of this behavior for the function (unless https://github.com/php/policies/pull/27/changes applies, but I'm not sure. cc @Girgias @TimWolla)

@alecpl

alecpl commented Jul 24, 2026

Copy link
Copy Markdown

The RFC also says: "An "=" followed by two hexadecimal digits, one or both of which are lowercase letters in "abcdef", is formally illegal. A robust implementation might choose to recognize them as the corresponding uppercase letters."

base64_decode() has a $strict argument, maybe that's the direction.

@iliaal

iliaal commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

On the strict direction: I'd keep that off this fix. It's an 8.4 bug fix, and the filter today doesn't reject lowercase, it silently decodes it to the wrong byte (=cd gives 0xED). Fixing that to the correct byte matches what quoted_printable_decode() has done for years and is what the RFC's "a robust implementation might choose to recognize them as the corresponding uppercase" line (alecpl's quote) allows, so it's a safe backportable fix. Rejecting lowercase instead turns input that currently produces output into an error, which isn't something I'd put on a stable branch.

The strict form (deprecate the leniency in both, or a $strict flag like base64_decode()) is worth doing, but as a separate master-side change, which lines up with your edit.

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