Support sgnl://signal.group links.
parent
ab2235fc88
commit
adf1674877
|
@ -223,6 +223,14 @@
|
||||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="sgnl"
|
||||||
|
android:host="signal.group" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter android:autoVerify="true"
|
<intent-filter android:autoVerify="true"
|
||||||
tools:targetApi="23">
|
tools:targetApi="23">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
|
@ -195,7 +195,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
private GroupInviteLinkUrl getGroupInviteLinkUrl() {
|
private GroupInviteLinkUrl getGroupInviteLinkUrl() {
|
||||||
try {
|
try {
|
||||||
//noinspection ConstantConditions
|
//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) {
|
} catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.whispersystems.util.Base64UrlSafe;
|
import org.whispersystems.util.Base64UrlSafe;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
public final class GroupInviteLinkUrl {
|
public final class GroupInviteLinkUrl {
|
||||||
|
|
||||||
|
@ -38,24 +38,24 @@ public final class GroupInviteLinkUrl {
|
||||||
* @return null iff not a group url.
|
* @return null iff not a group url.
|
||||||
* @throws InvalidGroupLinkException If group url, but cannot be parsed.
|
* @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
|
throws InvalidGroupLinkException, UnknownGroupLinkVersionException
|
||||||
{
|
{
|
||||||
URL url = getGroupUrl(urlString);
|
URI uri = getGroupUrl(urlString);
|
||||||
|
|
||||||
if (url == null) {
|
if (uri == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!"/".equals(url.getPath()) && url.getPath().length() > 0) {
|
if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) {
|
||||||
throw new InvalidGroupLinkException("No path was expected in url");
|
throw new InvalidGroupLinkException("No path was expected in uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
String encoding = url.getRef();
|
String encoding = uri.getFragment();
|
||||||
|
|
||||||
if (encoding == null || encoding.length() == 0) {
|
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);
|
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 {
|
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())
|
return GROUP_URL_HOST.equalsIgnoreCase(url.getHost())
|
||||||
? url
|
? url
|
||||||
: null;
|
: null;
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,7 +252,7 @@ public class LinkPreviewRepository {
|
||||||
{
|
{
|
||||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||||
try {
|
try {
|
||||||
GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(groupUrl);
|
GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(groupUrl);
|
||||||
if (groupInviteLinkUrl == null) {
|
if (groupInviteLinkUrl == null) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class CommunicationActions {
|
||||||
*/
|
*/
|
||||||
public static boolean handlePotentialGroupLinkUrl(@NonNull FragmentActivity activity, @NonNull String potentialGroupLinkUrl) {
|
public static boolean handlePotentialGroupLinkUrl(@NonNull FragmentActivity activity, @NonNull String potentialGroupLinkUrl) {
|
||||||
try {
|
try {
|
||||||
GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUrl(potentialGroupLinkUrl);
|
GroupInviteLinkUrl groupInviteLinkUrl = GroupInviteLinkUrl.fromUri(potentialGroupLinkUrl);
|
||||||
|
|
||||||
if (groupInviteLinkUrl == null) {
|
if (groupInviteLinkUrl == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -37,7 +37,11 @@ public final class GroupInviteLinkUrlTest {
|
||||||
|
|
||||||
givenGroup().withMasterKey("00f7e0c2a71ab064cc3ced4c04f08d7b7ef4b84b2c2206f69833be6cfe34df80")
|
givenGroup().withMasterKey("00f7e0c2a71ab064cc3ced4c04f08d7b7ef4b84b2c2206f69833be6cfe34df80")
|
||||||
.andPassword("9b")
|
.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
|
@Test
|
||||||
public void can_extract_group_master_key_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
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
|
@Test
|
||||||
public void can_extract_group_master_key_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
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
|
@Test
|
||||||
public void can_extract_password_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
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
|
@Test
|
||||||
public void can_extract_password_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
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
|
@Test
|
||||||
public void can_reconstruct_url() {
|
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() {
|
private static TestBuilder givenGroup() {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
|
||||||
import org.whispersystems.util.Base64UrlSafe;
|
import org.whispersystems.util.Base64UrlSafe;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
@ -21,50 +20,55 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void empty_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
public void empty_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||||
assertNull(GroupInviteLinkUrl.fromUrl(""));
|
assertNull(GroupInviteLinkUrl.fromUri(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void not_a_url_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
public void not_a_url_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||||
assertNull(GroupInviteLinkUrl.fromUrl("abc"));
|
assertNull(GroupInviteLinkUrl.fromUri("abc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrong_host() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
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
|
@Test
|
||||||
public void has_path() {
|
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)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasMessage("No path was expected in url");
|
.hasMessage("No path was expected in uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void missing_ref() {
|
public void missing_ref() {
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/"))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/"))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasMessage("No reference was in the url");
|
.hasMessage("No reference was in the uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void empty_ref() {
|
public void empty_ref() {
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#"))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#"))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasMessage("No reference was in the url");
|
.hasMessage("No reference was in the uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bad_base64() {
|
public void bad_base64() {
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCX;mwVi31USVY"))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCX;mwVi31USVY"))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasCauseExactlyInstanceOf(IOException.class);
|
.hasCauseExactlyInstanceOf(IOException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bad_protobuf() {
|
public void bad_protobuf() {
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY"))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri("https://signal.group/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY"))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasCauseExactlyInstanceOf(InvalidProtocolBufferException.class);
|
.hasCauseExactlyInstanceOf(InvalidProtocolBufferException.class);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +77,7 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test {
|
||||||
public void version_999_url() {
|
public void version_999_url() {
|
||||||
String url = "https://signal.group/#uj4zCiDMSxlNUvF4bQ3z3fYzGyZTFbJ1xEqWbPE3uZSD8bjOrxIP8NxV-0GUz3jpxMLR1rN3";
|
String url = "https://signal.group/#uj4zCiDMSxlNUvF4bQ3z3fYzGyZTFbJ1xEqWbPE3uZSD8bjOrxIP8NxV-0GUz3jpxMLR1rN3";
|
||||||
|
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri(url))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.UnknownGroupLinkVersionException.class)
|
.isInstanceOf(GroupInviteLinkUrl.UnknownGroupLinkVersionException.class)
|
||||||
.hasMessage("Url contains no known group link content");
|
.hasMessage("Url contains no known group link content");
|
||||||
}
|
}
|
||||||
|
@ -87,7 +91,7 @@ public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test {
|
||||||
|
|
||||||
String url = "https://signal.group/#" + encoding;
|
String url = "https://signal.group/#" + encoding;
|
||||||
|
|
||||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url))
|
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUri(url))
|
||||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||||
.hasCauseExactlyInstanceOf(InvalidInputException.class);
|
.hasCauseExactlyInstanceOf(InvalidInputException.class);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue