diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index ddf8764b3..a43756177 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -67,6 +67,15 @@ export const PgpUserStore = new class { return !!(OpenPGPUserStore.isSupported() || GnuPGUserStore.isSupported() || window.mailvelope); } + async mailvelopeHasPublicKeyForEmails(recipients, all) { + const + keyring = this.mailvelopeKeyring, + mailvelope = keyring && await keyring.validKeyForAddress(recipients) + /*.then(LookupResult => Object.entries(LookupResult))*/, + entries = mailvelope && Object.entries(mailvelope); + return !!(entries && (all ? (entries.filter(value => value[1]).length === recipients.length) : entries.length)); + } + /** * Checks if verifying/encrypting a message is possible with given email addresses. * Returns the first library that can. @@ -82,11 +91,7 @@ export const PgpUserStore = new class { return 'gnupg'; } - let keyring = this.mailvelopeKeyring, - mailvelope = keyring && await keyring.validKeyForAddress(recipients) - /*.then(LookupResult => Object.entries(LookupResult))*/; - mailvelope = mailvelope && Object.entries(mailvelope); - if (mailvelope && (all ? (mailvelope.filter(([, value]) => value).length === count) : mailvelope.length)) { + if (await this.mailvelopeHasPublicKeyForEmails(recipients, all)) { return 'mailvelope'; } } @@ -197,52 +202,29 @@ export const PgpUserStore = new class { } } - /** - * Creates an iframe with an editor for a new encrypted mail. - * The iframe will be injected into the container identified by selector. - * https://mailvelope.github.io/mailvelope/Editor.html - */ -/* - mailvelope.createEditorContainer(selector, this.mailvelopeKeyring, { - quota: 20480, // mail content (text + attachments) limit in kilobytes (default: 20480) - signMsg: false, // if true then the mail will be signed (default: false) - armoredDraft: '', // Ascii Armored PGP Text Block - a PGP message, signed and encrypted with the default key of the user, will be used to restore a draft in the editor - The armoredDraft parameter can't be combined with the parameters: predefinedText, quotedMail... parameters, keepAttachments - predefinedText: '', // text that will be added to the editor - quotedMail: '', // Ascii Armored PGP Text Block mail that should be quoted - quotedMailIndent: true, // if true the quoted mail will be indented (default: true) - quotedMailHeader: '', // header to be added before the quoted mail - keepAttachments: false, // add attachments of quotedMail to editor (default: false) - }).then(editor => { - editor.editorId; - }, error_handler) -*/ - /** * Returns headers that should be added to an outgoing email. * So far this is only the autocrypt header. */ /* this.mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers) -*/ - -/* this.mailvelopeKeyring.addSyncHandler(syncHandlerObj) -*/ -/* + this.mailvelopeKeyring.createKeyBackupContainer(selector, options) this.mailvelopeKeyring.createKeyGenContainer(selector, { // userIds: [], keySize: 4096 }) -*/ - -/* - exportOwnPublicKey(emailAddr).then() - - this.mailvelopeKeyring.hasPrivateKey(fingerprint) + this.mailvelopeKeyring.exportOwnPublicKey(emailAddr).then() this.mailvelopeKeyring.importPublicKey(armored) + + // https://mailvelope.github.io/mailvelope/global.html#SyncHandlerObject + this.mailvelopeKeyring.addSyncHandler({ + uploadSync + downloadSync + backup + restore + }); */ }; diff --git a/dev/Styles/User/Compose.less b/dev/Styles/User/Compose.less index 4e96f78dc..06fe38946 100644 --- a/dev/Styles/User/Compose.less +++ b/dev/Styles/User/Compose.less @@ -1,4 +1,11 @@ +.mailvelope-icon { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACjElEQVQ4y2XTXWjVdRgH8M/vf457OUVJzc7U5myWmysrFROKjFyUgqasEsNIrFFetC5KYkXMdVP0chne1MVWUBGWnlAKcogLJmiGXciUkaPtHPYSWumo6XbOv4v/afbyXP14+H6/z8vv+Qb/iNijEdFK4p1Yh1sQ4WccJe4h9Ae54t+ccJW88VpSb+NZdZWVmm+muR4xgwVODzM0dQWfEb0Q7L80KxDbmCH6irDO82uCHZvIVJMfT9Tn11Askevlnd7YdOk4xYeCg5PpBBG9R2gB9bXkjrD/GIXLSYlsBetX0NZK/cLgue41pLrxeIhtuZP4R6uyvPoEVdUcOMKH3/tf3FXDx6/x6de89W1MfH+qS2MnxdVynYyeZzDPM1sYHeH2LJMX2XAHjTfSN8zkOO1P8v6hQLQgjRZLarlhLk/vxTW0b6XjKaoqmJomjslUsrqPjn28tIOVi/lh+O4Ii61dQmEs2WnrUobHad3DR4eoruCxPXz+DQ+sSDAD51jbBDdFYKZ0dc4Hl5EvkJ/itiwjBfKXqath4tfyn8ekEmqEIf3nWJBFKRml/3QCzFzH4VPl/Dy+G0jyyxroOwMTafT6aazJxUlefpjXe7gwnbTa2c0v5ff5C2y+l/saSQdODCGcSnVpzJPaJT0ZtG+j9ySDvyeVfpvhSpwInDzL8vnJZb7yAaN/ILSl3nB2vEtTjRP5eyzKsHs7188wMZaQ5wQWVbFpFQ31bN/L6J+wL8i9W77Emd2kb9XxxSMK48HW9WzbwMhYsrDaeZRKfHkYUYzjlHb+x0ytVRTfxC51ldWaF7K0jjkpzuQZmDXTJxRfDA5e+pdAIrI5Il5OaEMLGsqYsp31EI4FB2bt/Bf/WNuibWZY5gAAAABJRU5ErkJggg==') center/contain no-repeat; + display: inline-block; + height: 1em; + width: 1em; +} + #V-PopupsCompose { height: calc(100vh - 52px); max-width: 1000px; diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 22dd803ed..03e39c9b2 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -14,14 +14,14 @@ import { import { inFocus, pInt, isArray, arrayLength, forEachObjectEntry } from 'Common/Utils'; import { delegateRunOnDestroy, initFullscreen } from 'Common/UtilsUser'; -import { encodeHtml, HtmlEditor } from 'Common/Html'; +import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { serverRequest } from 'Common/Links'; import { i18n, getNotification, getUploadErrorDescByCode } from 'Common/Translator'; import { timestampToString } from 'Common/Momentor'; import { MessageFlagsCache, setFolderHash } from 'Common/Cache'; -import { doc, Settings, SettingsGet, getFullscreenElement, exitFullscreen } from 'Common/Globals'; +import { doc, Settings, SettingsGet, getFullscreenElement, exitFullscreen, elementById } from 'Common/Globals'; import { AppUserStore } from 'Stores/User/App'; import { SettingsUserStore } from 'Stores/User/Settings'; @@ -172,21 +172,21 @@ class ComposePopupView extends AbstractViewPopup { pgpEncrypt: false, canPgpSign: false, canPgpEncrypt: false, + canMailvelope: false, draftsFolder: '', draftUid: 0, sending: false, saving: false, - attachmentsPlace: false, + viewArea: 'body', - composeUploaderButton: null, - composeUploaderDropPlace: null, + composeUploaderButton: null, // initDom + composeUploaderDropPlace: null, // initDom attacheMultipleAllowed: false, addAttachmentEnabled: false, - // div.textAreaParent - composeEditorArea: null, + editorArea: null, // initDom currentIdentity: IdentityUserStore()[0] || null }); @@ -382,10 +382,10 @@ class ComposePopupView extends AbstractViewPopup { if (this.attachmentsInProcess().length) { this.attachmentsInProcessError(true); - this.attachmentsPlace(true); + this.attachmentsArea(); } else if (this.attachmentsInError().length) { this.attachmentsInErrorError(true); - this.attachmentsPlace(true); + this.attachmentsArea(); } if (!this.to().trim() && !this.cc().trim() && !this.bcc().trim()) { @@ -452,7 +452,13 @@ class ComposePopupView extends AbstractViewPopup { 30000 ); - if (encrypt) { + if (this.mailvelope && 'mailvelope' === this.viewArea()) { + this.mailvelope.encrypt(this.allRecipients()).then(armored => { + params.Html = ''; + params.Text = armored; + send(); + }); + } else if (encrypt) { if (params.Html) { throw 'Encrypt HTML with ' + encrypt + ' not yet implemented'; } @@ -557,44 +563,56 @@ class ComposePopupView extends AbstractViewPopup { setFolderHash(FolderUserStore.draftsFolder(), ''); - Remote.request('SaveMessage', - (iError, oData) => { - let result = false; + const + params = this.getMessageRequestParams(FolderUserStore.draftsFolder()), + save = () => + Remote.request('SaveMessage', + (iError, oData) => { + let result = false; - this.saving(false); + this.saving(false); - if (!iError) { - if (oData.Result.NewFolder && oData.Result.NewUid) { - result = true; + if (!iError) { + if (oData.Result.NewFolder && oData.Result.NewUid) { + result = true; - if (this.bFromDraft) { - const message = MessageUserStore.message(); - if (message && this.draftsFolder() === message.folder && this.draftUid() == message.uid) { - MessageUserStore.message(null); + if (this.bFromDraft) { + const message = MessageUserStore.message(); + if (message && this.draftsFolder() === message.folder && this.draftUid() == message.uid) { + MessageUserStore.message(null); + } + } + + this.draftsFolder(oData.Result.NewFolder); + this.draftUid(oData.Result.NewUid); + + this.savedTime(new Date); + + if (this.bFromDraft) { + setFolderHash(this.draftsFolder(), ''); + } } } - this.draftsFolder(oData.Result.NewFolder); - this.draftUid(oData.Result.NewUid); - - this.savedTime(new Date); - - if (this.bFromDraft) { - setFolderHash(this.draftsFolder(), ''); + if (!result) { + this.savedError(true); + this.savedErrorDesc(getNotification(Notification.CantSaveMessage)); } - } - } - if (!result) { - this.savedError(true); - this.savedErrorDesc(getNotification(Notification.CantSaveMessage)); - } + this.reloadDraftFolder(); + }, + params, + 200000 + ); - this.reloadDraftFolder(); - }, - this.getMessageRequestParams(FolderUserStore.draftsFolder()), - 200000 - ); + if (this.mailvelope && 'mailvelope' === this.viewArea()) { + this.mailvelope.createDraft().then(armored => { + params.Text = armored; + save(); + }); + } else { + save(); + } } return true; @@ -736,14 +754,21 @@ class ComposePopupView extends AbstractViewPopup { (getFullscreenElement() === this.oContent) && exitFullscreen(); } + dropMailvelope() { + if (this.mailvelope) { + elementById('mailvelope-editor').textContent = ''; + this.mailvelope = null; + } + } + editor(fOnInit) { - if (fOnInit && this.composeEditorArea()) { + if (fOnInit && this.editorArea()) { if (this.oEditor) { fOnInit(this.oEditor); } else { // setTimeout(() => { this.oEditor = new HtmlEditor( - this.composeEditorArea(), + this.editorArea(), null, () => fOnInit(this.oEditor), bHtml => this.isHtml(!!bHtml) @@ -1201,7 +1226,7 @@ class ComposePopupView extends AbstractViewPopup { this.dragAndDropOver(false); }) .on('onBodyDragEnter', () => { - this.attachmentsPlace(true); + this.attachmentsArea(); this.dragAndDropVisible(true); }) .on('onBodyDragLeave', () => { @@ -1231,7 +1256,7 @@ class ComposePopupView extends AbstractViewPopup { this.attachments.push(attachment); - this.attachmentsPlace(true); + this.attachmentsArea(); if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size) { attachment @@ -1423,7 +1448,7 @@ class ComposePopupView extends AbstractViewPopup { this.attachments.push(attachment); - this.attachmentsPlace(true); + this.attachmentsArea(); return attachment; } @@ -1498,7 +1523,7 @@ class ComposePopupView extends AbstractViewPopup { this.requestReadReceipt(false); this.markAsImportant(false); - this.attachmentsPlace(false); + this.bodyArea(); this.aDraftInfo = null; this.sInReplyTo = ''; @@ -1532,6 +1557,8 @@ class ComposePopupView extends AbstractViewPopup { this.saving(false); this.oEditor && this.oEditor.clear(); + + this.dropMailvelope(); } /** @@ -1543,6 +1570,43 @@ class ComposePopupView extends AbstractViewPopup { ); } + mailvelopeArea() { + /** + * Creates an iframe with an editor for a new encrypted mail. + * The iframe will be injected into the container identified by selector. + * https://mailvelope.github.io/mailvelope/Editor.html + */ + let text = this.oEditor.getData(true), + size = SettingsGet('PhpUploadSizes')['post_max_size'], + quota = pInt(size); + switch (size.slice(-1)) { + case 'G': quota *= 1024; // fallthrough + case 'M': quota *= 1024; // fallthrough + case 'K': quota *= 1024; + } + this.mailvelope || + mailvelope.createEditorContainer('#mailvelope-editor', PgpUserStore.mailvelopeKeyring, { + // https://mailvelope.github.io/mailvelope/global.html#EditorContainerOptions + quota: Math.max(2048, (quota / 1024)) - 48, // (text + attachments) limit in kilobytes + predefinedText: this.oEditor.isHtml() ? htmlToPlain(text) : text +/* + signMsg: false, // if true then the mail will be signed (default: false) + armoredDraft: '', // Ascii Armored PGP Text Block + quotedMail: '', // Ascii Armored PGP Text Block mail that should be quoted + quotedMailIndent: true, // if true the quoted mail will be indented (default: true) + quotedMailHeader: '', // header to be added before the quoted mail + keepAttachments: false, // add attachments of quotedMail to editor (default: false) +*/ + }).then(editor => this.mailvelope = editor); + this.viewArea('mailvelope'); + } + attachmentsArea() { + this.viewArea('attachments'); + } + bodyArea() { + this.viewArea('body'); + } + allRecipients() { const email = new EmailModel(); return [ @@ -1555,14 +1619,22 @@ class ComposePopupView extends AbstractViewPopup { email.clear(); email.parse(value.trim()); return email.email || false; - }).filter(v => v); + }).validUnique(); } initPgpEncrypt() { - return PgpUserStore.hasPublicKeyForEmails(this.allRecipients(), 1).then(result => { - console.log({canPgpEncrypt:result}); - this.canPgpEncrypt(result); - }); + PgpUserStore.hasPublicKeyForEmails(this.allRecipients(), 1).then(result => { + console.log({canPgpEncrypt:result}); + this.canPgpEncrypt(result); + }); + PgpUserStore.mailvelopeHasPublicKeyForEmails(this.allRecipients(), 1).then(result => { + console.log({canMailvelope:result}); + this.canMailvelope(result); + if (!result) { + 'mailvelope' === this.viewArea() && this.bodyArea(); +// this.dropMailvelope(); + } + }); } togglePgpSign() { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index 72558cd44..33285d47d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -782,14 +782,11 @@ class Actions $aResult['ContactsPdoPassword'] = (string)APP_DUMMY; $aResult['WeakPassword'] = \is_file($passfile); - - $aResult['PhpUploadSizes'] = array( - 'upload_max_filesize' => \ini_get('upload_max_filesize'), - 'post_max_size' => \ini_get('post_max_size') - ); } $aResult['Capa'] = $this->Capa(true); + $aResult['LanguageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true); + $aResult['UserLanguageAdmin'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true); } else { $oAccount = $this->getAccountFromToken(false); if ($oAccount) { @@ -901,16 +898,19 @@ class Actions $aResult['Capa'] = $this->Capa(false, $oAccount); } + if ($aResult['Auth']) { + $aResult['PhpUploadSizes'] = array( + 'upload_max_filesize' => \ini_get('upload_max_filesize'), + 'post_max_size' => \ini_get('post_max_size') + ); + } + $sStaticCache = $this->StaticCache(); $aResult['Theme'] = $this->GetTheme($bAdmin); $aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false); $aResult['UserLanguage'] = $this->ValidateLanguage($UserLanguageRaw, '', false, true); - if ($bAdmin) { - $aResult['LanguageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true); - $aResult['UserLanguageAdmin'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true); - } $aResult['PluginsLink'] = ''; if (0 < $this->oPlugins->Count() && $this->oPlugins->HaveJs($bAdmin)) { diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html index 569432785..f067f2c28 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsCompose.html @@ -134,13 +134,13 @@ -
- - +
-
+
    @@ -182,5 +185,7 @@ data-i18n="COMPOSE/NO_ATTACHMENTS_HERE_DESC">
-
+
+ +