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