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}