package org.asamk.signal.manager.api; import org.signal.core.util.Base64; import org.whispersystems.signalservice.api.push.UsernameLinkComponents; import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.IOException; import java.util.Arrays; import java.util.regex.Pattern; public final class UsernameLinkUrl { private static final Pattern URL_REGEX = Pattern.compile("(https://)?signal.me/?#eu/([a-zA-Z0-9+\\-_/]+)"); private static final String BASE_URL = "https://signal.me/#eu/"; private final String url; private final UsernameLinkComponents usernameLinkComponents; public static UsernameLinkUrl fromUri(String url) throws InvalidUsernameLinkException { final var matcher = URL_REGEX.matcher(url); if (!matcher.matches()) { throw new InvalidUsernameLinkException("Invalid username link"); } final var path = matcher.group(2); final byte[] allBytes; try { allBytes = Base64.decode(path); } catch (IOException e) { throw new InvalidUsernameLinkException("Invalid base64 encoding"); } if (allBytes.length != 48) { throw new InvalidUsernameLinkException("Invalid username link"); } final var entropy = Arrays.copyOfRange(allBytes, 0, 32); final var serverId = Arrays.copyOfRange(allBytes, 32, allBytes.length); final var serverIdUuid = UuidUtil.parseOrNull(serverId); if (serverIdUuid == null) { throw new InvalidUsernameLinkException("Invalid serverId"); } return new UsernameLinkUrl(new UsernameLinkComponents(entropy, serverIdUuid)); } public UsernameLinkUrl(UsernameLinkComponents usernameLinkComponents) { this.usernameLinkComponents = usernameLinkComponents; this.url = createUrl(usernameLinkComponents); } private static String createUrl(UsernameLinkComponents usernameLinkComponents) { final var entropy = usernameLinkComponents.getEntropy(); final var serverId = UuidUtil.toByteArray(usernameLinkComponents.getServerId()); final var combined = new byte[entropy.length + serverId.length]; System.arraycopy(entropy, 0, combined, 0, entropy.length); System.arraycopy(serverId, 0, combined, entropy.length, serverId.length); final var base64 = Base64.encodeUrlSafeWithoutPadding(combined); return BASE_URL + base64; } public String getUrl() { return url; } public UsernameLinkComponents getComponents() { return usernameLinkComponents; } public static final class InvalidUsernameLinkException extends Exception { public InvalidUsernameLinkException(String message) { super(message); } public InvalidUsernameLinkException(Throwable cause) { super(cause); } } }