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

Compare commits

..

16 commits

Author SHA1 Message Date
Florian Schmaus
f0300dc906 Remove deprecated "PacketReplyTimeout" methods in SmackConfiguration 2019-09-25 23:45:29 +02:00
Florian Schmaus
35cf094386 sasl-javax: Do not set system properties in SASLGSSAPIMechanism
Smack did this for a long time, since eb56f8a55 ("GSSAPI work by Jay
Kline (SMACK-218)."). Not always in a static block though. But
irregardless this is bad practice as it causes side-effects and may
overrides settings.

For example, one users reports:

java.lang.SecurityException: java.io.IOException: gss.conf (No such file or directory)
    at sun.security.provider.ConfigFile$Spi.<init>(ConfigFile.java:137)
    at sun.security.provider.ConfigFile.<init>(ConfigFile.java:102)
    at sun.reflect.GeneratedConstructorAccessor119.newInstance(Unknown Source)
2019-09-25 23:36:21 +02:00
Florian Schmaus
eeb6c52f7e Move SASL logic into AbstractXMPPConnection
Besides the way the transport handles the stream after SASL
<success/>, the SASL logic is independend from the underlying
transport (BOSH, TCP, …). Hence move it up into
AbstractXMPPConnection.

This also has the benefit that we can make some more methods private
or package-private.

Also introduce XmlStringBuilder.optTextChild(), which causes some
associated changes.
2019-09-25 13:49:21 +02:00
Florian Schmaus
c3247ef006 Use optElement() instead of optAppend() in StreamInitiation.toXML() 2019-09-24 23:29:26 +02:00
Florian Schmaus
cf48f12565 Add INSTANCE shortcut to SmackTestUtil 2019-09-24 23:29:00 +02:00
Florian Schmaus
3ee4dd7b3a Move SASL authentication state into SASLMechanism 2019-09-24 10:19:12 +02:00
Florian Schmaus
1d03cfdc79 checkstyle: Remove redundant whitespace check
The "line contains only whitespace character(s)" check is the same as
the trailing whitespace characters check a few lines below.
2019-09-24 10:12:13 +02:00
Florian Schmaus
93aaf6d8d7 Remove SASLAuthentication.init() 2019-09-23 20:01:10 +02:00
Florian Schmaus
bf538129c2 SASL: Add missing InterruptedException to "else if" cascade 2019-09-23 19:54:47 +02:00
Florian Schmaus
5b1d2664af Clear saslException class field before throwing
Thanks to PolFW, who writes:

A SASLErrorException is thrown before we have received the "<success
xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>". This is because
SASLAuthentication is keeping a reference to a "saslException" so to
clear that after a authentication failure we have to invoke again
connect on the xmpptcpconnection to initialize again the
saslAuthentication. But it doesn't solve the issue because an
AlreadyConnectedException is thrown before the initialisation of the
"this.saslAuthentication.init();"

Note that the user uses one time tokens for authentication.
2019-09-23 19:47:37 +02:00
Florian Schmaus
be0830fc8f smack-xmlparser: Improve ISE message 2019-09-23 16:12:48 +02:00
Florian Schmaus
28ef30b4b3 smack-java7: Add missing smack-xmlparser-stax dependency
Since 2f667f95a ("gradle: Remove archives configuration") the

:smack-repl:printXmppNioTcpConnectionStateGraph

JavaExec task failed with

> Task :smack-repl:printXmppNioTcpConnectionStateGraph FAILED
Exception in thread "main" java.lang.ExceptionInInitializerError
        at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.java:108)
        at org.jivesoftware.smack.AbstractXMPPConnection.<clinit>(AbstractXMPPConnection.java:187)
        at org.igniterealtime.smack.smackrepl.StateGraph.main(StateGraph.java:37)
Caused by: java.lang.IllegalStateException: Could not parse Smack configuration file
        at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:100)
        ... 3 more
Caused by: java.lang.IllegalStateException: Could not load a XmlPullParserFactory via Service Provider Interface (SPI)
        at org.jivesoftware.smack.xml.SmackXmlParser.getXmlPullParserFactory(SmackXmlParser.java:34)
        at org.jivesoftware.smack.xml.SmackXmlParser.newXmlParser(SmackXmlParser.java:53)
        at org.jivesoftware.smack.util.PacketParserUtils.getParserFor(PacketParserUtils.java:76)
        at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:144)
        at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:139)
        at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:97)
        ... 3 more

because no XmlPullParser was registered via SPI. The 'archives'
configuration which was removed with 2f667f95a ("gradle: Remove
archives configuration"), previously pulled in the parser.

Just like smack-android delcares a dependency on smack-xmlparser-xpp3,
smack-java7 should declare a dependency on smack-xmlparser-stax.
2019-09-23 16:12:48 +02:00
Florian Schmaus
b1a5509927 smack-tcp: Split dot to png into two makefile steps
Since using a pipe, as we did previously would not error the target if
the first command in the pipe fails.

It is still far from ideal, since the dot file is also generated if
the gradle command fails. At some point, this should probably become
part of gradle build step instead of shelling out to a Makefile.
2019-09-23 16:12:48 +02:00
Florian Schmaus
bd4b91fc26 Introduce AbstractXMPPConnection.outgoingStreamXmlEnvironment 2019-09-23 16:12:48 +02:00
Florian Schmaus
002d060584 XmlStringBuilder: Map all XML serialization to appendXmlTo()
this is now the single place where serializatin happens.
2019-09-23 16:12:48 +02:00
Florian Schmaus
6d7b2b70e8 Add util.Consumer and use it in StateDescriptorGraph 2019-09-23 16:12:48 +02:00
35 changed files with 662 additions and 428 deletions

View file

@ -35,10 +35,6 @@
<property name="format" value="\.append\(&quot;(.|\\.)&quot;\)"/>
<property name="message" value="Don&apos;t use StringBuilder.append(String) when you can use StringBuilder.append(char). Solution: Replace double quotes of append&apos;s argument with single quotes."/>
</module>
<module name="RegexpSingleline">
<property name="format" value="^\s+$"/>
<property name="message" value="Line containing only whitespace character(s)"/>
</module>
<module name="FileTabCharacter"/>
<module name="RegexpSingleline">
<!--

View file

@ -29,6 +29,7 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
@ -39,8 +40,6 @@ import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.CloseableUtil;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -218,7 +217,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
SmackException, IOException, InterruptedException {
// Authenticate using SASL
saslAuthentication.authenticate(username, password, config.getAuthzid(), null);
authenticate(username, password, config.getAuthzid(), null);
bindResourceAndEstablishSession(resource);
@ -395,6 +394,26 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
readerConsumer.start();
}
@Override
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
// requires a special XML element ot be send after successful SASL authentication.
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
ComposableBody composeableBody = ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build();
try {
send(composeableBody);
} catch (BOSHException e) {
// jbosh's exception API does not really match the one of Smack.
throw new SmackException.SmackWrappedException(e);
}
}
/**
* A listener class which listen for a successfully established connection
* and connection errors and notifies the BOSHConnection.
@ -490,30 +509,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
case Presence.ELEMENT:
parseAndProcessStanza(parser);
break;
case "challenge":
// The server is challenging the SASL authentication
// made by the client
final String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case "success":
send(ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build());
Success success = new Success(parser.nextText());
getSASLAuthentication().authenticated(success);
break;
case "features":
parseFeatures(parser);
break;
case "failure":
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
getSASLAuthentication().authenticationFailed(failure);
}
break;
case "error":
// Some BOSH error isn't stream error.
if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) {
@ -522,6 +520,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
StanzaError.Builder builder = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, builder.build());
}
default:
parseAndProcessNonza(parser);
break;
}
break;
default:

View file

@ -60,6 +60,7 @@ import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.callback.Callback;
@ -78,6 +79,7 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
@ -113,9 +115,14 @@ import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
@ -127,6 +134,7 @@ import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
@ -235,7 +243,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private XmlEnvironment incomingStreamXmlEnvironment;
final Map<QName, NonzaCallback> nonzaCallbacks = new HashMap<>();
protected XmlEnvironment outgoingStreamXmlEnvironment;
final MultiMap<QName, NonzaCallback> nonzaCallbacksMap = new MultiMap<>();
protected final Lock connectionLock = new ReentrantLock();
@ -306,7 +316,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* The SASLAuthentication manager that is responsible for authenticating with the server.
*/
protected final SASLAuthentication saslAuthentication;
private final SASLAuthentication saslAuthentication;
/**
* A number to uniquely identify connections that are created. This is distinct from the
@ -400,6 +410,26 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
saslAuthentication = new SASLAuthentication(this, configuration);
config = configuration;
// Install the SASL Nonza callbacks.
buildNonzaCallback()
.listenFor(SaslNonza.Challenge.class, c -> {
try {
saslAuthentication.challengeReceived(c);
} catch (SmackException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.Success.class, s -> {
try {
saslAuthentication.authenticated(s);
} catch (SmackSaslException | NotConnectedException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.SASLFailure.class, f -> saslAuthentication.authenticationFailed(f))
.install();
SmackDebuggerFactory debuggerFactory = configuration.getDebuggerFactory();
if (debuggerFactory != null) {
debugger = debuggerFactory.create(this);
@ -498,7 +528,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Reset the connection state
initState();
saslAuthentication.init();
streamId = null;
try {
@ -809,14 +838,50 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
/**
* Returns the SASLAuthentication manager that is responsible for authenticating with
* the server.
* Authenticate a connection.
*
* @return the SASLAuthentication manager that is responsible for authenticating with
* the server.
* @param username the username that is authenticating with the server.
* @param password the password to send to the server.
* @param authzid the authorization identifier (typically null).
* @param sslSession the optional SSL/TLS session (if one was established)
* @return the used SASLMechanism.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws IOException if an I/O error occured.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackSaslException if a SASL specific error occured.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
* @throws SmackWrappedException in case of an exception.
* @see SASLAuthentication#authenticate(String, String, EntityBareJid, SSLSession)
*/
protected SASLAuthentication getSASLAuthentication() {
return saslAuthentication;
protected final SASLMechanism authenticate(String username, String password, EntityBareJid authzid,
SSLSession sslSession) throws XMPPErrorException, SASLErrorException, SmackSaslException,
NotConnectedException, NoResponseException, IOException, InterruptedException, SmackWrappedException {
SASLMechanism saslMechanism = saslAuthentication.authenticate(username, password, authzid, sslSession);
afterSaslAuthenticationSuccess();
return saslMechanism;
}
/**
* Hook for subclasses right after successful SASL authentication. RFC 6120 § 6.4.6. specifies a that the initiating
* entity, needs to initiate a new stream in this case. But some transports, like BOSH, requires a special handling.
* <p>
* Note that we can not reset XMPPTCPConnection's parser here, because this method is invoked by the thread calling
* {@link #login()}, but the parser reset has to be done within the reader thread.
* </p>
*
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackWrappedException in case of an exception.
*/
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
sendStreamOpen();
}
protected final boolean isSaslAuthenticated() {
return saslAuthentication.authenticationSuccessful();
}
/**
@ -1224,6 +1289,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
protected final void parseAndProcessNonza(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
final String element = parser.getName();
final String namespace = parser.getNamespace();
final QName key = new QName(namespace, element);
@ -1231,22 +1299,27 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key);
if (nonzaProvider == null) {
LOGGER.severe("Unknown nonza: " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return;
}
NonzaCallback nonzaCallback;
synchronized (nonzaCallbacks) {
nonzaCallback = nonzaCallbacks.get(key);
List<NonzaCallback> nonzaCallbacks;
synchronized (nonzaCallbacksMap) {
nonzaCallbacks = nonzaCallbacksMap.getAll(key);
nonzaCallbacks = CollectionUtil.newListWith(nonzaCallbacks);
}
if (nonzaCallback == null) {
if (nonzaCallbacks == null) {
LOGGER.info("No nonza callback for " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return;
}
Nonza nonza = nonzaProvider.parse(parser, incomingStreamXmlEnvironment);
for (NonzaCallback nonzaCallback : nonzaCallbacks) {
nonzaCallback.onNonzaReceived(nonza);
}
}
protected void parseAndProcessStanza(XmlPullParser parser)
throws XmlPullParserException, IOException, InterruptedException {
@ -2017,7 +2090,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
from = XmppStringUtils.completeJidFrom(localpart, to);
}
String id = getStreamId();
sendNonza(new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client));
StreamOpen streamOpen = new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client);
sendNonza(streamOpen);
XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder();
xmlEnvironmentBuilder.with(streamOpen);
outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build();
}
public static final class SmackTlsContext {

View file

@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ -30,7 +31,7 @@ import org.jivesoftware.smack.util.XmppElementUtil;
public class NonzaCallback {
protected final AbstractXMPPConnection connection;
protected final Map<QName, GenericElementListener<? extends Nonza>> filterAndListeners;
protected final Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners;
private NonzaCallback(Builder builder) {
this.connection = builder.connection;
@ -38,21 +39,18 @@ public class NonzaCallback {
install();
}
void onNonzaReceived(Nonza nonza) {
void onNonzaReceived(Nonza nonza) throws IOException {
QName key = nonza.getQName();
GenericElementListener<? extends Nonza> nonzaListener = filterAndListeners.get(key);
ClassAndConsumer<? extends Nonza> classAndConsumer = filterAndListeners.get(key);
nonzaListener.processElement(nonza);
classAndConsumer.accept(nonza);
}
public void cancel() {
synchronized (connection.nonzaCallbacks) {
for (Map.Entry<QName, GenericElementListener<? extends Nonza>> entry : filterAndListeners.entrySet()) {
for (Map.Entry<QName, ClassAndConsumer<? extends Nonza>> entry : filterAndListeners.entrySet()) {
QName filterKey = entry.getKey();
NonzaCallback installedCallback = connection.nonzaCallbacks.get(filterKey);
if (equals(installedCallback)) {
connection.nonzaCallbacks.remove(filterKey);
}
synchronized (connection.nonzaCallbacksMap) {
connection.nonzaCallbacksMap.removeOne(filterKey, this);
}
}
}
@ -62,9 +60,9 @@ public class NonzaCallback {
return;
}
synchronized (connection.nonzaCallbacks) {
for (QName key : filterAndListeners.keySet()) {
connection.nonzaCallbacks.put(key, this);
synchronized (connection.nonzaCallbacksMap) {
connection.nonzaCallbacksMap.put(key, this);
}
}
}
@ -74,31 +72,35 @@ public class NonzaCallback {
private SN successNonza;
private FN failedNonza;
private NonzaResponseCallback(Class<? extends SN> successNonzaClass, Class<? extends FN> failedNonzaClass,
private NonzaResponseCallback(Class<SN> successNonzaClass, Class<FN> failedNonzaClass,
Builder builder) {
super(builder);
final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass);
final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass);
final GenericElementListener<SN> successListener = new GenericElementListener<SN>(successNonzaClass) {
final NonzaListener<SN> successListener = new NonzaListener<SN>() {
@Override
public void process(SN successNonza) {
public void accept(SN successNonza) {
NonzaResponseCallback.this.successNonza = successNonza;
notifyResponse();
}
};
final ClassAndConsumer<SN> successClassAndConsumer = new ClassAndConsumer<>(successNonzaClass,
successListener);
final GenericElementListener<FN> failedListener = new GenericElementListener<FN>(failedNonzaClass) {
final NonzaListener<FN> failedListener = new NonzaListener<FN>() {
@Override
public void process(FN failedNonza) {
public void accept(FN failedNonza) {
NonzaResponseCallback.this.failedNonza = failedNonza;
notifyResponse();
}
};
final ClassAndConsumer<FN> failedClassAndConsumer = new ClassAndConsumer<>(failedNonzaClass,
failedListener);
filterAndListeners.put(successNonzaKey, successListener);
filterAndListeners.put(failedNonzaKey, failedListener);
filterAndListeners.put(successNonzaKey, successClassAndConsumer);
filterAndListeners.put(failedNonzaKey, failedClassAndConsumer);
install();
}
@ -139,15 +141,16 @@ public class NonzaCallback {
public static final class Builder {
private final AbstractXMPPConnection connection;
private Map<QName, GenericElementListener<? extends Nonza>> filterAndListeners = new HashMap<>();
private Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners = new HashMap<>();
Builder(AbstractXMPPConnection connection) {
this.connection = connection;
}
public <N extends Nonza> Builder listenFor(Class<? extends N> nonza, GenericElementListener<? extends N> nonzaListener) {
public <N extends Nonza> Builder listenFor(Class<N> nonza, NonzaListener<N> nonzaListener) {
QName key = XmppElementUtil.getQNameFor(nonza);
filterAndListeners.put(key, nonzaListener);
ClassAndConsumer<N> classAndConsumer = new ClassAndConsumer<>(nonza, nonzaListener);
filterAndListeners.put(key, classAndConsumer);
return this;
}
@ -156,6 +159,25 @@ public class NonzaCallback {
}
}
public interface NonzaListener<N extends Nonza> {
void accept(N nonza) throws IOException;
}
private static final class ClassAndConsumer<N extends Nonza> {
private final Class<N> clazz;
private final NonzaListener<N> consumer;
private ClassAndConsumer(Class<N> clazz, NonzaListener<N> consumer) {
this.clazz = clazz;
this.consumer = consumer;
}
private void accept(Object object) throws IOException {
N nonza = clazz.cast(object);
consumer.accept(nonza);
}
}
static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass,
Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {

View file

@ -39,8 +39,9 @@ import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Success;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
@ -154,20 +155,9 @@ public final class SASLAuthentication {
private final ConnectionConfiguration configuration;
private SASLMechanism currentMechanism = null;
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackException} or {@link SASLErrorException}
*/
private Exception saslException;
SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) {
this.configuration = configuration;
this.connection = connection;
this.init();
}
/**
@ -191,23 +181,26 @@ public final class SASLAuthentication {
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
*/
public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
throws XMPPErrorException, SASLErrorException, IOException,
InterruptedException, SmackSaslException, NotConnectedException, NoResponseException {
currentMechanism = selectMechanism(authzid);
final SASLMechanism mechanism = selectMechanism(authzid);
final CallbackHandler callbackHandler = configuration.getCallbackHandler();
final String host = connection.getHost();
final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain();
synchronized (this) {
currentMechanism = mechanism;
if (callbackHandler != null) {
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession);
}
else {
currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession);
}
final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
while (!authenticationSuccessful && saslException == null) {
while (!mechanism.isFinished()) {
final long now = System.currentTimeMillis();
if (now >= deadline) break;
// Wait until SASL negotiation finishes
@ -215,35 +208,21 @@ public final class SASLAuthentication {
}
}
if (saslException != null) {
if (saslException instanceof SmackSaslException) {
throw (SmackSaslException) saslException;
} else if (saslException instanceof SASLErrorException) {
throw (SASLErrorException) saslException;
} else if (saslException instanceof NotConnectedException) {
throw (NotConnectedException) saslException;
} else {
throw new IllegalStateException("Unexpected exception type" , saslException);
}
}
mechanism.throwExceptionIfRequired();
if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
return currentMechanism;
return mechanism;
}
/**
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
* to <code>false</code>.
*
* @param challenge a base64 encoded string representing the challenge.
* @param challenge the challenge Nonza.
* @throws SmackException if Smack detected an exceptional situation.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void challengeReceived(String challenge) throws SmackException, InterruptedException {
challengeReceived(challenge, false);
void challengeReceived(SaslNonza.Challenge challenge) throws SmackException, InterruptedException {
challengeReceived(challenge.getData(), false);
}
/**
@ -258,13 +237,12 @@ public final class SASLAuthentication {
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException {
try {
currentMechanism.challengeReceived(challenge, finalChallenge);
} catch (InterruptedException | SmackSaslException | NotConnectedException e) {
authenticationFailed(e);
throw e;
private void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException {
SASLMechanism mechanism;
synchronized (this) {
mechanism = currentMechanism;
}
mechanism.challengeReceived(challenge, finalChallenge);
}
/**
@ -273,8 +251,10 @@ public final class SASLAuthentication {
* @param success result of the authentication.
* @throws SmackException if Smack detected an exceptional situation.
* @throws InterruptedException if the calling thread was interrupted.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws SmackSaslException if a SASL specific error occured.
*/
public void authenticated(Success success) throws SmackException, InterruptedException {
void authenticated(Success success) throws InterruptedException, SmackSaslException, NotConnectedException {
// RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP
// "receiving entity") can include "additional data with success" if appropriate for the
// SASL mechanism in use. In XMPP, this is done by including the additional data as the XML
@ -283,10 +263,11 @@ public final class SASLAuthentication {
if (success.getData() != null) {
challengeReceived(success.getData(), true);
}
currentMechanism.checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
// Wake up the thread that is waiting in the #authenticate method
synchronized (this) {
currentMechanism.afterFinalSaslChallenge();
notify();
}
}
@ -298,30 +279,29 @@ public final class SASLAuthentication {
* @param saslFailure the SASL failure as reported by the server
* @see <a href="https://tools.ietf.org/html/rfc6120#section-6.5">RFC6120 6.5</a>
*/
public void authenticationFailed(SASLFailure saslFailure) {
authenticationFailed(new SASLErrorException(currentMechanism.getName(), saslFailure));
void authenticationFailed(SASLFailure saslFailure) {
SASLErrorException saslErrorException;
synchronized (this) {
saslErrorException = new SASLErrorException(currentMechanism.getName(), saslFailure);
}
authenticationFailed(saslErrorException);
}
private void authenticationFailed(Exception exception) {
saslException = exception;
void authenticationFailed(Exception exception) {
// Wake up the thread that is waiting in the #authenticate method
synchronized (this) {
currentMechanism.setException(exception);
notify();
}
}
public boolean authenticationSuccessful() {
return authenticationSuccessful;
synchronized (this) {
if (currentMechanism == null) {
return false;
}
return currentMechanism.isAuthenticationSuccessful();
}
/**
* Initializes the internal state in order to be able to be reused. The authentication
* is used by the connection at the first login and then reused after the connection
* is disconnected and then reconnected.
*/
void init() {
authenticationSuccessful = false;
saslException = null;
}
String getNameOfLastUsedSaslMechansism() {

View file

@ -108,30 +108,6 @@ public final class SmackConfiguration {
return SmackInitialization.SMACK_VERSION;
}
/**
* Returns the number of milliseconds to wait for a response from
* the server. The default value is 5000 ms.
*
* @return the milliseconds to wait for a response from the server
* @deprecated use {@link #getDefaultReplyTimeout()} instead.
*/
@Deprecated
public static int getDefaultPacketReplyTimeout() {
return getDefaultReplyTimeout();
}
/**
* Sets the number of milliseconds to wait for a response from
* the server.
*
* @param timeout the milliseconds to wait for a response from the server
* @deprecated use {@link #setDefaultReplyTimeout(int)} instead.
*/
@Deprecated
public static void setDefaultPacketReplyTimeout(int timeout) {
setDefaultReplyTimeout(timeout);
}
/**
* Returns the number of milliseconds to wait for a response from
* the server. The default value is 5000 ms.

View file

@ -37,6 +37,9 @@ import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider;
import org.jivesoftware.smack.provider.BodyElementProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.provider.SaslChallengeProvider;
import org.jivesoftware.smack.provider.SaslFailureProvider;
import org.jivesoftware.smack.provider.SaslSuccessProvider;
import org.jivesoftware.smack.provider.TlsFailureProvider;
import org.jivesoftware.smack.provider.TlsProceedProvider;
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
@ -126,6 +129,9 @@ public final class SmackInitialization {
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider());
ProviderManager.addNonzaProvider(SaslChallengeProvider.INSTANCE);
ProviderManager.addNonzaProvider(SaslSuccessProvider.INSTANCE);
ProviderManager.addNonzaProvider(SaslFailureProvider.INSTANCE);
ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE);
ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE);
ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE);

View file

@ -53,8 +53,6 @@ import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -335,19 +333,6 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon
parseFeatures(parser);
afterFeaturesReceived();
break;
// SASL related top level stream elements
case Challenge.ELEMENT:
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case Success.ELEMENT:
Success success = new Success(parser.nextText());
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
getSASLAuthentication().authenticated(success);
sendStreamOpen();
break;
default:
parseAndProcessNonza(parser);
break;
@ -613,7 +598,7 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon
prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.loginContext;
SASLMechanism usedSaslMechanism = saslAuthentication.authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession());
SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession());
// authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features.
waitForFeaturesReceived("server stream features after SASL authentication");

View file

@ -31,6 +31,7 @@ import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.MultiMap;
/**
@ -330,7 +331,8 @@ public class StateDescriptorGraph {
return res;
}
private static <E> void dfsVisit(GraphVertex<E> vertex, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
private static <E> void dfsVisit(GraphVertex<E> vertex, Consumer<GraphVertex<E>> dfsFinishedVertex,
DfsEdgeFound<E> dfsEdgeFound) {
vertex.color = GraphVertex.VertexColor.grey;
final int totalEdgeCount = vertex.getOutgoingEdges().size();
@ -349,11 +351,12 @@ public class StateDescriptorGraph {
vertex.color = GraphVertex.VertexColor.black;
if (dfsFinishedVertex != null) {
dfsFinishedVertex.onVertexFinished(vertex);
dfsFinishedVertex.accept(vertex);
}
}
private static <E> void dfs(Collection<GraphVertex<E>> vertexes, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
private static <E> void dfs(Collection<GraphVertex<E>> vertexes, Consumer<GraphVertex<E>> dfsFinishedVertex,
DfsEdgeFound<E> dfsEdgeFound) {
for (GraphVertex<E> vertex : vertexes) {
if (vertex.color == GraphVertex.VertexColor.white) {
dfsVisit(vertex, dfsFinishedVertex, dfsEdgeFound);
@ -407,12 +410,6 @@ public class StateDescriptorGraph {
dotOut.append("}\n");
}
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsFinishedVertex<E> {
void onVertexFinished(GraphVertex<E> vertex);
}
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsEdgeFound<E> {
void onEdgeFound(GraphVertex<E> from, GraphVertex<E> to, int edgeId, int totalEdgeCount);
}

View file

@ -163,6 +163,12 @@ public class XmlEnvironment {
return this;
}
public Builder with(StreamOpen streamOpen) {
withNamespace(streamOpen.getNamespace());
withLanguage(streamOpen.getLanguage());
return this;
}
public XmlEnvironment build() {
return new XmlEnvironment(this);
}

View file

@ -0,0 +1,40 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslChallengeProvider extends NonzaProvider<SaslNonza.Challenge> {
public static final SaslChallengeProvider INSTANCE = new SaslChallengeProvider();
private SaslChallengeProvider() {
}
@Override
public SaslNonza.Challenge parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws IOException, XmlPullParserException {
String data = parser.nextText();
return new SaslNonza.Challenge(data);
}
}

View file

@ -0,0 +1,62 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import java.util.Map;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslFailureProvider extends NonzaProvider<SASLFailure> {
public static final SaslFailureProvider INSTANCE = new SaslFailureProvider();
private SaslFailureProvider() {
}
@Override
public SASLFailure parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
String condition = null;
Map<String, String> descriptiveTexts = null;
outerloop: while (true) {
XmlPullParser.TagEvent eventType = parser.nextTag();
switch (eventType) {
case START_ELEMENT:
String name = parser.getName();
if (name.equals("text")) {
descriptiveTexts = PacketParserUtils.parseDescriptiveTexts(parser, descriptiveTexts);
}
else {
assert condition == null;
condition = parser.getName();
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
return new SASLFailure(condition, descriptiveTexts);
}
}

View file

@ -0,0 +1,40 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslSuccessProvider extends NonzaProvider<SaslNonza.Success> {
public static final SaslSuccessProvider INSTANCE = new SaslSuccessProvider();
private SaslSuccessProvider() {
}
@Override
public SaslNonza.Success parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws IOException, XmlPullParserException {
String data = parser.nextText();
return new SaslNonza.Success(data);
}
}

View file

@ -20,7 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
public class SASLErrorException extends XMPPException {

View file

@ -23,11 +23,12 @@ import javax.net.ssl.SSLSession;
import javax.security.auth.callback.CallbackHandler;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Response;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64;
@ -56,6 +57,17 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
public static final String GSSAPI = "GSSAPI";
public static final String PLAIN = "PLAIN";
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackSaslException},{@link SASLErrorException}, {@link NotConnectedException} or
* {@link InterruptedException}.
*/
private Exception exception;
protected XMPPConnection connection;
protected ConnectionConfiguration connectionConfiguration;
@ -275,7 +287,18 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
*/
public abstract int getPriority();
public abstract void checkIfSuccessfulOrThrow() throws SmackSaslException;
/**
* Check if the SASL mechanism was successful and if it was, then mark it so.
*
* @throws SmackSaslException in case of an SASL error.
*/
public final void afterFinalSaslChallenge() throws SmackSaslException {
checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
}
protected abstract void checkIfSuccessfulOrThrow() throws SmackSaslException;
public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
SASLMechanism saslMechansim = newInstance();
@ -288,6 +311,39 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
return false;
}
public boolean isAuthenticationSuccessful() {
return authenticationSuccessful;
}
public boolean isFinished() {
return isAuthenticationSuccessful() || exception != null;
}
public void throwExceptionIfRequired() throws SmackSaslException, SASLErrorException, NotConnectedException,
InterruptedException, NoResponseException {
if (exception != null) {
if (exception instanceof SmackSaslException) {
throw (SmackSaslException) exception;
} else if (exception instanceof SASLErrorException) {
throw (SASLErrorException) exception;
} else if (exception instanceof NotConnectedException) {
throw (NotConnectedException) exception;
} else if (exception instanceof InterruptedException) {
throw (InterruptedException) exception;
} else {
throw new IllegalStateException("Unexpected exception type", exception);
}
}
if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
}
public void setException(Exception exception) {
this.exception = exception;
}
protected abstract SASLMechanism newInstance();
protected static byte[] toBytes(String string) {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2018 Florian Schmaus
* Copyright 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,21 +18,30 @@ package org.jivesoftware.smack.sasl.packet;
import java.util.Map;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.AbstractError;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class SaslStreamElements {
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
public interface SaslNonza extends Nonza {
String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
@Override
default String getNamespace() {
return NAMESPACE;
}
/**
* Initiating SASL authentication by select a mechanism.
*/
public static class AuthMechanism implements Nonza {
class AuthMechanism implements SaslNonza {
public static final String ELEMENT = "auth";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String mechanism;
private final String authenticationText;
@ -44,11 +53,11 @@ public class SaslStreamElements {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).attribute("mechanism", mechanism).rightAngleBracket();
xml.optAppend(authenticationText);
xml.closeElement(ELEMENT);
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.attribute("mechanism", mechanism).rightAngleBracket();
xml.escape(authenticationText);
xml.closeElement(this);
return xml;
}
@ -60,11 +69,6 @@ public class SaslStreamElements {
return authenticationText;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
@ -74,8 +78,9 @@ public class SaslStreamElements {
/**
* A SASL challenge stream element.
*/
public static class Challenge implements Nonza {
class Challenge implements SaslNonza {
public static final String ELEMENT = "challenge";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String data;
@ -83,18 +88,15 @@ public class SaslStreamElements {
this.data = StringUtils.returnIfNotEmptyTrimmed(data);
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder().halfOpenElement(ELEMENT).xmlnsAttribute(
NAMESPACE).rightAngleBracket();
xml.optAppend(data);
xml.closeElement(ELEMENT);
return xml;
public String getData() {
return data;
}
@Override
public String getNamespace() {
return NAMESPACE;
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.optTextChild(data, this);
return xml;
}
@Override
@ -106,8 +108,9 @@ public class SaslStreamElements {
/**
* A SASL response stream element.
*/
public static class Response implements Nonza {
class Response implements SaslNonza {
public static final String ELEMENT = "response";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String authenticationText;
@ -120,11 +123,9 @@ public class SaslStreamElements {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
xml.optAppend(authenticationText);
xml.closeElement(ELEMENT);
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.optTextChild(authenticationText, this);
return xml;
}
@ -132,11 +133,6 @@ public class SaslStreamElements {
return authenticationText;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
@ -146,8 +142,9 @@ public class SaslStreamElements {
/**
* A SASL success stream element.
*/
public static class Success implements Nonza {
class Success implements SaslNonza {
public static final String ELEMENT = "success";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String data;
@ -171,19 +168,12 @@ public class SaslStreamElements {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
xml.optAppend(data);
xml.closeElement(ELEMENT);
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.optTextChild(data, this);
return xml;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
@ -194,8 +184,9 @@ public class SaslStreamElements {
* A SASL failure stream element, also called "SASL Error".
* @see <a href="http://xmpp.org/rfcs/rfc6120.html#sasl-errors">RFC 6120 6.5 SASL Errors</a>
*/
public static class SASLFailure extends AbstractError implements Nonza {
class SASLFailure extends AbstractError implements SaslNonza {
public static final String ELEMENT = "failure";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final SASLError saslError;
private final String saslErrorString;
@ -250,11 +241,6 @@ public class SaslStreamElements {
return toXML().toString();
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;

View file

@ -51,8 +51,9 @@ public class CollectionUtil {
}
public static <T> ArrayList<T> newListWith(Collection<? extends T> collection) {
ArrayList<T> arrayList = new ArrayList<>(collection.size());
arrayList.addAll(collection);
return arrayList;
if (collection == null) {
return null;
}
return new ArrayList<>(collection);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,23 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
package org.jivesoftware.smack.util;
import org.jivesoftware.smack.packet.Element;
// TODO: Replace with java.util.function.Consumer once Smack's minimum Android SDK level is 24 or higher.
public interface Consumer<T> {
public abstract class GenericElementListener<E extends Element> {
private final Class<? extends E> elementClass;
public GenericElementListener(Class<? extends E> elementClass) {
this.elementClass = elementClass;
}
public abstract void process(E element);
public final void processElement(Element element) {
E concreteEleement = elementClass.cast(element);
process(concreteEleement);
}
void accept(T t);
}

View file

@ -49,7 +49,6 @@ import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -686,44 +685,6 @@ public class PacketParserUtils {
return descriptiveTexts;
}
/**
* Parses SASL authentication error packets.
*
* @param parser the XML parser.
* @return a SASL Failure packet.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
*/
public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
final int initialDepth = parser.getDepth();
String condition = null;
Map<String, String> descriptiveTexts = null;
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
switch (eventType) {
case START_ELEMENT:
String name = parser.getName();
if (name.equals("text")) {
descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
}
else {
assert condition == null;
condition = parser.getName();
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
break;
}
}
return new SASLFailure(condition, descriptiveTexts);
}
public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
return parseStreamError(parser, null);
}

View file

@ -18,9 +18,10 @@ package org.jivesoftware.smack.util;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement;
@ -496,13 +497,6 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this;
}
public XmlStringBuilder optAppend(CharSequence csq) {
if (csq != null) {
append(csq);
}
return this;
}
public XmlStringBuilder optAppend(Element element) {
if (element != null) {
append(element.toXML(effectiveXmlEnvironment));
@ -510,6 +504,16 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this;
}
public XmlStringBuilder optTextChild(CharSequence sqc, NamedElement parentElement) {
if (sqc == null) {
return closeEmptyElement();
}
rightAngleBracket();
escape(sqc);
closeElement(parentElement);
return this;
}
public XmlStringBuilder append(XmlStringBuilder xsb) {
assert xsb != null;
sb.append(xsb.sb);
@ -606,24 +610,46 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return toString().hashCode();
}
private static final class WrappedIoException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final IOException wrappedIoException;
private WrappedIoException(IOException wrappedIoException) {
this.wrappedIoException = wrappedIoException;
}
}
/**
* Write the contents of this <code>XmlStringBuilder</code> to a {@link Writer}. This will write
* the single parts one-by-one, avoiding allocation of a big continuous memory block holding the
* XmlStringBuilder contents.
*
* @param writer TODO javadoc me please
* @param enclosingNamespace the enclosing XML namespace.
* @param enclosingXmlEnvironment the enclosing XML environment.
* @throws IOException if an I/O error occured.
*/
public void write(Writer writer, String enclosingNamespace) throws IOException {
XmlEnvironment enclosingXmlEnvironment = XmlEnvironment.builder()
.withNamespace(enclosingNamespace)
.build();
appendXmlTo(writer, enclosingXmlEnvironment);
public void write(Writer writer, XmlEnvironment enclosingXmlEnvironment) throws IOException {
try {
appendXmlTo(csq -> {
try {
writer.append(csq);
} catch (IOException e) {
throw new WrappedIoException(e);
}
}, enclosingXmlEnvironment);
} catch (WrappedIoException e) {
throw e.wrappedIoException;
}
}
public Iterator<CharSequence> getCharSequenceIterator() {
return sb.getAsList().iterator();
public List<CharSequence> toList(XmlEnvironment enclosingXmlEnvironment) {
List<CharSequence> res = new ArrayList<>(sb.getAsList().size());
appendXmlTo(csq -> res.add(csq), enclosingXmlEnvironment);
return res;
}
@Override
@ -631,29 +657,26 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
// This is only the potential length, since the actual length depends on the given XmlEnvironment.
int potentialLength = length();
StringBuilder res = new StringBuilder(potentialLength);
try {
appendXmlTo(res, enclosingXmlEnvironment);
} catch (IOException e) {
// Should never happen.
throw new AssertionError(e);
}
appendXmlTo(csq -> res.append(csq), enclosingXmlEnvironment);
return res;
}
private void appendXmlTo(Appendable appendable, XmlEnvironment enclosingXmlEnvironment) throws IOException {
private void appendXmlTo(Consumer<CharSequence> charSequenceSink, XmlEnvironment enclosingXmlEnvironment) {
for (CharSequence csq : sb.getAsList()) {
if (csq instanceof XmlStringBuilder) {
((XmlStringBuilder) csq).appendXmlTo(appendable, enclosingXmlEnvironment);
((XmlStringBuilder) csq).appendXmlTo(charSequenceSink, enclosingXmlEnvironment);
}
else if (csq instanceof XmlNsAttribute) {
XmlNsAttribute xmlNsAttribute = (XmlNsAttribute) csq;
if (!xmlNsAttribute.value.equals(enclosingXmlEnvironment.getEffectiveNamespace())) {
appendable.append(xmlNsAttribute);
charSequenceSink.accept(xmlNsAttribute);
enclosingXmlEnvironment = new XmlEnvironment(xmlNsAttribute.value);
}
}
else {
appendable.append(csq);
charSequenceSink.accept(csq);
}
}
}

View file

@ -0,0 +1,78 @@
/**
*
* Copyright (C) 2007 Jive Software, 2019 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.provider;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import java.io.IOException;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.xml.XmlPullParserException;
import com.jamesmurty.utils.XMLBuilder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
public class SaslProviderTest {
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void parseSASLFailureSimple(SmackTestUtil.XmlPullParserKind parserKind)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError,
XmlPullParserException, IOException, SmackParsingException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE)
.e(SASLError.account_disabled.toString())
.asString();
// @formatter:on
SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind);
assertXmlSimilar(saslFailureString, saslFailure.toString());
}
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void parseSASLFailureExtended(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException, TransformerException,
ParserConfigurationException, FactoryConfigurationError {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE)
.e(SASLError.account_disabled.toString())
.up()
.e("text").a("xml:lang", "en")
.t("Call 212-555-1212 for assistance.")
.up()
.e("text").a("xml:lang", "de")
.t("Bitte wenden sie sich an (04321) 123-4444")
.up()
.e("text")
.t("Wusel dusel")
.asString();
// @formatter:on
SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind);
assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE));
}
}

View file

@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Response;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.util.stringencoder.Base64;

View file

@ -125,10 +125,19 @@ public class SmackTestUtil {
return parser;
}
@SuppressWarnings("unchecked")
private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
P provider;
// TODO: Consider adding a shortcut in case there is a static INSTANCE field holding an instance of the
// requested provider.
try {
provider = (P) providerClass.getDeclaredField("INSTANCE").get(null);
return provider;
} catch (NoSuchFieldException e) {
// Continue with the next approach.
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
throw new AssertionError(e);
}
try {
provider = providerClass.getDeclaredConstructor().newInstance();
}

View file

@ -39,9 +39,6 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -814,41 +811,6 @@ public class PacketParserUtilsTest {
assertXmlSimilar(stanza, result.toString());
}
@Test
public void parseSASLFailureSimple() throws FactoryConfigurationError, SAXException, IOException,
TransformerException, ParserConfigurationException, XmlPullParserException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE)
.e(SASLError.account_disabled.toString())
.asString();
// @formatter:on
XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT);
SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser);
assertXmlSimilar(saslFailureString, saslFailure.toString());
}
@Test
public void parseSASLFailureExtended() throws FactoryConfigurationError, TransformerException,
ParserConfigurationException, XmlPullParserException, IOException, SAXException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE)
.e(SASLError.account_disabled.toString())
.up()
.e("text").a("xml:lang", "en")
.t("Call 212-555-1212 for assistance.")
.up()
.e("text").a("xml:lang", "de")
.t("Bitte wenden sie sich an (04321) 123-4444")
.up()
.e("text")
.t("Wusel dusel")
.asString();
// @formatter:on
XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT);
SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser);
assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE));
}
@SuppressWarnings("ReferenceEquality")
private static String determineNonDefaultLanguage() {
String otherLanguage = "jp";

View file

@ -225,9 +225,7 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
xml.optTextChild(text, this);
return xml;
}
@ -269,9 +267,7 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
xml.optTextChild(text, this);
return xml;
}
@ -313,9 +309,7 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
xml.optTextChild(text, this);
return xml;
}

View file

@ -46,7 +46,7 @@ public class QueryArchiveTest extends MamTest {
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
+ "<result xmlns='urn:xmpp:mam:1' queryid='g27' id='34482-21985-73620'>"
+ "<forwarded xmlns='urn:xmpp:forward:0'>"
+ "<delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37.000+00:00'></delay>" + "<message "
+ "<delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37.000+00:00'/>" + "<message "
+ "xmlns='jabber:client' from='coven@chat.shakespeare.lit/firstwitch' " + "id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2' "
+ "type='chat'>" + "<body>Thrice the brinded cat hath mew.</body>" + "</message>" + "</forwarded>"
+ "</result>" + "</message>";

View file

@ -106,9 +106,7 @@ public class DelayInformation implements ExtensionElement {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.attribute("stamp", XmppDateTime.formatXEP0082Date(stamp));
xml.optAttribute("from", from);
xml.rightAngleBracket();
xml.optAppend(reason);
xml.closeElement(this);
xml.optTextChild(reason, this);
return xml;
}

View file

@ -146,7 +146,7 @@ public class StreamInitiation extends IQ {
buf.rightAngleBracket();
// Add the file section if there is one.
buf.optAppend(file.toXML());
buf.optElement(file);
break;
case result:
buf.rightAngleBracket();

View file

@ -8,6 +8,7 @@ dependencies {
compile project(":smack-core")
compile project(":smack-resolver-javax")
compile project(":smack-sasl-javax")
implementation project(":smack-xmlparser-stax")
}
javadoc {

View file

@ -29,11 +29,6 @@ public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
public static final String NAME = GSSAPI;
static {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("java.security.auth.login.config", "gss.conf");
}
@Override
public boolean authzidSupported() {
return true;

View file

@ -1,12 +1,21 @@
.PHONY := clean generate
.PHONY := all clean
GRADLE_QUITE_ARGS := --quiet --console plain
GENERATED_FILES := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
generate: $(GENERATED_FILES)
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG:.png=.dot)
GENERATED_FILES := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG) $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT)
all: $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG)
clean:
rm -f $(GENERATED_FILES)
src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png: src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph | dot -Tpng -o $@
%.png: %.dot
dot -Tpng -o $@ $^
$(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT): src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
# TODO: This also creates the dot file even if the command
# fails. It would be better if this was not the case.
gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph > $@

View file

@ -1 +1,2 @@
/XmppNioTcpConnectionStateGraph.png
/XmppNioTcpConnectionStateGraph.dot

View file

@ -87,12 +87,8 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sm.SMUtils;
import org.jivesoftware.smack.sm.StreamManagementException;
import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException;
@ -302,6 +298,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
}
});
// Re-init the reader and writer in case of SASL <success/>. This is done to reset the parser since a new stream
// is initiated.
buildNonzaCallback().listenFor(SaslNonza.Success.class, s -> resetParser()).install();
}
/**
@ -371,7 +371,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
SmackException, IOException, InterruptedException {
// Authenticate using SASL
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
authenticate(username, password, config.getAuthzid(), sslSession);
// Wait for stream features after the authentication.
// TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be
@ -857,7 +857,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
tlsHandled.reportSuccess();
}
if (getSASLAuthentication().authenticationSuccessful()) {
if (isSaslAuthenticated()) {
// If we have received features after the SASL has been successfully completed, then we
// have also *maybe* received, as it is an optional feature, the compression feature
// from the server.
@ -865,18 +865,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
}
/**
* Resets the parser using the latest connection's reader. Resetting the parser is necessary
* when the plain connection has been secured or when a new opening stream element is going
* to be sent by the server.
*
* @throws SmackException if the parser could not be reset.
* @throws InterruptedException if the calling thread was interrupted.
* @throws XmlPullParserException if an error in the XML parser occured.
*/
void openStream() throws SmackException, InterruptedException, XmlPullParserException {
sendStreamOpen();
private void resetParser() throws IOException {
try {
packetReader.parser = SmackXmlParser.newXmlParser(reader);
} catch (XmlPullParserException e) {
throw new IOException(e);
}
}
private void openStreamAndResetParser() throws IOException, NotConnectedException, InterruptedException {
sendStreamOpen();
resetParser();
}
protected class PacketReader {
@ -921,7 +920,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
private void parsePackets() {
boolean initialStreamOpenSend = false;
try {
openStream();
openStreamAndResetParser();
initialStreamOpenSend = true;
XmlPullParser.Event eventType = parser.getEventType();
while (!done) {
@ -957,7 +956,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// Secure the connection by negotiating TLS
proceedTLSReceived();
// Send a new opening stream to the server
openStream();
openStreamAndResetParser();
}
catch (Exception e) {
SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e);
@ -980,35 +979,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
compressSyncPoint.reportFailure(new SmackException.SmackMessageException(
"Could not establish compression"));
break;
case SaslStreamElements.NAMESPACE:
// SASL authentication has failed. The server may close the connection
// depending on the number of retries
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
getSASLAuthentication().authenticationFailed(failure);
break;
default:
parseAndProcessNonza(parser);
}
break;
case Challenge.ELEMENT:
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case Success.ELEMENT:
Success success = new Success(parser.nextText());
// We now need to bind a resource for the connection
// Open a new stream and wait for the response
openStream();
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
getSASLAuthentication().authenticated(success);
break;
case Compressed.ELEMENT:
// Server confirmed that it's possible to use stream compression. Start
// stream compression
// Initialize the reader and writer with the new compressed version
initReaderAndWriter();
// Send a new opening stream to the server
openStream();
openStreamAndResetParser();
// Notify that compression is being used
compressSyncPoint.reportSuccess();
break;
@ -1090,7 +1071,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
break;
default:
LOGGER.warning("Unknown top level stream element: " + name);
parseAndProcessNonza(parser);
break;
}
break;
@ -1347,9 +1328,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
maybeAddToUnacknowledgedStanzas(packet);
CharSequence elementXml = element.toXML(StreamOpen.CLIENT_NAMESPACE);
CharSequence elementXml = element.toXML(outgoingStreamXmlEnvironment);
if (elementXml instanceof XmlStringBuilder) {
((XmlStringBuilder) elementXml).write(writer, StreamOpen.CLIENT_NAMESPACE);
((XmlStringBuilder) elementXml).write(writer, outgoingStreamXmlEnvironment);
}
else {
writer.write(elementXml.toString());

View file

@ -509,7 +509,7 @@ public class XmppNioTcpConnection extends AbstractXmppNioConnection {
CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE);
if (nextCharSequence instanceof XmlStringBuilder) {
XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence;
outgoingCharSequenceIterator = xmlStringBuilder.getCharSequenceIterator();
outgoingCharSequenceIterator = xmlStringBuilder.toList(outgoingStreamXmlEnvironment).iterator();
} else {
outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator();
}

View file

@ -31,7 +31,8 @@ public class SmackXmlParser {
public static XmlPullParserFactory getXmlPullParserFactory() {
Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException("Could not load a XmlPullParserFactory via Service Provider Interface (SPI)");
throw new IllegalStateException(
"No XmlPullParserFactory registered with Service Provider Interface (SPI). Is smack-xmlparser-xpp3 or smack-xmlparser-stax in classpath?");
}
return iterator.next();
}