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. After this method returns the parser
305     * position will be END_TAG, following the established pull parser calling convention.
306     * <p>
307     * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
308     * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
309     * </p>
310     * This method is used for the parts where the XMPP specification requires elements that contain
311     * only text or are the empty element.
312     * 
313     * @param parser
314     * @return the textual content of the element as String
315     * @throws XmlPullParserException
316     * @throws IOException
317     */
318    public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
319        assert (parser.getEventType() == XmlPullParser.START_TAG);
320        String res;
321        if (parser.isEmptyElementTag()) {
322            res = "";
323        }
324        else {
325            // Advance to the text of the Element
326            int event = parser.next();
327            if (event != XmlPullParser.TEXT) {
328                if (event == XmlPullParser.END_TAG) {
329                    // Assume this is the end tag of the start tag at the
330                    // beginning of this method. Typical examples where this
331                    // happens are body elements containing the empty string,
332                    // ie. <body></body>, which appears to be valid XMPP, or a
333                    // least it's not explicitly forbidden by RFC 6121 5.2.3
334                    return "";
335                } else {
336                    throw new XmlPullParserException(
337                                    "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
338                }
339            }
340            res = parser.getText();
341            event = parser.next();
342            if (event != XmlPullParser.END_TAG) {
343                throw new XmlPullParserException(
344                                "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
345            }
346        }
347        return res;
348    }
349
350    /**
351     * Returns the current element as string.
352     * <p>
353     * The parser must be positioned on START_TAG.
354     * </p>
355     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
356     *
357     * @param parser the XML pull parser
358     * @return the element as string
359     * @throws XmlPullParserException
360     * @throws IOException
361     */
362    public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
363        return parseElement(parser, false);
364    }
365
366    public static CharSequence parseElement(XmlPullParser parser,
367                    boolean fullNamespaces) throws XmlPullParserException,
368                    IOException {
369        assert (parser.getEventType() == XmlPullParser.START_TAG);
370        return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
371    }
372
373    /**
374     * Returns the content of a element.
375     * <p>
376     * The parser must be positioned on the START_TAG of the element which content is going to get
377     * returned. If the current element is the empty element, then the empty string is returned. If
378     * it is a element which contains just text, then just the text is returned. If it contains
379     * nested elements (and text), then everything from the current opening tag to the corresponding
380     * closing tag of the same depth is returned as String.
381     * </p>
382     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
383     * 
384     * @param parser the XML pull parser
385     * @return the content of a tag
386     * @throws XmlPullParserException if parser encounters invalid XML
387     * @throws IOException if an IO error occurs
388     */
389    public static CharSequence parseContent(XmlPullParser parser)
390                    throws XmlPullParserException, IOException {
391        assert(parser.getEventType() == XmlPullParser.START_TAG);
392        if (parser.isEmptyElementTag()) {
393            return "";
394        }
395        // Advance the parser, since we want to parse the content of the current element
396        parser.next();
397        return parseContentDepth(parser, parser.getDepth(), false);
398    }
399
400    public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
401                    throws XmlPullParserException, IOException {
402        return parseContentDepth(parser, depth, false);
403    }
404
405    /**
406     * Returns the content from the current position of the parser up to the closing tag of the
407     * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
408     * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
409     * parent elements will be added to child elements that don't define a different namespace.
410     * <p>
411     * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
412     * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the
413     * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
414     * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
415     * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
416     * </p>
417     * <p>
418     * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
419     * </p>
420     * 
421     * @param parser
422     * @param depth
423     * @param fullNamespaces
424     * @return the content of the current depth
425     * @throws XmlPullParserException
426     * @throws IOException
427     */
428    public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
429        if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) {
430            return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces);
431        } else {
432            return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
433        }
434    }
435
436    private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
437                    boolean fullNamespaces) throws XmlPullParserException, IOException {
438        XmlStringBuilder xml = new XmlStringBuilder();
439        int event = parser.getEventType();
440        boolean isEmptyElement = false;
441        // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
442        // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
443        // in a nested element. It's an ugly workaround that has the potential to break things.
444        String namespaceElement = null;
445        outerloop: while (true) {
446            switch (event) {
447            case XmlPullParser.START_TAG:
448                xml.halfOpenElement(parser.getName());
449                if (namespaceElement == null || fullNamespaces) {
450                    String namespace = parser.getNamespace();
451                    if (StringUtils.isNotEmpty(namespace)) {
452                        xml.attribute("xmlns", namespace);
453                        namespaceElement = parser.getName();
454                    }
455                }
456                for (int i = 0; i < parser.getAttributeCount(); i++) {
457                    xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
458                }
459                if (parser.isEmptyElementTag()) {
460                    xml.closeEmptyElement();
461                    isEmptyElement = true;
462                }
463                else {
464                    xml.rightAngleBracket();
465                }
466                break;
467            case XmlPullParser.END_TAG:
468                if (isEmptyElement) {
469                    // Do nothing as the element was already closed, just reset the flag
470                    isEmptyElement = false;
471                }
472                else {
473                    xml.closeElement(parser.getName());
474                }
475                if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
476                    // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
477                    namespaceElement = null;
478                }
479                if (parser.getDepth() <= depth) {
480                    // Abort parsing, we are done
481                    break outerloop;
482                }
483                break;
484            case XmlPullParser.TEXT:
485                xml.escape(parser.getText());
486                break;
487            }
488            event = parser.next();
489        }
490        return xml;
491    }
492
493    private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces)
494                    throws XmlPullParserException, IOException {
495        StringBuilder sb = new StringBuilder();
496        int event = parser.getEventType();
497        outerloop: while (true) {
498            // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
499            // twice, so in order to prevent duplication we only add their text when we are on their end tag.
500            if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
501                CharSequence text = parser.getText();
502                if (event == XmlPullParser.TEXT) {
503                    // TODO the toString() can be removed in Smack 4.2.
504                    text = StringUtils.escapeForXML(text.toString());
505                }
506                sb.append(text);
507            }
508            if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
509                break outerloop;
510            }
511            event = parser.next();
512        }
513        return sb;
514    }
515
516    /**
517     * Parses a presence packet.
518     *
519     * @param parser the XML parser, positioned at the start of a presence packet.
520     * @return a Presence packet.
521     * @throws IOException 
522     * @throws XmlPullParserException 
523     * @throws SmackException 
524     */
525    public static Presence parsePresence(XmlPullParser parser)
526                    throws XmlPullParserException, IOException, SmackException {
527        ParserUtils.assertAtStartTag(parser);
528        final int initialDepth = parser.getDepth();
529
530        Presence.Type type = Presence.Type.available;
531        String typeString = parser.getAttributeValue("", "type");
532        if (typeString != null && !typeString.equals("")) {
533            type = Presence.Type.fromString(typeString);
534        }
535        Presence presence = new Presence(type);
536        presence.setTo(parser.getAttributeValue("", "to"));
537        presence.setFrom(parser.getAttributeValue("", "from"));
538        presence.setStanzaId(parser.getAttributeValue("", "id"));
539
540        String language = getLanguageAttribute(parser);
541        if (language != null && !"".equals(language.trim())) {
542                presence.setLanguage(language);
543        }
544
545        // Parse sub-elements
546        outerloop: while (true) {
547            int eventType = parser.next();
548            switch (eventType) {
549            case XmlPullParser.START_TAG:
550                String elementName = parser.getName();
551                String namespace = parser.getNamespace();
552                switch(elementName) {
553                case "status":
554                    presence.setStatus(parser.nextText());
555                    break;
556                case "priority":
557                    int priority = Integer.parseInt(parser.nextText());
558                    presence.setPriority(priority);
559                    break;
560                case "show":
561                    String modeText = parser.nextText();
562                    if (StringUtils.isNotEmpty(modeText)) {
563                        presence.setMode(Presence.Mode.fromString(modeText));
564                    } else {
565                        // Some implementations send presence stanzas with a
566                        // '<show />' element, which is a invalid XMPP presence
567                        // stanza according to RFC 6121 4.7.2.1
568                        LOGGER.warning("Empty or null mode text in presence show element form "
569                                        + presence.getFrom()
570                                        + " with id '"
571                                        + presence.getStanzaId()
572                                        + "' which is invalid according to RFC6121 4.7.2.1");
573                    }
574                    break;
575                case "error":
576                    presence.setError(parseError(parser));
577                    break;
578                default:
579                // Otherwise, it must be a packet extension.
580                    // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
581                    // failing completely here. See SMACK-390 for more information.
582                    try {
583                        PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace);
584                    } catch (Exception e) {
585                        LOGGER.log(Level.WARNING,
586                                        "Failed to parse extension packet in Presence packet. Attributes: from="
587                                                        + presence.getFrom() + " id=" + presence.getStanzaId(), e);
588                    }
589                    break;
590                }
591                break;
592            case XmlPullParser.END_TAG:
593                if (parser.getDepth() == initialDepth) {
594                    break outerloop;
595                }
596                break;
597            }
598        }
599        return presence;
600    }
601
602    /**
603     * Parses an IQ packet.
604     *
605     * @param parser the XML parser, positioned at the start of an IQ packet.
606     * @return an IQ object.
607     * @throws XmlPullParserException 
608     * @throws IOException 
609     * @throws SmackException 
610     */
611    public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
612        ParserUtils.assertAtStartTag(parser);
613        final int initialDepth = parser.getDepth();
614        IQ iqPacket = null;
615        XMPPError error = null;
616
617        final String id = parser.getAttributeValue("", "id");
618        final String to = parser.getAttributeValue("", "to");
619        final String from = parser.getAttributeValue("", "from");
620        final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
621
622        outerloop: while (true) {
623            int eventType = parser.next();
624
625            switch (eventType) {
626            case XmlPullParser.START_TAG:
627                String elementName = parser.getName();
628                String namespace = parser.getNamespace();
629                switch(elementName) {
630                case "error":
631                    error = PacketParserUtils.parseError(parser);
632                    break;
633                // Otherwise, see if there is a registered provider for
634                // this element name and namespace.
635                default:
636                    IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
637                    if (provider != null) {
638                            iqPacket = provider.parse(parser);
639                    }
640                    // Note that if we reach this code, it is guranteed that the result IQ contained a child element
641                    // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first.
642                    else {
643                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
644                        // so that the content of the IQ can be examined later on
645                        iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
646                    }
647                    break;
648                }
649                break;
650            case XmlPullParser.END_TAG:
651                if (parser.getDepth() == initialDepth) {
652                    break outerloop;
653                }
654                break;
655            }
656        }
657        // Decide what to do when an IQ packet was not understood
658        if (iqPacket == null) {
659            switch (type) {
660            case error:
661                // If an IQ packet wasn't created above, create an empty error IQ packet.
662                iqPacket = new ErrorIQ(error);
663                break;
664            case result:
665                iqPacket = new EmptyResultIQ();
666                break;
667            default:
668                break;
669            }
670        }
671
672        // Set basic values on the iq packet.
673        iqPacket.setStanzaId(id);
674        iqPacket.setTo(to);
675        iqPacket.setFrom(from);
676        iqPacket.setType(type);
677        iqPacket.setError(error);
678
679        return iqPacket;
680    }
681
682    /**
683     * Parse the available SASL mechanisms reported from the server.
684     *
685     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
686     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
687     * @throws IOException 
688     * @throws XmlPullParserException 
689     */
690    public static Collection<String> parseMechanisms(XmlPullParser parser)
691                    throws XmlPullParserException, IOException {
692        List<String> mechanisms = new ArrayList<String>();
693        boolean done = false;
694        while (!done) {
695            int eventType = parser.next();
696
697            if (eventType == XmlPullParser.START_TAG) {
698                String elementName = parser.getName();
699                if (elementName.equals("mechanism")) {
700                    mechanisms.add(parser.nextText());
701                }
702            }
703            else if (eventType == XmlPullParser.END_TAG) {
704                if (parser.getName().equals("mechanisms")) {
705                    done = true;
706                }
707            }
708        }
709        return mechanisms;
710    }
711
712    /**
713     * Parse the Compression Feature reported from the server.
714     *
715     * @param parser the XML parser, positioned at the start of the compression stanza.
716     * @return The CompressionFeature stream element
717     * @throws XmlPullParserException if an exception occurs while parsing the stanza.
718     */
719    public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
720                    throws IOException, XmlPullParserException {
721        assert (parser.getEventType() == XmlPullParser.START_TAG);
722        String name;
723        final int initialDepth = parser.getDepth();
724        List<String> methods = new LinkedList<String>();
725        outerloop: while (true) {
726            int eventType = parser.next();
727            switch (eventType) {
728            case XmlPullParser.START_TAG:
729                name = parser.getName();
730                switch (name) {
731                case "method":
732                    methods.add(parser.nextText());
733                    break;
734                }
735                break;
736            case XmlPullParser.END_TAG:
737                name = parser.getName();
738                switch (name) {
739                case Compress.Feature.ELEMENT:
740                    if (parser.getDepth() == initialDepth) {
741                        break outerloop;
742                    }
743                }
744            }
745        }
746        assert (parser.getEventType() == XmlPullParser.END_TAG);
747        assert (parser.getDepth() == initialDepth);
748        return new Compress.Feature(methods);
749    }
750
751    public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
752                    throws XmlPullParserException, IOException {
753        if (descriptiveTexts == null) {
754            descriptiveTexts = new HashMap<String, String>();
755        }
756        String xmllang = getLanguageAttribute(parser);
757        String text = parser.nextText();
758        String previousValue = descriptiveTexts.put(xmllang, text);
759        assert (previousValue == null);
760        return descriptiveTexts;
761    }
762
763    /**
764     * Parses SASL authentication error packets.
765     * 
766     * @param parser the XML parser.
767     * @return a SASL Failure packet.
768     * @throws IOException 
769     * @throws XmlPullParserException 
770     */
771    public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
772        final int initialDepth = parser.getDepth();
773        String condition = null;
774        Map<String, String> descriptiveTexts = null;
775        outerloop: while (true) {
776            int eventType = parser.next();
777            switch (eventType) {
778            case XmlPullParser.START_TAG:
779                String name = parser.getName();
780                if (name.equals("text")) {
781                    descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
782                }
783                else {
784                    assert(condition == null);
785                    condition = parser.getName();
786                }
787                break;
788            case XmlPullParser.END_TAG:
789                if (parser.getDepth() == initialDepth) {
790                    break outerloop;
791                }
792                break;
793            }
794        }
795        return new SASLFailure(condition, descriptiveTexts);
796    }
797
798    /**
799     * Parses stream error packets.
800     *
801     * @param parser the XML parser.
802     * @return an stream error packet.
803     * @throws XmlPullParserException if an exception occurs while parsing the packet.
804     * @throws SmackException 
805     */
806    public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException,
807                    SmackException {
808        final int initialDepth = parser.getDepth();
809        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
810        Map<String, String> descriptiveTexts = null;
811        StreamError.Condition condition = null;
812        String conditionText = null;
813        outerloop: while (true) {
814            int eventType = parser.next();
815            switch (eventType) {
816            case XmlPullParser.START_TAG:
817                String name = parser.getName();
818                String namespace = parser.getNamespace();
819                switch (namespace) {
820                case StreamError.NAMESPACE:
821                    switch (name) {
822                    case "text":
823                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
824                        break;
825                    default:
826                        // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
827                        // then it has to be the stream error code
828                        condition = StreamError.Condition.fromString(name);
829                        if (!parser.isEmptyElementTag()) {
830                            conditionText = parser.nextText();
831                        }
832                        break;
833                    }
834                    break;
835                default:
836                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
837                    break;
838                }
839                break;
840            case XmlPullParser.END_TAG:
841                if (parser.getDepth() == initialDepth) {
842                    break outerloop;
843                }
844                break;
845            }
846        }
847        return new StreamError(condition, conditionText, descriptiveTexts, extensions);
848    }
849
850    /**
851     * Parses error sub-packets.
852     *
853     * @param parser the XML parser.
854     * @return an error sub-packet.
855     * @throws IOException 
856     * @throws XmlPullParserException 
857     * @throws SmackException 
858     */
859    public static XMPPError parseError(XmlPullParser parser)
860                    throws XmlPullParserException, IOException, SmackException {
861        final int initialDepth = parser.getDepth();
862        Map<String, String> descriptiveTexts = null;
863        XMPPError.Condition condition = null;
864        String conditionText = null;
865        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
866
867        // Parse the error header
868        XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type"));
869        String errorGenerator = parser.getAttributeValue("", "by");
870
871        outerloop: while (true) {
872            int eventType = parser.next();
873            switch (eventType) {
874            case XmlPullParser.START_TAG:
875                String name = parser.getName();
876                String namespace = parser.getNamespace();
877                switch (namespace) {
878                case XMPPError.NAMESPACE:
879                    switch (name) {
880                    case Stanza.TEXT:
881                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
882                        break;
883                    default:
884                        condition = XMPPError.Condition.fromString(name);
885                        if (!parser.isEmptyElementTag()) {
886                            conditionText = parser.nextText();
887                        }
888                        break;
889                    }
890                    break;
891                default:
892                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
893                }
894                break;
895            case XmlPullParser.END_TAG:
896                if (parser.getDepth() == initialDepth) {
897                    break outerloop;
898                }
899            }
900        }
901        return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions);
902    }
903
904    /**
905     * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead.
906     */
907    @Deprecated
908    public static ExtensionElement parsePacketExtension(String elementName, String namespace,
909                    XmlPullParser parser) throws XmlPullParserException,
910                    IOException, SmackException {
911        return parseExtensionElement(elementName, namespace, parser);
912    }
913 
914    /**
915     * Parses an extension element.
916     *
917     * @param elementName the XML element name of the extension element.
918     * @param namespace the XML namespace of the stanza(/packet) extension.
919     * @param parser the XML parser, positioned at the starting element of the extension.
920     * @return an extension element.
921     */
922    public static ExtensionElement parseExtensionElement(String elementName, String namespace,
923                    XmlPullParser parser) throws XmlPullParserException,
924                    IOException, SmackException {
925        ParserUtils.assertAtStartTag(parser);
926        // See if a provider is registered to handle the extension.
927        ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
928        if (provider != null) {
929                return provider.parse(parser);
930        }
931
932        final int initialDepth = parser.getDepth();
933        // No providers registered, so use a default extension.
934        DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace);
935        outerloop: while (true) {
936            int eventType = parser.next();
937            switch (eventType) {
938            case XmlPullParser.START_TAG:
939                String name = parser.getName();
940                // If an empty element, set the value with the empty string.
941                if (parser.isEmptyElementTag()) {
942                    extension.setValue(name,"");
943                }
944                // Otherwise, get the the element text.
945                else {
946                    eventType = parser.next();
947                    if (eventType == XmlPullParser.TEXT) {
948                        String value = parser.getText();
949                        extension.setValue(name, value);
950                    }
951                }
952                break;
953            case XmlPullParser.END_TAG:
954                if (parser.getDepth() == initialDepth) {
955                    break outerloop;
956                }
957            }
958        }
959        return extension;
960    }
961
962    public static StartTls parseStartTlsFeature(XmlPullParser parser)
963                    throws XmlPullParserException, IOException {
964        assert (parser.getEventType() == XmlPullParser.START_TAG);
965        assert (parser.getNamespace().equals(StartTls.NAMESPACE));
966        int initalDepth = parser.getDepth();
967        boolean required = false;
968        outerloop: while (true) {
969            int event = parser.next();
970            switch (event) {
971            case XmlPullParser.START_TAG:
972                String name = parser.getName();
973                switch (name) {
974                case "required":
975                    required = true;
976                    break;
977                }
978                break;
979            case XmlPullParser.END_TAG:
980                if (parser.getDepth() == initalDepth) {
981                    break outerloop;
982                }
983            }
984        }
985        assert(parser.getEventType() == XmlPullParser.END_TAG);
986        return new StartTls(required);
987    }
988
989    public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
990        ParserUtils.assertAtStartTag(parser);
991        final int initialDepth = parser.getDepth();
992        boolean optional = false;
993        if (!parser.isEmptyElementTag()) {
994        outerloop: while(true) {
995            int event = parser.next();
996            switch (event) {
997            case XmlPullParser.START_TAG:
998                String name = parser.getName();
999                switch (name) {
1000                    case Session.Feature.OPTIONAL_ELEMENT:
1001                        optional = true;
1002                        break;
1003                }
1004                break;
1005            case XmlPullParser.END_TAG:
1006                if (parser.getDepth() == initialDepth) {
1007                    break outerloop;
1008                }
1009            }
1010        }
1011        }
1012        return new Session.Feature(optional);
1013
1014    }
1015    private static String getLanguageAttribute(XmlPullParser parser) {
1016        for (int i = 0; i < parser.getAttributeCount(); i++) {
1017            String attributeName = parser.getAttributeName(i);
1018            if ( "xml:lang".equals(attributeName) ||
1019                    ("lang".equals(attributeName) &&
1020                            "xml".equals(parser.getAttributePrefix(i)))) {
1021                        return parser.getAttributeValue(i);
1022                }
1023        }
1024        return null;
1025    }
1026
1027    @Deprecated
1028    public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException,
1029                    IOException, SmackException {
1030        addExtensionElement(packet, parser);
1031    }
1032
1033    @Deprecated
1034    public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace)
1035                    throws XmlPullParserException, IOException, SmackException {
1036        addExtensionElement(packet, parser, elementName, namespace);
1037    }
1038
1039    @Deprecated
1040    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser)
1041                    throws XmlPullParserException, IOException, SmackException {
1042        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1043    }
1044
1045    @Deprecated
1046    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser,
1047                    String elementName, String namespace) throws XmlPullParserException, IOException, SmackException {
1048        addExtensionElement(collection, parser, elementName, namespace);
1049    }
1050
1051
1052    public static void addExtensionElement(Stanza packet, XmlPullParser parser)
1053                    throws XmlPullParserException, IOException, SmackException {
1054        ParserUtils.assertAtStartTag(parser);
1055        addExtensionElement(packet, parser, parser.getName(), parser.getNamespace());
1056    }
1057
1058    public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
1059                    String namespace) throws XmlPullParserException, IOException, SmackException {
1060        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1061        packet.addExtension(packetExtension);
1062    }
1063
1064    public static void addExtensionElement(Collection<ExtensionElement> collection,
1065                    XmlPullParser parser) throws XmlPullParserException, IOException,
1066                    SmackException {
1067        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1068    }
1069
1070    public static void addExtensionElement(Collection<ExtensionElement> collection,
1071                    XmlPullParser parser, String elementName, String namespace)
1072                    throws XmlPullParserException, IOException, SmackException {
1073        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1074        collection.add(packetExtension);
1075    }
1076}