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