001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util;
018
019import java.io.IOException;
020import java.io.Reader;
021import java.io.StringReader;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030
031import org.jivesoftware.smack.SmackConfiguration;
032import org.jivesoftware.smack.compress.packet.Compress;
033import org.jivesoftware.smack.packet.EmptyResultIQ;
034import org.jivesoftware.smack.packet.ErrorIQ;
035import org.jivesoftware.smack.packet.ExtensionElement;
036import org.jivesoftware.smack.packet.IQ;
037import org.jivesoftware.smack.packet.Message;
038import org.jivesoftware.smack.packet.Presence;
039import org.jivesoftware.smack.packet.Session;
040import org.jivesoftware.smack.packet.Stanza;
041import org.jivesoftware.smack.packet.StanzaError;
042import org.jivesoftware.smack.packet.StartTls;
043import org.jivesoftware.smack.packet.StreamError;
044import org.jivesoftware.smack.packet.UnparsedIQ;
045import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
046import org.jivesoftware.smack.provider.ExtensionElementProvider;
047import org.jivesoftware.smack.provider.IQProvider;
048import org.jivesoftware.smack.provider.ProviderManager;
049import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
050
051import org.jxmpp.jid.Jid;
052import org.xmlpull.v1.XmlPullParser;
053import org.xmlpull.v1.XmlPullParserException;
054import org.xmlpull.v1.XmlPullParserFactory;
055
056/**
057 * Utility class that helps to parse packets. Any parsing packets method that must be shared
058 * between many clients must be placed in this utility class.
059 *
060 * @author Gaston Dombiak
061 */
062public class PacketParserUtils {
063    private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());
064
065    public static final String FEATURE_XML_ROUNDTRIP = "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
066
067    private static final XmlPullParserFactory XML_PULL_PARSER_FACTORY;
068
069    /**
070     * True if the XmlPullParser supports the XML_ROUNDTRIP feature.
071     */
072    public static final boolean XML_PULL_PARSER_SUPPORTS_ROUNDTRIP;
073
074    static {
075        // Ensure that Smack is initialized.
076        SmackConfiguration.getVersion();
077
078        XmlPullParser xmlPullParser;
079        boolean roundtrip = false;
080        try {
081            XML_PULL_PARSER_FACTORY = XmlPullParserFactory.newInstance();
082            xmlPullParser = XML_PULL_PARSER_FACTORY.newPullParser();
083            try {
084                xmlPullParser.setFeature(FEATURE_XML_ROUNDTRIP, true);
085                // We could successfully set the feature
086                roundtrip = true;
087            } catch (XmlPullParserException e) {
088                // Doesn't matter if FEATURE_XML_ROUNDTRIP isn't available
089                LOGGER.log(Level.FINEST, "XmlPullParser does not support XML_ROUNDTRIP", e);
090            }
091        }
092        catch (XmlPullParserException e) {
093            // Something really bad happened
094            throw new AssertionError(e);
095        }
096        XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip;
097    }
098
099    public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
100        return getParserFor(new StringReader(stanza));
101    }
102
103    public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
104        XmlPullParser parser = newXmppParser(reader);
105        // Wind the parser forward to the first start tag
106        int event = parser.getEventType();
107        while (event != XmlPullParser.START_TAG) {
108            if (event == XmlPullParser.END_DOCUMENT) {
109                throw new IllegalArgumentException("Document contains no start tag");
110            }
111            event = parser.next();
112        }
113        return parser;
114    }
115
116    public static XmlPullParser getParserFor(String stanza, String startTag)
117                    throws XmlPullParserException, IOException {
118        XmlPullParser parser = getParserFor(stanza);
119
120        while (true) {
121            int event = parser.getEventType();
122            String name = parser.getName();
123            if (event == XmlPullParser.START_TAG && name.equals(startTag)) {
124                break;
125            }
126            else if (event == XmlPullParser.END_DOCUMENT) {
127                throw new IllegalArgumentException("Could not find start tag '" + startTag
128                                + "' in stanza: " + stanza);
129            }
130            parser.next();
131        }
132
133        return parser;
134    }
135
136    @SuppressWarnings("unchecked")
137    public static <S extends Stanza> S parseStanza(String stanza) throws Exception {
138        return (S) parseStanza(getParserFor(stanza));
139    }
140
141    /**
142     * Tries to parse and return either a Message, IQ or Presence stanza.
143     *
144     * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
145     *
146     * @param parser
147     * @return a stanza which is either a Message, IQ or Presence.
148     * @throws Exception
149     */
150    public static Stanza parseStanza(XmlPullParser parser) throws Exception {
151        ParserUtils.assertAtStartTag(parser);
152        final String name = parser.getName();
153        switch (name) {
154        case Message.ELEMENT:
155            return parseMessage(parser);
156        case IQ.IQ_ELEMENT:
157            return parseIQ(parser);
158        case Presence.ELEMENT:
159            return parsePresence(parser);
160        default:
161            throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
162        }
163    }
164
165    /**
166     * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
167     * FEATURE_PROCESS_NAMESPACES is enabled.
168     * <p>
169     * Note that not all XmlPullParser implementations will return a String on
170     * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
171     * behavior when using the parser.
172     * </p>
173     *
174     * @return A suitable XmlPullParser for XMPP parsing
175     * @throws XmlPullParserException
176     */
177    public static XmlPullParser newXmppParser() throws XmlPullParserException {
178        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
179        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
180        if (XML_PULL_PARSER_SUPPORTS_ROUNDTRIP) {
181            try {
182                parser.setFeature(FEATURE_XML_ROUNDTRIP, true);
183            }
184            catch (XmlPullParserException e) {
185                LOGGER.log(Level.SEVERE,
186                                "XmlPullParser does not support XML_ROUNDTRIP, although it was first determined to be supported",
187                                e);
188            }
189        }
190        return parser;
191    }
192
193    /**
194     * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
195     * FEATURE_PROCESS_NAMESPACES is enabled.
196     * <p>
197     * Note that not all XmlPullParser implementations will return a String on
198     * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
199     * behavior when using the parser.
200     * </p>
201     *
202     * @param reader
203     * @return A suitable XmlPullParser for XMPP parsing
204     * @throws XmlPullParserException
205     */
206    public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException {
207        XmlPullParser parser = newXmppParser();
208        parser.setInput(reader);
209        return parser;
210    }
211
212    /**
213     * Parses a message packet.
214     *
215     * @param parser the XML parser, positioned at the start of a message packet.
216     * @return a Message packet.
217     * @throws Exception
218     */
219    public static Message parseMessage(XmlPullParser parser)
220                    throws Exception {
221        ParserUtils.assertAtStartTag(parser);
222        assert (parser.getName().equals(Message.ELEMENT));
223
224        final int initialDepth = parser.getDepth();
225        Message message = new Message();
226        message.setStanzaId(parser.getAttributeValue("", "id"));
227        message.setTo(ParserUtils.getJidAttribute(parser, "to"));
228        message.setFrom(ParserUtils.getJidAttribute(parser, "from"));
229        String typeString = parser.getAttributeValue("", "type");
230        if (typeString != null) {
231            message.setType(Message.Type.fromString(typeString));
232        }
233        String language = ParserUtils.getXmlLang(parser);
234        message.setLanguage(language);
235
236        // Parse sub-elements. We include extra logic to make sure the values
237        // are only read once. This is because it's possible for the names to appear
238        // in arbitrary sub-elements.
239        String thread = null;
240        outerloop: while (true) {
241            int eventType = parser.next();
242            switch (eventType) {
243            case XmlPullParser.START_TAG:
244                String elementName = parser.getName();
245                String namespace = parser.getNamespace();
246                switch (elementName) {
247                case "subject":
248                    String xmlLangSubject = ParserUtils.getXmlLang(parser);
249                    String subject = parseElementText(parser);
250
251                    if (message.getSubject(xmlLangSubject) == null) {
252                        message.addSubject(xmlLangSubject, subject);
253                    }
254                    break;
255                case "thread":
256                    if (thread == null) {
257                        thread = parser.nextText();
258                    }
259                    break;
260                case "error":
261                    message.setError(parseError(parser));
262                    break;
263                 default:
264                    PacketParserUtils.addExtensionElement(message, parser, elementName, namespace);
265                    break;
266                }
267                break;
268            case XmlPullParser.END_TAG:
269                if (parser.getDepth() == initialDepth) {
270                    break outerloop;
271                }
272                break;
273            }
274        }
275
276        message.setThread(thread);
277
278        // TODO check for duplicate body elements. This means we need to check for duplicate xml:lang pairs and for
279        // situations where we have a body element with an explicit xml lang set and once where the value is inherited
280        // and both values are equal.
281
282        return message;
283    }
284
285    /**
286     * Returns the textual content of an element as String. After this method returns the parser
287     * position will be END_TAG, following the established pull parser calling convention.
288     * <p>
289     * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
290     * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
291     * </p>
292     * This method is used for the parts where the XMPP specification requires elements that contain
293     * only text or are the empty element.
294     *
295     * @param parser
296     * @return the textual content of the element as String
297     * @throws XmlPullParserException
298     * @throws IOException
299     */
300    public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
301        assert (parser.getEventType() == XmlPullParser.START_TAG);
302        String res;
303        if (parser.isEmptyElementTag()) {
304            res = "";
305        }
306        else {
307            // Advance to the text of the Element
308            int event = parser.next();
309            if (event != XmlPullParser.TEXT) {
310                if (event == XmlPullParser.END_TAG) {
311                    // Assume this is the end tag of the start tag at the
312                    // beginning of this method. Typical examples where this
313                    // happens are body elements containing the empty string,
314                    // ie. <body></body>, which appears to be valid XMPP, or a
315                    // least it's not explicitly forbidden by RFC 6121 5.2.3
316                    return "";
317                } else {
318                    throw new XmlPullParserException(
319                                    "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
320                }
321            }
322            res = parser.getText();
323            event = parser.next();
324            if (event != XmlPullParser.END_TAG) {
325                throw new XmlPullParserException(
326                                "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
327            }
328        }
329        return res;
330    }
331
332    /**
333     * Returns the current element as string.
334     * <p>
335     * The parser must be positioned on START_TAG.
336     * </p>
337     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
338     *
339     * @param parser the XML pull parser
340     * @return the element as string
341     * @throws XmlPullParserException
342     * @throws IOException
343     */
344    public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
345        return parseElement(parser, false);
346    }
347
348    public static CharSequence parseElement(XmlPullParser parser,
349                    boolean fullNamespaces) throws XmlPullParserException,
350                    IOException {
351        assert (parser.getEventType() == XmlPullParser.START_TAG);
352        return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
353    }
354
355    /**
356     * Returns the content of a element.
357     * <p>
358     * The parser must be positioned on the START_TAG of the element which content is going to get
359     * returned. If the current element is the empty element, then the empty string is returned. If
360     * it is a element which contains just text, then just the text is returned. If it contains
361     * nested elements (and text), then everything from the current opening tag to the corresponding
362     * closing tag of the same depth is returned as String.
363     * </p>
364     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
365     *
366     * @param parser the XML pull parser
367     * @return the content of a tag
368     * @throws XmlPullParserException if parser encounters invalid XML
369     * @throws IOException if an IO error occurs
370     */
371    public static CharSequence parseContent(XmlPullParser parser)
372                    throws XmlPullParserException, IOException {
373        assert (parser.getEventType() == XmlPullParser.START_TAG);
374        if (parser.isEmptyElementTag()) {
375            return "";
376        }
377        // Advance the parser, since we want to parse the content of the current element
378        parser.next();
379        return parseContentDepth(parser, parser.getDepth(), false);
380    }
381
382    public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
383                    throws XmlPullParserException, IOException {
384        return parseContentDepth(parser, depth, false);
385    }
386
387    /**
388     * Returns the content from the current position of the parser up to the closing tag of the
389     * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
390     * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
391     * parent elements will be added to child elements that don't define a different namespace.
392     * <p>
393     * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
394     * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the
395     * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
396     * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
397     * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
398     * </p>
399     * <p>
400     * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
401     * </p>
402     *
403     * @param parser
404     * @param depth
405     * @param fullNamespaces
406     * @return the content of the current depth
407     * @throws XmlPullParserException
408     * @throws IOException
409     */
410    public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
411        if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) {
412            return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces);
413        } else {
414            return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
415        }
416    }
417
418    private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
419                    boolean fullNamespaces) throws XmlPullParserException, IOException {
420        XmlStringBuilder xml = new XmlStringBuilder();
421        int event = parser.getEventType();
422        boolean isEmptyElement = false;
423        // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
424        // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
425        // in a nested element. It's an ugly workaround that has the potential to break things.
426        String namespaceElement = null;
427        outerloop: while (true) {
428            switch (event) {
429            case XmlPullParser.START_TAG:
430                xml.halfOpenElement(parser.getName());
431                if (namespaceElement == null || fullNamespaces) {
432                    String namespace = parser.getNamespace();
433                    if (StringUtils.isNotEmpty(namespace)) {
434                        xml.attribute("xmlns", namespace);
435                        namespaceElement = parser.getName();
436                    }
437                }
438                for (int i = 0; i < parser.getAttributeCount(); i++) {
439                    xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
440                }
441                if (parser.isEmptyElementTag()) {
442                    xml.closeEmptyElement();
443                    isEmptyElement = true;
444                }
445                else {
446                    xml.rightAngleBracket();
447                }
448                break;
449            case XmlPullParser.END_TAG:
450                if (isEmptyElement) {
451                    // Do nothing as the element was already closed, just reset the flag
452                    isEmptyElement = false;
453                }
454                else {
455                    xml.closeElement(parser.getName());
456                }
457                if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
458                    // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
459                    namespaceElement = null;
460                }
461                if (parser.getDepth() <= depth) {
462                    // Abort parsing, we are done
463                    break outerloop;
464                }
465                break;
466            case XmlPullParser.TEXT:
467                xml.escape(parser.getText());
468                break;
469            }
470            event = parser.next();
471        }
472        return xml;
473    }
474
475    private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces)
476                    throws XmlPullParserException, IOException {
477        StringBuilder sb = new StringBuilder();
478        int event = parser.getEventType();
479        outerloop: while (true) {
480            // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
481            // twice, so in order to prevent duplication we only add their text when we are on their end tag.
482            if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
483                CharSequence text = parser.getText();
484                if (event == XmlPullParser.TEXT) {
485                    text = StringUtils.escapeForXmlText(text);
486                }
487                sb.append(text);
488            }
489            if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
490                break outerloop;
491            }
492            event = parser.next();
493        }
494        return sb;
495    }
496
497    /**
498     * Parses a presence packet.
499     *
500     * @param parser the XML parser, positioned at the start of a presence packet.
501     * @return a Presence packet.
502     * @throws Exception
503     */
504    public static Presence parsePresence(XmlPullParser parser)
505                    throws Exception {
506        ParserUtils.assertAtStartTag(parser);
507        final int initialDepth = parser.getDepth();
508
509        Presence.Type type = Presence.Type.available;
510        String typeString = parser.getAttributeValue("", "type");
511        if (typeString != null && !typeString.equals("")) {
512            type = Presence.Type.fromString(typeString);
513        }
514        Presence presence = new Presence(type);
515        presence.setTo(ParserUtils.getJidAttribute(parser, "to"));
516        presence.setFrom(ParserUtils.getJidAttribute(parser, "from"));
517        presence.setStanzaId(parser.getAttributeValue("", "id"));
518
519        String language = ParserUtils.getXmlLang(parser);
520        if (language != null && !"".equals(language.trim())) {
521        // CHECKSTYLE:OFF
522                presence.setLanguage(language);
523        // CHECKSTYLE:ON
524        }
525
526        // Parse sub-elements
527        outerloop: while (true) {
528            int eventType = parser.next();
529            switch (eventType) {
530            case XmlPullParser.START_TAG:
531                String elementName = parser.getName();
532                String namespace = parser.getNamespace();
533                switch (elementName) {
534                case "status":
535                    presence.setStatus(parser.nextText());
536                    break;
537                case "priority":
538                    int priority = Integer.parseInt(parser.nextText());
539                    presence.setPriority(priority);
540                    break;
541                case "show":
542                    String modeText = parser.nextText();
543                    if (StringUtils.isNotEmpty(modeText)) {
544                        presence.setMode(Presence.Mode.fromString(modeText));
545                    } else {
546                        // Some implementations send presence stanzas with a
547                        // '<show />' element, which is a invalid XMPP presence
548                        // stanza according to RFC 6121 4.7.2.1
549                        LOGGER.warning("Empty or null mode text in presence show element form "
550                                        + presence.getFrom()
551                                        + " with id '"
552                                        + presence.getStanzaId()
553                                        + "' which is invalid according to RFC6121 4.7.2.1");
554                    }
555                    break;
556                case "error":
557                    presence.setError(parseError(parser));
558                    break;
559                default:
560                // Otherwise, it must be a packet extension.
561                    // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
562                    // failing completely here. See SMACK-390 for more information.
563                    try {
564                        PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace);
565                    } catch (Exception e) {
566                        LOGGER.warning("Failed to parse extension element in Presence stanza: \"" + e + "\" from: '"
567                                        + presence.getFrom() + " id: '" + presence.getStanzaId() + "'");
568                    }
569                    break;
570                }
571                break;
572            case XmlPullParser.END_TAG:
573                if (parser.getDepth() == initialDepth) {
574                    break outerloop;
575                }
576                break;
577            }
578        }
579        return presence;
580    }
581
582    /**
583     * Parses an IQ packet.
584     *
585     * @param parser the XML parser, positioned at the start of an IQ packet.
586     * @return an IQ object.
587     * @throws Exception
588     */
589    public static IQ parseIQ(XmlPullParser parser) throws Exception {
590        ParserUtils.assertAtStartTag(parser);
591        final int initialDepth = parser.getDepth();
592        IQ iqPacket = null;
593        StanzaError.Builder error = null;
594
595        final String id = parser.getAttributeValue("", "id");
596        final Jid to = ParserUtils.getJidAttribute(parser, "to");
597        final Jid from = ParserUtils.getJidAttribute(parser, "from");
598        final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
599
600        outerloop: while (true) {
601            int eventType = parser.next();
602
603            switch (eventType) {
604            case XmlPullParser.START_TAG:
605                String elementName = parser.getName();
606                String namespace = parser.getNamespace();
607                switch (elementName) {
608                case "error":
609                    error = PacketParserUtils.parseError(parser);
610                    break;
611                // Otherwise, see if there is a registered provider for
612                // this element name and namespace.
613                default:
614                    IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
615                    if (provider != null) {
616                            iqPacket = provider.parse(parser);
617                    }
618                    // Note that if we reach this code, it is guranteed that the result IQ contained a child element
619                    // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first.
620                    else {
621                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
622                        // so that the content of the IQ can be examined later on
623                        iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
624                    }
625                    break;
626                }
627                break;
628            case XmlPullParser.END_TAG:
629                if (parser.getDepth() == initialDepth) {
630                    break outerloop;
631                }
632                break;
633            }
634        }
635        // Decide what to do when an IQ packet was not understood
636        if (iqPacket == null) {
637            switch (type) {
638            case error:
639                // If an IQ packet wasn't created above, create an empty error IQ packet.
640                iqPacket = new ErrorIQ(error);
641                break;
642            case result:
643                iqPacket = new EmptyResultIQ();
644                break;
645            default:
646                break;
647            }
648        }
649
650        // Set basic values on the iq packet.
651        iqPacket.setStanzaId(id);
652        iqPacket.setTo(to);
653        iqPacket.setFrom(from);
654        iqPacket.setType(type);
655        iqPacket.setError(error);
656
657        return iqPacket;
658    }
659
660    /**
661     * Parse the available SASL mechanisms reported from the server.
662     *
663     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
664     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
665     * @throws IOException
666     * @throws XmlPullParserException
667     */
668    public static Collection<String> parseMechanisms(XmlPullParser parser)
669                    throws XmlPullParserException, IOException {
670        List<String> mechanisms = new ArrayList<String>();
671        boolean done = false;
672        while (!done) {
673            int eventType = parser.next();
674
675            if (eventType == XmlPullParser.START_TAG) {
676                String elementName = parser.getName();
677                if (elementName.equals("mechanism")) {
678                    mechanisms.add(parser.nextText());
679                }
680            }
681            else if (eventType == XmlPullParser.END_TAG) {
682                if (parser.getName().equals("mechanisms")) {
683                    done = true;
684                }
685            }
686        }
687        return mechanisms;
688    }
689
690    /**
691     * Parse the Compression Feature reported from the server.
692     *
693     * @param parser the XML parser, positioned at the start of the compression stanza.
694     * @return The CompressionFeature stream element
695     * @throws IOException
696     * @throws XmlPullParserException if an exception occurs while parsing the stanza.
697     */
698    public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
699                    throws IOException, XmlPullParserException {
700        assert (parser.getEventType() == XmlPullParser.START_TAG);
701        String name;
702        final int initialDepth = parser.getDepth();
703        List<String> methods = new LinkedList<>();
704        outerloop: while (true) {
705            int eventType = parser.next();
706            switch (eventType) {
707            case XmlPullParser.START_TAG:
708                name = parser.getName();
709                switch (name) {
710                case "method":
711                    methods.add(parser.nextText());
712                    break;
713                }
714                break;
715            case XmlPullParser.END_TAG:
716                name = parser.getName();
717                switch (name) {
718                case Compress.Feature.ELEMENT:
719                    if (parser.getDepth() == initialDepth) {
720                        break outerloop;
721                    }
722                }
723            }
724        }
725        assert (parser.getEventType() == XmlPullParser.END_TAG);
726        assert (parser.getDepth() == initialDepth);
727        return new Compress.Feature(methods);
728    }
729
730    public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
731                    throws XmlPullParserException, IOException {
732        if (descriptiveTexts == null) {
733            descriptiveTexts = new HashMap<>();
734        }
735        String xmllang = ParserUtils.getXmlLang(parser);
736        if (xmllang == null) {
737            // XMPPError assumes the default locale, 'en', or the empty string.
738            // Establish the invariant that there is never null as a key.
739            xmllang = "";
740        }
741
742        String text = parser.nextText();
743        String previousValue = descriptiveTexts.put(xmllang, text);
744        assert (previousValue == null);
745        return descriptiveTexts;
746    }
747
748    /**
749     * Parses SASL authentication error packets.
750     *
751     * @param parser the XML parser.
752     * @return a SASL Failure packet.
753     * @throws IOException
754     * @throws XmlPullParserException
755     */
756    public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
757        final int initialDepth = parser.getDepth();
758        String condition = null;
759        Map<String, String> descriptiveTexts = null;
760        outerloop: while (true) {
761            int eventType = parser.next();
762            switch (eventType) {
763            case XmlPullParser.START_TAG:
764                String name = parser.getName();
765                if (name.equals("text")) {
766                    descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
767                }
768                else {
769                    assert (condition == null);
770                    condition = parser.getName();
771                }
772                break;
773            case XmlPullParser.END_TAG:
774                if (parser.getDepth() == initialDepth) {
775                    break outerloop;
776                }
777                break;
778            }
779        }
780        return new SASLFailure(condition, descriptiveTexts);
781    }
782
783    /**
784     * Parses stream error packets.
785     *
786     * @param parser the XML parser.
787     * @return an stream error packet.
788     * @throws Exception if an exception occurs while parsing the packet.
789     */
790    public static StreamError parseStreamError(XmlPullParser parser) throws Exception {
791        final int initialDepth = parser.getDepth();
792        List<ExtensionElement> extensions = new ArrayList<>();
793        Map<String, String> descriptiveTexts = null;
794        StreamError.Condition condition = null;
795        String conditionText = null;
796        outerloop: while (true) {
797            int eventType = parser.next();
798            switch (eventType) {
799            case XmlPullParser.START_TAG:
800                String name = parser.getName();
801                String namespace = parser.getNamespace();
802                switch (namespace) {
803                case StreamError.NAMESPACE:
804                    switch (name) {
805                    case "text":
806                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
807                        break;
808                    default:
809                        // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
810                        // then it has to be the stream error code
811                        condition = StreamError.Condition.fromString(name);
812                        if (!parser.isEmptyElementTag()) {
813                            conditionText = parser.nextText();
814                        }
815                        break;
816                    }
817                    break;
818                default:
819                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
820                    break;
821                }
822                break;
823            case XmlPullParser.END_TAG:
824                if (parser.getDepth() == initialDepth) {
825                    break outerloop;
826                }
827                break;
828            }
829        }
830        return new StreamError(condition, conditionText, descriptiveTexts, extensions);
831    }
832
833    /**
834     * Parses error sub-packets.
835     *
836     * @param parser the XML parser.
837     * @return an error sub-packet.
838     * @throws Exception
839     */
840    public static StanzaError.Builder parseError(XmlPullParser parser)
841                    throws Exception {
842        final int initialDepth = parser.getDepth();
843        Map<String, String> descriptiveTexts = null;
844        List<ExtensionElement> extensions = new ArrayList<>();
845        StanzaError.Builder builder = StanzaError.getBuilder();
846
847        // Parse the error header
848        builder.setType(StanzaError.Type.fromString(parser.getAttributeValue("", "type")));
849        builder.setErrorGenerator(parser.getAttributeValue("", "by"));
850
851        outerloop: while (true) {
852            int eventType = parser.next();
853            switch (eventType) {
854            case XmlPullParser.START_TAG:
855                String name = parser.getName();
856                String namespace = parser.getNamespace();
857                switch (namespace) {
858                case StanzaError.ERROR_CONDITION_AND_TEXT_NAMESPACE:
859                    switch (name) {
860                    case Stanza.TEXT:
861                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
862                        break;
863                    default:
864                        builder.setCondition(StanzaError.Condition.fromString(name));
865                        if (!parser.isEmptyElementTag()) {
866                            builder.setConditionText(parser.nextText());
867                        }
868                        break;
869                    }
870                    break;
871                default:
872                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
873                }
874                break;
875            case XmlPullParser.END_TAG:
876                if (parser.getDepth() == initialDepth) {
877                    break outerloop;
878                }
879            }
880        }
881        builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts);
882        return builder;
883    }
884
885    /**
886     * Parses an extension element.
887     *
888     * @param elementName the XML element name of the extension element.
889     * @param namespace the XML namespace of the stanza extension.
890     * @param parser the XML parser, positioned at the starting element of the extension.
891     *
892     * @return an extension element.
893     * @throws Exception when an error occurs during parsing.
894     * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead.
895     */
896    @Deprecated
897    public static ExtensionElement parsePacketExtension(String elementName, String namespace,
898                    XmlPullParser parser) throws Exception {
899        return parseExtensionElement(elementName, namespace, parser);
900    }
901
902    /**
903     * Parses an extension element.
904     *
905     * @param elementName the XML element name of the extension element.
906     * @param namespace the XML namespace of the stanza extension.
907     * @param parser the XML parser, positioned at the starting element of the extension.
908     *
909     * @return an extension element.
910     * @throws Exception when an error occurs during parsing.
911     */
912    public static ExtensionElement parseExtensionElement(String elementName, String namespace,
913                    XmlPullParser parser) throws Exception {
914        ParserUtils.assertAtStartTag(parser);
915        // See if a provider is registered to handle the extension.
916        ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
917        if (provider != null) {
918                return provider.parse(parser);
919        }
920
921        // No providers registered, so use a default extension.
922        return StandardExtensionElementProvider.INSTANCE.parse(parser);
923    }
924
925    public static StartTls parseStartTlsFeature(XmlPullParser parser)
926                    throws XmlPullParserException, IOException {
927        assert (parser.getEventType() == XmlPullParser.START_TAG);
928        assert (parser.getNamespace().equals(StartTls.NAMESPACE));
929        int initalDepth = parser.getDepth();
930        boolean required = false;
931        outerloop: while (true) {
932            int event = parser.next();
933            switch (event) {
934            case XmlPullParser.START_TAG:
935                String name = parser.getName();
936                switch (name) {
937                case "required":
938                    required = true;
939                    break;
940                }
941                break;
942            case XmlPullParser.END_TAG:
943                if (parser.getDepth() == initalDepth) {
944                    break outerloop;
945                }
946            }
947        }
948        assert (parser.getEventType() == XmlPullParser.END_TAG);
949        return new StartTls(required);
950    }
951
952    public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
953        ParserUtils.assertAtStartTag(parser);
954        final int initialDepth = parser.getDepth();
955        boolean optional = false;
956        if (parser.isEmptyElementTag()) {
957            return new Session.Feature(optional);
958        }
959
960        outerloop: while (true) {
961            int event = parser.next();
962            switch (event) {
963            case XmlPullParser.START_TAG:
964                String name = parser.getName();
965                switch (name) {
966                    case Session.Feature.OPTIONAL_ELEMENT:
967                        optional = true;
968                        break;
969                }
970                break;
971            case XmlPullParser.END_TAG:
972                if (parser.getDepth() == initialDepth) {
973                    break outerloop;
974                }
975            }
976        }
977
978        return new Session.Feature(optional);
979    }
980
981    public static void addExtensionElement(Stanza packet, XmlPullParser parser)
982                    throws Exception {
983        ParserUtils.assertAtStartTag(parser);
984        addExtensionElement(packet, parser, parser.getName(), parser.getNamespace());
985    }
986
987    public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
988                    String namespace) throws Exception {
989        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
990        packet.addExtension(packetExtension);
991    }
992
993    public static void addExtensionElement(Collection<ExtensionElement> collection,
994                    XmlPullParser parser) throws Exception {
995        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
996    }
997
998    public static void addExtensionElement(Collection<ExtensionElement> collection,
999                    XmlPullParser parser, String elementName, String namespace)
1000                    throws Exception {
1001        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1002        collection.add(packetExtension);
1003    }
1004}