Browse Source

Fix handling of binary mail parts (e.g. PDF) encoded with quoted-printable (#9728)

pull/9742/head
Aleksander Machniak 7 months ago
parent
commit
4bde475ea1
  1. 1
      CHANGELOG.md
  2. 36
      program/lib/Roundcube/rcube_imap_generic.php
  3. 17
      tests/Framework/ImapGenericTest.php

1
CHANGELOG.md

@ -75,6 +75,7 @@
- Fix PHP fatal error when parsing some malformed BODYSTRUCTURE responses (#9689)
- Fix insert_or_update() and reading database server config on PostgreSQL (#9710)
- Fix Oauth issues with use_secure_urls=true (#9722)
- Fix handling of binary mail parts (e.g. PDF) encoded with quoted-printable (#9728)
## Release 1.6.9

36
program/lib/Roundcube/rcube_imap_generic.php

@ -2939,7 +2939,7 @@ class rcube_imap_generic
}
if ($result !== false) {
$result = $this->decodeContent($result, $mode, true);
$result = $this->decodeContent($result, $mode, true, '', $formatted);
}
}
// response with string literal
@ -2973,7 +2973,7 @@ class rcube_imap_generic
}
$bytes -= $len;
$chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev);
$chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev, $formatted);
if ($file) {
if (($result = fwrite($file, $chunk)) === false) {
@ -3008,14 +3008,15 @@ class rcube_imap_generic
/**
* Decodes a chunk of a message part content from a FETCH response.
*
* @param string $chunk Content
* @param int $mode Encoding mode
* @param bool $is_last Whether it is a last chunk of data
* @param string $prev Extra content from the previous chunk
* @param string $chunk Content
* @param int $mode Encoding mode
* @param bool $is_last Whether it is a last chunk of data
* @param string $prev Extra content from the previous chunk
* @param bool $formatted Format the content for output
*
* @return string Encoded string
*/
protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '')
protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '', $formatted = false)
{
// BASE64
if ($mode == 1) {
@ -3039,22 +3040,18 @@ class rcube_imap_generic
$result .= base64_decode($_chunk);
}
return $result;
$chunk = $result;
}
// QUOTED-PRINTABLE
if ($mode == 2) {
elseif ($mode == 2) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
$chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
return quoted_printable_decode($chunk);
$chunk = quoted_printable_decode($chunk);
}
// X-UUENCODE
if ($mode == 3) {
elseif ($mode == 3) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
@ -3069,12 +3066,11 @@ class rcube_imap_generic
return '';
}
return convert_uudecode($chunk);
$chunk = convert_uudecode($chunk);
}
// Plain text formatted
// TODO: Formatting should be handled outside of this class
if ($mode == 4) {
elseif ($mode == 4) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
@ -3082,8 +3078,10 @@ class rcube_imap_generic
if ($is_last) {
$chunk = rtrim($chunk, "\t\r\n\0\x0B");
}
}
return preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
if ($formatted) {
$chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
}
return $chunk;

17
tests/Framework/ImapGenericTest.php

@ -177,10 +177,16 @@ class ImapGenericTest extends TestCase
*/
public function test_decode_content_qp()
{
$content = "test quoted-printable\n\n żąśźć encoded content\ntest quoted-printable żąśźć encoded content";
$encoded = \Mail_mimePart::quotedPrintableEncode($content, 12);
$content = "test quoted-printable\r\n\r\n żąśźć encoded content\r\ntest quoted-printable żąśźć encoded content";
$encoded = \Mail_mimePart::quotedPrintableEncode($content, 12, "\r\n");
$this->runDecodeContent($content, $encoded, 2);
$content = file_get_contents(TESTS_DIR . '/src/test.pdf');
$encoded = \Mail_mimePart::quotedPrintableEncode($content, 76, "\r\n");
$this->runDecodeContent($content, $encoded, 2, strlen($encoded));
$this->runDecodeContent($content, $encoded, 2, strlen($encoded) / 2);
}
/**
@ -207,16 +213,15 @@ class ImapGenericTest extends TestCase
public function test_decode_content_formatted()
{
$content = "test \r\n plain text\tcontent\t\r\n test plain text content\t";
$expected = "test \n plain text\tcontent\n test plain text content";
$this->runDecodeContent($expected, $content, 4);
$this->runDecodeContent(rtrim($content), $content, 4, true);
}
/**
* Helper to execute decodeCOntent() method in multiple variations of an input
* and assert with the expected output
*/
public function runDecodeContent($expected, $encoded, $mode, $size = null)
public function runDecodeContent($expected, $encoded, $mode, $size = null, $formatted = false)
{
$method = new \ReflectionMethod('rcube_imap_generic', 'decodeContent');
$method->setAccessible(true);
@ -231,7 +236,7 @@ class ImapGenericTest extends TestCase
$chunks = str_split($encoded, $x);
foreach ($chunks as $idx => $chunk) {
$decoded .= $method->invokeArgs(null, [$chunk, $mode, $idx == count($chunks) - 1, &$prev]);
$decoded .= $method->invokeArgs(null, [$chunk, $mode, $idx == count($chunks) - 1, &$prev, $formatted]);
}
$this->assertSame($expected, $decoded, "Failed on chunk size of {$x}");

Loading…
Cancel
Save