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.util.ArrayList;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026import java.util.logging.Level;
027import java.util.logging.Logger;
028
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.packet.Bind;
031import org.jivesoftware.smack.packet.DefaultPacketExtension;
032import org.jivesoftware.smack.packet.IQ;
033import org.jivesoftware.smack.packet.Message;
034import org.jivesoftware.smack.packet.Packet;
035import org.jivesoftware.smack.packet.PacketExtension;
036import org.jivesoftware.smack.packet.Presence;
037import org.jivesoftware.smack.packet.Registration;
038import org.jivesoftware.smack.packet.RosterPacket;
039import org.jivesoftware.smack.packet.StreamError;
040import org.jivesoftware.smack.packet.XMPPError;
041import org.jivesoftware.smack.provider.IQProvider;
042import org.jivesoftware.smack.provider.PacketExtensionProvider;
043import org.jivesoftware.smack.provider.ProviderManager;
044import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
045import org.xmlpull.v1.XmlPullParser;
046import org.xmlpull.v1.XmlPullParserException;
047import org.xmlpull.v1.XmlPullParserFactory;
048
049/**
050 * Utility class that helps to parse packets. Any parsing packets method that must be shared
051 * between many clients must be placed in this utility class.
052 *
053 * @author Gaston Dombiak
054 */
055public class PacketParserUtils {
056    private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());
057
058    /**
059     * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
060     * FEATURE_PROCESS_NAMESPACES is enabled.
061     * <p>
062     * Note that not all XmlPullParser implementations will return a String on
063     * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
064     * behavior when using the parser.
065     * </p>
066     * 
067     * @return A suitable XmlPullParser for XMPP parsing
068     * @throws XmlPullParserException
069     */
070    public static XmlPullParser newXmppParser() throws XmlPullParserException {
071        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
072        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
073        return parser;
074    }
075
076    /**
077     * Parses a message packet.
078     *
079     * @param parser the XML parser, positioned at the start of a message packet.
080     * @return a Message packet.
081     * @throws Exception if an exception occurs while parsing the packet.
082     */
083    public static Message parseMessage(XmlPullParser parser) throws Exception {
084        Message message = new Message();
085        String id = parser.getAttributeValue("", "id");
086        message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
087        message.setTo(parser.getAttributeValue("", "to"));
088        message.setFrom(parser.getAttributeValue("", "from"));
089        message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
090        String language = getLanguageAttribute(parser);
091        
092        // determine message's default language
093        String defaultLanguage = null;
094        if (language != null && !"".equals(language.trim())) {
095            message.setLanguage(language);
096            defaultLanguage = language;
097        } 
098        else {
099            defaultLanguage = Packet.getDefaultLanguage();
100        }
101
102        // Parse sub-elements. We include extra logic to make sure the values
103        // are only read once. This is because it's possible for the names to appear
104        // in arbitrary sub-elements.
105        boolean done = false;
106        String thread = null;
107        while (!done) {
108            int eventType = parser.next();
109            if (eventType == XmlPullParser.START_TAG) {
110                String elementName = parser.getName();
111                String namespace = parser.getNamespace();
112                if (elementName.equals("subject")) {
113                    String xmlLang = getLanguageAttribute(parser);
114                    if (xmlLang == null) {
115                        xmlLang = defaultLanguage;
116                    }
117
118                    String subject = parseElementText(parser);
119
120                    if (message.getSubject(xmlLang) == null) {
121                        message.addSubject(xmlLang, subject);
122                    }
123                }
124                else if (elementName.equals("body")) {
125                    String xmlLang = getLanguageAttribute(parser);
126                    if (xmlLang == null) {
127                        xmlLang = defaultLanguage;
128                    }
129
130                    String body = parseElementText(parser);
131
132                    if (message.getBody(xmlLang) == null) {
133                        message.addBody(xmlLang, body);
134                    }
135                }
136                else if (elementName.equals("thread")) {
137                    if (thread == null) {
138                        thread = parser.nextText();
139                    }
140                }
141                else if (elementName.equals("error")) {
142                    message.setError(parseError(parser));
143                }
144                // Otherwise, it must be a packet extension.
145                else {
146                    message.addExtension(
147                    PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
148                }
149            }
150            else if (eventType == XmlPullParser.END_TAG) {
151                if (parser.getName().equals("message")) {
152                    done = true;
153                }
154            }
155        }
156
157        message.setThread(thread);
158        return message;
159    }
160
161    /**
162     * Returns the textual content of an element as String.
163     * <p>
164     * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
165     * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
166     * </p>
167     * This method is used for the parts where the XMPP specification requires elements that contain
168     * only text or are the empty element.
169     * 
170     * @param parser
171     * @return the textual content of the element as String
172     * @throws XmlPullParserException
173     * @throws IOException
174     */
175    public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
176        assert (parser.getEventType() == XmlPullParser.START_TAG);
177        String res;
178        if (parser.isEmptyElementTag()) {
179            res = "";
180        }
181        else {
182            // Advance to the text of the Element
183            int event = parser.next();
184            if (event != XmlPullParser.TEXT) {
185                throw new XmlPullParserException(
186                                "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
187            }
188            res = parser.getText();
189            event = parser.next();
190            if (event != XmlPullParser.END_TAG) {
191                throw new XmlPullParserException(
192                                "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
193            }
194        }
195        return res;
196    }
197
198    /**
199     * Returns the current element as string.
200     * <p>
201     * The parser must be positioned on START_TAG.
202     * </p>
203     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
204     *
205     * @param parser the XML pull parser
206     * @return the element as string
207     * @throws XmlPullParserException
208     * @throws IOException
209     */
210    public static String parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
211        assert(parser.getEventType() == XmlPullParser.START_TAG);
212        return parseContentDepth(parser, parser.getDepth());
213    }
214
215    /**
216     * Returns the content of a element as string.
217     * <p>
218     * The parser must be positioned on the START_TAG of the element which content is going to get
219     * returned. If the current element is the empty element, then the empty string is returned. If
220     * it is a element which contains just text, then just the text is returned. If it contains
221     * nested elements (and text), then everything from the current opening tag to the corresponding
222     * closing tag of the same depth is returned as String.
223     * </p>
224     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
225     * 
226     * @param parser the XML pull parser
227     * @return the content of a tag as string
228     * @throws XmlPullParserException if parser encounters invalid XML
229     * @throws IOException if an IO error occurs
230     */
231    public static String parseContent(XmlPullParser parser)
232                    throws XmlPullParserException, IOException {
233        assert(parser.getEventType() == XmlPullParser.START_TAG);
234        if (parser.isEmptyElementTag()) {
235            return "";
236        }
237        // Advance the parser, since we want to parse the content of the current element
238        parser.next();
239        return parseContentDepth(parser, parser.getDepth());
240    }
241
242    /**
243     * Returns the content from the current position of the parser up to the closing tag of the
244     * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
245     * not nested ones.
246     * <p>
247     * This method is able to parse the content with MX- and KXmlParser. In order to achieve
248     * this some trade-off has to be make, because KXmlParser does not support xml-roundtrip (ie.
249     * return a String on getText() on START_TAG and END_TAG). We are therefore required to work
250     * around this limitation, which results in only partial support for XML namespaces ("xmlns"):
251     * Only the outermost namespace of elements will be included in the resulting String.
252     * </p>
253     * 
254     * @param parser
255     * @param depth
256     * @return the content of the current depth
257     * @throws XmlPullParserException
258     * @throws IOException
259     */
260    public static String parseContentDepth(XmlPullParser parser, int depth) throws XmlPullParserException, IOException {
261        XmlStringBuilder xml = new XmlStringBuilder();
262        int event = parser.getEventType();
263        boolean isEmptyElement = false;
264        // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
265        // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
266        // in a nested element. It's an ugly workaround that has the potential to break things.
267        String namespaceElement = null;;
268        while (true) {
269            if (event == XmlPullParser.START_TAG) {
270                xml.halfOpenElement(parser.getName());
271                if (namespaceElement == null) {
272                    String namespace = parser.getNamespace();
273                    if (StringUtils.isNotEmpty(namespace)) {
274                        xml.attribute("xmlns", namespace);
275                        namespaceElement = parser.getName();
276                    }
277                }
278                for (int i = 0; i < parser.getAttributeCount(); i++) {
279                    xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
280                }
281                if (parser.isEmptyElementTag()) {
282                    xml.closeEmptyElement();
283                    isEmptyElement = true;
284                }
285                else {
286                    xml.rightAngelBracket();
287                }
288            }
289            else if (event == XmlPullParser.END_TAG) {
290                if (isEmptyElement) {
291                    // Do nothing as the element was already closed, just reset the flag
292                    isEmptyElement = false;
293                }
294                else {
295                    xml.closeElement(parser.getName());
296                }
297                if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
298                    // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
299                    namespaceElement = null;
300                }
301                if (parser.getDepth() <= depth) {
302                    // Abort parsing, we are done
303                    break;
304                }
305            }
306            else if (event == XmlPullParser.TEXT) {
307                xml.append(parser.getText());
308            }
309            event = parser.next();
310        }
311        return xml.toString();
312    }
313
314    /**
315     * Parses a presence packet.
316     *
317     * @param parser the XML parser, positioned at the start of a presence packet.
318     * @return a Presence packet.
319     * @throws Exception if an exception occurs while parsing the packet.
320     */
321    public static Presence parsePresence(XmlPullParser parser) throws Exception {
322        Presence.Type type = Presence.Type.available;
323        String typeString = parser.getAttributeValue("", "type");
324        if (typeString != null && !typeString.equals("")) {
325            try {
326                type = Presence.Type.valueOf(typeString);
327            }
328            catch (IllegalArgumentException iae) {
329                LOGGER.warning("Found invalid presence type " + typeString);
330            }
331        }
332        Presence presence = new Presence(type);
333        presence.setTo(parser.getAttributeValue("", "to"));
334        presence.setFrom(parser.getAttributeValue("", "from"));
335        String id = parser.getAttributeValue("", "id");
336        presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
337
338        String language = getLanguageAttribute(parser);
339        if (language != null && !"".equals(language.trim())) {
340                presence.setLanguage(language);
341        }
342        presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
343
344        // Parse sub-elements
345        boolean done = false;
346        while (!done) {
347            int eventType = parser.next();
348            if (eventType == XmlPullParser.START_TAG) {
349                String elementName = parser.getName();
350                String namespace = parser.getNamespace();
351                if (elementName.equals("status")) {
352                    presence.setStatus(parser.nextText());
353                }
354                else if (elementName.equals("priority")) {
355                    try {
356                        int priority = Integer.parseInt(parser.nextText());
357                        presence.setPriority(priority);
358                    }
359                    catch (NumberFormatException nfe) {
360                        // Ignore.
361                    }
362                    catch (IllegalArgumentException iae) {
363                        // Presence priority is out of range so assume priority to be zero
364                        presence.setPriority(0);
365                    }
366                }
367                else if (elementName.equals("show")) {
368                    String modeText = parser.nextText();
369                    try {
370                        presence.setMode(Presence.Mode.valueOf(modeText));
371                    }
372                    catch (IllegalArgumentException iae) {
373                        LOGGER.warning("Found invalid presence mode " + modeText);
374                    }
375                }
376                else if (elementName.equals("error")) {
377                    presence.setError(parseError(parser));
378                }
379                // Otherwise, it must be a packet extension.
380                else {
381                        try {
382                        presence.addExtension(PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
383                        }
384                        catch (Exception e) {
385                                LOGGER.warning("Failed to parse extension packet in Presence packet.");
386                        }
387                }
388            }
389            else if (eventType == XmlPullParser.END_TAG) {
390                if (parser.getName().equals("presence")) {
391                    done = true;
392                }
393            }
394        }
395        return presence;
396    }
397
398    /**
399     * Parses an IQ packet.
400     *
401     * @param parser the XML parser, positioned at the start of an IQ packet.
402     * @return an IQ object.
403     * @throws Exception if an exception occurs while parsing the packet.
404     */
405    public static IQ parseIQ(XmlPullParser parser, XMPPConnection connection) throws Exception {
406        IQ iqPacket = null;
407        XMPPError error = null;
408
409        final String id = parser.getAttributeValue("", "id");
410        final String to = parser.getAttributeValue("", "to");
411        final String from = parser.getAttributeValue("", "from");
412        final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
413
414        boolean done = false;
415        while (!done) {
416            int eventType = parser.next();
417
418            if (eventType == XmlPullParser.START_TAG) {
419                String elementName = parser.getName();
420                String namespace = parser.getNamespace();
421                if (elementName.equals("error")) {
422                    error = PacketParserUtils.parseError(parser);
423                }
424                else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) {
425                    iqPacket = parseRoster(parser);
426                }
427                else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
428                    iqPacket = parseRegistration(parser);
429                }
430                else if (elementName.equals("bind") &&
431                        namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
432                    iqPacket = parseResourceBinding(parser);
433                }
434                // Otherwise, see if there is a registered provider for
435                // this element name and namespace.
436                else {
437                    Object provider = ProviderManager.getIQProvider(elementName, namespace);
438                    if (provider != null) {
439                        if (provider instanceof IQProvider) {
440                            iqPacket = ((IQProvider)provider).parseIQ(parser);
441                        }
442                        else if (provider instanceof Class) {
443                            iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
444                                    (Class<?>)provider, parser);
445                        }
446                    }
447                    // Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood
448                    // have to be answered with an IQ error response. See the code a few lines below
449                    else if (IQ.Type.RESULT == type){
450                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
451                        // so that the content of the IQ can be examined later on
452                        iqPacket = new UnparsedResultIQ(parseContent(parser));
453                    }
454                }
455            }
456            else if (eventType == XmlPullParser.END_TAG) {
457                if (parser.getName().equals("iq")) {
458                    done = true;
459                }
460            }
461        }
462        // Decide what to do when an IQ packet was not understood
463        if (iqPacket == null) {
464            if (IQ.Type.GET == type || IQ.Type.SET == type ) {
465                // If the IQ stanza is of type "get" or "set" containing a child element qualified
466                // by a namespace with no registered Smack provider, then answer an IQ of type
467                // "error" with code 501 ("feature-not-implemented")
468                iqPacket = new IQ() {
469                    @Override
470                    public String getChildElementXML() {
471                        return null;
472                    }
473                };
474                iqPacket.setPacketID(id);
475                iqPacket.setTo(from);
476                iqPacket.setFrom(to);
477                iqPacket.setType(IQ.Type.ERROR);
478                iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented));
479                connection.sendPacket(iqPacket);
480                return null;
481            }
482            else {
483                // If an IQ packet wasn't created above, create an empty IQ packet.
484                iqPacket = new IQ() {
485                    @Override
486                    public String getChildElementXML() {
487                        return null;
488                    }
489                };
490            }
491        }
492
493        // Set basic values on the iq packet.
494        iqPacket.setPacketID(id);
495        iqPacket.setTo(to);
496        iqPacket.setFrom(from);
497        iqPacket.setType(type);
498        iqPacket.setError(error);
499
500        return iqPacket;
501    }
502
503    private static RosterPacket parseRoster(XmlPullParser parser) throws Exception {
504        RosterPacket roster = new RosterPacket();
505        boolean done = false;
506        RosterPacket.Item item = null;
507
508        String version = parser.getAttributeValue("", "ver");
509        roster.setVersion(version);
510
511        while (!done) {
512            int eventType = parser.next();
513            if (eventType == XmlPullParser.START_TAG) {
514                if (parser.getName().equals("item")) {
515                    String jid = parser.getAttributeValue("", "jid");
516                    String name = parser.getAttributeValue("", "name");
517                    // Create packet.
518                    item = new RosterPacket.Item(jid, name);
519                    // Set status.
520                    String ask = parser.getAttributeValue("", "ask");
521                    RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask);
522                    item.setItemStatus(status);
523                    // Set type.
524                    String subscription = parser.getAttributeValue("", "subscription");
525                    RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
526                    item.setItemType(type);
527                }
528                else if (parser.getName().equals("group") && item!= null) {
529                    final String groupName = parser.nextText();
530                    if (groupName != null && groupName.trim().length() > 0) {
531                        item.addGroupName(groupName);
532                    }
533                }
534            }
535            else if (eventType == XmlPullParser.END_TAG) {
536                if (parser.getName().equals("item")) {
537                    roster.addRosterItem(item);
538                }
539                if (parser.getName().equals("query")) {
540                    done = true;
541                }
542            }
543        }
544        return roster;
545    }
546
547     private static Registration parseRegistration(XmlPullParser parser) throws Exception {
548        Registration registration = new Registration();
549        Map<String, String> fields = null;
550        boolean done = false;
551        while (!done) {
552            int eventType = parser.next();
553            if (eventType == XmlPullParser.START_TAG) {
554                // Any element that's in the jabber:iq:register namespace,
555                // attempt to parse it if it's in the form <name>value</name>.
556                if (parser.getNamespace().equals("jabber:iq:register")) {
557                    String name = parser.getName();
558                    String value = "";
559                    if (fields == null) {
560                        fields = new HashMap<String, String>();
561                    }
562
563                    if (parser.next() == XmlPullParser.TEXT) {
564                        value = parser.getText();
565                    }
566                    // Ignore instructions, but anything else should be added to the map.
567                    if (!name.equals("instructions")) {
568                        fields.put(name, value);
569                    }
570                    else {
571                        registration.setInstructions(value);
572                    }
573                }
574                // Otherwise, it must be a packet extension.
575                else {
576                    registration.addExtension(
577                        PacketParserUtils.parsePacketExtension(
578                            parser.getName(),
579                            parser.getNamespace(),
580                            parser));
581                }
582            }
583            else if (eventType == XmlPullParser.END_TAG) {
584                if (parser.getName().equals("query")) {
585                    done = true;
586                }
587            }
588        }
589        registration.setAttributes(fields);
590        return registration;
591    }
592
593    private static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
594            XmlPullParserException {
595        Bind bind = new Bind();
596        boolean done = false;
597        while (!done) {
598            int eventType = parser.next();
599            if (eventType == XmlPullParser.START_TAG) {
600                if (parser.getName().equals("resource")) {
601                    bind.setResource(parser.nextText());
602                }
603                else if (parser.getName().equals("jid")) {
604                    bind.setJid(parser.nextText());
605                }
606            } else if (eventType == XmlPullParser.END_TAG) {
607                if (parser.getName().equals("bind")) {
608                    done = true;
609                }
610            }
611        }
612
613        return bind;
614    }
615
616    /**
617     * Parse the available SASL mechanisms reported from the server.
618     *
619     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
620     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
621     * @throws Exception if an exception occurs while parsing the stanza.
622     */
623    public static Collection<String> parseMechanisms(XmlPullParser parser) throws Exception {
624        List<String> mechanisms = new ArrayList<String>();
625        boolean done = false;
626        while (!done) {
627            int eventType = parser.next();
628
629            if (eventType == XmlPullParser.START_TAG) {
630                String elementName = parser.getName();
631                if (elementName.equals("mechanism")) {
632                    mechanisms.add(parser.nextText());
633                }
634            }
635            else if (eventType == XmlPullParser.END_TAG) {
636                if (parser.getName().equals("mechanisms")) {
637                    done = true;
638                }
639            }
640        }
641        return mechanisms;
642    }
643
644    /**
645     * Parse the available compression methods reported from the server.
646     *
647     * @param parser the XML parser, positioned at the start of the compression stanza.
648     * @return a collection of Stings with the methods included in the compression stanza.
649     * @throws XmlPullParserException if an exception occurs while parsing the stanza.
650     */
651    public static Collection<String> parseCompressionMethods(XmlPullParser parser)
652            throws IOException, XmlPullParserException {
653        List<String> methods = new ArrayList<String>();
654        boolean done = false;
655        while (!done) {
656            int eventType = parser.next();
657
658            if (eventType == XmlPullParser.START_TAG) {
659                String elementName = parser.getName();
660                if (elementName.equals("method")) {
661                    methods.add(parser.nextText());
662                }
663            }
664            else if (eventType == XmlPullParser.END_TAG) {
665                if (parser.getName().equals("compression")) {
666                    done = true;
667                }
668            }
669        }
670        return methods;
671    }
672
673    /**
674     * Parses SASL authentication error packets.
675     * 
676     * @param parser the XML parser.
677     * @return a SASL Failure packet.
678     * @throws Exception if an exception occurs while parsing the packet.
679     */
680    public static SASLFailure parseSASLFailure(XmlPullParser parser) throws Exception {
681        String condition = null;
682        boolean done = false;
683        while (!done) {
684            int eventType = parser.next();
685
686            if (eventType == XmlPullParser.START_TAG) {
687                if (!parser.getName().equals("failure")) {
688                    condition = parser.getName();
689                }
690            }
691            else if (eventType == XmlPullParser.END_TAG) {
692                if (parser.getName().equals("failure")) {
693                    done = true;
694                }
695            }
696        }
697        return new SASLFailure(condition);
698    }
699
700    /**
701     * Parses stream error packets.
702     *
703     * @param parser the XML parser.
704     * @return an stream error packet.
705     * @throws XmlPullParserException if an exception occurs while parsing the packet.
706     */
707    public static StreamError parseStreamError(XmlPullParser parser) throws IOException,
708            XmlPullParserException {
709    final int depth = parser.getDepth();
710    boolean done = false;
711    String code = null;
712    String text = null;
713    while (!done) {
714        int eventType = parser.next();
715
716        if (eventType == XmlPullParser.START_TAG) {
717            String namespace = parser.getNamespace();
718            if (StreamError.NAMESPACE.equals(namespace)) {
719                String name = parser.getName();
720                if (name.equals("text") && !parser.isEmptyElementTag()) {
721                    parser.next();
722                    text = parser.getText();
723                }
724                else {
725                    // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
726                    // then it has to be the stream error code
727                    code = name;
728                }
729            }
730        }
731        else if (eventType == XmlPullParser.END_TAG && depth == parser.getDepth()) {
732            done = true;
733        }
734    }
735    return new StreamError(code, text);
736}
737
738    /**
739     * Parses error sub-packets.
740     *
741     * @param parser the XML parser.
742     * @return an error sub-packet.
743     * @throws Exception if an exception occurs while parsing the packet.
744     */
745    public static XMPPError parseError(XmlPullParser parser) throws Exception {
746        final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
747        String type = null;
748        String message = null;
749        String condition = null;
750        List<PacketExtension> extensions = new ArrayList<PacketExtension>();
751
752        // Parse the error header
753        for (int i=0; i<parser.getAttributeCount(); i++) {
754            if (parser.getAttributeName(i).equals("type")) {
755                type = parser.getAttributeValue("", "type");
756            }
757        }
758        boolean done = false;
759        // Parse the text and condition tags
760        while (!done) {
761            int eventType = parser.next();
762            if (eventType == XmlPullParser.START_TAG) {
763                if (parser.getName().equals("text")) {
764                    message = parser.nextText();
765                }
766                else {
767                        // Condition tag, it can be xmpp error or an application defined error.
768                    String elementName = parser.getName();
769                    String namespace = parser.getNamespace();
770                    if (errorNamespace.equals(namespace)) {
771                        condition = elementName;
772                    }
773                    else {
774                        extensions.add(parsePacketExtension(elementName, namespace, parser));
775                    }
776                }
777            }
778                else if (eventType == XmlPullParser.END_TAG) {
779                    if (parser.getName().equals("error")) {
780                        done = true;
781                    }
782                }
783        }
784        // Parse the error type.
785        XMPPError.Type errorType = XMPPError.Type.CANCEL;
786        try {
787            if (type != null) {
788                errorType = XMPPError.Type.valueOf(type.toUpperCase(Locale.US));
789            }
790        }
791        catch (IllegalArgumentException iae) {
792            LOGGER.log(Level.SEVERE, "Could not find error type for " + type.toUpperCase(Locale.US), iae);
793        }
794        return new XMPPError(errorType, condition, message, extensions);
795    }
796
797    /**
798     * Parses a packet extension sub-packet.
799     *
800     * @param elementName the XML element name of the packet extension.
801     * @param namespace the XML namespace of the packet extension.
802     * @param parser the XML parser, positioned at the starting element of the extension.
803     * @return a PacketExtension.
804     * @throws Exception if a parsing error occurs.
805     */
806    public static PacketExtension parsePacketExtension(String elementName, String namespace, XmlPullParser parser)
807            throws Exception
808    {
809        // See if a provider is registered to handle the extension.
810        Object provider = ProviderManager.getExtensionProvider(elementName, namespace);
811        if (provider != null) {
812            if (provider instanceof PacketExtensionProvider) {
813                return ((PacketExtensionProvider)provider).parseExtension(parser);
814            }
815            else if (provider instanceof Class) {
816                return (PacketExtension)parseWithIntrospection(
817                        elementName, (Class<?>)provider, parser);
818            }
819        }
820        // No providers registered, so use a default extension.
821        DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
822        boolean done = false;
823        while (!done) {
824            int eventType = parser.next();
825            if (eventType == XmlPullParser.START_TAG) {
826                String name = parser.getName();
827                // If an empty element, set the value with the empty string.
828                if (parser.isEmptyElementTag()) {
829                    extension.setValue(name,"");
830                }
831                // Otherwise, get the the element text.
832                else {
833                    eventType = parser.next();
834                    if (eventType == XmlPullParser.TEXT) {
835                        String value = parser.getText();
836                        extension.setValue(name, value);
837                    }
838                }
839            }
840            else if (eventType == XmlPullParser.END_TAG) {
841                if (parser.getName().equals(elementName)) {
842                    done = true;
843                }
844            }
845        }
846        return extension;
847    }
848
849    private static String getLanguageAttribute(XmlPullParser parser) {
850        for (int i = 0; i < parser.getAttributeCount(); i++) {
851            String attributeName = parser.getAttributeName(i);
852            if ( "xml:lang".equals(attributeName) ||
853                    ("lang".equals(attributeName) &&
854                            "xml".equals(parser.getAttributePrefix(i)))) {
855                        return parser.getAttributeValue(i);
856                }
857        }
858        return null;
859    }
860
861    public static Object parseWithIntrospection(String elementName,
862            Class<?> objectClass, XmlPullParser parser) throws Exception
863    {
864        boolean done = false;
865        Object object = objectClass.newInstance();
866        while (!done) {
867            int eventType = parser.next();
868            if (eventType == XmlPullParser.START_TAG) {
869                String name = parser.getName();
870                String stringValue = parser.nextText();
871                Class<?> propertyType = object.getClass().getMethod(
872                                "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1)).getReturnType();
873                // Get the value of the property by converting it from a
874                // String to the correct object type.
875                Object value = decode(propertyType, stringValue);
876                // Set the value of the bean.
877                object.getClass().getMethod(
878                                "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1),
879                                propertyType).invoke(object, value);
880            }
881            else if (eventType == XmlPullParser.END_TAG) {
882                if (parser.getName().equals(elementName)) {
883                    done = true;
884                }
885            }
886        }
887        return object;
888    }
889
890    /**
891     * Decodes a String into an object of the specified type. If the object
892     * type is not supported, null will be returned.
893     *
894     * @param type the type of the property.
895     * @param value the encode String value to decode.
896     * @return the String value decoded into the specified type.
897     * @throws Exception If decoding failed due to an error.
898     */
899    private static Object decode(Class<?> type, String value) throws Exception {
900        if (type.getName().equals("java.lang.String")) {
901            return value;
902        }
903        if (type.getName().equals("boolean")) {
904            return Boolean.valueOf(value);
905        }
906        if (type.getName().equals("int")) {
907            return Integer.valueOf(value);
908        }
909        if (type.getName().equals("long")) {
910            return Long.valueOf(value);
911        }
912        if (type.getName().equals("float")) {
913            return Float.valueOf(value);
914        }
915        if (type.getName().equals("double")) {
916            return Double.valueOf(value);
917        }
918        if (type.getName().equals("java.lang.Class")) {
919            return Class.forName(value);
920        }
921        return null;
922    }
923
924    /**
925     * This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider
926     * was found for the IQ element.
927     * 
928     * The child elements can be examined with the getChildElementXML() method.
929     *
930     */
931    public static class UnparsedResultIQ extends IQ {
932        public UnparsedResultIQ(String content) {
933            this.str = content;
934        }
935
936        private final String str;
937
938        @Override
939        public String getChildElementXML() {
940            return this.str;
941        }
942    }
943}