diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 0086db65..c1d62e69 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -322,10 +322,16 @@ public class Manager implements Closeable { * @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 * @throws IOException if its unable to get the contacts to check if they're registered + * @throws InvalidNumberException if phone number is incorrectly formatted */ - public Map areUsersRegistered(Set numbers) throws IOException { + public Map areUsersRegistered(Set numbers) throws IOException, InvalidNumberException { // Note "contactDetails" has no optionals. It only gives us info on users who are registered - var contactDetails = getRegisteredUsers(numbers); + Map contactDetails = null; + try { + contactDetails = getRegisteredUsers(numbers); + } catch (InvalidNumberException e) { + throw new InvalidNumberException(e.getMessage()); + } var registeredUsers = contactDetails.keySet(); @@ -924,6 +930,7 @@ public class Manager implements Closeable { .map(this::resolveSignalServiceAddress) .collect(Collectors.toList()); final var newE164Members = new HashSet(); + Map registeredUsers = null; for (var member : newMemberAddresses) { if (!member.getNumber().isPresent()) { continue; @@ -931,7 +938,11 @@ public class Manager implements Closeable { newE164Members.add(member.getNumber().get()); } - final var registeredUsers = getRegisteredUsers(newE164Members); + try { + registeredUsers = getRegisteredUsers(newE164Members); + } catch (InvalidNumberException e) { + throw new IOException("Invalid number detected: " + e.getMessage()); + } if (registeredUsers.size() != newE164Members.size()) { // Some of the new members are not registered on Signal newE164Members.removeAll(registeredUsers.keySet()); @@ -1513,25 +1524,35 @@ public class Manager implements Closeable { return signalServiceAddresses.stream().map(this::resolveRecipient).collect(Collectors.toSet()); } - private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException { + private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException, InvalidNumberException { final var address = resolveSignalServiceAddress(recipientId); if (!address.getNumber().isPresent()) { return recipientId; } final var number = address.getNumber().get(); - final var uuidMap = getRegisteredUsers(Set.of(number)); + Map uuidMap; + try { + uuidMap = getRegisteredUsers(Set.of(number)); + } catch (InvalidNumberException e) { + logger.warn("Improperly formatted number: {}", e.getMessage()); + uuidMap = Map.of(); + } return resolveRecipientTrusted(new SignalServiceAddress(uuidMap.getOrDefault(number, null), number)); } - private Map getRegisteredUsers(final Set numbers) throws IOException { + private Map getRegisteredUsers(final Set numbers) throws IOException, InvalidNumberException { + Map registeredUsers = null; try { - return dependencies.getAccountManager() + registeredUsers = dependencies.getAccountManager() .getRegisteredUsers(ServiceConfig.getIasKeyStore(), numbers, serviceEnvironmentConfig.getCdsMrenclave()); } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) { - throw new IOException(e); + throw new IOException(e.getMessage()); + } catch (NumberFormatException e) { + throw new InvalidNumberException(e.getMessage()); } + return registeredUsers; } public void sendTypingMessage( @@ -1681,7 +1702,13 @@ public class Manager implements Closeable { ContentHint.DEFAULT, message); } catch (UnregisteredUserException e) { - final var newRecipientId = refreshRegisteredUser(recipientId); + RecipientId newRecipientId = null; + try { + newRecipientId = refreshRegisteredUser(recipientId); + } catch (InvalidNumberException ine) { + logger.warn("Invalid number"); + return SendMessageResult.unregisteredFailure(resolveSignalServiceAddress(recipientId)); + } return messageSender.sendDataMessage(resolveSignalServiceAddress(newRecipientId), unidentifiedAccessHelper.getAccessFor(newRecipientId), ContentHint.DEFAULT, @@ -1700,7 +1727,13 @@ public class Manager implements Closeable { try { return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId)); } catch (UnregisteredUserException e) { - final var newRecipientId = refreshRegisteredUser(recipientId); + RecipientId newRecipientId = null; + try { + newRecipientId = refreshRegisteredUser(recipientId); + } catch (InvalidNumberException ine) { + logger.warn("Invalid number"); + return SendMessageResult.unregisteredFailure(resolveSignalServiceAddress(recipientId)); + } final var newAddress = resolveSignalServiceAddress(newRecipientId); return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId)); } diff --git a/man/signal-cli-dbus.5.adoc b/man/signal-cli-dbus.5.adoc index 76b9f541..fc69c1f2 100755 --- a/man/signal-cli-dbus.5.adoc +++ b/man/signal-cli-dbus.5.adoc @@ -113,7 +113,7 @@ isMember(base64GroupId) -> active:: * base64GroupId : String representing the internal group identifier in Base64 format * active : Boolean representing whether you are a member of the group -Note that this method does not raise an Exception for a non-existing/unknown group but will simply return false +Exceptions: None; returns false for a non-existing/unknown group sendEndSessionMessage(recipients) -> <>:: * recipients : String array of phone numbers @@ -195,13 +195,13 @@ Depending on the type of the recipient(s) field this deletes a message with one Exceptions: Failure, InvalidNumber -sendContacts() -> <> +sendContacts() -> <>:: Sends a synchronization message with the local contacts list to all linked devices. Exceptions: Failure, UntrustedIdentity -sendSyncRequest() -> <> +sendSyncRequest() -> <>:: Sends a synchronization request to the primary device (for group, contacts, ...). Only works if a secondary device is running the daemon. @@ -213,7 +213,7 @@ trust(number, safetyNumber) -> <>:: Exceptions: Failure, InvalidNumber; -sendTyping(typingAction, base64GroupId, recipients) -> <> +sendTyping(typingAction, base64GroupId, recipients) -> <>:: * typingAction : true = start typing, false = stop typing * base64GroupId : String representing the internal group identifier in Base64 format * recipients : List of phone numbers @@ -226,14 +226,20 @@ getContactName(number) -> name:: * number : Phone number * name : Contact's name in local storage (from the primary device for a linked account, or the one set with setContactName); if not set, contact's profile name is used +Exception: InvalidNumber + setContactName(number,name<>) -> <>:: * number : Phone number * name : Name to be set in contacts (in local storage with signal-cli) +Exception: InvalidNumber + setExpirationTimer(number,expiration) -> <>:: * number : Phone number * expiration : Expiration time for messages sent to this number (in seconds). Set to 0 to disable. +Exception: InvalidNumber + getGroupIds() -> groupList:: getGroupIds(dummy) -> groupList:: dummy : any string (ignored by method; forces output to be identical with getBase64GroupIds) @@ -242,18 +248,22 @@ base64GroupList : Array of strings representing the internal group identifiers i All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked() +Exceptions: None + getBase64GroupIds() -> groupList:: groupList : Array of strings representing the internal group identifiers in Base64 format All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked() +Exceptions: None + getGroupName(groupId) -> groupName:: getGroupName(base64GroupId) -> groupName:: groupName : The display name of the group groupId : Byte array representing the internal group identifier base64GroupId : String representing the internal group identifier in Base64 format -Exceptions: None, if the group name is not found an empty string is returned +Exceptions: None; if the group name is not found an empty string is returned getGroupMembers(groupId) -> members:: getGroupMembers(base64GroupId) -> members:: @@ -261,24 +271,28 @@ members : String array with the phone numbers of all active members of a g groupId : Byte array representing the internal group identifier base64GroupId : String representing the internal group identifier in Base64 format -Exceptions: None, if the group name is not found an empty array is returned +Exceptions: None; if the group name is not found an empty array is returned listNumbers() -> numbers:: numbers : String array of all known numbers This is a concatenated list of all defined contacts as well of profiles known (e.g. peer group members or sender of received messages) +Exceptions: None + getContactNumber(name) -> numbers:: * numbers : Array of phone numbers * name : Contact or profile name ("firstname lastname") Searches contacts and known profiles for a given name and returns the list of all known numbers. May result in e.g. two entries if a contact and profile name is set. +Exception: Failure + isContactBlocked(number) -> state:: * number : Phone number * state : true=blocked, false=not blocked -Exceptions: None, for unknown numbers false is returned +Exception: InvalidNumber for an incorrectly formatted phone number. For unknown numbers, false is returned, but no exception is raised. isGroupBlocked(groupId) -> state:: isGroupBlocked(base64GroupId) -> state:: @@ -286,11 +300,13 @@ isGroupBlocked(base64GroupId) -> state:: * base64GroupId : String representing the internal group identifier in Base64 format * state : true=blocked, false=not blocked -Exceptions: None, for unknown groups false is returned +Exceptions: None; for unknown groups false is returned version() -> version:: * version : Version string of signal-cli +Exceptions: None + isRegistered(number) -> result:: isRegistered(numbers) -> results:: * number : Phone number @@ -298,6 +314,8 @@ isRegistered(numbers) -> results:: * result : true=number is registered, false=number is not registered * results : Boolean array of results +Exception: InvalidNumber for an incorrectly formatted phone number. For unknown numbers, false is returned, but no exception is raised. + listIdentity(number) -> results:: * number : Phone number * results : Array of elements, each consisting of four strings: trust_level, date_added, fingerprint, safety_number @@ -309,7 +327,7 @@ listIdentity(number) -> results:: getObjectPath() -> objectPath:: * objectPath : The DBus object path associated with this connection -updateAccount() -> <> +updateAccount() -> <>:: Updates the account attributes on the Signal server. @@ -351,17 +369,22 @@ removePin() -> <>:: Removes registration PIN protection. -verify(number, verificationCode) -> <> +verify(number, verificationCode) -> <>:: * number : Phone number * verificationCode : Code received from Signal after successful registration request Command fails if PIN was set after previous registration; use verifyWithPin instead. -verifyWithPin(number, verificationCode, pin) -> <> +verifyWithPin(number, verificationCode, pin) -> <>:: * number : Phone number * verificationCode : Code received from Signal after successful registration request * pin : PIN you set with setPin command after verifying previous registration +uploadStickerPack(stickerPackPath) -> <>:: +* stickerPackPath : Path to the sticker pack + +Exception: Failure + == Signals SyncMessageReceived (timestamp, sender, destination, groupId, message, attachments):: diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 3f20648e..27115bd8 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -133,7 +133,7 @@ public interface Signal extends DBusInterface { void setGroupBlocked(String base64GroupId, boolean blocked) throws Error.GroupNotFound; - List getGroupMembers(String dummy); + List getGroupMembers(String base64GroupId); long sendGroupMessage( String message, List attachments, String base64GroupId @@ -147,9 +147,9 @@ public interface Signal extends DBusInterface { String base64GroupId, String name, List members, String avatar ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound; - boolean isRegistered(String number); + boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber; - List isRegistered(List numbers); + List isRegistered(List numbers) throws Error.Failure, Error.InvalidNumber; void updateProfile( String givenName, String familyName, String about, String aboutEmoji, String avatarPath, boolean removeAvatar @@ -209,6 +209,8 @@ public interface Signal extends DBusInterface { void joinGroup(final String groupLink) throws Error.Failure; + void uploadStickerPack(String stickerPackPath) throws Error.Failure; + class MessageReceived extends DBusSignal { private final long timestamp; diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 91e6e47c..01c64f28 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -11,6 +11,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.manager.Manager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; import java.util.HashSet; @@ -34,12 +35,14 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { @Override public void handleCommand(final Namespace ns, final Manager m) throws CommandException { // Get a map of registration statuses - Map registered; + Map registered = null; try { registered = m.areUsersRegistered(new HashSet<>(ns.getList("number"))); } catch (IOException e) { logger.debug("Failed to check registered users", e); throw new IOErrorException("Unable to check if users are registered"); + } catch (InvalidNumberException e) { + logger.debug("Invalid number format", e); } // Output diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 177ffcc6..99bd7ed2 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -19,6 +19,7 @@ import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.AvatarStore; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotMasterDeviceException; +import org.asamk.signal.manager.StickerPackInvalidException; import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.groups.GroupId; @@ -753,6 +754,9 @@ public class DbusSignalImpl implements Signal { @Override public boolean isRegistered(String number) { + if (number.isEmpty()) { + return false; + } try { Map registered; List numbers = new ArrayList(); @@ -761,14 +765,19 @@ public class DbusSignalImpl implements Signal { return registered.get(number); } catch (IOException e) { throw new Error.Failure(e.getMessage()); + } catch (InvalidNumberException e) { + throw new Error.InvalidNumber(e.getMessage()); } } @Override public List isRegistered(List numbers) { + List results = new ArrayList (); + if (numbers.isEmpty()) { + return results; + } try { Map registered; - List results = new ArrayList (); registered = m.areUsersRegistered(new HashSet(numbers)); for (String number : numbers) { results.add(registered.get(number)); @@ -776,6 +785,8 @@ public class DbusSignalImpl implements Signal { return results; } catch (IOException e) { throw new Error.Failure(e.getMessage()); + } catch (InvalidNumberException e) { + throw new Error.InvalidNumber(e.getMessage()); } } @@ -1014,4 +1025,17 @@ public class DbusSignalImpl implements Signal { return group.isMember(m.getSelfRecipientId()); } } + + @Override + public void uploadStickerPack(String stickerPackPath) { + File path = new File(stickerPackPath); + + try { + var url = m.uploadStickerPack(path); + } catch (IOException e) { + throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage()); + } catch (StickerPackInvalidException e) { + throw new Error.Failure("Invalid sticker pack: " + e.getMessage()); + } + } }