1 package org
.asamk
.signal
.manager
.api
;
3 import org
.asamk
.signal
.manager
.groups
.GroupLinkPassword
;
4 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
5 import org
.signal
.libsignal
.zkgroup
.groups
.GroupMasterKey
;
6 import org
.signal
.storageservice
.protos
.groups
.GroupInviteLink
;
7 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
8 import org
.whispersystems
.util
.Base64UrlSafe
;
10 import java
.io
.IOException
;
12 import java
.net
.URISyntaxException
;
14 import okio
.ByteString
;
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
.inviteLinkPassword
.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 GroupInviteLink groupInviteLink
= GroupInviteLink
.ADAPTER
.decode(bytes
);
55 if (groupInviteLink
.v1Contents
!= null) {
56 var groupInviteLinkContentsV1
= groupInviteLink
.v1Contents
;
57 var groupMasterKey
= new GroupMasterKey(groupInviteLinkContentsV1
.groupMasterKey
.toByteArray());
58 var password
= GroupLinkPassword
.fromBytes(groupInviteLinkContentsV1
.inviteLinkPassword
.toByteArray());
60 return new GroupInviteLinkUrl(groupMasterKey
, password
);
62 throw new UnknownGroupLinkVersionException("Url contains no known group link content");
64 } catch (InvalidInputException
| IOException e
) {
65 throw new InvalidGroupLinkException(e
);
70 * @return {@link URI} if the host name matches.
72 private static URI
getGroupUrl(String urlString
) {
74 var url
= new URI(urlString
);
76 if (!"https".equalsIgnoreCase(url
.getScheme()) && !"sgnl".equalsIgnoreCase(url
.getScheme())) {
80 return GROUP_URL_HOST
.equalsIgnoreCase(url
.getHost()) ? url
: null;
81 } catch (URISyntaxException e
) {
86 private GroupInviteLinkUrl(GroupMasterKey groupMasterKey
, GroupLinkPassword password
) {
87 this.groupMasterKey
= groupMasterKey
;
88 this.password
= password
;
89 this.url
= createUrl(groupMasterKey
, password
);
92 private static String
createUrl(GroupMasterKey groupMasterKey
, GroupLinkPassword password
) {
93 var groupInviteLink
= new GroupInviteLink
.Builder().v1Contents(new GroupInviteLink
.GroupInviteLinkContentsV1
.Builder().groupMasterKey(
94 ByteString
.of(groupMasterKey
.serialize()))
95 .inviteLinkPassword(ByteString
.of(password
.serialize()))
98 var encoding
= Base64UrlSafe
.encodeBytesWithoutPadding(groupInviteLink
.encode());
100 return GROUP_URL_PREFIX
+ encoding
;
103 public String
getUrl() {
107 public GroupMasterKey
getGroupMasterKey() {
108 return groupMasterKey
;
111 public GroupLinkPassword
getPassword() {
115 public final static class InvalidGroupLinkException
extends Exception
{
117 public InvalidGroupLinkException(String message
) {
121 public InvalidGroupLinkException(Throwable cause
) {
126 public final static class UnknownGroupLinkVersionException
extends Exception
{
128 public UnknownGroupLinkVersionException(String message
) {