From 85261afe4296fdd087adde0b0b50635c669304a0 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 2 Jul 2026 14:41:56 +0000 Subject: [PATCH] Preserve semicolons within linkified URLs. --- .../src/main/java/org/signal/core/util/Linkifier.kt | 7 ++++--- .../src/test/java/org/signal/core/util/LinkifierTest.kt | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/util-jvm/src/main/java/org/signal/core/util/Linkifier.kt b/core/util-jvm/src/main/java/org/signal/core/util/Linkifier.kt index cca9e7310d..b4a44a0ec9 100644 --- a/core/util-jvm/src/main/java/org/signal/core/util/Linkifier.kt +++ b/core/util-jvm/src/main/java/org/signal/core/util/Linkifier.kt @@ -28,11 +28,12 @@ object Linkifier { private val CLOSING_BRACKETS = mapOf(')' to '(', ']' to '[', '}' to '{') /** - * Characters we treat as definitely-not-part-of-a-URL when extending past the host. Commas are - * allowed inside a URL, but not when they are acting as a separator before another obvious URL. + * Characters we treat as definitely-not-part-of-a-URL when extending past the host. Commas and + * semicolons are allowed inside a URL, but not when they are acting as a separator before another + * obvious URL. */ private const val URL_CHAR = - "(?:[^\\s\\u0085\\u00A0\\u1680\\u2000-\\u200D\\u2028\\u2029\\u202A-\\u202F\\u205F\\u2066-\\u2069\\u3000\\uFEFF<>\"'`,;|\\\\]|,(?!https?://|www\\.))" + "(?:[^\\s\\u0085\\u00A0\\u1680\\u2000-\\u200D\\u2028\\u2029\\u202A-\\u202F\\u205F\\u2066-\\u2069\\u3000\\uFEFF<>\"'`,;|\\\\]|[,;](?!https?://|www\\.))" /** * A single domain label: letter/digit, optional letter/digit/hyphen body. Used for intermediate diff --git a/core/util-jvm/src/test/java/org/signal/core/util/LinkifierTest.kt b/core/util-jvm/src/test/java/org/signal/core/util/LinkifierTest.kt index 318b5d041a..c6c0d29c62 100644 --- a/core/util-jvm/src/test/java/org/signal/core/util/LinkifierTest.kt +++ b/core/util-jvm/src/test/java/org/signal/core/util/LinkifierTest.kt @@ -105,6 +105,8 @@ class LinkifierTest(private val case: Case) { Case("trailing underscore is preserved", "https://example.com/path_ tail", listOf(web("https://example.com/path_"))), Case("comma in url path is preserved", "Go to https://example.com/a,b/c", listOf(web("https://example.com/a,b/c"))), Case("comma in url query is preserved", "Go to https://example.com/search?q=a,b", listOf(web("https://example.com/search?q=a,b"))), + Case("semicolon in url path is preserved", "https://example.com/pricewatch/compare/2290176;2126182/", listOf(web("https://example.com/pricewatch/compare/2290176;2126182/"))), + Case("semicolon in url query is preserved", "Go to https://example.com/search?a=1;b=2", listOf(web("https://example.com/search?a=1;b=2"))), // ----- bracket / paren handling ----- Case("trailing closing paren without opener is trimmed", "(see https://signal.org)", listOf(web("https://signal.org"))), @@ -116,6 +118,7 @@ class LinkifierTest(private val case: Case) { // ----- multiple URLs in one input ----- Case("two urls separated by text", "First https://a.com then https://b.com", listOf(web("https://a.com"), web("https://b.com"))), Case("two urls separated only by comma", "https://a.com,https://b.com", listOf(web("https://a.com"), web("https://b.com"))), + Case("two urls separated only by semicolon", "https://a.com;https://b.com", listOf(web("https://a.com"), web("https://b.com"))), Case( name = "multi-line text with several urls per line", input = """