diff --git a/dev/App/User.js b/dev/App/User.js
index 8c7e15b01..0036860da 100644
--- a/dev/App/User.js
+++ b/dev/App/User.js
@@ -347,7 +347,7 @@ class AppUser extends AbstractApp {
}
reloadOpenPgpKeys() {
- if (PgpUserStore.capaOpenPGP()) {
+ if (PgpUserStore.openpgp) {
const keys = [],
email = new EmailModel(),
openpgpKeyring = PgpUserStore.openpgpKeyring,
@@ -784,7 +784,6 @@ class AppUser extends AbstractApp {
}
}
PgpUserStore.openpgpKeyring = new openpgp.Keyring();
- PgpUserStore.capaOpenPGP(true);
this.reloadOpenPgpKeys();
};
script.onerror = () => console.error(script.src);
diff --git a/dev/Model/Message.js b/dev/Model/Message.js
index 4a58e4f3b..9c02f0d1b 100644
--- a/dev/Model/Message.js
+++ b/dev/Model/Message.js
@@ -20,8 +20,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
import PreviewHTML from 'Html/PreviewMessage.html';
-import { PgpUserStore } from 'Stores/User/Pgp';
-
const
/*eslint-disable max-len*/
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
@@ -102,7 +100,7 @@ export class MessageModel extends AbstractModel {
hasImages: false,
hasExternals: false,
- isPgpSigned: false,
+ pgpSigned: null, // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
isPgpEncrypted: false,
pgpSignedVerifyStatus: SignedVerifyStatus.None,
pgpSignedVerifyUser: '',
@@ -181,7 +179,7 @@ export class MessageModel extends AbstractModel {
this.hasExternals(false);
this.attachments(new AttachmentCollectionModel);
- this.isPgpSigned(false);
+ this.pgpSigned(null);
this.isPgpEncrypted(false);
this.pgpSignedVerifyStatus(SignedVerifyStatus.None);
this.pgpSignedVerifyUser('');
@@ -404,7 +402,7 @@ export class MessageModel extends AbstractModel {
viewHtml() {
const body = this.body;
if (body && this.html()) {
- let html = this.html().toString()
+ let html = this.html()
.replace(/font-size:\s*[0-9]px/g, 'font-size:11px')
// Strip utm_* tracking
.replace(/(\\?|&|&)utm_[a-z]+=[a-z0-9_-]*/si, '$1');
@@ -465,7 +463,7 @@ export class MessageModel extends AbstractModel {
if (body && this.plain()) {
body.classList.toggle('html', 0);
body.classList.toggle('plain', 1);
- body.innerHTML = plainToHtml(this.plain().toString())
+ body.innerHTML = plainToHtml(this.plain())
// Strip utm_* tracking
.replace(/(\\?|&|&)utm_[a-z]+=[a-z0-9_-]*/si, '$1')
.replace(url, '$1$2')
@@ -479,8 +477,6 @@ export class MessageModel extends AbstractModel {
}
initView() {
- PgpUserStore.initMessageBodyControls(this.body, this);
-
// init BlockquoteSwitcher
this.body.querySelectorAll('blockquote:not(.rl-bq-switcher)').forEach(node => {
if (node.textContent.trim() && !node.parentNode.closest('blockquote')) {
diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js
index 7c10b7880..209b24300 100644
--- a/dev/Stores/User/Message.js
+++ b/dev/Stores/User/Message.js
@@ -370,7 +370,7 @@ export const MessageUserStore = new class {
message.hasImages(body.rlHasImages);
} else {
body = Element.fromHTML('
'
+ '
');
diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js
index 4ab12ea8b..c7578284b 100644
--- a/dev/Stores/User/Pgp.js
+++ b/dev/Stores/User/Pgp.js
@@ -1,6 +1,5 @@
import ko from 'ko';
-import { i18n } from 'Common/Translator';
import { isArray, arrayLength, pString, addComputablesTo } from 'Common/Utils';
import { AccountUserStore } from 'Stores/User/Account';
@@ -9,132 +8,8 @@ import { showScreenPopup } from 'Knoin/Knoin';
import { MessageOpenPgpPopupView } from 'View/Popup/MessageOpenPgp';
-function controlsHelper(dom, verControl, success, title, text)
-{
- dom.classList.toggle('error', !success);
- dom.classList.toggle('success', success);
- verControl.classList.toggle('error', !success);
- verControl.classList.toggle('success', success);
- dom.title = verControl.title = title;
-
- if (undefined !== text) {
- dom.textContent = text.trim();
- }
-}
-
-function domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) {
- return function() {
- let message = null;
-
- if (this.classList.contains('success')) {
- return false;
- }
-
- try {
- message = store.openpgp.message.readArmored(armoredMessage);
- } catch (e) {
- console.log(e);
- }
-
- if (message && message.getText && message.verify && message.decrypt) {
- store.decryptMessage(
- message,
- recipients,
- (validPrivateKey, decryptedMessage, validPublicKey, signingKeyIds) => {
- if (decryptedMessage) {
- if (validPublicKey) {
- controlsHelper(
- dom,
- this,
- true,
- i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
- USER: validPublicKey.user + ' (' + validPublicKey.id + ')'
- }),
- decryptedMessage.getText()
- );
- } else if (validPrivateKey) {
- const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
- additional = keyIds
- ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
- : '';
-
- controlsHelper(
- dom,
- this,
- false,
- i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''),
- decryptedMessage.getText()
- );
- } else {
- controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
- }
- } else {
- controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
- }
- }
- );
-
- return false;
- }
-
- controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
- return false;
- };
-}
-
-function domControlSignedClickHelper(store, dom, armoredMessage) {
- return function() {
- let message = null;
-
- if (this.classList.contains('success') || this.classList.contains('error')) {
- return false;
- }
-
- try {
- message = store.openpgp.cleartext.readArmored(armoredMessage);
- } catch (e) {
- console.log(e);
- }
-
- if (message && message.getText && message.verify) {
- store.verifyMessage(message, (validKey, signingKeyIds) => {
- if (validKey) {
- controlsHelper(
- dom,
- this,
- true,
- i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
- USER: validKey.user + ' (' + validKey.id + ')'
- }),
- message.getText()
- );
- } else {
- const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
- additional = keyIds
- ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
- : '';
-
- controlsHelper(
- dom,
- this,
- false,
- i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
- );
- }
- });
-
- return false;
- }
-
- controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
- return false;
- };
-}
-
export const PgpUserStore = new class {
constructor() {
- this.capaOpenPGP = ko.observable(false);
-
this.openpgp = null;
this.openpgpkeys = ko.observableArray();
@@ -335,28 +210,4 @@ export const PgpUserStore = new class {
return false;
}
- /**
- * @param {*} dom
- * @param {MessageModel} rainLoopMessage
- */
- initMessageBodyControls(dom, rainLoopMessage) {
- const cl = dom.classList,
- signed = cl.contains('openpgp-signed'),
- encrypted = cl.contains('openpgp-encrypted');
- if (encrypted || signed) {
- const
- domText = dom.textContent,
- recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [],
- verControl = Element.fromHTML('🔒
');
- if (encrypted) {
- verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC');
- verControl.addEventListener('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
- } else {
- verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC');
- verControl.addEventListener('click', domControlSignedClickHelper(this, dom, domText));
- }
-
- dom.prepend(verControl);
- }
- }
};
diff --git a/dev/Styles/User/MessageView.less b/dev/Styles/User/MessageView.less
index 06955d4f4..02970bfbf 100644
--- a/dev/Styles/User/MessageView.less
+++ b/dev/Styles/User/MessageView.less
@@ -429,27 +429,28 @@ html.rl-no-preview-pane {
}
}
*/
- .b-openpgp-control {
+ }
- color: #FA0;
- cursor: pointer;
- display: block;
- opacity: 0.5;
- margin: 15px;
+ .b-openpgp-control {
- &:hover {
- opacity: 1;
- }
+ color: #FA0;
+ cursor: pointer;
+ display: block;
+ opacity: 0.5;
+ margin: 15px;
- &.success {
- color: green;
- opacity: 1;
- }
+ &:hover {
+ opacity: 1;
+ }
- &.error {
- color: red;
- opacity: 1;
- }
+ &.success {
+ color: green;
+ opacity: 1;
+ }
+
+ &.error {
+ color: red;
+ opacity: 1;
}
}
}
diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js
index c4226a991..b519b4999 100644
--- a/dev/View/Popup/Compose.js
+++ b/dev/View/Popup/Compose.js
@@ -131,7 +131,7 @@ class ComposePopupView extends AbstractViewPopup {
this.bSkipNextHide = false;
- this.capaOpenPGP = PgpUserStore.capaOpenPGP;
+ this.capaOpenPGP = !!PgpUserStore.openpgp;
this.identities = IdentityUserStore;
@@ -552,7 +552,7 @@ class ComposePopupView extends AbstractViewPopup {
}
openOpenPgpPopup() {
- if (PgpUserStore.capaOpenPGP() && !this.oEditor.isHtml()) {
+ if (PgpUserStore.openpgp && !this.oEditor.isHtml()) {
showScreenPopup(ComposeOpenPgpPopupView, [
result => this.editor(editor => editor.setPlain(result)),
this.oEditor.getData(false),
diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js
index 28a6e3edb..f4c360e3a 100644
--- a/dev/View/User/MailBox/MessageView.js
+++ b/dev/View/User/MailBox/MessageView.js
@@ -40,6 +40,67 @@ import Remote from 'Remote/User/Fetch';
import { decorateKoCommands, createCommand } from 'Knoin/Knoin';
import { AbstractViewRight } from 'Knoin/AbstractViews';
+import { PgpUserStore } from 'Stores/User/Pgp';
+
+function controlsHelper(dom, verControl, success, title, text)
+{
+ dom.classList.toggle('error', !success);
+ dom.classList.toggle('success', success);
+// verControl.classList.toggle('error', !success);
+// verControl.classList.toggle('success', success);
+ dom.title = verControl.title = title;
+
+ if (undefined !== text) {
+ dom.textContent = text.trim();
+ }
+}
+
+function pgpClickHelper(dom, armoredMessage) {
+ if (dom.classList.contains('success') || dom.classList.contains('error')) {
+ return;
+ }
+
+ let message = null;
+ try {
+ message = PgpUserStore.openpgp.cleartext.readArmored(armoredMessage);
+ } catch (e) {
+ console.log(e);
+ }
+
+ if (message && message.getText && message.verify) {
+ PgpUserStore.verifyMessage(message, (validKey, signingKeyIds) => {
+ console.dir([validKey, signingKeyIds]);
+/*
+ if (validKey) {
+ controlsHelper(
+ dom,
+ this,
+ true,
+ i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
+ USER: validKey.user + ' (' + validKey.id + ')'
+ }),
+ message.getText()
+ );
+ } else {
+ const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
+ additional = keyIds
+ ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
+ : '';
+
+ controlsHelper(
+ dom,
+ this,
+ false,
+ i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
+ );
+ }
+*/
+ });
+ } else {
+ controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
+ }
+}
+
export class MailMessageView extends AbstractViewRight {
constructor() {
super('MailMessageView');
@@ -176,6 +237,12 @@ export class MailMessageView extends AbstractViewRight {
return '';
},
+ pgpSigned: () => PgpUserStore.openpgp
+ && MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
+
+ pgpEncrypted: () => PgpUserStore.openpgp
+ && MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
+
messageListOrViewLoading:
() => MessageUserStore.listIsLoading() | MessageUserStore.messageLoading()
});
@@ -614,4 +681,14 @@ export class MailMessageView extends AbstractViewRight {
rl.app.reloadFlagsCurrentMessageListAndMessageFromCache();
}
}
+
+ pgpDecrypt(self/*, event*/) {
+ const message = self.message();
+ message && pgpClickHelper(message.body, message.plain(), message.getEmails(['from', 'to', 'cc']));
+ }
+
+ pgpVerify(self/*, event*/) {
+ const message = self.message();
+ message && pgpClickHelper(message.body, message.plain());
+ }
}
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/BodyStructure.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/BodyStructure.php
index 95eaef2a5..31ec3dd47 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/BodyStructure.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/BodyStructure.php
@@ -138,6 +138,11 @@ class BodyStructure
return $this->sLocation;
}
+ public function SubParts() : array
+ {
+ return $this->aSubParts;
+ }
+
public function IsInline() : bool
{
return 'inline' === $this->sDisposition || \strlen($this->sContentID);
@@ -163,6 +168,17 @@ class BodyStructure
return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
}
+ public function IsPgpSigned() : bool
+ {
+ // https://datatracker.ietf.org/doc/html/rfc3156#section-5
+ return 'multipart/signed' === $this->sContentType
+ && !empty($this->aBodyParams['protocol'])
+ && 'application/pgp-signature' === \strtolower(\trim($this->aBodyParams['protocol']))
+ // The multipart/signed body MUST consist of exactly two parts.
+ && 2 === \count($this->aSubParts)
+ && $this->aSubParts[1]->IsPgpSignature();
+ }
+
public function IsPgpSignature() : bool
{
return \in_array($this->sContentType,
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php
index 01cdff93f..395644833 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php
@@ -15,6 +15,8 @@ namespace MailSo\Imap\Enumerations;
* @category MailSo
* @package Imap
* @subpackage Enumerations
+ *
+ * https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.5
*/
abstract class FetchType
{
@@ -25,18 +27,25 @@ abstract class FetchType
// Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)
const FULL = 'FULL';
+ const HEADER = 'HEADER'; // ([RFC-2822] header of the message)
+ const TEXT = 'TEXT'; // ([RFC-2822] text body of the message)
+ const MIME = 'MIME'; // ([MIME-IMB] header)
+
+ // Non-extensible form of BODYSTRUCTURE
const BODY = 'BODY';
+ // An alternate form of BODY[] that does not implicitly set the \Seen flag.
const BODY_PEEK = 'BODY.PEEK';
+ // The text of a particular body section.
const BODY_HEADER = 'BODY[HEADER]';
const BODY_HEADER_PEEK = 'BODY.PEEK[HEADER]';
const BODYSTRUCTURE = 'BODYSTRUCTURE';
const ENVELOPE = 'ENVELOPE';
const FLAGS = 'FLAGS';
const INTERNALDATE = 'INTERNALDATE';
- const RFC822 = 'RFC822';
- const RFC822_HEADER = 'RFC822.HEADER';
+// const RFC822 = 'RFC822'; // Functionally equivalent to BODY[]
+// const RFC822_HEADER = 'RFC822.HEADER'; // Functionally equivalent to BODY.PEEK[HEADER]
const RFC822_SIZE = 'RFC822.SIZE';
- const RFC822_TEXT = 'RFC822.TEXT';
+// const RFC822_TEXT = 'RFC822.TEXT'; // Functionally equivalent to BODY[TEXT]
const UID = 'UID';
// RFC 3516
const BINARY = 'BINARY';
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
index cf92b029d..e07557764 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
@@ -158,6 +158,7 @@ class MailClient
$aFetchItems = array(
FetchType::UID,
+// FetchType::FAST,
FetchType::RFC822_SIZE,
FetchType::INTERNALDATE,
FetchType::FLAGS,
@@ -180,12 +181,18 @@ class MailClient
$aFetchItems[] = $sLine;
}
-
- $gSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
- foreach ($gSignatureParts as $oPart)
- {
- $aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->PartID().']';
+/*
+ $gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
+ foreach ($gSignatureParts as $oPart) {
+ if ($oPart->IsPgpSigned()) {
+ // An empty section specification refers to the entire message, including the header.
+ // But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
+ $aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[0]->PartID().'.MIME]';
+ $aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[0]->PartID().']';
+ $aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[1]->PartID().']';
+ }
}
+*/
}
}
@@ -197,8 +204,7 @@ class MailClient
$aFetchResponse = $this->oImapClient->Fetch($aFetchItems, $iIndex, $bIndexIsUid);
if (\count($aFetchResponse))
{
- $oMessage = Message::NewFetchResponseInstance(
- $sFolderName, $aFetchResponse[0], $oBodyStructure);
+ $oMessage = Message::NewFetchResponseInstance($sFolderName, $aFetchResponse[0], $oBodyStructure);
}
return $oMessage;
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php
index 8c3c1aeb4..5a65d33fb 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php
@@ -79,9 +79,7 @@ class Message implements \JsonSerializable
$bTextPartIsTrimmed = false,
- $sPgpSignature = '',
- $sPgpSignatureMicAlg = '',
- $bPgpSigned = false,
+ $aPgpSigned = null,
$bPgpEncrypted = false;
function __construct()
@@ -99,19 +97,9 @@ class Message implements \JsonSerializable
return $this->sHtml;
}
- public function PgpSignature() : string
+ public function PgpSigned() : ?array
{
- return $this->sPgpSignature;
- }
-
- public function PgpSignatureMicAlg() : string
- {
- return $this->sPgpSignatureMicAlg;
- }
-
- public function isPgpSigned() : bool
- {
- return $this->bPgpSigned;
+ return $this->aPgpSigned;
}
public function isPgpEncrypted() : bool
@@ -286,11 +274,8 @@ class Message implements \JsonSerializable
public static function NewFetchResponseInstance(string $sFolder, \MailSo\Imap\FetchResponse $oFetchResponse, ?\MailSo\Imap\BodyStructure $oBodyStructure = null) : self
{
- return (new self)->InitByFetchResponse($sFolder, $oFetchResponse, $oBodyStructure);
- }
+ $oMessage = new self;
- public function InitByFetchResponse(string $sFolder, \MailSo\Imap\FetchResponse $oFetchResponse, ?\MailSo\Imap\BodyStructure $oBodyStructure = null) : self
- {
if (!$oBodyStructure)
{
$oBodyStructure = $oFetchResponse->GetFetchBodyStructure();
@@ -299,12 +284,12 @@ class Message implements \JsonSerializable
$sInternalDate = $oFetchResponse->GetFetchValue(FetchType::INTERNALDATE);
$aFlags = $oFetchResponse->GetFetchValue(FetchType::FLAGS);
- $this->sFolder = $sFolder;
- $this->iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
- $this->iSize = (int) $oFetchResponse->GetFetchValue(FetchType::RFC822_SIZE);
- $this->aFlagsLowerCase = \array_map('strtolower', $aFlags ?: []);
+ $oMessage->sFolder = $sFolder;
+ $oMessage->iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
+ $oMessage->iSize = (int) $oFetchResponse->GetFetchValue(FetchType::RFC822_SIZE);
+ $oMessage->aFlagsLowerCase = \array_map('strtolower', $aFlags ?: []);
- $this->iInternalTimeStampInUTC =
+ $oMessage->iInternalTimeStampInUTC =
\MailSo\Base\DateTimeHelper::ParseInternalDateString($sInternalDate);
$sCharset = $oBodyStructure ? Utils::NormalizeCharset($oBodyStructure->SearchCharset()) : '';
@@ -331,33 +316,33 @@ class Message implements \JsonSerializable
$bCharsetAutoDetect = !\strlen($sCharset);
- $this->sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, $bCharsetAutoDetect);
- $this->sMessageId = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::MESSAGE_ID);
- $this->sContentType = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE);
+ $oMessage->sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, $bCharsetAutoDetect);
+ $oMessage->sMessageId = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::MESSAGE_ID);
+ $oMessage->sContentType = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE);
- $this->oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, $bCharsetAutoDetect);
- $this->oTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::TO_, $bCharsetAutoDetect);
- $this->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect);
- $this->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect);
+ $oMessage->oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, $bCharsetAutoDetect);
+ $oMessage->oTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::TO_, $bCharsetAutoDetect);
+ $oMessage->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect);
+ $oMessage->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect);
- if ($this->oFrom) {
- $oHeaders->PopulateEmailColectionByDkim($this->oFrom);
+ if ($oMessage->oFrom) {
+ $oHeaders->PopulateEmailColectionByDkim($oMessage->oFrom);
}
- $this->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect);
- $this->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect);
- $this->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect);
+ $oMessage->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect);
+ $oMessage->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect);
+ $oMessage->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect);
- $this->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO);
- $this->sReferences = Utils::StripSpaces(
+ $oMessage->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO);
+ $oMessage->sReferences = Utils::StripSpaces(
$oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES));
$sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE);
- $this->sHeaderDate = $sHeaderDate;
- $this->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sHeaderDate);
+ $oMessage->sHeaderDate = $sHeaderDate;
+ $oMessage->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sHeaderDate);
// Priority
- $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
+ $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
$sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY);
if (!\strlen($sPriority))
{
@@ -376,7 +361,7 @@ class Message implements \JsonSerializable
case '2(high)':
case '1':
case '2':
- $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH;
+ $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH;
break;
case 'low':
@@ -384,78 +369,78 @@ class Message implements \JsonSerializable
case '5(lowest)':
case '4':
case '5':
- $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW;
+ $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW;
break;
}
}
// Delivery Receipt
- $this->sDeliveryReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO));
+ $oMessage->sDeliveryReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO));
// Read Receipt
- $this->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO));
- if (empty($this->sReadReceipt))
+ $oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO));
+ if (empty($oMessage->sReadReceipt))
{
- $this->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO));
+ $oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO));
}
// Unsubscribe links
- $this->aUnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE);
- if (empty($this->aUnsubsribeLinks))
+ $oMessage->aUnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE);
+ if (empty($oMessage->aUnsubsribeLinks))
{
- $this->aUnsubsribeLinks = array();
+ $oMessage->aUnsubsribeLinks = array();
}
else
{
- $this->aUnsubsribeLinks = explode(',', $this->aUnsubsribeLinks);
- $this->aUnsubsribeLinks = array_map(
+ $oMessage->aUnsubsribeLinks = explode(',', $oMessage->aUnsubsribeLinks);
+ $oMessage->aUnsubsribeLinks = array_map(
function ($link) {
return trim($link, ' <>');
},
- $this->aUnsubsribeLinks
+ $oMessage->aUnsubsribeLinks
);
}
if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAMD_RESULT)) {
if (\preg_match('/\\[([\\d\\.-]+)\\s*\\/\\s*([\\d\\.]+)\\];/', $spam, $match)) {
if ($threshold = \floatval($match[2])) {
- $this->iSpamScore = \intval(\max(0, \min(100, 100 * \floatval($match[1]) / $threshold)));
- $this->sSpamResult = "{$match[1]} / {$match[2]}";
+ $oMessage->iSpamScore = \intval(\max(0, \min(100, 100 * \floatval($match[1]) / $threshold)));
+ $oMessage->sSpamResult = "{$match[1]} / {$match[2]}";
}
}
- $this->bIsSpam = false !== \stripos($this->sSubject, '*** SPAM ***');
+ $oMessage->bIsSpam = false !== \stripos($oMessage->sSubject, '*** SPAM ***');
} else if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_BOGOSITY)) {
- $this->sSpamResult = $spam;
- $this->bIsSpam = !!\preg_match('/yes|spam/', $spam);
+ $oMessage->sSpamResult = $spam;
+ $oMessage->bIsSpam = !!\preg_match('/yes|spam/', $spam);
if (\preg_match('/spamicity=([\\d\\.]+)/', $spam, $spamicity)) {
- $this->iSpamScore = \intval(\max(0, \min(100, \floatval($spamicity[1]))));
+ $oMessage->iSpamScore = \intval(\max(0, \min(100, \floatval($spamicity[1]))));
}
} else if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAM_STATUS)) {
- $this->sSpamResult = $spam;
+ $oMessage->sSpamResult = $spam;
if (\preg_match('/(?:hits|score)=([\\d\\.-]+)/', $spam, $value)
&& \preg_match('/required=([\\d\\.-]+)/', $spam, $required)) {
if ($threshold = \floatval($required[1])) {
- $this->iSpamScore = \intval(\max(0, \min(100, 100 * \floatval($value[1]) / $threshold)));
- $this->sSpamResult = "{$value[1]} / {$required[1]}";
+ $oMessage->iSpamScore = \intval(\max(0, \min(100, 100 * \floatval($value[1]) / $threshold)));
+ $oMessage->sSpamResult = "{$value[1]} / {$required[1]}";
}
}
- $this->bIsSpam = 'Yes' === \substr($spam, 0, 3);
+ $oMessage->bIsSpam = 'Yes' === \substr($spam, 0, 3);
// $spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAM_FLAG);
-// $this->bIsSpam = false !== \stripos($spam, 'YES');
+// $oMessage->bIsSpam = false !== \stripos($spam, 'YES');
}
if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS)) {
- $this->bHasVirus = true;
+ $oMessage->bHasVirus = true;
}
if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS_STATUS)) {
if (false !== \stripos($spam, 'infected')) {
- $this->bHasVirus = true;
+ $oMessage->bHasVirus = true;
} else if (false !== \stripos($spam, 'clean')) {
- $this->bHasVirus = false;
+ $oMessage->bHasVirus = false;
}
}
if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS_SCANNED)) {
- $this->sVirusScanned = $virus;
+ $oMessage->sVirusScanned = $virus;
}
$sDraftInfo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_DRAFT_INFO);
@@ -481,7 +466,7 @@ class Message implements \JsonSerializable
}
if (\strlen($sType) && \strlen($sFolder) && $iUid) {
- $this->aDraftInfo = array($sType, $iUid, $sFolder);
+ $oMessage->aDraftInfo = array($sType, $iUid, $sFolder);
}
}
}
@@ -490,119 +475,141 @@ class Message implements \JsonSerializable
$sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::ISO_8859_1;
// date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to, message-id
- $this->sMessageId = $oFetchResponse->GetFetchEnvelopeValue(9, '');
- $this->sSubject = Utils::DecodeHeaderValue($oFetchResponse->GetFetchEnvelopeValue(1, ''), $sCharset);
+ $oMessage->sMessageId = $oFetchResponse->GetFetchEnvelopeValue(9, '');
+ $oMessage->sSubject = Utils::DecodeHeaderValue($oFetchResponse->GetFetchEnvelopeValue(1, ''), $sCharset);
- $this->oFrom = $oFetchResponse->GetFetchEnvelopeEmailCollection(2, $sCharset);
- $this->oSender = $oFetchResponse->GetFetchEnvelopeEmailCollection(3, $sCharset);
- $this->oReplyTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(4, $sCharset);
- $this->oTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(5, $sCharset);
- $this->oCc = $oFetchResponse->GetFetchEnvelopeEmailCollection(6, $sCharset);
- $this->oBcc = $oFetchResponse->GetFetchEnvelopeEmailCollection(7, $sCharset);
- $this->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
- }
-
- // Content-Type: multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"
- if ('multipart/signed' === \strtolower($this->sContentType)
- && 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)))
- {
- $gPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null;
- $this->bPgpSigned = $gPgpSignatureParts && $gPgpSignatureParts->valid();
- if ($this->bPgpSigned) {
- $sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$gPgpSignatureParts->current()->PartID().']');
- if (\is_string($sPgpSignatureText) && \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
- $this->sPgpSignature = \trim($sPgpSignatureText);
- $this->sPgpSignatureMicAlg = (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg');
- }
- }
+ $oMessage->oFrom = $oFetchResponse->GetFetchEnvelopeEmailCollection(2, $sCharset);
+ $oMessage->oSender = $oFetchResponse->GetFetchEnvelopeEmailCollection(3, $sCharset);
+ $oMessage->oReplyTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(4, $sCharset);
+ $oMessage->oTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(5, $sCharset);
+ $oMessage->oCc = $oFetchResponse->GetFetchEnvelopeEmailCollection(6, $sCharset);
+ $oMessage->oBcc = $oFetchResponse->GetFetchEnvelopeEmailCollection(7, $sCharset);
+ $oMessage->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
}
// Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"
- $this->bPgpEncrypted = ('multipart/encrypted' === \strtolower($this->sContentType)
+ $oMessage->bPgpEncrypted = ('multipart/encrypted' === \strtolower($oMessage->sContentType)
&& 'application/pgp-encrypted' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)));
- $aTextParts = $oBodyStructure ? $oBodyStructure->GetHtmlAndPlainParts() : null;
-
- if ($aTextParts)
- {
- $sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::UTF_8;
-
- $aHtmlParts = array();
- $aPlainParts = array();
-
- foreach ($aTextParts as $oPart)
- {
- $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
- if (null === $sText)
- {
- $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
- if (\is_string($sText) && \strlen($sText))
- {
- $this->bTextPartIsTrimmed = true;
- }
- }
-
- if (\is_string($sText) && \strlen($sText))
- {
- $sTextCharset = $oPart->Charset();
- if (empty($sTextCharset))
- {
- $sTextCharset = $sCharset;
- }
-
- $sTextCharset = Utils::NormalizeCharset($sTextCharset, true);
-
- $sText = Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName());
- $sText = Utils::ConvertEncoding($sText, $sTextCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
- $sText = Utils::Utf8Clear($sText);
-
- if ('text/html' === $oPart->ContentType())
- {
- $aHtmlParts[] = $sText;
- }
- else
- {
- if ($oPart->IsFlowedFormat())
- {
- $sText = Utils::DecodeFlowedFormat($sText);
- }
-
- $aPlainParts[] = $sText;
- }
- }
- }
-
- $this->sHtml = \implode('
', $aHtmlParts);
- $this->sPlain = \trim(\implode("\n", $aPlainParts));
-
- $aMatch = array();
- if (!$this->bPgpSigned && \preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
- {
- $this->sPgpSignature = \trim($aMatch[0]);
- $this->bPgpSigned = true;
- }
-
- $this->bPgpEncrypted = !$this->bPgpEncrypted && false !== \stripos($this->sPlain, '-----BEGIN PGP MESSAGE-----');
-
- unset($aHtmlParts, $aPlainParts, $aMatch);
- }
-
if ($oBodyStructure)
{
+ $gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
+ foreach ($gSignatureParts as $oPart) {
+ if (!$oPart->IsPgpSigned()) {
+ continue;
+ }
+ $oPgpSignaturePart = $oBodyStructure->SubParts()[1];
+ $oMessage->aPgpSigned = [
+ // /?/Raw/&q[]=/0/Download/&q[]=/...
+ // /?/Raw/&q[]=/0/View/&q[]=/...
+ 'BodyPartId' => $oBodyStructure->SubParts()[0]->PartID(),
+ 'SigPartId' => $oPgpSignaturePart->PartID(),
+ 'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg')
+ ];
+/*
+ // An empty section specification refers to the entire message, including the header.
+ // But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
+ $sPgpText = \trim(
+ \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].'.MIME]'))
+ . "\r\n\r\n"
+ . \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].']'))
+ );
+ if ($sPgpText) {
+ $oMessage->aPgpSigned['Body'] = $sPgpText;
+ }
+ $sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['SigPartId'].']');
+ if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
+ $oMessage->aPgpSigned['Signature'] = $oBodyStructure->SubParts()[0]->PartID();
+ }
+*/
+ break;
+ }
+
+ $aTextParts = $oBodyStructure->GetHtmlAndPlainParts();
+ if ($aTextParts)
+ {
+ $sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::UTF_8;
+
+ $aHtmlParts = array();
+ $aPlainParts = array();
+
+ foreach ($aTextParts as $oPart)
+ {
+ $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
+ if (null === $sText)
+ {
+ $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
+ if (\is_string($sText) && \strlen($sText))
+ {
+ $oMessage->bTextPartIsTrimmed = true;
+ }
+ }
+
+ if (\is_string($sText) && \strlen($sText))
+ {
+ $sTextCharset = $oPart->Charset();
+ if (empty($sTextCharset))
+ {
+ $sTextCharset = $sCharset;
+ }
+
+ $sTextCharset = Utils::NormalizeCharset($sTextCharset, true);
+
+ $sText = Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName());
+ $sText = Utils::ConvertEncoding($sText, $sTextCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
+ $sText = Utils::Utf8Clear($sText);
+
+ if ('text/html' === $oPart->ContentType())
+ {
+ $aHtmlParts[] = $sText;
+ }
+ else
+ {
+ if ($oPart->IsFlowedFormat())
+ {
+ $sText = Utils::DecodeFlowedFormat($sText);
+ }
+
+ $aPlainParts[] = $sText;
+ }
+ }
+ }
+
+ $oMessage->sHtml = \implode('
', $aHtmlParts);
+ $oMessage->sPlain = \trim(\implode("\n", $aPlainParts));
+
+ $aMatch = array();
+ if (!$oMessage->aPgpSigned && \preg_match('/-----BEGIN PGP SIGNATURE-----.+?-----END PGP SIGNATURE-----/ism', $oMessage->sPlain, $aMatch))
+ {
+ $oMessage->aPgpSigned = [
+ // /?/Raw/&q[]=/0/Download/&q[]=/...
+ // /?/Raw/&q[]=/0/View/&q[]=/...
+ 'BodyPartId' => 0,
+ 'SigPartId' => 0,
+ 'MicAlg' => '',
+ 'Signature' => \trim($aMatch[0])
+ ];
+ }
+
+ $oMessage->bPgpEncrypted = !$oMessage->bPgpEncrypted && false !== \stripos($oMessage->sPlain, '-----BEGIN PGP MESSAGE-----');
+
+ unset($aHtmlParts, $aPlainParts, $aMatch);
+ }
+
$gAttachmentsParts = $oBodyStructure->SearchAttachmentsParts();
if ($gAttachmentsParts->valid())
{
- $this->oAttachments = new AttachmentCollection;
+ $oMessage->oAttachments = new AttachmentCollection;
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem)
{
- $this->oAttachments->append(
- Attachment::NewBodyStructureInstance($this->sFolder, $this->iUid, $oAttachmentItem)
+ $oMessage->oAttachments->append(
+ Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem)
);
}
}
}
- return $this;
+ return $oMessage;
}
public function jsonSerialize()
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php
index 707400ac2..fc8bf4a70 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php
@@ -254,10 +254,9 @@ trait Response
$mResult['Plain'] = $mResponse->Plain();
- $mResult['isPgpSigned'] = $mResponse->isPgpSigned();
+// $this->GetCapa(false, Capa::OPEN_PGP)
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
-// $mResult['PgpSignature'] = $mResponse->PgpSignature();
-// $mResult['PgpSignatureMicAlg'] = $mResponse->PgpSignatureMicAlg();
+ $mResult['PgpSigned'] = $mResponse->PgpSigned();
$mResult['HasExternals'] = $bHasExternals;
$mResult['HasInternals'] = \count($aFoundCIDs) || \count($aFoundContentLocationUrls);
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php
new file mode 100644
index 000000000..9e4568e76
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php
@@ -0,0 +1,188 @@
+doRequest('GET', "{$host}/pks/lookup?op={$op}&options=mr{$fingerprint}&search={$search}");
+ }
+
+ private static $HTTP;
+ private static function HTTP() : \SnappyMail\HTTP\Request
+ {
+ if (!static::$HTTP) {
+ static::$HTTP = \SnappyMail\HTTP\Request::factory(/*'socket' or 'curl'*/);
+ static::$HTTP->max_response_kb = 0;
+ static::$HTTP->timeout = 15; // timeout in seconds.
+ }
+ return static::$HTTP;
+ }
+
+ /**
+ * Request the public key from the hkp servers
+ * Returns PGP PUBLIC KEY BLOCK
+ */
+ public static function get(string $keyId) : string
+ {
+ // add the 0x prefix if absent
+ if ('0x' !== \substr($keyId, 0, 2)) {
+ $keyId = '0x' . $keyId;
+ }
+
+ foreach ($this->keyservers as $host) {
+ $oResponse = static::fetch($host, 'get', $keyId);
+ if (!$oResponse) {
+ \SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
+ continue;
+ }
+ if (200 !== $oResponse->status) {
+ \SnappyMail\Log::info('PGP', "{$oResponse->status} for key {$keyId} on {$host}");
+ continue;
+ }
+
+ return $oResponse->body;
+ }
+
+ throw new \Exception('Could not obtain public key from the keyserver.');
+ }
+
+ /**
+ * Returns all matching keys found on a public keyserver.
+ *
+ * @param string $search String to search for (usually an email, name, or username).
+ * @param bool $fingerprint Provide the key fingerprint for each key.
+ * @param bool $exact Instruct the server to search for an exact match.
+ *
+ * @throws Exception
+ */
+ public static function index(string $search, bool $fingerprint = true, bool $exact = false) : array
+ {
+ $keys = [];
+ foreach ($this->keyservers as $host) {
+ $oResponse = static::fetch($host, 'index', $search, $fingerprint, $exact);
+ if (!$oResponse) {
+ \SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
+ continue;
+ }
+ if (200 !== $oResponse->status) {
+ \SnappyMail\Log::info('PGP', "{$oResponse->status} for search `{$search}` on {$host}");
+ continue;
+ }
+
+ $result = \explode("\n", $oResponse->body);
+ foreach ($result as $line) {
+ // https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00#section-5.2
+ $line = \explode(':', $line);
+ // pub::::::
+ if ('pub' === $line[0]) {
+ if ($curKey) {
+ $keys[] = $curKey;
+ $curKey = null;
+ }
+ // Ignore invalid line
+ if (7 !== \count($line)) {
+ \SnappyMail\Log::info('PGP', "Invalid pub line for search `{$search}` on {$host}");
+ continue;
+ }
+ // Ignore flagged or expired key
+ if (!empty($line[6]) || (!empty($line[5]) && $line[5] <= time())) {
+ continue;
+ }
+ $keyids[$line[4]] = $line[1];
+ $curKey = [
+ 'keyid' => $line[1],
+ 'host' => $host,
+ 'algo' => \intval($line[2]), // https://datatracker.ietf.org/doc/html/rfc2440#section-9.1
+ 'keylen' => \intval($line[3]),
+ 'creationdate' => \strlen($line[4]) ? \intval($line[4]) : null,
+ 'expirationdate' => \strlen($line[5]) ? \intval($line[5]) : null,
+// 'revoked' => \str_contains($line[6], 'r'),
+// 'disabled' => \str_contains($line[6], 'd'),
+// 'expired' => \str_contains($line[6], 'e'),
+ 'uids' => [],
+ ];
+ }
+ // uid::::
+ else if ('uid' === $line[0] && $curKey) {
+ // Ignore invalid line
+ if (5 !== \count($line)) {
+ \SnappyMail\Log::info('PGP', "Invalid uid line for search `{$search}` on {$host}");
+ continue;
+ }
+ // Ignore flagged or expired key
+ if (!empty($line[4]) || (!empty($line[3]) && $line[3] <= time())) {
+ continue;
+ }
+ $curKey['uids'][] = [
+ 'uid' => \urldecode($line[1]),
+ 'creationdate' => \strlen($line[2]) ? \intval($line[2]) : null,
+ 'expirationdate' => \strlen($line[3]) ? \intval($line[3]) : null,
+// 'revoked' => \str_contains($line[4], 'r'),
+// 'disabled' => \str_contains($line[4], 'd'),
+// 'expired' => \str_contains($line[4], 'e'),
+ ];
+ }
+ }
+
+ if ($curKey) {
+ $keys[] = $curKey;
+ }
+
+ return $keys;
+ }
+
+ throw new \Exception('Could not obtain public key from the keyservers');
+ }
+
+ /**
+ * Sends a PGP public key to a public keyserver.
+ *
+ * @param string $host
+ * @param mixed $key The PGP public key.
+ *
+ * @throws Exception
+ */
+ public static function add(string $host, string $key)
+ {
+/*
+ $key = PublicKey::create($key);
+
+ if (!$this->get($key->id)) {
+ $keytext = \urlencode(\trim($key));
+ static::HTTP()->doRequest('POST', "{$host}/pks/add", 'keytext=' . $keytext, [
+ 'Content-Type: application/x-www-form-urlencoded',
+ 'Content-Length: ' . \strlen($keytext),
+ 'Connection: close'
+ ]);
+ }
+*/
+ }
+}
diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html
index 4bc0ab01b..946c65416 100644
--- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html
+++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html
@@ -320,6 +320,15 @@
+
+ 🔒
+
+
+
+ 🔒
+
+
+