SMACK-425 Introduced smack.parsing.ParsingExceptionCallback, a callback invoked when a exception is thrown while parsing a stanza. Smack is now able to either rethrow the exception ulitmatly causing a disconnect *or* log/ignore the exception and resume parsing after the faulty stanza.

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/branches/smack_3_3_1@13688 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Florian Schmaus 2013-06-22 17:01:40 +00:00 committed by flow
parent 18a603d932
commit 30bc5afa2b
12 changed files with 524 additions and 8 deletions

View File

@ -1,4 +1,3 @@
#Tue Jan 29 23:27:16 CET 2013
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_ignite
@ -37,7 +36,7 @@ sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
sp_cleanup.remove_trailing_whitespaces=false
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
sp_cleanup.remove_unnecessary_casts=true

View File

@ -22,6 +22,10 @@ package org.jivesoftware.smack;
import org.jivesoftware.smack.Connection.ListenerWrapper;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.UnparsedIQ;
import org.jivesoftware.smack.parsing.UnparsedMessage;
import org.jivesoftware.smack.parsing.UnparsedPresence;
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
@ -176,14 +180,49 @@ class PacketReader {
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
int parserDepth = parser.getDepth();
ParsingExceptionCallback callback = connection.getParsingExceptionCallback();
if (parser.getName().equals("message")) {
processPacket(PacketParserUtils.parseMessage(parser));
Packet packet;
try {
packet = PacketParserUtils.parseMessage(parser);
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedMessage message = new UnparsedMessage(content, e);
if (callback != null) {
callback.messageParsingException(e, message);
}
continue;
}
processPacket(packet);
}
else if (parser.getName().equals("iq")) {
processPacket(PacketParserUtils.parseIQ(parser, connection));
IQ iq;
try {
iq = PacketParserUtils.parseIQ(parser, connection);
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedIQ uniq = new UnparsedIQ(content, e);
if (callback != null) {
callback.iqParsingException(e, uniq);
}
continue;
}
processPacket(iq);
}
else if (parser.getName().equals("presence")) {
processPacket(PacketParserUtils.parsePresence(parser));
Presence presence;
try {
presence = PacketParserUtils.parsePresence(parser);
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedPresence unpresence = new UnparsedPresence(content, e);
if (callback != null) {
callback.presenceParsingException(e, unpresence);
}
continue;
}
processPacket(presence);
}
// We found an opening stream. Record information about it, then notify
// the connectionID lock so that the packet reader startup can finish.

View File

@ -28,6 +28,8 @@ import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.ThrowException;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
@ -58,6 +60,12 @@ public final class SmackConfiguration {
private static int localSocks5ProxyPort = 7777;
private static int packetCollectorSize = 5000;
/**
* The default parsing exception callback is {@link ThrowException} which will
* throw an exception and therefore disconnect the active connection.
*/
private static ParsingExceptionCallback defaultCallback = new ThrowException();
/**
* This automatically enables EntityCaps for new connections if it is set to true
*/
@ -328,6 +336,26 @@ public final class SmackConfiguration {
autoEnableEntityCaps = b;
}
/**
* Set the default parsing exception callback for all newly created connections
*
* @param callback
* @see ParsingExceptionCallback
*/
public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) {
defaultCallback = callback;
}
/**
* Returns the default parsing exception callback
*
* @return the default parsing exception callback
* @see ParsingExceptionCallback
*/
public static ParsingExceptionCallback getDefaultParsingExceptionCallback() {
return defaultCallback;
}
private static void parseClassToLoad(XmlPullParser parser) throws Exception {
String className = parser.nextText();
// Attempt to load the class so that the class can get initialized

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
@ -89,6 +90,8 @@ public class XMPPConnection extends Connection {
private boolean anonymous = false;
private boolean usingTLS = false;
private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback();
PacketWriter packetWriter;
PacketReader packetReader;
@ -202,6 +205,25 @@ public class XMPPConnection extends Connection {
return user;
}
/**
* Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a
* stanza
*
* @param callback the callback to install
*/
public void setParsingExceptionCallback(ParsingExceptionCallback callback) {
parsingExceptionCallback = callback;
}
/**
* Get the current active parsing exception callback.
*
* @return the active exception callback or null if there is none
*/
public ParsingExceptionCallback getParsingExceptionCallback() {
return parsingExceptionCallback;
}
@Override
public synchronized void login(String username, String password, String resource) throws XMPPException {
if (!isConnected()) {

View File

@ -0,0 +1,51 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
/**
* Simple parsing exception callback that only logs the encountered parsing exception to stderr.
*
* @author Florian Schmaus
*
*/
public class LogException extends ParsingExceptionCallback {
@Override
public void messageParsingException(Exception e, UnparsedMessage message) throws Exception {
System.err.print("Smack message parsing exception: " + e.getMessage());
e.printStackTrace();
System.err.println("Unparsed content: " + message.getContent());
}
@Override
public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception {
System.err.print("Smack iq parsing exception: " + e.getMessage());
e.printStackTrace();
System.err.println("Unparsed content: " + iq.getContent());
}
@Override
public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception {
System.err.print("Smack presence parsing exception: " + e.getMessage());
e.printStackTrace();
System.err.println("Unparsed content: " + presence.getContent());
}
}

View File

@ -0,0 +1,76 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
/**
* Base class to receive parsing exceptions.
*
* If this class is used as callback, then Smack will silently ignore the stanza that caused the parsing exception and
* place the parser after the faulty stanza.
*
* Subclasses may or may not override certain methods of this class. Each of these methods will receive the exception
* that caused the parsing error and an instance of an Unparsed Packet type. The latter can be used to inspect the
* stanza that caused the parsing error by using the getContent() (for example {@link UnparsedIQ#getContent()})
* method.
*
* Smack provides 2 predefined ParsingExceptionCallback's: {@link LogException} and {@link ThrowException}.
*
* @author Florian Schmaus
*
*/
public abstract class ParsingExceptionCallback {
/**
* Called when parsing an message stanza caused an exception.
*
* @param e
* the exception thrown while parsing the message stanza
* @param message
* the raw message stanza data that caused the exception
* @throws Exception
*/
public void messageParsingException(Exception e, UnparsedMessage message) throws Exception {
}
/**
* Called when parsing an IQ stanza caused an exception.
*
* @param e
* the exception thrown while parsing the iq stanza
* @param iq
* the raw iq stanza data that caused the exception
* @throws Exception
*/
public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception {
}
/**
* Called when parsing a presence stanza caused an exception.
*
* @param e
* the exception thrown while parsing the presence stanza
* @param presence
* the raw presence stanza data that caused the exception
* @throws Exception
*/
public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception {
}
}

View File

@ -0,0 +1,48 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
import org.jivesoftware.smack.ConnectionListener;
/**
* Parsing exception callback class that simply throws the encountered parsing exception. This usually leads to an
* {@link ConnectionListener#connectionClosedOnError(Exception)} disconnect of the connection.
*
* @author Florian Schmaus
*
*/
public class ThrowException extends ParsingExceptionCallback {
@Override
public void messageParsingException(Exception e, UnparsedMessage message) throws Exception {
throw e;
}
@Override
public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception {
throw e;
}
@Override
public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception {
throw e;
}
}

View File

@ -0,0 +1,62 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
import org.jivesoftware.smack.packet.IQ;
/**
* Representation of an unparsed IQ stanza.
*
* @author Florian Schmaus
*
*/
public class UnparsedIQ extends IQ {
private final String content;
private final Exception e;
public UnparsedIQ(final String content, final Exception e) {
this.content = content;
this.e = e;
}
/**
*
* @return the exception that caused the parser to fail
*/
public Exception getException() {
return e;
}
/**
* Retrieve the raw stanza data
*
* @return the raw stanza data
*/
public String getContent() {
return content;
}
@Override
public String getChildElementXML() {
return null;
}
}

View File

@ -0,0 +1,57 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
import org.jivesoftware.smack.packet.Message;
/**
* Representation of an unparsed IQ stanza.
*
* @author Florian Schmaus
*
*/
public class UnparsedMessage extends Message {
private final String content;
private final Exception e;
public UnparsedMessage(final String content, final Exception e) {
this.content = content;
this.e = e;
}
/**
*
* @return the exception that caused the parser to fail
*/
public Exception getException() {
return e;
}
/**
* Retrieve the raw stanza data
*
* @return the raw stanza data
*/
public String getContent() {
return content;
}
}

View File

@ -0,0 +1,61 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2013 Florian Schmaus.
*
* All rights reserved. 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.parsing;
import org.jivesoftware.smack.packet.Presence;
/**
* Representation of an unparsed IQ stanza.
*
* @author Florian Schmaus
*
*/
public class UnparsedPresence extends Presence {
private String content;
private Exception e;
public UnparsedPresence(Type type) {
super(type);
}
public UnparsedPresence(final String content, final Exception e) {
super(Presence.Type.error);
this.content = content;
this.e = e;
}
/**
*
* @return the exception that caused the parser to fail
*/
public Exception getException() {
return e;
}
/**
* Retrieve the raw stanza data
*
* @return the raw stanza data
*/
public String getContent() {
return content;
}
}

View File

@ -171,10 +171,13 @@ public class PacketParserUtils {
*/
private static String parseContent(XmlPullParser parser)
throws XmlPullParserException, IOException {
StringBuffer content = new StringBuffer();
int parserDepth = parser.getDepth();
while (!(parser.next() == XmlPullParser.END_TAG && parser
.getDepth() == parserDepth)) {
return parseContentDepth(parser, parserDepth);
}
public static String parseContentDepth(XmlPullParser parser, int depth) throws XmlPullParserException, IOException {
StringBuffer content = new StringBuffer();
while (!(parser.next() == XmlPullParser.END_TAG && parser.getDepth() == depth)) {
content.append(parser.getText());
}
return content.toString();

View File

@ -0,0 +1,70 @@
package org.jivesoftware.smack.parsing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.jivesoftware.smack.TestUtils;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
public class ParsingExceptionTest {
private final static ProviderManager PM = ProviderManager.getInstance();
private final static String EXTENSION2 =
"<extension2 xmlns='namespace'>" +
"<bar node='testNode'>" +
"<i id='testid1' >" +
"</i>" +
"</bar>" +
"</extension2>";
@Before
public void init() {
PM.addExtensionProvider(ThrowException.ELEMENT, ThrowException.NAMESPACE, new ThrowException());
}
@After
public void tini() {
PM.removeExtensionProvider(ThrowException.ELEMENT, ThrowException.NAMESPACE);
}
@Test
public void consumeUnparsedInput() throws Exception {
XmlPullParser parser = TestUtils.getMessageParser(
"<message from='user@server.example' to='francisco@denmark.lit' id='foo'>" +
"<" + ThrowException.ELEMENT + " xmlns='" + ThrowException.NAMESPACE + "'>" +
"<nothingInHere>" +
"</nothingInHere>" +
"</" + ThrowException.ELEMENT + ">" +
EXTENSION2 +
"</message>");
int parserDepth = parser.getDepth();
String content = null;
try {
PacketParserUtils.parseMessage(parser);
} catch (Exception e) {
content = PacketParserUtils.parseContentDepth(parser, parserDepth);
}
assertNotNull(content);
assertEquals(content, "<nothingInHere></nothingInHere>" + "</" + ThrowException.ELEMENT + ">" + EXTENSION2);
}
static class ThrowException implements PacketExtensionProvider {
public static final String ELEMENT = "exception";
public static final String NAMESPACE = "http://smack.jivesoftware.org/exception";
@Override
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
throw new XMPPException("Test Exception");
}
}
}