diff --git a/lib/IMAP/MessageMapper.php b/lib/IMAP/MessageMapper.php index 8503ed2738..b1ceaab744 100644 --- a/lib/IMAP/MessageMapper.php +++ b/lib/IMAP/MessageMapper.php @@ -35,11 +35,14 @@ use Horde_Imap_Client_Socket; use Horde_Mime_Exception; use Horde_Mime_Headers; +use Horde_Mime_Headers_ContentParam_ContentType; +use Horde_Mime_Headers_ContentTransferEncoding; use Horde_Mime_Part; use Html2Text\Html2Text; use OCA\Mail\Attachment; use OCA\Mail\Db\Mailbox; use OCA\Mail\Exception\ServiceException; +use OCA\Mail\IMAP\Charset\Converter; use OCA\Mail\Model\IMAPMessage; use OCA\Mail\Service\SmimeService; use OCA\Mail\Support\PerformanceLoggerTask; @@ -64,13 +67,16 @@ class MessageMapper { private SMimeService $smimeService; private ImapMessageFetcherFactory $imapMessageFactory; + private Converter $converter; public function __construct(LoggerInterface $logger, SmimeService $smimeService, - ImapMessageFetcherFactory $imapMessageFactory) { + ImapMessageFetcherFactory $imapMessageFactory, + Converter $converter) { $this->logger = $logger; $this->smimeService = $smimeService; $this->imapMessageFactory = $imapMessageFactory; + $this->converter = $converter; } /** @@ -943,15 +949,39 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client, return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted); } + // Convert a given binary body to utf-8 according to the transfer encoding and content + // type headers of the underlying MIME part + $convertBody = function (string $body, Horde_Mime_Headers $mimeHeaders) use ($structure): string { + /** @var Horde_Mime_Headers_ContentParam_ContentType $contentType */ + $contentType = $mimeHeaders->getHeader('content-type'); + /** @var Horde_Mime_Headers_ContentTransferEncoding $transferEncoding */ + $transferEncoding = $mimeHeaders->getHeader('content-transfer-encoding'); + + if (!$contentType && !$transferEncoding) { + // Nothing to convert here ... + return $body; + } + + if ($transferEncoding) { + $structure->setTransferEncoding($transferEncoding->value_single); + } + + if ($contentType) { + $structure->setType($contentType->value_single); + if (isset($contentType['charset'])) { + $structure->setCharset($contentType['charset']); + } + } + + $structure->setContents($body); + return $this->converter->convert($structure); + }; + $htmlBody = ($htmlBodyId !== null) ? $part->getBodyPart($htmlBodyId) : null; if (!empty($htmlBody)) { $mimeHeaders = $part->getMimeHeader($htmlBodyId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); - if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) { - $structure->setTransferEncoding($enc); - $structure->setContents($htmlBody); - $htmlBody = $structure->getContents(); - } + $htmlBody = $convertBody($htmlBody, $mimeHeaders); $html = new Html2Text($htmlBody, ['do_links' => 'none','alt_image' => 'hide']); return new MessageStructureData( $hasAttachments, @@ -964,11 +994,7 @@ public function getBodyStructureData(Horde_Imap_Client_Socket $client, if (!empty($textBody)) { $mimeHeaders = $part->getMimeHeader($textBodyId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); - if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) { - $structure->setTransferEncoding($enc); - $structure->setContents($textBody); - $textBody = $structure->getContents(); - } + $textBody = $convertBody($textBody, $mimeHeaders); return new MessageStructureData( $hasAttachments, $textBody, diff --git a/tests/Unit/IMAP/MessageMapperTest.php b/tests/Unit/IMAP/MessageMapperTest.php index 057d96ca56..a45e3bef44 100644 --- a/tests/Unit/IMAP/MessageMapperTest.php +++ b/tests/Unit/IMAP/MessageMapperTest.php @@ -32,6 +32,7 @@ use Horde_Imap_Client_Ids; use Horde_Imap_Client_Socket; use OCA\Mail\Db\Mailbox; +use OCA\Mail\IMAP\Charset\Converter; use OCA\Mail\IMAP\ImapMessageFetcher; use OCA\Mail\IMAP\ImapMessageFetcherFactory; use OCA\Mail\IMAP\MessageMapper; @@ -55,17 +56,22 @@ class MessageMapperTest extends TestCase { /** @var ImapMessageFetcherFactory|MockObject */ private $imapMessageFactory; + /** @var Converter|MockObject */ + private $converter; + protected function setUp(): void { parent::setUp(); $this->logger = $this->createMock(LoggerInterface::class); $this->sMimeService = $this->createMock(SmimeService::class); $this->imapMessageFactory = $this->createMock(ImapMessageFetcherFactory::class); + $this->converter = $this->createMock(Converter::class); $this->mapper = new MessageMapper( $this->logger, $this->sMimeService, $this->imapMessageFactory, + $this->converter, ); }