Revamp MailSo\Mime\Message for sending proper multipart/encrypted

This commit is contained in:
the-djmaze 2022-02-03 21:41:59 +01:00
parent 5514264914
commit fb1c0dd606
6 changed files with 174 additions and 149 deletions

View file

@ -454,8 +454,8 @@ class ComposePopupView extends AbstractViewPopup {
if (this.mailvelope && 'mailvelope' === this.viewArea()) { if (this.mailvelope && 'mailvelope' === this.viewArea()) {
this.mailvelope.encrypt(this.allRecipients()).then(armored => { this.mailvelope.encrypt(this.allRecipients()).then(armored => {
params.Html = ''; params.Text = params.Html = '';
params.Text = armored; params.Encrypted = armored;
send(); send();
}); });
} else if (encrypt) { } else if (encrypt) {
@ -474,7 +474,7 @@ class ComposePopupView extends AbstractViewPopup {
send(); send();
} else { } else {
this.sendError(true); 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); this.sending(false);
} }
}); });
@ -489,8 +489,8 @@ class ComposePopupView extends AbstractViewPopup {
if (detached) { if (detached) {
// Append headers // Append headers
params.Text = [ params.Text = [
'Content-Transfer-Encoding: base64',
'Content-Type: text/plain; charset="utf-8"; protected-headers="v1"', 'Content-Type: text/plain; charset="utf-8"; protected-headers="v1"',
'Content-Transfer-Encoding: base64',
// 'From: Demo <demo@snappymail.eu>', // 'From: Demo <demo@snappymail.eu>',
// 'To: Demo <demo@snappymail.eu>', // 'To: Demo <demo@snappymail.eu>',
// 'Subject: text detached signed' // 'Subject: text detached signed'

View file

@ -19,7 +19,6 @@ namespace MailSo\Mime\Enumerations;
abstract class MimeType abstract class MimeType
{ {
const TEXT_PLAIN = 'text/plain'; const TEXT_PLAIN = 'text/plain';
const TEXT_HTML = 'text/html';
const MULTIPART_ALTERNATIVE = 'multipart/alternative'; const MULTIPART_ALTERNATIVE = 'multipart/alternative';
const MULTIPART_RELATED = 'multipart/related'; const MULTIPART_RELATED = 'multipart/related';

View file

@ -15,18 +15,13 @@ namespace MailSo\Mime;
* @category MailSo * @category MailSo
* @package Mime * @package Mime
*/ */
class Message class Message extends Part
{ {
/** /**
* @var array * @var array
*/ */
private $aHeadersValue = array(); private $aHeadersValue = array();
/**
* @var array
*/
private $aAlternativeParts = array();
/** /**
* @var AttachmentCollection * @var AttachmentCollection
*/ */
@ -44,6 +39,7 @@ class Message
function __construct() function __construct()
{ {
parent::__construct();
$this->oAttachmentCollection = new AttachmentCollection; $this->oAttachmentCollection = new AttachmentCollection;
} }
@ -293,28 +289,7 @@ class Message
return $this; return $this;
} }
public function AddPlain(string $sPlain) : self public function generateNewBoundary() : string
{
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
{ {
return '--='.\MailSo\Config::$BoundaryPrefix. return '--='.\MailSo\Config::$BoundaryPrefix.
\rand(100, 999).'_'.\rand(100000000, 999999999).'.'.\time(); \rand(100, 999).'_'.\rand(100000000, 999999999).'.'.\time();
@ -447,7 +422,6 @@ class Message
$aAlternativeData[0].'; '.$oParameters->ToString()) $aAlternativeData[0].'; '.$oParameters->ToString())
); );
$oAlternativePart->Body = null;
if (isset($aAlternativeData[1])) if (isset($aAlternativeData[1]))
{ {
if (\is_resource($aAlternativeData[1])) if (\is_resource($aAlternativeData[1]))
@ -493,60 +467,6 @@ class Message
return $oAlternativePart; 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 private function setDefaultHeaders(Part $oIncPart, bool $bWithoutBcc = false) : Part
{ {
if (!isset($this->aHeadersValue[Enumerations\Header::DATE])) if (!isset($this->aHeadersValue[Enumerations\Header::DATE]))
@ -590,54 +510,48 @@ class Message
} }
/** /**
* @return resource * @return resource|bool
*/ */
public function ToStream(bool $bWithoutBcc = false) public function ToStream(bool $bWithoutBcc = false)
{ {
$oPart = null; $oResultPart = null;
if (1 < \count($this->aAlternativeParts)) 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( $oResultPart->SubParts->append($this->createNewMessageAttachmentBody($oAttachment));
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);
} }
unset($oAlternativePart);
}
}
else if (1 === \count($this->aAlternativeParts))
{
$oAlternativePart = $this->createNewMessageAlternativePartBody($this->aAlternativeParts[0]);
if ($oAlternativePart)
{
$oPart = $oAlternativePart;
} }
} }
else
if (!$oPart)
{ {
if ($this->bAddEmptyTextPart) if ($this->bAddEmptyTextPart)
{ {
$oPart = $this->createNewMessageAlternativePartBody(array( $oPart = new Part;
Enumerations\MimeType::TEXT_PLAIN, null $oPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"');
)); $oPart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString('');
$this->SubParts->append($oPart);
} }
else else
{ {
@ -645,18 +559,40 @@ class Message
if (1 === \count($aAttachments) && isset($aAttachments[0])) if (1 === \count($aAttachments) && isset($aAttachments[0]))
{ {
$this->oAttachmentCollection->Clear(); $this->oAttachmentCollection->Clear();
$oPart = $this->createNewMessageAlternativePartBody(array( $oPart = $this->createNewMessageAlternativePartBody(array(
$aAttachments[0]->ContentType(), $aAttachments[0]->Resource(), $aAttachments[0]->ContentType(),
'', $aAttachments[0]->CustomContentTypeParams() $aAttachments[0]->Resource(),
'',
$aAttachments[0]->CustomContentTypeParams()
)); ));
$this->SubParts->append($oPart);
} }
} }
} }
$oPart = $this->createNewMessageRelatedBody($oPart); // createNewMessageMixedBody
$oPart = $this->createNewMessageMixedBody($oPart); foreach ($this->oAttachmentCollection as $oAttachment) {
$oPart = $this->setDefaultHeaders($oPart, $bWithoutBcc); 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(); return $oPart->ToStream();
} }
/* /*

View file

@ -520,7 +520,7 @@ class Part
} }
/** /**
* @return resource * @return resource|bool
*/ */
public function ToStream() public function ToStream()
{ {
@ -542,7 +542,7 @@ class Part
"\r\n" "\r\n"
); );
if (0 < $this->SubParts->Count()) if ($this->SubParts->Count())
{ {
$sBoundary = $this->HeaderBoundary(); $sBoundary = $this->HeaderBoundary();
if (\strlen($sBoundary)) if (\strlen($sBoundary))

View file

@ -24,7 +24,7 @@ class PartCollection extends \MailSo\Base\Collection
} }
/** /**
* @return resource * @return resource|bool|null
*/ */
public function ToStream(string $sBoundary) public function ToStream(string $sBoundary)
{ {

View file

@ -9,6 +9,7 @@ use RainLoop\Notifications;
use MailSo\Imap\SequenceSet; use MailSo\Imap\SequenceSet;
use MailSo\Imap\Enumerations\FetchType; use MailSo\Imap\Enumerations\FetchType;
use MailSo\Imap\Enumerations\MessageFlag; use MailSo\Imap\Enumerations\MessageFlag;
use MailSo\Mime\Part as MimePart;
trait Messages trait Messages
{ {
@ -1010,7 +1011,14 @@ trait Messages
$this->Plugins()->RunHook('filter.read-receipt-message-plain', array($oAccount, $oMessage, &$sText)); $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)); $this->Plugins()->RunHook('filter.build-read-receipt-message', array($oMessage, $oAccount));
@ -1097,25 +1105,107 @@ trait Messages
$aFoundDataURL = array(); $aFoundDataURL = array();
$aFoundContentLocationUrls = array(); $aFoundContentLocationUrls = array();
$sHtml = $this->GetActionParam('Html', ''); if ($sHtml = $this->GetActionParam('Html', '')) {
$sText = $this->GetActionParam('Text', ''); $oPart = new MimePart;
if ($sHtml) { $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); $sHtml = \MailSo\Base\HtmlUtils::BuildHtml($sHtml, $aFoundCids, $aFoundDataURL, $aFoundContentLocationUrls);
$this->Plugins()->RunHook('filter.message-html', array($oAccount, $oMessage, &$sHtml)); $this->Plugins()->RunHook('filter.message-html', array($oAccount, $oMessage, &$sHtml));
$sTextConverted = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sText); $oAlternativePart = new MimePart;
$this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sTextConverted)); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/html; charset=utf-8');
$oMessage->AddPlain($sTextConverted); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oMessage->AddHtml($sText); $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 { } else {
$sSignature = $this->GetActionParam('Signature', null); $sPlain = $this->GetActionParam('Text', '');
if ($sSignature) { if ($sSignature = $this->GetActionParam('Signature', null)) {
// MimeType::MULTIPART_SIGNED $oPart = new MimePart;
// MimeType::APPLICATION_PGP_SIGNATURE $oPart->Headers->AddByName(
$oMessage->AddPlain($sText); \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 { } else {
$this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sText)); $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain));
$oMessage->AddPlain($sText); $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); $aAttachments = $this->GetActionParam('Attachments', null);