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