mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-09 01:17:04 +03:00
Improved detection of PGP/MIME encrypted messages instead of showing them as attachments
This commit is contained in:
parent
48febdb414
commit
0ed7f000d6
4 changed files with 60 additions and 45 deletions
|
|
@ -168,6 +168,19 @@ class BodyStructure
|
||||||
return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
|
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
|
public function IsPgpSigned() : bool
|
||||||
{
|
{
|
||||||
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
|
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
|
||||||
|
|
@ -187,44 +200,21 @@ class BodyStructure
|
||||||
|
|
||||||
public function IsAttachBodyPart() : bool
|
public function IsAttachBodyPart() : bool
|
||||||
{
|
{
|
||||||
return 'attachment' === $this->sDisposition
|
return 'application/pgp-encrypted' !== $this->sContentType
|
||||||
|| (
|
&& (
|
||||||
|
'attachment' === $this->sDisposition || (
|
||||||
!\str_starts_with($this->sContentType, 'multipart/')
|
!\str_starts_with($this->sContentType, 'multipart/')
|
||||||
&& 'text/html' !== $this->sContentType
|
&& 'text/html' !== $this->sContentType
|
||||||
&& 'text/plain' !== $this->sContentType
|
&& 'text/plain' !== $this->sContentType
|
||||||
);
|
)
|
||||||
|
);
|
||||||
return $bResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function IsFlowedFormat() : bool
|
public function IsFlowedFormat() : bool
|
||||||
{
|
{
|
||||||
$bResult = !empty($this->aBodyParams['format']) &&
|
return !empty($this->aBodyParams['format'])
|
||||||
'flowed' === \strtolower(\trim($this->aBodyParams['format']));
|
&& 'flowed' === \strtolower(\trim($this->aBodyParams['format']))
|
||||||
|
&& !\in_array($this->sMailEncodingName, array('base64', 'quoted-printable'));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetHtmlAndPlainParts() : array
|
public function GetHtmlAndPlainParts() : array
|
||||||
|
|
@ -238,9 +228,15 @@ class BodyStructure
|
||||||
return \array_merge([$aParts->current()], \iterator_to_array($aParts));
|
return \array_merge([$aParts->current()], \iterator_to_array($aParts));
|
||||||
}
|
}
|
||||||
|
|
||||||
$oPart = $this->SearchInlineEncryptedPart();
|
/**
|
||||||
if ($oPart instanceof self) {
|
* No text found, is it encrypted?
|
||||||
return array($oPart);
|
* 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 [];
|
return [];
|
||||||
|
|
@ -267,20 +263,21 @@ class BodyStructure
|
||||||
* @param mixed $fCallback
|
* @param mixed $fCallback
|
||||||
*/
|
*/
|
||||||
// public function SearchByCallback($fCallback) : \Generator
|
// 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;
|
yield $this;
|
||||||
}
|
}
|
||||||
foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ $oSubPart) {
|
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
|
public function SearchAttachmentsParts() : iterable
|
||||||
{
|
{
|
||||||
return $this->SearchByCallback(function ($oItem) {
|
return $this->SearchByCallback(function ($oItem, $oParent) {
|
||||||
return $oItem->IsAttachBodyPart();
|
// return $oItem->IsAttachBodyPart();
|
||||||
|
return $oItem->IsAttachBodyPart() && (!$oParent || !$oParent->IsPgpEncrypted());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -490,9 +490,8 @@ class MailClient
|
||||||
if (!\in_array(\strtolower(MessageFlag::SEEN), $aFlags))
|
if (!\in_array(\strtolower(MessageFlag::SEEN), $aFlags))
|
||||||
{
|
{
|
||||||
$iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
|
$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);
|
$sContentTypeCharset = $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, MimeParameter::CHARSET);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ class Message implements \JsonSerializable
|
||||||
$aThreads = array(),
|
$aThreads = array(),
|
||||||
|
|
||||||
$aPgpSigned = null,
|
$aPgpSigned = null,
|
||||||
|
$aPgpEncrypted = null,
|
||||||
$bPgpEncrypted = false;
|
$bPgpEncrypted = false;
|
||||||
|
|
||||||
function __construct()
|
function __construct()
|
||||||
|
|
@ -100,9 +101,14 @@ class Message implements \JsonSerializable
|
||||||
return $this->aPgpSigned;
|
return $this->aPgpSigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function PgpEncrypted() : ?array
|
||||||
|
{
|
||||||
|
return $this->aPgpEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
public function isPgpEncrypted() : bool
|
public function isPgpEncrypted() : bool
|
||||||
{
|
{
|
||||||
return $this->bPgpEncrypted;
|
return $this->bPgpEncrypted || $this->aPgpEncrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Folder() : string
|
public function Folder() : string
|
||||||
|
|
@ -491,16 +497,28 @@ class Message implements \JsonSerializable
|
||||||
|
|
||||||
if ($oBodyStructure)
|
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');
|
$gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
|
||||||
foreach ($gSignatureParts as $oPart) {
|
foreach ($gSignatureParts as $oPart) {
|
||||||
if (!$oPart->IsPgpSigned()) {
|
if (!$oPart->IsPgpSigned()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$oPgpSignaturePart = $oBodyStructure->SubParts()[1];
|
$oPgpSignaturePart = $oPart->SubParts()[1];
|
||||||
$oMessage->aPgpSigned = [
|
$oMessage->aPgpSigned = [
|
||||||
// /?/Raw/&q[]=/0/Download/&q[]=/...
|
// /?/Raw/&q[]=/0/Download/&q[]=/...
|
||||||
// /?/Raw/&q[]=/0/View/&q[]=/...
|
// /?/Raw/&q[]=/0/View/&q[]=/...
|
||||||
'BodyPartId' => $oBodyStructure->SubParts()[0]->PartID(),
|
'BodyPartId' => $oPart->SubParts()[0]->PartID(),
|
||||||
'SigPartId' => $oPgpSignaturePart->PartID(),
|
'SigPartId' => $oPgpSignaturePart->PartID(),
|
||||||
'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg')
|
'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'].']');
|
$sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['SigPartId'].']');
|
||||||
if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
|
if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
|
||||||
$oMessage->aPgpSigned['Signature'] = $oBodyStructure->SubParts()[0]->PartID();
|
$oMessage->aPgpSigned['Signature'] = $oPart->SubParts()[0]->PartID();
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,7 @@ trait Response
|
||||||
// $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUPG)
|
// $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUPG)
|
||||||
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
|
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
|
||||||
$mResult['PgpSigned'] = $mResponse->PgpSigned();
|
$mResult['PgpSigned'] = $mResponse->PgpSigned();
|
||||||
|
$mResult['PgpEncrypted'] = $mResponse->PgpEncrypted();
|
||||||
|
|
||||||
$mResult['HasExternals'] = $bHasExternals;
|
$mResult['HasExternals'] = $bHasExternals;
|
||||||
$mResult['HasInternals'] = \count($aFoundCIDs) || \count($aFoundContentLocationUrls);
|
$mResult['HasInternals'] = \count($aFoundCIDs) || \count($aFoundContentLocationUrls);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue