From fb1c0dd606b7fde6445265bc361d6b0485367fe6 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 3 Feb 2022 21:41:59 +0100 Subject: [PATCH] Revamp MailSo\Mime\Message for sending proper multipart/encrypted --- dev/View/Popup/Compose.js | 8 +- .../MailSo/Mime/Enumerations/MimeType.php | 1 - .../app/libraries/MailSo/Mime/Message.php | 188 ++++++------------ .../0.0.0/app/libraries/MailSo/Mime/Part.php | 4 +- .../libraries/MailSo/Mime/PartCollection.php | 2 +- .../libraries/RainLoop/Actions/Messages.php | 120 +++++++++-- 6 files changed, 174 insertions(+), 149 deletions(-) diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 03e39c9b2..a3427a72b 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -454,8 +454,8 @@ class ComposePopupView extends AbstractViewPopup { if (this.mailvelope && 'mailvelope' === this.viewArea()) { this.mailvelope.encrypt(this.allRecipients()).then(armored => { - params.Html = ''; - params.Text = armored; + params.Text = params.Html = ''; + params.Encrypted = armored; send(); }); } else if (encrypt) { @@ -474,7 +474,7 @@ class ComposePopupView extends AbstractViewPopup { send(); } else { this.sendError(true); - this.sendErrorDesc(i18n('PGP_NOTIFICATIONS/PGP_ERROR', { ERROR: 'Signing failed' })); + this.sendErrorDesc(i18n('PGP_NOTIFICATIONS/PGP_ERROR', { ERROR: 'Encryption failed' })); this.sending(false); } }); @@ -489,8 +489,8 @@ class ComposePopupView extends AbstractViewPopup { if (detached) { // Append headers params.Text = [ - 'Content-Transfer-Encoding: base64', 'Content-Type: text/plain; charset="utf-8"; protected-headers="v1"', + 'Content-Transfer-Encoding: base64', // 'From: Demo ', // 'To: Demo ', // 'Subject: text detached signed' diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/MimeType.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/MimeType.php index f1fe30d97..625b587b6 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/MimeType.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/MimeType.php @@ -19,7 +19,6 @@ namespace MailSo\Mime\Enumerations; abstract class MimeType { const TEXT_PLAIN = 'text/plain'; - const TEXT_HTML = 'text/html'; const MULTIPART_ALTERNATIVE = 'multipart/alternative'; const MULTIPART_RELATED = 'multipart/related'; diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php index 2390e214d..04e737e18 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php @@ -15,18 +15,13 @@ namespace MailSo\Mime; * @category MailSo * @package Mime */ -class Message +class Message extends Part { /** * @var array */ private $aHeadersValue = array(); - /** - * @var array - */ - private $aAlternativeParts = array(); - /** * @var AttachmentCollection */ @@ -44,6 +39,7 @@ class Message function __construct() { + parent::__construct(); $this->oAttachmentCollection = new AttachmentCollection; } @@ -293,28 +289,7 @@ class Message return $this; } - public function AddPlain(string $sPlain) : self - { - return $this->AddAlternative(Enumerations\MimeType::TEXT_PLAIN, $sPlain); - } - - public function AddHtml(string $sHtml) : self - { - return $this->AddAlternative(Enumerations\MimeType::TEXT_HTML, $sHtml); - } - - public function AddAlternative(string $sContentType, string $sData) : self - { - $this->aAlternativeParts[] = array( - $sContentType, - \preg_replace('/\\r?\\n/', "\r\n", \trim($sData)), - \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_LOWER, - array() - ); - return $this; - } - - private function generateNewBoundary() : string + public function generateNewBoundary() : string { return '--='.\MailSo\Config::$BoundaryPrefix. \rand(100, 999).'_'.\rand(100000000, 999999999).'.'.\time(); @@ -447,7 +422,6 @@ class Message $aAlternativeData[0].'; '.$oParameters->ToString()) ); - $oAlternativePart->Body = null; if (isset($aAlternativeData[1])) { if (\is_resource($aAlternativeData[1])) @@ -493,60 +467,6 @@ class Message return $oAlternativePart; } - private function createNewMessageRelatedBody(Part $oIncPart) : Part - { - $oResultPart = null; - - foreach ($this->oAttachmentCollection as $oAttachment) { - if ($oAttachment->IsLinked()) { - if (!$oResultPart) { - $oResultPart = new Part; - $oResultPart->Headers->append( - new Header(Enumerations\Header::CONTENT_TYPE, - Enumerations\MimeType::MULTIPART_RELATED.'; '. - (new ParameterCollection)->Add( - new Parameter( - Enumerations\Parameter::BOUNDARY, - $this->generateNewBoundary()) - )->ToString() - ) - ); - $oResultPart->SubParts->append($oIncPart); - } - - $oResultPart->SubParts->append($this->createNewMessageAttachmentBody($oAttachment)); - } - } - - return $oResultPart ?: $oIncPart; - } - - private function createNewMessageMixedBody(Part $oIncPart) : Part - { - $oResultPart = null; - - foreach ($this->oAttachmentCollection as $oAttachment) { - if (!$oAttachment->IsLinked()) { - if (!$oResultPart) { - $oResultPart = new Part; - $oResultPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, - Enumerations\MimeType::MULTIPART_MIXED.'; '. - (new ParameterCollection)->Add( - new Parameter( - Enumerations\Parameter::BOUNDARY, - $this->generateNewBoundary()) - )->ToString() - ); - $oResultPart->SubParts->append($oIncPart); - } - - $oResultPart->SubParts->append($this->createNewMessageAttachmentBody($oAttachment)); - } - } - - return $oResultPart ?: $oIncPart; - } - private function setDefaultHeaders(Part $oIncPart, bool $bWithoutBcc = false) : Part { if (!isset($this->aHeadersValue[Enumerations\Header::DATE])) @@ -590,54 +510,48 @@ class Message } /** - * @return resource + * @return resource|bool */ public function ToStream(bool $bWithoutBcc = false) { - $oPart = null; - if (1 < \count($this->aAlternativeParts)) + $oResultPart = null; + if (\count($this->SubParts)) { - $oPart = new Part; + if (1 !== \count($this->SubParts)) { + throw new \Exception('Invalid part structure'); + } + // createNewMessageRelatedBody + foreach ($this->oAttachmentCollection as $oAttachment) { + if ($oAttachment->IsLinked()) { + if (!$oResultPart) { + $oResultPart = new Part; + $oResultPart->Headers->append( + new Header(Enumerations\Header::CONTENT_TYPE, + Enumerations\MimeType::MULTIPART_RELATED.'; '. + (new ParameterCollection)->Add( + new Parameter( + Enumerations\Parameter::BOUNDARY, + $this->generateNewBoundary()) + )->ToString() + ) + ); + $oResultPart->SubParts = $this->SubParts; + $this->SubParts = new PartCollection(); + $this->SubParts->append($oResultPart); + } - $oPart->Headers->append( - new Header(Enumerations\Header::CONTENT_TYPE, - Enumerations\MimeType::MULTIPART_ALTERNATIVE.'; '. - (new ParameterCollection)->Add( - new Parameter( - Enumerations\Parameter::BOUNDARY, - $this->generateNewBoundary()) - )->ToString() - ) - ); - - foreach ($this->aAlternativeParts as $aAlternativeData) - { - $oAlternativePart = $this->createNewMessageAlternativePartBody($aAlternativeData); - if ($oAlternativePart) - { - $oPart->SubParts->append($oAlternativePart); + $oResultPart->SubParts->append($this->createNewMessageAttachmentBody($oAttachment)); } - - unset($oAlternativePart); - } - - } - else if (1 === \count($this->aAlternativeParts)) - { - $oAlternativePart = $this->createNewMessageAlternativePartBody($this->aAlternativeParts[0]); - if ($oAlternativePart) - { - $oPart = $oAlternativePart; } } - - if (!$oPart) + else { if ($this->bAddEmptyTextPart) { - $oPart = $this->createNewMessageAlternativePartBody(array( - Enumerations\MimeType::TEXT_PLAIN, null - )); + $oPart = new Part; + $oPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"'); + $oPart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(''); + $this->SubParts->append($oPart); } else { @@ -645,18 +559,40 @@ class Message if (1 === \count($aAttachments) && isset($aAttachments[0])) { $this->oAttachmentCollection->Clear(); - $oPart = $this->createNewMessageAlternativePartBody(array( - $aAttachments[0]->ContentType(), $aAttachments[0]->Resource(), - '', $aAttachments[0]->CustomContentTypeParams() + $aAttachments[0]->ContentType(), + $aAttachments[0]->Resource(), + '', + $aAttachments[0]->CustomContentTypeParams() )); + $this->SubParts->append($oPart); } } } - $oPart = $this->createNewMessageRelatedBody($oPart); - $oPart = $this->createNewMessageMixedBody($oPart); - $oPart = $this->setDefaultHeaders($oPart, $bWithoutBcc); + // createNewMessageMixedBody + foreach ($this->oAttachmentCollection as $oAttachment) { + if (!$oAttachment->IsLinked()) { + if (!$oResultPart) { + $oResultPart = new Part; + $oResultPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, + Enumerations\MimeType::MULTIPART_MIXED.'; '. + (new ParameterCollection)->Add( + new Parameter( + Enumerations\Parameter::BOUNDARY, + $this->generateNewBoundary()) + )->ToString() + ); + $oResultPart->SubParts = $this->SubParts; + $this->SubParts = new PartCollection(); + $this->SubParts->append($oResultPart); + } + + $oResultPart->SubParts->append($this->createNewMessageAttachmentBody($oAttachment)); + } + } + + $oPart = $this->setDefaultHeaders($this->SubParts[0], $bWithoutBcc); return $oPart->ToStream(); } /* diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Part.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Part.php index bf9d2f682..504040633 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Part.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Part.php @@ -520,7 +520,7 @@ class Part } /** - * @return resource + * @return resource|bool */ public function ToStream() { @@ -542,7 +542,7 @@ class Part "\r\n" ); - if (0 < $this->SubParts->Count()) + if ($this->SubParts->Count()) { $sBoundary = $this->HeaderBoundary(); if (\strlen($sBoundary)) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/PartCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/PartCollection.php index b531ee119..36bafc0e9 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/PartCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/PartCollection.php @@ -24,7 +24,7 @@ class PartCollection extends \MailSo\Base\Collection } /** - * @return resource + * @return resource|bool|null */ public function ToStream(string $sBoundary) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 194ed3775..d7d94f3e9 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -9,6 +9,7 @@ use RainLoop\Notifications; use MailSo\Imap\SequenceSet; use MailSo\Imap\Enumerations\FetchType; use MailSo\Imap\Enumerations\MessageFlag; +use MailSo\Mime\Part as MimePart; trait Messages { @@ -1010,7 +1011,14 @@ trait Messages $this->Plugins()->RunHook('filter.read-receipt-message-plain', array($oAccount, $oMessage, &$sText)); - $oMessage->AddPlain($sText); + $oPart = new MimePart; + $oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"'); + $oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable'); + $oPart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream( + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sText))), + 'convert.quoted-printable-encode' + ); + $this->SubParts->append($oPart); $this->Plugins()->RunHook('filter.build-read-receipt-message', array($oMessage, $oAccount)); @@ -1097,25 +1105,107 @@ trait Messages $aFoundDataURL = array(); $aFoundContentLocationUrls = array(); - $sHtml = $this->GetActionParam('Html', ''); - $sText = $this->GetActionParam('Text', ''); - if ($sHtml) { + if ($sHtml = $this->GetActionParam('Html', '')) { + $oPart = new MimePart; + $oPart->Headers->AddByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\MimeType::MULTIPART_ALTERNATIVE + . '; ' . (new \MailSo\Mime\ParameterCollection)->Add( + new \MailSo\Mime\Parameter(\MailSo\Mime\Enumerations\Parameter::BOUNDARY, $oMessage->generateNewBoundary()) + )->ToString() + ); + $oMessage->SubParts->append($oPart); + $sHtml = \MailSo\Base\HtmlUtils::BuildHtml($sHtml, $aFoundCids, $aFoundDataURL, $aFoundContentLocationUrls); $this->Plugins()->RunHook('filter.message-html', array($oAccount, $oMessage, &$sHtml)); - $sTextConverted = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sText); - $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sTextConverted)); - $oMessage->AddPlain($sTextConverted); - $oMessage->AddHtml($sText); + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/html; charset=utf-8'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable'); + $oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream( + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sHtml))), + 'convert.quoted-printable-encode' + ); + $oPart->SubParts->append($oAlternativePart); + + $sPlain = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sHtml); + $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain)); + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset=utf-8'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable'); + $oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream( + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))), + 'convert.quoted-printable-encode' + ); + $oPart->SubParts->append($oAlternativePart); + + unset($oAlternativePart); + unset($sHtml); + unset($sPlain); + + } else if ($sEncrypted = $this->GetActionParam('Encrypted', '')) { + $oPart = new MimePart; + $oPart->Headers->AddByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + 'multipart/encrypted; ' . (new \MailSo\Mime\ParameterCollection)->Add( + new \MailSo\Mime\Parameter(\MailSo\Mime\Enumerations\Parameter::BOUNDARY, $oMessage->generateNewBoundary()) + )->ToString() . '; protocol="application/pgp-encrypted"' + ); + $oMessage->SubParts->append($oPart); + + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-encrypted'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, 'attachment'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit'); + $oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString('Version: 1'); + $oPart->SubParts->append($oAlternativePart); + + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/octet-stream'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, 'inline; filename="msg.asc"'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit'); + $oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sEncrypted))); + $oPart->SubParts->append($oAlternativePart); + + unset($oAlternativePart); + unset($sEncrypted); + } else { - $sSignature = $this->GetActionParam('Signature', null); - if ($sSignature) { - // MimeType::MULTIPART_SIGNED - // MimeType::APPLICATION_PGP_SIGNATURE - $oMessage->AddPlain($sText); + $sPlain = $this->GetActionParam('Text', ''); + if ($sSignature = $this->GetActionParam('Signature', null)) { + $oPart = new MimePart; + $oPart->Headers->AddByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + 'multipart/signed; ' . (new ParameterCollection)->Add( + new \MailSo\Mime\Parameter(\MailSo\Mime\Enumerations\Parameter::BOUNDARY, $oMessage->generateNewBoundary()) + )->ToString() . '; micalg="pgp-sha256"; protocol="application/pgp-signature"' + ); + $oMessage->SubParts->append($oPart); + + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"; protected-headers="v1"'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'base64'); + $oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))); + $oPart->SubParts->append($oAlternativePart); + + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-signature; name="signature.asc"'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit'); + $oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sSignature))); + $oPart->SubParts->append($oAlternativePart); } else { - $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sText)); - $oMessage->AddPlain($sText); + $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain)); + $oAlternativePart = new MimePart; + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"'); + $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable'); + $oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream( + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))), + 'convert.quoted-printable-encode' + ); + $oMessage->SubParts->append($oAlternativePart); } + unset($oAlternativePart); + unset($sSignature); + unset($sPlain); } $aAttachments = $this->GetActionParam('Attachments', null);