diff --git a/build.gradle b/build.gradle index b962c9105..9e4d6651f 100644 --- a/build.gradle +++ b/build.gradle @@ -122,8 +122,8 @@ allprojects { // See also: // - https://issues.apache.org/jira/browse/MNG-6232 // - https://issues.igniterealtime.org/browse/SMACK-858 - jxmppVersion = '0.7.0-alpha6' - miniDnsVersion = '0.4.0-alpha6' + jxmppVersion = '1.0.0' + miniDnsVersion = '1.0.0' smackMinAndroidSdk = 19 junitVersion = '5.6.2' commonsIoVersion = '2.6' diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 78a2bbc57..96501b39f 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -123,6 +123,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.6.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. | | [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. | | DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. | +| Stanza Content Encryption | [XEP-0420](https://xmpp.org/extensions/xep-0420.html) | 0.3.0 | End-to-end encryption of arbitrary extension elements. Smack provides elements and providers to be used by encryption mechanisms. | | Message Fastening | [XEP-0422](https://xmpp.org/extensions/xep-0422.html) | 0.1.1 | Mark payloads on a message to be logistically fastened to a previous message. | | Message Retraction | [XEP-0424](https://xmpp.org/extensions/xep-0424.html) | 0.2.0 | Mark messages as retracted. | | Fallback Indication | [XEP-0428](https://xmpp.org/extensions/xep-0428.html) | 0.1.0 | Declare body elements of a message as ignorable fallback for naive legacy clients. | 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 bbb104fe8..bd3d47554 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -514,6 +514,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { // Check if not already connected throwAlreadyConnectedExceptionIfAppropriate(); + // Notify connection listeners that we are trying to connect + callConnectionConnectingListener(); + // Reset the connection state initState(); closingStreamReceived = false; @@ -760,7 +763,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { user = response.getJid(); xmppServiceDomain = user.asDomainBareJid(); - Session.Feature sessionFeature = getFeature(Session.ELEMENT, Session.NAMESPACE); + Session.Feature sessionFeature = getFeature(Session.Feature.class); // Only bind the session if it's announced as stream feature by the server, is not optional and not disabled // For more information see http://tools.ietf.org/html/draft-cridland-xmpp-session-01 if (sessionFeature != null && !sessionFeature.isOptional()) { @@ -1680,6 +1683,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { } } + protected void callConnectionConnectingListener() { + for (ConnectionListener listener : connectionListeners) { + listener.connecting(this); + } + } + protected void callConnectionConnectedListener() { for (ConnectionListener listener : connectionListeners) { listener.connected(this); @@ -1904,14 +1913,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { @SuppressWarnings("unchecked") @Override - public F getFeature(String element, String namespace) { - QName qname = new QName(namespace, element); + public F getFeature(QName qname) { return (F) streamFeatures.get(qname); } @Override - public boolean hasFeature(String element, String namespace) { - return getFeature(element, namespace) != null; + public boolean hasFeature(QName qname) { + return streamFeatures.containsKey(qname); } protected void addStreamFeature(FullyQualifiedElement feature) { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java index 3e85191b9..304c8c75a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2020 Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,16 @@ package org.jivesoftware.smack; */ public interface ConnectionListener { + /** + * Notification that the connection is in the process of connecting. + * This method is called when {@link AbstractXMPPConnection#connect()} is executed. + * + * @param connection connection + * @since 4.4 + */ + default void connecting(XMPPConnection connection) { + } + /** * Notification that the connection has been successfully connected to the remote endpoint (e.g. the XMPP server). *

diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java index 6c1c31a5d..2e02b5080 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java @@ -356,7 +356,7 @@ public final class SASLAuthentication { } private List getServerMechanisms() { - Mechanisms mechanisms = connection.getFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE); + Mechanisms mechanisms = connection.getFeature(Mechanisms.class); if (mechanisms == null) { return Collections.emptyList(); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java index 842007f1e..1d3ca49d1 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java @@ -18,6 +18,8 @@ package org.jivesoftware.smack; import java.util.concurrent.TimeUnit; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; @@ -36,6 +38,7 @@ import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StanzaFactory; import org.jivesoftware.smack.util.Consumer; import org.jivesoftware.smack.util.Predicate; +import org.jivesoftware.smack.util.XmppElementUtil; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityFullJid; @@ -576,8 +579,39 @@ public interface XMPPConnection { * @param element TODO javadoc me please * @param namespace TODO javadoc me please * @return a stanza extensions of the feature or null + * @deprecated use {@link #getFeature(Class)} instead. */ - F getFeature(String element, String namespace); + // TODO: Remove in Smack 4.5. + @Deprecated + default F getFeature(String element, String namespace) { + QName qname = new QName(namespace, element); + return getFeature(qname); + } + + /** + * Get the feature stanza extensions for a given stream feature of the + * server, or null if the server doesn't support that feature. + * + * @param {@link ExtensionElement} type of the feature. + * @param qname the qualified name of the XML element of feature. + * @return a stanza extensions of the feature or null + * @since 4.4 + */ + F getFeature(QName qname); + + /** + * Get the feature stanza extensions for a given stream feature of the + * server, or null if the server doesn't support that feature. + * + * @param {@link ExtensionElement} type of the feature. + * @param featureClass the class of the feature. + * @return a stanza extensions of the feature or null + * @since 4.4 + */ + default F getFeature(Class featureClass) { + QName qname = XmppElementUtil.getQNameFor(featureClass); + return getFeature(qname); + } /** * Return true if the server supports the given stream feature. @@ -586,7 +620,18 @@ public interface XMPPConnection { * @param namespace TODO javadoc me please * @return true if the server supports the stream feature. */ - boolean hasFeature(String element, String namespace); + default boolean hasFeature(String element, String namespace) { + QName qname = new QName(namespace, element); + return hasFeature(qname); + } + + /** + * Return true if the server supports the given stream feature. + * + * @param qname the qualified name of the XML element of feature. + * @return true if the server supports the stream feature. + */ + boolean hasFeature(QName qname); /** * Send an IQ request asynchronously. The connection's default reply timeout will be used. diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compress.java b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compress.java index a3ba65245..3a197aab6 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compress.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compress.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2015 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.jivesoftware.smack.compress.packet; import java.util.Collections; import java.util.List; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -55,6 +57,7 @@ public class Compress implements Nonza { public static class Feature implements ExtensionElement { public static final String ELEMENT = "compression"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public final List methods; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java index ce0dddeea..79bd0a21d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java @@ -70,7 +70,7 @@ public class CompressionModule extends ModularXmppClientToServerConnectionModule return new StateTransitionResult.TransitionImpossibleReason("Stream compression disabled by connection configuration"); } - Compress.Feature compressFeature = connectionInternal.connection.getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE); + Compress.Feature compressFeature = connectionInternal.connection.getFeature(Compress.Feature.class); if (compressFeature == null) { return new StateTransitionResult.TransitionImpossibleReason("Stream compression not supported or enabled by service"); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Mechanisms.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Mechanisms.java index 4ef0bcd66..a981589ee 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Mechanisms.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Mechanisms.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,15 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.util.XmlStringBuilder; public class Mechanisms implements ExtensionElement { public static final String ELEMENT = "mechanisms"; public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public final List mechanisms = new LinkedList(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Session.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Session.java index a226b729d..48e0330c6 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Session.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Session.java @@ -17,6 +17,8 @@ package org.jivesoftware.smack.packet; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.util.XmlStringBuilder; /** @@ -44,6 +46,8 @@ public class Session extends SimpleIQ { public static class Feature implements ExtensionElement { + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + public static final String OPTIONAL_ELEMENT = "optional"; private final boolean optional; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StartTls.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StartTls.java index aebb1ad3f..070b287b7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StartTls.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StartTls.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2019 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ */ package org.jivesoftware.smack.packet; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.util.XmlStringBuilder; public class StartTls implements Nonza { @@ -24,6 +26,7 @@ public class StartTls implements Nonza { public static final String ELEMENT = "starttls"; public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final boolean required; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java index 6fadfd3a8..bf8a4a224 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2019 Florian Schmaus + * Copyright 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -227,7 +227,7 @@ public abstract class ScramMechanism extends SASLMechanism { authzidPortion = "a=" + authorizationId; } - String cbName = getChannelBindingName(); + String cbName = getGs2CbindFlag(); assert StringUtils.isNotEmpty(cbName); return cbName + ',' + authzidPortion + ","; @@ -244,7 +244,13 @@ public abstract class ScramMechanism extends SASLMechanism { return ByteUtils.concat(gs2Header, cbindData); } - protected String getChannelBindingName() { + /** + * Get the SCRAM GSS-API Channel Binding Flag value. + * + * @return the gs2-cbind-flag value. + * @see RFC 5802 § 6. + */ + protected String getGs2CbindFlag() { // Check if we are using TLS and if a "-PLUS" variant of this mechanism is enabled. Assuming that the "-PLUS" // variants always have precedence before the non-"-PLUS" variants this means that the server did not announce // the "-PLUS" variant, as otherwise we would have tried it. diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java index 0aa7eee3f..849eee55d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2019 Florian Schmaus + * Copyright 2016-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public abstract class ScramPlusMechanism extends ScramMechanism { } @Override - protected String getChannelBindingName() { + protected String getGs2CbindFlag() { return "p=tls-server-end-point"; } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java index 34ccb0d81..b78f62ab9 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java @@ -37,7 +37,7 @@ public class XmppElementUtil { } LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME."); } catch (NoSuchFieldException e) { - LOGGER.finer("The class " + fullyQualifiedElement + " has no static QNAME field. Consider adding one."); + LOGGER.finer("The " + fullyQualifiedElement + " has no static QNAME field. Consider adding one."); // Proceed to fallback strategy. } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) { throw new IllegalArgumentException(e); @@ -49,7 +49,7 @@ public class XmppElementUtil { namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null); } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { - throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); + throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); } return new QName(namespace, element); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java index 1a448b516..d5f6935da 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java @@ -16,6 +16,8 @@ */ package org.jivesoftware.smackx.sid.element; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.MessageBuilder; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -25,6 +27,8 @@ import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager; public class OriginIdElement extends StableAndUniqueIdElement { public static final String ELEMENT = "origin-id"; + public static final String NAMESPACE = StableUniqueStanzaIdManager.NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public OriginIdElement() { super(); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java new file mode 100644 index 000000000..eb4db3c17 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java @@ -0,0 +1,29 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.Element; + +/** + * Interface that marks elements that may be used as affix elements inside a {@link ContentElement}. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + */ +public interface AffixElement extends Element { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java new file mode 100644 index 000000000..7ff838ae3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.ExtensionElement; + +/** + * Affix element that is identified by element name and namespace. + * You should extend this interface with your custom affix extension elements + * and also provide a {@link org.jivesoftware.smackx.stanza_content_encryption.provider.AffixExtensionElementProvider} + * for them. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + */ +public interface AffixExtensionElement extends ExtensionElement, AffixElement { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java new file mode 100644 index 000000000..1ec2e98c1 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java @@ -0,0 +1,288 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.address.packet.MultipleAddresses; +import org.jivesoftware.smackx.hints.element.MessageProcessingHint; +import org.jivesoftware.smackx.sid.element.StanzaIdElement; + +import org.jxmpp.jid.Jid; + +/** + * Extension element that holds the payload element, as well as a list of affix elements. + * In SCE, the XML representation of this element is what will be encrypted using the encryption mechanism of choice. + */ +public class ContentElement implements ExtensionElement { + + private static final String NAMESPACE_UNVERSIONED = "urn:xmpp:sce"; + public static final String NAMESPACE_0 = NAMESPACE_UNVERSIONED + ":0"; + public static final String NAMESPACE = NAMESPACE_0; + public static final String ELEMENT = "content"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + private final PayloadElement payload; + private final List affixElements; + + ContentElement(PayloadElement payload, List affixElements) { + this.payload = payload; + this.affixElements = Collections.unmodifiableList(affixElements); + } + + /** + * Return the {@link PayloadElement} which holds the sensitive payload extensions. + * + * @return payload element + */ + public PayloadElement getPayload() { + return payload; + } + + /** + * Return a list of affix elements. + * Those are elements that need to be verified upon reception by the encryption mechanisms implementation. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + * + * @return list of affix elements + */ + public List getAffixElements() { + return affixElements; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); + xml.append(affixElements); + xml.append(payload); + return xml.closeElement(this); + } + + @Override + public QName getQName() { + return QNAME; + } + + /** + * Return a {@link Builder} that can be used to build the {@link ContentElement}. + * @return builder + */ + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private static final Set BLACKLISTED_NAMESPACES = Collections.singleton(MessageProcessingHint.NAMESPACE); + private static final Set BLACKLISTED_QNAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + StanzaIdElement.QNAME, + MultipleAddresses.QNAME + ))); + + private FromAffixElement from = null; + private TimestampAffixElement timestamp = null; + private RandomPaddingAffixElement rpad = null; + + private final List otherAffixElements = new ArrayList<>(); + private final List payloadItems = new ArrayList<>(); + + private Builder() { + + } + + /** + * Add an affix element of type 'to' which addresses one recipient. + * The jid in the 'to' element SHOULD be a bare jid. + * + * @param jid jid + * @return builder + */ + public Builder addTo(Jid jid) { + return addTo(new ToAffixElement(jid)); + } + + /** + * Add an affix element of type 'to' which addresses one recipient. + * + * @param to affix element + * @return builder + */ + public Builder addTo(ToAffixElement to) { + this.otherAffixElements.add(Objects.requireNonNull(to, "'to' affix element MUST NOT be null.")); + return this; + } + + /** + * Set the senders jid as a 'from' affix element. + * + * @param jid jid of the sender + * @return builder + */ + public Builder setFrom(Jid jid) { + return setFrom(new FromAffixElement(jid)); + } + + /** + * Set the senders jid as a 'from' affix element. + * + * @param from affix element + * @return builder + */ + public Builder setFrom(FromAffixElement from) { + this.from = Objects.requireNonNull(from, "'form' affix element MUST NOT be null."); + return this; + } + + /** + * Set the given date as a 'time' affix element. + * + * @param date timestamp as date + * @return builder + */ + public Builder setTimestamp(Date date) { + return setTimestamp(new TimestampAffixElement(date)); + } + + /** + * Set the timestamp of the message as a 'time' affix element. + * + * @param timestamp timestamp affix element + * @return builder + */ + public Builder setTimestamp(TimestampAffixElement timestamp) { + this.timestamp = Objects.requireNonNull(timestamp, "'time' affix element MUST NOT be null."); + return this; + } + + /** + * Set some random length random content padding. + * + * @return builder + */ + public Builder setRandomPadding() { + this.rpad = new RandomPaddingAffixElement(); + return this; + } + + /** + * Set the given string as padding. + * The padding should be of length between 1 and 200 characters. + * + * @param padding padding string + * @return builder + */ + public Builder setRandomPadding(String padding) { + return setRandomPadding(new RandomPaddingAffixElement(padding)); + } + + /** + * Set a padding affix element. + * + * @param padding affix element + * @return builder + */ + public Builder setRandomPadding(RandomPaddingAffixElement padding) { + this.rpad = Objects.requireNonNull(padding, "'rpad' affix element MUST NOT be empty."); + return this; + } + + /** + * Add an additional, SCE profile specific affix element. + * + * @param customAffixElement additional affix element + * @return builder + */ + public Builder addFurtherAffixElement(AffixElement customAffixElement) { + this.otherAffixElements.add(Objects.requireNonNull(customAffixElement, + "Custom affix element MUST NOT be null.")); + return this; + } + + /** + * Add a payload item as child element of the payload element. + * There are some items that are not allowed as payload. + * Adding those will throw an exception. + * + * @see + * XEP-0420: Stanza Content Encryption - §9. Server-processed Elements + * + * @param payloadItem extension element + * @return builder + * @throws IllegalArgumentException in case an extension element from the blacklist is added. + */ + public Builder addPayloadItem(ExtensionElement payloadItem) { + Objects.requireNonNull(payloadItem, "Payload item MUST NOT be null."); + this.payloadItems.add(checkForIllegalPayloadsAndPossiblyThrow(payloadItem)); + return this; + } + + /** + * Construct a content element from this builder. + * + * @return content element + */ + public ContentElement build() { + List allAffixElements = collectAffixElements(); + PayloadElement payloadElement = new PayloadElement(payloadItems); + return new ContentElement(payloadElement, allAffixElements); + } + + private static ExtensionElement checkForIllegalPayloadsAndPossiblyThrow(ExtensionElement payloadItem) { + QName qName = payloadItem.getQName(); + if (BLACKLISTED_QNAMES.contains(qName)) { + throw new IllegalArgumentException("Element identified by " + qName + + " is not allowed as payload item. See https://xmpp.org/extensions/xep-0420.html#server-processed"); + } + + String namespace = payloadItem.getNamespace(); + if (BLACKLISTED_NAMESPACES.contains(namespace)) { + throw new IllegalArgumentException("Elements of namespace '" + namespace + + "' are not allowed as payload items. See https://xmpp.org/extensions/xep-0420.html#server-processed"); + } + + return payloadItem; + } + + private List collectAffixElements() { + List allAffixElements = new ArrayList<>(Arrays.asList(rpad, from, timestamp)); + allAffixElements.addAll(otherAffixElements); + return allAffixElements; + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java new file mode 100644 index 000000000..6af403fae --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java @@ -0,0 +1,33 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import org.jxmpp.jid.Jid; + +public class FromAffixElement extends JidAffixElement { + + public static final String ELEMENT = "from"; + + public FromAffixElement(Jid jid) { + super(jid); + } + + @Override + public String getElementName() { + return ELEMENT; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java new file mode 100644 index 000000000..1543d044e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java @@ -0,0 +1,57 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; + +import org.jxmpp.jid.Jid; + +public abstract class JidAffixElement implements NamedElement, AffixElement { + + public static final String ATTR_JID = "jid"; + + private final Jid jid; + + public JidAffixElement(Jid jid) { + this.jid = Objects.requireNonNull(jid, "Value of 'jid' MUST NOT be null."); + } + + public Jid getJid() { + return jid; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this) + .attribute(ATTR_JID, getJid()) + .closeEmptyElement(); + } + + @Override + public final boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getJid(), o.getJid()).append(getElementName(), o.getElementName())); + } + + @Override + public final int hashCode() { + return (getElementName() + getJid().toString()).hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java new file mode 100644 index 000000000..c3a3431ec --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java @@ -0,0 +1,52 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class PayloadElement implements NamedElement { + + public static final String ELEMENT = "payload"; + + private final List payloadElements; + + public PayloadElement(List payloadElements) { + this.payloadElements = Collections.unmodifiableList(payloadElements); + } + + public List getItems() { + return payloadElements; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); + xml.append(payloadElements); + return xml.closeElement(this); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java new file mode 100644 index 000000000..df43ad456 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java @@ -0,0 +1,75 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import java.security.SecureRandom; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.RandomUtil; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class RandomPaddingAffixElement implements NamedElement, AffixElement { + + private static final int minPaddingLength = 1; + private static final int maxPaddingLength = 200; + public static final String ELEMENT = "rpad"; + + private final String padding; + + public RandomPaddingAffixElement(String padding) { + this.padding = StringUtils.escapeForXmlText( + StringUtils.requireNotNullNorEmpty(padding, "Value of 'rpad' MUST NOT be null nor empty.")) + .toString(); + } + + public RandomPaddingAffixElement() { + this(StringUtils.randomString(randomPaddingLength(), new SecureRandom())); + } + + private static int randomPaddingLength() { + return minPaddingLength + RandomUtil.nextSecureRandomInt(maxPaddingLength - minPaddingLength); + } + + public String getPadding() { + return padding; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this).rightAngleBracket() + .append(getPadding()) + .closeElement(this); + } + + @Override + public boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getPadding(), o.getPadding())); + } + + @Override + public int hashCode() { + return getPadding().hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java new file mode 100644 index 000000000..0c48f7b7a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java @@ -0,0 +1,63 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import java.util.Date; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class TimestampAffixElement implements NamedElement, AffixElement { + + public static final String ELEMENT = "time"; + public static final String ATTR_STAMP = "stamp"; + + private final Date timestamp; + + public TimestampAffixElement(Date timestamp) { + this.timestamp = Objects.requireNonNull(timestamp, "Date must not be null."); + } + + public Date getTimestamp() { + return timestamp; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this) + .attribute(ATTR_STAMP, getTimestamp()) + .closeEmptyElement(); + } + + @Override + public boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getTimestamp(), o.getTimestamp())); + } + + @Override + public int hashCode() { + return timestamp.hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java new file mode 100644 index 000000000..896d0548a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java @@ -0,0 +1,34 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import org.jxmpp.jid.Jid; + +public class ToAffixElement extends JidAffixElement { + + public static final String ELEMENT = "to"; + + public ToAffixElement(Jid jid) { + super(jid); + } + + @Override + public String getElementName() { + return ELEMENT; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java new file mode 100644 index 000000000..7fc4a033f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption: Element classes. + */ +package org.jivesoftware.smackx.stanza_content_encryption.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java new file mode 100644 index 000000000..938a0af63 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java @@ -0,0 +1,22 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption. + * + * @see XEP-0420: Stanza Content Encryption + */ +package org.jivesoftware.smackx.stanza_content_encryption; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java new file mode 100644 index 000000000..be4ffe19d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java @@ -0,0 +1,29 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.stanza_content_encryption.element.AffixExtensionElement; + +/** + * Abstract class that needs to be extended by provider classes that parse out affix extension elements. + * + * @param affix extension element. + */ +public abstract class AffixExtensionElementProvider extends ExtensionElementProvider { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java new file mode 100644 index 000000000..e97b71e7a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java @@ -0,0 +1,137 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.provider; + +import java.io.IOException; +import java.util.Date; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.stanza_content_encryption.element.AffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.ContentElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.FromAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.PayloadElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.RandomPaddingAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.TimestampAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.ToAffixElement; + +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +public class ContentElementProvider extends ExtensionElementProvider { + + @Override + public ContentElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException { + ContentElement.Builder builder = ContentElement.builder(); + + while (true) { + XmlPullParser.Event tag = parser.next(); + if (tag == XmlPullParser.Event.START_ELEMENT) { + String name = parser.getName(); + switch (name) { + case ToAffixElement.ELEMENT: + parseToAffix(parser, builder); + break; + + case FromAffixElement.ELEMENT: + parseFromAffix(parser, builder); + break; + + case TimestampAffixElement.ELEMENT: + parseTimestampAffix(parser, builder); + break; + + case RandomPaddingAffixElement.ELEMENT: + parseRPadAffix(parser, builder); + break; + + case PayloadElement.ELEMENT: + parsePayload(parser, xmlEnvironment, builder); + break; + + default: + parseCustomAffix(parser, xmlEnvironment, builder); + break; + } + } else if (tag == XmlPullParser.Event.END_ELEMENT) { + if (parser.getDepth() == initialDepth) { + break; + } + } + } + return builder.build(); + } + + private static void parseCustomAffix(XmlPullParser parser, XmlEnvironment outerXmlEnvironment, ContentElement.Builder builder) + throws XmlPullParserException, IOException, SmackParsingException { + String name = parser.getName(); + String namespace = parser.getNamespace(); + + AffixElement element = (AffixElement) PacketParserUtils.parseExtensionElement(name, namespace, parser, outerXmlEnvironment); + builder.addFurtherAffixElement(element); + } + + private static void parsePayload(XmlPullParser parser, XmlEnvironment outerXmlEnvironment, ContentElement.Builder builder) + throws IOException, XmlPullParserException, SmackParsingException { + final int initialDepth = parser.getDepth(); + while (true) { + XmlPullParser.Event tag = parser.next(); + + if (tag == XmlPullParser.Event.START_ELEMENT) { + String name = parser.getName(); + String namespace = parser.getNamespace(); + ExtensionElement element = PacketParserUtils.parseExtensionElement(name, namespace, parser, outerXmlEnvironment); + builder.addPayloadItem(element); + } + + if (tag == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == initialDepth) { + return; + } + } + } + + private static void parseRPadAffix(XmlPullParser parser, ContentElement.Builder builder) + throws IOException, XmlPullParserException { + builder.setRandomPadding(parser.nextText()); + } + + private static void parseTimestampAffix(XmlPullParser parser, ContentElement.Builder builder) + throws SmackParsingException.SmackTextParseException { + Date timestamp = ParserUtils.getDateFromXep82String( + parser.getAttributeValue("", TimestampAffixElement.ATTR_STAMP)); + builder.setTimestamp(timestamp); + } + + private static void parseFromAffix(XmlPullParser parser, ContentElement.Builder builder) + throws XmppStringprepException { + String jidString = parser.getAttributeValue("", FromAffixElement.ATTR_JID); + builder.setFrom(JidCreate.from(jidString)); + } + + private static void parseToAffix(XmlPullParser parser, ContentElement.Builder builder) + throws XmppStringprepException { + String jidString = parser.getAttributeValue("", ToAffixElement.ATTR_JID); + builder.addTo(JidCreate.from(jidString)); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java new file mode 100644 index 000000000..450e2c53b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption: Provider classes. + */ +package org.jivesoftware.smackx.stanza_content_encryption.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index 8e6d3df51..a4d9f15e9 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -292,6 +292,13 @@ org.jivesoftware.smackx.dox.provider.DnsIqProvider + + + content + urn:xmpp:sce:0 + org.jivesoftware.smackx.stanza_content_encryption.provider.ContentElementProvider + + apply-to diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java new file mode 100644 index 000000000..2d7fcec0d --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java @@ -0,0 +1,156 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * 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.stanza_content_encryption.element; + +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.text.ParseException; +import java.util.Date; + +import org.junit.jupiter.api.Test; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.util.XmppDateTime; + +public class AffixElementsTest { + + public static final EntityBareJid JID_HOUSTON = JidCreate.entityBareFromOrThrowUnchecked("missioncontrol@houston.nasa.gov"); + public static final EntityBareJid JID_OPPORTUNITY = JidCreate.entityBareFromOrThrowUnchecked("opportunity@mars.planet"); + + /** + * Test serialization of 'to' affix element. + * + * @see XEP-420 Example 1 + */ + @Test + public void testToAffixElement() { + ToAffixElement to = new ToAffixElement(JID_HOUSTON); + String expectedXml = ""; + + assertXmlSimilar(expectedXml, to.toXML()); + assertEquals(JID_HOUSTON, to.getJid()); + } + + @Test + public void testToAffixElementEquals() { + ToAffixElement to1 = new ToAffixElement(JID_HOUSTON); + ToAffixElement to2 = new ToAffixElement(JID_HOUSTON); + + assertEquals(to1, to2); + assertEquals(to1, to1); + assertEquals(to1.hashCode(), to2.hashCode()); + assertFalse(to1.equals(null)); + } + + @Test + public void toElementNullArgThrows() { + assertThrows(IllegalArgumentException.class, () -> new ToAffixElement(null)); + } + + /** + * Test serialization of 'from' affix element. + * + * @see XEP-420 Example 1 + */ + @Test + public void testFromAffixElement() { + FromAffixElement from = new FromAffixElement(JID_OPPORTUNITY); + String expectedXml = ""; + + assertXmlSimilar(expectedXml, from.toXML()); + assertEquals(JID_OPPORTUNITY, from.getJid()); + } + + @Test + public void testFromAffixElementEquals() { + FromAffixElement from1 = new FromAffixElement(JID_HOUSTON); + FromAffixElement from2 = new FromAffixElement(JID_HOUSTON); + + assertEquals(from1, from2); + assertEquals(from1, from1); + assertEquals(from1.hashCode(), from2.hashCode()); + assertFalse(from1.equals(null)); + } + + @Test + public void fromElementNullArgThrows() { + assertThrows(IllegalArgumentException.class, () -> new FromAffixElement(null)); + } + + @Test + public void testTimestampAffixElement() throws ParseException { + Date date = XmppDateTime.parseDate("2004-01-25T05:05:00.000+00:00"); + TimestampAffixElement timestamp = new TimestampAffixElement(date); + String expectedXml = "