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 { AttachmentCollectionModel } from 'Model/AttachmentCollection';
|
||||||
import { EmailCollectionModel } from 'Model/EmailCollection';
|
import { EmailCollectionModel } from 'Model/EmailCollection';
|
||||||
import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection';
|
import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection';
|
||||||
|
//import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
import PreviewHTML from 'Html/PreviewMessage.html';
|
import PreviewHTML from 'Html/PreviewMessage.html';
|
||||||
|
|
@ -75,7 +76,7 @@ export class MessageModel extends AbstractModel {
|
||||||
messageId: '',
|
messageId: '',
|
||||||
inReplyTo: '',
|
inReplyTo: '',
|
||||||
references: '',
|
references: '',
|
||||||
autocrypt: {/*addr:'', 'prefer-encrypt':'nopreference', keydata:'BASE64'*/},
|
// autocrypt: ko.observableArray(),
|
||||||
hasVirus: null, // or boolean when scanned
|
hasVirus: null, // or boolean when scanned
|
||||||
priority: 3, // Normal
|
priority: 3, // Normal
|
||||||
internalTimestamp: 0,
|
internalTimestamp: 0,
|
||||||
|
|
@ -287,12 +288,9 @@ export class MessageModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://autocrypt.org/level1.html#the-autocrypt-header
|
// https://autocrypt.org/level1.html#the-autocrypt-header
|
||||||
if (value = headers.valueByName('Autocrypt')) {
|
headers.valuesByName('Autocrypt').forEach(value => {
|
||||||
value.split(';').forEach(entry => {
|
this.autocrypt.push(new MimeHeaderAutocryptModel(value));
|
||||||
entry = entry.trim().split('=', 2);
|
|
||||||
json.autocrypt[entry[0]] = entry[1];
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
return true;
|
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 : '';
|
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) {
|
setTrigger(trigger, value) {
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
value = !!value;
|
value = !!value;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const
|
||||||
? {password:Passphrases.get(key), remember:false}
|
? {password:Passphrases.get(key), remember:false}
|
||||||
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
||||||
pass && pass.remember && Passphrases.set(key, pass.password);
|
pass && pass.remember && Passphrases.set(key, pass.password);
|
||||||
return pass.password;
|
return pass?.password;
|
||||||
},
|
},
|
||||||
|
|
||||||
findGnuPGKey = (keys, query/*, sign*/) =>
|
findGnuPGKey = (keys, query/*, sign*/) =>
|
||||||
|
|
@ -77,29 +77,31 @@ export const GnuPGUserStore = new class {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
key.view = () => {
|
key.fetch = async callback => {
|
||||||
const fetch = pass => Remote.request('GnupgExportKey',
|
if (key.armor) {
|
||||||
(iError, oData) => {
|
callback && callback();
|
||||||
if (oData?.Result) {
|
|
||||||
key.armor = oData.Result;
|
|
||||||
showScreenPopup(OpenPgpKeyPopupView, [key]);
|
|
||||||
} else {
|
} else {
|
||||||
Passphrases.delete(key.id);
|
let pass = '';
|
||||||
|
if (isPrivate) {
|
||||||
|
pass = await askPassphrase(key, 'POPUP_VIEW_TITLE');
|
||||||
}
|
}
|
||||||
}, {
|
if (null != pass) {
|
||||||
|
const result = await Remote.post('GnupgExportKey', null, {
|
||||||
keyId: key.id,
|
keyId: key.id,
|
||||||
isPrivate: isPrivate,
|
isPrivate: isPrivate,
|
||||||
passphrase: pass
|
passphrase: pass
|
||||||
}
|
|
||||||
);
|
|
||||||
if (isPrivate) {
|
|
||||||
askPassphrase(key, 'POPUP_VIEW_TITLE').then(passphrase => {
|
|
||||||
(null !== passphrase) && fetch(passphrase);
|
|
||||||
});
|
});
|
||||||
|
if (result?.Result) {
|
||||||
|
key.armor = result.Result;
|
||||||
|
callback && callback();
|
||||||
} else {
|
} else {
|
||||||
fetch('');
|
Passphrases.delete(key.id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key.armor;
|
||||||
};
|
};
|
||||||
|
key.view = () => key.fetch(() => showScreenPopup(OpenPgpKeyPopupView, [key]));
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
this.publicKeys(oData.Result.public.map(key => initKey(key, 0)));
|
this.publicKeys(oData.Result.public.map(key => initKey(key, 0)));
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,7 @@ export const
|
||||||
* Returns the first library that can.
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
async hasPublicKeyForEmails(recipients) {
|
async hasPublicKeyForEmails(recipients) {
|
||||||
const count = recipients.length;
|
if (recipients.length) {
|
||||||
if (count) {
|
|
||||||
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
||||||
return 'gnupg';
|
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.
|
* Returns headers that should be added to an outgoing email.
|
||||||
* So far this is only the autocrypt header.
|
* 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.addSyncHandler(syncHandlerObj)
|
||||||
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
||||||
mailvelopeKeyring.createKeyGenContainer(selector, {
|
mailvelopeKeyring.createKeyGenContainer(selector, {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
||||||
import { EmailModel } from 'Model/Email';
|
import { EmailModel } from 'Model/Email';
|
||||||
|
import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
|
||||||
import { addressparser } from 'Mime/Address';
|
import { addressparser } from 'Mime/Address';
|
||||||
|
|
||||||
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
|
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
@ -885,13 +886,8 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder];
|
this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder];
|
||||||
this.sInReplyTo = oLastMessage.messageId;
|
this.sInReplyTo = oLastMessage.messageId;
|
||||||
this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim();
|
this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim();
|
||||||
// OpenPGP “Transferable Public Key”
|
oLastMessage.headers().valuesByName('autocrypt').forEach(value => {
|
||||||
let autocrypt = {}, value = oLastMessage.headers().valueByName('autocrypt');
|
let autocrypt = new MimeHeaderAutocryptModel(value);
|
||||||
if (value) {
|
|
||||||
value.split(';').forEach(entry => {
|
|
||||||
entry = entry.split('=', 2);
|
|
||||||
autocrypt[entry[0].trim()] = entry[1].trim();
|
|
||||||
});
|
|
||||||
if (autocrypt.addr && autocrypt.keydata) {
|
if (autocrypt.addr && autocrypt.keydata) {
|
||||||
PgpUserStore.hasPublicKeyForEmails([autocrypt.addr]).then(result =>
|
PgpUserStore.hasPublicKeyForEmails([autocrypt.addr]).then(result =>
|
||||||
result || PgpUserStore.importKey(
|
result || PgpUserStore.importKey(
|
||||||
|
|
@ -908,7 +904,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
*/
|
*/
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ComposeType.Forward:
|
case ComposeType.Forward:
|
||||||
|
|
@ -1444,6 +1440,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
dsn: this.requestDsn() ? 1 : 0,
|
dsn: this.requestDsn() ? 1 : 0,
|
||||||
requireTLS: this.requireTLS() ? 1 : 0,
|
requireTLS: this.requireTLS() ? 1 : 0,
|
||||||
readReceiptRequest: this.requestReadReceipt() ? 1 : 0,
|
readReceiptRequest: this.requestReadReceipt() ? 1 : 0,
|
||||||
|
autocrypt: [],
|
||||||
/**
|
/**
|
||||||
* Basic support for Linked Data (Structured Email)
|
* Basic support for Linked Data (Structured Email)
|
||||||
* https://json-ld.org/
|
* https://json-ld.org/
|
||||||
|
|
@ -1475,6 +1472,11 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
params.encrypted = draft
|
params.encrypted = draft
|
||||||
? await this.mailvelope.createDraft()
|
? await this.mailvelope.createDraft()
|
||||||
: await this.mailvelope.encrypt(recipients);
|
: 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) {
|
} else if (sign || encrypt) {
|
||||||
if (!draft && !hasAttachments && !Text.length) {
|
if (!draft && !hasAttachments && !Text.length) {
|
||||||
throw i18n('COMPOSE/ERROR_EMPTY_BODY');
|
throw i18n('COMPOSE/ERROR_EMPTY_BODY');
|
||||||
|
|
@ -1522,6 +1524,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (encrypt) {
|
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) {
|
if ('openpgp' == encrypt) {
|
||||||
// Doesn't encrypt attachments
|
// Doesn't encrypt attachments
|
||||||
params.encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
|
params.encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
namespace MailSo\Imap;
|
namespace MailSo\Imap;
|
||||||
|
|
||||||
use MailSo\Mime\ParameterCollection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @category MailSo
|
* @category MailSo
|
||||||
* @package Imap
|
* @package Imap
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ class Header implements \JsonSerializable
|
||||||
return \in_array(\strtolower($this->sName), array(
|
return \in_array(\strtolower($this->sName), array(
|
||||||
\strtolower(Enumerations\Header::CONTENT_TYPE),
|
\strtolower(Enumerations\Header::CONTENT_TYPE),
|
||||||
\strtolower(Enumerations\Header::CONTENT_DISPOSITION)
|
\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
|
public function append($oParameter, bool $bToTop = false) : void
|
||||||
{
|
{
|
||||||
assert($oParameter instanceof Parameter);
|
assert($oParameter instanceof Parameter);
|
||||||
parent::append($oParameter, $bToTop);
|
parent::offsetSet(\strtolower($oParameter->Name()), $oParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ParameterValueByName(string $sName) : string
|
public function ParameterValueByName(string $sName) : string
|
||||||
|
|
@ -39,12 +39,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
||||||
public function getParameter(string $sName) : ?Parameter
|
public function getParameter(string $sName) : ?Parameter
|
||||||
{
|
{
|
||||||
$sName = \strtolower(\trim($sName));
|
$sName = \strtolower(\trim($sName));
|
||||||
foreach ($this as $oParam) {
|
return parent::offsetExists($sName) ? parent::offsetGet($sName) : null;
|
||||||
if ($sName === \strtolower($oParam->Name())) {
|
|
||||||
return $oParam;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setParameter(string $sName, string $sValue) : void
|
public function setParameter(string $sName, string $sValue) : void
|
||||||
|
|
@ -53,7 +48,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
||||||
if ($oParam) {
|
if ($oParam) {
|
||||||
$oParam->setValue($sValue);
|
$oParam->setValue($sValue);
|
||||||
} else {
|
} 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->SetInReplyTo($this->GetActionParam('inReplyTo', ''));
|
||||||
$oMessage->SetReferences($this->GetActionParam('references', ''));
|
$oMessage->SetReferences($this->GetActionParam('references', ''));
|
||||||
|
|
||||||
|
$aAutocrypt = $this->GetActionParam('autocrypt', []);
|
||||||
|
foreach ($aAutocrypt as $header) {
|
||||||
|
$oMessage->SetCustomHeader('Autocrypt', "addr={$header['addr']}; keydata={$header['keydata']}");
|
||||||
|
}
|
||||||
|
|
||||||
$aFoundCids = array();
|
$aFoundCids = array();
|
||||||
$aFoundDataURL = array();
|
$aFoundDataURL = array();
|
||||||
$aFoundContentLocationUrls = array();
|
$aFoundContentLocationUrls = array();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue