148 lines
5.1 KiB
Java
148 lines
5.1 KiB
Java
package org.thoughtcrime.securesms.groups.v2;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.google.protobuf.ByteString;
|
|
|
|
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|
import org.signal.zkgroup.InvalidInputException;
|
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
|
import org.whispersystems.util.Base64UrlSafe;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
|
|
public final class GroupInviteLinkUrl {
|
|
|
|
private static final String GROUP_URL_HOST = "signal.group";
|
|
private static final String GROUP_URL_PREFIX = "https://" + GROUP_URL_HOST + "/#";
|
|
|
|
private final GroupMasterKey groupMasterKey;
|
|
private final GroupLinkPassword password;
|
|
private final String url;
|
|
|
|
public static GroupInviteLinkUrl forGroup(@NonNull GroupMasterKey groupMasterKey,
|
|
@NonNull DecryptedGroup group)
|
|
{
|
|
return new GroupInviteLinkUrl(groupMasterKey, GroupLinkPassword.fromBytes(group.getInviteLinkPassword().toByteArray()));
|
|
}
|
|
|
|
public static boolean isGroupLink(@NonNull String urlString) {
|
|
return getGroupUrl(urlString) != null;
|
|
}
|
|
|
|
/**
|
|
* @return null iff not a group url.
|
|
* @throws InvalidGroupLinkException If group url, but cannot be parsed.
|
|
*/
|
|
public static @Nullable GroupInviteLinkUrl fromUri(@NonNull String urlString)
|
|
throws InvalidGroupLinkException, UnknownGroupLinkVersionException
|
|
{
|
|
URI uri = getGroupUrl(urlString);
|
|
|
|
if (uri == null) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) {
|
|
throw new InvalidGroupLinkException("No path was expected in uri");
|
|
}
|
|
|
|
String encoding = uri.getFragment();
|
|
|
|
if (encoding == null || encoding.length() == 0) {
|
|
throw new InvalidGroupLinkException("No reference was in the uri");
|
|
}
|
|
|
|
byte[] bytes = Base64UrlSafe.decodePaddingAgnostic(encoding);
|
|
GroupInviteLink groupInviteLink = GroupInviteLink.parseFrom(bytes);
|
|
|
|
//noinspection SwitchStatementWithTooFewBranches
|
|
switch (groupInviteLink.getContentsCase()) {
|
|
case V1CONTENTS: {
|
|
GroupInviteLink.GroupInviteLinkContentsV1 groupInviteLinkContentsV1 = groupInviteLink.getV1Contents();
|
|
GroupMasterKey groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.getGroupMasterKey().toByteArray());
|
|
GroupLinkPassword password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.getInviteLinkPassword().toByteArray());
|
|
|
|
return new GroupInviteLinkUrl(groupMasterKey, password);
|
|
}
|
|
default: throw new UnknownGroupLinkVersionException("Url contains no known group link content");
|
|
}
|
|
} catch (InvalidInputException | IOException e) {
|
|
throw new InvalidGroupLinkException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {@link URI} if the host name matches.
|
|
*/
|
|
private static URI getGroupUrl(@NonNull String urlString) {
|
|
try {
|
|
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 (URISyntaxException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private GroupInviteLinkUrl(@NonNull GroupMasterKey groupMasterKey, @NonNull GroupLinkPassword password) {
|
|
this.groupMasterKey = groupMasterKey;
|
|
this.password = password;
|
|
this.url = createUrl(groupMasterKey, password);
|
|
}
|
|
|
|
protected static @NonNull String createUrl(@NonNull GroupMasterKey groupMasterKey, @NonNull GroupLinkPassword password) {
|
|
GroupInviteLink groupInviteLink = GroupInviteLink.newBuilder()
|
|
.setV1Contents(GroupInviteLink.GroupInviteLinkContentsV1.newBuilder()
|
|
.setGroupMasterKey(ByteString.copyFrom(groupMasterKey.serialize()))
|
|
.setInviteLinkPassword(ByteString.copyFrom(password.serialize())))
|
|
.build();
|
|
|
|
String encoding = Base64UrlSafe.encodeBytesWithoutPadding(groupInviteLink.toByteArray());
|
|
|
|
return GROUP_URL_PREFIX + encoding;
|
|
}
|
|
|
|
public @NonNull String getUrl() {
|
|
return url;
|
|
}
|
|
|
|
public @NonNull GroupMasterKey getGroupMasterKey() {
|
|
return groupMasterKey;
|
|
}
|
|
|
|
public @NonNull GroupLinkPassword getPassword() {
|
|
return password;
|
|
}
|
|
|
|
public final static class InvalidGroupLinkException extends Exception {
|
|
public InvalidGroupLinkException(String message) {
|
|
super(message);
|
|
}
|
|
|
|
public InvalidGroupLinkException(Throwable cause) {
|
|
super(cause);
|
|
}
|
|
}
|
|
|
|
public final static class UnknownGroupLinkVersionException extends Exception {
|
|
public UnknownGroupLinkVersionException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
}
|