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