diff --git a/extension/css/cryptup.css b/extension/css/cryptup.css index 929c995aaaa..82765b607d3 100644 --- a/extension/css/cryptup.css +++ b/extension/css/cryptup.css @@ -2833,7 +2833,10 @@ body#new_message.full_window { } body#new_message.full_window table#compose { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + box-shadow: + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), + 0 5px 5px -3px rgba(0, 0, 0, 0.2); } div#reply_message_table_container { diff --git a/extension/js/common/api/account-servers/external-service.ts b/extension/js/common/api/account-servers/external-service.ts index 11892712030..e9dc1537623 100644 --- a/extension/js/common/api/account-servers/external-service.ts +++ b/extension/js/common/api/account-servers/external-service.ts @@ -14,6 +14,7 @@ import { ClientConfigurationError, ClientConfigurationJson } from '../../client- import { InMemoryStore } from '../../platform/store/in-memory-store.js'; import { GoogleOAuth } from '../authentication/google/google-oauth.js'; import { AuthenticationConfiguration } from '../../authentication-configuration.js'; +import { Xss } from '../../platform/xss.js'; // todo - decide which tags to use type EventTag = 'compose' | 'decrypt' | 'setup' | 'settings' | 'import-pub' | 'import-prv'; @@ -143,9 +144,9 @@ export class ExternalService extends Api { JSON.stringify({ associateReplyToken, from, - to: (recipients.to || []).map(Str.formatEmailWithOptionalName), - cc: (recipients.cc || []).map(Str.formatEmailWithOptionalName), - bcc: (recipients.bcc || []).map(Str.formatEmailWithOptionalName), + to: (recipients.to || []).map(Str.formatEmailWithOptionalName).map(Xss.stripEmojis), + cc: (recipients.cc || []).map(Str.formatEmailWithOptionalName).map(Xss.stripEmojis), + bcc: (recipients.bcc || []).map(Str.formatEmailWithOptionalName).map(Xss.stripEmojis), }) ), }); diff --git a/extension/js/common/platform/xss.ts b/extension/js/common/platform/xss.ts index 4e038161e44..7d0240e1e10 100644 --- a/extension/js/common/platform/xss.ts +++ b/extension/js/common/platform/xss.ts @@ -7,7 +7,6 @@ import * as DOMPurify from 'dompurify'; import { checkValidURL, CID_PATTERN, Str } from '../core/common.js'; export type SanitizeImgHandling = 'IMG-DEL' | 'IMG-KEEP' | 'IMG-TO-PLAIN-TEXT'; - /** * This class is in platform/ folder because most of it depends on platform specific code * - in browser the implementation uses DOMPurify @@ -51,6 +50,7 @@ export class Xss { private static FORBID_ATTR = ['background']; private static HREF_REGEX_CACHE: RegExp | undefined; private static FORBID_CSS_STYLE = /z-index:[^;]+;|position:[^;]+;|background[^;]+;/g; + private static EMOJI_REGEX = /(?![*#0-9]+)[\p{Emoji}\p{Emoji_Modifier}\p{Emoji_Component}\p{Emoji_Modifier_Base}\p{Emoji_Presentation}]/gu; public static sanitizeRender = (selector: string | HTMLElement | JQuery, dirtyHtml: string) => { // browser-only (not on node) @@ -230,6 +230,10 @@ export class Xss { return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>').replace(/\//g, '/'); }; + public static stripEmojis = (str: string) => { + return str.replace(Xss.EMOJI_REGEX, ''); + }; + public static htmlUnescape = (str: string) => { // the   at the end is replaced with an actual NBSP character, not a space character. IDE won't show you the difference. Do not change. return str diff --git a/package-lock.json b/package-lock.json index d472035bbcf..b149d6d11f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4416,12 +4416,6 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, "node_modules/encoding-japanese": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", @@ -11120,6 +11114,12 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "node_modules/string.prototype.trim": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", diff --git a/test/source/mock/fes/shared-tenant-fes-endpoints.ts b/test/source/mock/fes/shared-tenant-fes-endpoints.ts index e0726f6ce31..35e7b5cdffa 100644 --- a/test/source/mock/fes/shared-tenant-fes-endpoints.ts +++ b/test/source/mock/fes/shared-tenant-fes-endpoints.ts @@ -142,6 +142,9 @@ export const getMockSharedTenantFesEndpoints = (config: FesConfig | undefined): if (req.method === 'POST' && typeof body === 'string') { expect(body).to.contain('-----BEGIN PGP MESSAGE-----'); expect(body).to.contain('"associateReplyToken":"mock-fes-reply-token"'); + if (body.includes('NameWithEmoji')) { + expect(body).to.not.include('⭐'); + } const response = { // this url is required for pubkey encrypted message url: `https://flowcrypt.com/shared-tenant-fes/message/6da5ea3c-d2d6-4714-b15e-f29c805e5c6a`, diff --git a/test/source/tests/compose.ts b/test/source/tests/compose.ts index bf2ac4dae6d..12617388fda 100644 --- a/test/source/tests/compose.ts +++ b/test/source/tests/compose.ts @@ -66,6 +66,24 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te }) ); + test( + 'compose - strip emojis in a recipient email address', + testWithBrowser(async (t, browser) => { + const acct = 'flowcrypt.compatibility@gmail.com'; + await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility', { + google: { acctAliases: flowcryptCompatibilityAliasList }, + }); + const recipientEmail = 'NameWithEmoji ⭐ Test '; + const msgPwd = 'super hard password for the message'; + const subject = 'Test Sending Message With Recipient Name Contains Emoji'; + const composePage = await ComposePageRecipe.openStandalone(t, browser, 'compatibility'); + await ComposePageRecipe.selectFromOption(composePage, acct); + await ComposePageRecipe.fillMsg(composePage, { to: recipientEmail }, subject); + await ComposePageRecipe.sendAndClose(composePage, { password: msgPwd }); + // The actualt test for this is present in '/shared-tenant-fes/api/v1/message' of shared-tenant-fes mock endpoint. + }) + ); + test( 'compose - check for sender [flowcrypt.compatibility@gmail.com] from a password-protected email', testWithBrowser(async (t, browser) => {