]> nmode's Git Repositories - signal-cli/commitdiff
Retrieve avatar profile image
authorAsamK <asamk@gmx.de>
Thu, 10 Sep 2020 08:19:59 +0000 (10:19 +0200)
committerAsamK <asamk@gmx.de>
Thu, 10 Sep 2020 10:20:04 +0000 (12:20 +0200)
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/ServiceConfig.java
src/main/java/org/asamk/signal/manager/SignalProfile.java
src/main/java/org/asamk/signal/util/IOUtils.java

index 73c2b5c79dd608dcad8c8c6681eb9eac352eb822..31f2d9d6fff05659e4bf03d2f0da28af875b1c2c 100644 (file)
@@ -142,6 +142,8 @@ import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import static org.asamk.signal.manager.ServiceConfig.capabilities;
+
 public class Manager implements Closeable {
 
     private final SleepTimer timer = new UptimeSleepTimer();
@@ -287,7 +289,7 @@ public class Manager implements Closeable {
     }
 
     public void updateAccountAttributes() throws IOException {
-        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities, discoverableByPhoneNumber);
+        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
     }
 
     public void setProfile(String name, File avatar) throws IOException {
@@ -372,7 +374,7 @@ public class Manager implements Closeable {
         verificationCode = verificationCode.replace("-", "");
         account.setSignalingKey(KeyUtils.createSignalingKey());
         // TODO make unrestricted unidentified access configurable
-        VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities, discoverableByPhoneNumber);
+        VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
 
         UUID uuid = UuidUtil.parseOrNull(response.getUuid());
         // TODO response.isStorageCapable()
@@ -440,7 +442,27 @@ public class Manager implements Closeable {
     }
 
     private SignalProfile getRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
-        return decryptProfile(getEncryptedRecipientProfile(address, unidentifiedAccess), profileKey);
+        final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address, unidentifiedAccess);
+
+        File avatarFile = null;
+        try {
+            avatarFile = encryptedProfile.getAvatar() == null ? null : retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
+        } catch (AssertionError e) {
+            System.err.println("Failed to retrieve profile avatar: " + e.getMessage());
+        }
+
+        ProfileCipher profileCipher = new ProfileCipher(profileKey);
+        try {
+            return new SignalProfile(
+                    encryptedProfile.getIdentityKey(),
+                    encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
+                    avatarFile,
+                    encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
+                    encryptedProfile.isUnrestrictedUnidentifiedAccess(),
+                    encryptedProfile.getCapabilities());
+        } catch (InvalidCiphertextException e) {
+            return null;
+        }
     }
 
     private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(byte[] groupId) throws IOException {
@@ -944,21 +966,6 @@ public class Manager implements Closeable {
         return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
     }
 
-    private static SignalProfile decryptProfile(SignalServiceProfile encryptedProfile, ProfileKey profileKey) throws IOException {
-        ProfileCipher profileCipher = new ProfileCipher(profileKey);
-        try {
-            return new SignalProfile(
-                    encryptedProfile.getIdentityKey(),
-                    encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
-                    encryptedProfile.getAvatar(),
-                    encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
-                    encryptedProfile.isUnrestrictedUnidentifiedAccess()
-            );
-        } catch (InvalidCiphertextException e) {
-            return null;
-        }
-    }
-
     private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
         ContactInfo contact = account.getContactStore().getContact(recipient);
         if (contact == null || contact.profileKey == null) {
@@ -1718,6 +1725,29 @@ public class Manager implements Closeable {
         }
     }
 
+    private File getProfileAvatarFile(SignalServiceAddress address) {
+        return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier());
+    }
+
+    private File retrieveProfileAvatar(SignalServiceAddress address, String avatarPath, ProfileKey profileKey) throws IOException {
+        IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
+        SignalServiceMessageReceiver receiver = getMessageReceiver();
+        File outputFile = getProfileAvatarFile(address);
+
+        File tmpFile = IOUtils.createTempFile();
+        try (InputStream input = receiver.retrieveProfileAvatar(avatarPath, tmpFile, profileKey, ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
+            // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
+            IOUtils.copyStreamToFile(input, outputFile, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
+        } finally {
+            try {
+                Files.delete(tmpFile.toPath());
+            } catch (IOException e) {
+                System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
+            }
+        }
+        return outputFile;
+    }
+
     public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
         return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
     }
@@ -1743,17 +1773,7 @@ public class Manager implements Closeable {
 
         File tmpFile = IOUtils.createTempFile();
         try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE)) {
-            try (OutputStream output = new FileOutputStream(outputFile)) {
-                byte[] buffer = new byte[4096];
-                int read;
-
-                while ((read = input.read(buffer)) != -1) {
-                    output.write(buffer, 0, read);
-                }
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-                return null;
-            }
+            IOUtils.copyStreamToFile(input, outputFile);
         } finally {
             try {
                 Files.delete(tmpFile.toPath());
index f8394adaf1f8fec19ad4a1b599efffd55b887a76..a8b0c5b66277fe63bb07d19fcca2ec9ac3ba1fc7 100644 (file)
@@ -26,6 +26,7 @@ public class ServiceConfig {
     final static int PREKEY_MINIMUM_COUNT = 20;
     final static int PREKEY_BATCH_SIZE = 100;
     final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
+    final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024;
 
     private final static String URL = "https://textsecure-service.whispersystems.org";
     private final static String CDN_URL = "https://cdn.signal.org";
index 4f529c0d241cc845b53e94c17d279435cea1e8e0..1217793cd551b3de14eba2dc356afaf91afd2bd1 100644 (file)
@@ -1,23 +1,30 @@
 package org.asamk.signal.manager;
 
+import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
+
+import java.io.File;
+
 public class SignalProfile {
 
     private final String identityKey;
 
     private final String name;
 
-    private final String avatar;
+    private final File avatarFile;
 
     private final String unidentifiedAccess;
 
     private final boolean unrestrictedUnidentifiedAccess;
 
-    public SignalProfile(final String identityKey, final String name, final String avatar, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess) {
+    private final SignalServiceProfile.Capabilities capabilities;
+
+    public SignalProfile(final String identityKey, final String name, final File avatarFile, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final SignalServiceProfile.Capabilities capabilities) {
         this.identityKey = identityKey;
         this.name = name;
-        this.avatar = avatar;
+        this.avatarFile = avatarFile;
         this.unidentifiedAccess = unidentifiedAccess;
         this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
+        this.capabilities = capabilities;
     }
 
     public String getIdentityKey() {
@@ -28,8 +35,8 @@ public class SignalProfile {
         return name;
     }
 
-    public String getAvatar() {
-        return avatar;
+    public File getAvatarFile() {
+        return avatarFile;
     }
 
     public String getUnidentifiedAccess() {
@@ -40,14 +47,19 @@ public class SignalProfile {
         return unrestrictedUnidentifiedAccess;
     }
 
+    public SignalServiceProfile.Capabilities getCapabilities() {
+        return capabilities;
+    }
+
     @Override
     public String toString() {
         return "SignalProfile{" +
                 "identityKey='" + identityKey + '\'' +
                 ", name='" + name + '\'' +
-                ", avatar='" + avatar + '\'' +
+                ", avatarFile=" + avatarFile +
                 ", unidentifiedAccess='" + unidentifiedAccess + '\'' +
                 ", unrestrictedUnidentifiedAccess=" + unrestrictedUnidentifiedAccess +
+                ", capabilities=" + capabilities +
                 '}';
     }
 }
index f21c1572b57334735b483cba7d827c377483d8d1..1163d07949b661a393039c7d10e540d1e27375c4 100644 (file)
@@ -4,8 +4,10 @@ import org.whispersystems.signalservice.internal.util.Util;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
@@ -77,4 +79,19 @@ public class IOUtils {
 
         return System.getProperty("user.home") + "/.local/share";
     }
+
+    public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
+        copyStreamToFile(input, outputFile, 8192);
+    }
+
+    public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
+        try (OutputStream output = new FileOutputStream(outputFile)) {
+            byte[] buffer = new byte[bufferSize];
+            int read;
+
+            while ((read = input.read(buffer)) != -1) {
+                output.write(buffer, 0, read);
+            }
+        }
+    }
 }