mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Resolve #342
This commit is contained in:
parent
a45cd5904f
commit
7da5692865
11 changed files with 115 additions and 56 deletions
|
|
@ -15,6 +15,7 @@ import { FileInfo, RFC822 } from 'Common/File';
|
|||
import { AttachmentCollectionModel } from 'Model/AttachmentCollection';
|
||||
import { EmailCollectionModel } from 'Model/EmailCollection';
|
||||
import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection';
|
||||
//import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
|
||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
|
||||
import PreviewHTML from 'Html/PreviewMessage.html';
|
||||
|
|
@ -75,7 +76,7 @@ export class MessageModel extends AbstractModel {
|
|||
messageId: '',
|
||||
inReplyTo: '',
|
||||
references: '',
|
||||
autocrypt: {/*addr:'', 'prefer-encrypt':'nopreference', keydata:'BASE64'*/},
|
||||
// autocrypt: ko.observableArray(),
|
||||
hasVirus: null, // or boolean when scanned
|
||||
priority: 3, // Normal
|
||||
internalTimestamp: 0,
|
||||
|
|
@ -287,12 +288,9 @@ export class MessageModel extends AbstractModel {
|
|||
}
|
||||
|
||||
// https://autocrypt.org/level1.html#the-autocrypt-header
|
||||
if (value = headers.valueByName('Autocrypt')) {
|
||||
value.split(';').forEach(entry => {
|
||||
entry = entry.trim().split('=', 2);
|
||||
json.autocrypt[entry[0]] = entry[1];
|
||||
});
|
||||
}
|
||||
headers.valuesByName('Autocrypt').forEach(value => {
|
||||
this.autocrypt.push(new MimeHeaderAutocryptModel(value));
|
||||
});
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
32
dev/Model/MimeHeaderAutocrypt.js
Normal file
32
dev/Model/MimeHeaderAutocrypt.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
|
||||
export class MimeHeaderAutocryptModel/* extends AbstractModel*/
|
||||
{
|
||||
constructor(value) {
|
||||
// super();
|
||||
this.addr = '';
|
||||
this.prefer_encrypt = 'nopreference', // nopreference or mutual
|
||||
this.keydata = '';
|
||||
|
||||
if (value) {
|
||||
value.split(';').forEach(entry => {
|
||||
entry = entry.split('=');
|
||||
const trim = str => (str || '').trim().replace(/^["']|["']+$/g, '');
|
||||
this[trim(entry[0]).replace('-', '_')] = trim(entry[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
if ('mutual' === this.prefer_encrypt) {
|
||||
return `addr=${this.addr}; prefer-encrypt=mutual; keydata=${this.keydata}`;
|
||||
}
|
||||
return `addr=${this.addr}; keydata=${this.keydata}`;
|
||||
}
|
||||
|
||||
key() {
|
||||
return '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n'
|
||||
+ this.keydata
|
||||
+ '\n-----END PGP PUBLIC KEY BLOCK-----';
|
||||
}
|
||||
}
|
||||
|
|
@ -29,4 +29,10 @@ export class MimeHeaderCollectionModel extends AbstractCollectionModel
|
|||
return header ? header.value : '';
|
||||
}
|
||||
|
||||
valuesByName(name)
|
||||
{
|
||||
name = name.toLowerCase();
|
||||
return this.filter(header => header.name.toLowerCase() === name).map(header => header.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,13 +170,6 @@ export class AbstractFetchRemote
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
getPublicKey(fCallback) {
|
||||
this.request('GetPublicKey', fCallback);
|
||||
}
|
||||
|
||||
setTrigger(trigger, value) {
|
||||
if (trigger) {
|
||||
value = !!value;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const
|
|||
? {password:Passphrases.get(key), remember:false}
|
||||
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
||||
pass && pass.remember && Passphrases.set(key, pass.password);
|
||||
return pass.password;
|
||||
return pass?.password;
|
||||
},
|
||||
|
||||
findGnuPGKey = (keys, query/*, sign*/) =>
|
||||
|
|
@ -77,29 +77,31 @@ export const GnuPGUserStore = new class {
|
|||
);
|
||||
}
|
||||
};
|
||||
key.view = () => {
|
||||
const fetch = pass => Remote.request('GnupgExportKey',
|
||||
(iError, oData) => {
|
||||
if (oData?.Result) {
|
||||
key.armor = oData.Result;
|
||||
showScreenPopup(OpenPgpKeyPopupView, [key]);
|
||||
} else {
|
||||
Passphrases.delete(key.id);
|
||||
}
|
||||
}, {
|
||||
keyId: key.id,
|
||||
isPrivate: isPrivate,
|
||||
passphrase: pass
|
||||
}
|
||||
);
|
||||
if (isPrivate) {
|
||||
askPassphrase(key, 'POPUP_VIEW_TITLE').then(passphrase => {
|
||||
(null !== passphrase) && fetch(passphrase);
|
||||
});
|
||||
key.fetch = async callback => {
|
||||
if (key.armor) {
|
||||
callback && callback();
|
||||
} else {
|
||||
fetch('');
|
||||
let pass = '';
|
||||
if (isPrivate) {
|
||||
pass = await askPassphrase(key, 'POPUP_VIEW_TITLE');
|
||||
}
|
||||
if (null != pass) {
|
||||
const result = await Remote.post('GnupgExportKey', null, {
|
||||
keyId: key.id,
|
||||
isPrivate: isPrivate,
|
||||
passphrase: pass
|
||||
});
|
||||
if (result?.Result) {
|
||||
key.armor = result.Result;
|
||||
callback && callback();
|
||||
} else {
|
||||
Passphrases.delete(key.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return key.armor;
|
||||
};
|
||||
key.view = () => key.fetch(() => showScreenPopup(OpenPgpKeyPopupView, [key]));
|
||||
return key;
|
||||
};
|
||||
this.publicKeys(oData.Result.public.map(key => initKey(key, 0)));
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ export const
|
|||
* Returns the first library that can.
|
||||
*/
|
||||
async hasPublicKeyForEmails(recipients) {
|
||||
const count = recipients.length;
|
||||
if (count) {
|
||||
if (recipients.length) {
|
||||
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
||||
return 'gnupg';
|
||||
}
|
||||
|
|
@ -228,12 +227,37 @@ export const
|
|||
}
|
||||
}
|
||||
|
||||
getPublicKeyOfEmails(recipients) {
|
||||
if (recipients.length) {
|
||||
let result = {};
|
||||
recipients.forEach(email => {
|
||||
OpenPGPUserStore.publicKeys().forEach(key => {
|
||||
if (key.emails.includes(email)) {
|
||||
result[email] = key.armor;
|
||||
}
|
||||
});
|
||||
GnuPGUserStore.publicKeys.map(async key => {
|
||||
if (!result[email] && key.emails.includes(email)) {
|
||||
result[email] = await key.fetch();
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers that should be added to an outgoing email.
|
||||
* So far this is only the autocrypt header.
|
||||
*/
|
||||
/*
|
||||
mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers)
|
||||
mailvelopeKeyring.additionalHeadersForOutgoingEmail(from: 'abc@web.de')
|
||||
.then(function(additional) {
|
||||
console.log('additionalHeadersForOutgoingEmail', additional);
|
||||
// logs: {autocrypt: "addr=abc@web.de; prefer-encrypt=mutual; keydata=..."}
|
||||
});
|
||||
|
||||
mailvelopeKeyring.addSyncHandler(syncHandlerObj)
|
||||
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
||||
mailvelopeKeyring.createKeyGenContainer(selector, {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import Remote from 'Remote/User/Fetch';
|
|||
|
||||
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
||||
import { EmailModel } from 'Model/Email';
|
||||
import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
|
||||
import { addressparser } from 'Mime/Address';
|
||||
|
||||
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
|
||||
|
|
@ -885,13 +886,8 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder];
|
||||
this.sInReplyTo = oLastMessage.messageId;
|
||||
this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim();
|
||||
// OpenPGP “Transferable Public Key”
|
||||
let autocrypt = {}, value = oLastMessage.headers().valueByName('autocrypt');
|
||||
if (value) {
|
||||
value.split(';').forEach(entry => {
|
||||
entry = entry.split('=', 2);
|
||||
autocrypt[entry[0].trim()] = entry[1].trim();
|
||||
});
|
||||
oLastMessage.headers().valuesByName('autocrypt').forEach(value => {
|
||||
let autocrypt = new MimeHeaderAutocryptModel(value);
|
||||
if (autocrypt.addr && autocrypt.keydata) {
|
||||
PgpUserStore.hasPublicKeyForEmails([autocrypt.addr]).then(result =>
|
||||
result || PgpUserStore.importKey(
|
||||
|
|
@ -908,7 +904,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
*/
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} break;
|
||||
|
||||
case ComposeType.Forward:
|
||||
|
|
@ -1444,6 +1440,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
dsn: this.requestDsn() ? 1 : 0,
|
||||
requireTLS: this.requireTLS() ? 1 : 0,
|
||||
readReceiptRequest: this.requestReadReceipt() ? 1 : 0,
|
||||
autocrypt: [],
|
||||
/**
|
||||
* Basic support for Linked Data (Structured Email)
|
||||
* https://json-ld.org/
|
||||
|
|
@ -1475,6 +1472,11 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
params.encrypted = draft
|
||||
? await this.mailvelope.createDraft()
|
||||
: await this.mailvelope.encrypt(recipients);
|
||||
/*
|
||||
Object.entries(PgpUserStore.getPublicKeyOfEmails(recipients) || {}).forEach(([k,v]) =>
|
||||
params.autocrypt.push({addr:k, keydata:v.replace(/-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----/).trim()})
|
||||
);
|
||||
*/
|
||||
} else if (sign || encrypt) {
|
||||
if (!draft && !hasAttachments && !Text.length) {
|
||||
throw i18n('COMPOSE/ERROR_EMPTY_BODY');
|
||||
|
|
@ -1522,6 +1524,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
}
|
||||
if (encrypt) {
|
||||
Object.entries(PgpUserStore.getPublicKeyOfEmails(recipients) || {}).forEach(([k,v]) =>
|
||||
params.autocrypt.push({addr:k, keydata:v.replace(/-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----/).trim()})
|
||||
);
|
||||
if ('openpgp' == encrypt) {
|
||||
// Doesn't encrypt attachments
|
||||
params.encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
|
||||
namespace MailSo\Imap;
|
||||
|
||||
use MailSo\Mime\ParameterCollection;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Imap
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ class Header implements \JsonSerializable
|
|||
return \in_array(\strtolower($this->sName), array(
|
||||
\strtolower(Enumerations\Header::CONTENT_TYPE),
|
||||
\strtolower(Enumerations\Header::CONTENT_DISPOSITION)
|
||||
// ,\strtolower(Enumerations\Header::AUTOCRYPT)
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
public function append($oParameter, bool $bToTop = false) : void
|
||||
{
|
||||
assert($oParameter instanceof Parameter);
|
||||
parent::append($oParameter, $bToTop);
|
||||
parent::offsetSet(\strtolower($oParameter->Name()), $oParameter);
|
||||
}
|
||||
|
||||
public function ParameterValueByName(string $sName) : string
|
||||
|
|
@ -39,12 +39,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
public function getParameter(string $sName) : ?Parameter
|
||||
{
|
||||
$sName = \strtolower(\trim($sName));
|
||||
foreach ($this as $oParam) {
|
||||
if ($sName === \strtolower($oParam->Name())) {
|
||||
return $oParam;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return parent::offsetExists($sName) ? parent::offsetGet($sName) : null;
|
||||
}
|
||||
|
||||
public function setParameter(string $sName, string $sValue) : void
|
||||
|
|
@ -53,7 +48,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
if ($oParam) {
|
||||
$oParam->setValue($sValue);
|
||||
} else {
|
||||
parent::append(new Parameter(\trim($sName), $sValue));
|
||||
$this->append(new Parameter(\trim($sName), $sValue));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1034,6 +1034,11 @@ trait Messages
|
|||
$oMessage->SetInReplyTo($this->GetActionParam('inReplyTo', ''));
|
||||
$oMessage->SetReferences($this->GetActionParam('references', ''));
|
||||
|
||||
$aAutocrypt = $this->GetActionParam('autocrypt', []);
|
||||
foreach ($aAutocrypt as $header) {
|
||||
$oMessage->SetCustomHeader('Autocrypt', "addr={$header['addr']}; keydata={$header['keydata']}");
|
||||
}
|
||||
|
||||
$aFoundCids = array();
|
||||
$aFoundDataURL = array();
|
||||
$aFoundContentLocationUrls = array();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue