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