Home | History | Annotate | Download | only in util
      1 /**
      2  * $RCSfile$
      3  * $Revision$
      4  * $Date$
      5  *
      6  * Copyright 2003-2007 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smack.util;
     22 
     23 import java.beans.PropertyDescriptor;
     24 import java.io.ByteArrayInputStream;
     25 import java.io.IOException;
     26 import java.io.ObjectInputStream;
     27 import java.util.ArrayList;
     28 import java.util.Collection;
     29 import java.util.HashMap;
     30 import java.util.List;
     31 import java.util.Map;
     32 
     33 import org.jivesoftware.smack.Connection;
     34 import org.jivesoftware.smack.packet.Authentication;
     35 import org.jivesoftware.smack.packet.Bind;
     36 import org.jivesoftware.smack.packet.DefaultPacketExtension;
     37 import org.jivesoftware.smack.packet.IQ;
     38 import org.jivesoftware.smack.packet.Message;
     39 import org.jivesoftware.smack.packet.Packet;
     40 import org.jivesoftware.smack.packet.PacketExtension;
     41 import org.jivesoftware.smack.packet.Presence;
     42 import org.jivesoftware.smack.packet.Registration;
     43 import org.jivesoftware.smack.packet.RosterPacket;
     44 import org.jivesoftware.smack.packet.StreamError;
     45 import org.jivesoftware.smack.packet.XMPPError;
     46 import org.jivesoftware.smack.provider.IQProvider;
     47 import org.jivesoftware.smack.provider.PacketExtensionProvider;
     48 import org.jivesoftware.smack.provider.ProviderManager;
     49 import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
     50 import org.xmlpull.v1.XmlPullParser;
     51 import org.xmlpull.v1.XmlPullParserException;
     52 
     53 /**
     54  * Utility class that helps to parse packets. Any parsing packets method that must be shared
     55  * between many clients must be placed in this utility class.
     56  *
     57  * @author Gaston Dombiak
     58  */
     59 public class PacketParserUtils {
     60 
     61     /**
     62      * Namespace used to store packet properties.
     63      */
     64     private static final String PROPERTIES_NAMESPACE =
     65             "http://www.jivesoftware.com/xmlns/xmpp/properties";
     66 
     67     /**
     68      * Parses a message packet.
     69      *
     70      * @param parser the XML parser, positioned at the start of a message packet.
     71      * @return a Message packet.
     72      * @throws Exception if an exception occurs while parsing the packet.
     73      */
     74     public static Packet parseMessage(XmlPullParser parser) throws Exception {
     75         Message message = new Message();
     76         String id = parser.getAttributeValue("", "id");
     77         message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
     78         message.setTo(parser.getAttributeValue("", "to"));
     79         message.setFrom(parser.getAttributeValue("", "from"));
     80         message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
     81         String language = getLanguageAttribute(parser);
     82 
     83         // determine message's default language
     84         String defaultLanguage = null;
     85         if (language != null && !"".equals(language.trim())) {
     86             message.setLanguage(language);
     87             defaultLanguage = language;
     88         }
     89         else {
     90             defaultLanguage = Packet.getDefaultLanguage();
     91         }
     92 
     93         // Parse sub-elements. We include extra logic to make sure the values
     94         // are only read once. This is because it's possible for the names to appear
     95         // in arbitrary sub-elements.
     96         boolean done = false;
     97         String thread = null;
     98         Map<String, Object> properties = null;
     99         while (!done) {
    100             int eventType = parser.next();
    101             if (eventType == XmlPullParser.START_TAG) {
    102                 String elementName = parser.getName();
    103                 String namespace = parser.getNamespace();
    104                 if (elementName.equals("subject")) {
    105                     String xmlLang = getLanguageAttribute(parser);
    106                     if (xmlLang == null) {
    107                         xmlLang = defaultLanguage;
    108                     }
    109 
    110                     String subject = parseContent(parser);
    111 
    112                     if (message.getSubject(xmlLang) == null) {
    113                         message.addSubject(xmlLang, subject);
    114                     }
    115                 }
    116                 else if (elementName.equals("body")) {
    117                     String xmlLang = getLanguageAttribute(parser);
    118                     if (xmlLang == null) {
    119                         xmlLang = defaultLanguage;
    120                     }
    121 
    122                     String body = parseContent(parser);
    123 
    124                     if (message.getBody(xmlLang) == null) {
    125                         message.addBody(xmlLang, body);
    126                     }
    127                 }
    128                 else if (elementName.equals("thread")) {
    129                     if (thread == null) {
    130                         thread = parser.nextText();
    131                     }
    132                 }
    133                 else if (elementName.equals("error")) {
    134                     message.setError(parseError(parser));
    135                 }
    136                 else if (elementName.equals("properties") &&
    137                         namespace.equals(PROPERTIES_NAMESPACE))
    138                 {
    139                     properties = parseProperties(parser);
    140                 }
    141                 // Otherwise, it must be a packet extension.
    142                 else {
    143                     message.addExtension(
    144                     PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
    145                 }
    146             }
    147             else if (eventType == XmlPullParser.END_TAG) {
    148                 if (parser.getName().equals("message")) {
    149                     done = true;
    150                 }
    151             }
    152         }
    153 
    154         message.setThread(thread);
    155         // Set packet properties.
    156         if (properties != null) {
    157             for (String name : properties.keySet()) {
    158                 message.setProperty(name, properties.get(name));
    159             }
    160         }
    161         return message;
    162     }
    163 
    164     /**
    165      * Returns the content of a tag as string regardless of any tags included.
    166      *
    167      * @param parser the XML pull parser
    168      * @return the content of a tag as string
    169      * @throws XmlPullParserException if parser encounters invalid XML
    170      * @throws IOException if an IO error occurs
    171      */
    172     private static String parseContent(XmlPullParser parser)
    173                     throws XmlPullParserException, IOException {
    174         StringBuffer content = new StringBuffer();
    175         int parserDepth = parser.getDepth();
    176         while (!(parser.next() == XmlPullParser.END_TAG && parser
    177                         .getDepth() == parserDepth)) {
    178             content.append(parser.getText());
    179         }
    180         return content.toString();
    181     }
    182 
    183     /**
    184      * Parses a presence packet.
    185      *
    186      * @param parser the XML parser, positioned at the start of a presence packet.
    187      * @return a Presence packet.
    188      * @throws Exception if an exception occurs while parsing the packet.
    189      */
    190     public static Presence parsePresence(XmlPullParser parser) throws Exception {
    191         Presence.Type type = Presence.Type.available;
    192         String typeString = parser.getAttributeValue("", "type");
    193         if (typeString != null && !typeString.equals("")) {
    194             try {
    195                 type = Presence.Type.valueOf(typeString);
    196             }
    197             catch (IllegalArgumentException iae) {
    198                 System.err.println("Found invalid presence type " + typeString);
    199             }
    200         }
    201         Presence presence = new Presence(type);
    202         presence.setTo(parser.getAttributeValue("", "to"));
    203         presence.setFrom(parser.getAttributeValue("", "from"));
    204         String id = parser.getAttributeValue("", "id");
    205         presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
    206 
    207         String language = getLanguageAttribute(parser);
    208         if (language != null && !"".equals(language.trim())) {
    209         	presence.setLanguage(language);
    210         }
    211         presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
    212 
    213         // Parse sub-elements
    214         boolean done = false;
    215         while (!done) {
    216             int eventType = parser.next();
    217             if (eventType == XmlPullParser.START_TAG) {
    218                 String elementName = parser.getName();
    219                 String namespace = parser.getNamespace();
    220                 if (elementName.equals("status")) {
    221                     presence.setStatus(parser.nextText());
    222                 }
    223                 else if (elementName.equals("priority")) {
    224                     try {
    225                         int priority = Integer.parseInt(parser.nextText());
    226                         presence.setPriority(priority);
    227                     }
    228                     catch (NumberFormatException nfe) {
    229                         // Ignore.
    230                     }
    231                     catch (IllegalArgumentException iae) {
    232                         // Presence priority is out of range so assume priority to be zero
    233                         presence.setPriority(0);
    234                     }
    235                 }
    236                 else if (elementName.equals("show")) {
    237                     String modeText = parser.nextText();
    238                     try {
    239                         presence.setMode(Presence.Mode.valueOf(modeText));
    240                     }
    241                     catch (IllegalArgumentException iae) {
    242                         System.err.println("Found invalid presence mode " + modeText);
    243                     }
    244                 }
    245                 else if (elementName.equals("error")) {
    246                     presence.setError(parseError(parser));
    247                 }
    248                 else if (elementName.equals("properties") &&
    249                         namespace.equals(PROPERTIES_NAMESPACE))
    250                 {
    251                     Map<String,Object> properties = parseProperties(parser);
    252                     // Set packet properties.
    253                     for (String name : properties.keySet()) {
    254                         presence.setProperty(name, properties.get(name));
    255                     }
    256                 }
    257                 // Otherwise, it must be a packet extension.
    258                 else {
    259                 	try {
    260                         presence.addExtension(PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
    261                 	}
    262                 	catch (Exception e) {
    263                 		System.err.println("Failed to parse extension packet in Presence packet.");
    264                 	}
    265                 }
    266             }
    267             else if (eventType == XmlPullParser.END_TAG) {
    268                 if (parser.getName().equals("presence")) {
    269                     done = true;
    270                 }
    271             }
    272         }
    273         return presence;
    274     }
    275 
    276     /**
    277      * Parses an IQ packet.
    278      *
    279      * @param parser the XML parser, positioned at the start of an IQ packet.
    280      * @return an IQ object.
    281      * @throws Exception if an exception occurs while parsing the packet.
    282      */
    283     public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception {
    284         IQ iqPacket = null;
    285 
    286         String id = parser.getAttributeValue("", "id");
    287         String to = parser.getAttributeValue("", "to");
    288         String from = parser.getAttributeValue("", "from");
    289         IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
    290         XMPPError error = null;
    291 
    292         boolean done = false;
    293         while (!done) {
    294             int eventType = parser.next();
    295 
    296             if (eventType == XmlPullParser.START_TAG) {
    297                 String elementName = parser.getName();
    298                 String namespace = parser.getNamespace();
    299                 if (elementName.equals("error")) {
    300                     error = PacketParserUtils.parseError(parser);
    301                 }
    302                 else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) {
    303                     iqPacket = parseAuthentication(parser);
    304                 }
    305                 else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) {
    306                     iqPacket = parseRoster(parser);
    307                 }
    308                 else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
    309                     iqPacket = parseRegistration(parser);
    310                 }
    311                 else if (elementName.equals("bind") &&
    312                         namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
    313                     iqPacket = parseResourceBinding(parser);
    314                 }
    315                 // Otherwise, see if there is a registered provider for
    316                 // this element name and namespace.
    317                 else {
    318                     Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace);
    319                     if (provider != null) {
    320                         if (provider instanceof IQProvider) {
    321                             iqPacket = ((IQProvider)provider).parseIQ(parser);
    322                         }
    323                         else if (provider instanceof Class) {
    324                             iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
    325                                     (Class<?>)provider, parser);
    326                         }
    327                     }
    328                     // Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood
    329                     // have to be answered with an IQ error response. See the code a few lines below
    330                     else if (IQ.Type.RESULT == type){
    331                         // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
    332                         // so that the content of the IQ can be examined later on
    333                         iqPacket = new UnparsedResultIQ(parseContent(parser));
    334                     }
    335                 }
    336             }
    337             else if (eventType == XmlPullParser.END_TAG) {
    338                 if (parser.getName().equals("iq")) {
    339                     done = true;
    340                 }
    341             }
    342         }
    343         // Decide what to do when an IQ packet was not understood
    344         if (iqPacket == null) {
    345             if (IQ.Type.GET == type || IQ.Type.SET == type ) {
    346                 // If the IQ stanza is of type "get" or "set" containing a child element
    347                 // qualified by a namespace it does not understand, then answer an IQ of
    348                 // type "error" with code 501 ("feature-not-implemented")
    349                 iqPacket = new IQ() {
    350                     @Override
    351                     public String getChildElementXML() {
    352                         return null;
    353                     }
    354                 };
    355                 iqPacket.setPacketID(id);
    356                 iqPacket.setTo(from);
    357                 iqPacket.setFrom(to);
    358                 iqPacket.setType(IQ.Type.ERROR);
    359                 iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented));
    360                 connection.sendPacket(iqPacket);
    361                 return null;
    362             }
    363             else {
    364                 // If an IQ packet wasn't created above, create an empty IQ packet.
    365                 iqPacket = new IQ() {
    366                     @Override
    367                     public String getChildElementXML() {
    368                         return null;
    369                     }
    370                 };
    371             }
    372         }
    373 
    374         // Set basic values on the iq packet.
    375         iqPacket.setPacketID(id);
    376         iqPacket.setTo(to);
    377         iqPacket.setFrom(from);
    378         iqPacket.setType(type);
    379         iqPacket.setError(error);
    380 
    381         return iqPacket;
    382     }
    383 
    384     private static Authentication parseAuthentication(XmlPullParser parser) throws Exception {
    385         Authentication authentication = new Authentication();
    386         boolean done = false;
    387         while (!done) {
    388             int eventType = parser.next();
    389             if (eventType == XmlPullParser.START_TAG) {
    390                 if (parser.getName().equals("username")) {
    391                     authentication.setUsername(parser.nextText());
    392                 }
    393                 else if (parser.getName().equals("password")) {
    394                     authentication.setPassword(parser.nextText());
    395                 }
    396                 else if (parser.getName().equals("digest")) {
    397                     authentication.setDigest(parser.nextText());
    398                 }
    399                 else if (parser.getName().equals("resource")) {
    400                     authentication.setResource(parser.nextText());
    401                 }
    402             }
    403             else if (eventType == XmlPullParser.END_TAG) {
    404                 if (parser.getName().equals("query")) {
    405                     done = true;
    406                 }
    407             }
    408         }
    409         return authentication;
    410     }
    411 
    412     private static RosterPacket parseRoster(XmlPullParser parser) throws Exception {
    413         RosterPacket roster = new RosterPacket();
    414         boolean done = false;
    415         RosterPacket.Item item = null;
    416         while (!done) {
    417         	if(parser.getEventType()==XmlPullParser.START_TAG &&
    418         			parser.getName().equals("query")){
    419         		String version = parser.getAttributeValue(null, "ver");
    420         		roster.setVersion(version);
    421         	}
    422             int eventType = parser.next();
    423             if (eventType == XmlPullParser.START_TAG) {
    424                 if (parser.getName().equals("item")) {
    425                     String jid = parser.getAttributeValue("", "jid");
    426                     String name = parser.getAttributeValue("", "name");
    427                     // Create packet.
    428                     item = new RosterPacket.Item(jid, name);
    429                     // Set status.
    430                     String ask = parser.getAttributeValue("", "ask");
    431                     RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask);
    432                     item.setItemStatus(status);
    433                     // Set type.
    434                     String subscription = parser.getAttributeValue("", "subscription");
    435                     RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
    436                     item.setItemType(type);
    437                 }
    438                 if (parser.getName().equals("group") && item!= null) {
    439                     final String groupName = parser.nextText();
    440                     if (groupName != null && groupName.trim().length() > 0) {
    441                         item.addGroupName(groupName);
    442                     }
    443                 }
    444             }
    445             else if (eventType == XmlPullParser.END_TAG) {
    446                 if (parser.getName().equals("item")) {
    447                     roster.addRosterItem(item);
    448                 }
    449                 if (parser.getName().equals("query")) {
    450                     done = true;
    451                 }
    452             }
    453         }
    454         return roster;
    455     }
    456 
    457      private static Registration parseRegistration(XmlPullParser parser) throws Exception {
    458         Registration registration = new Registration();
    459         Map<String, String> fields = null;
    460         boolean done = false;
    461         while (!done) {
    462             int eventType = parser.next();
    463             if (eventType == XmlPullParser.START_TAG) {
    464                 // Any element that's in the jabber:iq:register namespace,
    465                 // attempt to parse it if it's in the form <name>value</name>.
    466                 if (parser.getNamespace().equals("jabber:iq:register")) {
    467                     String name = parser.getName();
    468                     String value = "";
    469                     if (fields == null) {
    470                         fields = new HashMap<String, String>();
    471                     }
    472 
    473                     if (parser.next() == XmlPullParser.TEXT) {
    474                         value = parser.getText();
    475                     }
    476                     // Ignore instructions, but anything else should be added to the map.
    477                     if (!name.equals("instructions")) {
    478                         fields.put(name, value);
    479                     }
    480                     else {
    481                         registration.setInstructions(value);
    482                     }
    483                 }
    484                 // Otherwise, it must be a packet extension.
    485                 else {
    486                     registration.addExtension(
    487                         PacketParserUtils.parsePacketExtension(
    488                             parser.getName(),
    489                             parser.getNamespace(),
    490                             parser));
    491                 }
    492             }
    493             else if (eventType == XmlPullParser.END_TAG) {
    494                 if (parser.getName().equals("query")) {
    495                     done = true;
    496                 }
    497             }
    498         }
    499         registration.setAttributes(fields);
    500         return registration;
    501     }
    502 
    503     private static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
    504             XmlPullParserException {
    505         Bind bind = new Bind();
    506         boolean done = false;
    507         while (!done) {
    508             int eventType = parser.next();
    509             if (eventType == XmlPullParser.START_TAG) {
    510                 if (parser.getName().equals("resource")) {
    511                     bind.setResource(parser.nextText());
    512                 }
    513                 else if (parser.getName().equals("jid")) {
    514                     bind.setJid(parser.nextText());
    515                 }
    516             } else if (eventType == XmlPullParser.END_TAG) {
    517                 if (parser.getName().equals("bind")) {
    518                     done = true;
    519                 }
    520             }
    521         }
    522 
    523         return bind;
    524     }
    525 
    526     /**
    527      * Parse the available SASL mechanisms reported from the server.
    528      *
    529      * @param parser the XML parser, positioned at the start of the mechanisms stanza.
    530      * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
    531      * @throws Exception if an exception occurs while parsing the stanza.
    532      */
    533     public static Collection<String> parseMechanisms(XmlPullParser parser) throws Exception {
    534         List<String> mechanisms = new ArrayList<String>();
    535         boolean done = false;
    536         while (!done) {
    537             int eventType = parser.next();
    538 
    539             if (eventType == XmlPullParser.START_TAG) {
    540                 String elementName = parser.getName();
    541                 if (elementName.equals("mechanism")) {
    542                     mechanisms.add(parser.nextText());
    543                 }
    544             }
    545             else if (eventType == XmlPullParser.END_TAG) {
    546                 if (parser.getName().equals("mechanisms")) {
    547                     done = true;
    548                 }
    549             }
    550         }
    551         return mechanisms;
    552     }
    553 
    554     /**
    555      * Parse the available compression methods reported from the server.
    556      *
    557      * @param parser the XML parser, positioned at the start of the compression stanza.
    558      * @return a collection of Stings with the methods included in the compression stanza.
    559      * @throws Exception if an exception occurs while parsing the stanza.
    560      */
    561     public static Collection<String> parseCompressionMethods(XmlPullParser parser)
    562             throws IOException, XmlPullParserException {
    563         List<String> methods = new ArrayList<String>();
    564         boolean done = false;
    565         while (!done) {
    566             int eventType = parser.next();
    567 
    568             if (eventType == XmlPullParser.START_TAG) {
    569                 String elementName = parser.getName();
    570                 if (elementName.equals("method")) {
    571                     methods.add(parser.nextText());
    572                 }
    573             }
    574             else if (eventType == XmlPullParser.END_TAG) {
    575                 if (parser.getName().equals("compression")) {
    576                     done = true;
    577                 }
    578             }
    579         }
    580         return methods;
    581     }
    582 
    583     /**
    584      * Parse a properties sub-packet. If any errors occur while de-serializing Java object
    585      * properties, an exception will be printed and not thrown since a thrown
    586      * exception will shut down the entire connection. ClassCastExceptions will occur
    587      * when both the sender and receiver of the packet don't have identical versions
    588      * of the same class.
    589      *
    590      * @param parser the XML parser, positioned at the start of a properties sub-packet.
    591      * @return a map of the properties.
    592      * @throws Exception if an error occurs while parsing the properties.
    593      */
    594     public static Map<String, Object> parseProperties(XmlPullParser parser) throws Exception {
    595         Map<String, Object> properties = new HashMap<String, Object>();
    596         while (true) {
    597             int eventType = parser.next();
    598             if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
    599                 // Parse a property
    600                 boolean done = false;
    601                 String name = null;
    602                 String type = null;
    603                 String valueText = null;
    604                 Object value = null;
    605                 while (!done) {
    606                     eventType = parser.next();
    607                     if (eventType == XmlPullParser.START_TAG) {
    608                         String elementName = parser.getName();
    609                         if (elementName.equals("name")) {
    610                             name = parser.nextText();
    611                         }
    612                         else if (elementName.equals("value")) {
    613                             type = parser.getAttributeValue("", "type");
    614                             valueText = parser.nextText();
    615                         }
    616                     }
    617                     else if (eventType == XmlPullParser.END_TAG) {
    618                         if (parser.getName().equals("property")) {
    619                             if ("integer".equals(type)) {
    620                                 value = Integer.valueOf(valueText);
    621                             }
    622                             else if ("long".equals(type))  {
    623                                 value = Long.valueOf(valueText);
    624                             }
    625                             else if ("float".equals(type)) {
    626                                 value = Float.valueOf(valueText);
    627                             }
    628                             else if ("double".equals(type)) {
    629                                 value = Double.valueOf(valueText);
    630                             }
    631                             else if ("boolean".equals(type)) {
    632                                 value = Boolean.valueOf(valueText);
    633                             }
    634                             else if ("string".equals(type)) {
    635                                 value = valueText;
    636                             }
    637                             else if ("java-object".equals(type)) {
    638                                 try {
    639                                     byte [] bytes = StringUtils.decodeBase64(valueText);
    640                                     ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
    641                                     value = in.readObject();
    642                                 }
    643                                 catch (Exception e) {
    644                                     e.printStackTrace();
    645                                 }
    646                             }
    647                             if (name != null && value != null) {
    648                                 properties.put(name, value);
    649                             }
    650                             done = true;
    651                         }
    652                     }
    653                 }
    654             }
    655             else if (eventType == XmlPullParser.END_TAG) {
    656                 if (parser.getName().equals("properties")) {
    657                     break;
    658                 }
    659             }
    660         }
    661         return properties;
    662     }
    663 
    664     /**
    665      * Parses SASL authentication error packets.
    666      *
    667      * @param parser the XML parser.
    668      * @return a SASL Failure packet.
    669      * @throws Exception if an exception occurs while parsing the packet.
    670      */
    671     public static Failure parseSASLFailure(XmlPullParser parser) throws Exception {
    672         String condition = null;
    673         boolean done = false;
    674         while (!done) {
    675             int eventType = parser.next();
    676 
    677             if (eventType == XmlPullParser.START_TAG) {
    678                 if (!parser.getName().equals("failure")) {
    679                     condition = parser.getName();
    680                 }
    681             }
    682             else if (eventType == XmlPullParser.END_TAG) {
    683                 if (parser.getName().equals("failure")) {
    684                     done = true;
    685                 }
    686             }
    687         }
    688         return new Failure(condition);
    689     }
    690 
    691     /**
    692      * Parses stream error packets.
    693      *
    694      * @param parser the XML parser.
    695      * @return an stream error packet.
    696      * @throws Exception if an exception occurs while parsing the packet.
    697      */
    698     public static StreamError parseStreamError(XmlPullParser parser) throws IOException,
    699             XmlPullParserException {
    700     StreamError streamError = null;
    701     boolean done = false;
    702     while (!done) {
    703         int eventType = parser.next();
    704 
    705         if (eventType == XmlPullParser.START_TAG) {
    706             streamError = new StreamError(parser.getName());
    707         }
    708         else if (eventType == XmlPullParser.END_TAG) {
    709             if (parser.getName().equals("error")) {
    710                 done = true;
    711             }
    712         }
    713     }
    714     return streamError;
    715 }
    716 
    717     /**
    718      * Parses error sub-packets.
    719      *
    720      * @param parser the XML parser.
    721      * @return an error sub-packet.
    722      * @throws Exception if an exception occurs while parsing the packet.
    723      */
    724     public static XMPPError parseError(XmlPullParser parser) throws Exception {
    725         final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
    726     	String errorCode = "-1";
    727         String type = null;
    728         String message = null;
    729         String condition = null;
    730         List<PacketExtension> extensions = new ArrayList<PacketExtension>();
    731 
    732         // Parse the error header
    733         for (int i=0; i<parser.getAttributeCount(); i++) {
    734             if (parser.getAttributeName(i).equals("code")) {
    735                 errorCode = parser.getAttributeValue("", "code");
    736             }
    737             if (parser.getAttributeName(i).equals("type")) {
    738             	type = parser.getAttributeValue("", "type");
    739             }
    740         }
    741         boolean done = false;
    742         // Parse the text and condition tags
    743         while (!done) {
    744             int eventType = parser.next();
    745             if (eventType == XmlPullParser.START_TAG) {
    746                 if (parser.getName().equals("text")) {
    747                     message = parser.nextText();
    748                 }
    749                 else {
    750                 	// Condition tag, it can be xmpp error or an application defined error.
    751                     String elementName = parser.getName();
    752                     String namespace = parser.getNamespace();
    753                     if (errorNamespace.equals(namespace)) {
    754                     	condition = elementName;
    755                     }
    756                     else {
    757                     	extensions.add(parsePacketExtension(elementName, namespace, parser));
    758                     }
    759                 }
    760             }
    761                 else if (eventType == XmlPullParser.END_TAG) {
    762                     if (parser.getName().equals("error")) {
    763                         done = true;
    764                     }
    765                 }
    766         }
    767         // Parse the error type.
    768         XMPPError.Type errorType = XMPPError.Type.CANCEL;
    769         try {
    770             if (type != null) {
    771                 errorType = XMPPError.Type.valueOf(type.toUpperCase());
    772             }
    773         }
    774         catch (IllegalArgumentException iae) {
    775             // Print stack trace. We shouldn't be getting an illegal error type.
    776             iae.printStackTrace();
    777         }
    778         return new XMPPError(Integer.parseInt(errorCode), errorType, condition, message, extensions);
    779     }
    780 
    781     /**
    782      * Parses a packet extension sub-packet.
    783      *
    784      * @param elementName the XML element name of the packet extension.
    785      * @param namespace the XML namespace of the packet extension.
    786      * @param parser the XML parser, positioned at the starting element of the extension.
    787      * @return a PacketExtension.
    788      * @throws Exception if a parsing error occurs.
    789      */
    790     public static PacketExtension parsePacketExtension(String elementName, String namespace, XmlPullParser parser)
    791             throws Exception
    792     {
    793         // See if a provider is registered to handle the extension.
    794         Object provider = ProviderManager.getInstance().getExtensionProvider(elementName, namespace);
    795         if (provider != null) {
    796             if (provider instanceof PacketExtensionProvider) {
    797                 return ((PacketExtensionProvider)provider).parseExtension(parser);
    798             }
    799             else if (provider instanceof Class) {
    800                 return (PacketExtension)parseWithIntrospection(
    801                         elementName, (Class<?>)provider, parser);
    802             }
    803         }
    804         // No providers registered, so use a default extension.
    805         DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
    806         boolean done = false;
    807         while (!done) {
    808             int eventType = parser.next();
    809             if (eventType == XmlPullParser.START_TAG) {
    810                 String name = parser.getName();
    811                 // If an empty element, set the value with the empty string.
    812                 if (parser.isEmptyElementTag()) {
    813                     extension.setValue(name,"");
    814                 }
    815                 // Otherwise, get the the element text.
    816                 else {
    817                     eventType = parser.next();
    818                     if (eventType == XmlPullParser.TEXT) {
    819                         String value = parser.getText();
    820                         extension.setValue(name, value);
    821                     }
    822                 }
    823             }
    824             else if (eventType == XmlPullParser.END_TAG) {
    825                 if (parser.getName().equals(elementName)) {
    826                     done = true;
    827                 }
    828             }
    829         }
    830         return extension;
    831     }
    832 
    833     private static String getLanguageAttribute(XmlPullParser parser) {
    834     	for (int i = 0; i < parser.getAttributeCount(); i++) {
    835             String attributeName = parser.getAttributeName(i);
    836             if ( "xml:lang".equals(attributeName) ||
    837                     ("lang".equals(attributeName) &&
    838                             "xml".equals(parser.getAttributePrefix(i)))) {
    839     			return parser.getAttributeValue(i);
    840     		}
    841     	}
    842     	return null;
    843     }
    844 
    845     public static Object parseWithIntrospection(String elementName,
    846             Class<?> objectClass, XmlPullParser parser) throws Exception
    847     {
    848         boolean done = false;
    849         Object object = objectClass.newInstance();
    850         while (!done) {
    851             int eventType = parser.next();
    852             if (eventType == XmlPullParser.START_TAG) {
    853                 String name = parser.getName();
    854                 String stringValue = parser.nextText();
    855                 PropertyDescriptor descriptor = new PropertyDescriptor(name, objectClass);
    856                 // Load the class type of the property.
    857                 Class<?> propertyType = descriptor.getPropertyType();
    858                 // Get the value of the property by converting it from a
    859                 // String to the correct object type.
    860                 Object value = decode(propertyType, stringValue);
    861                 // Set the value of the bean.
    862                 descriptor.getWriteMethod().invoke(object, value);
    863             }
    864             else if (eventType == XmlPullParser.END_TAG) {
    865                 if (parser.getName().equals(elementName)) {
    866                     done = true;
    867                 }
    868             }
    869         }
    870         return object;
    871             }
    872 
    873     /**
    874      * Decodes a String into an object of the specified type. If the object
    875      * type is not supported, null will be returned.
    876      *
    877      * @param type the type of the property.
    878      * @param value the encode String value to decode.
    879      * @return the String value decoded into the specified type.
    880      * @throws Exception If decoding failed due to an error.
    881      */
    882     private static Object decode(Class<?> type, String value) throws Exception {
    883         if (type.getName().equals("java.lang.String")) {
    884             return value;
    885         }
    886         if (type.getName().equals("boolean")) {
    887             return Boolean.valueOf(value);
    888         }
    889         if (type.getName().equals("int")) {
    890             return Integer.valueOf(value);
    891         }
    892         if (type.getName().equals("long")) {
    893             return Long.valueOf(value);
    894         }
    895         if (type.getName().equals("float")) {
    896             return Float.valueOf(value);
    897         }
    898         if (type.getName().equals("double")) {
    899             return Double.valueOf(value);
    900         }
    901         if (type.getName().equals("java.lang.Class")) {
    902             return Class.forName(value);
    903         }
    904         return null;
    905     }
    906 
    907     /**
    908      * This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider
    909      * was found for the IQ element.
    910      *
    911      * The child elements can be examined with the getChildElementXML() method.
    912      *
    913      */
    914     public static class UnparsedResultIQ extends IQ {
    915         public UnparsedResultIQ(String content) {
    916             this.str = content;
    917         }
    918 
    919         private final String str;
    920 
    921         @Override
    922         public String getChildElementXML() {
    923             return this.str;
    924         }
    925     }
    926 }
    927