From adf167487737b261d6789eeed77fbaa91ea3948e Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 7 Oct 2020 13:43:53 -0300 Subject: [PATCH] Support sgnl://signal.group links. --- app/src/main/AndroidManifest.xml | 8 +++++ .../GroupJoinBottomSheetDialogFragment.java | 2 +- .../groups/v2/GroupInviteLinkUrl.java | 33 +++++++++++-------- .../linkpreview/LinkPreviewRepository.java | 2 +- .../securesms/util/CommunicationActions.java | 2 +- .../groups/v2/GroupInviteLinkUrlTest.java | 22 +++++++++---- ...inkUrl_InvalidGroupLinkException_Test.java | 32 ++++++++++-------- 7 files changed, 65 insertions(+), 36 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e50e84d16..1221f99d41 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -223,6 +223,14 @@ + + + + + + + diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java index fa62526160..6f708bec7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java @@ -195,7 +195,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF private GroupInviteLinkUrl getGroupInviteLinkUrl() { try { //noinspection ConstantConditions - return GroupInviteLinkUrl.fromUrl(requireArguments().getString(ARG_GROUP_INVITE_LINK_URL)); + return GroupInviteLinkUrl.fromUri(requireArguments().getString(ARG_GROUP_INVITE_LINK_URL)); } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { throw new AssertionError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java index 7995fe9ed5..40ee561b9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl.java @@ -12,8 +12,8 @@ import org.signal.zkgroup.groups.GroupMasterKey; import org.whispersystems.util.Base64UrlSafe; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; public final class GroupInviteLinkUrl { @@ -38,24 +38,24 @@ public final class GroupInviteLinkUrl { * @return null iff not a group url. * @throws InvalidGroupLinkException If group url, but cannot be parsed. */ - public static @Nullable GroupInviteLinkUrl fromUrl(@NonNull String urlString) + public static @Nullable GroupInviteLinkUrl fromUri(@NonNull String urlString) throws InvalidGroupLinkException, UnknownGroupLinkVersionException { - URL url = getGroupUrl(urlString); + URI uri = getGroupUrl(urlString); - if (url == null) { + if (uri == null) { return null; } try { - if (!"/".equals(url.getPath()) && url.getPath().length() > 0) { - throw new InvalidGroupLinkException("No path was expected in url"); + if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) { + throw new InvalidGroupLinkException("No path was expected in uri"); } - String encoding = url.getRef(); + String encoding = uri.getFragment(); if (encoding == null || encoding.length() == 0) { - throw new InvalidGroupLinkException("No reference was in the url"); + throw new InvalidGroupLinkException("No reference was in the uri"); } byte[] bytes = Base64UrlSafe.decodePaddingAgnostic(encoding); @@ -78,16 +78,23 @@ public final class GroupInviteLinkUrl { } /** - * @return {@link URL} if the host name matches. + * @return {@link URI} if the host name matches. */ - private static URL getGroupUrl(@NonNull String urlString) { + private static URI getGroupUrl(@NonNull String urlString) { try { - URL url = new URL(urlString); + URI url = new URI(urlString); + + if (!"https".equalsIgnoreCase(url.getScheme()) && + !"sgnl".equalsIgnoreCase(url.getScheme())) + { + return null; + } return GROUP_URL_HOST.equalsIgnoreCase(url.getHost()) ? url : null; - } catch (MalformedURLException e) { + + } catch (URISyntaxException e) { return null; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 136fb581a8..d5e667caad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -252,7 +252,7 @@ public class LinkPreviewRepository { { SignalExecutors.UNBOUNDED.execute(() -> { try { - GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(groupUrl); + GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(groupUrl); if (groupInviteLinkUrl == null) { throw new AssertionError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 511ae27dff..4fb9460d05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -165,7 +165,7 @@ public class CommunicationActions { */ public static boolean handlePotentialGroupLinkUrl(@NonNull FragmentActivity activity, @NonNull String potentialGroupLinkUrl) { try { - GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(potentialGroupLinkUrl); + GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(potentialGroupLinkUrl); if (groupInviteLinkUrl == null) { return false; diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrlTest.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrlTest.java index 14ca75cc11..a48797edfa 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrlTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrlTest.java @@ -37,7 +37,11 @@ public final class GroupInviteLinkUrlTest { givenGroup().withMasterKey("00f7e0c2a71ab064cc3ced4c04f08d7b7ef4b84b2c2206f69833be6cfe34df80") .andPassword("9b") - .expectUrl("https://signal.group/#CiUKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEgGb") + .expectUrl("https://signal.group/#CiUKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEgGb"), + + givenGroup().withMasterKey("2ca23c04d7cf60fe04039ae76d1912202c2a463d345d9cd48cf27f260dd37f6f") + .andPassword("2734457c02ce51da71ad0b62f3c222f7") + .expectUrl("sgnl://signal.group/#CjQKICyiPATXz2D-BAOa520ZEiAsKkY9NF2c1IzyfyYN039vEhAnNEV8As5R2nGtC2LzwiL3") ); } @@ -49,27 +53,33 @@ public final class GroupInviteLinkUrlTest { @Test public void can_extract_group_master_key_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUrl(expectedUrl).getGroupMasterKey()); + assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUri(expectedUrl).getGroupMasterKey()); } @Test public void can_extract_group_master_key_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUrl(expectedUrl + "=").getGroupMasterKey()); + assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUri(expectedUrl + "=").getGroupMasterKey()); } @Test public void can_extract_password_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertEquals(password, GroupInviteLinkUrl.fromUrl(expectedUrl).getPassword()); + assertEquals(password, GroupInviteLinkUrl.fromUri(expectedUrl).getPassword()); } @Test public void can_extract_password_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertEquals(password, GroupInviteLinkUrl.fromUrl(expectedUrl + "=").getPassword()); + assertEquals(password, GroupInviteLinkUrl.fromUri(expectedUrl + "=").getPassword()); } @Test public void can_reconstruct_url() { - assertEquals(expectedUrl, GroupInviteLinkUrl.createUrl(groupMasterKey, password)); + String urlToCompare = expectedUrl; + + if (urlToCompare.startsWith("sgnl")) { + urlToCompare = urlToCompare.replace("sgnl", "https"); + } + + assertEquals(urlToCompare, GroupInviteLinkUrl.createUrl(groupMasterKey, password)); } private static TestBuilder givenGroup() { diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl_InvalidGroupLinkException_Test.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl_InvalidGroupLinkException_Test.java index f1b327094b..c6f079f1b5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl_InvalidGroupLinkException_Test.java +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/GroupInviteLinkUrl_InvalidGroupLinkException_Test.java @@ -8,7 +8,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.junit.Test; import org.signal.storageservice.protos.groups.GroupInviteLink; import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; import org.whispersystems.util.Base64UrlSafe; import org.thoughtcrime.securesms.util.Util; @@ -21,50 +20,55 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test { @Test public void empty_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertNull(GroupInviteLinkUrl.fromUrl("")); + assertNull(GroupInviteLinkUrl.fromUri("")); } @Test public void not_a_url_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertNull(GroupInviteLinkUrl.fromUrl("abc")); + assertNull(GroupInviteLinkUrl.fromUri("abc")); } @Test public void wrong_host() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { - assertNull(GroupInviteLinkUrl.fromUrl("https://x.signal.org/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6OHTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY")); + assertNull(GroupInviteLinkUrl.fromUri("https://x.signal.org/#CjQKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEhCbwyTuxDfP2mrluK779H7o")); + } + + @Test + public void wrong_scheme() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException { + assertNull(GroupInviteLinkUrl.fromUri("http://signal.group/#CjQKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEhCbwyTuxDfP2mrluK779H7o")); } @Test public void has_path() { - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/not_expected/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6OHTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY")) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/not_expected/#CjQKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEhCbwyTuxDfP2mrluK779H7o")) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) - .hasMessage("No path was expected in url"); + .hasMessage("No path was expected in uri"); } @Test public void missing_ref() { - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/")) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/")) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) - .hasMessage("No reference was in the url"); + .hasMessage("No reference was in the uri"); } @Test public void empty_ref() { - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#")) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#")) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) - .hasMessage("No reference was in the url"); + .hasMessage("No reference was in the uri"); } @Test public void bad_base64() { - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCX;mwVi31USVY")) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCX;mwVi31USVY")) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) .hasCauseExactlyInstanceOf(IOException.class); } @Test public void bad_protobuf() { - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY")) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY")) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) .hasCauseExactlyInstanceOf(InvalidProtocolBufferException.class); } @@ -73,7 +77,7 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test { public void version_999_url() { String url = "https://signal.group/#uj4zCiDMSxlNUvF4bQ3z3fYzGyZTFbJ1xEqWbPE3uZSD8bjOrxIP8NxV-0GUz3jpxMLR1rN3"; - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url)) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri(url)) .isInstanceOf(GroupInviteLinkUrl.UnknownGroupLinkVersionException.class) .hasMessage("Url contains no known group link content"); } @@ -87,7 +91,7 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test { String url = "https://signal.group/#" + encoding; - assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url)) + assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri(url)) .isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class) .hasCauseExactlyInstanceOf(InvalidInputException.class); }