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