diff --git a/dev/External/ko.js b/dev/External/ko.js index 7022c646f..0d693148d 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -66,8 +66,7 @@ Object.assign(ko.bindingHandlers, { }, update: (element, fValueAccessor) => { let value = ko.unwrap(fValueAccessor()); - value = isFunction(value) ? value() : value; - errorTip(element, value); + errorTip(element, isFunction(value) ? value() : value); } }, diff --git a/dev/Stores/User/GnuPG.js b/dev/Stores/User/GnuPG.js index e390162d5..4d4a2422e 100644 --- a/dev/Stores/User/GnuPG.js +++ b/dev/Stores/User/GnuPG.js @@ -204,7 +204,7 @@ export const GnuPGUserStore = new class { } async verify(message) { - let data = message.pgpSigned(); // { bodyPartId: "1", sigPartId: "2", micAlg: "pgp-sha256" } + let data = message.pgpSigned(); // { partId: "1", sigPartId: "2", micAlg: "pgp-sha256" } if (data) { data = { ...data }; // clone // const sender = message.from[0].email; diff --git a/dev/Stores/User/OpenPGP.js b/dev/Stores/User/OpenPGP.js index 1a637f311..198d3fd17 100644 --- a/dev/Stores/User/OpenPGP.js +++ b/dev/Stores/User/OpenPGP.js @@ -229,7 +229,7 @@ export const OpenPGPUserStore = new class { * https://docs.openpgpjs.org/#sign-and-verify-cleartext-messages */ async verify(message) { - const data = message.pgpSigned(), // { bodyPartId: "1", sigPartId: "2", micAlg: "pgp-sha256" } + const data = message.pgpSigned(), // { partId: "1", sigPartId: "2", micAlg: "pgp-sha256" } publicKey = this.publicKeys().find(key => key.emails.includes(message.from[0].email)); if (data && publicKey) { data.folder = message.folder; diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 7eb788464..006da3cc1 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -108,19 +108,14 @@ export const } async verify(message) { - const signed = message.pgpSigned(); + const signed = message.pgpSigned(), + sender = message.from[0].email; if (signed) { - const sender = message.from[0].email, - gnupg = GnuPGUserStore.hasPublicKeyForEmails([sender]), - openpgp = OpenPGPUserStore.hasPublicKeyForEmails([sender]); - // Detached signature use GnuPG first, else we must download whole message - if (gnupg && signed.sigPartId) { - return GnuPGUserStore.verify(message); - } - if (openpgp) { + // OpenPGP only when inline, else we must download the whole message + if (!signed.sigPartId && OpenPGPUserStore.hasPublicKeyForEmails([sender])) { return OpenPGPUserStore.verify(message); } - if (gnupg) { + if (GnuPGUserStore.hasPublicKeyForEmails([sender])) { return GnuPGUserStore.verify(message); } // Mailvelope can't diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 29331d771..51266c971 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -570,29 +570,32 @@ export class MailMessageView extends AbstractViewRight { } pgpDecrypt() { - const oMessage = currentMessage(); + const oMessage = currentMessage(), + data = oMessage.pgpEncrypted(); + delete data.error; PgpUserStore.decrypt(oMessage).then(result => { - if (result) { - oMessage.pgpDecrypted(true); - if (result.data) { - MimeToMessage(result.data, oMessage); - oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain(); - if (result.signatures?.length) { - oMessage.pgpSigned(true); - oMessage.pgpVerified({ - signatures: result.signatures, - success: !!result.signatures.length - }); - } - } - } else { + if (!result) { // TODO: translate throw Error('Decryption failed, canceled or not possible'); } + oMessage.pgpDecrypted(true); + if (result.data) { + MimeToMessage(result.data, oMessage); + oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain(); + if (result.signatures?.length) { + oMessage.pgpSigned(true); + oMessage.pgpVerified({ + signatures: result.signatures, + success: !!result.signatures.length + }); + } + } }) .catch(e => { - console.error(e) - alert(e.message); + data.error = e.message; + }) + .finally(() => { + oMessage.pgpEncrypted(data); }); } @@ -627,16 +630,17 @@ export class MailMessageView extends AbstractViewRight { async smimeDecrypt() { const message = currentMessage(); - let pass, data = message.smimeEncrypted(); // { partId: "1" } const addresses = message.from.concat(message.to, message.cc, message.bcc).map(item => item.email), - identity = IdentityUserStore.find(item => addresses.includes(item.email())); + identity = IdentityUserStore.find(item => addresses.includes(item.email())), + data = message.smimeEncrypted(); // { partId: "1" } if (data && identity) { - data = { ...data }; // clone - data.folder = message.folder; - data.uid = message.uid; -// data.bodyPart = data.bodyPart?.raw; - data.certificate = identity.smimeCertificate(); - data.privateKey = identity.smimeKey(); + delete data.error; + let pass, params = { ...data }; // clone + params.folder = message.folder; + params.uid = message.uid; +// params.bodyPart = params.bodyPart?.raw; + params.certificate = identity.smimeCertificate(); + params.privateKey = identity.smimeKey(); if (identity.smimeKeyEncrypted()) { pass = await Passphrases.ask(identity, i18n('SMIME/PRIVATE_KEY_OF', {EMAIL: identity.email()}), @@ -645,15 +649,20 @@ export class MailMessageView extends AbstractViewRight { if (!pass) { return; } - data.passphrase = pass?.password; + params.passphrase = pass?.password; } - Remote.post('SMimeDecryptMessage', null, data).then(response => { - if (response?.Result) { + Remote.post('SMimeDecryptMessage', null, params).then(response => { + if (response?.Result?.data) { message.smimeDecrypted(true); - MimeToMessage(response.Result, message); + MimeToMessage(response.Result.data, message); message.html() ? message.viewHtml() : message.viewPlain(); pass && pass.remember && Passphrases.set(identity, pass.password); } + }).catch(e => { + data.error = e.message + }) + .finally(() => { + message.smimeEncrypted(data); }); } } 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 bb26b9b17..295fc1fd3 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 @@ -341,7 +341,7 @@ class Message implements \JsonSerializable $oMessage->pgpSigned = [ // /?/Raw/&q[]=/0/Download/&q[]=/... // /?/Raw/&q[]=/0/View/&q[]=/... - 'bodyPartId' => $oPart->SubParts()[0]->PartID(), + 'partId' => $oPart->SubParts()[0]->PartID(), 'sigPartId' => $oPart->SubParts()[1]->PartID(), 'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, 'micalg') : '' ]; @@ -357,9 +357,9 @@ class Message implements \JsonSerializable // 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->pgpSigned['bodyPartId'].'.MIME]')) + \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['partId'].'.MIME]')) . "\r\n\r\n" - . \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['bodyPartId'].']')) + . \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['partId'].']')) ); if ($sPgpText) { $oMessage->pgpSigned['body'] = $sPgpText; @@ -398,7 +398,7 @@ class Message implements \JsonSerializable // Cleartext Signature if (!$oMessage->pgpSigned && \str_contains($sText, '-----BEGIN PGP SIGNED MESSAGE-----')) { $oMessage->pgpSigned = [ - 'bodyPartId' => $oPart->PartID() + 'partId' => $oPart->PartID() ]; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php index 57aba42ea..2f2eae016 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php @@ -303,7 +303,7 @@ trait Pgp } else { $sFolderName = $this->GetActionParam('folder', ''); $iUid = (int) $this->GetActionParam('uid', 0); - $sBodyPartId = $this->GetActionParam('bodyPartId', ''); + $sPartId = $this->GetActionParam('partId', ''); $sSigPartId = $this->GetActionParam('sigPartId', ''); // $sMicAlg = $this->GetActionParam('micAlg', ''); @@ -312,10 +312,10 @@ trait Pgp $oImapClient->FolderExamine($sFolderName); $aParts = [ - FetchType::BODY_PEEK.'['.$sBodyPartId.']', + FetchType::BODY_PEEK.'['.$sPartId.']', // 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]. - FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]' + FetchType::BODY_PEEK.'['.$sPartId.'.MIME]' ]; if ($sSigPartId) { $aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']'; @@ -323,11 +323,11 @@ trait Pgp $oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0]; - $sBodyMime = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]'); + $sBodyMime = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.'.MIME]'); if ($sSigPartId) { $result = [ 'text' => \preg_replace('/\\r?\\n/su', "\r\n", - $sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']') + $sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']') ), 'signature' => preg_replace('/[^\x00-\x7F]/', '', $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']') @@ -336,7 +336,7 @@ trait Pgp } else { // clearsigned text $result = [ - 'text' => $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']'), + 'text' => $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']'), 'signature' => '' ]; $decode = (new \MailSo\Mime\HeaderCollection($sBodyMime))->ValueByName(MimeEnumHeader::CONTENT_TRANSFER_ENCODING); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php index 75e35e5d2..a16850ff7 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php @@ -105,7 +105,7 @@ trait SMime $SMIME->setPrivateKey($sPrivateKey, $oPassphrase); $result = $SMIME->decrypt($sBody); - return $this->DefaultResponse($result ?: false); + return $this->DefaultResponse($result ? ['data' => $result] : false); } public function DoSMimeVerifyMessage() : array diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php index c0c675202..587000ce2 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php @@ -333,9 +333,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface } } if (!$fingerprint) { - if (!empty($result['errors'])) { - \SnappyMail\Log::error('GPG', \implode("\n\t", $result['errors'])); - } return false; } @@ -394,10 +391,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface } } - if (!empty($result['errors'][0])) { - \SnappyMail\Log::warning('GPG', $result['errors'][0]); - } - return false; } @@ -809,12 +802,11 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface $this->_debug('BEGIN DETECT MESSAGE KEY IDs'); $this->setInput($data); // $_ENV['PINENTRY_USER_DATA'] = null; - $result = $this->exec(['--decrypt','--skip-verify']); + $result = $this->exec(['--decrypt','--skip-verify'], false); $info = [ 'ENC_TO' => [], // 'KEY_CONSIDERED' => [], // 'NO_SECKEY' => [], -// 'errors' => $result['errors'] ]; foreach ($result['status'] as $line) { $tokens = \explode(' ', $line); @@ -826,7 +818,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface return $info['ENC_TO']; } - private function exec(array $arguments) /*: array|false*/ + private function exec(array $arguments, bool $throw = true) /*: array|false*/ { if (\version_compare($this->version, '2.2.5', '<')) { \SnappyMail\Log::error('GPG', "{$this->version} too old"); @@ -948,12 +940,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // Timeout after 5 seconds if (5 < \microtime(1) - $start) { $errors[] = 'timeout'; - return [ - 'output' => '', - 'status' => $status, - 'errors' => $errors - ]; - exit; + throw new \RuntimeException(\implode("\n", $errors)); } $inputStreams = []; @@ -1076,7 +1063,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // to use too much memory if (\in_array($this->_input, $inputStreams, true) && \strlen($inputBuffer) < self::CHUNK_SIZE) { $this->_debug('input stream is ready for reading'); - $this->_debug('=> about to read ' . self::CHUNK_SIZE . ' bytes from input stream'); $chunk = \fread($this->_input, self::CHUNK_SIZE); $length = \strlen($chunk); $inputBuffer .= $chunk; @@ -1099,7 +1085,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // read message (from PHP stream) if (\in_array($this->_message, $inputStreams, true)) { $this->_debug('message stream is ready for reading'); - $this->_debug('=> about to read ' . self::CHUNK_SIZE . ' bytes from message stream'); $chunk = \fread($this->_message, self::CHUNK_SIZE); $length = \strlen($chunk); $messageBuffer .= $chunk; @@ -1109,7 +1094,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // read output (from GPG) if (\in_array($fdOutput, $inputStreams, true)) { $this->_debug('output stream ready for reading'); - $this->_debug('=> about to read ' . self::CHUNK_SIZE . ' bytes from output'); $chunk = \fread($fdOutput, self::CHUNK_SIZE); $length = \strlen($chunk); $outputBuffer .= $chunk; @@ -1136,7 +1120,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // read error (from GPG) if (\in_array($fdError, $inputStreams, true)) { $this->_debug('error stream ready for reading'); - $this->_debug('=> about to read ' . self::CHUNK_SIZE . ' bytes from error'); foreach ($this->_openPipes->readPipeLines(self::FD_ERROR) as $line) { $errors[] = $line; $this->_debug("\t{$line}"); @@ -1146,7 +1129,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface // read status (from GPG) if (\in_array($fdStatus, $inputStreams, true)) { $this->_debug('status stream ready for reading'); - $this->_debug('=> about to read ' . self::CHUNK_SIZE . ' bytes from status'); // pass lines to status handlers foreach ($this->_openPipes->readPipeLines(self::FD_STATUS) as $line) { // only pass lines beginning with magic prefix @@ -1199,12 +1181,16 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface $this->_debug('END PROCESSING'); - $this->proc_close(); + $exitCode = $this->proc_close(); $this->_message = null; $this->_input = null; $this->_output = null; + if ($throw && $exitCode && $errors) { + throw new \RuntimeException(\implode("\n", $errors), $exitCode); + } + return [ 'output' => $outputBuffer, 'status' => $status, 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 111b344f6..8459043bd 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 @@ -296,10 +296,11 @@