mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-21 22:02:06 +01:00
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.
This commit is contained in:
parent
c3247ef006
commit
eeb6c52f7e
23 changed files with 490 additions and 329 deletions
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
@ -237,7 +245,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
|
||||
protected XmlEnvironment outgoingStreamXmlEnvironment;
|
||||
|
||||
final Map<QName, NonzaCallback> nonzaCallbacks = new HashMap<>();
|
||||
final MultiMap<QName, NonzaCallback> nonzaCallbacksMap = new MultiMap<>();
|
||||
|
||||
protected final Lock connectionLock = new ReentrantLock();
|
||||
|
||||
|
@ -308,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
|
||||
|
@ -402,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);
|
||||
|
@ -810,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1225,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);
|
||||
|
@ -1232,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 {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 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 org.jivesoftware.smack.packet.Element;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
@ -180,7 +181,7 @@ 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 {
|
||||
final SASLMechanism mechanism = selectMechanism(authzid);
|
||||
|
@ -216,12 +217,12 @@ public final class SASLAuthentication {
|
|||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,15 +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 {
|
||||
private void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException {
|
||||
SASLMechanism mechanism;
|
||||
synchronized (this) {
|
||||
currentMechanism.challengeReceived(challenge, finalChallenge);
|
||||
}
|
||||
} catch (InterruptedException | SmackSaslException | NotConnectedException e) {
|
||||
authenticationFailed(e);
|
||||
throw e;
|
||||
mechanism = currentMechanism;
|
||||
}
|
||||
mechanism.challengeReceived(challenge, finalChallenge);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -253,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
|
||||
|
@ -279,11 +279,15 @@ 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) {
|
||||
void authenticationFailed(Exception exception) {
|
||||
// Wake up the thread that is waiting in the #authenticate method
|
||||
synchronized (this) {
|
||||
currentMechanism.setException(exception);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ 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;
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -497,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));
|
||||
|
@ -511,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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,10 +88,7 @@ import org.jivesoftware.smack.packet.Stanza;
|
|||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.StreamError;
|
||||
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;
|
||||
|
@ -301,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -370,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
|
||||
|
@ -856,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.
|
||||
|
@ -864,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 {
|
||||
|
@ -920,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) {
|
||||
|
@ -956,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);
|
||||
|
@ -979,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;
|
||||
|
@ -1089,7 +1071,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
LOGGER.warning("Unknown top level stream element: " + name);
|
||||
parseAndProcessNonza(parser);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
Loading…
Reference in a new issue