From 5771bb858f6894c5eeb52e8614cd1c4132bf6b7f Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 14 Nov 2022 19:31:40 +0100 Subject: [PATCH] Allow using data URIs for updateGroup/updateProfile avatars Fixes #1082 --- .../org/asamk/signal/manager/Manager.java | 2 +- .../org/asamk/signal/manager/ManagerImpl.java | 2 +- .../asamk/signal/manager/api/UpdateGroup.java | 13 +++--- .../signal/manager/api/UpdateProfile.java | 10 ++--- .../signal/manager/helper/GroupHelper.java | 45 +++++++++++-------- .../signal/manager/helper/GroupV2Helper.java | 24 +++------- .../signal/manager/helper/ProfileHelper.java | 11 ++--- .../signal/commands/UpdateGroupCommand.java | 7 +-- .../signal/commands/UpdateProfileCommand.java | 3 +- .../asamk/signal/dbus/DbusManagerImpl.java | 8 ++-- .../org/asamk/signal/dbus/DbusSignalImpl.java | 8 ++-- 11 files changed, 60 insertions(+), 73 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 75503790..d4f8b76f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -102,7 +102,7 @@ public interface Manager extends Closeable { void deleteGroup(GroupId groupId) throws IOException; Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException; SendGroupMessageResults updateGroup( diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 652b4e76..5191e2d9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -394,7 +394,7 @@ class ManagerImpl implements Manager { @Override public Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException { return context.getGroupHelper() .createGroup(name, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java index 71b18a67..0a2cb5ee 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java @@ -3,7 +3,6 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupPermission; -import java.io.File; import java.util.Set; public class UpdateGroup { @@ -20,7 +19,7 @@ public class UpdateGroup { private final GroupLinkState groupLinkState; private final GroupPermission addMemberPermission; private final GroupPermission editDetailsPermission; - private final File avatarFile; + private final String avatarFile; private final Integer expirationTimer; private final Boolean isAnnouncementGroup; @@ -77,7 +76,7 @@ public class UpdateGroup { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) { @@ -146,7 +145,7 @@ public class UpdateGroup { return editDetailsPermission; } - public File getAvatarFile() { + public String getAvatarFile() { return avatarFile; } @@ -172,7 +171,7 @@ public class UpdateGroup { private GroupLinkState groupLinkState; private GroupPermission addMemberPermission; private GroupPermission editDetailsPermission; - private File avatarFile; + private String avatarFile; private Integer expirationTimer; private Boolean isAnnouncementGroup; @@ -192,7 +191,7 @@ public class UpdateGroup { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) { @@ -273,7 +272,7 @@ public class UpdateGroup { return this; } - public Builder withAvatarFile(final File val) { + public Builder withAvatarFile(final String val) { avatarFile = val; return this; } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java index d5de308e..3cfffe5f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java @@ -1,14 +1,12 @@ package org.asamk.signal.manager.api; -import java.io.File; - public class UpdateProfile { private final String givenName; private final String familyName; private final String about; private final String aboutEmoji; - private final File avatar; + private final String avatar; private final boolean deleteAvatar; private final byte[] mobileCoinAddress; @@ -54,7 +52,7 @@ public class UpdateProfile { return aboutEmoji; } - public File getAvatar() { + public String getAvatar() { return avatar; } @@ -72,7 +70,7 @@ public class UpdateProfile { private String familyName; private String about; private String aboutEmoji; - private File avatar; + private String avatar; private boolean deleteAvatar; private byte[] mobileCoinAddress; @@ -99,7 +97,7 @@ public class UpdateProfile { return this; } - public Builder withAvatar(final File val) { + public Builder withAvatar(final String val) { avatar = val; return this; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index a342064c..bb9a3ea4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -26,6 +26,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.IOUtils; +import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; @@ -47,7 +48,6 @@ import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.ConflictException; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -164,7 +164,7 @@ public class GroupHelper { } public Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException { final var selfRecipientId = account.getSelfRecipientId(); if (members != null && members.contains(selfRecipientId)) { @@ -172,14 +172,15 @@ public class GroupHelper { members.remove(selfRecipientId); } + final var avatarBytes = readAvatarBytes(avatarFile); var gv2Pair = context.getGroupV2Helper() - .createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarFile); + .createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarBytes); if (gv2Pair == null) { // Failed to create v2 group, creating v1 group instead var gv1 = new GroupInfoV1(GroupIdV1.createRandom()); gv1.addMembers(List.of(selfRecipientId)); - final var result = updateGroupV1(gv1, name, members, avatarFile); + final var result = updateGroupV1(gv1, name, members, avatarBytes); return new Pair<>(gv1.getGroupId(), result); } @@ -187,10 +188,9 @@ public class GroupHelper { final var decryptedGroup = gv2Pair.second(); gv2.setGroup(decryptedGroup); - if (avatarFile != null) { + if (avatarBytes != null) { context.getAvatarStore() - .storeGroupAvatar(gv2.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + .storeGroupAvatar(gv2.getGroupId(), outputStream -> outputStream.write(avatarBytes)); } account.getGroupStore().updateGroup(gv2); @@ -217,11 +217,12 @@ public class GroupHelper { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { var group = getGroupForUpdating(groupId); + final var avatarBytes = readAvatarBytes(avatarFile); if (group instanceof GroupInfoV2) { try { @@ -238,7 +239,7 @@ public class GroupHelper { groupLinkState, addMemberPermission, editDetailsPermission, - avatarFile, + avatarBytes, expirationTimer, isAnnouncementGroup); } catch (ConflictException e) { @@ -257,14 +258,14 @@ public class GroupHelper { groupLinkState, addMemberPermission, editDetailsPermission, - avatarFile, + avatarBytes, expirationTimer, isAnnouncementGroup); } } final var gv1 = (GroupInfoV1) group; - final var result = updateGroupV1(gv1, name, members, avatarFile); + final var result = updateGroupV1(gv1, name, members, avatarBytes); if (expirationTimer != null) { setExpirationTimer(gv1, expirationTimer); } @@ -521,7 +522,7 @@ public class GroupHelper { } private SendGroupMessageResults updateGroupV1( - final GroupInfoV1 gv1, final String name, final Set members, final File avatarFile + final GroupInfoV1 gv1, final String name, final Set members, final byte[] avatarFile ) throws IOException, AttachmentInvalidException { updateGroupV1Details(gv1, name, members, avatarFile); @@ -534,7 +535,7 @@ public class GroupHelper { } private void updateGroupV1Details( - final GroupInfoV1 g, final String name, final Collection members, final File avatarFile + final GroupInfoV1 g, final String name, final Collection members, final byte[] avatarFile ) throws IOException { if (name != null) { g.name = name; @@ -545,9 +546,7 @@ public class GroupHelper { } if (avatarFile != null) { - context.getAvatarStore() - .storeGroupAvatar(g.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + context.getAvatarStore().storeGroupAvatar(g.getGroupId(), outputStream -> outputStream.write(avatarFile)); } } @@ -581,7 +580,7 @@ public class GroupHelper { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final byte[] avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) throws IOException { @@ -716,8 +715,7 @@ public class GroupHelper { var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile); if (avatarFile != null) { context.getAvatarStore() - .storeGroupAvatar(group.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + .storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile)); } result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -819,4 +817,13 @@ public class GroupHelper { account.getRecipientAddressResolver())) .toList()); } + + private byte[] readAvatarBytes(final String avatarFile) throws IOException { + if (avatarFile == null) { + return null; + } + try (final var avatar = Utils.createStreamDetails(avatarFile).first().getStream()) { + return IOUtils.readFully(avatar); + } + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 84747e9c..e410d232 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -12,7 +12,6 @@ import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.recipients.RecipientId; -import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; @@ -47,10 +46,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -138,10 +134,9 @@ class GroupV2Helper { } Pair createGroup( - String name, Set members, File avatarFile - ) throws IOException { - final var avatarBytes = readAvatarBytes(avatarFile); - final var newGroup = buildNewGroup(name, members, avatarBytes); + String name, Set members, byte[] avatarFile + ) { + final var newGroup = buildNewGroup(name, members, avatarFile); if (newGroup == null) { return null; } @@ -170,14 +165,6 @@ class GroupV2Helper { return new Pair<>(g, decryptedGroup); } - private byte[] readAvatarBytes(final File avatarFile) throws IOException { - final byte[] avatarBytes; - try (InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile)) { - avatarBytes = avatar == null ? null : IOUtils.readFully(avatar); - } - return avatarBytes; - } - private GroupsV2Operations.NewGroup buildNewGroup( String name, Set members, byte[] avatar ) { @@ -210,7 +197,7 @@ class GroupV2Helper { } Pair updateGroup( - GroupInfoV2 groupInfoV2, String name, String description, File avatarFile + GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); @@ -222,9 +209,8 @@ class GroupV2Helper { } if (avatarFile != null) { - final var avatarBytes = readAvatarBytes(avatarFile); var avatarCdnKey = dependencies.getGroupsV2Api() - .uploadAvatar(avatarBytes, groupSecretParams, getGroupAuthForToday(groupSecretParams)); + .uploadAvatar(avatarFile, groupSecretParams, getGroupAuthForToday(groupSecretParams)); change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar(avatarCdnKey)); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 8113000b..da7440ed 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -30,7 +30,6 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; @@ -146,7 +145,7 @@ public final class ProfileHelper { final String familyName, String about, String aboutEmoji, - Optional avatar, + Optional avatar, byte[] mobileCoinAddress ) throws IOException { setProfile(true, false, givenName, familyName, about, aboutEmoji, avatar, mobileCoinAddress); @@ -159,7 +158,7 @@ public final class ProfileHelper { final String familyName, String about, String aboutEmoji, - Optional avatar, + Optional avatar, byte[] mobileCoinAddress ) throws IOException { var profile = getSelfProfile(); @@ -183,7 +182,8 @@ public final class ProfileHelper { if (uploadProfile) { final var streamDetails = avatar != null && avatar.isPresent() - ? Utils.createStreamDetailsFromFile(avatar.get()) + ? Utils.createStreamDetails(avatar.get()) + .first() : forceUploadAvatar && avatar == null ? context.getAvatarStore() .retrieveProfileAvatar(account.getSelfRecipientAddress()) : null; try (streamDetails) { @@ -212,9 +212,10 @@ public final class ProfileHelper { if (avatar != null) { if (avatar.isPresent()) { + final var streamDetails = Utils.createStreamDetails(avatar.get()).first(); context.getAvatarStore() .storeProfileAvatar(account.getSelfRecipientAddress(), - outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream)); + outputStream -> IOUtils.copyStream(streamDetails.getStream(), outputStream)); } else { context.getAvatarStore().deleteProfileAvatar(account.getSelfRecipientAddress()); } diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 411ae747..14ad14f8 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -26,7 +26,6 @@ import org.asamk.signal.util.SendMessageResultUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.stream.Stream; @@ -131,9 +130,7 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { SendGroupMessageResults groupMessageResults = null; if (groupId == null) { isNewGroup = true; - var results = m.createGroup(groupName, - groupMembers, - groupAvatar == null ? null : new File(groupAvatar)); + var results = m.createGroup(groupName, groupMembers, groupAvatar); groupMessageResults = results.second(); groupId = results.first(); groupName = null; @@ -155,7 +152,7 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { .withGroupLinkState(groupLinkState) .withAddMemberPermission(groupAddMemberPermission) .withEditDetailsPermission(groupEditDetailsPermission) - .withAvatarFile(groupAvatar == null ? null : new File(groupAvatar)) + .withAvatarFile(groupAvatar) .withExpirationTimer(groupExpiration) .withIsAnnouncementGroup(groupSendMessagesPermission == null ? null diff --git a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java index d8e87430..cea2da0d 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java @@ -10,7 +10,6 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.output.OutputWriter; -import java.io.File; import java.io.IOException; import java.util.Base64; @@ -50,7 +49,7 @@ public class UpdateProfileCommand implements JsonRpcLocalCommand { var avatarPath = ns.getString("avatar"); boolean removeAvatar = Boolean.TRUE.equals(ns.getBoolean("remove-avatar")); - File avatarFile = removeAvatar || avatarPath == null ? null : new File(avatarPath); + String avatarFile = removeAvatar || avatarPath == null ? null : avatarPath; try { m.updateProfile(UpdateProfile.newBuilder() diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index c0dc1cf8..d2a08131 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -147,7 +147,7 @@ public class DbusManagerImpl implements Manager { emptyIfNull(updateProfile.getFamilyName()), emptyIfNull(updateProfile.getAbout()), emptyIfNull(updateProfile.getAboutEmoji()), - updateProfile.getAvatar() == null ? "" : updateProfile.getAvatar().getPath(), + updateProfile.getAvatar() == null ? "" : updateProfile.getAvatar(), updateProfile.isDeleteAvatar()); } @@ -231,11 +231,11 @@ public class DbusManagerImpl implements Manager { @Override public Pair createGroup( - final String name, final Set members, final File avatarFile + final String name, final Set members, final String avatarFile ) throws IOException, AttachmentInvalidException { final var newGroupId = signal.createGroup(emptyIfNull(name), members.stream().map(RecipientIdentifier.Single::getIdentifier).toList(), - avatarFile == null ? "" : avatarFile.getPath()); + avatarFile == null ? "" : avatarFile); return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of())); } @@ -253,7 +253,7 @@ public class DbusManagerImpl implements Manager { if (updateGroup.getAvatarFile() != null) { group.Set("org.asamk.Signal.Group", "Avatar", - updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile().getPath()); + updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile()); } if (updateGroup.getExpirationTimer() != null) { group.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup.getExpirationTimer()); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 1a9118be..2c0c30b2 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -618,7 +618,7 @@ public class DbusSignalImpl implements Signal { avatar = nullIfEmpty(avatar); final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber()); if (groupId == null) { - final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar)); + final var results = m.createGroup(name, memberIdentifiers, avatar); updateGroups(); checkGroupSendMessageResults(results.second().timestamp(), results.second().results()); return results.first().serialize(); @@ -627,7 +627,7 @@ public class DbusSignalImpl implements Signal { UpdateGroup.newBuilder() .withName(name) .withMembers(memberIdentifiers) - .withAvatarFile(avatar == null ? null : new File(avatar)) + .withAvatarFile(avatar) .build()); if (results != null) { checkGroupSendMessageResults(results.timestamp(), results.results()); @@ -687,7 +687,7 @@ public class DbusSignalImpl implements Signal { about = nullIfEmpty(about); aboutEmoji = nullIfEmpty(aboutEmoji); avatarPath = nullIfEmpty(avatarPath); - File avatarFile = removeAvatar || avatarPath == null ? null : new File(avatarPath); + final var avatarFile = removeAvatar || avatarPath == null ? null : avatarPath; m.updateProfile(UpdateProfile.newBuilder() .withGivenName(givenName) .withFamilyName(familyName) @@ -1270,7 +1270,7 @@ public class DbusSignalImpl implements Signal { } private void setGroupAvatar(final String avatar) { - updateGroup(UpdateGroup.newBuilder().withAvatarFile(new File(avatar)).build()); + updateGroup(UpdateGroup.newBuilder().withAvatarFile(avatar).build()); } private void setMessageExpirationTime(final int expirationTime) { -- 2.50.1