From 90cba056f1812426583e3f044c6c0e74631ae461 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 10 Jul 2026 11:29:51 -0400 Subject: [PATCH] Remove emoji from plaintext export names. --- .../plaintext/PlaintextExportRepository.kt | 19 +++++++- .../PlaintextExportRepositoryTest.kt | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepository.kt index 80af5011bb..cf51dc8ef6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepository.kt @@ -44,6 +44,18 @@ object PlaintextExportRepository { private val TAG = Log.tag(PlaintextExportRepository::class.java) private const val BATCH_SIZE = 500 + private val EMOJI_REGEX = Regex( + "[" + + "\\x{1F000}-\\x{1FAFF}" + + "\\x{2600}-\\x{27BF}" + + "\\x{2300}-\\x{23FF}" + + "\\x{2B00}-\\x{2BFF}" + + "\\x{FE00}-\\x{FE0F}" + + "\\x{200D}" + + "\\x{20E3}" + + "]" + ) + fun export( context: Context, threadId: Long, @@ -435,7 +447,12 @@ object PlaintextExportRepository { @VisibleForTesting internal fun sanitizeFileName(name: String): String { - val sanitized = name.replace(Regex("[\\\\/:*?\"<>|]"), "_").trim().take(100) + val sanitized = name + .replace(EMOJI_REGEX, "") + .replace(Regex("[\\\\/:*?\"<>|]"), "_") + .replace(Regex("\\s+"), " ") + .trim() + .take(100) return if (sanitized.isEmpty() || sanitized.all { it == '.' }) "chat" else sanitized } diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepositoryTest.kt index 35d3a27efb..32ebf9b606 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/plaintext/PlaintextExportRepositoryTest.kt @@ -731,6 +731,29 @@ class PlaintextExportRepositoryTest { assertEquals("chat", PlaintextExportRepository.sanitizeFileName(" .. ")) } + @Test + fun `sanitizeFileName rejects long dot-only names`() { + assertEquals("chat", PlaintextExportRepository.sanitizeFileName(".".repeat(200))) + } + + @Test + fun `sanitizeFileName rejects dot-only names surrounded by mixed whitespace`() { + assertEquals("chat", PlaintextExportRepository.sanitizeFileName("\t..\n")) + assertEquals("chat", PlaintextExportRepository.sanitizeFileName("\n.\n")) + } + + @Test + fun `sanitizeFileName preserves dots combined with replaced reserved characters`() { + assertEquals(".._", PlaintextExportRepository.sanitizeFileName("../")) + assertEquals(".._..", PlaintextExportRepository.sanitizeFileName("..\\..")) + } + + @Test + fun `sanitizeFileName falls back when name reduces to dots after emoji removal`() { + assertEquals("chat", PlaintextExportRepository.sanitizeFileName("..๐ŸŽ‰")) + assertEquals("chat", PlaintextExportRepository.sanitizeFileName("๐ŸŽ‰.๐ŸŽ‰")) + } + @Test fun `sanitizeFileName falls back for empty result`() { assertEquals("chat", PlaintextExportRepository.sanitizeFileName("")) @@ -743,6 +766,27 @@ class PlaintextExportRepositoryTest { assertEquals("my.chat.group", PlaintextExportRepository.sanitizeFileName("my.chat.group")) } + @Test + fun `sanitizeFileName strips emoji and collapses surrounding whitespace`() { + assertEquals("Team Chat", PlaintextExportRepository.sanitizeFileName("Team ๐ŸŽ‰ Chat")) + assertEquals("Party", PlaintextExportRepository.sanitizeFileName("๐ŸŽ‰Party๐ŸŽ‰")) + assertEquals("Family", PlaintextExportRepository.sanitizeFileName("Family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ")) + assertEquals("flags", PlaintextExportRepository.sanitizeFileName("flags๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ‡ฌ๐Ÿ‡ง")) + assertEquals("heart", PlaintextExportRepository.sanitizeFileName("heartโค๏ธ")) + } + + @Test + fun `sanitizeFileName falls back when name is only emoji`() { + assertEquals("chat", PlaintextExportRepository.sanitizeFileName("๐ŸŽ‰๐ŸŽŠ๐Ÿฅณ")) + } + + @Test + fun `sanitizeFileName preserves non-emoji unicode scripts`() { + assertEquals("ะŸั€ะธะฒะตั‚", PlaintextExportRepository.sanitizeFileName("ะŸั€ะธะฒะตั‚")) + assertEquals("ๆ—ฅๆœฌ่ชžใฎใƒใƒฃใƒƒใƒˆ", PlaintextExportRepository.sanitizeFileName("ๆ—ฅๆœฌ่ชžใฎใƒใƒฃใƒƒใƒˆ")) + assertEquals("ู…ุฌู…ูˆุนุฉ", PlaintextExportRepository.sanitizeFileName("ู…ุฌู…ูˆุนุฉ")) + } + // ==================== getSenderName tests ==================== @Test