1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-22 12:02:05 +01:00

Merge branch '4.2'

This commit is contained in:
Florian Schmaus 2017-05-25 10:39:59 +02:00
commit 7a5f9e6a03
187 changed files with 2284 additions and 588 deletions

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
# IntelliJ # IntelliJ
.idea .idea
*.iml *.iml
*.ipr
*.iws
# Mac OS X # Mac OS X
.DS_Store .DS_Store

View file

@ -10,7 +10,7 @@ cache:
- $HOME/.m2 - $HOME/.m2
before_install: before_install:
- export GRADLE_VERSION=2.12 - export GRADLE_VERSION=3.5
- wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip - wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip
- unzip -q gradle-${GRADLE_VERSION}-all.zip - unzip -q gradle-${GRADLE_VERSION}-all.zip
- export PATH="$(pwd)/gradle-${GRADLE_VERSION}/bin:$PATH" - export PATH="$(pwd)/gradle-${GRADLE_VERSION}/bin:$PATH"
@ -20,5 +20,5 @@ script: gradle check --stacktrace
after_success: after_success:
- JAVAC_VERSION=$((javac -version) 2>&1) - JAVAC_VERSION=$((javac -version) 2>&1)
# Only run jacocoRootReport in the Java7 build # Only run jacocoRootReport in the Java 8 build
- if [[ "$JAVAC_VERSION" = javac\ 1.7.* ]]; then gradle jacocoRootReport coveralls; fi - if [[ "$JAVAC_VERSION" = javac\ 1.8.* ]]; then gradle jacocoRootReport coveralls; fi

View file

@ -8,9 +8,9 @@ buildscript {
} }
dependencies { dependencies {
classpath 'org.kordamp:markdown-gradle-plugin:1.0.0' classpath 'org.kordamp:markdown-gradle-plugin:1.0.0'
classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.0' classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2'
classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.3.1" classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.3.1"
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.9' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
} }
} }
apply plugin: 'org.kordamp.gradle.markdown' apply plugin: 'org.kordamp.gradle.markdown'
@ -20,6 +20,7 @@ apply from: 'version.gradle'
allprojects { allprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'jacoco' apply plugin: 'jacoco'
apply plugin: 'net.ltgt.errorprone' apply plugin: 'net.ltgt.errorprone'
@ -266,6 +267,7 @@ subprojects {
checkstyle { checkstyle {
configFile = new File(rootConfigDir, 'checkstyle.xml') configFile = new File(rootConfigDir, 'checkstyle.xml')
toolVersion = '7.7'
} }
task sourcesJar(type: Jar, dependsOn: classes) { task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources' classifier = 'sources'

View file

@ -117,5 +117,75 @@
, INDEX_OP , INDEX_OP
"/> "/>
</module> </module>
<module name="WhitespaceAfter">
<property name="tokens" value="TYPECAST
, LITERAL_IF
, LITERAL_ELSE
, LITERAL_WHILE
, LITERAL_DO
, LITERAL_FOR
, DO_WHILE
"/>
</module>
<module name="WhitespaceAround">
<property
name="ignoreEnhancedForColon"
value="false"
/>
<!-- Currently disabled tokens: LCURLY, RCURLY, WILDCARD_TYPE, GENERIC_START, GENERIC_END -->
<property
name="tokens"
value="ASSIGN
, ARRAY_INIT
, BAND
, BAND_ASSIGN
, BOR
, BOR_ASSIGN
, BSR
, BSR_ASSIGN
, BXOR
, BXOR_ASSIGN
, COLON
, DIV
, DIV_ASSIGN
, DO_WHILE
, EQUAL
, GE
, GT
, LAMBDA
, LAND
, LE
, LITERAL_CATCH
, LITERAL_DO
, LITERAL_ELSE
, LITERAL_FINALLY
, LITERAL_FOR
, LITERAL_IF
, LITERAL_RETURN
, LITERAL_SWITCH
, LITERAL_SYNCHRONIZED
, LITERAL_TRY
, LITERAL_WHILE
, LOR
, LT
, MINUS
, MINUS_ASSIGN
, MOD
, MOD_ASSIGN
, NOT_EQUAL
, PLUS
, PLUS_ASSIGN
, QUESTION
, SL
, SLIST
, SL_ASSIGN
, SR
, SR_ASSIGN
, STAR
, STAR_ASSIGN
, LITERAL_ASSERT
, TYPE_EXTENSION_AND
"/>
</module>
</module> </module>
</module> </module>

View file

@ -87,6 +87,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Internet of Things - Control](iot.md) | [XEP-0325](http://xmpp.org/extensions/xep-0325.html) | Describes how to control devices or actuators in an XMPP-based sensor netowrk. | | [Internet of Things - Control](iot.md) | [XEP-0325](http://xmpp.org/extensions/xep-0325.html) | Describes how to control devices or actuators in an XMPP-based sensor netowrk. |
| [HTTP over XMPP transport](hoxt.md) | [XEP-0332](http://xmpp.org/extensions/xep-0332.html) | Allows to transport HTTP communication over XMPP peer-to-peer networks. | | [HTTP over XMPP transport](hoxt.md) | [XEP-0332](http://xmpp.org/extensions/xep-0332.html) | Allows to transport HTTP communication over XMPP peer-to-peer networks. |
| Chat Markers | [XEP-0333](http://xmpp.org/extensions/xep-0333.html) | A solution of marking the last received, displayed and acknowledged message in a chat. | | Chat Markers | [XEP-0333](http://xmpp.org/extensions/xep-0333.html) | A solution of marking the last received, displayed and acknowledged message in a chat. |
| Message Processing Hints | [XEP-0334](http://xmpp.org/extensions/xep-0334.html) | Hints to entities routing or receiving a message. |
| JSON Containers | [XEP-0335](http://xmpp.org/extensions/xep-0335.html) | Encapsulation of JSON data within XMPP Stanzas. | | JSON Containers | [XEP-0335](http://xmpp.org/extensions/xep-0335.html) | Encapsulation of JSON data within XMPP Stanzas. |
| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. | | [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. |
| Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. | | Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. |

View file

@ -41,6 +41,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode;
import org.jivesoftware.smack.SmackException.AlreadyConnectedException; import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException; import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
@ -894,7 +895,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
continue; continue;
} }
} }
}}); }
});
} }
@Override @Override
@ -991,20 +993,31 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
replyTimeout = timeout; replyTimeout = timeout;
} }
private static boolean replyToUnknownIqDefault = true;
/** /**
* Set the default value used to determine if new connection will reply to unknown IQ requests. The pre-configured * Set the default value used to determine if new connection will reply to unknown IQ requests. The pre-configured
* default is 'true'. * default is 'true'.
* *
* @param replyToUnkownIqDefault * @param replyToUnkownIqDefault
* @see #setReplyToUnknownIq(boolean) * @see #setReplyToUnknownIq(boolean)
* @deprecated Use {@link SmackConfiguration#setUnknownIqRequestReplyMode(org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode)} instead.
*/ */
@Deprecated
// TODO Remove in Smack 4.3
public static void setReplyToUnknownIqDefault(boolean replyToUnkownIqDefault) { public static void setReplyToUnknownIqDefault(boolean replyToUnkownIqDefault) {
AbstractXMPPConnection.replyToUnknownIqDefault = replyToUnkownIqDefault; SmackConfiguration.UnknownIqRequestReplyMode mode;
if (replyToUnkownIqDefault) {
mode = SmackConfiguration.UnknownIqRequestReplyMode.replyServiceUnavailable;
} else {
mode = SmackConfiguration.UnknownIqRequestReplyMode.doNotReply;
}
SmackConfiguration.setUnknownIqRequestReplyMode(mode);
} }
private boolean replyToUnkownIq = replyToUnknownIqDefault; private SmackConfiguration.UnknownIqRequestReplyMode unknownIqRequestReplyMode = SmackConfiguration.getUnknownIqRequestReplyMode();
public void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
this.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Mode must not be null");
}
/** /**
* Set if Smack will automatically send * Set if Smack will automatically send
@ -1012,9 +1025,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* registered {@link IQRequestHandler} is received. * registered {@link IQRequestHandler} is received.
* *
* @param replyToUnknownIq * @param replyToUnknownIq
* @deprecated use {@link #setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode)} instead.
*/ */
@Deprecated
// TODO Remove in Smack 4.3
public void setReplyToUnknownIq(boolean replyToUnknownIq) { public void setReplyToUnknownIq(boolean replyToUnknownIq) {
this.replyToUnkownIq = replyToUnknownIq; SmackConfiguration.UnknownIqRequestReplyMode mode;
if (replyToUnknownIq) {
mode = SmackConfiguration.UnknownIqRequestReplyMode.replyServiceUnavailable;
} else {
mode = SmackConfiguration.UnknownIqRequestReplyMode.doNotReply;
}
unknownIqRequestReplyMode = mode;
} }
protected void parseAndProcessStanza(XmlPullParser parser) throws Exception { protected void parseAndProcessStanza(XmlPullParser parser) throws Exception {
@ -1089,13 +1111,24 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
throw new IllegalStateException("Should only encounter IQ type 'get' or 'set'"); throw new IllegalStateException("Should only encounter IQ type 'get' or 'set'");
} }
if (iqRequestHandler == null) { if (iqRequestHandler == null) {
if (!replyToUnkownIq) { XMPPError.Condition replyCondition;
switch (unknownIqRequestReplyMode) {
case doNotReply:
return; return;
case replyFeatureNotImplemented:
replyCondition = XMPPError.Condition.feature_not_implemented;
break;
case replyServiceUnavailable:
replyCondition = XMPPError.Condition.service_unavailable;
break;
default:
throw new AssertionError();
} }
// If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an // If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an
// IQ of type 'error' with condition 'service-unavailable'. // IQ of type 'error' with condition 'service-unavailable'.
ErrorIQ errorIQ = IQ.createErrorResponse(iq, XMPPError.getBuilder(( ErrorIQ errorIQ = IQ.createErrorResponse(iq, XMPPError.getBuilder((
XMPPError.Condition.service_unavailable))); replyCondition)));
try { try {
sendStanza(errorIQ); sendStanza(errorIQ);
} }

View file

@ -34,6 +34,7 @@ import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory; import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.parsing.ExceptionThrowingCallback; import org.jivesoftware.smack.parsing.ExceptionThrowingCallback;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback; import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.util.Objects;
/** /**
* Represents the configuration of Smack. The configuration is used for: * Represents the configuration of Smack. The configuration is used for:
@ -370,4 +371,20 @@ public final class SmackConfiguration {
return defaultHostnameVerififer; return defaultHostnameVerififer;
} }
enum UnknownIqRequestReplyMode {
doNotReply,
replyFeatureNotImplemented,
replyServiceUnavailable,
}
// TODO Change to replyFeatureNotImplemented in Smack 4.3
private static UnknownIqRequestReplyMode unknownIqRequestReplyMode = UnknownIqRequestReplyMode.replyServiceUnavailable;
public static UnknownIqRequestReplyMode getUnknownIqRequestReplyMode() {
return unknownIqRequestReplyMode;
}
public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
}
} }

View file

@ -0,0 +1,170 @@
/**
*
* 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.smack;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.packet.Stanza;
public abstract class SmackFuture<V> implements Future<V> {
private boolean cancelled;
private V result;
protected Exception exception;
private SuccessCallback<V> successCallback;
private ExceptionCallback exceptionCallback;
@Override
public synchronized final boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
return false;
}
cancelled = true;
return true;
}
@Override
public synchronized final boolean isCancelled() {
return cancelled;
}
@Override
public synchronized final boolean isDone() {
return result != null;
}
public void onSuccessOrError(SuccessCallback<V> successCallback, ExceptionCallback exceptionCallback) {
this.successCallback = successCallback;
this.exceptionCallback = exceptionCallback;
maybeInvokeCallbacks();
}
public void onSuccess(SuccessCallback<V> successCallback) {
onSuccessOrError(successCallback, null);
}
public void onError(ExceptionCallback exceptionCallback) {
onSuccessOrError(null, exceptionCallback);
}
private final V getResultOrThrow() throws ExecutionException {
assert (result != null || exception != null);
if (result != null) {
return result;
}
throw new ExecutionException(exception);
}
@Override
public synchronized final V get() throws InterruptedException, ExecutionException {
while (result == null && exception == null) {
wait();
}
return getResultOrThrow();
}
@Override
public synchronized final V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
final long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
while (result != null && exception != null) {
final long waitTimeRemaining = deadline - System.currentTimeMillis();
if (waitTimeRemaining > 0) {
wait(waitTimeRemaining);
}
}
if (result == null || exception == null) {
throw new TimeoutException();
}
return getResultOrThrow();
}
protected final synchronized void maybeInvokeCallbacks() {
if (result != null && successCallback != null) {
successCallback.onSuccess(result);
} else if (exception != null && exceptionCallback != null) {
exceptionCallback.processException(exception);
}
}
/**
* This method checks if the given exception is <b>not</b> fatal. If this method returns <code>false</code>, then
* the future will automatically set the given exception as failure reason and notify potential waiting threads.
*
* @param exception the exception to check.
* @return <code>true</code> if the exception is not fatal, <code>false</code> otherwise.
*/
protected abstract boolean isNonFatalException(Exception exception);
protected abstract void handleStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
protected final void setResult(V result) {
assert (Thread.holdsLock(this));
this.result = result;
this.notifyAll();
maybeInvokeCallbacks();
}
public static abstract class InternalSmackFuture<V> extends SmackFuture<V> implements StanzaListener, ExceptionCallback {
@Override
public synchronized final void processException(Exception exception) {
if (!isNonFatalException(exception)) {
this.exception = exception;
this.notifyAll();
maybeInvokeCallbacks();
}
}
/**
* Wrapper method for {@link #handleStanza(Stanza)}. Note that this method is <code>synchronized</code>.
*/
@Override
public synchronized final void processStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
handleStanza(stanza);
}
}
/**
* A simple version of InternalSmackFuture which implements {@link #isNonFatalException(Exception)} as always returning <code>false</code> method.
*
* @param <V>
*/
public static abstract class SimpleInternalSmackFuture<V> extends InternalSmackFuture<V> {
@Override
protected boolean isNonFatalException(Exception exception) {
return false;
}
}
}

View file

@ -0,0 +1,23 @@
/**
*
* 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.smack;
public interface SuccessCallback<T> {
public void onSuccess(T result);
}

View file

@ -413,6 +413,7 @@ public interface XMPPConnection {
* @deprecated use {@link #getReplyTimeout()} instead. * @deprecated use {@link #getReplyTimeout()} instead.
*/ */
@Deprecated @Deprecated
// TODO Remove in Smack 4.3
public long getPacketReplyTimeout(); public long getPacketReplyTimeout();
/** /**
@ -423,6 +424,7 @@ public interface XMPPConnection {
* @deprecated use {@link #setReplyTimeout(long)} instead. * @deprecated use {@link #setReplyTimeout(long)} instead.
*/ */
@Deprecated @Deprecated
// TODO Remove in Smack 4.3
public void setPacketReplyTimeout(long timeout); public void setPacketReplyTimeout(long timeout);
/** /**

View file

@ -39,7 +39,7 @@ public abstract class AbstractJidTypeFilter implements StanzaFilter {
@Override @Override
public final boolean accept(Stanza stanza) { public final boolean accept(Stanza stanza) {
final Jid jid = stanza.getFrom(); final Jid jid = getJidToInspect(stanza);
if (jid == null) { if (jid == null) {
return false; return false;

View file

@ -0,0 +1,43 @@
/**
*
* 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.smack.filter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
/**
* Filters message stanzas which have at least one body.
*/
public final class MessageWithThreadFilter extends FlexibleStanzaTypeFilter<Message> {
public static final StanzaFilter INSTANCE = new MessageWithThreadFilter();
private MessageWithThreadFilter() {
super(Message.class);
}
@Override
protected boolean acceptSpecific(Message message) {
return StringUtils.isNotEmpty(message.getThread());
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View file

@ -66,9 +66,12 @@ public class AbstractError {
public String getDescriptiveText() { public String getDescriptiveText() {
String defaultLocale = Locale.getDefault().getLanguage(); String defaultLocale = Locale.getDefault().getLanguage();
String descriptiveText = getDescriptiveText(defaultLocale); String descriptiveText = getDescriptiveText(defaultLocale);
if (descriptiveText == null) {
descriptiveText = getDescriptiveText("en");
if (descriptiveText == null) { if (descriptiveText == null) {
descriptiveText = getDescriptiveText(""); descriptiveText = getDescriptiveText("");
} }
}
return descriptiveText; return descriptiveText;
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014 Florian Schmaus * Copyright 2014-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,9 +19,13 @@ package org.jivesoftware.smack.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LazyStringBuilder implements Appendable, CharSequence { public class LazyStringBuilder implements Appendable, CharSequence {
private static final Logger LOGGER = Logger.getLogger(LazyStringBuilder.class.getName());
private final List<CharSequence> list; private final List<CharSequence> list;
private String cache; private String cache;
@ -69,9 +73,16 @@ public class LazyStringBuilder implements Appendable, CharSequence {
return cache.length(); return cache.length();
} }
int length = 0; int length = 0;
try {
for (CharSequence csq : list) { for (CharSequence csq : list) {
length += csq.length(); length += csq.length();
} }
}
catch (NullPointerException npe) {
StringBuilder sb = safeToStringBuilder();
LOGGER.log(Level.SEVERE, "The following LazyStringBuilder threw a NullPointerException: " + sb, npe);
throw npe;
}
return length; return length;
} }
@ -107,6 +118,14 @@ public class LazyStringBuilder implements Appendable, CharSequence {
return cache; return cache;
} }
public StringBuilder safeToStringBuilder() {
StringBuilder sb = new StringBuilder();
for (CharSequence csq : list) {
sb.append(csq);
}
return sb;
}
/** /**
* Get the List of CharSequences representation of this instance. The list is unmodifiable. If * Get the List of CharSequences representation of this instance. The list is unmodifiable. If
* the resulting String was already cached, a list with a single String entry will be returned. * the resulting String was already cached, a list with a single String entry will be returned.

View file

@ -0,0 +1,57 @@
/**
*
* 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.smack;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.SmackFuture.SimpleInternalSmackFuture;
import org.jivesoftware.smack.packet.Stanza;
import org.junit.Test;
public class SmackFutureTest {
@Test
public void simpleSmackFutureSuccessTest() throws NotConnectedException, InterruptedException, ExecutionException {
InternalSmackFuture<Boolean> future = new SimpleInternalSmackFuture<Boolean>() {
@Override
protected void handleStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
setResult(true);
}
};
future.processStanza(null);
assertTrue(future.get());
}
@Test(expected = TimeoutException.class)
public void simpleSmackFutureTimeoutTest() throws InterruptedException, ExecutionException, TimeoutException {
InternalSmackFuture<Boolean> future = new SimpleInternalSmackFuture<Boolean>() {
@Override
protected void handleStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
}
};
future.get(5, TimeUnit.SECONDS);
}
}

View file

@ -57,9 +57,10 @@ public class ThreadedDummyConnection extends DummyConnection {
replyQ.add(replyPacket); replyQ.add(replyPacket);
} }
replyPacket.setStanzaId(packet.getStanzaId()); replyPacket.setStanzaId(packet.getStanzaId());
replyPacket.setFrom(packet.getTo());
replyPacket.setTo(packet.getFrom()); replyPacket.setTo(packet.getFrom());
if (replyPacket.getType() == null) {
replyPacket.setType(Type.result); replyPacket.setType(Type.result);
}
new ProcessQueue(replyQ).start(); new ProcessQueue(replyQ).start();
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright © 2014-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,7 +32,7 @@ import org.jxmpp.jid.EntityBareJid;
public class DigestMd5SaslTest extends AbstractSaslTest { public class DigestMd5SaslTest extends AbstractSaslTest {
protected static final String challenge = "realm=\"xmpp.org\",nonce=\"aTUr3GXqUtyy2B7HVDW6C+gQs+j+0EhWWjoBKkkg\",qop=\"auth\",charset=utf-8,algorithm=md5-sess"; protected static final String challenge = "realm=\"xmpp.org\",nonce=\"jgGgnz+cQcmyVaAs2n88kQ==\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";
protected static final byte[] challengeBytes = StringUtils.toBytes(challenge); protected static final byte[] challengeBytes = StringUtils.toBytes(challenge);
public DigestMd5SaslTest(SASLMechanism saslMechanism) { public DigestMd5SaslTest(SASLMechanism saslMechanism) {
@ -50,8 +50,7 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
String[] responseParts = responseString.split(","); String[] responseParts = responseString.split(",");
Map<String, String> responsePairs = new HashMap<String, String>(); Map<String, String> responsePairs = new HashMap<String, String>();
for (String part : responseParts) { for (String part : responseParts) {
String[] keyValue = part.split("="); String[] keyValue = part.split("=", 2);
assertTrue(keyValue.length == 2);
String key = keyValue[0]; String key = keyValue[0];
String value = keyValue[1].replace("\"", ""); String value = keyValue[1].replace("\"", "");
responsePairs.put(key, value); responsePairs.put(key, value);
@ -59,7 +58,7 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
if (useAuthzid) { if (useAuthzid) {
assertMapValue("authzid", "shazbat@xmpp.org", responsePairs); assertMapValue("authzid", "shazbat@xmpp.org", responsePairs);
} else { } else {
assert(!responsePairs.containsKey("authzid")); assertTrue (!responsePairs.containsKey("authzid"));
} }
assertMapValue("username", "florian", responsePairs); assertMapValue("username", "florian", responsePairs);
assertMapValue("realm", "xmpp.org", responsePairs); assertMapValue("realm", "xmpp.org", responsePairs);

View file

@ -0,0 +1,57 @@
/**
*
* 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.eme;
import java.util.Map;
import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
public final class ExplicitMessageEncryptionManager {
private static final Map<XMPPConnection, ExplicitMessageEncryptionManager> INSTANCES = new WeakHashMap<>();
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
public static final String NAMESPACE_V0 = ExplicitMessageEncryptionElement.NAMESPACE;
public static synchronized ExplicitMessageEncryptionManager getInstanceFor(XMPPConnection connection) {
ExplicitMessageEncryptionManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new ExplicitMessageEncryptionManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
private ExplicitMessageEncryptionManager(XMPPConnection connection) {
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
sdm.addFeature(NAMESPACE_V0);
}
}

View file

@ -0,0 +1,146 @@
/**
*
* 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.eme.element;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class ExplicitMessageEncryptionElement implements ExtensionElement {
private static final Map<String, ExplicitMessageEncryptionProtocol> PROTOCOL_LUT = new HashMap<>();
public static final String ELEMENT = "encryption";
public static final String NAMESPACE = "urn:xmpp:eme:0";
public enum ExplicitMessageEncryptionProtocol {
/**
* The encryption method specified in <a href="https://xmpp.org/extensions/xep-0373.html">XEP-0373: OpenPGP for
* XMPP</a>.
*/
openpgpV0("urn:xmpp:openpgp:0", "OpenPGP for XMPP (XEP-0373)"),
otrV0("urn:xmpp:otr:0", "Off-the-Record Messaging (XEP-0364)"),
legacyOpenPGP("jabber:x:encrypted", "Legacy OpenPGP for XMPP [DANGEROUS, DO NOT USE!]"),
;
private final String namespace;
private final String name;
private ExplicitMessageEncryptionProtocol(String namespace, String name) {
this.namespace = namespace;
this.name = name;
PROTOCOL_LUT.put(namespace, this);
}
public String getNamespace() {
return namespace;
}
public String getName() {
return name;
}
public static ExplicitMessageEncryptionProtocol from(String namespace) {
return PROTOCOL_LUT.get(namespace);
}
}
private final String encryptionNamespace;
private final String name;
private boolean isUnknownProtocol;
private ExplicitMessageEncryptionProtocol protocolCache;
public ExplicitMessageEncryptionElement(ExplicitMessageEncryptionProtocol protocol) {
this(protocol.getNamespace(), protocol.getName());
}
public ExplicitMessageEncryptionElement(String encryptionNamespace) {
this(encryptionNamespace, null);
}
public ExplicitMessageEncryptionElement(String encryptionNamespace, String name) {
this.encryptionNamespace = StringUtils.requireNotNullOrEmpty(encryptionNamespace,
"encryptionNamespace must not be null");
this.name = name;
}
public ExplicitMessageEncryptionProtocol getProtocol() {
if (protocolCache != null) {
return protocolCache;
}
if (isUnknownProtocol) {
return null;
}
ExplicitMessageEncryptionProtocol protocol = PROTOCOL_LUT.get(encryptionNamespace);
if (protocol == null) {
isUnknownProtocol = true;
return null;
}
protocolCache = protocol;
return protocol;
}
public String getEncryptionNamespace() {
return encryptionNamespace;
}
/**
* Get the optional name of the encryption method.
*
* @return the name of the encryption method or <code>null</code>.
*/
public String getName() {
return name;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.attribute("namespace", getEncryptionNamespace());
xml.optAttribute("name", getName());
xml.closeEmptyElement();
return xml;
}
public static ExplicitMessageEncryptionElement from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
}
}

View file

@ -0,0 +1,20 @@
/**
*
* 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.
*/
/**
* XMPP extension elements for XEP-0380: Explicit Message Encryption.
*/
package org.jivesoftware.smackx.eme.element;

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,8 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/** /**
* TODO describe me * Smack's API for XEP-0380: Explicit Message Encryption.
*/ */
package org.jivesoftware.smackx.iqprivate; package org.jivesoftware.smackx.eme;

View file

@ -0,0 +1,32 @@
/**
*
* 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.eme.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
import org.xmlpull.v1.XmlPullParser;
public class ExplicitMessageEncryptionProvider extends ExtensionElementProvider<ExplicitMessageEncryptionElement> {
@Override
public ExplicitMessageEncryptionElement parse(XmlPullParser parser, int initialDepth) throws Exception {
String namespace = parser.getAttributeValue(null, "namespace");
String name = parser.getAttributeValue(null, "name");
return new ExplicitMessageEncryptionElement(namespace, name);
}
}

View file

@ -0,0 +1,20 @@
/**
*
* 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.
*/
/**
* Smack Provider for XEP-0380: Explicit Message Encryption.
*/
package org.jivesoftware.smackx.eme.provider;

View file

@ -0,0 +1,71 @@
/**
*
* 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.hints;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.hints.element.MessageProcessingHintType;
import org.jivesoftware.smackx.hints.element.NoCopyHint;
import org.jivesoftware.smackx.hints.element.NoPermanentStoreHint;
import org.jivesoftware.smackx.hints.element.NoStoreHint;
import org.jivesoftware.smackx.hints.element.StoreHint;
public class MessageProcessingHintsManager {
public static Set<MessageProcessingHintType> getHintsFrom(Message message) {
Set<MessageProcessingHintType> hints = null;
boolean noCopyHint = NoCopyHint.hasHint(message);
if (noCopyHint) {
hints = new HashSet<>(MessageProcessingHintType.values().length);
hints.add(MessageProcessingHintType.no_copy);
}
boolean noPermanentStoreHint = NoPermanentStoreHint.hasHint(message);
if (noPermanentStoreHint) {
if (hints == null) {
hints = new HashSet<>(MessageProcessingHintType.values().length);
}
hints.add(MessageProcessingHintType.no_permanent_store);
}
boolean noStoreHint = NoStoreHint.hasHint(message);
if (noStoreHint) {
if (hints == null) {
hints = new HashSet<>(MessageProcessingHintType.values().length);
}
hints.add(MessageProcessingHintType.no_store);
}
boolean storeHint = StoreHint.hasHint(message);
if (storeHint) {
if (hints == null) {
hints = new HashSet<>(MessageProcessingHintType.values().length);
}
hints.add(MessageProcessingHintType.store);
}
if (hints == null) {
return Collections.emptySet();
}
return hints;
}
}

View file

@ -0,0 +1,32 @@
/**
*
* 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.hints.element;
import org.jivesoftware.smack.packet.ExtensionElement;
public abstract class MessageProcessingHint implements ExtensionElement {
public static final String NAMESPACE = "urn:xmpp:hints";
@Override
public final String getNamespace() {
return NAMESPACE;
}
public abstract MessageProcessingHintType getHintType();
}

View file

@ -0,0 +1,27 @@
/**
*
* 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.hints.element;
public enum MessageProcessingHintType {
no_permanent_store,
no_store,
no_copy,
store,
;
}

View file

@ -0,0 +1,61 @@
/**
*
* 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.hints.element;
import org.jivesoftware.smack.packet.Message;
/**
* A "no copy" hint. Messages with this hint should not be copied to addresses other than the one to which it is addressed.
*
* @see <a href="https://xmpp.org/extensions/xep-0334.html#no-copy">XEP-0344 § 4.3 No copies</a>
*/
public final class NoCopyHint extends MessageProcessingHint {
public static final NoCopyHint INSTANCE = new NoCopyHint();
public static final String ELEMENT = "no-copy";
private NoCopyHint() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String toXML() {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
@Override
public MessageProcessingHintType getHintType() {
return MessageProcessingHintType.no_copy;
}
public static NoCopyHint from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
}
public static boolean hasHint(Message message) {
return from(message) != null;
}
public static void set(Message message) {
message.addExtension(INSTANCE);
}
}

View file

@ -0,0 +1,69 @@
/**
*
* 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.hints.element;
import org.jivesoftware.smack.packet.Message;
/**
* A "no permanent store" hint. Messages with this hint should not be stored in permanent stores or archives.
*
* @see <a href="https://xmpp.org/extensions/xep-0334.html#no-permanent-store">XEP-0334 § 4.1 No permanent store</a>
*/
public final class NoPermanentStoreHint extends MessageProcessingHint {
public static final NoPermanentStoreHint INSTANCE = new NoPermanentStoreHint();
public static final String ELEMENT = "no-permanent-store";
private NoPermanentStoreHint() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String toXML() {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
@Override
public MessageProcessingHintType getHintType() {
return MessageProcessingHintType.no_permanent_store;
}
public static NoPermanentStoreHint from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
}
public static boolean hasHint(Message message) {
return from(message) != null;
}
public static void set(Message message) {
if (StoreHint.hasHint(message)) {
// No need to set the no-permanent-store hint when a no-store hint is already set.
return;
}
setExplicitly(message);
}
public static void setExplicitly(Message message) {
message.addExtension(INSTANCE);
}
}

View file

@ -0,0 +1,61 @@
/**
*
* 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.hints.element;
import org.jivesoftware.smack.packet.Message;
/**
* A "no store" hint. Messages with this hint should not be stored in stores or archives.
*
* <a href="https://xmpp.org/extensions/xep-0334.html#no-store">XEP-0334 § 4.2 No store</a>
*/
public final class NoStoreHint extends MessageProcessingHint {
public static final NoStoreHint INSTANCE = new NoStoreHint();
public static final String ELEMENT = "no-store";
private NoStoreHint() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String toXML() {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
@Override
public MessageProcessingHintType getHintType() {
return MessageProcessingHintType.no_store;
}
public static NoStoreHint from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
}
public static boolean hasHint(Message message) {
return from(message) != null;
}
public static void set(Message message) {
message.addExtension(INSTANCE);
}
}

View file

@ -0,0 +1,61 @@
/**
*
* 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.hints.element;
import org.jivesoftware.smack.packet.Message;
/**
* A "store" hint. Messages with this hint should be stored in permanent stores or archives.
*
* @see <a href="https://xmpp.org/extensions/xep-0334.html#sect-idm140684698220992">XEP-0334 § 4.4 Store</a>
*/
public final class StoreHint extends MessageProcessingHint {
public static final StoreHint INSTANCE = new StoreHint();
public static final String ELEMENT = "store";
private StoreHint() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String toXML() {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
@Override
public MessageProcessingHintType getHintType() {
return MessageProcessingHintType.store;
}
public static StoreHint from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
}
public static boolean hasHint(Message message) {
return from(message) != null;
}
public static void set(Message message) {
message.addExtension(INSTANCE);
}
}

View file

@ -0,0 +1,20 @@
/**
*
* 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.
*/
/**
* XMPP extension elements for XEP-0334: Message Processing Hints.
*/
package org.jivesoftware.smackx.hints.element;

View file

@ -0,0 +1,20 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-0334: Message Processing Hints.
*/
package org.jivesoftware.smackx.hints;

View file

@ -0,0 +1,31 @@
/**
*
* 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.hints.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.hints.element.MessageProcessingHint;
import org.xmlpull.v1.XmlPullParser;
public abstract class MessageProcessingHintProvider<H extends MessageProcessingHint> extends ExtensionElementProvider<H> {
@Override
public H parse(XmlPullParser parser, int initialDepth) throws Exception {
return getHint();
}
protected abstract H getHint();
}

View file

@ -0,0 +1,28 @@
/**
*
* 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.hints.provider;
import org.jivesoftware.smackx.hints.element.NoCopyHint;
public class NoCopyHintProvider extends MessageProcessingHintProvider<NoCopyHint> {
@Override
protected NoCopyHint getHint() {
return NoCopyHint.INSTANCE;
}
}

View file

@ -0,0 +1,28 @@
/**
*
* 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.hints.provider;
import org.jivesoftware.smackx.hints.element.NoPermanentStoreHint;
public class NoPermanentStoreHintProvider extends MessageProcessingHintProvider<NoPermanentStoreHint> {
@Override
protected NoPermanentStoreHint getHint() {
return NoPermanentStoreHint.INSTANCE;
}
}

View file

@ -0,0 +1,28 @@
/**
*
* 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.hints.provider;
import org.jivesoftware.smackx.hints.element.NoStoreHint;
public class NoStoreHintProvider extends MessageProcessingHintProvider<NoStoreHint> {
@Override
protected NoStoreHint getHint() {
return NoStoreHint.INSTANCE;
}
}

View file

@ -0,0 +1,28 @@
/**
*
* 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.hints.provider;
import org.jivesoftware.smackx.hints.element.StoreHint;
public class StoreHintProvider extends MessageProcessingHintProvider<StoreHint> {
@Override
protected StoreHint getHint() {
return StoreHint.INSTANCE;
}
}

View file

@ -0,0 +1,20 @@
/**
*
* 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.
*/
/**
* Smack Provider for XEP-0334: Message Processing Hints.
*/
package org.jivesoftware.smackx.hints.provider;

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2016 Florian Schmaus * Copyright © 2016-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ import org.jxmpp.jid.BareJid;
public class IoTIsFriendResponse extends IQ { public class IoTIsFriendResponse extends IQ {
public static final String ELEMENT = "isFriend"; public static final String ELEMENT = "isFriendResponse";
public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE;
private final BareJid jid; private final BareJid jid;
@ -46,6 +46,7 @@ public class IoTIsFriendResponse extends IQ {
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.attribute("jid", jid); xml.attribute("jid", jid);
xml.attribute("result", result); xml.attribute("result", result);
xml.setEmptyElement();
return xml; return xml;
} }

View file

@ -96,7 +96,7 @@ public class EnablePushNotificationsIQ extends IQ {
xml.rightAngleBracket(); xml.rightAngleBracket();
if (publishOptions != null) { if (publishOptions != null) {
DataForm dataForm = new DataForm(DataForm.Type.form); DataForm dataForm = new DataForm(DataForm.Type.submit);
FormField formTypeField = new FormField("FORM_TYPE"); FormField formTypeField = new FormField("FORM_TYPE");
formTypeField.addValue(PubSub.NAMESPACE + "#publish-options"); formTypeField.addValue(PubSub.NAMESPACE + "#publish-options");

View file

@ -233,6 +233,28 @@
<className>org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider</className> <className>org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider</className>
</extensionProvider> </extensionProvider>
<!-- XEP-0334: Message Processing Hints -->
<extensionProvider>
<elementName>no-copy</elementName>
<namespace>urn:xmpp:hints</namespace>
<className>org.jivesoftware.smackx.hints.provider.NoCopyHintProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>no-permanent-store</elementName>
<namespace>urn:xmpp:hints</namespace>
<className>org.jivesoftware.smackx.hints.provider.NoPermanentStoreHintProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>no-store</elementName>
<namespace>urn:xmpp:hints</namespace>
<className>org.jivesoftware.smackx.hints.provider.NoStoreHintProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>store</elementName>
<namespace>urn:xmpp:hints</namespace>
<className>org.jivesoftware.smackx.hints.provider.StoreHintProvider</className>
</extensionProvider>
g
<!-- XEP-0363: HTTP File Upload --> <!-- XEP-0363: HTTP File Upload -->
<iqProvider> <iqProvider>
<elementName>slot</elementName> <elementName>slot</elementName>
@ -255,4 +277,11 @@
<className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className> <className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className>
</extensionProvider> </extensionProvider>
<!-- XEP-0380: Explicit Message Encryption -->
<extensionProvider>
<elementName>encryption</elementName>
<namespace>urn:xmpp:eme:0</namespace>
<className>org.jivesoftware.smackx.eme.provider.ExplicitMessageEncryptionProvider</className>
</extensionProvider>
</smackProviders> </smackProviders>

View file

@ -5,5 +5,6 @@
<className>org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager</className> <className>org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager</className>
<className>org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager</className> <className>org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager</className>
<className>org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager</className> <className>org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager</className>
<className>org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager</className>
</startupClasses> </startupClasses>
</smack> </smack>

View file

@ -0,0 +1,47 @@
/**
*
* 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.eme.provider;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol;
import org.junit.Test;
public class ExplicitMessageEncryptionProviderTest {
private static final String OX_EME_ELEMENT = "<encryption xmlns='urn:xmpp:eme:0' namespace='urn:xmpp:openpgp:0'/>";
private static final String UNKNOWN_NAMESPACE = "urn:xmpp:foobar:0";
private static final String UNKNOWN_NAME = "Foo Bar";
private static final String UNKNOWN_EME_ELEMENT = "<encryption xmlns='urn:xmpp:eme:0' namespace='" + UNKNOWN_NAMESPACE
+ "' name='" + UNKNOWN_NAME + "'/>";
@Test
public void testParseOxEmeElement() throws Exception {
ExplicitMessageEncryptionElement eme = TestUtils.parseExtensionElement(OX_EME_ELEMENT);
assertEquals(ExplicitMessageEncryptionProtocol.openpgpV0, eme.getProtocol());
}
@Test
public void testParseUnknownEmeElement() throws Exception {
ExplicitMessageEncryptionElement eme = TestUtils.parseExtensionElement(UNKNOWN_EME_ELEMENT);
assertEquals(UNKNOWN_NAMESPACE, eme.getEncryptionNamespace());
assertEquals(UNKNOWN_NAME, eme.getName());
}
}

View file

@ -31,7 +31,7 @@ public class EnablePushNotificationsIQTest {
String exampleEnableIQWithPublishOptions = "<iq id='x42' type='set'>" String exampleEnableIQWithPublishOptions = "<iq id='x42' type='set'>"
+ "<enable xmlns='urn:xmpp:push:0' jid='push-5.client.example' node='yxs32uqsflafdk3iuqo'>" + "<enable xmlns='urn:xmpp:push:0' jid='push-5.client.example' node='yxs32uqsflafdk3iuqo'>"
+ "<x xmlns='jabber:x:data' type='form'>" + "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>" + "<field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>"
+ "<field var='secret'><value>eruio234vzxc2kla-91</value></field>" + "</x>" + "</enable>" + "</iq>"; + "<field var='secret'><value>eruio234vzxc2kla-91</value></field>" + "</x>" + "</enable>" + "</iq>";

View file

@ -41,7 +41,9 @@ import org.jivesoftware.smack.chat.ChatMessageListener;
import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromMatchesFilter; import org.jivesoftware.smack.filter.FromMatchesFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter; import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.filter.MessageWithSubjectFilter; import org.jivesoftware.smack.filter.MessageWithSubjectFilter;
import org.jivesoftware.smack.filter.MessageWithThreadFilter;
import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.OrFilter; import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.PresenceTypeFilter; import org.jivesoftware.smack.filter.PresenceTypeFilter;
@ -170,10 +172,6 @@ public class MultiUserChat {
public void processStanza(Stanza packet) { public void processStanza(Stanza packet) {
Message msg = (Message) packet; Message msg = (Message) packet;
EntityFullJid from = msg.getFrom().asEntityFullJidIfPossible(); EntityFullJid from = msg.getFrom().asEntityFullJidIfPossible();
if (from == null) {
LOGGER.warning("Message subject not changed by a full JID: " + msg.getFrom());
return;
}
// Update the room subject // Update the room subject
subject = msg.getSubject(); subject = msg.getSubject();
// Fire event for subject updated listeners // Fire event for subject updated listeners
@ -322,8 +320,17 @@ public class MultiUserChat {
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter); connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter, connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter,
StanzaTypeFilter.PRESENCE)); StanzaTypeFilter.PRESENCE));
connection.addSyncStanzaListener(subjectListener, new AndFilter(fromRoomFilter, // @formatter:off
MessageWithSubjectFilter.INSTANCE, new NotFilter(MessageTypeFilter.ERROR))); connection.addSyncStanzaListener(subjectListener,
new AndFilter(fromRoomFilter,
MessageWithSubjectFilter.INSTANCE,
new NotFilter(MessageTypeFilter.ERROR),
// According to XEP-0045 § 8.1 "A message with a <subject/> and a <body/> or a <subject/> and a <thread/> is a
// legitimate message, but it SHALL NOT be interpreted as a subject change."
new NotFilter(MessageWithBodiesFilter.INSTANCE),
new NotFilter(MessageWithThreadFilter.INSTANCE))
);
// @formatter:on
connection.addSyncStanzaListener(declinesListener, DECLINE_FILTER); connection.addSyncStanzaListener(declinesListener, DECLINE_FILTER);
connection.addPacketInterceptor(presenceInterceptor, new AndFilter(ToMatchesFilter.create(room), connection.addPacketInterceptor(presenceInterceptor, new AndFilter(ToMatchesFilter.create(room),
StanzaTypeFilter.PRESENCE)); StanzaTypeFilter.PRESENCE));

View file

@ -124,7 +124,7 @@ public class Occupant {
int result; int result;
result = affiliation.hashCode(); result = affiliation.hashCode();
result = 17 * result + role.hashCode(); result = 17 * result + role.hashCode();
result = 17 * result + jid.hashCode(); result = 17 * result + (jid != null ? jid.hashCode() : 0);
result = 17 * result + (nick != null ? nick.hashCode() : 0); result = 17 * result + (nick != null ? nick.hashCode() : 0);
return result; return result;
} }

View file

@ -30,7 +30,7 @@ public interface SubjectUpdatedListener {
* Called when a MUC room has changed its subject. * Called when a MUC room has changed its subject.
* *
* @param subject the new room's subject. * @param subject the new room's subject.
* @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick). * @param from the user that changed the room's subject or <code>null</code> if the room itself changed the subject.
*/ */
public abstract void subjectUpdated(String subject, EntityFullJid from); public abstract void subjectUpdated(String subject, EntityFullJid from);

View file

@ -31,16 +31,20 @@ import org.jivesoftware.smack.AbstractConnectionClosedListener;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.SmackExecutorThreadFactory; import org.jivesoftware.smack.util.SmackExecutorThreadFactory;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ping.packet.Ping; import org.jivesoftware.smackx.ping.packet.Ping;
@ -142,6 +146,71 @@ public final class PingManager extends Manager {
maybeSchedulePingServerTask(); maybeSchedulePingServerTask();
} }
private boolean isValidErrorPong(Jid destinationJid, XMPPErrorException xmppErrorException) {
// If it is an error error response and the destination was our own service, then this must mean that the
// service responded, i.e. is up and pingable.
if (destinationJid.equals(connection().getServiceName())) {
return true;
}
final XMPPError xmppError = xmppErrorException.getXMPPError();
// We may received an error response from an intermediate service returning an error like
// 'remote-server-not-found' or 'remote-server-timeout' to us (which would fake the 'from' address,
// see RFC 6120 § 8.3.1 2.). Or the recipient could became unavailable.
// Sticking with the current rules of RFC 6120/6121, it is undecidable at this point whether we received an
// error response from the pinged entity or not. This is because a service-unavailable error condition is
// *required* (as per the RFCs) to be send back in both relevant cases:
// 1. When the receiving entity is unaware of the IQ request type. RFC 6120 § 8.4.:
// "If an intended recipient receives an IQ stanza of type "get" or
// "set" containing a child element qualified by a namespace it does
// not understand, then the entity MUST return an IQ stanza of type
// "error" with an error condition of <service-unavailable/>.
// 2. When the receiving resource is not available. RFC 6121 § 8.5.3.2.3.
// Some clients don't obey the first rule and instead send back a feature-not-implement condition with type 'cancel',
// which allows us to consider this response as valid "error response" pong.
XMPPError.Type type = xmppError.getType();
XMPPError.Condition condition = xmppError.getCondition();
return type == XMPPError.Type.CANCEL && condition == XMPPError.Condition.feature_not_implemented;
}
public SmackFuture<Boolean> pingAsync(Jid jid) {
return pingAsync(jid, connection().getReplyTimeout());
}
public SmackFuture<Boolean> pingAsync(final Jid jid, long pongTimeout) {
final InternalSmackFuture<Boolean> future = new InternalSmackFuture<Boolean>() {
@Override
public void handleStanza(Stanza packet) throws NotConnectedException, InterruptedException {
setResult(true);
}
@Override
public boolean isNonFatalException(Exception exception) {
if (exception instanceof XMPPErrorException) {
XMPPErrorException xmppErrorException = (XMPPErrorException) exception;
if (isValidErrorPong(jid, xmppErrorException)) {
setResult(true);
return true;
}
}
return false;
}
};
Ping ping = new Ping(jid);
try {
XMPPConnection connection = getAuthenticatedConnectionOrThrow();
connection.sendIqWithResponseCallback(ping, future, future, pongTimeout);
}
catch (NotLoggedInException | NotConnectedException | InterruptedException e) {
future.processException(e);
}
return future;
}
/** /**
* Pings the given jid. This method will return false if an error occurs. The exception * Pings the given jid. This method will return false if an error occurs. The exception
* to this, is a server ping, which will always return true if the server is reachable, * to this, is a server ping, which will always return true if the server is reachable,
@ -168,8 +237,8 @@ public final class PingManager extends Manager {
try { try {
connection.createStanzaCollectorAndSend(ping).nextResultOrThrow(pingTimeout); connection.createStanzaCollectorAndSend(ping).nextResultOrThrow(pingTimeout);
} }
catch (XMPPException exc) { catch (XMPPErrorException e) {
return jid.equals(connection.getXMPPServiceDomain()); return isValidErrorPong(jid, e);
} }
return true; return true;
} }

View file

@ -608,9 +608,7 @@ abstract public class Node
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
public void processStanza(Stanza packet) public void processStanza(Stanza packet)
{ {
// CHECKSTYLE:OFF
EventElement event = (EventElement) packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); EventElement event = (EventElement) packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
// CHECKSTYLE:ON
ItemsExtension itemsElem = (ItemsExtension) event.getEvent(); ItemsExtension itemsElem = (ItemsExtension) event.getEvent();
ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), itemsElem.getItems(), getSubscriptionIds(packet), DelayInformationManager.getDelayTimestamp(packet)); ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), itemsElem.getItems(), getSubscriptionIds(packet), DelayInformationManager.getDelayTimestamp(packet));
listener.handlePublishedItems(eventItems); listener.handlePublishedItems(eventItems);

View file

@ -0,0 +1,53 @@
/**
*
* 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.jivesoftware.smack.SmackException;
import org.jxmpp.jid.BareJid;
public abstract class PubSubException extends SmackException {
/**
*
*/
private static final long serialVersionUID = 1L;
public static class NotALeafNodeException extends PubSubException {
/**
*
*/
private static final long serialVersionUID = 1L;
private final String nodeId;
private final BareJid pubSubService;
NotALeafNodeException(String nodeId, BareJid pubSubService) {
this.nodeId = nodeId;
this.pubSubService = pubSubService;
}
public String getNodeId() {
return nodeId;
}
public BareJid getPubSubService() {
return pubSubService;
}
}
}

View file

@ -16,8 +16,6 @@
*/ */
package org.jivesoftware.smackx.pubsub; package org.jivesoftware.smackx.pubsub;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -42,6 +40,7 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
import org.jivesoftware.smackx.pubsub.util.NodeUtils; import org.jivesoftware.smackx.pubsub.util.NodeUtils;
@ -64,6 +63,8 @@ import org.jxmpp.stringprep.XmppStringprepException;
*/ */
public final class PubSubManager extends Manager { public final class PubSubManager extends Manager {
public static final String AUTO_CREATE_FEATURE = "http://jabber.org/protocol/pubsub#auto-create";
private static final Logger LOGGER = Logger.getLogger(PubSubManager.class.getName()); private static final Logger LOGGER = Logger.getLogger(PubSubManager.class.getName());
private static final Map<XMPPConnection, Map<BareJid, PubSubManager>> INSTANCES = new WeakHashMap<>(); private static final Map<XMPPConnection, Map<BareJid, PubSubManager>> INSTANCES = new WeakHashMap<>();
@ -267,10 +268,11 @@ public final class PubSubManager extends Manager {
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
* @throws XMPPErrorException * @throws XMPPErrorException
* @throws NotALeafNodeException in case the node already exists as collection node.
* @since 4.2.1 * @since 4.2.1
*/ */
public LeafNode getOrCreateLeafNode(final String id) public LeafNode getOrCreateLeafNode(final String id)
throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException { throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException, NotALeafNodeException {
try { try {
return getNode(id); return getNode(id);
} }
@ -287,9 +289,7 @@ public final class PubSubManager extends Manager {
throw e2; throw e2;
} }
} }
throw e1; if (e1.getXMPPError().getCondition() == Condition.service_unavailable) {
}
catch (PubSubAssertionError.DiscoInfoNodeAssertionError e) {
// This could be caused by Prosody bug #805 (see https://prosody.im/issues/issue/805). Prosody does not // This could be caused by Prosody bug #805 (see https://prosody.im/issues/issue/805). Prosody does not
// answer to disco#info requests on the node ID, which makes it undecidable if a node is a leaf or // answer to disco#info requests on the node ID, which makes it undecidable if a node is a leaf or
// collection node. // collection node.
@ -297,32 +297,107 @@ public final class PubSubManager extends Manager {
+ " threw an DiscoInfoNodeAssertionError, trying workaround for Prosody bug #805 (https://prosody.im/issues/issue/805)"); + " threw an DiscoInfoNodeAssertionError, trying workaround for Prosody bug #805 (https://prosody.im/issues/issue/805)");
return getOrCreateLeafNodeProsodyWorkaround(id); return getOrCreateLeafNodeProsodyWorkaround(id);
} }
throw e1;
}
}
/**
* Try to get a leaf node with the given node ID.
*
* @param id the node ID.
* @return the requested leaf node.
* @throws NotALeafNodeException in case the node exists but is a collection node.
* @throws NoResponseException
* @throws NotConnectedException
* @throws InterruptedException
* @throws XMPPErrorException
* @since 4.2.1
*/
public LeafNode getLeafNode(String id) throws NotALeafNodeException, NoResponseException, NotConnectedException,
InterruptedException, XMPPErrorException {
Node node;
try {
node = getNode(id);
}
catch (XMPPErrorException e) {
if (e.getXMPPError().getCondition() == Condition.service_unavailable) {
// This could be caused by Prosody bug #805 (see https://prosody.im/issues/issue/805). Prosody does not
// answer to disco#info requests on the node ID, which makes it undecidable if a node is a leaf or
// collection node.
return getLeafNodeProsodyWorkaround(id);
}
throw e;
}
if (node instanceof LeafNode) {
return (LeafNode) node;
}
throw new PubSubException.NotALeafNodeException(id, pubSubService);
}
private LeafNode getLeafNodeProsodyWorkaround(final String id) throws NoResponseException, NotConnectedException,
InterruptedException, NotALeafNodeException, XMPPErrorException {
LeafNode leafNode = new LeafNode(this, id);
try {
// Try to ensure that this is not a collection node by asking for one item form the node.
leafNode.getItems(1);
} catch (XMPPErrorException e) {
Condition condition = e.getXMPPError().getCondition();
if (condition == Condition.feature_not_implemented) {
// XEP-0060 § 6.5.9.5: Item retrieval not supported, e.g. because node is a collection node
throw new PubSubException.NotALeafNodeException(id, pubSubService);
}
throw e;
}
nodeMap.put(id, leafNode);
return leafNode;
} }
private LeafNode getOrCreateLeafNodeProsodyWorkaround(final String id) private LeafNode getOrCreateLeafNodeProsodyWorkaround(final String id)
throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException { throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException, NotALeafNodeException {
try { try {
return createNode(id); return createNode(id);
} }
catch (XMPPErrorException e1) { catch (XMPPErrorException e1) {
if (e1.getXMPPError().getCondition() == Condition.conflict) { if (e1.getXMPPError().getCondition() == Condition.conflict) {
Constructor<?> constructor = LeafNode.class.getDeclaredConstructors()[0]; return getLeafNodeProsodyWorkaround(id);
constructor.setAccessible(true);
LeafNode res;
try {
res = (LeafNode) constructor.newInstance(this, id);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e2) {
throw new AssertionError(e2);
}
// TODO: How to verify that this is actually a leafe node and not a conflict with a collection node?
return res;
} }
throw e1; throw e1;
} }
} }
/**
* Try to publish an item and, if the node with the given ID does not exists, auto-create the node.
* <p>
* Not every PubSub service supports automatic node creation. You can discover if this service supports it by using
* {@link #supportsAutomaticNodeCreation()}.
* </p>
*
* @param id The unique id of the node.
* @param item The item to publish.
* @return the LeafNode on which the item was published.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @since 4.2.1
*/
public <I extends Item> LeafNode tryToPublishAndPossibleAutoCreate(String id, I item)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
LeafNode leafNode = new LeafNode(this, id);
leafNode.publish(item);
// If LeafNode.publish() did not throw then we have successfully published an item and possible auto-created
// (XEP-0163 § 3., XEP-0060 § 7.1.4) the node. So we can put the node into the nodeMap.
nodeMap.put(id, leafNode);
return leafNode;
}
/** /**
* Get all the nodes that currently exist as a child of the specified * Get all the nodes that currently exist as a child of the specified
* collection node. If the service does not support collection nodes * collection node. If the service does not support collection nodes
@ -440,6 +515,23 @@ public final class PubSubManager extends Manager {
return mgr.discoverInfo(pubSubService); return mgr.discoverInfo(pubSubService);
} }
/**
* Check if the PubSub service supports automatic node creation.
*
* @return true if the PubSub service supports automatic node creation.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @since 4.2.1
* @see <a href="https://xmpp.org/extensions/xep-0060.html#publisher-publish-autocreate">XEP-0060 § 7.1.4 Automatic Node Creation</a>
*/
public boolean supportsAutomaticNodeCreation()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
return sdm.supportsFeature(pubSubService, AUTO_CREATE_FEATURE);
}
/** /**
* Check if it is possible to create PubSub nodes on this service. It could be possible that the * Check if it is possible to create PubSub nodes on this service. It could be possible that the
* PubSub service allows only certain XMPP entities (clients) to create nodes and publish items * PubSub service allows only certain XMPP entities (clients) to create nodes and publish items

View file

@ -26,7 +26,9 @@ import org.jivesoftware.smack.ThreadedDummyConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.XMPPError.Condition; import org.jivesoftware.smack.packet.XMPPError.Condition;
import org.jivesoftware.smackx.InitExtensions;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity;
import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSub;
@ -39,7 +41,7 @@ import org.junit.Test;
* @author Robin Collier * @author Robin Collier
* *
*/ */
public class ConfigureFormTest public class ConfigureFormTest extends InitExtensions
{ {
@Test @Test
public void checkChildrenAssocPolicy() public void checkChildrenAssocPolicy()
@ -55,6 +57,8 @@ public class ConfigureFormTest
ThreadedDummyConnection con = ThreadedDummyConnection.newInstance(); ThreadedDummyConnection con = ThreadedDummyConnection.newInstance();
PubSubManager mgr = new PubSubManager(con, PubSubManagerTest.DUMMY_PUBSUB_SERVICE); PubSubManager mgr = new PubSubManager(con, PubSubManagerTest.DUMMY_PUBSUB_SERVICE);
DiscoverInfo info = new DiscoverInfo(); DiscoverInfo info = new DiscoverInfo();
info.setType(Type.result);
info.setFrom(PubSubManagerTest.DUMMY_PUBSUB_SERVICE);
Identity ident = new Identity("pubsub", null, "leaf"); Identity ident = new Identity("pubsub", null, "leaf");
info.addIdentity(ident); info.addIdentity(ident);
con.addIQReply(info); con.addIQReply(info);
@ -62,6 +66,8 @@ public class ConfigureFormTest
Node node = mgr.getNode("princely_musings"); Node node = mgr.getNode("princely_musings");
PubSub errorIq = new PubSub(); PubSub errorIq = new PubSub();
errorIq.setType(Type.error);
errorIq.setFrom(PubSubManagerTest.DUMMY_PUBSUB_SERVICE);
XMPPError.Builder error = XMPPError.getBuilder(Condition.forbidden); XMPPError.Builder error = XMPPError.getBuilder(Condition.forbidden);
errorIq.setError(error); errorIq.setError(error);
con.addIQReply(errorIq); con.addIQReply(errorIq);

View file

@ -283,14 +283,46 @@ public final class RosterEntry extends Manager {
return other.item.equals(this.item); return other.item.equals(this.item);
} }
/**
* Convert the RosterEntry to a Roster stanza &lt;item/&gt; element.
*
* @param entry the roster entry.
* @return the roster item.
*/
static RosterPacket.Item toRosterItem(RosterEntry entry) { static RosterPacket.Item toRosterItem(RosterEntry entry) {
return toRosterItem(entry, entry.getName()); return toRosterItem(entry, entry.getName(), false);
} }
private static RosterPacket.Item toRosterItem(RosterEntry entry, String name) { /**
* Convert the RosterEntry to a Roster stanza &lt;item/&gt; element.
*
* @param entry the roster entry
* @param name the name of the roster item.
* @return the roster item.
*/
static RosterPacket.Item toRosterItem(RosterEntry entry, String name) {
return toRosterItem(entry, name, false);
}
static RosterPacket.Item toRosterItem(RosterEntry entry, boolean includeAskAttribute) {
return toRosterItem(entry, entry.getName(), includeAskAttribute);
}
/**
* Convert a roster entry with the given name to a roster item. As per RFC 6121 § 2.1.2.2., clients MUST NOT include
* the 'ask' attribute, thus set {@code includeAskAttribute} to {@code false}.
*
* @param entry the roster entry.
* @param name the name of the roster item.
* @param includeAskAttribute whether or not to include the 'ask' attribute.
* @return the roster item.
*/
private static RosterPacket.Item toRosterItem(RosterEntry entry, String name, boolean includeAskAttribute) {
RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), name); RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), name);
item.setItemType(entry.getType()); item.setItemType(entry.getType());
if (includeAskAttribute) {
item.setSubscriptionPending(entry.isSubscriptionPending()); item.setSubscriptionPending(entry.isSubscriptionPending());
}
item.setApproved(entry.isApproved()); item.setApproved(entry.isApproved());
// Set the correct group names for the item. // Set the correct group names for the item.
for (RosterGroup group : entry.getGroups()) { for (RosterGroup group : entry.getGroups()) {

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,10 +18,23 @@ package org.jivesoftware.smackx.ping;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jxmpp.jid.Jid;
public class PingIntegrationTest extends AbstractSmackIntegrationTest { public class PingIntegrationTest extends AbstractSmackIntegrationTest {
@ -35,4 +48,52 @@ public class PingIntegrationTest extends AbstractSmackIntegrationTest {
assertTrue(pingManager.pingMyServer()); assertTrue(pingManager.pingMyServer());
} }
private static final class Pinger implements Runnable {
private final List<Jid> toPing;
private final Collection<Future<Boolean>> pongFutures;
private final PingManager pingManager;
private Pinger(XMPPConnection connection, Collection<Future<Boolean>> pongFutures, Jid... toPing) {
this(connection, pongFutures, Arrays.asList(toPing));
}
private Pinger(XMPPConnection connection, Collection<Future<Boolean>> pongFutures, List<Jid> toPing) {
this.toPing = toPing;
this.pongFutures = pongFutures;
this.pingManager = PingManager.getInstanceFor(connection);
}
@Override
public void run() {
List<Future<Boolean>> futures = new ArrayList<>();
for (Jid jid : toPing) {
Future<Boolean> future = pingManager.pingAsync(jid);
futures.add(future);
}
pongFutures.addAll(futures);
}
}
@SmackIntegrationTest
public void pingAsync() throws InterruptedException, ExecutionException {
List<Future<Boolean>> pongFutures = Collections.synchronizedList(new ArrayList<Future<Boolean>>());
Runnable[] pinger = new Runnable[3];
pinger[0] = new Pinger(conOne, pongFutures, conTwo.getUser(), conThree.getUser());
pinger[1] = new Pinger(conTwo, pongFutures, conOne.getUser(), conThree.getUser());
pinger[2] = new Pinger(conThree, pongFutures, conOne.getUser(), conTwo.getUser());
ExecutorService executorService = Executors.newFixedThreadPool(pinger.length);
for (Runnable runnable : pinger) {
executorService.execute(runnable);
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
for (Future<Boolean> pongFuture : pongFutures) {
assertTrue(pongFuture.get());
}
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014 Florian Schmaus * Copyright 2014-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -115,8 +115,7 @@ public class SASLDigestMD5Mechanism extends SASLMechanism {
switch (state) { switch (state) {
case INITIAL: case INITIAL:
for (String part : challengeParts) { for (String part : challengeParts) {
String[] keyValue = part.split("="); String[] keyValue = part.split("=", 2);
assert (keyValue.length == 2);
String key = keyValue[0]; String key = keyValue[0];
// RFC 2831 § 7.1 about the formating of the digest-challenge: // RFC 2831 § 7.1 about the formating of the digest-challenge:
// "The full form is "<n>#<m>element" indicating at least <n> and // "The full form is "<n>#<m>element" indicating at least <n> and

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright © 2014-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.