diff --git a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java index 325ccea65..ab3f739a3 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java @@ -1,6 +1,7 @@ package im.conversations.android.xmpp.manager; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.common.base.Strings; @@ -25,8 +26,10 @@ import im.conversations.android.xmpp.model.disco.items.Item; import im.conversations.android.xmpp.model.disco.items.ItemsQuery; import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.version.Version; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import org.slf4j.Logger; @@ -34,10 +37,8 @@ import org.slf4j.LoggerFactory; public class DiscoManager extends AbstractManager { - private static final Logger LOGGER = LoggerFactory.getLogger(DiscoManager.class); - public static final String CAPABILITY_NODE = "http://conversations.im"; - + private static final Logger LOGGER = LoggerFactory.getLogger(DiscoManager.class); private static final Collection FEATURES_BASE = Arrays.asList( Namespace.JINGLE, @@ -55,7 +56,6 @@ public class DiscoManager extends AbstractManager { Namespace.ENTITY_CAPABILITIES_2, Namespace.DISCO_INFO, Namespace.PING, - Namespace.VERSION, Namespace.CHAT_STATES, Namespace.LAST_MESSAGE_CORRECTION, Namespace.DELIVERY_RECEIPTS); @@ -69,6 +69,9 @@ public class DiscoManager extends AbstractManager { Namespace.JINGLE_APPS_DTLS, Namespace.JINGLE_MESSAGE); + private static final Collection FEATURES_IMPACTING_PRIVACY = + Collections.singleton(Namespace.VERSION); + private static final Collection FEATURES_NOTIFY = Arrays.asList(Namespace.NICK, Namespace.AVATAR_METADATA, Namespace.BOOKMARKS2); @@ -76,6 +79,38 @@ public class DiscoManager extends AbstractManager { super(context, connection); } + public static EntityCapabilities.Hash buildHashFromNode(final String node) { + final var capsPrefix = CAPABILITY_NODE + "#"; + final var caps2Prefix = Namespace.ENTITY_CAPABILITIES_2 + "#"; + if (node.startsWith(capsPrefix)) { + final String hash = node.substring(capsPrefix.length()); + if (Strings.isNullOrEmpty(hash)) { + return null; + } + if (BaseEncoding.base64().canDecode(hash)) { + return EntityCapabilities.EntityCapsHash.of(hash); + } + } else if (node.startsWith(caps2Prefix)) { + final String caps = node.substring(caps2Prefix.length()); + if (Strings.isNullOrEmpty(caps)) { + return null; + } + final int separator = caps.lastIndexOf('.'); + if (separator < 0) { + return null; + } + final Hash.Algorithm algorithm = Hash.Algorithm.tryParse(caps.substring(0, separator)); + final String hash = caps.substring(separator + 1); + if (algorithm == null || Strings.isNullOrEmpty(hash)) { + return null; + } + if (BaseEncoding.base64().canDecode(hash)) { + return EntityCapabilities2.EntityCaps2Hash.of(algorithm, hash); + } + } + return null; + } + public ListenableFuture info(final Entity entity) { return info(entity, null); } @@ -210,30 +245,29 @@ public class DiscoManager extends AbstractManager { } public ServiceDescription getServiceDescription() { - return getServiceDescription(false); + return getServiceDescription(isPrivacyModeEnabled()); } private ServiceDescription getServiceDescription(final boolean privacyMode) { - final ImmutableList.Builder stringFeatureBuilder = ImmutableList.builder(); - stringFeatureBuilder.addAll(FEATURES_BASE); - stringFeatureBuilder.addAll( + final ImmutableList.Builder builder = ImmutableList.builder(); + final List features; + builder.addAll(FEATURES_BASE); + builder.addAll( Collections2.transform(FEATURES_NOTIFY, fn -> String.format("%s+notify", fn))); - if (!privacyMode) { - stringFeatureBuilder.addAll(FEATURES_AV_CALLS); + if (privacyMode) { + features = builder.build(); + } else { + features = builder.addAll(FEATURES_AV_CALLS).addAll(FEATURES_IMPACTING_PRIVACY).build(); } return new ServiceDescription( - stringFeatureBuilder.build(), - new ServiceDescription.Identity(getIdentityName(), "client", getIdentityType())); + features, + new ServiceDescription.Identity(BuildConfig.APP_NAME, "client", getIdentityType())); } String getIdentityVersion() { return BuildConfig.VERSION_NAME; } - String getIdentityName() { - return BuildConfig.APP_NAME; - } - String getIdentityType() { if ("chromium".equals(android.os.Build.BRAND)) { return "pc"; @@ -270,35 +304,19 @@ public class DiscoManager extends AbstractManager { connection.sendResultFor(request, infoQuery); } - public static EntityCapabilities.Hash buildHashFromNode(final String node) { - final var capsPrefix = CAPABILITY_NODE + "#"; - final var caps2Prefix = Namespace.ENTITY_CAPABILITIES_2 + "#"; - if (node.startsWith(capsPrefix)) { - final String hash = node.substring(capsPrefix.length()); - if (Strings.isNullOrEmpty(hash)) { - return null; - } - if (BaseEncoding.base64().canDecode(hash)) { - return EntityCapabilities.EntityCapsHash.of(hash); - } - } else if (node.startsWith(caps2Prefix)) { - final String caps = node.substring(caps2Prefix.length()); - if (Strings.isNullOrEmpty(caps)) { - return null; - } - final int separator = caps.lastIndexOf('.'); - if (separator < 0) { - return null; - } - final Hash.Algorithm algorithm = Hash.Algorithm.tryParse(caps.substring(0, separator)); - final String hash = caps.substring(separator + 1); - if (algorithm == null || Strings.isNullOrEmpty(hash)) { - return null; - } - if (BaseEncoding.base64().canDecode(hash)) { - return EntityCapabilities2.EntityCaps2Hash.of(algorithm, hash); - } + public void handleVersion(final Iq request) { + if (isPrivacyModeEnabled()) { + connection.sendErrorFor(request, new Condition.ServiceUnavailable()); + } else { + final var version = new Version(); + version.setSoftwareName(BuildConfig.APP_NAME); + version.setVersion(BuildConfig.VERSION_NAME); + version.setOs(String.format("Android %s", Build.VERSION.RELEASE)); + connection.sendResultFor(request, version); } - return null; + } + + private boolean isPrivacyModeEnabled() { + return false; } } diff --git a/src/main/java/im/conversations/android/xmpp/model/version/Version.java b/src/main/java/im/conversations/android/xmpp/model/version/Version.java new file mode 100644 index 000000000..7644517fa --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/version/Version.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.version; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "query", namespace = Namespace.VERSION) +public class Version extends Extension { + + public Version() { + super(Version.class); + } + + public void setSoftwareName(final String name) { + this.addChild("name").setContent(name); + } + + public void setVersion(final String version) { + this.addChild("version").setContent(version); + } + + public void setOs(final String os) { + this.addChild("os").setContent(os); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java index 88680bb68..d8439a1dd 100644 --- a/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java +++ b/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java @@ -13,6 +13,7 @@ import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.ping.Ping; import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.version.Version; import java.util.Arrays; import java.util.function.Consumer; import org.slf4j.Logger; @@ -62,6 +63,11 @@ public class IqProcessor extends XmppConnection.Delegate implements Consumer return; } + if (type == Iq.Type.GET && packet.hasExtension(Version.class)) { + getManager(DiscoManager.class).handleVersion(packet); + return; + } + final var extensionIds = packet.getExtensionIds(); LOGGER.info("Could not handle {}. Sending feature-not-implemented", extensionIds); connection.sendErrorFor(packet, new Condition.FeatureNotImplemented());