diff --git a/extension/chrome/elements/compose-modules/compose-input-module.ts b/extension/chrome/elements/compose-modules/compose-input-module.ts index 0afaa631b05..762f9fbcfd8 100644 --- a/extension/chrome/elements/compose-modules/compose-input-module.ts +++ b/extension/chrome/elements/compose-modules/compose-input-module.ts @@ -10,7 +10,9 @@ 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'; +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)); @@ -78,12 +80,25 @@ export class ComposeInputModule extends ViewModule { return this.view.sendBtnModule.popover.choices.richtext; }; + public willInputLimitBeExceeded = (textToPaste: string, targetInputField: HTMLElement, selectionLengthGetter: () => number | undefined) => { + const limit = 50000; + const toBeRemoved = selectionLengthGetter() || 0; + const currentLength = targetInputField.innerText.trim().length; + const isInputLimitExceeded = currentLength - toBeRemoved + textToPaste.length > limit; + return isInputLimitExceeded; + }; + 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)) { + e.preventDefault(); + await Ui.modal.warning(Lang.compose.inputLimitExceededOnPaste); + return; + } 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 d7311b88c6a..a32d8564bf1 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; @@ -222,6 +223,25 @@ 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 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(); + await Ui.modal.warning(Lang.compose.inputLimitExceededOnPaste); + } + } + }) + ); this.attachmentsModule.setHandlers(); this.inputModule.setHandlers(); this.myPubkeyModule.setHandlers(); 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, 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 {