From 36ca290faa42cd97428aeec87cf3cda892f62c69 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Thu, 2 Apr 2026 17:46:47 -0400 Subject: [PATCH] dont linkify invalid URIs `Message.getLinks` wasn't broken, but I thought it was at first, and then I had already written the test, so here it is. --- src/main/java/de/gultsch/common/Linkify.java | 7 ++++ .../java/de/gultsch/common/LinkifyTest.java | 27 ++++++++++++++++ .../conversations/entities/MessageTest.java | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/test/java/de/gultsch/common/LinkifyTest.java create mode 100644 src/test/java/eu/siacs/conversations/entities/MessageTest.java diff --git a/src/main/java/de/gultsch/common/Linkify.java b/src/main/java/de/gultsch/common/Linkify.java index 408b23c49c7d6285f28cf2fad81c122c0ba49e26..6f556c81d4202aa678ef45ac654d3b165895c4a7 100644 --- a/src/main/java/de/gultsch/common/Linkify.java +++ b/src/main/java/de/gultsch/common/Linkify.java @@ -79,6 +79,13 @@ public class Linkify { public static void addLinks(final Spannable body) { android.text.util.Linkify.addLinks(body, Patterns.URI_GENERIC, null, MATCH_FILTER, null); + for (final URLSpan span : body.getSpans(0, body.length(), URLSpan.class)) { + try { + new java.net.URI(span.getURL()); + } catch (final java.net.URISyntaxException e) { + body.removeSpan(span); + } + } } public static void addLinks(final Editable body, final Account account, final Jid context) { diff --git a/src/test/java/de/gultsch/common/LinkifyTest.java b/src/test/java/de/gultsch/common/LinkifyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f4e439c27a8fb627f723c48521d633fec1943b91 --- /dev/null +++ b/src/test/java/de/gultsch/common/LinkifyTest.java @@ -0,0 +1,27 @@ +package de.gultsch.common; + +import android.os.Build; +import android.text.SpannableStringBuilder; +import android.text.style.URLSpan; + +import eu.siacs.conversations.Conversations; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.ConscryptMode; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Build.VERSION_CODES.TIRAMISU, application = Conversations.class) +@ConscryptMode(ConscryptMode.Mode.OFF) +public class LinkifyTest { + + @Test + public void addLinksDoesNotLinkifyInvalidUris() { + final var text = new SpannableStringBuilder("https://example.com?q=%s"); + Linkify.addLinks(text); + Assert.assertEquals(0, text.getSpans(0, text.length(), URLSpan.class).length); + } +} diff --git a/src/test/java/eu/siacs/conversations/entities/MessageTest.java b/src/test/java/eu/siacs/conversations/entities/MessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d321ea3b6c3c67263c37a3b8f08efe309835e12b --- /dev/null +++ b/src/test/java/eu/siacs/conversations/entities/MessageTest.java @@ -0,0 +1,32 @@ +package eu.siacs.conversations.entities; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.os.Build; + +import eu.siacs.conversations.Conversations; +import eu.siacs.conversations.xmpp.Jid; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.ConscryptMode; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Build.VERSION_CODES.TIRAMISU, application = Conversations.class) +@ConscryptMode(ConscryptMode.Mode.OFF) +public class MessageTest { + + @Test + public void extractLinksIgnoresInvalidUris() { + final var conversation = mock(Conversational.class); + when(conversation.getUuid()).thenReturn("test-uuid"); + when(conversation.getJid()).thenReturn(Jid.ofLocalAndDomain("test", "example.com")); + + final var message = new Message(conversation, "https://example.com?q=%s", Message.ENCRYPTION_NONE); + Assert.assertEquals(0, message.getLinks().size()); + } +}