PacketParserUtils.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.util;

  18. import java.io.IOException;
  19. import java.io.Reader;
  20. import java.io.StringReader;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.HashMap;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.logging.Level;
  28. import java.util.logging.Logger;

  29. import org.jivesoftware.smack.SmackException;
  30. import org.jivesoftware.smack.compress.packet.Compress;
  31. import org.jivesoftware.smack.packet.DefaultExtensionElement;
  32. import org.jivesoftware.smack.packet.EmptyResultIQ;
  33. import org.jivesoftware.smack.packet.ErrorIQ;
  34. import org.jivesoftware.smack.packet.IQ;
  35. import org.jivesoftware.smack.packet.Message;
  36. import org.jivesoftware.smack.packet.Stanza;
  37. import org.jivesoftware.smack.packet.ExtensionElement;
  38. import org.jivesoftware.smack.packet.Presence;
  39. import org.jivesoftware.smack.packet.Session;
  40. import org.jivesoftware.smack.packet.StartTls;
  41. import org.jivesoftware.smack.packet.StreamError;
  42. import org.jivesoftware.smack.packet.UnparsedIQ;
  43. import org.jivesoftware.smack.packet.XMPPError;
  44. import org.jivesoftware.smack.provider.IQProvider;
  45. import org.jivesoftware.smack.provider.ExtensionElementProvider;
  46. import org.jivesoftware.smack.provider.ProviderManager;
  47. import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
  48. import org.jxmpp.jid.Jid;
  49. import org.xmlpull.v1.XmlPullParser;
  50. import org.xmlpull.v1.XmlPullParserException;
  51. import org.xmlpull.v1.XmlPullParserFactory;

  52. /**
  53.  * Utility class that helps to parse packets. Any parsing packets method that must be shared
  54.  * between many clients must be placed in this utility class.
  55.  *
  56.  * @author Gaston Dombiak
  57.  */
  58. public class PacketParserUtils {
  59.     private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());

  60.     public static final String FEATURE_XML_ROUNDTRIP = "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";

  61.     private static final XmlPullParserFactory XML_PULL_PARSER_FACTORY;

  62.     /**
  63.      * True if the XmlPullParser supports the XML_ROUNDTRIP feature.
  64.      */
  65.     public static final boolean XML_PULL_PARSER_SUPPORTS_ROUNDTRIP;

  66.     static {
  67.         XmlPullParser xmlPullParser;
  68.         boolean roundtrip = false;
  69.         try {
  70.             XML_PULL_PARSER_FACTORY = XmlPullParserFactory.newInstance();
  71.             xmlPullParser = XML_PULL_PARSER_FACTORY.newPullParser();
  72.             try {
  73.                 xmlPullParser.setFeature(FEATURE_XML_ROUNDTRIP, true);
  74.                 // We could successfully set the feature
  75.                 roundtrip = true;
  76.             } catch (XmlPullParserException e) {
  77.                 // Doesn't matter if FEATURE_XML_ROUNDTRIP isn't available
  78.                 LOGGER.log(Level.FINEST, "XmlPullParser does not support XML_ROUNDTRIP", e);
  79.             }
  80.         }
  81.         catch (XmlPullParserException e) {
  82.             // Something really bad happened
  83.             throw new AssertionError(e);
  84.         }
  85.         XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip;
  86.     }

  87.     public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
  88.         return getParserFor(new StringReader(stanza));
  89.     }

  90.     public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
  91.         XmlPullParser parser = newXmppParser(reader);
  92.         // Wind the parser forward to the first start tag
  93.         int event = parser.getEventType();
  94.         while (event != XmlPullParser.START_TAG) {
  95.             if (event == XmlPullParser.END_DOCUMENT) {
  96.                 throw new IllegalArgumentException("Document contains no start tag");
  97.             }
  98.             event = parser.next();
  99.         }
  100.         return parser;
  101.     }

  102.     public static XmlPullParser getParserFor(String stanza, String startTag)
  103.                     throws XmlPullParserException, IOException {
  104.         XmlPullParser parser = getParserFor(stanza);

  105.         while (true) {
  106.             int event = parser.getEventType();
  107.             String name = parser.getName();
  108.             if (event == XmlPullParser.START_TAG && name.equals(startTag)) {
  109.                 break;
  110.             }
  111.             else if (event == XmlPullParser.END_DOCUMENT) {
  112.                 throw new IllegalArgumentException("Could not find start tag '" + startTag
  113.                                 + "' in stanza: " + stanza);
  114.             }
  115.             parser.next();
  116.         }

  117.         return parser;
  118.     }

  119.     public static Stanza parseStanza(String stanza) throws Exception {
  120.         return parseStanza(getParserFor(stanza));
  121.     }

  122.     /**
  123.      * Tries to parse and return either a Message, IQ or Presence stanza.
  124.      *
  125.      * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
  126.      *
  127.      * @param parser
  128.      * @return a packet which is either a Message, IQ or Presence.
  129.      * @throws Exception
  130.      */
  131.     public static Stanza parseStanza(XmlPullParser parser) throws Exception {
  132.         ParserUtils.assertAtStartTag(parser);
  133.         final String name = parser.getName();
  134.         switch (name) {
  135.         case Message.ELEMENT:
  136.             return parseMessage(parser);
  137.         case IQ.IQ_ELEMENT:
  138.             return parseIQ(parser);
  139.         case Presence.ELEMENT:
  140.             return parsePresence(parser);
  141.         default:
  142.             throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
  143.         }
  144.     }

  145.     /**
  146.      * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
  147.      * FEATURE_PROCESS_NAMESPACES is enabled.
  148.      * <p>
  149.      * Note that not all XmlPullParser implementations will return a String on
  150.      * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
  151.      * behavior when using the parser.
  152.      * </p>
  153.      *
  154.      * @return A suitable XmlPullParser for XMPP parsing
  155.      * @throws XmlPullParserException
  156.      */
  157.     public static XmlPullParser newXmppParser() throws XmlPullParserException {
  158.         XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
  159.         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
  160.         if (XML_PULL_PARSER_SUPPORTS_ROUNDTRIP) {
  161.             try {
  162.                 parser.setFeature(FEATURE_XML_ROUNDTRIP, true);
  163.             }
  164.             catch (XmlPullParserException e) {
  165.                 LOGGER.log(Level.SEVERE,
  166.                                 "XmlPullParser does not support XML_ROUNDTRIP, although it was first determined to be supported",
  167.                                 e);
  168.             }
  169.         }
  170.         return parser;
  171.     }

  172.     /**
  173.      * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
  174.      * FEATURE_PROCESS_NAMESPACES is enabled.
  175.      * <p>
  176.      * Note that not all XmlPullParser implementations will return a String on
  177.      * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
  178.      * behavior when using the parser.
  179.      * </p>
  180.      *
  181.      * @param reader
  182.      * @return A suitable XmlPullParser for XMPP parsing
  183.      * @throws XmlPullParserException
  184.      */
  185.     public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException {
  186.         XmlPullParser parser = newXmppParser();
  187.         parser.setInput(reader);
  188.         return parser;
  189.     }

  190.     /**
  191.      * Parses a message packet.
  192.      *
  193.      * @param parser the XML parser, positioned at the start of a message packet.
  194.      * @return a Message packet.
  195.      * @throws Exception
  196.      */
  197.     public static Message parseMessage(XmlPullParser parser)
  198.                     throws Exception {
  199.         ParserUtils.assertAtStartTag(parser);
  200.         assert(parser.getName().equals(Message.ELEMENT));

  201.         final int initialDepth = parser.getDepth();
  202.         Message message = new Message();
  203.         message.setStanzaId(parser.getAttributeValue("", "id"));
  204.         message.setTo(ParserUtils.getJidAttribute(parser, "to"));
  205.         message.setFrom(ParserUtils.getJidAttribute(parser, "from"));
  206.         String typeString = parser.getAttributeValue("", "type");
  207.         if (typeString != null) {
  208.             message.setType(Message.Type.fromString(typeString));
  209.         }
  210.         String language = getLanguageAttribute(parser);
  211.        
  212.         // determine message's default language
  213.         String defaultLanguage = null;
  214.         if (language != null && !"".equals(language.trim())) {
  215.             message.setLanguage(language);
  216.             defaultLanguage = language;
  217.         }
  218.         else {
  219.             defaultLanguage = Stanza.getDefaultLanguage();
  220.         }

  221.         // Parse sub-elements. We include extra logic to make sure the values
  222.         // are only read once. This is because it's possible for the names to appear
  223.         // in arbitrary sub-elements.
  224.         String thread = null;
  225.         outerloop: while (true) {
  226.             int eventType = parser.next();
  227.             switch (eventType) {
  228.             case XmlPullParser.START_TAG:
  229.                 String elementName = parser.getName();
  230.                 String namespace = parser.getNamespace();
  231.                 switch(elementName) {
  232.                 case "subject":
  233.                     String xmlLangSubject = getLanguageAttribute(parser);
  234.                     if (xmlLangSubject == null) {
  235.                         xmlLangSubject = defaultLanguage;
  236.                     }

  237.                     String subject = parseElementText(parser);

  238.                     if (message.getSubject(xmlLangSubject) == null) {
  239.                         message.addSubject(xmlLangSubject, subject);
  240.                     }
  241.                     break;
  242.                 case Message.BODY:
  243.                     String xmlLang = getLanguageAttribute(parser);
  244.                     if (xmlLang == null) {
  245.                         xmlLang = defaultLanguage;
  246.                     }

  247.                     String body = parseElementText(parser);

  248.                     if (message.getBody(xmlLang) == null) {
  249.                         message.addBody(xmlLang, body);
  250.                     }
  251.                     break;
  252.                 case "thread":
  253.                     if (thread == null) {
  254.                         thread = parser.nextText();
  255.                     }
  256.                     break;
  257.                 case "error":
  258.                     message.setError(parseError(parser));
  259.                     break;
  260.                  default:
  261.                     PacketParserUtils.addExtensionElement(message, parser, elementName, namespace);
  262.                     break;
  263.                 }
  264.                 break;
  265.             case XmlPullParser.END_TAG:
  266.                 if (parser.getDepth() == initialDepth) {
  267.                     break outerloop;
  268.                 }
  269.                 break;
  270.             }
  271.         }

  272.         message.setThread(thread);
  273.         return message;
  274.     }

  275.     /**
  276.      * Returns the textual content of an element as String.
  277.      * <p>
  278.      * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
  279.      * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
  280.      * </p>
  281.      * This method is used for the parts where the XMPP specification requires elements that contain
  282.      * only text or are the empty element.
  283.      *
  284.      * @param parser
  285.      * @return the textual content of the element as String
  286.      * @throws XmlPullParserException
  287.      * @throws IOException
  288.      */
  289.     public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
  290.         assert (parser.getEventType() == XmlPullParser.START_TAG);
  291.         String res;
  292.         if (parser.isEmptyElementTag()) {
  293.             res = "";
  294.         }
  295.         else {
  296.             // Advance to the text of the Element
  297.             int event = parser.next();
  298.             if (event != XmlPullParser.TEXT) {
  299.                 if (event == XmlPullParser.END_TAG) {
  300.                     // Assume this is the end tag of the start tag at the
  301.                     // beginning of this method. Typical examples where this
  302.                     // happens are body elements containing the empty string,
  303.                     // ie. <body></body>, which appears to be valid XMPP, or a
  304.                     // least it's not explicitly forbidden by RFC 6121 5.2.3
  305.                     return "";
  306.                 } else {
  307.                     throw new XmlPullParserException(
  308.                                     "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
  309.                 }
  310.             }
  311.             res = parser.getText();
  312.             event = parser.next();
  313.             if (event != XmlPullParser.END_TAG) {
  314.                 throw new XmlPullParserException(
  315.                                 "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
  316.             }
  317.         }
  318.         return res;
  319.     }

  320.     /**
  321.      * Returns the current element as string.
  322.      * <p>
  323.      * The parser must be positioned on START_TAG.
  324.      * </p>
  325.      * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
  326.      *
  327.      * @param parser the XML pull parser
  328.      * @return the element as string
  329.      * @throws XmlPullParserException
  330.      * @throws IOException
  331.      */
  332.     public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
  333.         return parseElement(parser, false);
  334.     }

  335.     public static CharSequence parseElement(XmlPullParser parser,
  336.                     boolean fullNamespaces) throws XmlPullParserException,
  337.                     IOException {
  338.         assert (parser.getEventType() == XmlPullParser.START_TAG);
  339.         return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
  340.     }

  341.     /**
  342.      * Returns the content of a element.
  343.      * <p>
  344.      * The parser must be positioned on the START_TAG of the element which content is going to get
  345.      * returned. If the current element is the empty element, then the empty string is returned. If
  346.      * it is a element which contains just text, then just the text is returned. If it contains
  347.      * nested elements (and text), then everything from the current opening tag to the corresponding
  348.      * closing tag of the same depth is returned as String.
  349.      * </p>
  350.      * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
  351.      *
  352.      * @param parser the XML pull parser
  353.      * @return the content of a tag
  354.      * @throws XmlPullParserException if parser encounters invalid XML
  355.      * @throws IOException if an IO error occurs
  356.      */
  357.     public static CharSequence parseContent(XmlPullParser parser)
  358.                     throws XmlPullParserException, IOException {
  359.         assert(parser.getEventType() == XmlPullParser.START_TAG);
  360.         if (parser.isEmptyElementTag()) {
  361.             return "";
  362.         }
  363.         // Advance the parser, since we want to parse the content of the current element
  364.         parser.next();
  365.         return parseContentDepth(parser, parser.getDepth(), false);
  366.     }

  367.     public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
  368.                     throws XmlPullParserException, IOException {
  369.         return parseContentDepth(parser, depth, false);
  370.     }

  371.     /**
  372.      * Returns the content from the current position of the parser up to the closing tag of the
  373.      * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
  374.      * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
  375.      * parent elements will be added to child elements that don't define a different namespace.
  376.      * <p>
  377.      * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
  378.      * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the
  379.      * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
  380.      * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
  381.      * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
  382.      * </p>
  383.      * <p>
  384.      * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
  385.      * </p>
  386.      *
  387.      * @param parser
  388.      * @param depth
  389.      * @param fullNamespaces
  390.      * @return the content of the current depth
  391.      * @throws XmlPullParserException
  392.      * @throws IOException
  393.      */
  394.     public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
  395.         if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) {
  396.             return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces);
  397.         } else {
  398.             return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
  399.         }
  400.     }

  401.     private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
  402.                     boolean fullNamespaces) throws XmlPullParserException, IOException {
  403.         XmlStringBuilder xml = new XmlStringBuilder();
  404.         int event = parser.getEventType();
  405.         boolean isEmptyElement = false;
  406.         // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
  407.         // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
  408.         // in a nested element. It's an ugly workaround that has the potential to break things.
  409.         String namespaceElement = null;
  410.         outerloop: while (true) {
  411.             switch (event) {
  412.             case XmlPullParser.START_TAG:
  413.                 xml.halfOpenElement(parser.getName());
  414.                 if (namespaceElement == null || fullNamespaces) {
  415.                     String namespace = parser.getNamespace();
  416.                     if (StringUtils.isNotEmpty(namespace)) {
  417.                         xml.attribute("xmlns", namespace);
  418.                         namespaceElement = parser.getName();
  419.                     }
  420.                 }
  421.                 for (int i = 0; i < parser.getAttributeCount(); i++) {
  422.                     xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
  423.                 }
  424.                 if (parser.isEmptyElementTag()) {
  425.                     xml.closeEmptyElement();
  426.                     isEmptyElement = true;
  427.                 }
  428.                 else {
  429.                     xml.rightAngleBracket();
  430.                 }
  431.                 break;
  432.             case XmlPullParser.END_TAG:
  433.                 if (isEmptyElement) {
  434.                     // Do nothing as the element was already closed, just reset the flag
  435.                     isEmptyElement = false;
  436.                 }
  437.                 else {
  438.                     xml.closeElement(parser.getName());
  439.                 }
  440.                 if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
  441.                     // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
  442.                     namespaceElement = null;
  443.                 }
  444.                 if (parser.getDepth() <= depth) {
  445.                     // Abort parsing, we are done
  446.                     break outerloop;
  447.                 }
  448.                 break;
  449.             case XmlPullParser.TEXT:
  450.                 xml.append(parser.getText());
  451.                 break;
  452.             }
  453.             event = parser.next();
  454.         }
  455.         return xml;
  456.     }

  457.     private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces)
  458.                     throws XmlPullParserException, IOException {
  459.         StringBuilder sb = new StringBuilder();
  460.         int event = parser.getEventType();
  461.         outerloop: while (true) {
  462.             // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
  463.             // twice, so in order to prevent duplication we only add their text when we are on their end tag.
  464.             if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
  465.                 sb.append(parser.getText());
  466.             }
  467.             if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
  468.                 break outerloop;
  469.             }
  470.             event = parser.next();
  471.         }
  472.         return sb;
  473.     }

  474.     /**
  475.      * Parses a presence packet.
  476.      *
  477.      * @param parser the XML parser, positioned at the start of a presence packet.
  478.      * @return a Presence packet.
  479.      * @throws Exception
  480.      */
  481.     public static Presence parsePresence(XmlPullParser parser)
  482.                     throws Exception {
  483.         ParserUtils.assertAtStartTag(parser);
  484.         final int initialDepth = parser.getDepth();

  485.         Presence.Type type = Presence.Type.available;
  486.         String typeString = parser.getAttributeValue("", "type");
  487.         if (typeString != null && !typeString.equals("")) {
  488.             type = Presence.Type.fromString(typeString);
  489.         }
  490.         Presence presence = new Presence(type);
  491.         presence.setTo(ParserUtils.getJidAttribute(parser, "to"));
  492.         presence.setFrom(ParserUtils.getJidAttribute(parser, "from"));
  493.         presence.setStanzaId(parser.getAttributeValue("", "id"));

  494.         String language = getLanguageAttribute(parser);
  495.         if (language != null && !"".equals(language.trim())) {
  496.             presence.setLanguage(language);
  497.         }

  498.         // Parse sub-elements
  499.         outerloop: while (true) {
  500.             int eventType = parser.next();
  501.             switch (eventType) {
  502.             case XmlPullParser.START_TAG:
  503.                 String elementName = parser.getName();
  504.                 String namespace = parser.getNamespace();
  505.                 switch(elementName) {
  506.                 case "status":
  507.                     presence.setStatus(parser.nextText());
  508.                     break;
  509.                 case "priority":
  510.                     int priority = Integer.parseInt(parser.nextText());
  511.                     presence.setPriority(priority);
  512.                     break;
  513.                 case "show":
  514.                     String modeText = parser.nextText();
  515.                     if (StringUtils.isNotEmpty(modeText)) {
  516.                         presence.setMode(Presence.Mode.fromString(modeText));
  517.                     } else {
  518.                         // Some implementations send presence stanzas with a
  519.                         // '<show />' element, which is a invalid XMPP presence
  520.                         // stanza according to RFC 6121 4.7.2.1
  521.                         LOGGER.warning("Empty or null mode text in presence show element form "
  522.                                         + presence.getFrom()
  523.                                         + " with id '"
  524.                                         + presence.getStanzaId()
  525.                                         + "' which is invalid according to RFC6121 4.7.2.1");
  526.                     }
  527.                     break;
  528.                 case "error":
  529.                     presence.setError(parseError(parser));
  530.                     break;
  531.                 default:
  532.                 // Otherwise, it must be a packet extension.
  533.                     // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
  534.                     // failing completely here. See SMACK-390 for more information.
  535.                     try {
  536.                         PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace);
  537.                     } catch (Exception e) {
  538.                         LOGGER.log(Level.WARNING, "Failed to parse extension packet in Presence packet.", e);
  539.                     }
  540.                     break;
  541.                 }
  542.             case XmlPullParser.END_TAG:
  543.                 if (parser.getDepth() == initialDepth) {
  544.                     break outerloop;
  545.                 }
  546.                 break;
  547.             }
  548.         }
  549.         return presence;
  550.     }

  551.     /**
  552.      * Parses an IQ packet.
  553.      *
  554.      * @param parser the XML parser, positioned at the start of an IQ packet.
  555.      * @return an IQ object.
  556.      * @throws XmlPullParserException
  557.      * @throws IOException
  558.      * @throws SmackException
  559.      */
  560.     public static IQ parseIQ(XmlPullParser parser) throws Exception {
  561.         ParserUtils.assertAtStartTag(parser);
  562.         final int initialDepth = parser.getDepth();
  563.         IQ iqPacket = null;
  564.         XMPPError error = null;

  565.         final String id = parser.getAttributeValue("", "id");
  566.         final Jid to = ParserUtils.getJidAttribute(parser, "to");
  567.         final Jid from = ParserUtils.getJidAttribute(parser, "from");
  568.         final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));

  569.         outerloop: while (true) {
  570.             int eventType = parser.next();

  571.             switch (eventType) {
  572.             case XmlPullParser.START_TAG:
  573.                 String elementName = parser.getName();
  574.                 String namespace = parser.getNamespace();
  575.                 switch(elementName) {
  576.                 case "error":
  577.                     error = PacketParserUtils.parseError(parser);
  578.                     break;
  579.                 // Otherwise, see if there is a registered provider for
  580.                 // this element name and namespace.
  581.                 default:
  582.                     IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
  583.                     if (provider != null) {
  584.                             iqPacket = provider.parse(parser);
  585.                     }
  586.                     // Note that if we reach this code, it is guranteed that the result IQ contained a child element
  587.                     // (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_TAG first.
  588.                     else {
  589.                         // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
  590.                         // so that the content of the IQ can be examined later on
  591.                         iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
  592.                     }
  593.                     break;
  594.                 }
  595.                 break;
  596.             case XmlPullParser.END_TAG:
  597.                 if (parser.getDepth() == initialDepth) {
  598.                     break outerloop;
  599.                 }
  600.                 break;
  601.             }
  602.         }
  603.         // Decide what to do when an IQ packet was not understood
  604.         if (iqPacket == null) {
  605.             switch (type) {
  606.             case error:
  607.                 // If an IQ packet wasn't created above, create an empty error IQ packet.
  608.                 iqPacket = new ErrorIQ(error);
  609.                 break;
  610.             case result:
  611.                 iqPacket = new EmptyResultIQ();
  612.                 break;
  613.             default:
  614.                 break;
  615.             }
  616.         }

  617.         // Set basic values on the iq packet.
  618.         iqPacket.setStanzaId(id);
  619.         iqPacket.setTo(to);
  620.         iqPacket.setFrom(from);
  621.         iqPacket.setType(type);
  622.         iqPacket.setError(error);

  623.         return iqPacket;
  624.     }

  625.     /**
  626.      * Parse the available SASL mechanisms reported from the server.
  627.      *
  628.      * @param parser the XML parser, positioned at the start of the mechanisms stanza.
  629.      * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
  630.      * @throws IOException
  631.      * @throws XmlPullParserException
  632.      */
  633.     public static Collection<String> parseMechanisms(XmlPullParser parser)
  634.                     throws XmlPullParserException, IOException {
  635.         List<String> mechanisms = new ArrayList<String>();
  636.         boolean done = false;
  637.         while (!done) {
  638.             int eventType = parser.next();

  639.             if (eventType == XmlPullParser.START_TAG) {
  640.                 String elementName = parser.getName();
  641.                 if (elementName.equals("mechanism")) {
  642.                     mechanisms.add(parser.nextText());
  643.                 }
  644.             }
  645.             else if (eventType == XmlPullParser.END_TAG) {
  646.                 if (parser.getName().equals("mechanisms")) {
  647.                     done = true;
  648.                 }
  649.             }
  650.         }
  651.         return mechanisms;
  652.     }

  653.     /**
  654.      * Parse the Compression Feature reported from the server.
  655.      *
  656.      * @param parser the XML parser, positioned at the start of the compression stanza.
  657.      * @return The CompressionFeature stream element
  658.      * @throws XmlPullParserException if an exception occurs while parsing the stanza.
  659.      */
  660.     public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
  661.                     throws IOException, XmlPullParserException {
  662.         assert (parser.getEventType() == XmlPullParser.START_TAG);
  663.         String name;
  664.         final int initialDepth = parser.getDepth();
  665.         List<String> methods = new LinkedList<String>();
  666.         outerloop: while (true) {
  667.             int eventType = parser.next();
  668.             switch (eventType) {
  669.             case XmlPullParser.START_TAG:
  670.                 name = parser.getName();
  671.                 switch (name) {
  672.                 case "method":
  673.                     methods.add(parser.nextText());
  674.                     break;
  675.                 }
  676.                 break;
  677.             case XmlPullParser.END_TAG:
  678.                 name = parser.getName();
  679.                 switch (name) {
  680.                 case Compress.Feature.ELEMENT:
  681.                     if (parser.getDepth() == initialDepth) {
  682.                         break outerloop;
  683.                     }
  684.                 }
  685.             }
  686.         }
  687.         assert (parser.getEventType() == XmlPullParser.END_TAG);
  688.         assert (parser.getDepth() == initialDepth);
  689.         return new Compress.Feature(methods);
  690.     }

  691.     public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
  692.                     throws XmlPullParserException, IOException {
  693.         if (descriptiveTexts == null) {
  694.             descriptiveTexts = new HashMap<String, String>();
  695.         }
  696.         String xmllang = getLanguageAttribute(parser);
  697.         String text = parser.nextText();
  698.         String previousValue = descriptiveTexts.put(xmllang, text);
  699.         assert (previousValue == null);
  700.         return descriptiveTexts;
  701.     }

  702.     /**
  703.      * Parses SASL authentication error packets.
  704.      *
  705.      * @param parser the XML parser.
  706.      * @return a SASL Failure packet.
  707.      * @throws IOException
  708.      * @throws XmlPullParserException
  709.      */
  710.     public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
  711.         final int initialDepth = parser.getDepth();
  712.         String condition = null;
  713.         Map<String, String> descriptiveTexts = null;
  714.         outerloop: while (true) {
  715.             int eventType = parser.next();
  716.             switch (eventType) {
  717.             case XmlPullParser.START_TAG:
  718.                 String name = parser.getName();
  719.                 if (name.equals("text")) {
  720.                     descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
  721.                 }
  722.                 else {
  723.                     assert(condition == null);
  724.                     condition = parser.getName();
  725.                 }
  726.                 break;
  727.             case XmlPullParser.END_TAG:
  728.                 if (parser.getDepth() == initialDepth) {
  729.                     break outerloop;
  730.                 }
  731.                 break;
  732.             }
  733.         }
  734.         return new SASLFailure(condition, descriptiveTexts);
  735.     }

  736.     /**
  737.      * Parses stream error packets.
  738.      *
  739.      * @param parser the XML parser.
  740.      * @return an stream error packet.
  741.      * @throws Exception if an exception occurs while parsing the packet.
  742.      */
  743.     public static StreamError parseStreamError(XmlPullParser parser) throws Exception {
  744.         final int initialDepth = parser.getDepth();
  745.         List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
  746.         Map<String, String> descriptiveTexts = null;
  747.         StreamError.Condition condition = null;
  748.         String conditionText = null;
  749.         outerloop: while (true) {
  750.             int eventType = parser.next();
  751.             switch (eventType) {
  752.             case XmlPullParser.START_TAG:
  753.                 String name = parser.getName();
  754.                 String namespace = parser.getNamespace();
  755.                 switch (namespace) {
  756.                 case StreamError.NAMESPACE:
  757.                     switch (name) {
  758.                     case "text":
  759.                         descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
  760.                         break;
  761.                     default:
  762.                         // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
  763.                         // then it has to be the stream error code
  764.                         condition = StreamError.Condition.fromString(name);
  765.                         if (!parser.isEmptyElementTag()) {
  766.                             conditionText = parser.nextText();
  767.                         }
  768.                         break;
  769.                     }
  770.                     break;
  771.                 default:
  772.                     PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
  773.                     break;
  774.                 }
  775.                 break;
  776.             case XmlPullParser.END_TAG:
  777.                 if (parser.getDepth() == initialDepth) {
  778.                     break outerloop;
  779.                 }
  780.                 break;
  781.             }
  782.         }
  783.         return new StreamError(condition, conditionText, descriptiveTexts, extensions);
  784.     }

  785.     /**
  786.      * Parses error sub-packets.
  787.      *
  788.      * @param parser the XML parser.
  789.      * @return an error sub-packet.
  790.      * @throws Exception
  791.      */
  792.     public static XMPPError parseError(XmlPullParser parser)
  793.                     throws Exception {
  794.         final int initialDepth = parser.getDepth();
  795.         Map<String, String> descriptiveTexts = null;
  796.         XMPPError.Condition condition = null;
  797.         String conditionText = null;
  798.         List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();

  799.         // Parse the error header
  800.         XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type"));
  801.         String errorGenerator = parser.getAttributeValue("", "by");

  802.         outerloop: while (true) {
  803.             int eventType = parser.next();
  804.             switch (eventType) {
  805.             case XmlPullParser.START_TAG:
  806.                 String name = parser.getName();
  807.                 String namespace = parser.getNamespace();
  808.                 switch (namespace) {
  809.                 case XMPPError.NAMESPACE:
  810.                     switch (name) {
  811.                     case Stanza.TEXT:
  812.                         descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
  813.                         break;
  814.                     default:
  815.                         condition = XMPPError.Condition.fromString(name);
  816.                         if (!parser.isEmptyElementTag()) {
  817.                             conditionText = parser.nextText();
  818.                         }
  819.                         break;
  820.                     }
  821.                     break;
  822.                 default:
  823.                     PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
  824.                 }
  825.                 break;
  826.             case XmlPullParser.END_TAG:
  827.                 if (parser.getDepth() == initialDepth) {
  828.                     break outerloop;
  829.                 }
  830.             }
  831.         }
  832.         return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions);
  833.     }

  834.     /**
  835.      * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead.
  836.      */
  837.     @Deprecated
  838.     public static ExtensionElement parsePacketExtension(String elementName, String namespace,
  839.                     XmlPullParser parser) throws Exception {
  840.         return parseExtensionElement(elementName, namespace, parser);
  841.     }
  842.  
  843.     /**
  844.      * Parses an extension element.
  845.      *
  846.      * @param elementName the XML element name of the extension element.
  847.      * @param namespace the XML namespace of the packet extension.
  848.      * @param parser the XML parser, positioned at the starting element of the extension.
  849.      * @return an extension element.
  850.      */
  851.     public static ExtensionElement parseExtensionElement(String elementName, String namespace,
  852.                     XmlPullParser parser) throws Exception {
  853.         ParserUtils.assertAtStartTag(parser);
  854.         // See if a provider is registered to handle the extension.
  855.         ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
  856.         if (provider != null) {
  857.                 return provider.parse(parser);
  858.         }

  859.         final int initialDepth = parser.getDepth();
  860.         // No providers registered, so use a default extension.
  861.         DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace);
  862.         outerloop: while (true) {
  863.             int eventType = parser.next();
  864.             switch (eventType) {
  865.             case XmlPullParser.START_TAG:
  866.                 String name = parser.getName();
  867.                 // If an empty element, set the value with the empty string.
  868.                 if (parser.isEmptyElementTag()) {
  869.                     extension.setValue(name,"");
  870.                 }
  871.                 // Otherwise, get the the element text.
  872.                 else {
  873.                     eventType = parser.next();
  874.                     if (eventType == XmlPullParser.TEXT) {
  875.                         String value = parser.getText();
  876.                         extension.setValue(name, value);
  877.                     }
  878.                 }
  879.                 break;
  880.             case XmlPullParser.END_TAG:
  881.                 if (parser.getDepth() == initialDepth) {
  882.                     break outerloop;
  883.                 }
  884.             }
  885.         }
  886.         return extension;
  887.     }

  888.     public static StartTls parseStartTlsFeature(XmlPullParser parser)
  889.                     throws XmlPullParserException, IOException {
  890.         assert (parser.getEventType() == XmlPullParser.START_TAG);
  891.         assert (parser.getNamespace().equals(StartTls.NAMESPACE));
  892.         int initalDepth = parser.getDepth();
  893.         boolean required = false;
  894.         outerloop: while (true) {
  895.             int event = parser.next();
  896.             switch (event) {
  897.             case XmlPullParser.START_TAG:
  898.                 String name = parser.getName();
  899.                 switch (name) {
  900.                 case "required":
  901.                     required = true;
  902.                     break;
  903.                 }
  904.                 break;
  905.             case XmlPullParser.END_TAG:
  906.                 if (parser.getDepth() == initalDepth) {
  907.                     break outerloop;
  908.                 }
  909.             }
  910.         }
  911.         assert(parser.getEventType() == XmlPullParser.END_TAG);
  912.         return new StartTls(required);
  913.     }

  914.     public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
  915.         ParserUtils.assertAtStartTag(parser);
  916.         final int initialDepth = parser.getDepth();
  917.         boolean optional = false;
  918.         if (!parser.isEmptyElementTag()) {
  919.         outerloop: while(true) {
  920.             int event = parser.next();
  921.             switch (event) {
  922.             case XmlPullParser.START_TAG:
  923.                 String name = parser.getName();
  924.                 switch (name) {
  925.                     case Session.Feature.OPTIONAL_ELEMENT:
  926.                         optional = true;
  927.                         break;
  928.                 }
  929.                 break;
  930.             case XmlPullParser.END_TAG:
  931.                 if (parser.getDepth() == initialDepth) {
  932.                     break outerloop;
  933.                 }
  934.             }
  935.         }
  936.         }
  937.         return new Session.Feature(optional);

  938.     }
  939.     private static String getLanguageAttribute(XmlPullParser parser) {
  940.         for (int i = 0; i < parser.getAttributeCount(); i++) {
  941.             String attributeName = parser.getAttributeName(i);
  942.             if ( "xml:lang".equals(attributeName) ||
  943.                     ("lang".equals(attributeName) &&
  944.                             "xml".equals(parser.getAttributePrefix(i)))) {
  945.                 return parser.getAttributeValue(i);
  946.             }
  947.         }
  948.         return null;
  949.     }

  950.     @Deprecated
  951.     public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws Exception {
  952.         addExtensionElement(packet, parser);
  953.     }

  954.     @Deprecated
  955.     public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace)
  956.                     throws Exception {
  957.         addExtensionElement(packet, parser, elementName, namespace);
  958.     }

  959.     @Deprecated
  960.     public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser)
  961.                     throws Exception {
  962.         addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
  963.     }

  964.     @Deprecated
  965.     public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser,
  966.                     String elementName, String namespace) throws Exception {
  967.         addExtensionElement(collection, parser, elementName, namespace);
  968.     }


  969.     public static void addExtensionElement(Stanza packet, XmlPullParser parser)
  970.                     throws Exception {
  971.         ParserUtils.assertAtStartTag(parser);
  972.         addExtensionElement(packet, parser, parser.getName(), parser.getNamespace());
  973.     }

  974.     public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
  975.                     String namespace) throws Exception{
  976.         ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
  977.         packet.addExtension(packetExtension);
  978.     }

  979.     public static void addExtensionElement(Collection<ExtensionElement> collection,
  980.                     XmlPullParser parser) throws Exception {
  981.         addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
  982.     }

  983.     public static void addExtensionElement(Collection<ExtensionElement> collection,
  984.                     XmlPullParser parser, String elementName, String namespace)
  985.                     throws Exception {
  986.         ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
  987.         collection.add(packetExtension);
  988.     }
  989. }