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.io.Reader; 021import java.io.StringReader; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031import org.jivesoftware.smack.SmackException; 032import org.jivesoftware.smack.compress.packet.Compress; 033import org.jivesoftware.smack.packet.DefaultExtensionElement; 034import org.jivesoftware.smack.packet.EmptyResultIQ; 035import org.jivesoftware.smack.packet.ErrorIQ; 036import org.jivesoftware.smack.packet.IQ; 037import org.jivesoftware.smack.packet.Message; 038import org.jivesoftware.smack.packet.Stanza; 039import org.jivesoftware.smack.packet.ExtensionElement; 040import org.jivesoftware.smack.packet.Presence; 041import org.jivesoftware.smack.packet.Session; 042import org.jivesoftware.smack.packet.StartTls; 043import org.jivesoftware.smack.packet.StreamError; 044import org.jivesoftware.smack.packet.UnparsedIQ; 045import org.jivesoftware.smack.packet.XMPPError; 046import org.jivesoftware.smack.provider.IQProvider; 047import org.jivesoftware.smack.provider.ExtensionElementProvider; 048import org.jivesoftware.smack.provider.ProviderManager; 049import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; 050import org.xmlpull.v1.XmlPullParser; 051import org.xmlpull.v1.XmlPullParserException; 052import org.xmlpull.v1.XmlPullParserFactory; 053 054/** 055 * Utility class that helps to parse packets. Any parsing packets method that must be shared 056 * between many clients must be placed in this utility class. 057 * 058 * @author Gaston Dombiak 059 */ 060public class PacketParserUtils { 061 private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName()); 062 063 public static final String FEATURE_XML_ROUNDTRIP = "http://xmlpull.org/v1/doc/features.html#xml-roundtrip"; 064 065 private static final XmlPullParserFactory XML_PULL_PARSER_FACTORY; 066 067 /** 068 * True if the XmlPullParser supports the XML_ROUNDTRIP feature. 069 */ 070 public static final boolean XML_PULL_PARSER_SUPPORTS_ROUNDTRIP; 071 072 static { 073 XmlPullParser xmlPullParser; 074 boolean roundtrip = false; 075 try { 076 XML_PULL_PARSER_FACTORY = XmlPullParserFactory.newInstance(); 077 xmlPullParser = XML_PULL_PARSER_FACTORY.newPullParser(); 078 try { 079 xmlPullParser.setFeature(FEATURE_XML_ROUNDTRIP, true); 080 // We could successfully set the feature 081 roundtrip = true; 082 } catch (XmlPullParserException e) { 083 // Doesn't matter if FEATURE_XML_ROUNDTRIP isn't available 084 LOGGER.log(Level.FINEST, "XmlPullParser does not support XML_ROUNDTRIP", e); 085 } 086 } 087 catch (XmlPullParserException e) { 088 // Something really bad happened 089 throw new AssertionError(e); 090 } 091 XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip; 092 } 093 094 public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException { 095 return getParserFor(new StringReader(stanza)); 096 } 097 098 public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException { 099 XmlPullParser parser = newXmppParser(reader); 100 // Wind the parser forward to the first start tag 101 int event = parser.getEventType(); 102 while (event != XmlPullParser.START_TAG) { 103 if (event == XmlPullParser.END_DOCUMENT) { 104 throw new IllegalArgumentException("Document contains no start tag"); 105 } 106 event = parser.next(); 107 } 108 return parser; 109 } 110 111 public static XmlPullParser getParserFor(String stanza, String startTag) 112 throws XmlPullParserException, IOException { 113 XmlPullParser parser = getParserFor(stanza); 114 115 while (true) { 116 int event = parser.getEventType(); 117 String name = parser.getName(); 118 if (event == XmlPullParser.START_TAG && name.equals(startTag)) { 119 break; 120 } 121 else if (event == XmlPullParser.END_DOCUMENT) { 122 throw new IllegalArgumentException("Could not find start tag '" + startTag 123 + "' in stanza: " + stanza); 124 } 125 parser.next(); 126 } 127 128 return parser; 129 } 130 131 public static Stanza parseStanza(String stanza) throws XmlPullParserException, IOException, SmackException { 132 return parseStanza(getParserFor(stanza)); 133 } 134 135 /** 136 * Tries to parse and return either a Message, IQ or Presence stanza. 137 * 138 * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas. 139 * 140 * @param parser 141 * @return a stanza(/packet) which is either a Message, IQ or Presence. 142 * @throws XmlPullParserException 143 * @throws SmackException 144 * @throws IOException 145 */ 146 public static Stanza parseStanza(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException { 147 ParserUtils.assertAtStartTag(parser); 148 final String name = parser.getName(); 149 switch (name) { 150 case Message.ELEMENT: 151 return parseMessage(parser); 152 case IQ.IQ_ELEMENT: 153 return parseIQ(parser); 154 case Presence.ELEMENT: 155 return parsePresence(parser); 156 default: 157 throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name); 158 } 159 } 160 161 /** 162 * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that 163 * FEATURE_PROCESS_NAMESPACES is enabled. 164 * <p> 165 * Note that not all XmlPullParser implementations will return a String on 166 * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this 167 * behavior when using the parser. 168 * </p> 169 * 170 * @return A suitable XmlPullParser for XMPP parsing 171 * @throws XmlPullParserException 172 */ 173 public static XmlPullParser newXmppParser() throws XmlPullParserException { 174 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 175 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 176 if (XML_PULL_PARSER_SUPPORTS_ROUNDTRIP) { 177 try { 178 parser.setFeature(FEATURE_XML_ROUNDTRIP, true); 179 } 180 catch (XmlPullParserException e) { 181 LOGGER.log(Level.SEVERE, 182 "XmlPullParser does not support XML_ROUNDTRIP, although it was first determined to be supported", 183 e); 184 } 185 } 186 return parser; 187 } 188 189 /** 190 * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that 191 * FEATURE_PROCESS_NAMESPACES is enabled. 192 * <p> 193 * Note that not all XmlPullParser implementations will return a String on 194 * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this 195 * behavior when using the parser. 196 * </p> 197 * 198 * @param reader 199 * @return A suitable XmlPullParser for XMPP parsing 200 * @throws XmlPullParserException 201 */ 202 public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException { 203 XmlPullParser parser = newXmppParser(); 204 parser.setInput(reader); 205 return parser; 206 } 207 208 /** 209 * Parses a message packet. 210 * 211 * @param parser the XML parser, positioned at the start of a message packet. 212 * @return a Message packet. 213 * @throws IOException 214 * @throws XmlPullParserException 215 * @throws SmackException 216 */ 217 public static Message parseMessage(XmlPullParser parser) 218 throws XmlPullParserException, IOException, SmackException { 219 ParserUtils.assertAtStartTag(parser); 220 assert(parser.getName().equals(Message.ELEMENT)); 221 222 final int initialDepth = parser.getDepth(); 223 Message message = new Message(); 224 message.setStanzaId(parser.getAttributeValue("", "id")); 225 message.setTo(parser.getAttributeValue("", "to")); 226 message.setFrom(parser.getAttributeValue("", "from")); 227 String typeString = parser.getAttributeValue("", "type"); 228 if (typeString != null) { 229 message.setType(Message.Type.fromString(typeString)); 230 } 231 String language = getLanguageAttribute(parser); 232 233 // determine message's default language 234 String defaultLanguage = null; 235 if (language != null && !"".equals(language.trim())) { 236 message.setLanguage(language); 237 defaultLanguage = language; 238 } 239 else { 240 defaultLanguage = Stanza.getDefaultLanguage(); 241 } 242 243 // Parse sub-elements. We include extra logic to make sure the values 244 // are only read once. This is because it's possible for the names to appear 245 // in arbitrary sub-elements. 246 String thread = null; 247 outerloop: while (true) { 248 int eventType = parser.next(); 249 switch (eventType) { 250 case XmlPullParser.START_TAG: 251 String elementName = parser.getName(); 252 String namespace = parser.getNamespace(); 253 switch(elementName) { 254 case "subject": 255 String xmlLangSubject = getLanguageAttribute(parser); 256 if (xmlLangSubject == null) { 257 xmlLangSubject = defaultLanguage; 258 } 259 260 String subject = parseElementText(parser); 261 262 if (message.getSubject(xmlLangSubject) == null) { 263 message.addSubject(xmlLangSubject, subject); 264 } 265 break; 266 case Message.BODY: 267 String xmlLang = getLanguageAttribute(parser); 268 if (xmlLang == null) { 269 xmlLang = defaultLanguage; 270 } 271 272 String body = parseElementText(parser); 273 274 if (message.getBody(xmlLang) == null) { 275 message.addBody(xmlLang, body); 276 } 277 break; 278 case "thread": 279 if (thread == null) { 280 thread = parser.nextText(); 281 } 282 break; 283 case "error": 284 message.setError(parseError(parser)); 285 break; 286 default: 287 PacketParserUtils.addExtensionElement(message, parser, elementName, namespace); 288 break; 289 } 290 break; 291 case XmlPullParser.END_TAG: 292 if (parser.getDepth() == initialDepth) { 293 break outerloop; 294 } 295 break; 296 } 297 } 298 299 message.setThread(thread); 300 return message; 301 } 302 303 /** 304 * Returns the textual content of an element as String. After this method returns the parser 305 * position will be END_TAG, following the established pull parser calling convention. 306 * <p> 307 * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed 308 * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown. 309 * </p> 310 * This method is used for the parts where the XMPP specification requires elements that contain 311 * only text or are the empty element. 312 * 313 * @param parser 314 * @return the textual content of the element as String 315 * @throws XmlPullParserException 316 * @throws IOException 317 */ 318 public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException { 319 assert (parser.getEventType() == XmlPullParser.START_TAG); 320 String res; 321 if (parser.isEmptyElementTag()) { 322 res = ""; 323 } 324 else { 325 // Advance to the text of the Element 326 int event = parser.next(); 327 if (event != XmlPullParser.TEXT) { 328 if (event == XmlPullParser.END_TAG) { 329 // Assume this is the end tag of the start tag at the 330 // beginning of this method. Typical examples where this 331 // happens are body elements containing the empty string, 332 // ie. <body></body>, which appears to be valid XMPP, or a 333 // least it's not explicitly forbidden by RFC 6121 5.2.3 334 return ""; 335 } else { 336 throw new XmlPullParserException( 337 "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed"); 338 } 339 } 340 res = parser.getText(); 341 event = parser.next(); 342 if (event != XmlPullParser.END_TAG) { 343 throw new XmlPullParserException( 344 "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed"); 345 } 346 } 347 return res; 348 } 349 350 /** 351 * Returns the current element as string. 352 * <p> 353 * The parser must be positioned on START_TAG. 354 * </p> 355 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 356 * 357 * @param parser the XML pull parser 358 * @return the element as string 359 * @throws XmlPullParserException 360 * @throws IOException 361 */ 362 public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException { 363 return parseElement(parser, false); 364 } 365 366 public static CharSequence parseElement(XmlPullParser parser, 367 boolean fullNamespaces) throws XmlPullParserException, 368 IOException { 369 assert (parser.getEventType() == XmlPullParser.START_TAG); 370 return parseContentDepth(parser, parser.getDepth(), fullNamespaces); 371 } 372 373 /** 374 * Returns the content of a element. 375 * <p> 376 * The parser must be positioned on the START_TAG of the element which content is going to get 377 * returned. If the current element is the empty element, then the empty string is returned. If 378 * it is a element which contains just text, then just the text is returned. If it contains 379 * nested elements (and text), then everything from the current opening tag to the corresponding 380 * closing tag of the same depth is returned as String. 381 * </p> 382 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 383 * 384 * @param parser the XML pull parser 385 * @return the content of a tag 386 * @throws XmlPullParserException if parser encounters invalid XML 387 * @throws IOException if an IO error occurs 388 */ 389 public static CharSequence parseContent(XmlPullParser parser) 390 throws XmlPullParserException, IOException { 391 assert(parser.getEventType() == XmlPullParser.START_TAG); 392 if (parser.isEmptyElementTag()) { 393 return ""; 394 } 395 // Advance the parser, since we want to parse the content of the current element 396 parser.next(); 397 return parseContentDepth(parser, parser.getDepth(), false); 398 } 399 400 public static CharSequence parseContentDepth(XmlPullParser parser, int depth) 401 throws XmlPullParserException, IOException { 402 return parseContentDepth(parser, depth, false); 403 } 404 405 /** 406 * Returns the content from the current position of the parser up to the closing tag of the 407 * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned, 408 * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of 409 * parent elements will be added to child elements that don't define a different namespace. 410 * <p> 411 * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support 412 * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the 413 * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which 414 * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of 415 * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false. 416 * </p> 417 * <p> 418 * In particular Android's XmlPullParser does not support XML_ROUNDTRIP. 419 * </p> 420 * 421 * @param parser 422 * @param depth 423 * @param fullNamespaces 424 * @return the content of the current depth 425 * @throws XmlPullParserException 426 * @throws IOException 427 */ 428 public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException { 429 if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) { 430 return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces); 431 } else { 432 return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces); 433 } 434 } 435 436 private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth, 437 boolean fullNamespaces) throws XmlPullParserException, IOException { 438 XmlStringBuilder xml = new XmlStringBuilder(); 439 int event = parser.getEventType(); 440 boolean isEmptyElement = false; 441 // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines 442 // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again 443 // in a nested element. It's an ugly workaround that has the potential to break things. 444 String namespaceElement = null; 445 outerloop: while (true) { 446 switch (event) { 447 case XmlPullParser.START_TAG: 448 xml.halfOpenElement(parser.getName()); 449 if (namespaceElement == null || fullNamespaces) { 450 String namespace = parser.getNamespace(); 451 if (StringUtils.isNotEmpty(namespace)) { 452 xml.attribute("xmlns", namespace); 453 namespaceElement = parser.getName(); 454 } 455 } 456 for (int i = 0; i < parser.getAttributeCount(); i++) { 457 xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i)); 458 } 459 if (parser.isEmptyElementTag()) { 460 xml.closeEmptyElement(); 461 isEmptyElement = true; 462 } 463 else { 464 xml.rightAngleBracket(); 465 } 466 break; 467 case XmlPullParser.END_TAG: 468 if (isEmptyElement) { 469 // Do nothing as the element was already closed, just reset the flag 470 isEmptyElement = false; 471 } 472 else { 473 xml.closeElement(parser.getName()); 474 } 475 if (namespaceElement != null && namespaceElement.equals(parser.getName())) { 476 // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag' 477 namespaceElement = null; 478 } 479 if (parser.getDepth() <= depth) { 480 // Abort parsing, we are done 481 break outerloop; 482 } 483 break; 484 case XmlPullParser.TEXT: 485 xml.escape(parser.getText()); 486 break; 487 } 488 event = parser.next(); 489 } 490 return xml; 491 } 492 493 private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces) 494 throws XmlPullParserException, IOException { 495 StringBuilder sb = new StringBuilder(); 496 int event = parser.getEventType(); 497 outerloop: while (true) { 498 // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported 499 // twice, so in order to prevent duplication we only add their text when we are on their end tag. 500 if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) { 501 CharSequence text = parser.getText(); 502 if (event == XmlPullParser.TEXT) { 503 // TODO the toString() can be removed in Smack 4.2. 504 text = StringUtils.escapeForXML(text.toString()); 505 } 506 sb.append(text); 507 } 508 if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) { 509 break outerloop; 510 } 511 event = parser.next(); 512 } 513 return sb; 514 } 515 516 /** 517 * Parses a presence packet. 518 * 519 * @param parser the XML parser, positioned at the start of a presence packet. 520 * @return a Presence packet. 521 * @throws IOException 522 * @throws XmlPullParserException 523 * @throws SmackException 524 */ 525 public static Presence parsePresence(XmlPullParser parser) 526 throws XmlPullParserException, IOException, SmackException { 527 ParserUtils.assertAtStartTag(parser); 528 final int initialDepth = parser.getDepth(); 529 530 Presence.Type type = Presence.Type.available; 531 String typeString = parser.getAttributeValue("", "type"); 532 if (typeString != null && !typeString.equals("")) { 533 type = Presence.Type.fromString(typeString); 534 } 535 Presence presence = new Presence(type); 536 presence.setTo(parser.getAttributeValue("", "to")); 537 presence.setFrom(parser.getAttributeValue("", "from")); 538 presence.setStanzaId(parser.getAttributeValue("", "id")); 539 540 String language = getLanguageAttribute(parser); 541 if (language != null && !"".equals(language.trim())) { 542 presence.setLanguage(language); 543 } 544 545 // Parse sub-elements 546 outerloop: while (true) { 547 int eventType = parser.next(); 548 switch (eventType) { 549 case XmlPullParser.START_TAG: 550 String elementName = parser.getName(); 551 String namespace = parser.getNamespace(); 552 switch(elementName) { 553 case "status": 554 presence.setStatus(parser.nextText()); 555 break; 556 case "priority": 557 int priority = Integer.parseInt(parser.nextText()); 558 presence.setPriority(priority); 559 break; 560 case "show": 561 String modeText = parser.nextText(); 562 if (StringUtils.isNotEmpty(modeText)) { 563 presence.setMode(Presence.Mode.fromString(modeText)); 564 } else { 565 // Some implementations send presence stanzas with a 566 // '<show />' element, which is a invalid XMPP presence 567 // stanza according to RFC 6121 4.7.2.1 568 LOGGER.warning("Empty or null mode text in presence show element form " 569 + presence.getFrom() 570 + " with id '" 571 + presence.getStanzaId() 572 + "' which is invalid according to RFC6121 4.7.2.1"); 573 } 574 break; 575 case "error": 576 presence.setError(parseError(parser)); 577 break; 578 default: 579 // Otherwise, it must be a packet extension. 580 // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of 581 // failing completely here. See SMACK-390 for more information. 582 try { 583 PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace); 584 } catch (Exception e) { 585 LOGGER.log(Level.WARNING, 586 "Failed to parse extension packet in Presence packet. Attributes: from=" 587 + presence.getFrom() + " id=" + presence.getStanzaId(), e); 588 } 589 break; 590 } 591 break; 592 case XmlPullParser.END_TAG: 593 if (parser.getDepth() == initialDepth) { 594 break outerloop; 595 } 596 break; 597 } 598 } 599 return presence; 600 } 601 602 /** 603 * Parses an IQ packet. 604 * 605 * @param parser the XML parser, positioned at the start of an IQ packet. 606 * @return an IQ object. 607 * @throws XmlPullParserException 608 * @throws IOException 609 * @throws SmackException 610 */ 611 public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException { 612 ParserUtils.assertAtStartTag(parser); 613 final int initialDepth = parser.getDepth(); 614 IQ iqPacket = null; 615 XMPPError error = null; 616 617 final String id = parser.getAttributeValue("", "id"); 618 final String to = parser.getAttributeValue("", "to"); 619 final String from = parser.getAttributeValue("", "from"); 620 final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); 621 622 outerloop: while (true) { 623 int eventType = parser.next(); 624 625 switch (eventType) { 626 case XmlPullParser.START_TAG: 627 String elementName = parser.getName(); 628 String namespace = parser.getNamespace(); 629 switch(elementName) { 630 case "error": 631 error = PacketParserUtils.parseError(parser); 632 break; 633 // Otherwise, see if there is a registered provider for 634 // this element name and namespace. 635 default: 636 IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace); 637 if (provider != null) { 638 iqPacket = provider.parse(parser); 639 } 640 // Note that if we reach this code, it is guranteed that the result IQ contained a child element 641 // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first. 642 else { 643 // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance 644 // so that the content of the IQ can be examined later on 645 iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser)); 646 } 647 break; 648 } 649 break; 650 case XmlPullParser.END_TAG: 651 if (parser.getDepth() == initialDepth) { 652 break outerloop; 653 } 654 break; 655 } 656 } 657 // Decide what to do when an IQ packet was not understood 658 if (iqPacket == null) { 659 switch (type) { 660 case error: 661 // If an IQ packet wasn't created above, create an empty error IQ packet. 662 iqPacket = new ErrorIQ(error); 663 break; 664 case result: 665 iqPacket = new EmptyResultIQ(); 666 break; 667 default: 668 break; 669 } 670 } 671 672 // Set basic values on the iq packet. 673 iqPacket.setStanzaId(id); 674 iqPacket.setTo(to); 675 iqPacket.setFrom(from); 676 iqPacket.setType(type); 677 iqPacket.setError(error); 678 679 return iqPacket; 680 } 681 682 /** 683 * Parse the available SASL mechanisms reported from the server. 684 * 685 * @param parser the XML parser, positioned at the start of the mechanisms stanza. 686 * @return a collection of Stings with the mechanisms included in the mechanisms stanza. 687 * @throws IOException 688 * @throws XmlPullParserException 689 */ 690 public static Collection<String> parseMechanisms(XmlPullParser parser) 691 throws XmlPullParserException, IOException { 692 List<String> mechanisms = new ArrayList<String>(); 693 boolean done = false; 694 while (!done) { 695 int eventType = parser.next(); 696 697 if (eventType == XmlPullParser.START_TAG) { 698 String elementName = parser.getName(); 699 if (elementName.equals("mechanism")) { 700 mechanisms.add(parser.nextText()); 701 } 702 } 703 else if (eventType == XmlPullParser.END_TAG) { 704 if (parser.getName().equals("mechanisms")) { 705 done = true; 706 } 707 } 708 } 709 return mechanisms; 710 } 711 712 /** 713 * Parse the Compression Feature reported from the server. 714 * 715 * @param parser the XML parser, positioned at the start of the compression stanza. 716 * @return The CompressionFeature stream element 717 * @throws XmlPullParserException if an exception occurs while parsing the stanza. 718 */ 719 public static Compress.Feature parseCompressionFeature(XmlPullParser parser) 720 throws IOException, XmlPullParserException { 721 assert (parser.getEventType() == XmlPullParser.START_TAG); 722 String name; 723 final int initialDepth = parser.getDepth(); 724 List<String> methods = new LinkedList<String>(); 725 outerloop: while (true) { 726 int eventType = parser.next(); 727 switch (eventType) { 728 case XmlPullParser.START_TAG: 729 name = parser.getName(); 730 switch (name) { 731 case "method": 732 methods.add(parser.nextText()); 733 break; 734 } 735 break; 736 case XmlPullParser.END_TAG: 737 name = parser.getName(); 738 switch (name) { 739 case Compress.Feature.ELEMENT: 740 if (parser.getDepth() == initialDepth) { 741 break outerloop; 742 } 743 } 744 } 745 } 746 assert (parser.getEventType() == XmlPullParser.END_TAG); 747 assert (parser.getDepth() == initialDepth); 748 return new Compress.Feature(methods); 749 } 750 751 public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts) 752 throws XmlPullParserException, IOException { 753 if (descriptiveTexts == null) { 754 descriptiveTexts = new HashMap<String, String>(); 755 } 756 String xmllang = getLanguageAttribute(parser); 757 String text = parser.nextText(); 758 String previousValue = descriptiveTexts.put(xmllang, text); 759 assert (previousValue == null); 760 return descriptiveTexts; 761 } 762 763 /** 764 * Parses SASL authentication error packets. 765 * 766 * @param parser the XML parser. 767 * @return a SASL Failure packet. 768 * @throws IOException 769 * @throws XmlPullParserException 770 */ 771 public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException { 772 final int initialDepth = parser.getDepth(); 773 String condition = null; 774 Map<String, String> descriptiveTexts = null; 775 outerloop: while (true) { 776 int eventType = parser.next(); 777 switch (eventType) { 778 case XmlPullParser.START_TAG: 779 String name = parser.getName(); 780 if (name.equals("text")) { 781 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 782 } 783 else { 784 assert(condition == null); 785 condition = parser.getName(); 786 } 787 break; 788 case XmlPullParser.END_TAG: 789 if (parser.getDepth() == initialDepth) { 790 break outerloop; 791 } 792 break; 793 } 794 } 795 return new SASLFailure(condition, descriptiveTexts); 796 } 797 798 /** 799 * Parses stream error packets. 800 * 801 * @param parser the XML parser. 802 * @return an stream error packet. 803 * @throws XmlPullParserException if an exception occurs while parsing the packet. 804 * @throws SmackException 805 */ 806 public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException, 807 SmackException { 808 final int initialDepth = parser.getDepth(); 809 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 810 Map<String, String> descriptiveTexts = null; 811 StreamError.Condition condition = null; 812 String conditionText = null; 813 outerloop: while (true) { 814 int eventType = parser.next(); 815 switch (eventType) { 816 case XmlPullParser.START_TAG: 817 String name = parser.getName(); 818 String namespace = parser.getNamespace(); 819 switch (namespace) { 820 case StreamError.NAMESPACE: 821 switch (name) { 822 case "text": 823 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 824 break; 825 default: 826 // If it's not a text element, that is qualified by the StreamError.NAMESPACE, 827 // then it has to be the stream error code 828 condition = StreamError.Condition.fromString(name); 829 if (!parser.isEmptyElementTag()) { 830 conditionText = parser.nextText(); 831 } 832 break; 833 } 834 break; 835 default: 836 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 837 break; 838 } 839 break; 840 case XmlPullParser.END_TAG: 841 if (parser.getDepth() == initialDepth) { 842 break outerloop; 843 } 844 break; 845 } 846 } 847 return new StreamError(condition, conditionText, descriptiveTexts, extensions); 848 } 849 850 /** 851 * Parses error sub-packets. 852 * 853 * @param parser the XML parser. 854 * @return an error sub-packet. 855 * @throws IOException 856 * @throws XmlPullParserException 857 * @throws SmackException 858 */ 859 public static XMPPError parseError(XmlPullParser parser) 860 throws XmlPullParserException, IOException, SmackException { 861 final int initialDepth = parser.getDepth(); 862 Map<String, String> descriptiveTexts = null; 863 XMPPError.Condition condition = null; 864 String conditionText = null; 865 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 866 867 // Parse the error header 868 XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type")); 869 String errorGenerator = parser.getAttributeValue("", "by"); 870 871 outerloop: while (true) { 872 int eventType = parser.next(); 873 switch (eventType) { 874 case XmlPullParser.START_TAG: 875 String name = parser.getName(); 876 String namespace = parser.getNamespace(); 877 switch (namespace) { 878 case XMPPError.NAMESPACE: 879 switch (name) { 880 case Stanza.TEXT: 881 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 882 break; 883 default: 884 condition = XMPPError.Condition.fromString(name); 885 if (!parser.isEmptyElementTag()) { 886 conditionText = parser.nextText(); 887 } 888 break; 889 } 890 break; 891 default: 892 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 893 } 894 break; 895 case XmlPullParser.END_TAG: 896 if (parser.getDepth() == initialDepth) { 897 break outerloop; 898 } 899 } 900 } 901 return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions); 902 } 903 904 /** 905 * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead. 906 */ 907 @Deprecated 908 public static ExtensionElement parsePacketExtension(String elementName, String namespace, 909 XmlPullParser parser) throws XmlPullParserException, 910 IOException, SmackException { 911 return parseExtensionElement(elementName, namespace, parser); 912 } 913 914 /** 915 * Parses an extension element. 916 * 917 * @param elementName the XML element name of the extension element. 918 * @param namespace the XML namespace of the stanza(/packet) extension. 919 * @param parser the XML parser, positioned at the starting element of the extension. 920 * @return an extension element. 921 */ 922 public static ExtensionElement parseExtensionElement(String elementName, String namespace, 923 XmlPullParser parser) throws XmlPullParserException, 924 IOException, SmackException { 925 ParserUtils.assertAtStartTag(parser); 926 // See if a provider is registered to handle the extension. 927 ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace); 928 if (provider != null) { 929 return provider.parse(parser); 930 } 931 932 final int initialDepth = parser.getDepth(); 933 // No providers registered, so use a default extension. 934 DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace); 935 outerloop: while (true) { 936 int eventType = parser.next(); 937 switch (eventType) { 938 case XmlPullParser.START_TAG: 939 String name = parser.getName(); 940 // If an empty element, set the value with the empty string. 941 if (parser.isEmptyElementTag()) { 942 extension.setValue(name,""); 943 } 944 // Otherwise, get the the element text. 945 else { 946 eventType = parser.next(); 947 if (eventType == XmlPullParser.TEXT) { 948 String value = parser.getText(); 949 extension.setValue(name, value); 950 } 951 } 952 break; 953 case XmlPullParser.END_TAG: 954 if (parser.getDepth() == initialDepth) { 955 break outerloop; 956 } 957 } 958 } 959 return extension; 960 } 961 962 public static StartTls parseStartTlsFeature(XmlPullParser parser) 963 throws XmlPullParserException, IOException { 964 assert (parser.getEventType() == XmlPullParser.START_TAG); 965 assert (parser.getNamespace().equals(StartTls.NAMESPACE)); 966 int initalDepth = parser.getDepth(); 967 boolean required = false; 968 outerloop: while (true) { 969 int event = parser.next(); 970 switch (event) { 971 case XmlPullParser.START_TAG: 972 String name = parser.getName(); 973 switch (name) { 974 case "required": 975 required = true; 976 break; 977 } 978 break; 979 case XmlPullParser.END_TAG: 980 if (parser.getDepth() == initalDepth) { 981 break outerloop; 982 } 983 } 984 } 985 assert(parser.getEventType() == XmlPullParser.END_TAG); 986 return new StartTls(required); 987 } 988 989 public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException { 990 ParserUtils.assertAtStartTag(parser); 991 final int initialDepth = parser.getDepth(); 992 boolean optional = false; 993 if (!parser.isEmptyElementTag()) { 994 outerloop: while(true) { 995 int event = parser.next(); 996 switch (event) { 997 case XmlPullParser.START_TAG: 998 String name = parser.getName(); 999 switch (name) { 1000 case Session.Feature.OPTIONAL_ELEMENT: 1001 optional = true; 1002 break; 1003 } 1004 break; 1005 case XmlPullParser.END_TAG: 1006 if (parser.getDepth() == initialDepth) { 1007 break outerloop; 1008 } 1009 } 1010 } 1011 } 1012 return new Session.Feature(optional); 1013 1014 } 1015 private static String getLanguageAttribute(XmlPullParser parser) { 1016 for (int i = 0; i < parser.getAttributeCount(); i++) { 1017 String attributeName = parser.getAttributeName(i); 1018 if ( "xml:lang".equals(attributeName) || 1019 ("lang".equals(attributeName) && 1020 "xml".equals(parser.getAttributePrefix(i)))) { 1021 return parser.getAttributeValue(i); 1022 } 1023 } 1024 return null; 1025 } 1026 1027 @Deprecated 1028 public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException, 1029 IOException, SmackException { 1030 addExtensionElement(packet, parser); 1031 } 1032 1033 @Deprecated 1034 public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace) 1035 throws XmlPullParserException, IOException, SmackException { 1036 addExtensionElement(packet, parser, elementName, namespace); 1037 } 1038 1039 @Deprecated 1040 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser) 1041 throws XmlPullParserException, IOException, SmackException { 1042 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1043 } 1044 1045 @Deprecated 1046 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser, 1047 String elementName, String namespace) throws XmlPullParserException, IOException, SmackException { 1048 addExtensionElement(collection, parser, elementName, namespace); 1049 } 1050 1051 1052 public static void addExtensionElement(Stanza packet, XmlPullParser parser) 1053 throws XmlPullParserException, IOException, SmackException { 1054 ParserUtils.assertAtStartTag(parser); 1055 addExtensionElement(packet, parser, parser.getName(), parser.getNamespace()); 1056 } 1057 1058 public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName, 1059 String namespace) throws XmlPullParserException, IOException, SmackException { 1060 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1061 packet.addExtension(packetExtension); 1062 } 1063 1064 public static void addExtensionElement(Collection<ExtensionElement> collection, 1065 XmlPullParser parser) throws XmlPullParserException, IOException, 1066 SmackException { 1067 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1068 } 1069 1070 public static void addExtensionElement(Collection<ExtensionElement> collection, 1071 XmlPullParser parser, String elementName, String namespace) 1072 throws XmlPullParserException, IOException, SmackException { 1073 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1074 collection.add(packetExtension); 1075 } 1076}