]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/Manager.java
Fix rare null pointer exception when receiving message from untrusted identity
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
index 9d0c355b2fe4a6674955fdb8e64b1d5928152af0..cde2714f25012ad39634b39a0bea5163283436bd 100644 (file)
@@ -327,16 +327,32 @@ public class Manager implements Closeable {
      * This is used for checking a set of phone numbers for registration on Signal
      *
      * @param numbers The set of phone number in question
-     * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
+     * @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null.
      * @throws IOException if its unable to get the contacts to check if they're registered
      */
-    public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
+    public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
+        Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
+            try {
+                return canonicalizePhoneNumber(n);
+            } catch (InvalidNumberException e) {
+                return "";
+            }
+        }));
+
         // Note "contactDetails" has no optionals. It only gives us info on users who are registered
-        var contactDetails = getRegisteredUsers(numbers);
+        var contactDetails = getRegisteredUsers(canonicalizedNumbers.values()
+                .stream()
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toSet()));
 
-        var registeredUsers = contactDetails.keySet();
+        // Store numbers as recipients so we have the number/uuid association
+        contactDetails.forEach((number, uuid) -> resolveRecipientTrusted(new SignalServiceAddress(uuid, number)));
 
-        return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
+        return numbers.stream().collect(Collectors.toMap(n -> n, n -> {
+            final var number = canonicalizedNumbers.get(n);
+            final var uuid = contactDetails.get(number);
+            return new Pair<>(number.isEmpty() ? null : number, uuid);
+        }));
     }
 
     public void updateAccountAttributes() throws IOException {
@@ -1175,11 +1191,31 @@ public class Manager implements Closeable {
         return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(resolveRecipient(recipient)));
     }
 
-    void sendReceipt(
-            SignalServiceAddress remoteAddress, long messageId
+    public void sendReadReceipt(
+            String sender, List<Long> messageIds
+    ) throws IOException, UntrustedIdentityException, InvalidNumberException {
+        var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
+                messageIds,
+                System.currentTimeMillis());
+
+        sendHelper.sendReceiptMessage(receiptMessage, canonicalizeAndResolveRecipient(sender));
+    }
+
+    public void sendViewedReceipt(
+            String sender, List<Long> messageIds
+    ) throws IOException, UntrustedIdentityException, InvalidNumberException {
+        var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
+                messageIds,
+                System.currentTimeMillis());
+
+        sendHelper.sendReceiptMessage(receiptMessage, canonicalizeAndResolveRecipient(sender));
+    }
+
+    void sendDeliveryReceipt(
+            SignalServiceAddress remoteAddress, List<Long> messageIds
     ) throws IOException, UntrustedIdentityException {
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
-                List.of(messageId),
+                messageIds,
                 System.currentTimeMillis());
 
         sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(remoteAddress));
@@ -1807,7 +1843,7 @@ public class Manager implements Closeable {
     ) throws IOException, InterruptedException {
         retryFailedReceivedMessages(handler, ignoreAttachments);
 
-        Set<HandleAction> queuedActions = null;
+        Set<HandleAction> queuedActions = new HashSet<>();
 
         final var signalWebSocket = dependencies.getSignalWebSocket();
         signalWebSocket.connect();
@@ -1836,20 +1872,17 @@ public class Manager implements Closeable {
                     // Received indicator that server queue is empty
                     hasCaughtUpWithOldMessages = true;
 
-                    if (queuedActions != null) {
-                        for (var action : queuedActions) {
-                            try {
-                                action.execute(this);
-                            } catch (Throwable e) {
-                                if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
-                                    Thread.currentThread().interrupt();
-                                }
-                                logger.warn("Message action failed.", e);
+                    for (var action : queuedActions) {
+                        try {
+                            action.execute(this);
+                        } catch (Throwable e) {
+                            if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
+                                Thread.currentThread().interrupt();
                             }
+                            logger.warn("Message action failed.", e);
                         }
-                        queuedActions.clear();
-                        queuedActions = null;
                     }
+                    queuedActions.clear();
 
                     // Continue to wait another timeout for new messages
                     continue;
@@ -1903,9 +1936,6 @@ public class Manager implements Closeable {
                         }
                     }
                 } else {
-                    if (queuedActions == null) {
-                        queuedActions = new HashSet<>();
-                    }
                     queuedActions.addAll(actions);
                 }
             }
@@ -2724,14 +2754,16 @@ public class Manager implements Closeable {
         return account.getRecipientStore().resolveServiceAddress(recipientId);
     }
 
-    public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
-        var canonicalizedNumber = UuidUtil.isUuid(identifier)
-                ? identifier
-                : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
+    private RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
+        var canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier : canonicalizePhoneNumber(identifier);
 
         return resolveRecipient(canonicalizedNumber);
     }
 
+    private String canonicalizePhoneNumber(final String number) throws InvalidNumberException {
+        return PhoneNumberFormatter.formatNumber(number, account.getUsername());
+    }
+
     private RecipientId resolveRecipient(final String identifier) {
         var address = Utils.getSignalServiceAddressFromIdentifier(identifier);