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.SmackException;
032import org.jivesoftware.smack.compress.packet.Compress;
033import org.jivesoftware.smack.packet.DefaultExtensionElement;
034import org.jivesoftware.smack.packet.EmptyResultIQ;
035import org.jivesoftware.smack.packet.ErrorIQ;
036import org.jivesoftware.smack.packet.IQ;
037import org.jivesoftware.smack.packet.Message;
038import org.jivesoftware.smack.packet.Stanza;
039import org.jivesoftware.smack.packet.ExtensionElement;
040import org.jivesoftware.smack.packet.Presence;
041import org.jivesoftware.smack.packet.Session;
042import org.jivesoftware.smack.packet.StartTls;
043import org.jivesoftware.smack.packet.StreamError;
044import org.jivesoftware.smack.packet.UnparsedIQ;
045import org.jivesoftware.smack.packet.XMPPError;
046import org.jivesoftware.smack.provider.IQProvider;
047import org.jivesoftware.smack.provider.ExtensionElementProvider;
048import org.jivesoftware.smack.provider.ProviderManager;
049import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
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    public static Stanza parseStanza(String stanza) throws XmlPullParserException, IOException, SmackException {
132        return parseStanza(getParserFor(stanza));
133    }
134
135    /**
136     * Tries to parse and return either a Message, IQ or Presence stanza.
137     * 
138     * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
139     *
140     * @param parser
141     * @return a stanza(/packet) which is either a Message, IQ or Presence.
142     * @throws XmlPullParserException 
143     * @throws SmackException 
144     * @throws IOException 
145     */
146    public static Stanza parseStanza(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
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 IOException 
214     * @throws XmlPullParserException 
215     * @throws SmackException 
216     */
217    public static Message parseMessage(XmlPullParser parser)
218                    throws XmlPullParserException, IOException, SmackException {
219        ParserUtils.assertAtStartTag(parser);
220        assert(parser.getName().equals(Message.ELEMENT));
221
222        final int initialDepth = parser.getDepth();
223        Message message = new Message();
224        message.setStanzaId(parser.getAttributeValue("", "id"));
225        message.setTo(parser.getAttributeValue("", "to"));
226        message.setFrom(parser.getAttributeValue("", "from"));
227        String typeString = parser.getAttributeValue("", "type");
228        if (typeString != null) {
229            message.setType(Message.Type.fromString(typeString));
230        }
231        String language = getLanguageAttribute(parser);
232        
233        // determine message's default language
234        String defaultLanguage = null;
235        if (language != null && !"".equals(language.trim())) {
236            message.setLanguage(language);
237            defaultLanguage = language;
238        } 
239        else {
240            defaultLanguage = Stanza.getDefaultLanguage();
241        }
242
243        // Parse sub-elements. We include extra logic to make sure the values
244        // are only read once. This is because it's possible for the names to appear
245        // in arbitrary sub-elements.
246        String thread = null;
247        outerloop: while (true) {
248            int eventType = parser.next();
249            switch (eventType) {
250            case XmlPullParser.START_TAG:
251                String elementName = parser.getName();
252                String namespace = parser.getNamespace();
253                switch(elementName) {
254                case "subject":
255                    String xmlLangSubject = getLanguageAttribute(parser);
256                    if (xmlLangSubject == null) {
257                        xmlLangSubject = defaultLanguage;
258                    }
259
260                    String subject = parseElementText(parser);
261
262                    if (message.getSubject(xmlLangSubject) == null) {
263                        message.addSubject(xmlLangSubject, subject);
264                    }
265                    break;
266                case Message.BODY:
267                    String xmlLang = getLanguageAttribute(parser);
268                    if (xmlLang == null) {
269                        xmlLang = defaultLanguage;
270                    }
271
272                    String body = parseElementText(parser);
273
274                    if (message.getBody(xmlLang) == null) {
275                        message.addBody(xmlLang, body);
276                    }
277                    break;
278                case "thread":
279                    if (thread == null) {
280                        thread = parser.nextText();
281                    }
282                    break;
283                case "error":
284                    message.setError(parseError(parser));
285                    break;
286                 default:
287                    PacketParserUtils.addExtensionElement(message, parser, elementName, namespace);
288                    break;
289                }
290                break;
291            case XmlPullParser.END_TAG:
292                if (parser.getDepth() == initialDepth) {
293                    break outerloop;
294                }
295                break;
296            }
297        }
298
299        message.setThread(thread);
300        return message;
301    }
302
303    /**
304     * Returns the textual content of an element as String.
305     * <p>
306     * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
307     * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
308     * </p>
309     * This method is used for the parts where the XMPP specification requires elements that contain
310     * only text or are the empty element.
311     * 
312     * @param parser
313     * @return the textual content of the element as String
314     * @throws XmlPullParserException
315     * @throws IOException
316     */
317    public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
318        assert (parser.getEventType() == XmlPullParser.START_TAG);
319        String res;
320        if (parser.isEmptyElementTag()) {
321            res = "";
322        }
323        else {
324            // Advance to the text of the Element
325            int event = parser.next();
326            if (event != XmlPullParser.TEXT) {
327                if (event == XmlPullParser.END_TAG) {
328                    // Assume this is the end tag of the start tag at the
329                    // beginning of this method. Typical examples where this
330                    // happens are body elements containing the empty string,
331                    // ie. <body></body>, which appears to be valid XMPP, or a
332                    // least it's not explicitly forbidden by RFC 6121 5.2.3
333                    return "";
334                } else {
335                    throw new XmlPullParserException(
336                                    "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
337                }
338            }
339            res = parser.getText();
340            event = parser.next();
341            if (event != XmlPullParser.END_TAG) {
342                throw new XmlPullParserException(
343                                "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
344            }
345        }
346        return res;
347    }
348
349    /**
350     * Returns the current element as string.
351     * <p>
352     * The parser must be positioned on START_TAG.
353     * </p>
354     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
355     *
356     * @param parser the XML pull parser
357     * @return the element as string
358     * @throws XmlPullParserException
359     * @throws IOException
360     */
361    public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
362        return parseElement(parser, false);
363    }
364
365    public static CharSequence parseElement(XmlPullParser parser,
366                    boolean fullNamespaces) throws XmlPullParserException,
367                    IOException {
368        assert (parser.getEventType() == XmlPullParser.START_TAG);
369        return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
370    }
371
372    /**
373     * Returns the content of a element.
374     * <p>
375     * The parser must be positioned on the START_TAG of the element which content is going to get
376     * returned. If the current element is the empty element, then the empty string is returned. If
377     * it is a element which contains just text, then just the text is returned. If it contains
378     * nested elements (and text), then everything from the current opening tag to the corresponding
379     * closing tag of the same depth is returned as String.
380     * </p>
381     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
382     * 
383     * @param parser the XML pull parser
384     * @return the content of a tag
385     * @throws XmlPullParserException if parser encounters invalid XML
386     * @throws IOException if an IO error occurs
387     */
388    public static CharSequence parseContent(XmlPullParser parser)
389                    throws XmlPullParserException, IOException {
390        assert(parser.getEventType() == XmlPullParser.START_TAG);
391        if (parser.isEmptyElementTag()) {
392            return "";
393        }
394        // Advance the parser, since we want to parse the content of the current element
395        parser.next();
396        return parseContentDepth(parser, parser.getDepth(), false);
397    }
398
399    public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
400                    throws XmlPullParserException, IOException {
401        return parseContentDepth(parser, depth, false);
402    }
403
404    /**
405     * Returns the content from the current position of the parser up to the closing tag of the
406     * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
407     * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
408     * parent elements will be added to child elements that don't define a different namespace.
409     * <p>
410     * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
411     * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the
412     * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
413     * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
414     * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
415     * </p>
416     * <p>
417     * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
418     * </p>
419     * 
420     * @param parser
421     * @param depth
422     * @param fullNamespaces
423     * @return the content of the current depth
424     * @throws XmlPullParserException
425     * @throws IOException
426     */
427    public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
428        if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) {
429            return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces);
430        } else {
431            return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
432        }
433    }
434
435    private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
436                    boolean fullNamespaces) throws XmlPullParserException, IOException {
437        XmlStringBuilder xml = new XmlStringBuilder();
438        int event = parser.getEventType();
439        boolean isEmptyElement = false;
440        // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
441        // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
442        // in a nested element. It's an ugly workaround that has the potential to break things.
443        String namespaceElement = null;
444        outerloop: while (true) {
445            switch (event) {
446            case XmlPullParser.START_TAG:
447                xml.halfOpenElement(parser.getName());
448                if (namespaceElement == null || fullNamespaces) {
449                    String namespace = parser.getNamespace();
450                    if (StringUtils.isNotEmpty(namespace)) {
451                        xml.attribute("xmlns", namespace);
452                        namespaceElement = parser.getName();
453                    }
454                }
455                for (int i = 0; i < parser.getAttributeCount(); i++) {
456                    xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
457                }
458                if (parser.isEmptyElementTag()) {
459                    xml.closeEmptyElement();
460                    isEmptyElement = true;
461                }
462                else {
463                    xml.rightAngleBracket();
464                }
465                break;
466            case XmlPullParser.END_TAG:
467                if (isEmptyElement) {
468                    // Do nothing as the element was already closed, just reset the flag
469                    isEmptyElement = false;
470                }
471                else {
472                    xml.closeElement(parser.getName());
473                }
474                if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
475                    // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
476                    namespaceElement = null;
477                }
478                if (parser.getDepth() <= depth) {
479                    // Abort parsing, we are done
480                    break outerloop;
481                }
482                break;
483            case XmlPullParser.TEXT:
484                xml.append(parser.getText());
485                break;
486            }
487            event = parser.next();
488        }
489        return xml;
490    }
491
492    private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces)
493                    throws XmlPullParserException, IOException {
494        StringBuilder sb = new StringBuilder();
495        int event = parser.getEventType();
496        outerloop: while (true) {
497            // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
498            // twice, so in order to prevent duplication we only add their text when we are on their end tag.
499            if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
500                sb.append(parser.getText());
501            }
502            if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
503                break outerloop;
504            }
505            event = parser.next();
506        }
507        return sb;
508    }
509
510    /**
511     * Parses a presence packet.
512     *
513     * @param parser the XML parser, positioned at the start of a presence packet.
514     * @return a Presence packet.
515     * @throws IOException 
516     * @throws XmlPullParserException 
517     * @throws SmackException 
518     */
519    public static Presence parsePresence(XmlPullParser parser)
520                    throws XmlPullParserException, IOException, SmackException {
521        ParserUtils.assertAtStartTag(parser);
522        final int initialDepth = parser.getDepth();
523
524        Presence.Type type = Presence.Type.available;
525        String typeString = parser.getAttributeValue("", "type");
526        if (typeString != null && !typeString.equals("")) {
527            type = Presence.Type.fromString(typeString);
528        }
529        Presence presence = new Presence(type);
530        presence.setTo(parser.getAttributeValue("", "to"));
531        presence.setFrom(parser.getAttributeValue("", "from"));
532        presence.setStanzaId(parser.getAttributeValue("", "id"));
533
534        String language = getLanguageAttribute(parser);
535        if (language != null && !"".equals(language.trim())) {
536                presence.setLanguage(language);
537        }
538
539        // Parse sub-elements
540        outerloop: while (true) {
541            int eventType = parser.next();
542            switch (eventType) {
543            case XmlPullParser.START_TAG:
544                String elementName = parser.getName();
545                String namespace = parser.getNamespace();
546                switch(elementName) {
547                case "status":
548                    presence.setStatus(parser.nextText());
549                    break;
550                case "priority":
551                    int priority = Integer.parseInt(parser.nextText());
552                    presence.setPriority(priority);
553                    break;
554                case "show":
555                    String modeText = parser.nextText();
556                    if (StringUtils.isNotEmpty(modeText)) {
557                        presence.setMode(Presence.Mode.fromString(modeText));
558                    } else {
559                        // Some implementations send presence stanzas with a
560                        // '<show />' element, which is a invalid XMPP presence
561                        // stanza according to RFC 6121 4.7.2.1
562                        LOGGER.warning("Empty or null mode text in presence show element form "
563                                        + presence.getFrom()
564                                        + " with id '"
565                                        + presence.getStanzaId()
566                                        + "' which is invalid according to RFC6121 4.7.2.1");
567                    }
568                    break;
569                case "error":
570                    presence.setError(parseError(parser));
571                    break;
572                default:
573                // Otherwise, it must be a packet extension.
574                    // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
575                    // failing completely here. See SMACK-390 for more information.
576                    try {
577                        PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace);
578                    } catch (Exception e) {
579                        LOGGER.log(Level.WARNING,
580                                        "Failed to parse extension packet in Presence packet. Attributes: from="
581                                                        + presence.getFrom() + " id=" + presence.getStanzaId(), e);
582                    }
583                    break;
584                }
585                break;
586            case XmlPullParser.END_TAG:
587                if (parser.getDepth() == initialDepth) {
588                    break outerloop;
589                }
590                break;
591            }
592        }
593        return presence;
594    }
595
596    /**
597     * Parses an IQ packet.
598     *
599     * @param parser the XML parser, positioned at the start of an IQ packet.
600     * @return an IQ object.
601     * @throws XmlPullParserException 
602     * @throws IOException 
603     * @throws SmackException 
604     */
605    public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
606        ParserUtils.assertAtStartTag(parser);
607        final int initialDepth = parser.getDepth();
608        IQ iqPacket = null;
609        XMPPError error = null;
610
611        final String id = parser.getAttributeValue("", "id");
612        final String to = parser.getAttributeValue("", "to");
613        final String from = parser.getAttributeValue("", "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 XmlPullParserException if an exception occurs while parsing the packet.
798     * @throws SmackException 
799     */
800    public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException,
801                    SmackException {
802        final int initialDepth = parser.getDepth();
803        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
804        Map<String, String> descriptiveTexts = null;
805        StreamError.Condition condition = null;
806        String conditionText = null;
807        outerloop: while (true) {
808            int eventType = parser.next();
809            switch (eventType) {
810            case XmlPullParser.START_TAG:
811                String name = parser.getName();
812                String namespace = parser.getNamespace();
813                switch (namespace) {
814                case StreamError.NAMESPACE:
815                    switch (name) {
816                    case "text":
817                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
818                        break;
819                    default:
820                        // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
821                        // then it has to be the stream error code
822                        condition = StreamError.Condition.fromString(name);
823                        if (!parser.isEmptyElementTag()) {
824                            conditionText = parser.nextText();
825                        }
826                        break;
827                    }
828                    break;
829                default:
830                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
831                    break;
832                }
833                break;
834            case XmlPullParser.END_TAG:
835                if (parser.getDepth() == initialDepth) {
836                    break outerloop;
837                }
838                break;
839            }
840        }
841        return new StreamError(condition, conditionText, descriptiveTexts, extensions);
842    }
843
844    /**
845     * Parses error sub-packets.
846     *
847     * @param parser the XML parser.
848     * @return an error sub-packet.
849     * @throws IOException 
850     * @throws XmlPullParserException 
851     * @throws SmackException 
852     */
853    public static XMPPError parseError(XmlPullParser parser)
854                    throws XmlPullParserException, IOException, SmackException {
855        final int initialDepth = parser.getDepth();
856        Map<String, String> descriptiveTexts = null;
857        XMPPError.Condition condition = null;
858        String conditionText = null;
859        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
860
861        // Parse the error header
862        XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type"));
863        String errorGenerator = parser.getAttributeValue("", "by");
864
865        outerloop: while (true) {
866            int eventType = parser.next();
867            switch (eventType) {
868            case XmlPullParser.START_TAG:
869                String name = parser.getName();
870                String namespace = parser.getNamespace();
871                switch (namespace) {
872                case XMPPError.NAMESPACE:
873                    switch (name) {
874                    case Stanza.TEXT:
875                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
876                        break;
877                    default:
878                        condition = XMPPError.Condition.fromString(name);
879                        if (!parser.isEmptyElementTag()) {
880                            conditionText = parser.nextText();
881                        }
882                        break;
883                    }
884                    break;
885                default:
886                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
887                }
888                break;
889            case XmlPullParser.END_TAG:
890                if (parser.getDepth() == initialDepth) {
891                    break outerloop;
892                }
893            }
894        }
895        return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions);
896    }
897
898    /**
899     * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead.
900     */
901    @Deprecated
902    public static ExtensionElement parsePacketExtension(String elementName, String namespace,
903                    XmlPullParser parser) throws XmlPullParserException,
904                    IOException, SmackException {
905        return parseExtensionElement(elementName, namespace, parser);
906    }
907 
908    /**
909     * Parses an extension element.
910     *
911     * @param elementName the XML element name of the extension element.
912     * @param namespace the XML namespace of the stanza(/packet) extension.
913     * @param parser the XML parser, positioned at the starting element of the extension.
914     * @return an extension element.
915     */
916    public static ExtensionElement parseExtensionElement(String elementName, String namespace,
917                    XmlPullParser parser) throws XmlPullParserException,
918                    IOException, SmackException {
919        ParserUtils.assertAtStartTag(parser);
920        // See if a provider is registered to handle the extension.
921        ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
922        if (provider != null) {
923                return provider.parse(parser);
924        }
925
926        final int initialDepth = parser.getDepth();
927        // No providers registered, so use a default extension.
928        DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace);
929        outerloop: while (true) {
930            int eventType = parser.next();
931            switch (eventType) {
932            case XmlPullParser.START_TAG:
933                String name = parser.getName();
934                // If an empty element, set the value with the empty string.
935                if (parser.isEmptyElementTag()) {
936                    extension.setValue(name,"");
937                }
938                // Otherwise, get the the element text.
939                else {
940                    eventType = parser.next();
941                    if (eventType == XmlPullParser.TEXT) {
942                        String value = parser.getText();
943                        extension.setValue(name, value);
944                    }
945                }
946                break;
947            case XmlPullParser.END_TAG:
948                if (parser.getDepth() == initialDepth) {
949                    break outerloop;
950                }
951            }
952        }
953        return extension;
954    }
955
956    public static StartTls parseStartTlsFeature(XmlPullParser parser)
957                    throws XmlPullParserException, IOException {
958        assert (parser.getEventType() == XmlPullParser.START_TAG);
959        assert (parser.getNamespace().equals(StartTls.NAMESPACE));
960        int initalDepth = parser.getDepth();
961        boolean required = false;
962        outerloop: while (true) {
963            int event = parser.next();
964            switch (event) {
965            case XmlPullParser.START_TAG:
966                String name = parser.getName();
967                switch (name) {
968                case "required":
969                    required = true;
970                    break;
971                }
972                break;
973            case XmlPullParser.END_TAG:
974                if (parser.getDepth() == initalDepth) {
975                    break outerloop;
976                }
977            }
978        }
979        assert(parser.getEventType() == XmlPullParser.END_TAG);
980        return new StartTls(required);
981    }
982
983    public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
984        ParserUtils.assertAtStartTag(parser);
985        final int initialDepth = parser.getDepth();
986        boolean optional = false;
987        if (!parser.isEmptyElementTag()) {
988        outerloop: while(true) {
989            int event = parser.next();
990            switch (event) {
991            case XmlPullParser.START_TAG:
992                String name = parser.getName();
993                switch (name) {
994                    case Session.Feature.OPTIONAL_ELEMENT:
995                        optional = true;
996                        break;
997                }
998                break;
999            case XmlPullParser.END_TAG:
1000                if (parser.getDepth() == initialDepth) {
1001                    break outerloop;
1002                }
1003            }
1004        }
1005        }
1006        return new Session.Feature(optional);
1007
1008    }
1009    private static String getLanguageAttribute(XmlPullParser parser) {
1010        for (int i = 0; i < parser.getAttributeCount(); i++) {
1011            String attributeName = parser.getAttributeName(i);
1012            if ( "xml:lang".equals(attributeName) ||
1013                    ("lang".equals(attributeName) &&
1014                            "xml".equals(parser.getAttributePrefix(i)))) {
1015                        return parser.getAttributeValue(i);
1016                }
1017        }
1018        return null;
1019    }
1020
1021    @Deprecated
1022    public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException,
1023                    IOException, SmackException {
1024        addExtensionElement(packet, parser);
1025    }
1026
1027    @Deprecated
1028    public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace)
1029                    throws XmlPullParserException, IOException, SmackException {
1030        addExtensionElement(packet, parser, elementName, namespace);
1031    }
1032
1033    @Deprecated
1034    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser)
1035                    throws XmlPullParserException, IOException, SmackException {
1036        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1037    }
1038
1039    @Deprecated
1040    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser,
1041                    String elementName, String namespace) throws XmlPullParserException, IOException, SmackException {
1042        addExtensionElement(collection, parser, elementName, namespace);
1043    }
1044
1045
1046    public static void addExtensionElement(Stanza packet, XmlPullParser parser)
1047                    throws XmlPullParserException, IOException, SmackException {
1048        ParserUtils.assertAtStartTag(parser);
1049        addExtensionElement(packet, parser, parser.getName(), parser.getNamespace());
1050    }
1051
1052    public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
1053                    String namespace) throws XmlPullParserException, IOException, SmackException {
1054        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1055        packet.addExtension(packetExtension);
1056    }
1057
1058    public static void addExtensionElement(Collection<ExtensionElement> collection,
1059                    XmlPullParser parser) throws XmlPullParserException, IOException,
1060                    SmackException {
1061        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1062    }
1063
1064    public static void addExtensionElement(Collection<ExtensionElement> collection,
1065                    XmlPullParser parser, String elementName, String namespace)
1066                    throws XmlPullParserException, IOException, SmackException {
1067        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1068        collection.add(packetExtension);
1069    }
1070}