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