PacketParserUtils.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software, 2019-2023 Florian Schmaus.
  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.InputStream;
  20. import java.io.InputStreamReader;
  21. import java.io.Reader;
  22. import java.io.StringReader;
  23. import java.nio.charset.StandardCharsets;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.HashMap;
  27. import java.util.LinkedList;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.logging.Level;
  31. import java.util.logging.Logger;

  32. import org.jivesoftware.smack.compress.packet.Compress;
  33. import org.jivesoftware.smack.packet.EmptyResultIQ;
  34. import org.jivesoftware.smack.packet.ErrorIQ;
  35. import org.jivesoftware.smack.packet.ExtensionElement;
  36. import org.jivesoftware.smack.packet.IQ;
  37. import org.jivesoftware.smack.packet.IqData;
  38. import org.jivesoftware.smack.packet.Message;
  39. import org.jivesoftware.smack.packet.MessageBuilder;
  40. import org.jivesoftware.smack.packet.Presence;
  41. import org.jivesoftware.smack.packet.PresenceBuilder;
  42. import org.jivesoftware.smack.packet.Session;
  43. import org.jivesoftware.smack.packet.Stanza;
  44. import org.jivesoftware.smack.packet.StanzaBuilder;
  45. import org.jivesoftware.smack.packet.StanzaError;
  46. import org.jivesoftware.smack.packet.StartTls;
  47. import org.jivesoftware.smack.packet.StreamError;
  48. import org.jivesoftware.smack.packet.UnparsedIQ;
  49. import org.jivesoftware.smack.packet.XmlElement;
  50. import org.jivesoftware.smack.packet.XmlEnvironment;
  51. import org.jivesoftware.smack.parsing.SmackParsingException;
  52. import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
  53. import org.jivesoftware.smack.provider.ExtensionElementProvider;
  54. import org.jivesoftware.smack.provider.IqProvider;
  55. import org.jivesoftware.smack.provider.ProviderManager;
  56. import org.jivesoftware.smack.xml.SmackXmlParser;
  57. import org.jivesoftware.smack.xml.XmlPullParser;
  58. import org.jivesoftware.smack.xml.XmlPullParserException;

  59. import org.jxmpp.jid.Jid;
  60. import org.jxmpp.stringprep.XmppStringprepException;

  61. /**
  62.  * Utility class that helps to parse packets. Any parsing packets method that must be shared
  63.  * between many clients must be placed in this utility class.
  64.  *
  65.  * @author Gaston Dombiak
  66.  */
  67. public class PacketParserUtils {
  68.     private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());

  69.     // TODO: Rename argument name from 'stanza' to 'element'.
  70.     public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
  71.         return getParserFor(new StringReader(stanza));
  72.     }

  73.     public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException, IOException {
  74.         InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
  75.         return getParserFor(inputStreamReader);
  76.     }

  77.     public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
  78.         XmlPullParser parser = SmackXmlParser.newXmlParser(reader);
  79.         ParserUtils.forwardToStartElement(parser);
  80.         return parser;
  81.     }

  82.     @SuppressWarnings("unchecked")
  83.     public static <S extends Stanza> S parseStanza(String stanza) throws XmlPullParserException, SmackParsingException, IOException {
  84.         return (S) parseStanza(getParserFor(stanza), XmlEnvironment.EMPTY);
  85.     }

  86.     /**
  87.      * Tries to parse and return either a Message, IQ or Presence stanza.
  88.      *
  89.      * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
  90.      *
  91.      * @param parser TODO javadoc me please
  92.      * @param outerXmlEnvironment the outer XML environment (optional).
  93.      * @return a stanza which is either a Message, IQ or Presence.
  94.      * @throws XmlPullParserException if an error in the XML parser occurred.
  95.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  96.      * @throws IOException if an I/O error occurred.
  97.      */
  98.     public static Stanza parseStanza(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, SmackParsingException, IOException {
  99.         ParserUtils.assertAtStartTag(parser);
  100.         final String name = parser.getName();
  101.         switch (name) {
  102.         case Message.ELEMENT:
  103.             return parseMessage(parser, outerXmlEnvironment);
  104.         case IQ.IQ_ELEMENT:
  105.             return parseIQ(parser, outerXmlEnvironment);
  106.         case Presence.ELEMENT:
  107.             return parsePresence(parser, outerXmlEnvironment);
  108.         default:
  109.             throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
  110.         }
  111.     }

  112.     private interface StanzaBuilderSupplier<SB extends StanzaBuilder<?>> {
  113.         SB get(String stanzaId);
  114.     }

  115.     private static <SB extends StanzaBuilder<?>> SB parseCommonStanzaAttributes(StanzaBuilderSupplier<SB> stanzaBuilderSupplier, XmlPullParser parser, XmlEnvironment xmlEnvironment) throws XmppStringprepException {
  116.         String id = parser.getAttributeValue("id");

  117.         SB stanzaBuilder = stanzaBuilderSupplier.get(id);

  118.         Jid to = ParserUtils.getJidAttribute(parser, "to");
  119.         stanzaBuilder.to(to);

  120.         Jid from = ParserUtils.getJidAttribute(parser, "from");
  121.         stanzaBuilder.from(from);

  122.         String language = ParserUtils.getXmlLang(parser, xmlEnvironment);
  123.         stanzaBuilder.setLanguage(language);

  124.         return stanzaBuilder;
  125.     }

  126.     public static Message parseMessage(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
  127.         return parseMessage(parser, XmlEnvironment.EMPTY);
  128.     }

  129.     /**
  130.      * Parses a message packet.
  131.      *
  132.      * @param parser the XML parser, positioned at the start of a message packet.
  133.      * @param outerXmlEnvironment the outer XML environment (optional).
  134.      * @return a Message packet.
  135.      * @throws XmlPullParserException if an error in the XML parser occurred.
  136.      * @throws IOException if an I/O error occurred.
  137.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  138.      */
  139.     public static Message parseMessage(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  140.         ParserUtils.assertAtStartTag(parser);
  141.         assert parser.getName().equals(Message.ELEMENT);

  142.         XmlEnvironment messageXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
  143.         final int initialDepth = parser.getDepth();

  144.         MessageBuilder message = parseCommonStanzaAttributes(id -> {
  145.             return StanzaBuilder.buildMessage(id);
  146.         }, parser, outerXmlEnvironment);

  147.         String typeString = parser.getAttributeValue("", "type");
  148.         if (typeString != null) {
  149.             message.ofType(Message.Type.fromString(typeString));
  150.         }

  151.         // Parse sub-elements. We include extra logic to make sure the values
  152.         // are only read once. This is because it's possible for the names to appear
  153.         // in arbitrary sub-elements.
  154.         outerloop: while (true) {
  155.             XmlPullParser.Event eventType = parser.next();
  156.             switch (eventType) {
  157.             case START_ELEMENT:
  158.                 String elementName = parser.getName();
  159.                 String namespace = parser.getNamespace();
  160.                 switch (elementName) {
  161.                 case "error":
  162.                     message.setError(parseError(parser, messageXmlEnvironment));
  163.                     break;
  164.                  default:
  165.                      XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, messageXmlEnvironment);
  166.                     message.addExtension(extensionElement);
  167.                     break;
  168.                 }
  169.                 break;
  170.             case END_ELEMENT:
  171.                 if (parser.getDepth() == initialDepth) {
  172.                     break outerloop;
  173.                 }
  174.                 break;
  175.             default: // fall out
  176.             }
  177.         }

  178.         // TODO check for duplicate body elements. This means we need to check for duplicate xml:lang pairs and for
  179.         // situations where we have a body element with an explicit xml lang set and once where the value is inherited
  180.         // and both values are equal.

  181.         return message.build();
  182.     }

  183.     /**
  184.      * Returns the textual content of an element as String. After this method returns the parser
  185.      * position will be END_ELEMENT, following the established pull parser calling convention.
  186.      * <p>
  187.      * The parser must be positioned on a START_ELEMENT of an element which MUST NOT contain Mixed
  188.      * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
  189.      * </p>
  190.      * This method is used for the parts where the XMPP specification requires elements that contain
  191.      * only text or are the empty element.
  192.      *
  193.      * @param parser TODO javadoc me please
  194.      * @return the textual content of the element as String
  195.      * @throws XmlPullParserException if an error in the XML parser occurred.
  196.      * @throws IOException if an I/O error occurred.
  197.      */
  198.     public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
  199.         assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
  200.         String res;
  201.         // Advance to the text of the Element
  202.         XmlPullParser.Event event = parser.next();
  203.         if (event != XmlPullParser.Event.TEXT_CHARACTERS) {
  204.             if (event == XmlPullParser.Event.END_ELEMENT) {
  205.                 // Assume this is the end tag of the start tag at the
  206.                 // beginning of this method. Typical examples where this
  207.                 // happens are body elements containing the empty string,
  208.                 // i.e. <body></body>, which appears to be valid XMPP, or a
  209.                 // least it's not explicitly forbidden by RFC 6121 5.2.3
  210.                 return "";
  211.             } else {
  212.                 throw new XmlPullParserException(
  213.                                 "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
  214.             }
  215.         }
  216.         res = parser.getText();
  217.         event = parser.next();
  218.         if (event != XmlPullParser.Event.END_ELEMENT) {
  219.             throw new XmlPullParserException(
  220.                             "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
  221.         }
  222.         return res;
  223.     }

  224.     /**
  225.      * Returns the current element as string.
  226.      * <p>
  227.      * The parser must be positioned on START_ELEMENT.
  228.      * </p>
  229.      * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
  230.      *
  231.      * @param parser the XML pull parser
  232.      * @return the element as string
  233.      * @throws XmlPullParserException if an error in the XML parser occurred.
  234.      * @throws IOException if an I/O error occurred.
  235.      */
  236.     public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
  237.         return parseElement(parser, false);
  238.     }

  239.     public static CharSequence parseElement(XmlPullParser parser,
  240.                     boolean fullNamespaces) throws XmlPullParserException,
  241.                     IOException {
  242.         assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
  243.         return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
  244.     }

  245.     public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
  246.                     throws XmlPullParserException, IOException {
  247.         return parseContentDepth(parser, depth, false);
  248.     }

  249.     /**
  250.      * Returns the content from the current position of the parser up to the closing tag of the
  251.      * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
  252.      * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
  253.      * parent elements will be added to child elements that don't define a different namespace.
  254.      * <p>
  255.      * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
  256.      * xml-roundtrip. i.e. return a String on getText() on START_ELEMENT and END_ELEMENT. We check for the
  257.      * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
  258.      * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
  259.      * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
  260.      * </p>
  261.      * <p>
  262.      * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
  263.      * </p>
  264.      *
  265.      * @param parser TODO javadoc me please
  266.      * @param depth TODO javadoc me please
  267.      * @param fullNamespaces TODO javadoc me please
  268.      * @return the content of the current depth
  269.      * @throws XmlPullParserException if an error in the XML parser occurred.
  270.      * @throws IOException if an I/O error occurred.
  271.      */
  272.     public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
  273.         if (parser.supportsRoundtrip()) {
  274.             return parseContentDepthWithRoundtrip(parser, depth);
  275.         } else {
  276.             return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
  277.         }
  278.     }

  279.     private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
  280.                     boolean fullNamespaces) throws XmlPullParserException, IOException {
  281.         XmlStringBuilder xml = new XmlStringBuilder();
  282.         XmlPullParser.Event event = parser.getEventType();
  283.         // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
  284.         // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
  285.         // in a nested element. It's an ugly workaround that has the potential to break things.
  286.         String namespaceElement = null;
  287.         boolean startElementJustSeen = false;
  288.         outerloop: while (true) {
  289.             switch (event) {
  290.             case START_ELEMENT:
  291.                 if (startElementJustSeen) {
  292.                     xml.rightAngleBracket();
  293.                 }
  294.                 else {
  295.                     startElementJustSeen = true;
  296.                 }
  297.                 xml.halfOpenElement(parser.getName());
  298.                 if (namespaceElement == null || fullNamespaces) {
  299.                     String namespace = parser.getNamespace();
  300.                     if (StringUtils.isNotEmpty(namespace)) {
  301.                         xml.attribute("xmlns", namespace);
  302.                         namespaceElement = parser.getName();
  303.                     }
  304.                 }
  305.                 for (int i = 0; i < parser.getAttributeCount(); i++) {
  306.                     xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
  307.                 }
  308.                 break;
  309.             case END_ELEMENT:
  310.                 if (startElementJustSeen) {
  311.                     xml.closeEmptyElement();
  312.                     startElementJustSeen = false;
  313.                 }
  314.                 else {
  315.                     xml.closeElement(parser.getName());
  316.                 }
  317.                 if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
  318.                     // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
  319.                     namespaceElement = null;
  320.                 }
  321.                 if (parser.getDepth() <= depth) {
  322.                     // Abort parsing, we are done
  323.                     break outerloop;
  324.                 }
  325.                 break;
  326.             case TEXT_CHARACTERS:
  327.                 if (startElementJustSeen) {
  328.                     startElementJustSeen = false;
  329.                     xml.rightAngleBracket();
  330.                 }
  331.                 xml.escape(parser.getText());
  332.                 break;
  333.             default:
  334.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  335.                 break;
  336.             }
  337.             event = parser.next();
  338.         }
  339.         return xml;
  340.     }

  341.     private static XmlStringBuilder parseContentDepthWithRoundtrip(XmlPullParser parser, int depth)
  342.                     throws XmlPullParserException, IOException {
  343.         XmlStringBuilder sb = new XmlStringBuilder();
  344.         XmlPullParser.Event event = parser.getEventType();
  345.         boolean startElementJustSeen = false;
  346.         outerloop: while (true) {
  347.             switch (event) {
  348.             case START_ELEMENT:
  349.                 startElementJustSeen = true;
  350.                 String openElementTag = parser.getText();
  351.                 sb.append(openElementTag);
  352.                 break;
  353.             case END_ELEMENT:
  354.                 boolean isEmptyElement = false;
  355.                 if (startElementJustSeen) {
  356.                     isEmptyElement = true;
  357.                     startElementJustSeen = false;
  358.                 }
  359.                 if (!isEmptyElement) {
  360.                     String text = parser.getText();
  361.                     sb.append(text);
  362.                 }
  363.                 if (parser.getDepth() <= depth) {
  364.                     break outerloop;
  365.                 }
  366.                 break;
  367.             default:
  368.                 startElementJustSeen = false;
  369.                 CharSequence text = parser.getText();
  370.                 if (event == XmlPullParser.Event.TEXT_CHARACTERS) {
  371.                     text = StringUtils.escapeForXml(text);
  372.                 }
  373.                 sb.append(text);
  374.                 break;
  375.             }
  376.             event = parser.next();
  377.         }
  378.         return sb;
  379.     }

  380.     public static Presence parsePresence(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
  381.         return parsePresence(parser, XmlEnvironment.EMPTY);
  382.     }

  383.     /**
  384.      * Parses a presence packet.
  385.      *
  386.      * @param parser the XML parser, positioned at the start of a presence packet.
  387.      * @param outerXmlEnvironment the outer XML environment (optional).
  388.      * @return a Presence packet.
  389.      * @throws IOException if an I/O error occurred.
  390.      * @throws XmlPullParserException if an error in the XML parser occurred.
  391.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  392.      */
  393.     public static Presence parsePresence(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  394.         ParserUtils.assertAtStartTag(parser);
  395.         final int initialDepth = parser.getDepth();
  396.         XmlEnvironment presenceXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);

  397.         PresenceBuilder presence = parseCommonStanzaAttributes(
  398.                         stanzaId -> StanzaBuilder.buildPresence(stanzaId), parser, outerXmlEnvironment);

  399.         Presence.Type type = Presence.Type.available;
  400.         String typeString = parser.getAttributeValue("", "type");
  401.         if (typeString != null && !typeString.equals("")) {
  402.             type = Presence.Type.fromString(typeString);
  403.         }

  404.         presence.ofType(type);

  405.         // Parse sub-elements
  406.         outerloop: while (true) {
  407.             XmlPullParser.Event eventType = parser.next();
  408.             switch (eventType) {
  409.             case START_ELEMENT:
  410.                 String elementName = parser.getName();
  411.                 String namespace = parser.getNamespace();
  412.                 switch (elementName) {
  413.                 case "status":
  414.                     presence.setStatus(parser.nextText());
  415.                     break;
  416.                 case "priority":
  417.                     Byte priority = ParserUtils.getByteAttributeFromNextText(parser);
  418.                     presence.setPriority(priority);
  419.                     break;
  420.                 case "show":
  421.                     String modeText = parser.nextText();
  422.                     if (StringUtils.isNotEmpty(modeText)) {
  423.                         presence.setMode(Presence.Mode.fromString(modeText));
  424.                     } else {
  425.                         // Some implementations send presence stanzas with a
  426.                         // '<show />' element, which is a invalid XMPP presence
  427.                         // stanza according to RFC 6121 4.7.2.1
  428.                         LOGGER.warning("Empty or null mode text in presence show element form "
  429.                                         + presence
  430.                                         + "' which is invalid according to RFC6121 4.7.2.1");
  431.                     }
  432.                     break;
  433.                 case "error":
  434.                     presence.setError(parseError(parser, presenceXmlEnvironment));
  435.                     break;
  436.                 default:
  437.                 // Otherwise, it must be a packet extension.
  438.                     // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
  439.                     // failing completely here. See SMACK-390 for more information.
  440.                     try {
  441.                         XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, presenceXmlEnvironment);
  442.                         presence.addExtension(extensionElement);
  443.                     } catch (Exception e) {
  444.                         LOGGER.log(Level.WARNING, "Failed to parse extension element in Presence stanza: " + presence, e);
  445.                     }
  446.                     break;
  447.                 }
  448.                 break;
  449.             case END_ELEMENT:
  450.                 if (parser.getDepth() == initialDepth) {
  451.                     break outerloop;
  452.                 }
  453.                 break;
  454.             default:
  455.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  456.                 break;
  457.             }
  458.         }

  459.         return presence.build();
  460.     }

  461.     public static IQ parseIQ(XmlPullParser parser) throws Exception {
  462.         return parseIQ(parser, null);
  463.     }

  464.     public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException {
  465.         final String id = parser.getAttributeValue("", "id");
  466.         IqData iqData = StanzaBuilder.buildIqData(id);

  467.         final Jid to = ParserUtils.getJidAttribute(parser, "to");
  468.         iqData.to(to);

  469.         final Jid from = ParserUtils.getJidAttribute(parser, "from");
  470.         iqData.from(from);

  471.         String typeString = parser.getAttributeValue("", "type");
  472.         final IQ.Type type = IQ.Type.fromString(typeString);
  473.         iqData.ofType(type);

  474.         return iqData;
  475.     }

  476.     /**
  477.      * Parses an IQ packet.
  478.      *
  479.      * @param parser the XML parser, positioned at the start of an IQ packet.
  480.      * @param outerXmlEnvironment the outer XML environment (optional).
  481.      * @return an IQ object.
  482.      * @throws XmlPullParserException if an error in the XML parser occurred.
  483.      * @throws XmppStringprepException if the provided string is invalid.
  484.      * @throws IOException if an I/O error occurred.
  485.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  486.      */
  487.     public static IQ parseIQ(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, XmppStringprepException, IOException, SmackParsingException {
  488.         ParserUtils.assertAtStartTag(parser);
  489.         final int initialDepth = parser.getDepth();
  490.         XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
  491.         IQ iqPacket = null;
  492.         StanzaError error = null;
  493.         IqData iqData = parseIqData(parser);

  494.         outerloop: while (true) {
  495.             XmlPullParser.Event eventType = parser.next();

  496.             switch (eventType) {
  497.             case START_ELEMENT:
  498.                 String elementName = parser.getName();
  499.                 String namespace = parser.getNamespace();
  500.                 switch (elementName) {
  501.                 case "error":
  502.                     error = PacketParserUtils.parseError(parser, iqXmlEnvironment);
  503.                     break;
  504.                 // Otherwise, see if there is a registered provider for
  505.                 // this element name and namespace.
  506.                 default:
  507.                     IqProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
  508.                     if (provider != null) {
  509.                             iqPacket = provider.parse(parser, iqData, outerXmlEnvironment);
  510.                     }
  511.                     // Note that if we reach this code, it is guaranteed that the result IQ contained a child element
  512.                     // (RFC 6120 § 8.2.3 6) because otherwise we would have reached the END_ELEMENT first.
  513.                     else {
  514.                         // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
  515.                         // so that the content of the IQ can be examined later on
  516.                         iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
  517.                     }
  518.                     break;
  519.                 }
  520.                 break;
  521.             case END_ELEMENT:
  522.                 if (parser.getDepth() == initialDepth) {
  523.                     break outerloop;
  524.                 }
  525.                 break;
  526.             default:
  527.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  528.                 break;
  529.             }
  530.         }
  531.         // Decide what to do when an IQ packet was not understood
  532.         if (iqPacket == null) {
  533.             switch (iqData.getType()) {
  534.             case error:
  535.                 // If an IQ packet wasn't created above, create an empty error IQ packet.
  536.                 iqPacket = ErrorIQ.builder(error, iqData).build();
  537.                 // The following return is simply to avoid setting iqData again below.
  538.                 return iqPacket;
  539.             case result:
  540.                 iqPacket = new EmptyResultIQ();
  541.                 break;
  542.             default:
  543.                 break;
  544.             }
  545.         }

  546.         // Set basic values on the iq packet.
  547.         iqPacket.setStanzaId(iqData.getStanzaId());
  548.         iqPacket.setTo(iqData.getTo());
  549.         iqPacket.setFrom(iqData.getFrom());
  550.         iqPacket.setType(iqData.getType());
  551.         iqPacket.setError(error);

  552.         return iqPacket;
  553.     }

  554.     /**
  555.      * Parse the available SASL mechanisms reported from the server.
  556.      *
  557.      * @param parser the XML parser, positioned at the start of the mechanisms stanza.
  558.      * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
  559.      * @throws IOException if an I/O error occurred.
  560.      * @throws XmlPullParserException if an error in the XML parser occurred.
  561.      */
  562.     public static Collection<String> parseMechanisms(XmlPullParser parser)
  563.                     throws XmlPullParserException, IOException {
  564.         List<String> mechanisms = new ArrayList<String>();
  565.         boolean done = false;
  566.         while (!done) {
  567.             XmlPullParser.Event eventType = parser.next();

  568.             if (eventType == XmlPullParser.Event.START_ELEMENT) {
  569.                 String elementName = parser.getName();
  570.                 if (elementName.equals("mechanism")) {
  571.                     mechanisms.add(parser.nextText());
  572.                 }
  573.             }
  574.             else if (eventType == XmlPullParser.Event.END_ELEMENT) {
  575.                 if (parser.getName().equals("mechanisms")) {
  576.                     done = true;
  577.                 }
  578.             }
  579.         }
  580.         return mechanisms;
  581.     }

  582.     /**
  583.      * Parse the Compression Feature reported from the server.
  584.      *
  585.      * @param parser the XML parser, positioned at the start of the compression stanza.
  586.      * @return The CompressionFeature stream element
  587.      * @throws IOException if an I/O error occurred.
  588.      * @throws XmlPullParserException if an exception occurs while parsing the stanza.
  589.      */
  590.     public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
  591.                     throws IOException, XmlPullParserException {
  592.         assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
  593.         String name;
  594.         final int initialDepth = parser.getDepth();
  595.         List<String> methods = new LinkedList<>();
  596.         outerloop: while (true) {
  597.             XmlPullParser.Event eventType = parser.next();
  598.             switch (eventType) {
  599.             case START_ELEMENT:
  600.                 name = parser.getName();
  601.                 switch (name) {
  602.                 case "method":
  603.                     methods.add(parser.nextText());
  604.                     break;
  605.                 }
  606.                 break;
  607.             case END_ELEMENT:
  608.                 name = parser.getName();
  609.                 switch (name) {
  610.                 case Compress.Feature.ELEMENT:
  611.                     if (parser.getDepth() == initialDepth) {
  612.                         break outerloop;
  613.                     }
  614.                 }
  615.                 break;
  616.             default:
  617.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  618.                 break;
  619.             }
  620.         }
  621.         assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT;
  622.         assert parser.getDepth() == initialDepth;
  623.         return new Compress.Feature(methods);
  624.     }

  625.     public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
  626.                     throws XmlPullParserException, IOException {
  627.         if (descriptiveTexts == null) {
  628.             descriptiveTexts = new HashMap<>();
  629.         }
  630.         String xmllang = ParserUtils.getXmlLang(parser);
  631.         if (xmllang == null) {
  632.             // XMPPError assumes the default locale, 'en', or the empty string.
  633.             // Establish the invariant that there is never null as a key.
  634.             xmllang = "";
  635.         }

  636.         String text = parser.nextText();
  637.         String previousValue = descriptiveTexts.put(xmllang, text);
  638.         assert previousValue == null;
  639.         return descriptiveTexts;
  640.     }

  641.     public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
  642.         return parseStreamError(parser, null);
  643.     }

  644.     /**
  645.      * Parses stream error packets.
  646.      *
  647.      * @param parser the XML parser.
  648.      * @param outerXmlEnvironment the outer XML environment (optional).
  649.      * @return an stream error packet.
  650.      * @throws IOException if an I/O error occurred.
  651.      * @throws XmlPullParserException if an error in the XML parser occurred.
  652.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  653.      */
  654.     public static StreamError parseStreamError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  655.         final int initialDepth = parser.getDepth();
  656.         List<XmlElement> extensions = new ArrayList<>();
  657.         Map<String, String> descriptiveTexts = null;
  658.         StreamError.Condition condition = null;
  659.         String conditionText = null;
  660.         XmlEnvironment streamErrorXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
  661.         outerloop: while (true) {
  662.             XmlPullParser.Event eventType = parser.next();
  663.             switch (eventType) {
  664.             case START_ELEMENT:
  665.                 String name = parser.getName();
  666.                 String namespace = parser.getNamespace();
  667.                 switch (namespace) {
  668.                 case StreamError.NAMESPACE:
  669.                     switch (name) {
  670.                     case "text":
  671.                         descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
  672.                         break;
  673.                     default:
  674.                         // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
  675.                         // then it has to be the stream error code
  676.                         condition = StreamError.Condition.fromString(name);
  677.                         conditionText = parser.nextText();
  678.                         if (conditionText.isEmpty()) {
  679.                             conditionText = null;
  680.                         }
  681.                         break;
  682.                     }
  683.                     break;
  684.                 default:
  685.                     PacketParserUtils.addExtensionElement(extensions, parser, name, namespace, streamErrorXmlEnvironment);
  686.                     break;
  687.                 }
  688.                 break;
  689.             case END_ELEMENT:
  690.                 if (parser.getDepth() == initialDepth) {
  691.                     break outerloop;
  692.                 }
  693.                 break;
  694.             default:
  695.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  696.                 break;
  697.             }
  698.         }
  699.         return new StreamError(condition, conditionText, descriptiveTexts, extensions);
  700.     }

  701.     public static StanzaError parseError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
  702.         return parseError(parser, null);
  703.     }

  704.     /**
  705.      * Parses error sub-packets.
  706.      *
  707.      * @param parser the XML parser.
  708.      * @param outerXmlEnvironment the outer XML environment (optional).
  709.      * @return an error sub-packet.
  710.      * @throws IOException if an I/O error occurred.
  711.      * @throws XmlPullParserException if an error in the XML parser occurred.
  712.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  713.      */
  714.     public static StanzaError parseError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  715.         final int initialDepth = parser.getDepth();
  716.         Map<String, String> descriptiveTexts = null;
  717.         XmlEnvironment stanzaErrorXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
  718.         List<XmlElement> extensions = new ArrayList<>();
  719.         StanzaError.Builder builder = StanzaError.getBuilder();

  720.         // Parse the error header
  721.         builder.setType(StanzaError.Type.fromString(parser.getAttributeValue("", "type")));
  722.         builder.setErrorGenerator(parser.getAttributeValue("", "by"));

  723.         outerloop: while (true) {
  724.             XmlPullParser.Event eventType = parser.next();
  725.             switch (eventType) {
  726.             case START_ELEMENT:
  727.                 String name = parser.getName();
  728.                 String namespace = parser.getNamespace();
  729.                 switch (namespace) {
  730.                 case StanzaError.ERROR_CONDITION_AND_TEXT_NAMESPACE:
  731.                     switch (name) {
  732.                     case Stanza.TEXT:
  733.                         descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
  734.                         break;
  735.                     default:
  736.                         builder.setCondition(StanzaError.Condition.fromString(name));
  737.                         String conditionText = parser.nextText();
  738.                         if (!conditionText.isEmpty()) {
  739.                             builder.setConditionText(conditionText);
  740.                         }
  741.                         break;
  742.                     }
  743.                     break;
  744.                 default:
  745.                     PacketParserUtils.addExtensionElement(extensions, parser, name, namespace, stanzaErrorXmlEnvironment);
  746.                 }
  747.                 break;
  748.             case END_ELEMENT:
  749.                 if (parser.getDepth() == initialDepth) {
  750.                     break outerloop;
  751.                 }
  752.                 break;
  753.             default:
  754.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  755.                 break;
  756.             }
  757.         }
  758.         builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts);

  759.         return builder.build();
  760.     }

  761.     /**
  762.      * Parses an extension element.
  763.      *
  764.      * @param elementName the XML element name of the extension element.
  765.      * @param namespace the XML namespace of the stanza extension.
  766.      * @param parser the XML parser, positioned at the starting element of the extension.
  767.      * @param outerXmlEnvironment the outer XML environment (optional).
  768.      *
  769.      * @return an extension element.
  770.      * @throws XmlPullParserException if an error in the XML parser occurred.
  771.      * @throws IOException if an I/O error occurred.
  772.      * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
  773.      */
  774.     public static XmlElement parseExtensionElement(String elementName, String namespace,
  775.                     XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  776.         ParserUtils.assertAtStartTag(parser);
  777.         // See if a provider is registered to handle the extension.
  778.         ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
  779.         if (provider != null) {
  780.                 return provider.parse(parser, outerXmlEnvironment);
  781.         }

  782.         // No providers registered, so use a default extension.
  783.         return StandardExtensionElementProvider.INSTANCE.parse(parser, outerXmlEnvironment);
  784.     }

  785.     public static StartTls parseStartTlsFeature(XmlPullParser parser)
  786.                     throws XmlPullParserException, IOException {
  787.         ParserUtils.assertAtStartTag(parser);
  788.         assert parser.getNamespace().equals(StartTls.NAMESPACE);
  789.         int initialDepth = parser.getDepth();
  790.         boolean required = false;
  791.         outerloop: while (true) {
  792.             XmlPullParser.Event event = parser.next();
  793.             switch (event) {
  794.             case START_ELEMENT:
  795.                 String name = parser.getName();
  796.                 switch (name) {
  797.                 case "required":
  798.                     required = true;
  799.                     break;
  800.                 }
  801.                 break;
  802.             case END_ELEMENT:
  803.                 if (parser.getDepth() == initialDepth) {
  804.                     break outerloop;
  805.                 }
  806.                 break;
  807.             default:
  808.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  809.                 break;
  810.             }
  811.         }
  812.         ParserUtils.assertAtEndTag(parser);
  813.         return new StartTls(required);
  814.     }

  815.     public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
  816.         ParserUtils.assertAtStartTag(parser);
  817.         final int initialDepth = parser.getDepth();
  818.         boolean optional = false;

  819.         outerloop: while (true) {
  820.             XmlPullParser.Event event = parser.next();
  821.             switch (event) {
  822.             case START_ELEMENT:
  823.                 String name = parser.getName();
  824.                 switch (name) {
  825.                     case Session.Feature.OPTIONAL_ELEMENT:
  826.                         optional = true;
  827.                         break;
  828.                 }
  829.                 break;
  830.             case END_ELEMENT:
  831.                 if (parser.getDepth() == initialDepth) {
  832.                     break outerloop;
  833.                 }
  834.                 break;
  835.             default:
  836.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  837.                 break;
  838.             }
  839.         }

  840.         return new Session.Feature(optional);
  841.     }

  842.     public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
  843.                     throws XmlPullParserException, IOException, SmackParsingException {
  844.         ParserUtils.assertAtStartTag(parser);
  845.         addExtensionElement(stanzaBuilder, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
  846.     }

  847.     public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, String elementName,
  848.             String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  849.         XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
  850.         stanzaBuilder.addExtension(extensionElement);
  851.     }

  852.     public static void addExtensionElement(Stanza packet, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
  853.                     throws XmlPullParserException, IOException, SmackParsingException {
  854.         ParserUtils.assertAtStartTag(parser);
  855.         addExtensionElement(packet, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
  856.     }

  857.     public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
  858.             String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  859.         XmlElement packetExtension = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
  860.         packet.addExtension(packetExtension);
  861.     }

  862.     public static void addExtensionElement(Collection<XmlElement> collection, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
  863.                     throws XmlPullParserException, IOException, SmackParsingException {
  864.         addExtensionElement(collection, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
  865.     }

  866.     public static void addExtensionElement(Collection<XmlElement> collection, XmlPullParser parser,
  867.                     String elementName, String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  868.         XmlElement packetExtension = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
  869.         collection.add(packetExtension);
  870.     }
  871. }