diff --git a/build/eclipse/settings/org.eclipse.jdt.ui.prefs b/build/eclipse/settings/org.eclipse.jdt.ui.prefs
index fcdbc532c..dde05c864 100644
--- a/build/eclipse/settings/org.eclipse.jdt.ui.prefs
+++ b/build/eclipse/settings/org.eclipse.jdt.ui.prefs
@@ -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
diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java
index 826360cf3..05f8de99c 100644
--- a/source/org/jivesoftware/smack/PacketReader.java
+++ b/source/org/jivesoftware/smack/PacketReader.java
@@ -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.
diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java
index aae8184bc..eb8069381 100644
--- a/source/org/jivesoftware/smack/SmackConfiguration.java
+++ b/source/org/jivesoftware/smack/SmackConfiguration.java
@@ -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
diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java
index 1b2dedad0..f0b6eafd8 100644
--- a/source/org/jivesoftware/smack/XMPPConnection.java
+++ b/source/org/jivesoftware/smack/XMPPConnection.java
@@ -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()) {
diff --git a/source/org/jivesoftware/smack/parsing/LogException.java b/source/org/jivesoftware/smack/parsing/LogException.java
new file mode 100644
index 000000000..dd3031e9a
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/LogException.java
@@ -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());
+ }
+}
diff --git a/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java b/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java
new file mode 100644
index 000000000..3c57d089c
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java
@@ -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 {
+ }
+}
diff --git a/source/org/jivesoftware/smack/parsing/ThrowException.java b/source/org/jivesoftware/smack/parsing/ThrowException.java
new file mode 100644
index 000000000..400950346
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/ThrowException.java
@@ -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;
+ }
+}
diff --git a/source/org/jivesoftware/smack/parsing/UnparsedIQ.java b/source/org/jivesoftware/smack/parsing/UnparsedIQ.java
new file mode 100644
index 000000000..a9ad6a6d5
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/UnparsedIQ.java
@@ -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;
+ }
+
+}
diff --git a/source/org/jivesoftware/smack/parsing/UnparsedMessage.java b/source/org/jivesoftware/smack/parsing/UnparsedMessage.java
new file mode 100644
index 000000000..e23773cf2
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/UnparsedMessage.java
@@ -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;
+ }
+
+}
diff --git a/source/org/jivesoftware/smack/parsing/UnparsedPresence.java b/source/org/jivesoftware/smack/parsing/UnparsedPresence.java
new file mode 100644
index 000000000..a14e0ae49
--- /dev/null
+++ b/source/org/jivesoftware/smack/parsing/UnparsedPresence.java
@@ -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;
+ }
+}
diff --git a/source/org/jivesoftware/smack/util/PacketParserUtils.java b/source/org/jivesoftware/smack/util/PacketParserUtils.java
index a574be3db..57d539c59 100644
--- a/source/org/jivesoftware/smack/util/PacketParserUtils.java
+++ b/source/org/jivesoftware/smack/util/PacketParserUtils.java
@@ -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();
diff --git a/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java b/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java
new file mode 100644
index 000000000..fd674ae0c
--- /dev/null
+++ b/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java
@@ -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 =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+ @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(
+ "" +
+ "<" + ThrowException.ELEMENT + " xmlns='" + ThrowException.NAMESPACE + "'>" +
+ "" +
+ "" +
+ "" + ThrowException.ELEMENT + ">" +
+ EXTENSION2 +
+ "");
+ int parserDepth = parser.getDepth();
+ String content = null;
+ try {
+ PacketParserUtils.parseMessage(parser);
+ } catch (Exception e) {
+ content = PacketParserUtils.parseContentDepth(parser, parserDepth);
+ }
+ assertNotNull(content);
+ assertEquals(content, "" + "" + 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");
+ }
+
+ }
+}