1 package org
.asamk
.signal
.manager
.api
;
3 import com
.google
.protobuf
.ByteString
;
5 import org
.asamk
.signal
.manager
.groups
.GroupLinkPassword
;
6 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
7 import org
.signal
.libsignal
.zkgroup
.groups
.GroupMasterKey
;
8 import org
.signal
.storageservice
.protos
.groups
.GroupInviteLink
;
9 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
10 import org
.whispersystems
.util
.Base64UrlSafe
;
12 import java
.io
.IOException
;
14 import java
.net
.URISyntaxException
;
16 public final class GroupInviteLinkUrl
{
18 private static final String GROUP_URL_HOST
= "signal.group";
19 private static final String GROUP_URL_PREFIX
= "https://" + GROUP_URL_HOST
+ "/#";
21 private final GroupMasterKey groupMasterKey
;
22 private final GroupLinkPassword password
;
23 private final String url
;
25 public static GroupInviteLinkUrl
forGroup(GroupMasterKey groupMasterKey
, DecryptedGroup group
) {
26 return new GroupInviteLinkUrl(groupMasterKey
,
27 GroupLinkPassword
.fromBytes(group
.getInviteLinkPassword().toByteArray()));
31 * @return null iff not a group url.
32 * @throws InvalidGroupLinkException If group url, but cannot be parsed.
34 public static GroupInviteLinkUrl
fromUri(String urlString
) throws InvalidGroupLinkException
, UnknownGroupLinkVersionException
{
35 var uri
= getGroupUrl(urlString
);
42 if (!"/".equals(uri
.getPath()) && uri
.getPath().length() > 0) {
43 throw new InvalidGroupLinkException("No path was expected in uri");
46 var encoding
= uri
.getFragment();
48 if (encoding
== null || encoding
.length() == 0) {
49 throw new InvalidGroupLinkException("No reference was in the uri");
52 var bytes
= Base64UrlSafe
.decodePaddingAgnostic(encoding
);
53 var groupInviteLink
= GroupInviteLink
.parseFrom(bytes
);
55 switch (groupInviteLink
.getContentsCase()) {
57 var groupInviteLinkContentsV1
= groupInviteLink
.getV1Contents();
58 var groupMasterKey
= new GroupMasterKey(groupInviteLinkContentsV1
.getGroupMasterKey()
60 var password
= GroupLinkPassword
.fromBytes(groupInviteLinkContentsV1
.getInviteLinkPassword()
63 return new GroupInviteLinkUrl(groupMasterKey
, password
);
65 default -> throw new UnknownGroupLinkVersionException("Url contains no known group link content");
67 } catch (InvalidInputException
| IOException e
) {
68 throw new InvalidGroupLinkException(e
);
73 * @return {@link URI} if the host name matches.
75 private static URI
getGroupUrl(String urlString
) {
77 var url
= new URI(urlString
);
79 if (!"https".equalsIgnoreCase(url
.getScheme()) && !"sgnl".equalsIgnoreCase(url
.getScheme())) {
83 return GROUP_URL_HOST
.equalsIgnoreCase(url
.getHost()) ? url
: null;
84 } catch (URISyntaxException e
) {
89 private GroupInviteLinkUrl(GroupMasterKey groupMasterKey
, GroupLinkPassword password
) {
90 this.groupMasterKey
= groupMasterKey
;
91 this.password
= password
;
92 this.url
= createUrl(groupMasterKey
, password
);
95 private static String
createUrl(GroupMasterKey groupMasterKey
, GroupLinkPassword password
) {
96 var groupInviteLink
= GroupInviteLink
.newBuilder()
97 .setV1Contents(GroupInviteLink
.GroupInviteLinkContentsV1
.newBuilder()
98 .setGroupMasterKey(ByteString
.copyFrom(groupMasterKey
.serialize()))
99 .setInviteLinkPassword(ByteString
.copyFrom(password
.serialize())))
102 var encoding
= Base64UrlSafe
.encodeBytesWithoutPadding(groupInviteLink
.toByteArray());
104 return GROUP_URL_PREFIX
+ encoding
;
107 public String
getUrl() {
111 public GroupMasterKey
getGroupMasterKey() {
112 return groupMasterKey
;
115 public GroupLinkPassword
getPassword() {
119 public final static class InvalidGroupLinkException
extends Exception
{
121 public InvalidGroupLinkException(String message
) {
125 public InvalidGroupLinkException(Throwable cause
) {
130 public final static class UnknownGroupLinkVersionException
extends Exception
{
132 public UnknownGroupLinkVersionException(String message
) {