]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Utils.java
Reformat project
[signal-cli] / src / main / java / org / asamk / signal / manager / Utils.java
index 4012674f1c76219296fa7e71f7433351c860dd90..0a815ea9ad3971df098134f69478ca702954d089 100644 (file)
@@ -1,6 +1,5 @@
 package org.asamk.signal.manager;
 
-import org.asamk.signal.AttachmentInvalidException;
 import org.signal.libsignal.metadata.certificate.CertificateValidator;
 import org.whispersystems.libsignal.IdentityKey;
 import org.whispersystems.libsignal.InvalidKeyException;
@@ -13,52 +12,106 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
-import org.whispersystems.signalservice.internal.util.Base64;
+import org.whispersystems.signalservice.api.util.StreamDetails;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
+import org.whispersystems.util.Base64;
 
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.URI;
+import java.net.URLConnection;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
 
 class Utils {
 
     static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
-        List<SignalServiceAttachment> SignalServiceAttachments = null;
+        List<SignalServiceAttachment> signalServiceAttachments = null;
         if (attachments != null) {
-            SignalServiceAttachments = new ArrayList<>(attachments.size());
+            signalServiceAttachments = new ArrayList<>(attachments.size());
             for (String attachment : attachments) {
                 try {
-                    SignalServiceAttachments.add(createAttachment(new File(attachment)));
+                    signalServiceAttachments.add(createAttachment(new File(attachment)));
                 } catch (IOException e) {
                     throw new AttachmentInvalidException(attachment, e);
                 }
             }
         }
-        return SignalServiceAttachments;
+        return signalServiceAttachments;
+    }
+
+    static String getFileMimeType(File file, String defaultMimeType) throws IOException {
+        String mime = Files.probeContentType(file.toPath());
+        if (mime == null) {
+            try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
+                mime = URLConnection.guessContentTypeFromStream(bufferedStream);
+            }
+        }
+        if (mime == null) {
+            return defaultMimeType;
+        }
+        return mime;
     }
 
     static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
         InputStream attachmentStream = new FileInputStream(attachmentFile);
         final long attachmentSize = attachmentFile.length();
-        String mime = Files.probeContentType(attachmentFile.toPath());
+        final String mime = getFileMimeType(attachmentFile, "application/octet-stream");
+        // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
+        final long uploadTimestamp = System.currentTimeMillis();
+        Optional<byte[]> preview = Optional.absent();
+        Optional<String> caption = Optional.absent();
+        Optional<String> blurHash = Optional.absent();
+        final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
+        return new SignalServiceAttachmentStream(attachmentStream,
+                mime,
+                attachmentSize,
+                Optional.of(attachmentFile.getName()),
+                false,
+                false,
+                preview,
+                0,
+                0,
+                uploadTimestamp,
+                caption,
+                blurHash,
+                null,
+                null,
+                resumableUploadSpec);
+    }
+
+    static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
+        InputStream stream = new FileInputStream(file);
+        final long size = file.length();
+        String mime = Files.probeContentType(file.toPath());
         if (mime == null) {
             mime = "application/octet-stream";
         }
-        // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
-        Optional<byte[]> preview = Optional.absent();
-        Optional<String> caption = Optional.absent();
-        return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
+        return new StreamDetails(stream, mime, size);
     }
 
     static CertificateValidator getCertificateValidator() {
         try {
-            ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
+            ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT),
+                    0);
             return new CertificateValidator(unidentifiedSenderTrustRoot);
         } catch (InvalidKeyException | IOException e) {
             throw new AssertionError(e);
@@ -69,31 +122,20 @@ class Utils {
         String[] params = query.split("&");
         Map<String, String> map = new HashMap<>();
         for (String param : params) {
-            String name = null;
             final String[] paramParts = param.split("=");
-            try {
-                name = URLDecoder.decode(paramParts[0], "utf-8");
-            } catch (UnsupportedEncodingException e) {
-                // Impossible
-            }
-            String value = null;
-            try {
-                value = URLDecoder.decode(paramParts[1], "utf-8");
-            } catch (UnsupportedEncodingException e) {
-                // Impossible
-            }
+            String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
+            String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
             map.put(name, value);
         }
         return map;
     }
 
     static String createDeviceLinkUri(DeviceLinkInfo info) {
-        try {
-            return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
-        } catch (UnsupportedEncodingException e) {
-            // Shouldn't happen
-            return null;
-        }
+        return "tsdevice:/?uuid="
+                + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8)
+                + "&pub_key="
+                + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()),
+                StandardCharsets.UTF_8);
     }
 
     static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
@@ -110,38 +152,19 @@ class Utils {
         return new DeviceLinkInfo(deviceIdentifier, deviceKey);
     }
 
-    static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
-        Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
-        for (String recipient : recipients) {
-            try {
-                recipientsTS.add(getPushAddress(recipient, localNumber));
-            } catch (InvalidNumberException e) {
-                System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
-                System.err.println("Aborting sending.");
-                return null;
-            }
-        }
-        return recipientsTS;
-    }
-
-    static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
-        return PhoneNumberFormatter.formatNumber(number, localNumber);
-    }
-
-    private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
-        String e164number = canonicalizeNumber(number, localNumber);
-        return new SignalServiceAddress(e164number);
-    }
-
     static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
         try (FileInputStream f = new FileInputStream(file)) {
             DataInputStream in = new DataInputStream(f);
             int version = in.readInt();
-            if (version > 2) {
+            if (version > 4) {
                 return null;
             }
             int type = in.readInt();
             String source = in.readUTF();
+            UUID sourceUuid = null;
+            if (version >= 3) {
+                sourceUuid = UuidUtil.parseOrNull(in.readUTF());
+            }
             int sourceDevice = in.readInt();
             if (version == 1) {
                 // read legacy relay field
@@ -160,25 +183,41 @@ class Utils {
                 legacyMessage = new byte[legacyMessageLen];
                 in.readFully(legacyMessage);
             }
-            long serverTimestamp = 0;
+            long serverReceivedTimestamp = 0;
             String uuid = null;
-            if (version == 2) {
-                serverTimestamp = in.readLong();
+            if (version >= 2) {
+                serverReceivedTimestamp = in.readLong();
                 uuid = in.readUTF();
                 if ("".equals(uuid)) {
                     uuid = null;
                 }
             }
-            return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
+            long serverDeliveredTimestamp = 0;
+            if (version >= 4) {
+                serverDeliveredTimestamp = in.readLong();
+            }
+            Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
+                    ? Optional.absent()
+                    : Optional.of(new SignalServiceAddress(sourceUuid, source));
+            return new SignalServiceEnvelope(type,
+                    addressOptional,
+                    sourceDevice,
+                    timestamp,
+                    legacyMessage,
+                    content,
+                    serverReceivedTimestamp,
+                    serverDeliveredTimestamp,
+                    uuid);
         }
     }
 
     static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
         try (FileOutputStream f = new FileOutputStream(file)) {
             try (DataOutputStream out = new DataOutputStream(f)) {
-                out.writeInt(2); // version
+                out.writeInt(4); // version
                 out.writeInt(envelope.getType());
-                out.writeUTF(envelope.getSource());
+                out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
+                out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
                 out.writeInt(envelope.getSourceDevice());
                 out.writeLong(envelope.getTimestamp());
                 if (envelope.hasContent()) {
@@ -193,9 +232,10 @@ class Utils {
                 } else {
                     out.writeInt(0);
                 }
-                out.writeLong(envelope.getServerTimestamp());
+                out.writeLong(envelope.getServerReceivedTimestamp());
                 String uuid = envelope.getUuid();
                 out.writeUTF(uuid == null ? "" : uuid);
+                out.writeLong(envelope.getServerDeliveredTimestamp());
             }
         }
     }
@@ -217,8 +257,37 @@ class Utils {
         return outputFile;
     }
 
-    static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
-        Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(ownUsername, ownIdentityKey, theirUsername, theirIdentityKey);
+    static String computeSafetyNumber(
+            SignalServiceAddress ownAddress,
+            IdentityKey ownIdentityKey,
+            SignalServiceAddress theirAddress,
+            IdentityKey theirIdentityKey
+    ) {
+        int version;
+        byte[] ownId;
+        byte[] theirId;
+
+        if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid()
+                .isPresent()) {
+            // Version 2: UUID user
+            version = 2;
+            ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
+            theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
+        } else {
+            // Version 1: E164 user
+            version = 1;
+            if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
+                return "INVALID ID";
+            }
+            ownId = ownAddress.getNumber().get().getBytes();
+            theirId = theirAddress.getNumber().get().getBytes();
+        }
+
+        Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
+                ownId,
+                ownIdentityKey,
+                theirId,
+                theirIdentityKey);
         return fingerprint.getDisplayableFingerprint().getDisplayText();
     }