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