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.escape(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                CharSequence text = parser.getText();
501                if (event == XmlPullParser.TEXT) {
502                    // TODO the toString() can be removed in Smack 4.2.
503                    text = StringUtils.escapeForXML(text.toString());
504                }
505                sb.append(text);
506            }
507            if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
508                break outerloop;
509            }
510            event = parser.next();
511        }
512        return sb;
513    }
514
515    /**
516     * Parses a presence packet.
517     *
518     * @param parser the XML parser, positioned at the start of a presence packet.
519     * @return a Presence packet.
520     * @throws IOException 
521     * @throws XmlPullParserException 
522     * @throws SmackException 
523     */
524    public static Presence parsePresence(XmlPullParser parser)
525                    throws XmlPullParserException, IOException, SmackException {
526        ParserUtils.assertAtStartTag(parser);
527        final int initialDepth = parser.getDepth();
528
529        Presence.Type type = Presence.Type.available;
530        String typeString = parser.getAttributeValue("", "type");
531        if (typeString != null && !typeString.equals("")) {
532            type = Presence.Type.fromString(typeString);
533        }
534        Presence presence = new Presence(type);
535        presence.setTo(parser.getAttributeValue("", "to"));
536        presence.setFrom(parser.getAttributeValue("", "from"));
537        presence.setStanzaId(parser.getAttributeValue("", "id"));
538
539        String language = getLanguageAttribute(parser);
540        if (language != null && !"".equals(language.trim())) {
541                presence.setLanguage(language);
542        }
543
544        // Parse sub-elements
545        outerloop: while (true) {
546            int eventType = parser.next();
547            switch (eventType) {
548            case XmlPullParser.START_TAG:
549                String elementName = parser.getName();
550                String namespace = parser.getNamespace();
551                switch(elementName) {
552                case "status":
553                    presence.setStatus(parser.nextText());
554                    break;
555                case "priority":
556                    int priority = Integer.parseInt(parser.nextText());
557                    presence.setPriority(priority);
558                    break;
559                case "show":
560                    String modeText = parser.nextText();
561                    if (StringUtils.isNotEmpty(modeText)) {
562                        presence.setMode(Presence.Mode.fromString(modeText));
563                    } else {
564                        // Some implementations send presence stanzas with a
565                        // '<show />' element, which is a invalid XMPP presence
566                        // stanza according to RFC 6121 4.7.2.1
567                        LOGGER.warning("Empty or null mode text in presence show element form "
568                                        + presence.getFrom()
569                                        + " with id '"
570                                        + presence.getStanzaId()
571                                        + "' which is invalid according to RFC6121 4.7.2.1");
572                    }
573                    break;
574                case "error":
575                    presence.setError(parseError(parser));
576                    break;
577                default:
578                // Otherwise, it must be a packet extension.
579                    // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
580                    // failing completely here. See SMACK-390 for more information.
581                    try {
582                        PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace);
583                    } catch (Exception e) {
584                        LOGGER.log(Level.WARNING,
585                                        "Failed to parse extension packet in Presence packet. Attributes: from="
586                                                        + presence.getFrom() + " id=" + presence.getStanzaId(), e);
587                    }
588                    break;
589                }
590                break;
591            case XmlPullParser.END_TAG:
592                if (parser.getDepth() == initialDepth) {
593                    break outerloop;
594                }
595                break;
596            }
597        }
598        return presence;
599    }
600
601    /**
602     * Parses an IQ packet.
603     *
604     * @param parser the XML parser, positioned at the start of an IQ packet.
605     * @return an IQ object.
606     * @throws XmlPullParserException 
607     * @throws IOException 
608     * @throws SmackException 
609     */
610    public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException {
611        ParserUtils.assertAtStartTag(parser);
612        final int initialDepth = parser.getDepth();
613        IQ iqPacket = null;
614        XMPPError error = null;
615
616        final String id = parser.getAttributeValue("", "id");
617        final String to = parser.getAttributeValue("", "to");
618        final String from = parser.getAttributeValue("", "from");
619        final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
620
621        outerloop: while (true) {
622            int eventType = parser.next();
623
624            switch (eventType) {
625            case XmlPullParser.START_TAG:
626                String elementName = parser.getName();
627                String namespace = parser.getNamespace();
628                switch(elementName) {
629                case "error":
630                    error = PacketParserUtils.parseError(parser);
631                    break;
632                // Otherwise, see if there is a registered provider for
633                // this element name and namespace.
634                default:
635                    IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
636                    if (provider != null) {
637                            iqPacket = provider.parse(parser);
638                    }
639                    // Note that if we reach this code, it is guranteed that the result IQ contained a child element
640                    // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first.
641                    else {
642                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
643                        // so that the content of the IQ can be examined later on
644                        iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
645                    }
646                    break;
647                }
648                break;
649            case XmlPullParser.END_TAG:
650                if (parser.getDepth() == initialDepth) {
651                    break outerloop;
652                }
653                break;
654            }
655        }
656        // Decide what to do when an IQ packet was not understood
657        if (iqPacket == null) {
658            switch (type) {
659            case error:
660                // If an IQ packet wasn't created above, create an empty error IQ packet.
661                iqPacket = new ErrorIQ(error);
662                break;
663            case result:
664                iqPacket = new EmptyResultIQ();
665                break;
666            default:
667                break;
668            }
669        }
670
671        // Set basic values on the iq packet.
672        iqPacket.setStanzaId(id);
673        iqPacket.setTo(to);
674        iqPacket.setFrom(from);
675        iqPacket.setType(type);
676        iqPacket.setError(error);
677
678        return iqPacket;
679    }
680
681    /**
682     * Parse the available SASL mechanisms reported from the server.
683     *
684     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
685     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
686     * @throws IOException 
687     * @throws XmlPullParserException 
688     */
689    public static Collection<String> parseMechanisms(XmlPullParser parser)
690                    throws XmlPullParserException, IOException {
691        List<String> mechanisms = new ArrayList<String>();
692        boolean done = false;
693        while (!done) {
694            int eventType = parser.next();
695
696            if (eventType == XmlPullParser.START_TAG) {
697                String elementName = parser.getName();
698                if (elementName.equals("mechanism")) {
699                    mechanisms.add(parser.nextText());
700                }
701            }
702            else if (eventType == XmlPullParser.END_TAG) {
703                if (parser.getName().equals("mechanisms")) {
704                    done = true;
705                }
706            }
707        }
708        return mechanisms;
709    }
710
711    /**
712     * Parse the Compression Feature reported from the server.
713     *
714     * @param parser the XML parser, positioned at the start of the compression stanza.
715     * @return The CompressionFeature stream element
716     * @throws XmlPullParserException if an exception occurs while parsing the stanza.
717     */
718    public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
719                    throws IOException, XmlPullParserException {
720        assert (parser.getEventType() == XmlPullParser.START_TAG);
721        String name;
722        final int initialDepth = parser.getDepth();
723        List<String> methods = new LinkedList<String>();
724        outerloop: while (true) {
725            int eventType = parser.next();
726            switch (eventType) {
727            case XmlPullParser.START_TAG:
728                name = parser.getName();
729                switch (name) {
730                case "method":
731                    methods.add(parser.nextText());
732                    break;
733                }
734                break;
735            case XmlPullParser.END_TAG:
736                name = parser.getName();
737                switch (name) {
738                case Compress.Feature.ELEMENT:
739                    if (parser.getDepth() == initialDepth) {
740                        break outerloop;
741                    }
742                }
743            }
744        }
745        assert (parser.getEventType() == XmlPullParser.END_TAG);
746        assert (parser.getDepth() == initialDepth);
747        return new Compress.Feature(methods);
748    }
749
750    public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
751                    throws XmlPullParserException, IOException {
752        if (descriptiveTexts == null) {
753            descriptiveTexts = new HashMap<String, String>();
754        }
755        String xmllang = getLanguageAttribute(parser);
756        String text = parser.nextText();
757        String previousValue = descriptiveTexts.put(xmllang, text);
758        assert (previousValue == null);
759        return descriptiveTexts;
760    }
761
762    /**
763     * Parses SASL authentication error packets.
764     * 
765     * @param parser the XML parser.
766     * @return a SASL Failure packet.
767     * @throws IOException 
768     * @throws XmlPullParserException 
769     */
770    public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
771        final int initialDepth = parser.getDepth();
772        String condition = null;
773        Map<String, String> descriptiveTexts = null;
774        outerloop: while (true) {
775            int eventType = parser.next();
776            switch (eventType) {
777            case XmlPullParser.START_TAG:
778                String name = parser.getName();
779                if (name.equals("text")) {
780                    descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
781                }
782                else {
783                    assert(condition == null);
784                    condition = parser.getName();
785                }
786                break;
787            case XmlPullParser.END_TAG:
788                if (parser.getDepth() == initialDepth) {
789                    break outerloop;
790                }
791                break;
792            }
793        }
794        return new SASLFailure(condition, descriptiveTexts);
795    }
796
797    /**
798     * Parses stream error packets.
799     *
800     * @param parser the XML parser.
801     * @return an stream error packet.
802     * @throws XmlPullParserException if an exception occurs while parsing the packet.
803     * @throws SmackException 
804     */
805    public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException,
806                    SmackException {
807        final int initialDepth = parser.getDepth();
808        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
809        Map<String, String> descriptiveTexts = null;
810        StreamError.Condition condition = null;
811        String conditionText = null;
812        outerloop: while (true) {
813            int eventType = parser.next();
814            switch (eventType) {
815            case XmlPullParser.START_TAG:
816                String name = parser.getName();
817                String namespace = parser.getNamespace();
818                switch (namespace) {
819                case StreamError.NAMESPACE:
820                    switch (name) {
821                    case "text":
822                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
823                        break;
824                    default:
825                        // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
826                        // then it has to be the stream error code
827                        condition = StreamError.Condition.fromString(name);
828                        if (!parser.isEmptyElementTag()) {
829                            conditionText = parser.nextText();
830                        }
831                        break;
832                    }
833                    break;
834                default:
835                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
836                    break;
837                }
838                break;
839            case XmlPullParser.END_TAG:
840                if (parser.getDepth() == initialDepth) {
841                    break outerloop;
842                }
843                break;
844            }
845        }
846        return new StreamError(condition, conditionText, descriptiveTexts, extensions);
847    }
848
849    /**
850     * Parses error sub-packets.
851     *
852     * @param parser the XML parser.
853     * @return an error sub-packet.
854     * @throws IOException 
855     * @throws XmlPullParserException 
856     * @throws SmackException 
857     */
858    public static XMPPError parseError(XmlPullParser parser)
859                    throws XmlPullParserException, IOException, SmackException {
860        final int initialDepth = parser.getDepth();
861        Map<String, String> descriptiveTexts = null;
862        XMPPError.Condition condition = null;
863        String conditionText = null;
864        List<ExtensionElement> extensions = new ArrayList<ExtensionElement>();
865
866        // Parse the error header
867        XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type"));
868        String errorGenerator = parser.getAttributeValue("", "by");
869
870        outerloop: while (true) {
871            int eventType = parser.next();
872            switch (eventType) {
873            case XmlPullParser.START_TAG:
874                String name = parser.getName();
875                String namespace = parser.getNamespace();
876                switch (namespace) {
877                case XMPPError.NAMESPACE:
878                    switch (name) {
879                    case Stanza.TEXT:
880                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
881                        break;
882                    default:
883                        condition = XMPPError.Condition.fromString(name);
884                        if (!parser.isEmptyElementTag()) {
885                            conditionText = parser.nextText();
886                        }
887                        break;
888                    }
889                    break;
890                default:
891                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace);
892                }
893                break;
894            case XmlPullParser.END_TAG:
895                if (parser.getDepth() == initialDepth) {
896                    break outerloop;
897                }
898            }
899        }
900        return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions);
901    }
902
903    /**
904     * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead.
905     */
906    @Deprecated
907    public static ExtensionElement parsePacketExtension(String elementName, String namespace,
908                    XmlPullParser parser) throws XmlPullParserException,
909                    IOException, SmackException {
910        return parseExtensionElement(elementName, namespace, parser);
911    }
912 
913    /**
914     * Parses an extension element.
915     *
916     * @param elementName the XML element name of the extension element.
917     * @param namespace the XML namespace of the stanza(/packet) extension.
918     * @param parser the XML parser, positioned at the starting element of the extension.
919     * @return an extension element.
920     */
921    public static ExtensionElement parseExtensionElement(String elementName, String namespace,
922                    XmlPullParser parser) throws XmlPullParserException,
923                    IOException, SmackException {
924        ParserUtils.assertAtStartTag(parser);
925        // See if a provider is registered to handle the extension.
926        ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
927        if (provider != null) {
928                return provider.parse(parser);
929        }
930
931        final int initialDepth = parser.getDepth();
932        // No providers registered, so use a default extension.
933        DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace);
934        outerloop: while (true) {
935            int eventType = parser.next();
936            switch (eventType) {
937            case XmlPullParser.START_TAG:
938                String name = parser.getName();
939                // If an empty element, set the value with the empty string.
940                if (parser.isEmptyElementTag()) {
941                    extension.setValue(name,"");
942                }
943                // Otherwise, get the the element text.
944                else {
945                    eventType = parser.next();
946                    if (eventType == XmlPullParser.TEXT) {
947                        String value = parser.getText();
948                        extension.setValue(name, value);
949                    }
950                }
951                break;
952            case XmlPullParser.END_TAG:
953                if (parser.getDepth() == initialDepth) {
954                    break outerloop;
955                }
956            }
957        }
958        return extension;
959    }
960
961    public static StartTls parseStartTlsFeature(XmlPullParser parser)
962                    throws XmlPullParserException, IOException {
963        assert (parser.getEventType() == XmlPullParser.START_TAG);
964        assert (parser.getNamespace().equals(StartTls.NAMESPACE));
965        int initalDepth = parser.getDepth();
966        boolean required = false;
967        outerloop: while (true) {
968            int event = parser.next();
969            switch (event) {
970            case XmlPullParser.START_TAG:
971                String name = parser.getName();
972                switch (name) {
973                case "required":
974                    required = true;
975                    break;
976                }
977                break;
978            case XmlPullParser.END_TAG:
979                if (parser.getDepth() == initalDepth) {
980                    break outerloop;
981                }
982            }
983        }
984        assert(parser.getEventType() == XmlPullParser.END_TAG);
985        return new StartTls(required);
986    }
987
988    public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
989        ParserUtils.assertAtStartTag(parser);
990        final int initialDepth = parser.getDepth();
991        boolean optional = false;
992        if (!parser.isEmptyElementTag()) {
993        outerloop: while(true) {
994            int event = parser.next();
995            switch (event) {
996            case XmlPullParser.START_TAG:
997                String name = parser.getName();
998                switch (name) {
999                    case Session.Feature.OPTIONAL_ELEMENT:
1000                        optional = true;
1001                        break;
1002                }
1003                break;
1004            case XmlPullParser.END_TAG:
1005                if (parser.getDepth() == initialDepth) {
1006                    break outerloop;
1007                }
1008            }
1009        }
1010        }
1011        return new Session.Feature(optional);
1012
1013    }
1014    private static String getLanguageAttribute(XmlPullParser parser) {
1015        for (int i = 0; i < parser.getAttributeCount(); i++) {
1016            String attributeName = parser.getAttributeName(i);
1017            if ( "xml:lang".equals(attributeName) ||
1018                    ("lang".equals(attributeName) &&
1019                            "xml".equals(parser.getAttributePrefix(i)))) {
1020                        return parser.getAttributeValue(i);
1021                }
1022        }
1023        return null;
1024    }
1025
1026    @Deprecated
1027    public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException,
1028                    IOException, SmackException {
1029        addExtensionElement(packet, parser);
1030    }
1031
1032    @Deprecated
1033    public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace)
1034                    throws XmlPullParserException, IOException, SmackException {
1035        addExtensionElement(packet, parser, elementName, namespace);
1036    }
1037
1038    @Deprecated
1039    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser)
1040                    throws XmlPullParserException, IOException, SmackException {
1041        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1042    }
1043
1044    @Deprecated
1045    public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser,
1046                    String elementName, String namespace) throws XmlPullParserException, IOException, SmackException {
1047        addExtensionElement(collection, parser, elementName, namespace);
1048    }
1049
1050
1051    public static void addExtensionElement(Stanza packet, XmlPullParser parser)
1052                    throws XmlPullParserException, IOException, SmackException {
1053        ParserUtils.assertAtStartTag(parser);
1054        addExtensionElement(packet, parser, parser.getName(), parser.getNamespace());
1055    }
1056
1057    public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
1058                    String namespace) throws XmlPullParserException, IOException, SmackException {
1059        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1060        packet.addExtension(packetExtension);
1061    }
1062
1063    public static void addExtensionElement(Collection<ExtensionElement> collection,
1064                    XmlPullParser parser) throws XmlPullParserException, IOException,
1065                    SmackException {
1066        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace());
1067    }
1068
1069    public static void addExtensionElement(Collection<ExtensionElement> collection,
1070                    XmlPullParser parser, String elementName, String namespace)
1071                    throws XmlPullParserException, IOException, SmackException {
1072        ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser);
1073        collection.add(packetExtension);
1074    }
1075}