1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-06-30 15:26:46 +02:00
Smack/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
Florian Schmaus 9e797c1b17 Enable PacketExtensions for IQs
This is actually only part one, i.e. with this commit if the user adds a
PacketExtension to an IQ it will be included in IQ.toXml(). Which was
previously only the case if the IQ subclass explicitly included packet
extensions.

The second part of the change is to change the IQ provider, so that
packet extensions are automatically parsed.

Cases where PacketExtensions are used for Message and IQ are slightly
changed. The IQ sublcass now only has a field with this
PacketExtension (see for example
bytestreams.ibb.packet.DataPacketExtension).

Also changed hoxt API: Removed unnecessary indirection and made the
API more Smack idiomatic.
2014-11-10 11:43:18 +01:00

1097 lines
44 KiB
Java

/**
*
* Copyright 2003-2007 Jive Software.
*
* 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.util;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.EmptyResultIQ;
import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* Utility class that helps to parse packets. Any parsing packets method that must be shared
* between many clients must be placed in this utility class.
*
* @author Gaston Dombiak
*/
public class PacketParserUtils {
private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());
public static final String FEATURE_XML_ROUNDTRIP = "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
private static final XmlPullParserFactory XML_PULL_PARSER_FACTORY;
/**
* True if the XmlPullParser supports the XML_ROUNDTRIP feature.
*/
public static final boolean XML_PULL_PARSER_SUPPORTS_ROUNDTRIP;
static {
XmlPullParser xmlPullParser;
boolean roundtrip = false;
try {
XML_PULL_PARSER_FACTORY = XmlPullParserFactory.newInstance();
xmlPullParser = XML_PULL_PARSER_FACTORY.newPullParser();
try {
xmlPullParser.setFeature(FEATURE_XML_ROUNDTRIP, true);
// We could successfully set the feature
roundtrip = true;
} catch (XmlPullParserException e) {
// Doesn't matter if FEATURE_XML_ROUNDTRIP isn't available
LOGGER.log(Level.FINEST, "XmlPullParser does not support XML_ROUNDTRIP", e);
}
}
catch (XmlPullParserException e) {
// Something really bad happened
throw new AssertionError(e);
}
XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip;
}
public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
return getParserFor(new StringReader(stanza));
}
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
XmlPullParser parser = newXmppParser(reader);
// Wind the parser forward to the first start tag
int event = parser.getEventType();
while (event != XmlPullParser.START_TAG) {
if (event == XmlPullParser.END_DOCUMENT) {
throw new IllegalArgumentException("Document contains no start tag");
}
event = parser.next();
}
return parser;
}
public static XmlPullParser getParserFor(String stanza, String startTag)
throws XmlPullParserException, IOException {
XmlPullParser parser = getParserFor(stanza);
while (true) {
int event = parser.getEventType();
String name = parser.getName();
if (event == XmlPullParser.START_TAG && name.equals(startTag)) {
break;
}
else if (event == XmlPullParser.END_DOCUMENT) {
throw new IllegalArgumentException("Could not find start tag '" + startTag
+ "' in stanza: " + stanza);
}
parser.next();
}
return parser;
}
public static Packet parseStanza(String stanza) throws Exception {
return parseStanza(getParserFor(stanza));
}
public static Packet parseStanza(XmlPullParser parser) throws Exception {
return parseStanza(parser, null);
}
/**
* Tries to parse and return either a Message, IQ or Presence stanza.
*
* connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
*
* @param parser
* @param connection
* @return a packet which is either a Message, IQ or Presence.
* @throws Exception
*/
public static Packet parseStanza(XmlPullParser parser, XMPPConnection connection) throws Exception {
assert(parser.getEventType() == XmlPullParser.START_TAG);
final String name = parser.getName();
switch (name) {
case Message.ELEMENT:
return parseMessage(parser);
case IQ.ELEMENT:
return parse(parser, connection);
case Presence.ELEMENT:
return parsePresence(parser);
default:
throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
}
}
/**
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
* FEATURE_PROCESS_NAMESPACES is enabled.
* <p>
* Note that not all XmlPullParser implementations will return a String on
* <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
* behavior when using the parser.
* </p>
*
* @return A suitable XmlPullParser for XMPP parsing
* @throws XmlPullParserException
*/
public static XmlPullParser newXmppParser() throws XmlPullParserException {
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
if (XML_PULL_PARSER_SUPPORTS_ROUNDTRIP) {
try {
parser.setFeature(FEATURE_XML_ROUNDTRIP, true);
}
catch (XmlPullParserException e) {
LOGGER.log(Level.SEVERE,
"XmlPullParser does not support XML_ROUNDTRIP, although it was first determined to be supported",
e);
}
}
return parser;
}
/**
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
* FEATURE_PROCESS_NAMESPACES is enabled.
* <p>
* Note that not all XmlPullParser implementations will return a String on
* <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
* behavior when using the parser.
* </p>
*
* @param reader
* @return A suitable XmlPullParser for XMPP parsing
* @throws XmlPullParserException
*/
public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException {
XmlPullParser parser = newXmppParser();
parser.setInput(reader);
return parser;
}
/**
* Parses a message packet.
*
* @param parser the XML parser, positioned at the start of a message packet.
* @return a Message packet.
* @throws IOException
* @throws XmlPullParserException
* @throws SmackException
*/
public static Message parseMessage(XmlPullParser parser)
throws XmlPullParserException, IOException, SmackException {
ParserUtils.assertAtStartTag(parser);
assert(parser.getName().equals(Message.ELEMENT));
final int initialDepth = parser.getDepth();
Message message = new Message();
message.setPacketID(parser.getAttributeValue("", "id"));
message.setTo(parser.getAttributeValue("", "to"));
message.setFrom(parser.getAttributeValue("", "from"));
String typeString = parser.getAttributeValue("", "type");
if (typeString != null) {
message.setType(Message.Type.fromString(typeString));
}
String language = getLanguageAttribute(parser);
// determine message's default language
String defaultLanguage = null;
if (language != null && !"".equals(language.trim())) {
message.setLanguage(language);
defaultLanguage = language;
}
else {
defaultLanguage = Packet.getDefaultLanguage();
}
// Parse sub-elements. We include extra logic to make sure the values
// are only read once. This is because it's possible for the names to appear
// in arbitrary sub-elements.
String thread = null;
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String elementName = parser.getName();
String namespace = parser.getNamespace();
switch(elementName) {
case "subject":
String xmlLangSubject = getLanguageAttribute(parser);
if (xmlLangSubject == null) {
xmlLangSubject = defaultLanguage;
}
String subject = parseElementText(parser);
if (message.getSubject(xmlLangSubject) == null) {
message.addSubject(xmlLangSubject, subject);
}
break;
case Message.BODY:
String xmlLang = getLanguageAttribute(parser);
if (xmlLang == null) {
xmlLang = defaultLanguage;
}
String body = parseElementText(parser);
if (message.getBody(xmlLang) == null) {
message.addBody(xmlLang, body);
}
break;
case "thread":
if (thread == null) {
thread = parser.nextText();
}
break;
case "error":
message.setError(parseError(parser));
break;
default:
PacketParserUtils.addPacketExtension(message, parser, elementName, namespace);
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
message.setThread(thread);
return message;
}
/**
* Returns the textual content of an element as String.
* <p>
* The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
* Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
* </p>
* This method is used for the parts where the XMPP specification requires elements that contain
* only text or are the empty element.
*
* @param parser
* @return the textual content of the element as String
* @throws XmlPullParserException
* @throws IOException
*/
public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
assert (parser.getEventType() == XmlPullParser.START_TAG);
String res;
if (parser.isEmptyElementTag()) {
res = "";
}
else {
// Advance to the text of the Element
int event = parser.next();
if (event != XmlPullParser.TEXT) {
if (event == XmlPullParser.END_TAG) {
// Assume this is the end tag of the start tag at the
// beginning of this method. Typical examples where this
// happens are body elements containing the empty string,
// ie. <body></body>, which appears to be valid XMPP, or a
// least it's not explicitly forbidden by RFC 6121 5.2.3
return "";
} else {
throw new XmlPullParserException(
"Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
}
}
res = parser.getText();
event = parser.next();
if (event != XmlPullParser.END_TAG) {
throw new XmlPullParserException(
"Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
}
}
return res;
}
/**
* Returns the current element as string.
* <p>
* The parser must be positioned on START_TAG.
* </p>
* Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
*
* @param parser the XML pull parser
* @return the element as string
* @throws XmlPullParserException
* @throws IOException
*/
public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
return parseElement(parser, false);
}
public static CharSequence parseElement(XmlPullParser parser,
boolean fullNamespaces) throws XmlPullParserException,
IOException {
assert (parser.getEventType() == XmlPullParser.START_TAG);
return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
}
/**
* Returns the content of a element.
* <p>
* The parser must be positioned on the START_TAG of the element which content is going to get
* returned. If the current element is the empty element, then the empty string is returned. If
* it is a element which contains just text, then just the text is returned. If it contains
* nested elements (and text), then everything from the current opening tag to the corresponding
* closing tag of the same depth is returned as String.
* </p>
* Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
*
* @param parser the XML pull parser
* @return the content of a tag
* @throws XmlPullParserException if parser encounters invalid XML
* @throws IOException if an IO error occurs
*/
public static CharSequence parseContent(XmlPullParser parser)
throws XmlPullParserException, IOException {
assert(parser.getEventType() == XmlPullParser.START_TAG);
if (parser.isEmptyElementTag()) {
return "";
}
// Advance the parser, since we want to parse the content of the current element
parser.next();
return parseContentDepth(parser, parser.getDepth(), false);
}
public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
throws XmlPullParserException, IOException {
return parseContentDepth(parser, depth, false);
}
/**
* Returns the content from the current position of the parser up to the closing tag of the
* given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
* not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
* parent elements will be added to child elements that don't define a different namespace.
* <p>
* This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
* xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the
* XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
* results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
* elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
* </p>
* <p>
* In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
* </p>
*
* @param parser
* @param depth
* @param fullNamespaces
* @return the content of the current depth
* @throws XmlPullParserException
* @throws IOException
*/
public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) {
return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces);
} else {
return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
}
}
private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
boolean fullNamespaces) throws XmlPullParserException, IOException {
XmlStringBuilder xml = new XmlStringBuilder();
int event = parser.getEventType();
boolean isEmptyElement = false;
// XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
// it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
// in a nested element. It's an ugly workaround that has the potential to break things.
String namespaceElement = null;
while (true) {
if (event == XmlPullParser.START_TAG) {
xml.halfOpenElement(parser.getName());
if (namespaceElement == null || fullNamespaces) {
String namespace = parser.getNamespace();
if (StringUtils.isNotEmpty(namespace)) {
xml.attribute("xmlns", namespace);
namespaceElement = parser.getName();
}
}
for (int i = 0; i < parser.getAttributeCount(); i++) {
xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
}
if (parser.isEmptyElementTag()) {
xml.closeEmptyElement();
isEmptyElement = true;
}
else {
xml.rightAngleBracket();
}
}
else if (event == XmlPullParser.END_TAG) {
if (isEmptyElement) {
// Do nothing as the element was already closed, just reset the flag
isEmptyElement = false;
}
else {
xml.closeElement(parser.getName());
}
if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
// We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
namespaceElement = null;
}
if (parser.getDepth() <= depth) {
// Abort parsing, we are done
break;
}
}
else if (event == XmlPullParser.TEXT) {
xml.append(parser.getText());
}
event = parser.next();
}
return xml;
}
private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces)
throws XmlPullParserException, IOException {
StringBuilder sb = new StringBuilder();
int event = parser.getEventType();
outerloop: while (true) {
// Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
// twice, so in order to prevent duplication we only add their text when we are on their end tag.
if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
sb.append(parser.getText());
}
if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
break outerloop;
}
event = parser.next();
}
return sb;
}
/**
* Parses a presence packet.
*
* @param parser the XML parser, positioned at the start of a presence packet.
* @return a Presence packet.
* @throws IOException
* @throws XmlPullParserException
* @throws SmackException
*/
public static Presence parsePresence(XmlPullParser parser)
throws XmlPullParserException, IOException, SmackException {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
Presence.Type type = Presence.Type.available;
String typeString = parser.getAttributeValue("", "type");
if (typeString != null && !typeString.equals("")) {
type = Presence.Type.fromString(typeString);
}
Presence presence = new Presence(type);
presence.setTo(parser.getAttributeValue("", "to"));
presence.setFrom(parser.getAttributeValue("", "from"));
presence.setPacketID(parser.getAttributeValue("", "id"));
String language = getLanguageAttribute(parser);
if (language != null && !"".equals(language.trim())) {
presence.setLanguage(language);
}
// Parse sub-elements
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String elementName = parser.getName();
String namespace = parser.getNamespace();
switch(elementName) {
case "status":
presence.setStatus(parser.nextText());
break;
case "priority":
int priority = Integer.parseInt(parser.nextText());
presence.setPriority(priority);
break;
case "show":
String modeText = parser.nextText();
if (StringUtils.isNotEmpty(modeText)) {
presence.setMode(Presence.Mode.fromString(modeText));
} else {
// Some implementations send presence stanzas with a
// '<show />' element, which is a invalid XMPP presence
// stanza according to RFC 6121 4.7.2.1
LOGGER.warning("Empty or null mode text in presence show element form "
+ presence.getFrom()
+ " with id '"
+ presence.getPacketID()
+ "' which is invalid according to RFC6121 4.7.2.1");
}
break;
case "error":
presence.setError(parseError(parser));
break;
default:
// Otherwise, it must be a packet extension.
// Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
// failing completely here. See SMACK-390 for more information.
try {
PacketParserUtils.addPacketExtension(presence, parser, elementName, namespace);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to parse extension packet in Presence packet.", e);
}
break;
}
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
return presence;
}
/**
* Parses an IQ packet.
*
* @param parser the XML parser, positioned at the start of an IQ packet.
* @param connection the optional XMPPConnection used to send feature-not-implemented replies.
* @return an IQ object.
* @throws Exception if an exception occurs while parsing the packet.
*/
public static IQ parse(XmlPullParser parser, XMPPConnection connection) throws Exception {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
IQ iqPacket = null;
XMPPError error = null;
final String id = parser.getAttributeValue("", "id");
final String to = parser.getAttributeValue("", "to");
final String from = parser.getAttributeValue("", "from");
final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String elementName = parser.getName();
String namespace = parser.getNamespace();
switch(elementName) {
case "error":
error = PacketParserUtils.parseError(parser);
break;
// Otherwise, see if there is a registered provider for
// this element name and namespace.
default:
IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
if (provider != null) {
iqPacket = provider.parse(parser);
} else {
Class<?> introspectionProvider = ProviderManager.getIQIntrospectionProvider(
elementName, namespace);
if (introspectionProvider != null) {
iqPacket = (IQ) PacketParserUtils.parseWithIntrospection(
elementName, introspectionProvider,
parser);
}
// Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood
// have to be answered with an IQ error response. See the code a few lines below
// Note that if we reach this code, it is guranteed that the result IQ contained a child element
// (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_TAG first.
else if (IQ.Type.result == type){
// No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
// so that the content of the IQ can be examined later on
iqPacket = new UnparsedResultIQ(elementName, namespace, parseElement(parser));
}
}
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
// Decide what to do when an IQ packet was not understood
if (iqPacket == null) {
switch (type) {
case get:
case set:
if (connection == null) {
return null;
}
// If the IQ stanza is of type "get" or "set" containing a child element qualified
// by a namespace with no registered Smack provider, then answer an IQ of type
// "error" with code 501 ("feature-not-implemented")
iqPacket = new ErrorIQ(new XMPPError(XMPPError.Condition.feature_not_implemented));
iqPacket.setPacketID(id);
iqPacket.setTo(from);
iqPacket.setFrom(to);
connection.sendPacket(iqPacket);
return null;
case error:
// If an IQ packet wasn't created above, create an empty error IQ packet.
iqPacket = new ErrorIQ(error);
break;
case result:
iqPacket = new EmptyResultIQ();
break;
}
}
// Set basic values on the iq packet.
iqPacket.setPacketID(id);
iqPacket.setTo(to);
iqPacket.setFrom(from);
iqPacket.setType(type);
iqPacket.setError(error);
return iqPacket;
}
/**
* Parse the available SASL mechanisms reported from the server.
*
* @param parser the XML parser, positioned at the start of the mechanisms stanza.
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
* @throws IOException
* @throws XmlPullParserException
*/
public static Collection<String> parseMechanisms(XmlPullParser parser)
throws XmlPullParserException, IOException {
List<String> mechanisms = new ArrayList<String>();
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
if (elementName.equals("mechanism")) {
mechanisms.add(parser.nextText());
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("mechanisms")) {
done = true;
}
}
}
return mechanisms;
}
/**
* Parse the Compression Feature reported from the server.
*
* @param parser the XML parser, positioned at the start of the compression stanza.
* @return The CompressionFeature stream element
* @throws XmlPullParserException if an exception occurs while parsing the stanza.
*/
public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
throws IOException, XmlPullParserException {
assert (parser.getEventType() == XmlPullParser.START_TAG);
String name;
final int initialDepth = parser.getDepth();
List<String> methods = new LinkedList<String>();
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
name = parser.getName();
switch (name) {
case "method":
methods.add(parser.nextText());
break;
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
switch (name) {
case Compress.Feature.ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}
}
assert (parser.getEventType() == XmlPullParser.END_TAG);
assert (parser.getDepth() == initialDepth);
return new Compress.Feature(methods);
}
/**
* Parses SASL authentication error packets.
*
* @param parser the XML parser.
* @return a SASL Failure packet.
* @throws Exception if an exception occurs while parsing the packet.
*/
public static SASLFailure parseSASLFailure(XmlPullParser parser) throws Exception {
String condition = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (!parser.getName().equals("failure")) {
condition = parser.getName();
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("failure")) {
done = true;
}
}
}
return new SASLFailure(condition);
}
/**
* Parses stream error packets.
*
* @param parser the XML parser.
* @return an stream error packet.
* @throws XmlPullParserException if an exception occurs while parsing the packet.
*/
public static StreamError parseStreamError(XmlPullParser parser) throws IOException,
XmlPullParserException {
final int depth = parser.getDepth();
boolean done = false;
String code = null;
String text = null;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String namespace = parser.getNamespace();
if (StreamError.NAMESPACE.equals(namespace)) {
String name = parser.getName();
if (name.equals(Packet.TEXT) && !parser.isEmptyElementTag()) {
parser.next();
text = parser.getText();
}
else {
// If it's not a text element, that is qualified by the StreamError.NAMESPACE,
// then it has to be the stream error code
code = name;
}
}
}
else if (eventType == XmlPullParser.END_TAG && depth == parser.getDepth()) {
done = true;
}
}
return new StreamError(code, text);
}
/**
* Parses error sub-packets.
*
* @param parser the XML parser.
* @return an error sub-packet.
* @throws IOException
* @throws XmlPullParserException
* @throws SmackException
*/
public static XMPPError parseError(XmlPullParser parser)
throws XmlPullParserException, IOException, SmackException {
String type = null;
String message = null;
String condition = null;
List<PacketExtension> extensions = new ArrayList<PacketExtension>();
// Parse the error header
type = parser.getAttributeValue("", "type");
// Parse the text and condition tags
outerloop:
while (true) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(Packet.TEXT)) {
message = parser.nextText();
}
else {
// Condition tag, it can be xmpp error or an application defined error.
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (namespace.equals(XMPPError.NAMESPACE)) {
condition = elementName;
}
else {
PacketParserUtils.addPacketExtension(extensions, parser, elementName, namespace);
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("error")) {
break outerloop;
}
}
}
// Parse the error type.
XMPPError.Type errorType = XMPPError.Type.CANCEL;
try {
if (type != null) {
errorType = XMPPError.Type.valueOf(type.toUpperCase(Locale.US));
}
}
catch (IllegalArgumentException iae) {
LOGGER.log(Level.SEVERE, "Could not find error type for " + type.toUpperCase(Locale.US), iae);
}
return new XMPPError(errorType, condition, message, extensions);
}
/**
* Parses a packet extension sub-packet.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML namespace of the packet extension.
* @param parser the XML parser, positioned at the starting element of the extension.
* @return a PacketExtension.
*/
public static PacketExtension parsePacketExtension(String elementName, String namespace,
XmlPullParser parser) throws XmlPullParserException,
IOException, SmackException {
ParserUtils.assertAtStartTag(parser);
// See if a provider is registered to handle the extension.
PacketExtensionProvider<PacketExtension> provider = ProviderManager.getExtensionProvider(elementName, namespace);
if (provider != null) {
return provider.parse(parser);
}
Class<?> introspectionProvider = ProviderManager.getExtensionIntrospectionProvider(elementName, namespace);
if (introspectionProvider != null) {
try {
return (PacketExtension)parseWithIntrospection(elementName, introspectionProvider, parser);
} catch (NoSuchMethodException | SecurityException
| InstantiationException | IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| ClassNotFoundException e) {
throw new SmackException(e);
}
}
final int initialDepth = parser.getDepth();
// No providers registered, so use a default extension.
DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String name = parser.getName();
// If an empty element, set the value with the empty string.
if (parser.isEmptyElementTag()) {
extension.setValue(name,"");
}
// Otherwise, get the the element text.
else {
eventType = parser.next();
if (eventType == XmlPullParser.TEXT) {
String value = parser.getText();
extension.setValue(name, value);
}
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}
return extension;
}
public static StartTls parseStartTlsFeature(XmlPullParser parser)
throws XmlPullParserException, IOException {
assert (parser.getEventType() == XmlPullParser.START_TAG);
assert (parser.getNamespace().equals(StartTls.NAMESPACE));
int initalDepth = parser.getDepth();
boolean required = false;
outerloop: while (true) {
int event = parser.next();
switch (event) {
case XmlPullParser.START_TAG:
String name = parser.getName();
switch (name) {
case "required":
required = true;
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initalDepth) {
break outerloop;
}
}
}
assert(parser.getEventType() == XmlPullParser.END_TAG);
return new StartTls(required);
}
private static String getLanguageAttribute(XmlPullParser parser) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
String attributeName = parser.getAttributeName(i);
if ( "xml:lang".equals(attributeName) ||
("lang".equals(attributeName) &&
"xml".equals(parser.getAttributePrefix(i)))) {
return parser.getAttributeValue(i);
}
}
return null;
}
public static Object parseWithIntrospection(String elementName, Class<?> objectClass,
XmlPullParser parser) throws NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException, XmlPullParserException,
IOException, IllegalArgumentException, InvocationTargetException,
ClassNotFoundException {
boolean done = false;
Object object = objectClass.newInstance();
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String name = parser.getName();
String stringValue = parser.nextText();
Class<?> propertyType = object.getClass().getMethod(
"get" + Character.toUpperCase(name.charAt(0)) + name.substring(1)).getReturnType();
// Get the value of the property by converting it from a
// String to the correct object type.
Object value = decode(propertyType, stringValue);
// Set the value of the bean.
object.getClass().getMethod(
"set" + Character.toUpperCase(name.charAt(0)) + name.substring(1),
propertyType).invoke(object, value);
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
done = true;
}
}
}
return object;
}
public static void addPacketExtension(Packet packet, XmlPullParser parser) throws XmlPullParserException,
IOException, SmackException {
ParserUtils.assertAtStartTag(parser);
addPacketExtension(packet, parser, parser.getName(), parser.getNamespace());
}
public static void addPacketExtension(Packet packet, XmlPullParser parser, String elementName, String namespace)
throws XmlPullParserException, IOException, SmackException {
PacketExtension packetExtension = parsePacketExtension(elementName, namespace, parser);
packet.addExtension(packetExtension);
}
public static void addPacketExtension(Collection<PacketExtension> collection, XmlPullParser parser)
throws XmlPullParserException, IOException, SmackException {
addPacketExtension(collection, parser, parser.getName(), parser.getNamespace());
}
public static void addPacketExtension(Collection<PacketExtension> collection, XmlPullParser parser,
String elementName, String namespace) throws XmlPullParserException, IOException, SmackException {
PacketExtension packetExtension = parsePacketExtension(elementName, namespace, parser);
collection.add(packetExtension);
}
/**
* Decodes a String into an object of the specified type. If the object
* type is not supported, null will be returned.
*
* @param type the type of the property.
* @param value the encode String value to decode.
* @return the String value decoded into the specified type.
* @throws ClassNotFoundException
*/
private static Object decode(Class<?> type, String value) throws ClassNotFoundException {
if (type.getName().equals("java.lang.String")) {
return value;
}
if (type.getName().equals("boolean")) {
return Boolean.valueOf(value);
}
if (type.getName().equals("int")) {
return Integer.valueOf(value);
}
if (type.getName().equals("long")) {
return Long.valueOf(value);
}
if (type.getName().equals("float")) {
return Float.valueOf(value);
}
if (type.getName().equals("double")) {
return Double.valueOf(value);
}
if (type.getName().equals("java.lang.Class")) {
return Class.forName(value);
}
return null;
}
/**
* This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider
* was found for the IQ element.
*
* The child elements can be examined with the getChildElementXML() method.
*
*/
public static class UnparsedResultIQ extends IQ {
private UnparsedResultIQ(String element, String namespace, CharSequence content) {
super(element, namespace);
this.content = content;
}
private final CharSequence content;
public CharSequence getContent() {
return content;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
throw new UnsupportedOperationException();
}
}
}