Retrieve avatar profile image

This commit is contained in:
AsamK 2020-09-10 10:19:59 +02:00
parent 6c4d272309
commit 0f3aa22519
4 changed files with 85 additions and 35 deletions

View 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());

View 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";

View 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 +
'}';
}
}

View 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);
}
}
}
}