[php-src] Issue #24462: convert.quoted-printable-encode drops or duplicates a CR at the end of a write
| From: | auraseb | Date: | Tue, 20 Oct 2026 15:32:33 +0000 |
| Subject: | [php-src] Issue #24462: convert.quoted-printable-encode drops or duplicates a CR at the end of a write | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253049@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24462
Author: auraseb
### Description
With
line-length and line-break-chars set, the
convert.quoted-printable-encode filter looks ahead for the line break sequence. When a
write ends in the middle of that sequence (for example a trailing "\r" with
line-break-chars set to "\r\n"), the held back characters are
handled wrongly:
1. If nothing else is written, the "\r" is lost when the stream is closed or
the filter removed.
2. If the next write starts with "\n", the "\r" can be
emitted twice: once encoded as =0D and again as part of the line break. Decoding the
result gives "\r\r\n".
Which of the two happens depends on how much room was left in the internal output buffer, so the
same data can encode differently depending on how it was split across writes.
The following code:
```php
<?php
function encode(array $chunks): string {
$fp = fopen('php://temp', 'w+');
$filter = stream_filter_append($fp, 'convert.quoted-printable-encode',
STREAM_FILTER_WRITE, [
'line-length' => 76,
'line-break-chars' => "\r\n",
]);
foreach ($chunks as $chunk) {
fwrite($fp, $chunk);
}
stream_filter_remove($filter);
rewind($fp);
return stream_get_contents($fp);
}
var_dump(bin2hex(quoted_printable_decode(encode(["a\r"]))));
var_dump(bin2hex(quoted_printable_decode(encode(["Hello=\r", "\nWorld"]))));
```
Resulted in this output:
```
string(2) "61"
string(28) "48656c6c6f3d0d0d0a576f726c64"
```
But I expected this output instead:
```
string(4) "610d"
string(26) "48656c6c6f3d0d0a576f726c64"
```
Root cause: in php_conv_qprint_encode_convert() (ext/standard/filters.c) the main loop
does not stop when the input is exhausted while a partial line break is pending. It starts emitting
the pending characters as encoded bytes, but leaves lb_cnt set, so a following write
that completes the sequence emits the whole line break again. When the output buffer is too small at
that point, the converter returns PHP_CONV_ERR_TOO_BIG with no input left, the caller
stops calling it, and the pending characters stay in the state. On close the converter is called
with a NULL input, which returns immediately, so they are never written.
### PHP Version
```
PHP 8.7.0-dev (master at b7256eee), same code in all supported branches
```
### Operating System
Any