From db6274476e84f19b9e53aeea821d2016b0f2363c Mon Sep 17 00:00:00 2001 From: martgil Date: Mon, 30 Jan 2023 14:16:04 +0800 Subject: [PATCH 01/11] prevent pasting large input on secure compose --- .../elements/compose-modules/compose-input-module.ts | 9 +++++++++ extension/chrome/elements/compose.ts | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 0afaa631b05..250c58af8bd 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -78,11 +78,20 @@ export class ComposeInputModule extends ViewModule { return this.view.sendBtnModule.popover.choices.richtext; }; + public checkInputLengthBeforePasting = (textToPaste: string, targetInputField: HTMLElement, ev: ClipboardEvent) => { + const currentLength = targetInputField.innerText.length; + const limit = 50000; + if (textToPaste.length + currentLength > limit) { + ev.preventDefault(); + } + }; + private handlePaste = () => { this.squire.addEventListener('willPaste', (e: WillPasteEvent) => { const div = document.createElement('div'); div.appendChild(e.fragment); const html = div.innerHTML; + this.checkInputLengthBeforePasting(div.innerText, this.view.S.cached('input_text').get(0), e); const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized e.fragment.appendChild(div); diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index b55017b75d0..6a2304b6967 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -222,6 +222,16 @@ export class ComposeView extends View { 'click', this.setHandler(async () => await this.renderModule.openSettingsWithDialog('help'), this.errModule.handle(`help dialog`)) ); + this.S.cached('input_intro').on( + 'paste', + this.setHandler(async (el, ev) => { + const clipboardEvent = ev.originalEvent as ClipboardEvent; + if (clipboardEvent.clipboardData) { + const clipboardData = clipboardEvent.clipboardData.getData('text/plain'); + this.inputModule.checkInputLengthBeforePasting(clipboardData, el, clipboardEvent); + } + }) + ); this.attachmentsModule.setHandlers(); this.inputModule.setHandlers(); this.myPubkeyModule.setHandlers(); From 9646d45ab9a92df224e2419e35d61e556281c831 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 31 Jan 2023 17:12:36 +0000 Subject: [PATCH 02/11] Added type definition for SquireEditor.getRoot() --- extension/types/squire.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extension/types/squire.d.ts b/extension/types/squire.d.ts index 9f4482466d7..26ddacfba5d 100644 --- a/extension/types/squire.d.ts +++ b/extension/types/squire.d.ts @@ -71,6 +71,7 @@ export declare class SquireEditor { removeAllFormatting(): void; changeFormat(formattingToAdd: any, formattingToRemove: any, range: Range): void; setConfig(config: any): SquireEditor; + getRoot(): HTMLElement; } export declare class WillPasteEvent extends ClipboardEvent { From 3349f7075dc1c1f38b3d0548dedb93a7583a4689 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 3 Feb 2023 17:10:45 +0800 Subject: [PATCH 03/11] apply requested change --- .../compose-modules/compose-input-module.ts | 14 ++++++++------ extension/chrome/elements/compose.ts | 12 ++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 250c58af8bd..6ab0285cf11 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -78,12 +78,12 @@ export class ComposeInputModule extends ViewModule { return this.view.sendBtnModule.popover.choices.richtext; }; - public checkInputLengthBeforePasting = (textToPaste: string, targetInputField: HTMLElement, ev: ClipboardEvent) => { - const currentLength = targetInputField.innerText.length; + public willInputLimitBeExceeded = (textToPaste: string, targetInputField: HTMLElement) => { const limit = 50000; - if (textToPaste.length + currentLength > limit) { - ev.preventDefault(); - } + const currentLength = targetInputField.innerText.length; + const isInputLimitExceeded = textToPaste.length + currentLength > limit; + console.log(targetInputField.innerText); + return isInputLimitExceeded; }; private handlePaste = () => { @@ -91,9 +91,11 @@ export class ComposeInputModule extends ViewModule { const div = document.createElement('div'); div.appendChild(e.fragment); const html = div.innerHTML; - this.checkInputLengthBeforePasting(div.innerText, this.view.S.cached('input_text').get(0), e); const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized + if (this.willInputLimitBeExceeded(sanitized.trim(), this.squire.getRoot())) { + e.preventDefault(); + } e.fragment.appendChild(div); }); }; diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 6a2304b6967..55a1f49a6d7 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -227,9 +227,17 @@ export class ComposeView extends View { this.setHandler(async (el, ev) => { const clipboardEvent = ev.originalEvent as ClipboardEvent; if (clipboardEvent.clipboardData) { - const clipboardData = clipboardEvent.clipboardData.getData('text/plain'); - this.inputModule.checkInputLengthBeforePasting(clipboardData, el, clipboardEvent); + const isInputLimitExceeded = this.inputModule.willInputLimitBeExceeded(clipboardEvent.clipboardData.getData('text/plain'), el); + if (isInputLimitExceeded) { + ev.preventDefault(); + } } + // const clipboardData = clipboardEvent?.clipboardData?.getData('text/plain') || ''; + // const isInputLimitExceeded = this.inputModule.willInputLimitBeExceeded(clipboardData, el); + // console.log(isInputLimitExceeded); + // if (isInputLimitExceeded) { + // ev.preventDefault(); + // } }) ); this.attachmentsModule.setHandlers(); From b90f9a53274ef853dde845ae117ceefed02eb2f1 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 3 Feb 2023 17:14:17 +0800 Subject: [PATCH 04/11] update --- .../chrome/elements/compose-modules/compose-input-module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 6ab0285cf11..9b47d22b1cb 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -95,6 +95,7 @@ export class ComposeInputModule extends ViewModule { Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized if (this.willInputLimitBeExceeded(sanitized.trim(), this.squire.getRoot())) { e.preventDefault(); + return; } e.fragment.appendChild(div); }); From f61d1e50f08d97d8480c8e60a86e13d357527c2c Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 3 Feb 2023 17:15:34 +0800 Subject: [PATCH 05/11] cleanup --- .../chrome/elements/compose-modules/compose-input-module.ts | 1 - extension/chrome/elements/compose.ts | 6 ------ 2 files changed, 7 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 9b47d22b1cb..54a59bbfa61 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -82,7 +82,6 @@ export class ComposeInputModule extends ViewModule { const limit = 50000; const currentLength = targetInputField.innerText.length; const isInputLimitExceeded = textToPaste.length + currentLength > limit; - console.log(targetInputField.innerText); return isInputLimitExceeded; }; diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 55a1f49a6d7..479ec45406e 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -232,12 +232,6 @@ export class ComposeView extends View { ev.preventDefault(); } } - // const clipboardData = clipboardEvent?.clipboardData?.getData('text/plain') || ''; - // const isInputLimitExceeded = this.inputModule.willInputLimitBeExceeded(clipboardData, el); - // console.log(isInputLimitExceeded); - // if (isInputLimitExceeded) { - // ev.preventDefault(); - // } }) ); this.attachmentsModule.setHandlers(); From 0d2174367e90126e39ac490f9f644e28a68b458c Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 16 Feb 2023 17:52:54 +0800 Subject: [PATCH 06/11] update --- .../chrome/elements/compose-modules/compose-input-module.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 54a59bbfa61..a6dfb055c9c 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -80,7 +80,7 @@ export class ComposeInputModule extends ViewModule { public willInputLimitBeExceeded = (textToPaste: string, targetInputField: HTMLElement) => { const limit = 50000; - const currentLength = targetInputField.innerText.length; + const currentLength = targetInputField.innerText.trim().length; const isInputLimitExceeded = textToPaste.length + currentLength > limit; return isInputLimitExceeded; }; @@ -91,11 +91,11 @@ export class ComposeInputModule extends ViewModule { div.appendChild(e.fragment); const html = div.innerHTML; const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); - Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized - if (this.willInputLimitBeExceeded(sanitized.trim(), this.squire.getRoot())) { + if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot())) { e.preventDefault(); return; } + Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized e.fragment.appendChild(div); }); }; From 82782ace0c4e135b72b09ebda98e681b8f447e64 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 17 Feb 2023 16:09:51 +0000 Subject: [PATCH 07/11] consider selection --- .../elements/compose-modules/compose-input-module.ts | 7 ++++--- extension/chrome/elements/compose.ts | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index a6dfb055c9c..a8babf03b7f 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -78,10 +78,11 @@ export class ComposeInputModule extends ViewModule { return this.view.sendBtnModule.popover.choices.richtext; }; - public willInputLimitBeExceeded = (textToPaste: string, targetInputField: HTMLElement) => { + public willInputLimitBeExceeded = (textToPaste: string, targetInputField: HTMLElement, selectionLengthGetter: () => number | undefined) => { const limit = 50000; + const toBeRemoved = selectionLengthGetter() || 0; const currentLength = targetInputField.innerText.trim().length; - const isInputLimitExceeded = textToPaste.length + currentLength > limit; + const isInputLimitExceeded = currentLength - toBeRemoved + textToPaste.length > limit; return isInputLimitExceeded; }; @@ -91,7 +92,7 @@ export class ComposeInputModule extends ViewModule { div.appendChild(e.fragment); const html = div.innerHTML; const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); - if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot())) { + if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length)) { e.preventDefault(); return; } diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index d7429df266b..58ff02e320d 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -227,7 +227,13 @@ export class ComposeView extends View { this.setHandler(async (el, ev) => { const clipboardEvent = ev.originalEvent as ClipboardEvent; if (clipboardEvent.clipboardData) { - const isInputLimitExceeded = this.inputModule.willInputLimitBeExceeded(clipboardEvent.clipboardData.getData('text/plain'), el); + const isInputLimitExceeded = this.inputModule.willInputLimitBeExceeded(clipboardEvent.clipboardData.getData('text/plain'), el, () => { + const selection = window.getSelection(); + if (selection && selection.anchorNode === selection.focusNode && selection.anchorNode?.parentElement === el) { + return Math.abs(selection.anchorOffset - selection.focusOffset); + } + return 0; + }); if (isInputLimitExceeded) { ev.preventDefault(); } From 8b394b3c4aa02f6590bdfc2211aed39e1de01e7b Mon Sep 17 00:00:00 2001 From: Mart Date: Sat, 18 Feb 2023 11:23:17 +0800 Subject: [PATCH 08/11] add warning modal --- .../chrome/elements/compose-modules/compose-input-module.ts | 4 +++- extension/chrome/elements/compose.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index a8babf03b7f..cb23a0eadeb 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -10,6 +10,7 @@ import { ParsedRecipients } from '../../../js/common/api/email-provider/email-pr import { Str } from '../../../js/common/core/common.js'; import { Xss } from '../../../js/common/platform/xss.js'; import { ViewModule } from '../../../js/common/view-module.js'; +import { Ui } from '../../../js/common/browser/ui.js'; import { ComposeView } from '../compose.js'; export class ComposeInputModule extends ViewModule { @@ -87,12 +88,13 @@ export class ComposeInputModule extends ViewModule { }; private handlePaste = () => { - this.squire.addEventListener('willPaste', (e: WillPasteEvent) => { + this.squire.addEventListener('willPaste', async (e: WillPasteEvent) => { const div = document.createElement('div'); div.appendChild(e.fragment); const html = div.innerHTML; const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length)) { + await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K"); e.preventDefault(); return; } diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 58ff02e320d..8b6db63a99f 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -235,6 +235,7 @@ export class ComposeView extends View { return 0; }); if (isInputLimitExceeded) { + await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K"); ev.preventDefault(); } } From 7188655bcf14cc4a40504f06fcbcf065ff99c5f7 Mon Sep 17 00:00:00 2001 From: Mart Date: Sat, 18 Feb 2023 11:32:24 +0800 Subject: [PATCH 09/11] update --- .../chrome/elements/compose-modules/compose-input-module.ts | 2 +- extension/chrome/elements/compose.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index cb23a0eadeb..e2c5ea3fa86 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -94,7 +94,7 @@ export class ComposeInputModule extends ViewModule { const html = div.innerHTML; const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length)) { - await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K"); + await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K."); e.preventDefault(); return; } diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 8b6db63a99f..2f08d546ef7 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -235,7 +235,7 @@ export class ComposeView extends View { return 0; }); if (isInputLimitExceeded) { - await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K"); + await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K."); ev.preventDefault(); } } From d1f23e73654eb6bfa34f9f09cd12a401a9323f7d Mon Sep 17 00:00:00 2001 From: Mart Date: Sat, 18 Feb 2023 16:40:15 +0800 Subject: [PATCH 10/11] update --- .../chrome/elements/compose-modules/compose-input-module.ts | 3 ++- extension/chrome/elements/compose.ts | 3 ++- extension/js/common/lang.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index e2c5ea3fa86..762f9fbcfd8 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -12,6 +12,7 @@ import { Xss } from '../../../js/common/platform/xss.js'; import { ViewModule } from '../../../js/common/view-module.js'; import { Ui } from '../../../js/common/browser/ui.js'; import { ComposeView } from '../compose.js'; +import { Lang } from '../../../js/common/lang.js'; export class ComposeInputModule extends ViewModule { public squire = new window.Squire(this.view.S.cached('input_text').get(0)); @@ -94,8 +95,8 @@ export class ComposeInputModule extends ViewModule { const html = div.innerHTML; const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '
', false); if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length)) { - await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K."); e.preventDefault(); + await Ui.modal.warning(Lang.compose.inputLimitExceededOnPaste); return; } Xss.setElementContentDANGEROUSLY(div, sanitized); // xss-sanitized diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 2f08d546ef7..938fad272ad 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -31,6 +31,7 @@ import { PubLookup } from '../../js/common/api/pub-lookup.js'; import { AcctStore } from '../../js/common/platform/store/acct-store.js'; import { AccountServer } from '../../js/common/api/account-server.js'; import { ComposeReplyBtnPopoverModule } from './compose-modules/compose-reply-btn-popover-module.js'; +import { Lang } from 'js/common/lang.js'; export class ComposeView extends View { public readonly acctEmail: string; @@ -235,8 +236,8 @@ export class ComposeView extends View { return 0; }); if (isInputLimitExceeded) { - await Ui.modal.warning("The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K."); ev.preventDefault(); + await Ui.modal.warning(Lang.compose.inputLimitExceededOnPaste); } } }) diff --git a/extension/js/common/lang.ts b/extension/js/common/lang.ts index e3d283c3d53..b19e32982fa 100644 --- a/extension/js/common/lang.ts +++ b/extension/js/common/lang.ts @@ -120,6 +120,7 @@ export const Lang = { enterprisePasswordPolicy: 'Please use password with the following properties:\n - one uppercase\n - one lowercase\n - one number\n - one special character eg &"#-\'_%-@,;:!*()\n - 8 characters length', consumerPasswordPolicy: 'Please use a password at least 8 characters long', + inputLimitExceededOnPaste: "The paste operation can't be completed because the resulting text size would exceed the allowed limit of 50K.", }, general: { contactMinimalSubsentence, From 691c5dff94a31a88a91009eb86383acc984010e5 Mon Sep 17 00:00:00 2001 From: Mart Date: Sat, 18 Feb 2023 16:45:52 +0800 Subject: [PATCH 11/11] update --- extension/chrome/elements/compose.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 938fad272ad..a32d8564bf1 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -31,7 +31,7 @@ import { PubLookup } from '../../js/common/api/pub-lookup.js'; import { AcctStore } from '../../js/common/platform/store/acct-store.js'; import { AccountServer } from '../../js/common/api/account-server.js'; import { ComposeReplyBtnPopoverModule } from './compose-modules/compose-reply-btn-popover-module.js'; -import { Lang } from 'js/common/lang.js'; +import { Lang } from '../../js/common/lang.js'; export class ComposeView extends View { public readonly acctEmail: string;