Improved detection of PGP/MIME encrypted messages instead of showing them as attachments

This commit is contained in:
the-djmaze 2022-01-25 17:02:29 +01:00
parent 48febdb414
commit 0ed7f000d6
4 changed files with 60 additions and 45 deletions

View file

@ -168,6 +168,19 @@ class BodyStructure
return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
}
public function IsPgpEncrypted() : bool
{
// https://datatracker.ietf.org/doc/html/rfc3156#section-4
return 'multipart/encrypted' === $this->sContentType
&& !empty($this->aBodyParams['protocol'])
&& 'application/pgp-encrypted' === \strtolower(\trim($this->aBodyParams['protocol']))
// The multipart/encrypted body MUST consist of exactly two parts.
&& 2 === \count($this->aSubParts)
&& 'application/pgp-encrypted' === $this->aSubParts[0]->ContentType()
&& 'application/octet-stream' === $this->aSubParts[1]->ContentType();
// && 'Version: 1' === $this->aSubParts[0]->Body()
}
public function IsPgpSigned() : bool
{
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
@ -187,44 +200,21 @@ class BodyStructure
public function IsAttachBodyPart() : bool
{
return 'attachment' === $this->sDisposition
|| (
return 'application/pgp-encrypted' !== $this->sContentType
&& (
'attachment' === $this->sDisposition || (
!\str_starts_with($this->sContentType, 'multipart/')
&& 'text/html' !== $this->sContentType
&& 'text/plain' !== $this->sContentType
);
return $bResult;
)
);
}
public function IsFlowedFormat() : bool
{
$bResult = !empty($this->aBodyParams['format']) &&
'flowed' === \strtolower(\trim($this->aBodyParams['format']));
if ($bResult && \in_array($this->sMailEncodingName, array('base64', 'quoted-printable')))
{
$bResult = false;
}
return $bResult;
}
public function SearchInlineEncryptedPart() : ?self
{
if ('multipart/encrypted' === \strtolower($this->sContentType))
{
$aSearchParts = \iterator_to_array($this->SearchByCallback(function ($oItem) {
return $oItem->IsInline();
}));
if (1 === \count($aSearchParts) && isset($aSearchParts[0]))
{
return $aSearchParts[0];
}
}
return null;
return !empty($this->aBodyParams['format'])
&& 'flowed' === \strtolower(\trim($this->aBodyParams['format']))
&& !\in_array($this->sMailEncodingName, array('base64', 'quoted-printable'));
}
public function GetHtmlAndPlainParts() : array
@ -238,9 +228,15 @@ class BodyStructure
return \array_merge([$aParts->current()], \iterator_to_array($aParts));
}
$oPart = $this->SearchInlineEncryptedPart();
if ($oPart instanceof self) {
return array($oPart);
/**
* No text found, is it encrypted?
* If so, just return that.
*/
$gEncryptedParts = $this->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted() && $oPart->SubParts()[1]->IsInline()) {
return array($oPart->SubParts()[1]);
}
}
return [];
@ -267,20 +263,21 @@ class BodyStructure
* @param mixed $fCallback
*/
// public function SearchByCallback($fCallback) : \Generator
public function SearchByCallback($fCallback) : iterable
public function SearchByCallback($fCallback, $parent = null) : iterable
{
if ($fCallback($this)) {
if ($fCallback($this, $parent)) {
yield $this;
}
foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ $oSubPart) {
yield from $oSubPart->SearchByCallback($fCallback);
yield from $oSubPart->SearchByCallback($fCallback, $this);
}
}
public function SearchAttachmentsParts() : iterable
{
return $this->SearchByCallback(function ($oItem) {
return $oItem->IsAttachBodyPart();
return $this->SearchByCallback(function ($oItem, $oParent) {
// return $oItem->IsAttachBodyPart();
return $oItem->IsAttachBodyPart() && (!$oParent || !$oParent->IsPgpEncrypted());
});
}

View file

@ -490,9 +490,8 @@ class MailClient
if (!\in_array(\strtolower(MessageFlag::SEEN), $aFlags))
{
$iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
$sHeaders = $oFetchResponse->GetHeaderFieldsValue();
$oHeaders = new \MailSo\Mime\HeaderCollection($sHeaders);
$oHeaders = new \MailSo\Mime\HeaderCollection($oFetchResponse->GetHeaderFieldsValue());
$sContentTypeCharset = $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, MimeParameter::CHARSET);

View file

@ -78,6 +78,7 @@ class Message implements \JsonSerializable
$aThreads = array(),
$aPgpSigned = null,
$aPgpEncrypted = null,
$bPgpEncrypted = false;
function __construct()
@ -100,9 +101,14 @@ class Message implements \JsonSerializable
return $this->aPgpSigned;
}
public function PgpEncrypted() : ?array
{
return $this->aPgpEncrypted;
}
public function isPgpEncrypted() : bool
{
return $this->bPgpEncrypted;
return $this->bPgpEncrypted || $this->aPgpEncrypted;
}
public function Folder() : string
@ -491,16 +497,28 @@ class Message implements \JsonSerializable
if ($oBodyStructure)
{
$gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted()) {
if (!$oMessage->aPgpEncrypted) {
$oMessage->aPgpEncrypted = [];
}
$oMessage->aPgpEncrypted = [
'PartId' => $oPart->SubParts()[1]->PartID()
];
}
}
$gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
foreach ($gSignatureParts as $oPart) {
if (!$oPart->IsPgpSigned()) {
continue;
}
$oPgpSignaturePart = $oBodyStructure->SubParts()[1];
$oPgpSignaturePart = $oPart->SubParts()[1];
$oMessage->aPgpSigned = [
// /?/Raw/&q[]=/0/Download/&q[]=/...
// /?/Raw/&q[]=/0/View/&q[]=/...
'BodyPartId' => $oBodyStructure->SubParts()[0]->PartID(),
'BodyPartId' => $oPart->SubParts()[0]->PartID(),
'SigPartId' => $oPgpSignaturePart->PartID(),
'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg')
];
@ -517,7 +535,7 @@ class Message implements \JsonSerializable
}
$sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['SigPartId'].']');
if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
$oMessage->aPgpSigned['Signature'] = $oBodyStructure->SubParts()[0]->PartID();
$oMessage->aPgpSigned['Signature'] = $oPart->SubParts()[0]->PartID();
}
*/
break;

View file

@ -257,6 +257,7 @@ trait Response
// $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUPG)
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
$mResult['PgpSigned'] = $mResponse->PgpSigned();
$mResult['PgpEncrypted'] = $mResponse->PgpEncrypted();
$mResult['HasExternals'] = $bHasExternals;
$mResult['HasInternals'] = \count($aFoundCIDs) || \count($aFoundContentLocationUrls);