From 3fc5216841168f7dec350805000a5de84fc88eb4 Mon Sep 17 00:00:00 2001
From: the-djmaze <>
Date: Fri, 4 Feb 2022 13:40:59 +0100
Subject: [PATCH] Improved Composer handling with PGP messages
---
dev/Common/Html.js | 81 +++++++++++--------------
dev/External/SquireUI.js | 4 +-
dev/Model/Message.js | 11 +---
dev/Stores/User/Pgp.js | 9 ++-
dev/View/Popup/Compose.js | 123 +++++++++++++++++---------------------
5 files changed, 103 insertions(+), 125 deletions(-)
diff --git a/dev/Common/Html.js b/dev/Common/Html.js
index 4cbe5b29a..0ecd2092f 100644
--- a/dev/Common/Html.js
+++ b/dev/Common/Html.js
@@ -376,60 +376,57 @@ export const
);
},
- convertDivs = (...args) => {
- let divText = 1 < args.length ? args[1].trim() : '';
- if (divText.length) {
- divText = '\n' + divText.replace(/
]*>([\s\S\r\n]*)<\/div>/gim, convertDivs).trim() + '\n';
- }
-
- return divText;
- },
-
convertPre = (...args) =>
1 < args.length
- ? args[1]
- .toString()
- .replace(/[\n]/gm, '
')
- .replace(/[\r]/gm, '')
+ ? args[1].toString().replace(/\n/g, '
')
: '',
+
fixAttibuteValue = (...args) => (1 < args.length ? args[1] + encodeHtml(args[2]) : ''),
convertLinks = (...args) => (1 < args.length ? args[1].trim() : '');
+ html = html
+ .replace(/\r?\n/, '')
+ .replace(/
]*>([\s\S]*?)<\/pre>/gim, convertPre)
+ .replace(/\s+/gm, ' ');
+
+ while (/<(div|tr)[\s>]/i.test(html)) {
+ html = html.replace(/\n*<(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
+ }
+ while (/<\/(div|tr)[\s>]/i.test(html)) {
+ html = html.replace(/\n*<\/(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
+ }
+
+ while (/<(ul|ol|p|h\d)[\s>]/i.test(html)) {
+ html = html.replace(/\n*<(ul|ol|p|h\d)(\s[\s\S]*?)?>\n*/gi, '\n\n');
+ }
+ while (/<\/(ul|ol|p|h\d)[\s>]/i.test(html)) {
+ html = html.replace(/\n*<\/(ul|ol|p|h\d)(\s[\s\S]*?)?>\n*/gi, '\n\n');
+ }
+
tpl.innerHTML = html
- .replace(/]*><\/p>/gi, '')
- .replace(/
]*>([\s\S\r\n\t]*)<\/pre>/gim, convertPre)
- .replace(/[\s]+/gm, ' ')
.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gim, fixAttibuteValue)
.replace(/
]*>/gim, '\n')
- .replace(/<\/h[\d]>/gi, '\n')
- .replace(/<\/p>/gi, '\n\n')
- .replace(/]*>/gim, '\n')
- .replace(/<\/ul>/gi, '\n')
.replace(/- ]*>/gim, ' * ')
.replace(/<\/li>/gi, '\n')
- .replace(/<\/td>/gi, '\n')
- .replace(/<\/tr>/gi, '\n')
- .replace(/
]*>/gim, '\n_______________________________\n\n')
- .replace(/]*>([\s\S\r\n]*)<\/div>/gim, convertDivs)
+ .replace(/\n*
/gi, '\t')
+ .replace(/<\/t[dh](\s[\s\S]*?)?>/gi, '\n')
+ .replace(/\n*
\n*/gi, '\n\n' + '⎯'.repeat(64) + '\n\n')
.replace(/]*>/gim, '\n__bq__start__\n')
.replace(/<\/blockquote>/gim, '\n__bq__end__\n')
- .replace(/]*>([\s\S\r\n]*?)<\/a>/gim, convertLinks)
- .replace(/<\/div>/gi, '\n')
+ .replace(/]*>([\s\S]*?)<\/a>/gim, convertLinks)
.replace(/ /gi, ' ')
.replace(/"/gi, '"')
- .replace(/<[^>]*>/gm, '');
+ .replace(/
/gi, '\n')
+ .replace(/<[\s\S]+?>/g, '');
text = tpl.content.textContent;
if (text) {
text = text
- .replace(/\n[ \t]+/gm, '\n')
- .replace(/[\n]{3,}/gm, '\n\n')
+ .replace(/\n{3,}/gm, '\n\n')
.replace(/>/gi, '>')
.replace(/</gi, '<')
- .replace(/&/gi, '&')
- // wordwrap max line length 100
- .match(/.{1,100}(\s|$)|\S+?(\s|$)/g).join('\n');
+ .replace(/&/gi, '&');
}
while (0 < --limit) {
@@ -506,9 +503,9 @@ export const
.replace(/&/g, '&')
.replace(/>/g, '>')
.replace(/')
- .replace(/[\s]*~~~\/blockquote~~~/g, '
')
- .replace(/\n/g, '
');
+ .replace(/~~~blockquote~~~\s*/g, '')
+ .replace(/\s*~~~\/blockquote~~~/g, '
')
+ .replace(/\n/g, '
');
};
export class HtmlEditor {
@@ -600,31 +597,25 @@ export class HtmlEditor {
* @param {boolean=} wrapIsHtml = false
* @returns {string}
*/
- getData(wrapIsHtml = false) {
+ getData() {
let result = '';
if (this.editor) {
try {
if (this.isPlain() && this.editor.plugins.plain && this.editor.__plain) {
result = this.editor.__plain.getRawData();
} else {
- result = wrapIsHtml
- ? '' +
- this.editor.getData() +
- '
'
- : this.editor.getData();
+ result = this.editor.getData();
}
} catch (e) {} // eslint-disable-line no-empty
}
-
return result;
}
/**
- * @param {boolean=} wrapIsHtml = false
* @returns {string}
*/
- getDataWithHtmlMark(wrapIsHtml = false) {
- return (this.isHtml() ? ':HTML:' : '') + this.getData(wrapIsHtml);
+ getDataWithHtmlMark() {
+ return (this.isHtml() ? ':HTML:' : '') + this.getData();
}
modeWysiwyg() {
diff --git a/dev/External/SquireUI.js b/dev/External/SquireUI.js
index 2c474f670..8e6cc1f83 100644
--- a/dev/External/SquireUI.js
+++ b/dev/External/SquireUI.js
@@ -35,7 +35,7 @@ const
addLinks: true // allow_smart_html_links
*/
sanitizeToDOMFragment: (html, isPaste/*, squire*/) => {
- tpl.innerHTML = html
+ tpl.innerHTML = (html||'')
.replace(/<\/?(BODY|HTML)[^>]*>/gi,'')
.replace(//g,'')
.replace(/]*>\s*<\/span>/gi,'')
@@ -104,7 +104,7 @@ const
}
if (!skipInsert) {
- signature = (isHtml ? '
' : "\n\n") + signature + (isHtml ? '' : '');
+ signature = isHtml ? `${signature}
` : `\n\n${signature}\n\n`;
text = insertBefore ? signature + text : text + signature;
diff --git a/dev/Model/Message.js b/dev/Model/Message.js
index 455a0bf34..a91690d58 100644
--- a/dev/Model/Message.js
+++ b/dev/Model/Message.js
@@ -616,23 +616,16 @@ export class MessageModel extends AbstractModel {
bodyAsHTML() {
// if (this.body && !this.body.querySelector('iframe[src*=decrypt]')) {
if (this.body && !this.body.querySelector('iframe')) {
- let clone = this.body.cloneNode(true),
- attr = 'data-html-editor-font-wrapper';
+ let clone = this.body.cloneNode(true);
clone.querySelectorAll('blockquote.rl-bq-switcher').forEach(
node => node.classList.remove('rl-bq-switcher','hidden-bq')
);
clone.querySelectorAll('.rlBlockquoteSwitcher').forEach(
node => node.remove()
);
- clone.querySelectorAll('['+attr+']').forEach(
- node => node.removeAttribute(attr)
- );
return clone.innerHTML;
}
- if (this.isPgpEncrypted()) {
- return this.html() || plainToHtml(this.plain());
- }
- return '';
+ return this.html() || plainToHtml(this.plain());
}
/**
diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js
index a43756177..0d4489e4d 100644
--- a/dev/Stores/User/Pgp.js
+++ b/dev/Stores/User/Pgp.js
@@ -67,6 +67,13 @@ export const PgpUserStore = new class {
return !!(OpenPGPUserStore.isSupported() || GnuPGUserStore.isSupported() || window.mailvelope);
}
+ /**
+ * @returns {boolean}
+ */
+ isEncrypted(text) {
+ return 0 === text.trim().indexOf('-----BEGIN PGP MESSAGE-----');
+ }
+
async mailvelopeHasPublicKeyForEmails(recipients, all) {
const
keyring = this.mailvelopeKeyring,
@@ -129,7 +136,7 @@ export const PgpUserStore = new class {
const sender = message.from[0].email,
armoredText = message.plain();
- if (!armoredText.includes('-----BEGIN PGP MESSAGE-----')) {
+ if (!this.isEncrypted(armoredText)) {
return;
}
diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js
index bcfa1cbca..69b8d749c 100644
--- a/dev/View/Popup/Compose.js
+++ b/dev/View/Popup/Compose.js
@@ -364,7 +364,7 @@ class ComposePopupView extends AbstractViewPopup {
sign = !draft && this.pgpSign() && this.canPgpSign(),
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
TextIsHtml = this.oEditor.isHtml(),
- Text = this.oEditor.getData(true);
+ Text = this.oEditor.getData();
if (TextIsHtml) {
let l;
do {
@@ -708,36 +708,27 @@ class ComposePopupView extends AbstractViewPopup {
}
}
- convertSignature(signature) {
- let fromLine = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
- if (fromLine) {
- signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
-
- if (!fromLine.includes(' ') && 0 < fromLine.indexOf('@')) {
- fromLine = fromLine.replace(/@\S+/, '');
- }
-
- signature = signature.replace(/{{FROM}}/g, fromLine);
- }
-
- return signature
- .replace(/\r/g, '')
- .replace(/\s{1,2}?{{FROM}}/g, '')
- .replace(/\s{1,2}?{{FROM-FULL}}/g, '')
- .replace(/{{DATE}}/g, new Date().format('LLLL'))
- .replace(/{{TIME}}/g, new Date().format('LT'))
- .replace(/{{MOMENT:[^}]+}}/g, '');
- }
-
setSignatureFromIdentity(identity) {
if (identity) {
this.editor(editor => {
- let signature = identity.signature(),
- isHtml = signature && ':HTML:' === signature.slice(0, 6);
-
- editor.setSignature(
- this.convertSignature(isHtml ? signature.slice(6) : signature),
- isHtml, !!identity.signatureInsertBefore());
+ let signature = identity.signature() || '',
+ isHtml = ':HTML:' === signature.slice(0, 6),
+ fromLine = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
+ if (fromLine) {
+ signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
+ if (!fromLine.includes(' ') && 0 < fromLine.indexOf('@')) {
+ fromLine = fromLine.replace(/@\S+/, '');
+ }
+ signature = signature.replace(/{{FROM}}/g, fromLine);
+ }
+ signature = (isHtml ? signature.slice(6) : signature)
+ .replace(/\r/g, '')
+ .replace(/\s{1,2}?{{FROM}}/g, '')
+ .replace(/\s{1,2}?{{FROM-FULL}}/g, '')
+ .replace(/{{DATE}}/g, new Date().format('LLLL'))
+ .replace(/{{TIME}}/g, new Date().format('LT'))
+ .replace(/{{MOMENT:[^}]+}}/g, '');
+ editor.setSignature(signature, isHtml, !!identity.signatureInsertBefore());
});
}
}
@@ -834,7 +825,6 @@ class ComposePopupView extends AbstractViewPopup {
sDate = '',
sSubject = '',
sText = '',
- sReplyTitle = '',
identity = null,
aDraftInfo = null,
message = null;
@@ -882,7 +872,6 @@ class ComposePopupView extends AbstractViewPopup {
sDate = timestampToString(message.dateTimeStampInUTC(), 'FULL');
sSubject = message.subject();
aDraftInfo = message.aDraftInfo;
- sText = message.bodyAsHTML();
let resplyAllParts = null;
switch (lineComposeType) {
@@ -960,65 +949,63 @@ class ComposePopupView extends AbstractViewPopup {
// no default
}
+ sText = message.bodyAsHTML();
+ let encrypted;
+
switch (lineComposeType) {
case ComposeType.Reply:
case ComposeType.ReplyAll:
sFrom = message.fromToLine(false, true);
- sReplyTitle = i18n('COMPOSE/REPLY_MESSAGE_TITLE', {
- DATETIME: sDate,
- EMAIL: sFrom
- });
-
- sText = sText.replace(/
]+>/g, '').replace(/]+><\/a>/g, '').trim();
- sText = '
' + sReplyTitle + ':
' + sText + '
';
-
+ sText = '' + i18n('COMPOSE/REPLY_MESSAGE_TITLE', { DATETIME: sDate, EMAIL: sFrom })
+ + ':
'
+ + sText.replace(/
]+>/g, '').replace(/]+><\/a>/g, '').trim()
+ + '
';
break;
case ComposeType.Forward:
sFrom = message.fromToLine(false, true);
sTo = message.toToLine(false, true);
sCc = message.ccToLine(false, true);
- sText =
- '
' +
- i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') +
- '
' +
- i18n('GLOBAL/FROM') +
- ': ' +
- sFrom +
- '
' +
- i18n('GLOBAL/TO') +
- ': ' +
- sTo +
- (sCc.length ? '
' + i18n('GLOBAL/CC') + ': ' + sCc : '') +
- '
' +
- i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT') +
- ': ' +
- encodeHtml(sDate) +
- '
' +
- i18n('GLOBAL/SUBJECT') +
- ': ' +
- encodeHtml(sSubject) +
- '
' +
- sText.trim() +
- '
';
+ sText = '' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') + '
'
+ + i18n('GLOBAL/FROM') + ': ' + sFrom
+ + '
'
+ + i18n('GLOBAL/TO') + ': ' + sTo
+ + (sCc.length ? '
' + i18n('GLOBAL/CC') + ': ' + sCc : '')
+ + '
'
+ + i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT')
+ + ': '
+ + encodeHtml(sDate)
+ + '
'
+ + i18n('GLOBAL/SUBJECT')
+ + ': '
+ + encodeHtml(sSubject)
+ + '
'
+ + sText.trim()
+ + '
';
break;
case ComposeType.ForwardAsAttachment:
sText = '';
break;
- // no default
+ default:
+ encrypted = PgpUserStore.isEncrypted(sText);
+ if (encrypted) {
+ sText = message.plain();
+ }
}
this.editor(editor => {
- editor.setHtml(sText);
+ encrypted || editor.setHtml(sText);
- if (
- EditorDefaultType.PlainForced === SettingsUserStore.editorDefaultType() ||
- (!message.isHtml() && EditorDefaultType.HtmlForced !== SettingsUserStore.editorDefaultType())
+ if (encrypted
+ || EditorDefaultType.PlainForced === SettingsUserStore.editorDefaultType()
+ || (!message.isHtml() && EditorDefaultType.HtmlForced !== SettingsUserStore.editorDefaultType())
) {
editor.modePlain();
}
+ !encrypted || editor.setPlain(sText);
+
if (identity && ComposeType.Draft !== lineComposeType && ComposeType.EditAsNew !== lineComposeType) {
this.setSignatureFromIdentity(identity);
}
@@ -1507,8 +1494,8 @@ class ComposePopupView extends AbstractViewPopup {
* The iframe will be injected into the container identified by selector.
* https://mailvelope.github.io/mailvelope/Editor.html
*/
- let text = this.oEditor.getData(true),
- encrypted = text.includes('-----BEGIN PGP MESSAGE-----'),
+ let text = this.oEditor.getData(),
+ encrypted = PgpUserStore.isEncrypted(text),
size = SettingsGet('PhpUploadSizes')['post_max_size'],
quota = pInt(size);
switch (size.slice(-1)) {