diff --git a/build.gradle b/build.gradle index 7bbae4c1d..7e14f24d7 100644 --- a/build.gradle +++ b/build.gradle @@ -191,7 +191,10 @@ gradle.taskGraph.whenReady { taskGraph -> if (signingRequired && taskGraph.allTasks.any { it instanceof Sign }) { // Use Java 6's console to read from the console (no good for a CI environment) - Console console = System.console() + def console = System.console() + if (console == null) { + throw new Exception("Could not obtain system console (Console is 'null'). Did you build with gradle daemon? Try the same Gradle command with \"--no-daemon\".") + } console.printf '\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n' def password = console.readPassword('GnuPG Private Key Password: ') @@ -245,11 +248,18 @@ task distributionZip(type: Zip, dependsOn: [javadocAll, prepareReleasedocs, mark task maybeCheckForSnapshotDependencies { // Don't check for Snapshot dependencies if this is a snapshot. - if (isSnapshot) return - allprojects { project -> - project.configurations.runtime.each { - if (it.toString().contains("-SNAPSHOT")) - throw new Exception("Release build contains snapshot dependencies: " + it) + onlyIf { isReleaseVersion } + // Run in the execution phase, not in configuration phase, as the + // 'each' forces the runtime configuration to be resovled, which + // causes "Cannot change dependencies of configuration after it + // has been included in dependency resolution." errors. + // See https://discuss.gradle.org/t/23153 + doLast { + allprojects { project -> + project.configurations.runtime.each { + if (it.toString().contains("-SNAPSHOT")) + throw new Exception("Release build contains snapshot dependencies: " + it) + } } } } @@ -402,6 +412,18 @@ configure(integrationTestProjects + project(':smack-repl')) { project(':smack-omemo').clirr.enabled = false project(':smack-omemo-signal').clirr.enabled = false +configure( + [ ':smack-omemo', + ':smack-omemo-signal', + ':smack-omemo-signal-integration-test', + ].collect{ project(it) }) { + uploadArchives { + // Only enable uploadArchives for the smack-omemo* projects + // for snapshots. Not yet for releases. + enabled = isSnapshot + } +} + subprojects*.jar { manifest { from sharedManifest diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 29036cc9f..6b8fca9d9 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -1,4 +1,4 @@ -smackSmack Extensions User Manual +Smack Extensions User Manual ============================ The XMPP protocol includes a base protocol and many optional extensions @@ -95,7 +95,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. | | HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. | | [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. | -| [OMEMO End Encryption (omemo.md) | [XEP-0384](http://xmpp.org/extensions/xep-0384.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | +| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | | Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers | diff --git a/resources/releasedocs/changelog.html b/resources/releasedocs/changelog.html index b8909c036..7d4dbc1d9 100644 --- a/resources/releasedocs/changelog.html +++ b/resources/releasedocs/changelog.html @@ -141,6 +141,57 @@ hr {
+

4.2.1 -- 2017-08-14

+ +

Bug +

+ + +

New Feature +

+ + +

Improvement +

+ +

4.2.0 -- 2017-03-10

Sub-task diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 484e07873..8814179db 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -255,7 +255,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { private final ExecutorService cachedExecutorService = Executors.newCachedThreadPool( // @formatter:off // CHECKSTYLE:OFF - new SmackExecutorThreadFactory( // threadFactory + new SmackExecutorThreadFactory( this, "Cached Executor" ) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/StanzaListener.java b/smack-core/src/main/java/org/jivesoftware/smack/StanzaListener.java index 992639a95..fb9b1b283 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/StanzaListener.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/StanzaListener.java @@ -41,7 +41,7 @@ public interface StanzaListener { /** * Process the next stanza(/packet) sent to this stanza(/packet) listener. *

- * A single thread is responsible for invoking all listeners, so + * If this listener is synchronous, then a single thread is responsible for invoking all listeners, so * it's very important that implementations of this method not block * for any extended period of time. *

diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java index 9d7f263a1..66a6d018a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java @@ -75,6 +75,22 @@ public class HostAddress { setException(e); } + public String getHost() { + if (fqdn != null) { + return fqdn; + } + + // In this case, the HostAddress(int, InetAddress) constructor must been used. We have no FQDN. And + // inetAddresses.size() must be exactly one. + assert inetAddresses.size() == 1; + return inetAddresses.get(0).getHostAddress(); + } + + /** + * Return the fully qualified domain name. This may return null in case there host address is only numeric, i.e. an IP address. + * + * @return the fully qualified domain name or null + */ public String getFQDN() { return fqdn; } @@ -109,7 +125,7 @@ public class HostAddress { @Override public String toString() { - return fqdn + ":" + port; + return getHost() + ":" + port; } @Override @@ -123,7 +139,7 @@ public class HostAddress { final HostAddress address = (HostAddress) o; - if (!fqdn.equals(address.fqdn)) { + if (!getHost().equals(address.getHost())) { return false; } return port == address.port; @@ -132,7 +148,7 @@ public class HostAddress { @Override public int hashCode() { int result = 1; - result = 37 * result + fqdn.hashCode(); + result = 37 * result + getHost().hashCode(); return result * 37 + port; } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java index 882e93054..961fc27e1 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java @@ -32,9 +32,9 @@ public class SlotRequest extends IQ { public static final String ELEMENT = "request"; public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE; - private final String filename; - private final long size; - private final String contentType; + protected final String filename; + protected final long size; + protected final String contentType; public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size) { this(uploadServiceAddress, filename, size, null); @@ -82,10 +82,10 @@ public class SlotRequest extends IQ { @Override protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { - xml.rightAngleBracket(); - xml.element("filename", filename); - xml.element("size", String.valueOf(size)); - xml.optElement("content-type", contentType); + xml.attribute("filename", filename); + xml.attribute("size", String.valueOf(size)); + xml.optAttribute("content-type", contentType); + xml.setEmptyElement(); return xml; } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest_V0_2.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest_V0_2.java index 4a7b02244..719371f52 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest_V0_2.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest_V0_2.java @@ -40,4 +40,13 @@ public class SlotRequest_V0_2 extends SlotRequest { public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) { super(uploadServiceAddress, filename, size, contentType, NAMESPACE); } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.rightAngleBracket(); + xml.element("filename", filename); + xml.element("size", String.valueOf(size)); + xml.optElement("content-type", contentType); + return xml; + } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index fb9c2535c..9d800f73c 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -50,6 +50,7 @@ import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.Jid; @@ -103,9 +104,12 @@ public final class MamManager extends Manager { private final Jid archiveAddress; + private final ServiceDiscoveryManager serviceDiscoveryManager; + private MamManager(XMPPConnection connection, Jid archiveAddress) { super(connection); this.archiveAddress = archiveAddress; + serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); } /** @@ -586,6 +590,23 @@ public final class MamManager extends Manager { } } + /** + * Check if MAM is supported for the XMPP connection managed by this MamManager. + * + * @return true if MAM is supported for the XMPP connection, falseotherwhise. + * + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + * @since 4.2.1 + * @see XEP-0313 § 7. Determining support + */ + public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + BareJid myBareJid = connection().getUser().asBareJid(); + return serviceDiscoveryManager.supportsFeature(myBareJid, MamElements.NAMESPACE); + } + /** * Returns true if Message Archive Management is supported by the server. * @@ -594,7 +615,10 @@ public final class MamManager extends Manager { * @throws XMPPErrorException * @throws NoResponseException * @throws InterruptedException + * @depreacted use {@link #isSupported()} instead. */ + @Deprecated + // TODO Remove in Smack 4.3 public boolean isSupportedByServer() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(MamElements.NAMESPACE); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java index 153dec1cc..525322357 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java @@ -16,49 +16,52 @@ */ package org.jivesoftware.smackx.httpfileupload; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import java.io.IOException; + import org.jivesoftware.smackx.httpfileupload.element.SlotRequest; import org.junit.Assert; import org.junit.Test; import org.jxmpp.jid.JidTestUtil; -import org.jxmpp.stringprep.XmppStringprepException; - +import org.xml.sax.SAXException; public class SlotRequestCreateTest { String testRequest - = "" - + "my_juliet.png" - + "23456" - + "image/jpeg" - + ""; + = ""; String testRequestWithoutContentType - = "" - + "my_romeo.png" - + "52523" - + ""; + = ""; @Test - public void checkSlotRequestCreation() throws XmppStringprepException { + public void checkSlotRequestCreation() throws SAXException, IOException { SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 23456, "image/jpeg"); Assert.assertEquals("my_juliet.png", slotRequest.getFilename()); Assert.assertEquals(23456, slotRequest.getSize()); Assert.assertEquals("image/jpeg", slotRequest.getContentType()); - Assert.assertEquals(testRequest, slotRequest.getChildElementXML().toString()); + assertXMLEqual(testRequest, slotRequest.getChildElementXML().toString()); } @Test - public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException { + public void checkSlotRequestCreationWithoutContentType() throws SAXException, IOException { SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_romeo.png", 52523); Assert.assertEquals("my_romeo.png", slotRequest.getFilename()); Assert.assertEquals(52523, slotRequest.getSize()); Assert.assertEquals(null, slotRequest.getContentType()); - Assert.assertEquals(testRequestWithoutContentType, slotRequest.getChildElementXML().toString()); + assertXMLEqual(testRequestWithoutContentType, slotRequest.getChildElementXML().toString()); } @Test(expected = IllegalArgumentException.class) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bob/BoBManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bob/BoBManager.java index 0c853a864..f7c7d261f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bob/BoBManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bob/BoBManager.java @@ -159,7 +159,7 @@ public final class BoBManager extends Manager { public BoBInfo addBoB(BoBData bobData) { // We only support SHA-1 for now. - BoBHash bobHash = new BoBHash("sha1", SHA1.hex(bobData.getContent())); + BoBHash bobHash = new BoBHash(SHA1.hex(bobData.getContent()), "sha1"); Set bobHashes = Collections.singleton(bobHash); bobHashes = Collections.unmodifiableSet(bobHashes); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 11ff6ac83..5d95069ce 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -1011,7 +1011,7 @@ public class MultiUserChat { * * @param presenceInterceptor the stanza(/packet) interceptor to remove. */ - public void removePresenceInterceptor(StanzaListener presenceInterceptor) { + public void removePresenceInterceptor(PresenceListener presenceInterceptor) { presenceInterceptors.remove(presenceInterceptor); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PEPManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PEPManager.java index ca5bb629d..ca0a582b2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PEPManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PEPManager.java @@ -39,6 +39,7 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.pubsub.EventElement; import org.jivesoftware.smackx.pubsub.Item; import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import org.jivesoftware.smackx.pubsub.PubSubFeature; import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter; @@ -137,9 +138,10 @@ public final class PEPManager extends Manager { * @throws InterruptedException * @throws XMPPErrorException * @throws NoResponseException + * @throws NotAPubSubNodeException */ public void publish(Item item, String node) throws NotConnectedException, InterruptedException, - NoResponseException, XMPPErrorException { + NoResponseException, XMPPErrorException, NotAPubSubNodeException { XMPPConnection connection = connection(); PubSubManager pubSubManager = PubSubManager.getInstance(connection, connection.getUser().asEntityBareJid()); LeafNode pubSubNode = pubSubManager.getNode(node); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubAssertionError.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubAssertionError.java deleted file mode 100644 index 50bb26383..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubAssertionError.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - * Copyright 2017 Florian Schmaus - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jivesoftware.smackx.pubsub; - -import org.jxmpp.jid.BareJid; - -public abstract class PubSubAssertionError extends AssertionError { - - /** - * - */ - private static final long serialVersionUID = 1L; - - protected PubSubAssertionError(String message) { - super(message); - } - - public static class DiscoInfoNodeAssertionError extends PubSubAssertionError { - - /** - * - */ - private static final long serialVersionUID = 1L; - - DiscoInfoNodeAssertionError(BareJid pubSubService, String nodeId) { - super("PubSub service '" + pubSubService + "' returned disco info result for node '" + nodeId - + "', but it did not contain an Identity of type 'leaf' or 'collection' (and category 'pubsub'), which is not allowed according to XEP-60 5.3."); - } - - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubException.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubException.java index cb65d8278..5deac4931 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubException.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubException.java @@ -18,6 +18,8 @@ package org.jivesoftware.smackx.pubsub; import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; + import org.jxmpp.jid.BareJid; public abstract class PubSubException extends SmackException { @@ -27,6 +29,16 @@ public abstract class PubSubException extends SmackException { */ private static final long serialVersionUID = 1L; + private final String nodeId; + + protected PubSubException(String nodeId) { + this.nodeId = nodeId; + } + + public String getNodeId() { + return nodeId; + } + public static class NotALeafNodeException extends PubSubException { /** @@ -34,21 +46,35 @@ public abstract class PubSubException extends SmackException { */ private static final long serialVersionUID = 1L; - private final String nodeId; private final BareJid pubSubService; NotALeafNodeException(String nodeId, BareJid pubSubService) { - this.nodeId = nodeId; + super(nodeId); this.pubSubService = pubSubService; } - public String getNodeId() { - return nodeId; - } - public BareJid getPubSubService() { return pubSubService; } } + + public static class NotAPubSubNodeException extends PubSubException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final DiscoverInfo discoverInfo; + + NotAPubSubNodeException(String nodeId, DiscoverInfo discoverInfo) { + super(nodeId); + this.discoverInfo = discoverInfo; + } + + public DiscoverInfo getDiscoverInfo() { + return discoverInfo; + } + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java index 1abf6cb4f..73764cec4 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java @@ -42,6 +42,7 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.util.NodeUtils; @@ -229,8 +230,9 @@ public final class PubSubManager extends Manager { * @throws NoResponseException if there was no response from the server. * @throws NotConnectedException * @throws InterruptedException + * @throws NotAPubSubNodeException */ - public T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException + public T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotAPubSubNodeException { Node node = nodeMap.get(id); @@ -249,10 +251,7 @@ public final class PubSubManager extends Manager { node = new CollectionNode(this, id); } else { - // XEP-60 5.3 states that - // "The 'disco#info' result MUST include an identity with a category of 'pubsub' and a type of either 'leaf' or 'collection'." - // If this is not the case, then we are dealing with an PubSub implementation that doesn't follow the specification. - throw new PubSubAssertionError.DiscoInfoNodeAssertionError(pubSubService, id); + throw new PubSubException.NotAPubSubNodeException(id, infoReply); } nodeMap.put(id, node); } @@ -278,6 +277,9 @@ public final class PubSubManager extends Manager { try { return getNode(id); } + catch (NotAPubSubNodeException e) { + return createNode(id); + } catch (XMPPErrorException e1) { if (e1.getXMPPError().getCondition() == Condition.item_not_found) { try { @@ -286,7 +288,13 @@ public final class PubSubManager extends Manager { catch (XMPPErrorException e2) { if (e2.getXMPPError().getCondition() == Condition.conflict) { // The node was created in the meantime, re-try getNode(). Note that this case should be rare. - return getNode(id); + try { + return getNode(id); + } + catch (NotAPubSubNodeException e) { + // Should not happen + throw new IllegalStateException(e); + } } throw e2; } @@ -313,10 +321,11 @@ public final class PubSubManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws XMPPErrorException + * @throws NotAPubSubNodeException * @since 4.2.1 */ public LeafNode getLeafNode(String id) throws NotALeafNodeException, NoResponseException, NotConnectedException, - InterruptedException, XMPPErrorException { + InterruptedException, XMPPErrorException, NotAPubSubNodeException { Node node; try { node = getNode(id); diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java index 9bfae16bc..dfd35a636 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java @@ -1676,8 +1676,8 @@ public final class Roster extends Manager { final XMPPConnection connection = connection(); RosterPacket rosterPacket = (RosterPacket) iqRequest; - EntityFullJid localAddress = connection.getUser(); - if (localAddress == null) { + EntityFullJid ourFullJid = connection.getUser(); + if (ourFullJid == null) { LOGGER.warning("Ignoring roster push " + iqRequest + " while " + connection + " has no bound resource. This may be a server bug."); return null; @@ -1685,12 +1685,23 @@ public final class Roster extends Manager { // Roster push (RFC 6121, 2.1.6) // A roster push with a non-empty from not matching our address MUST be ignored - EntityBareJid jid = localAddress.asEntityBareJid(); + EntityBareJid ourBareJid = ourFullJid.asEntityBareJid(); Jid from = rosterPacket.getFrom(); - if (from != null && !from.equals(jid)) { - LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from - + "'"); - return IQ.createErrorResponse(iqRequest, Condition.service_unavailable); + if (from != null) { + if (from.equals(ourFullJid)) { + // Since RFC 6121 roster pushes are no longer allowed to + // origin from the full JID as it was the case with RFC + // 3921. Log a warning an continue processing the push. + // See also SMACK-773. + LOGGER.warning( + "Received roster push from full JID. This behavior is since RFC 6121 not longer standard compliant. " + + "Please ask your server vendor to fix this and comply to RFC 6121 § 2.1.6. IQ roster push stanza: " + + iqRequest); + } else if (!from.equals(ourBareJid)) { + LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + ourBareJid + "' from='" + + from + "'"); + return IQ.createErrorResponse(iqRequest, Condition.service_unavailable); + } } // A roster push must contain exactly one entry diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java index 665194be9..587885229 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java @@ -46,7 +46,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest { mamManagerConTwo = MamManager.getInstanceFor(conTwo); - if (!mamManagerConTwo.isSupportedByServer()) { + if (!mamManagerConTwo.isSupported()) { throw new TestNotPossibleException("Message Archive Management (XEP-0313) is not supported by the server."); } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java index 155b9ca3a..e019dce82 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java @@ -30,6 +30,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.util.OmemoConstants; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; @@ -52,9 +53,10 @@ public class OmemoInitializationTest extends AbstractOmemoIntegrationTest { /** * Tests, if the initialization is done properly. + * @throws NotAPubSubNodeException */ @SmackIntegrationTest - public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException { + public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException, NotAPubSubNodeException { //test keys. setUpOmemoManager(alice); assertNotNull("IdentityKey must not be null after initialization.", store.loadOmemoIdentityKeyPair(alice)); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java index 08b9b356b..78895cf08 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java @@ -34,8 +34,8 @@ import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionExcep import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; import org.jivesoftware.smackx.omemo.util.OmemoConstants; -import org.jivesoftware.smackx.pubsub.PubSubAssertionError; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import org.jivesoftware.smackx.pubsub.PubSubManager; /** @@ -70,26 +70,26 @@ final class OmemoIntegrationTestHelper { for (int id : deviceList.getAllDevices()) { try { pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems(); - } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) { + } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) { //Silent } try { pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)); - } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) { + } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { //Silent } } try { pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems(); - } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) { + } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) { //Silent } try { pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST); - } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) { + } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { //Silent } } @@ -147,7 +147,7 @@ final class OmemoIntegrationTestHelper { } - static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException { + static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, NotAPubSubNodeException { omemoManager.initialize(); OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice()); assertNotNull("Bundle must not be null.", bundle); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java index 690c19b81..bcdc29ed3 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java @@ -40,6 +40,7 @@ import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import junit.framework.TestCase; import org.igniterealtime.smack.inttest.SmackIntegrationTest; @@ -85,9 +86,15 @@ public class OmemoMessageSendingTest extends AbstractOmemoIntegrationTest { * @throws UndecidedOmemoIdentityException * @throws NoSuchAlgorithmException * @throws CryptoFailedException + * @throws NotAPubSubNodeException */ @SmackIntegrationTest - public void messageSendingTest() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException { + public void messageSendingTest() + throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, + SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, + CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, + CryptoFailedException, PubSubException.NotAPubSubNodeException { final String alicesSecret = "Hey Bob! I love you!"; final String bobsSecret = "I love you too, Alice."; //aww <3 diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java index dbebf20ce..d17582172 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java @@ -19,7 +19,6 @@ package org.jivesoftware.smackx.omemo; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.BODY_OMEMO_HINT; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL; -import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -32,8 +31,8 @@ import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import org.jivesoftware.smack.AbstractConnectionListener; import org.jivesoftware.smack.AbstractXMPPConnection; -import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; @@ -41,6 +40,7 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.carbons.CarbonManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; @@ -78,7 +78,6 @@ import org.jxmpp.jid.BareJid; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; -import org.jxmpp.jid.FullJid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; @@ -110,8 +109,29 @@ public final class OmemoManager extends Manager { */ private OmemoManager(XMPPConnection connection, int deviceId) { super(connection); - setConnectionListener(); + this.deviceId = deviceId; + + connection.addConnectionListener(new AbstractConnectionListener() { + @Override + public void authenticated(XMPPConnection connection, boolean resumed) { + if (resumed) { + return; + } + Async.go(new Runnable() { + @Override + public void run() { + try { + initialize(); + } catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: " + + e.getMessage()); + } + } + }); + } + }); + service = OmemoService.getInstance(); } @@ -163,13 +183,13 @@ public final class OmemoManager extends Manager { } } - int defaulDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user); - if (defaulDeviceId < 1) { - defaulDeviceId = randomDeviceId(); - OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaulDeviceId); + int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user); + if (defaultDeviceId < 1) { + defaultDeviceId = randomDeviceId(); + OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId); } - return getInstanceFor(connection, defaulDeviceId); + return getInstanceFor(connection, defaultDeviceId); } /** @@ -471,24 +491,6 @@ public final class OmemoManager extends Manager { .getActiveDevices().isEmpty(); } - /** - * Returns true, if the device resource has announced OMEMO support. - * Throws an IllegalArgumentException if the provided FullJid does not have a resource part. - * - * @param fullJid jid of a resource - * @return true if resource supports OMEMO - * @throws XMPPException.XMPPErrorException if - * @throws SmackException.NotConnectedException something - * @throws InterruptedException goes - * @throws SmackException.NoResponseException wrong - */ - public boolean resourceSupportsOmemo(FullJid fullJid) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { - if (fullJid.hasNoResource()) { - throw new IllegalArgumentException("Jid " + fullJid + " has no resource part."); - } - return ServiceDiscoveryManager.getInstanceFor(connection()).discoverInfo(fullJid).containsFeature(PEP_NODE_DEVICE_LIST_NOTIFY); - } - /** * Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite * for OMEMO encryption in MUC). @@ -641,54 +643,6 @@ public final class OmemoManager extends Manager { } } - private void setConnectionListener() { - connection().addConnectionListener(new ConnectionListener() { - @Override - public void connected(XMPPConnection connection) { - LOGGER.log(Level.INFO, "connected"); - } - - @Override - public void authenticated(XMPPConnection connection, boolean resumed) { - LOGGER.log(Level.INFO, "authenticated. Resumed: " + resumed); - if (resumed) { - return; - } - try { - initialize(); - } catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { - LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: " - + e.getMessage()); - } - } - - @Override - public void connectionClosed() { - - } - - @Override - public void connectionClosedOnError(Exception e) { - connectionClosed(); - } - - @Override - public void reconnectionSuccessful() { - - } - - @Override - public void reconnectingIn(int seconds) { - - } - - @Override - public void reconnectionFailed(Exception e) { - - } - }); - } - public static int randomDeviceId() { int i = new Random().nextInt(Integer.MAX_VALUE); diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java index 17332669b..093767166 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java @@ -39,7 +39,6 @@ import java.util.Random; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; - import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; @@ -51,6 +50,7 @@ import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener; import org.jivesoftware.smackx.carbons.CarbonManager; @@ -82,12 +82,13 @@ import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder; import org.jivesoftware.smackx.pep.PEPManager; import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PayloadItem; -import org.jivesoftware.smackx.pubsub.PubSubAssertionError; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; import org.jivesoftware.smackx.pubsub.PubSubManager; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.Jid; /** * This class contains OMEMO related logic and registers listeners etc. @@ -427,11 +428,11 @@ public abstract class OmemoService getUntrustedDevices() { + public HashSet getUndecidedDevices() { return this.devices; } /** - * Add all untrusted devices of another Exception to this Exceptions HashSet of untrusted devices. + * Add all undecided devices of another Exception to this Exceptions HashSet of undecided devices. * * @param other other Exception */ public void join(UndecidedOmemoIdentityException other) { - this.devices.addAll(other.getUntrustedDevices()); + this.devices.addAll(other.getUndecidedDevices()); } } diff --git a/smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoExceptionsTest.java b/smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoExceptionsTest.java index 2de3a1505..e78f31630 100644 --- a/smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoExceptionsTest.java +++ b/smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoExceptionsTest.java @@ -45,18 +45,18 @@ public class OmemoExceptionsTest { OmemoDevice mallory = new OmemoDevice(JidCreate.bareFrom("mallory@server.tld"), 9876); UndecidedOmemoIdentityException u = new UndecidedOmemoIdentityException(alice); - assertTrue(u.getUntrustedDevices().contains(alice)); - assertTrue(u.getUntrustedDevices().size() == 1); + assertTrue(u.getUndecidedDevices().contains(alice)); + assertTrue(u.getUndecidedDevices().size() == 1); UndecidedOmemoIdentityException v = new UndecidedOmemoIdentityException(bob); - v.getUntrustedDevices().add(mallory); - assertTrue(v.getUntrustedDevices().size() == 2); - assertTrue(v.getUntrustedDevices().contains(bob)); - assertTrue(v.getUntrustedDevices().contains(mallory)); + v.getUndecidedDevices().add(mallory); + assertTrue(v.getUndecidedDevices().size() == 2); + assertTrue(v.getUndecidedDevices().contains(bob)); + assertTrue(v.getUndecidedDevices().contains(mallory)); - u.getUntrustedDevices().add(bob); + u.getUndecidedDevices().add(bob); u.join(v); - assertTrue(u.getUntrustedDevices().size() == 3); + assertTrue(u.getUndecidedDevices().size() == 3); } @Test diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 9d598dd7e..7624ac05f 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -563,7 +563,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } for (HostAddress hostAddress : hostAddresses) { Iterator inetAddresses = null; - String host = hostAddress.getFQDN(); + String host = hostAddress.getHost(); int port = hostAddress.getPort(); if (proxyInfo == null) { inetAddresses = hostAddress.getInetAddresses().iterator(); @@ -737,7 +737,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } if (ks != null) { - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); try { if (pcb == null) { kmf.init(ks, null);