]> nmode's Git Repositories - signal-cli/commitdiff
Extract some utils from manager
authorAsamK <asamk@gmx.de>
Thu, 14 Jan 2021 21:18:40 +0000 (22:18 +0100)
committerAsamK <asamk@gmx.de>
Thu, 14 Jan 2021 21:19:54 +0000 (22:19 +0100)
src/main/java/org/asamk/signal/manager/HandleAction.java
src/main/java/org/asamk/signal/manager/JsonStickerPack.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
src/main/java/org/asamk/signal/manager/util/ProfileUtils.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/util/StickerUtils.java [new file with mode: 0644]

index 0dd151a93b9fd8ade674613513849fc304674032..8338e4e67a05e6b9e2a8cfa312422a1473415bcf 100644 (file)
@@ -124,19 +124,19 @@ class SendGroupInfoRequestAction implements HandleAction {
     }
 }
 
-class SendGroupUpdateAction implements HandleAction {
+class SendGroupInfoAction implements HandleAction {
 
     private final SignalServiceAddress address;
     private final GroupIdV1 groupId;
 
-    public SendGroupUpdateAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
+    public SendGroupInfoAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
         this.address = address;
         this.groupId = groupId;
     }
 
     @Override
     public void execute(Manager m) throws Throwable {
-        m.sendUpdateGroupMessage(groupId, address);
+        m.sendGroupInfoMessage(groupId, address);
     }
 
     @Override
@@ -144,7 +144,7 @@ class SendGroupUpdateAction implements HandleAction {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
-        final SendGroupUpdateAction that = (SendGroupUpdateAction) o;
+        final SendGroupInfoAction that = (SendGroupInfoAction) o;
 
         if (!address.equals(that.address)) return false;
         return groupId.equals(that.groupId);
index a7e5eb7f5b0f6c084737dcbc667b733a8cbffcb9..e5e0e445ce47cf558c9eceef7d6597a61b8dcfcc 100644 (file)
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 import java.util.List;
 
-class JsonStickerPack {
+public class JsonStickerPack {
 
     @JsonProperty
     public String title;
index 977ff8b71d0bf45d909a4a2d7323947a2b3ffe33..a0f6aa532a1ef2313eb4deaf1fdf5b3a3eb3c1a3 100644 (file)
@@ -16,8 +16,6 @@
  */
 package org.asamk.signal.manager;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdV1;
 import org.asamk.signal.manager.groups.GroupIdV2;
@@ -42,6 +40,8 @@ import org.asamk.signal.manager.storage.stickers.Sticker;
 import org.asamk.signal.manager.util.AttachmentUtils;
 import org.asamk.signal.manager.util.IOUtils;
 import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.ProfileUtils;
+import org.asamk.signal.manager.util.StickerUtils;
 import org.asamk.signal.manager.util.Utils;
 import org.signal.libsignal.metadata.InvalidMetadataMessageException;
 import org.signal.libsignal.metadata.InvalidMetadataVersionException;
@@ -84,8 +84,6 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
 import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
-import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
-import org.whispersystems.signalservice.api.crypto.ProfileCipher;
 import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -107,7 +105,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
-import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
@@ -141,7 +138,6 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
 import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
 import org.whispersystems.signalservice.internal.util.Hex;
-import org.whispersystems.util.Base64;
 
 import java.io.Closeable;
 import java.io.File;
@@ -170,8 +166,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 import static org.asamk.signal.manager.ServiceConfig.CDS_MRENCLAVE;
 import static org.asamk.signal.manager.ServiceConfig.capabilities;
@@ -492,7 +486,7 @@ public class Manager implements Closeable {
             return null;
         }
         long now = new Date().getTime();
-        // Profiles are cache for 24h before retrieving them again
+        // Profiles are cached for 24h before retrieving them again
         if (!profileEntry.isRequestPending() && (
                 profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
         )) {
@@ -508,8 +502,8 @@ public class Manager implements Closeable {
                 profileEntry.setRequestPending(false);
             }
 
-            ProfileKey profileKey = profileEntry.getProfileKey();
-            SignalProfile profile = decryptProfile(address, profileKey, encryptedProfile);
+            final ProfileKey profileKey = profileEntry.getProfileKey();
+            final SignalProfile profile = decryptProfileAndDownloadAvatar(address, profileKey, encryptedProfile);
             account.getProfileStore()
                     .updateProfile(address, profileKey, now, profile, profileEntry.getProfileKeyCredential());
             return profile;
@@ -534,7 +528,7 @@ public class Manager implements Closeable {
 
             long now = new Date().getTime();
             final ProfileKeyCredential profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
-            final SignalProfile profile = decryptProfile(address,
+            final SignalProfile profile = decryptProfileAndDownloadAvatar(address,
                     profileEntry.getProfileKey(),
                     profileAndCredential.getProfile());
             account.getProfileStore()
@@ -544,40 +538,14 @@ public class Manager implements Closeable {
         return profileEntry.getProfileKeyCredential();
     }
 
-    private SignalProfile decryptProfile(
+    private SignalProfile decryptProfileAndDownloadAvatar(
             final SignalServiceAddress address, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
     ) {
         if (encryptedProfile.getAvatar() != null) {
             downloadProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
         }
 
-        ProfileCipher profileCipher = new ProfileCipher(profileKey);
-        try {
-            String name;
-            try {
-                name = encryptedProfile.getName() == null
-                        ? null
-                        : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName())));
-            } catch (IOException e) {
-                name = null;
-            }
-            String unidentifiedAccess;
-            try {
-                unidentifiedAccess = encryptedProfile.getUnidentifiedAccess() == null
-                        || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess()))
-                        ? null
-                        : encryptedProfile.getUnidentifiedAccess();
-            } catch (IOException e) {
-                unidentifiedAccess = null;
-            }
-            return new SignalProfile(encryptedProfile.getIdentityKey(),
-                    name,
-                    unidentifiedAccess,
-                    encryptedProfile.isUnrestrictedUnidentifiedAccess(),
-                    encryptedProfile.getCapabilities());
-        } catch (InvalidCiphertextException e) {
-            return null;
-        }
+        return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
     }
 
     private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(GroupId groupId) throws IOException {
@@ -624,17 +592,6 @@ public class Manager implements Closeable {
         return account.getGroupStore().getGroups();
     }
 
-    public Pair<Long, List<SendMessageResult>> sendGroupMessage(
-            SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
-    ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
-        final GroupInfo g = getGroupForSending(groupId);
-
-        GroupUtils.setGroupContext(messageBuilder, g);
-        messageBuilder.withExpiration(g.getMessageExpirationTime());
-
-        return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
-    }
-
     public Pair<Long, List<SendMessageResult>> sendGroupMessage(
             String messageText, List<String> attachments, GroupId groupId
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
@@ -660,8 +617,18 @@ public class Manager implements Closeable {
         return sendGroupMessage(messageBuilder, groupId);
     }
 
-    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+    public Pair<Long, List<SendMessageResult>> sendGroupMessage(
+            SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
+    ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
+        final GroupInfo g = getGroupForSending(groupId);
 
+        GroupUtils.setGroupContext(messageBuilder, g);
+        messageBuilder.withExpiration(g.getMessageExpirationTime());
+
+        return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
+    }
+
+    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
         SignalServiceDataMessage.Builder messageBuilder;
 
         final GroupInfo g = getGroupForUpdating(groupId);
@@ -684,6 +651,15 @@ public class Manager implements Closeable {
         return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
+    public Pair<GroupId, List<SendMessageResult>> updateGroup(
+            GroupId groupId, String name, List<String> members, File avatarFile
+    ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
+        return sendUpdateGroupMessage(groupId,
+                name,
+                members == null ? null : getSignalServiceAddresses(members),
+                avatarFile);
+    }
+
     private Pair<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
             GroupId groupId, String name, Collection<SignalServiceAddress> members, File avatarFile
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
@@ -764,44 +740,6 @@ public class Manager implements Closeable {
         return new Pair<>(g.getGroupId(), result.second());
     }
 
-    public Pair<GroupId, List<SendMessageResult>> joinGroup(
-            GroupInviteLinkUrl inviteLinkUrl
-    ) throws IOException, GroupLinkNotActiveException {
-        return sendJoinGroupMessage(inviteLinkUrl);
-    }
-
-    private Pair<GroupId, List<SendMessageResult>> sendJoinGroupMessage(
-            GroupInviteLinkUrl inviteLinkUrl
-    ) throws IOException, GroupLinkNotActiveException {
-        final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
-                inviteLinkUrl.getPassword());
-        final GroupChange groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
-                inviteLinkUrl.getPassword(),
-                groupJoinInfo);
-        final GroupInfoV2 group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
-                groupJoinInfo.getRevision() + 1,
-                groupChange.toByteArray());
-
-        if (group.getGroup() == null) {
-            // Only requested member, can't send update to group members
-            return new Pair<>(group.getGroupId(), List.of());
-        }
-
-        final Pair<Long, List<SendMessageResult>> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange);
-
-        return new Pair<>(group.getGroupId(), result.second());
-    }
-
-    private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
-            GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
-    ) throws IOException {
-        group.setGroup(newDecryptedGroup);
-        final SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(group,
-                groupChange.toByteArray());
-        account.getGroupStore().updateGroup(group);
-        return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress()));
-    }
-
     private void updateGroupV1(
             final GroupInfoV1 g,
             final String name,
@@ -841,7 +779,67 @@ public class Manager implements Closeable {
         }
     }
 
-    Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
+    public Pair<GroupId, List<SendMessageResult>> joinGroup(
+            GroupInviteLinkUrl inviteLinkUrl
+    ) throws IOException, GroupLinkNotActiveException {
+        return sendJoinGroupMessage(inviteLinkUrl);
+    }
+
+    private Pair<GroupId, List<SendMessageResult>> sendJoinGroupMessage(
+            GroupInviteLinkUrl inviteLinkUrl
+    ) throws IOException, GroupLinkNotActiveException {
+        final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
+                inviteLinkUrl.getPassword());
+        final GroupChange groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
+                inviteLinkUrl.getPassword(),
+                groupJoinInfo);
+        final GroupInfoV2 group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
+                groupJoinInfo.getRevision() + 1,
+                groupChange.toByteArray());
+
+        if (group.getGroup() == null) {
+            // Only requested member, can't send update to group members
+            return new Pair<>(group.getGroupId(), List.of());
+        }
+
+        final Pair<Long, List<SendMessageResult>> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange);
+
+        return new Pair<>(group.getGroupId(), result.second());
+    }
+
+    private static int currentTimeDays() {
+        return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
+    }
+
+    private GroupsV2AuthorizationString getGroupAuthForToday(
+            final GroupSecretParams groupSecretParams
+    ) throws IOException {
+        final int today = currentTimeDays();
+        // Returns credentials for the next 7 days
+        final HashMap<Integer, AuthCredentialResponse> credentials = groupsV2Api.getCredentials(today);
+        // TODO cache credentials until they expire
+        AuthCredentialResponse authCredentialResponse = credentials.get(today);
+        try {
+            return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
+                    today,
+                    groupSecretParams,
+                    authCredentialResponse);
+        } catch (VerificationFailedException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
+            GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
+    ) throws IOException {
+        group.setGroup(newDecryptedGroup);
+        final SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(group,
+                groupChange.toByteArray());
+        account.getGroupStore().updateGroup(group);
+        return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress()));
+    }
+
+    Pair<Long, List<SendMessageResult>> sendGroupInfoMessage(
             GroupIdV1 groupId, SignalServiceAddress recipient
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
         GroupInfoV1 g;
@@ -1011,15 +1009,6 @@ public class Manager implements Closeable {
         account.save();
     }
 
-    public Pair<GroupId, List<SendMessageResult>> updateGroup(
-            GroupId groupId, String name, List<String> members, File avatarFile
-    ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
-        return sendUpdateGroupMessage(groupId,
-                name,
-                members == null ? null : getSignalServiceAddresses(members),
-                avatarFile);
-    }
-
     /**
      * Change the expiration timer for a contact
      */
@@ -1068,7 +1057,7 @@ public class Manager implements Closeable {
      * @return if successful, returns the URL to install the sticker pack in the signal app
      */
     public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
-        SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path);
+        SignalServiceStickerManifestUpload manifest = StickerUtils.getSignalServiceStickerManifestUpload(path);
 
         SignalServiceMessageSender messageSender = createMessageSender();
 
@@ -1091,96 +1080,6 @@ public class Manager implements Closeable {
         }
     }
 
-    private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(
-            final File file
-    ) throws IOException, StickerPackInvalidException {
-        ZipFile zip = null;
-        String rootPath = null;
-
-        if (file.getName().endsWith(".zip")) {
-            zip = new ZipFile(file);
-        } else if (file.getName().equals("manifest.json")) {
-            rootPath = file.getParent();
-        } else {
-            throw new StickerPackInvalidException("Could not find manifest.json");
-        }
-
-        JsonStickerPack pack = parseStickerPack(rootPath, zip);
-
-        if (pack.stickers == null) {
-            throw new StickerPackInvalidException("Must set a 'stickers' field.");
-        }
-
-        if (pack.stickers.isEmpty()) {
-            throw new StickerPackInvalidException("Must include stickers.");
-        }
-
-        List<StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
-        for (JsonStickerPack.JsonSticker sticker : pack.stickers) {
-            if (sticker.file == null) {
-                throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
-            }
-
-            Pair<InputStream, Long> data;
-            try {
-                data = getInputStreamAndLength(rootPath, zip, sticker.file);
-            } catch (IOException ignored) {
-                throw new StickerPackInvalidException("Could not find find " + sticker.file);
-            }
-
-            String contentType = Utils.getFileMimeType(new File(sticker.file), null);
-            StickerInfo stickerInfo = new StickerInfo(data.first(),
-                    data.second(),
-                    Optional.fromNullable(sticker.emoji).or(""),
-                    contentType);
-            stickers.add(stickerInfo);
-        }
-
-        StickerInfo cover = null;
-        if (pack.cover != null) {
-            if (pack.cover.file == null) {
-                throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
-            }
-
-            Pair<InputStream, Long> data;
-            try {
-                data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
-            } catch (IOException ignored) {
-                throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
-            }
-
-            String contentType = Utils.getFileMimeType(new File(pack.cover.file), null);
-            cover = new StickerInfo(data.first(),
-                    data.second(),
-                    Optional.fromNullable(pack.cover.emoji).or(""),
-                    contentType);
-        }
-
-        return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers);
-    }
-
-    private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {
-        InputStream inputStream;
-        if (zip != null) {
-            inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
-        } else {
-            inputStream = new FileInputStream((new File(rootPath, "manifest.json")));
-        }
-        return new ObjectMapper().readValue(inputStream, JsonStickerPack.class);
-    }
-
-    private static Pair<InputStream, Long> getInputStreamAndLength(
-            final String rootPath, final ZipFile zip, final String subfile
-    ) throws IOException {
-        if (zip != null) {
-            final ZipEntry entry = zip.getEntry(subfile);
-            return new Pair<>(zip.getInputStream(entry), entry.getSize());
-        } else {
-            final File file = new File(rootPath, subfile);
-            return new Pair<>(new FileInputStream(file), file.length());
-        }
-    }
-
     void requestSyncGroups() throws IOException {
         SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS)
@@ -1189,7 +1088,7 @@ public class Manager implements Closeable {
         try {
             sendSyncMessage(message);
         } catch (UntrustedIdentityException e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
         }
     }
 
@@ -1201,7 +1100,7 @@ public class Manager implements Closeable {
         try {
             sendSyncMessage(message);
         } catch (UntrustedIdentityException e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
         }
     }
 
@@ -1213,7 +1112,7 @@ public class Manager implements Closeable {
         try {
             sendSyncMessage(message);
         } catch (UntrustedIdentityException e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
         }
     }
 
@@ -1225,7 +1124,7 @@ public class Manager implements Closeable {
         try {
             sendSyncMessage(message);
         } catch (UntrustedIdentityException e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
         }
     }
 
@@ -1426,28 +1325,6 @@ public class Manager implements Closeable {
         account.getSignalProtocolStore().deleteAllSessions(source);
     }
 
-    private static int currentTimeDays() {
-        return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
-    }
-
-    private GroupsV2AuthorizationString getGroupAuthForToday(
-            final GroupSecretParams groupSecretParams
-    ) throws IOException {
-        final int today = currentTimeDays();
-        // Returns credentials for the next 7 days
-        final HashMap<Integer, AuthCredentialResponse> credentials = groupsV2Api.getCredentials(today);
-        // TODO cache credentials until they expire
-        AuthCredentialResponse authCredentialResponse = credentials.get(today);
-        try {
-            return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
-                    today,
-                    groupSecretParams,
-                    authCredentialResponse);
-        } catch (VerificationFailedException e) {
-            throw new IOException(e);
-        }
-    }
-
     private List<HandleAction> handleSignalServiceDataMessage(
             SignalServiceDataMessage message,
             boolean isSync,
@@ -1503,7 +1380,7 @@ public class Manager implements Closeable {
                         }
                         case REQUEST_INFO:
                             if (groupV1 != null && !isSync) {
-                                actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId()));
+                                actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
                             }
                             break;
                     }
@@ -1682,7 +1559,7 @@ public class Manager implements Closeable {
                 try {
                     action.execute(this);
                 } catch (Throwable e) {
-                    e.printStackTrace();
+                    logger.warn("Message action failed.", e);
                 }
             }
         }
@@ -1727,7 +1604,7 @@ public class Manager implements Closeable {
                             try {
                                 action.execute(this);
                             } catch (Throwable e) {
-                                e.printStackTrace();
+                                logger.warn("Message action failed.", e);
                             }
                         }
                         account.save();
@@ -1763,7 +1640,7 @@ public class Manager implements Closeable {
                         try {
                             action.execute(this);
                         } catch (Throwable e) {
-                            e.printStackTrace();
+                            logger.warn("Message action failed.", e);
                         }
                     }
                 } else {
@@ -1945,7 +1822,6 @@ public class Manager implements Closeable {
                         logger.warn("Failed to handle received sync groups “{}”, ignoring: {}",
                                 tmpFile,
                                 e.getMessage());
-                        e.printStackTrace();
                     } finally {
                         if (tmpFile != null) {
                             try {
@@ -2026,7 +1902,9 @@ public class Manager implements Closeable {
                             }
                         }
                     } catch (Exception e) {
-                        e.printStackTrace();
+                        logger.warn("Failed to handle received sync contacts “{}”, ignoring: {}",
+                                tmpFile,
+                                e.getMessage());
                     } finally {
                         if (tmpFile != null) {
                             try {
@@ -2400,7 +2278,7 @@ public class Manager implements Closeable {
             try {
                 sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             } catch (IOException | UntrustedIdentityException e) {
-                e.printStackTrace();
+                logger.warn("Failed to send verification sync message: {}", e.getMessage());
             }
             account.save();
             return true;
@@ -2430,7 +2308,7 @@ public class Manager implements Closeable {
             try {
                 sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             } catch (IOException | UntrustedIdentityException e) {
-                e.printStackTrace();
+                logger.warn("Failed to send verification sync message: {}", e.getMessage());
             }
             account.save();
             return true;
@@ -2456,7 +2334,7 @@ public class Manager implements Closeable {
                 try {
                     sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
                 } catch (IOException | UntrustedIdentityException e) {
-                    e.printStackTrace();
+                    logger.warn("Failed to send verification sync message: {}", e.getMessage());
                 }
             }
         }
index 87b1efd4db5c954d6fbb6ad8d63aee72c3ac234e..236c7996c9ccad61cc66ed63bae0f0701cfc49fd 100644 (file)
@@ -138,6 +138,8 @@ public class SignalAccount implements Closeable {
 
         account.registered = false;
 
+        account.migrateLegacyConfigs();
+
         return account;
     }
 
@@ -179,6 +181,8 @@ public class SignalAccount implements Closeable {
         account.registered = true;
         account.isMultiDevice = true;
 
+        account.migrateLegacyConfigs();
+
         return account;
     }
 
diff --git a/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java b/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
new file mode 100644 (file)
index 0000000..13ce3cb
--- /dev/null
@@ -0,0 +1,45 @@
+package org.asamk.signal.manager.util;
+
+import org.asamk.signal.manager.storage.profiles.SignalProfile;
+import org.signal.zkgroup.profiles.ProfileKey;
+import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
+import org.whispersystems.signalservice.api.crypto.ProfileCipher;
+import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
+import org.whispersystems.util.Base64;
+
+import java.io.IOException;
+
+public class ProfileUtils {
+
+    public static SignalProfile decryptProfile(
+            final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
+    ) {
+        ProfileCipher profileCipher = new ProfileCipher(profileKey);
+        try {
+            String name;
+            try {
+                name = encryptedProfile.getName() == null
+                        ? null
+                        : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName())));
+            } catch (IOException e) {
+                name = null;
+            }
+            String unidentifiedAccess;
+            try {
+                unidentifiedAccess = encryptedProfile.getUnidentifiedAccess() == null
+                        || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess()))
+                        ? null
+                        : encryptedProfile.getUnidentifiedAccess();
+            } catch (IOException e) {
+                unidentifiedAccess = null;
+            }
+            return new SignalProfile(encryptedProfile.getIdentityKey(),
+                    name,
+                    unidentifiedAccess,
+                    encryptedProfile.isUnrestrictedUnidentifiedAccess(),
+                    encryptedProfile.getCapabilities());
+        } catch (InvalidCiphertextException e) {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/util/StickerUtils.java b/src/main/java/org/asamk/signal/manager/util/StickerUtils.java
new file mode 100644 (file)
index 0000000..fd5ce77
--- /dev/null
@@ -0,0 +1,113 @@
+package org.asamk.signal.manager.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.asamk.signal.manager.JsonStickerPack;
+import org.asamk.signal.manager.StickerPackInvalidException;
+import org.whispersystems.libsignal.util.Pair;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class StickerUtils {
+
+    public static SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(
+            final File file
+    ) throws IOException, StickerPackInvalidException {
+        ZipFile zip = null;
+        String rootPath = null;
+
+        if (file.getName().endsWith(".zip")) {
+            zip = new ZipFile(file);
+        } else if (file.getName().equals("manifest.json")) {
+            rootPath = file.getParent();
+        } else {
+            throw new StickerPackInvalidException("Could not find manifest.json");
+        }
+
+        JsonStickerPack pack = parseStickerPack(rootPath, zip);
+
+        if (pack.stickers == null) {
+            throw new StickerPackInvalidException("Must set a 'stickers' field.");
+        }
+
+        if (pack.stickers.isEmpty()) {
+            throw new StickerPackInvalidException("Must include stickers.");
+        }
+
+        List<SignalServiceStickerManifestUpload.StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
+        for (JsonStickerPack.JsonSticker sticker : pack.stickers) {
+            if (sticker.file == null) {
+                throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
+            }
+
+            Pair<InputStream, Long> data;
+            try {
+                data = getInputStreamAndLength(rootPath, zip, sticker.file);
+            } catch (IOException ignored) {
+                throw new StickerPackInvalidException("Could not find find " + sticker.file);
+            }
+
+            String contentType = Utils.getFileMimeType(new File(sticker.file), null);
+            SignalServiceStickerManifestUpload.StickerInfo stickerInfo = new SignalServiceStickerManifestUpload.StickerInfo(
+                    data.first(),
+                    data.second(),
+                    Optional.fromNullable(sticker.emoji).or(""),
+                    contentType);
+            stickers.add(stickerInfo);
+        }
+
+        SignalServiceStickerManifestUpload.StickerInfo cover = null;
+        if (pack.cover != null) {
+            if (pack.cover.file == null) {
+                throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
+            }
+
+            Pair<InputStream, Long> data;
+            try {
+                data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
+            } catch (IOException ignored) {
+                throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
+            }
+
+            String contentType = Utils.getFileMimeType(new File(pack.cover.file), null);
+            cover = new SignalServiceStickerManifestUpload.StickerInfo(data.first(),
+                    data.second(),
+                    Optional.fromNullable(pack.cover.emoji).or(""),
+                    contentType);
+        }
+
+        return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers);
+    }
+
+    private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {
+        InputStream inputStream;
+        if (zip != null) {
+            inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
+        } else {
+            inputStream = new FileInputStream((new File(rootPath, "manifest.json")));
+        }
+        return new ObjectMapper().readValue(inputStream, JsonStickerPack.class);
+    }
+
+    private static Pair<InputStream, Long> getInputStreamAndLength(
+            final String rootPath, final ZipFile zip, final String subfile
+    ) throws IOException {
+        if (zip != null) {
+            final ZipEntry entry = zip.getEntry(subfile);
+            return new Pair<>(zip.getInputStream(entry), entry.getSize());
+        } else {
+            final File file = new File(rootPath, subfile);
+            return new Pair<>(new FileInputStream(file), file.length());
+        }
+    }
+
+}