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. 305 * <p> 306 * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed 307 * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown. 308 * </p> 309 * This method is used for the parts where the XMPP specification requires elements that contain 310 * only text or are the empty element. 311 * 312 * @param parser 313 * @return the textual content of the element as String 314 * @throws XmlPullParserException 315 * @throws IOException 316 */ 317 public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException { 318 assert (parser.getEventType() == XmlPullParser.START_TAG); 319 String res; 320 if (parser.isEmptyElementTag()) { 321 res = ""; 322 } 323 else { 324 // Advance to the text of the Element 325 int event = parser.next(); 326 if (event != XmlPullParser.TEXT) { 327 if (event == XmlPullParser.END_TAG) { 328 // Assume this is the end tag of the start tag at the 329 // beginning of this method. Typical examples where this 330 // happens are body elements containing the empty string, 331 // ie. <body></body>, which appears to be valid XMPP, or a 332 // least it's not explicitly forbidden by RFC 6121 5.2.3 333 return ""; 334 } else { 335 throw new XmlPullParserException( 336 "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed"); 337 } 338 } 339 res = parser.getText(); 340 event = parser.next(); 341 if (event != XmlPullParser.END_TAG) { 342 throw new XmlPullParserException( 343 "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed"); 344 } 345 } 346 return res; 347 } 348 349 /** 350 * Returns the current element as string. 351 * <p> 352 * The parser must be positioned on START_TAG. 353 * </p> 354 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 355 * 356 * @param parser the XML pull parser 357 * @return the element as string 358 * @throws XmlPullParserException 359 * @throws IOException 360 */ 361 public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException { 362 return parseElement(parser, false); 363 } 364 365 public static CharSequence parseElement(XmlPullParser parser, 366 boolean fullNamespaces) throws XmlPullParserException, 367 IOException { 368 assert (parser.getEventType() == XmlPullParser.START_TAG); 369 return parseContentDepth(parser, parser.getDepth(), fullNamespaces); 370 } 371 372 /** 373 * Returns the content of a element. 374 * <p> 375 * The parser must be positioned on the START_TAG of the element which content is going to get 376 * returned. If the current element is the empty element, then the empty string is returned. If 377 * it is a element which contains just text, then just the text is returned. If it contains 378 * nested elements (and text), then everything from the current opening tag to the corresponding 379 * closing tag of the same depth is returned as String. 380 * </p> 381 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 382 * 383 * @param parser the XML pull parser 384 * @return the content of a tag 385 * @throws XmlPullParserException if parser encounters invalid XML 386 * @throws IOException if an IO error occurs 387 */ 388 public static CharSequence parseContent(XmlPullParser parser) 389 throws XmlPullParserException, IOException { 390 assert(parser.getEventType() == XmlPullParser.START_TAG); 391 if (parser.isEmptyElementTag()) { 392 return ""; 393 } 394 // Advance the parser, since we want to parse the content of the current element 395 parser.next(); 396 return parseContentDepth(parser, parser.getDepth(), false); 397 } 398 399 public static CharSequence parseContentDepth(XmlPullParser parser, int depth) 400 throws XmlPullParserException, IOException { 401 return parseContentDepth(parser, depth, false); 402 } 403 404 /** 405 * Returns the content from the current position of the parser up to the closing tag of the 406 * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned, 407 * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of 408 * parent elements will be added to child elements that don't define a different namespace. 409 * <p> 410 * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support 411 * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the 412 * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which 413 * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of 414 * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false. 415 * </p> 416 * <p> 417 * In particular Android's XmlPullParser does not support XML_ROUNDTRIP. 418 * </p> 419 * 420 * @param parser 421 * @param depth 422 * @param fullNamespaces 423 * @return the content of the current depth 424 * @throws XmlPullParserException 425 * @throws IOException 426 */ 427 public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException { 428 if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) { 429 return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces); 430 } else { 431 return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces); 432 } 433 } 434 435 private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth, 436 boolean fullNamespaces) throws XmlPullParserException, IOException { 437 XmlStringBuilder xml = new XmlStringBuilder(); 438 int event = parser.getEventType(); 439 boolean isEmptyElement = false; 440 // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines 441 // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again 442 // in a nested element. It's an ugly workaround that has the potential to break things. 443 String namespaceElement = null; 444 outerloop: while (true) { 445 switch (event) { 446 case XmlPullParser.START_TAG: 447 xml.halfOpenElement(parser.getName()); 448 if (namespaceElement == null || fullNamespaces) { 449 String namespace = parser.getNamespace(); 450 if (StringUtils.isNotEmpty(namespace)) { 451 xml.attribute("xmlns", namespace); 452 namespaceElement = parser.getName(); 453 } 454 } 455 for (int i = 0; i < parser.getAttributeCount(); i++) { 456 xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i)); 457 } 458 if (parser.isEmptyElementTag()) { 459 xml.closeEmptyElement(); 460 isEmptyElement = true; 461 } 462 else { 463 xml.rightAngleBracket(); 464 } 465 break; 466 case XmlPullParser.END_TAG: 467 if (isEmptyElement) { 468 // Do nothing as the element was already closed, just reset the flag 469 isEmptyElement = false; 470 } 471 else { 472 xml.closeElement(parser.getName()); 473 } 474 if (namespaceElement != null && namespaceElement.equals(parser.getName())) { 475 // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag' 476 namespaceElement = null; 477 } 478 if (parser.getDepth() <= depth) { 479 // Abort parsing, we are done 480 break outerloop; 481 } 482 break; 483 case XmlPullParser.TEXT: 484 xml.append(parser.getText()); 485 break; 486 } 487 event = parser.next(); 488 } 489 return xml; 490 } 491 492 private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces) 493 throws XmlPullParserException, IOException { 494 StringBuilder sb = new StringBuilder(); 495 int event = parser.getEventType(); 496 outerloop: while (true) { 497 // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported 498 // twice, so in order to prevent duplication we only add their text when we are on their end tag. 499 if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) { 500 sb.append(parser.getText()); 501 } 502 if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) { 503 break outerloop; 504 } 505 event = parser.next(); 506 } 507 return sb; 508 } 509 510 /** 511 * Parses a presence packet. 512 * 513 * @param parser the XML parser, positioned at the start of a presence packet. 514 * @return a Presence packet. 515 * @throws IOException 516 * @throws XmlPullParserException 517 * @throws SmackException 518 */ 519 public static Presence parsePresence(XmlPullParser parser) 520 throws XmlPullParserException, IOException, SmackException { 521 ParserUtils.assertAtStartTag(parser); 522 final int initialDepth = parser.getDepth(); 523 524 Presence.Type type = Presence.Type.available; 525 String typeString = parser.getAttributeValue("", "type"); 526 if (typeString != null && !typeString.equals("")) { 527 type = Presence.Type.fromString(typeString); 528 } 529 Presence presence = new Presence(type); 530 presence.setTo(parser.getAttributeValue("", "to")); 531 presence.setFrom(parser.getAttributeValue("", "from")); 532 presence.setStanzaId(parser.getAttributeValue("", "id")); 533 534 String language = getLanguageAttribute(parser); 535 if (language != null && !"".equals(language.trim())) { 536 presence.setLanguage(language); 537 } 538 539 // Parse sub-elements 540 outerloop: while (true) { 541 int eventType = parser.next(); 542 switch (eventType) { 543 case XmlPullParser.START_TAG: 544 String elementName = parser.getName(); 545 String namespace = parser.getNamespace(); 546 switch(elementName) { 547 case "status": 548 presence.setStatus(parser.nextText()); 549 break; 550 case "priority": 551 int priority = Integer.parseInt(parser.nextText()); 552 presence.setPriority(priority); 553 break; 554 case "show": 555 String modeText = parser.nextText(); 556 if (StringUtils.isNotEmpty(modeText)) { 557 presence.setMode(Presence.Mode.fromString(modeText)); 558 } else { 559 // Some implementations send presence stanzas with a 560 // '<show />' element, which is a invalid XMPP presence 561 // stanza according to RFC 6121 4.7.2.1 562 LOGGER.warning("Empty or null mode text in presence show element form " 563 + presence.getFrom() 564 + " with id '" 565 + presence.getStanzaId() 566 + "' which is invalid according to RFC6121 4.7.2.1"); 567 } 568 break; 569 case "error": 570 presence.setError(parseError(parser)); 571 break; 572 default: 573 // Otherwise, it must be a packet extension. 574 // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of 575 // failing completely here. See SMACK-390 for more information. 576 try { 577 PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace); 578 } catch (Exception e) { 579 LOGGER.log(Level.WARNING, 580 "Failed to parse extension packet in Presence packet. Attributes: from=" 581 + presence.getFrom() + " id=" + presence.getStanzaId(), e); 582 } 583 break; 584 } 585 break; 586 case XmlPullParser.END_TAG: 587 if (parser.getDepth() == initialDepth) { 588 break outerloop; 589 } 590 break; 591 } 592 } 593 return presence; 594 } 595 596 /** 597 * Parses an IQ packet. 598 * 599 * @param parser the XML parser, positioned at the start of an IQ packet. 600 * @return an IQ object. 601 * @throws XmlPullParserException 602 * @throws IOException 603 * @throws SmackException 604 */ 605 public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException { 606 ParserUtils.assertAtStartTag(parser); 607 final int initialDepth = parser.getDepth(); 608 IQ iqPacket = null; 609 XMPPError error = null; 610 611 final String id = parser.getAttributeValue("", "id"); 612 final String to = parser.getAttributeValue("", "to"); 613 final String from = parser.getAttributeValue("", "from"); 614 final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); 615 616 outerloop: while (true) { 617 int eventType = parser.next(); 618 619 switch (eventType) { 620 case XmlPullParser.START_TAG: 621 String elementName = parser.getName(); 622 String namespace = parser.getNamespace(); 623 switch(elementName) { 624 case "error": 625 error = PacketParserUtils.parseError(parser); 626 break; 627 // Otherwise, see if there is a registered provider for 628 // this element name and namespace. 629 default: 630 IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace); 631 if (provider != null) { 632 iqPacket = provider.parse(parser); 633 } 634 // Note that if we reach this code, it is guranteed that the result IQ contained a child element 635 // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first. 636 else { 637 // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance 638 // so that the content of the IQ can be examined later on 639 iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser)); 640 } 641 break; 642 } 643 break; 644 case XmlPullParser.END_TAG: 645 if (parser.getDepth() == initialDepth) { 646 break outerloop; 647 } 648 break; 649 } 650 } 651 // Decide what to do when an IQ packet was not understood 652 if (iqPacket == null) { 653 switch (type) { 654 case error: 655 // If an IQ packet wasn't created above, create an empty error IQ packet. 656 iqPacket = new ErrorIQ(error); 657 break; 658 case result: 659 iqPacket = new EmptyResultIQ(); 660 break; 661 default: 662 break; 663 } 664 } 665 666 // Set basic values on the iq packet. 667 iqPacket.setStanzaId(id); 668 iqPacket.setTo(to); 669 iqPacket.setFrom(from); 670 iqPacket.setType(type); 671 iqPacket.setError(error); 672 673 return iqPacket; 674 } 675 676 /** 677 * Parse the available SASL mechanisms reported from the server. 678 * 679 * @param parser the XML parser, positioned at the start of the mechanisms stanza. 680 * @return a collection of Stings with the mechanisms included in the mechanisms stanza. 681 * @throws IOException 682 * @throws XmlPullParserException 683 */ 684 public static Collection<String> parseMechanisms(XmlPullParser parser) 685 throws XmlPullParserException, IOException { 686 List<String> mechanisms = new ArrayList<String>(); 687 boolean done = false; 688 while (!done) { 689 int eventType = parser.next(); 690 691 if (eventType == XmlPullParser.START_TAG) { 692 String elementName = parser.getName(); 693 if (elementName.equals("mechanism")) { 694 mechanisms.add(parser.nextText()); 695 } 696 } 697 else if (eventType == XmlPullParser.END_TAG) { 698 if (parser.getName().equals("mechanisms")) { 699 done = true; 700 } 701 } 702 } 703 return mechanisms; 704 } 705 706 /** 707 * Parse the Compression Feature reported from the server. 708 * 709 * @param parser the XML parser, positioned at the start of the compression stanza. 710 * @return The CompressionFeature stream element 711 * @throws XmlPullParserException if an exception occurs while parsing the stanza. 712 */ 713 public static Compress.Feature parseCompressionFeature(XmlPullParser parser) 714 throws IOException, XmlPullParserException { 715 assert (parser.getEventType() == XmlPullParser.START_TAG); 716 String name; 717 final int initialDepth = parser.getDepth(); 718 List<String> methods = new LinkedList<String>(); 719 outerloop: while (true) { 720 int eventType = parser.next(); 721 switch (eventType) { 722 case XmlPullParser.START_TAG: 723 name = parser.getName(); 724 switch (name) { 725 case "method": 726 methods.add(parser.nextText()); 727 break; 728 } 729 break; 730 case XmlPullParser.END_TAG: 731 name = parser.getName(); 732 switch (name) { 733 case Compress.Feature.ELEMENT: 734 if (parser.getDepth() == initialDepth) { 735 break outerloop; 736 } 737 } 738 } 739 } 740 assert (parser.getEventType() == XmlPullParser.END_TAG); 741 assert (parser.getDepth() == initialDepth); 742 return new Compress.Feature(methods); 743 } 744 745 public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts) 746 throws XmlPullParserException, IOException { 747 if (descriptiveTexts == null) { 748 descriptiveTexts = new HashMap<String, String>(); 749 } 750 String xmllang = getLanguageAttribute(parser); 751 String text = parser.nextText(); 752 String previousValue = descriptiveTexts.put(xmllang, text); 753 assert (previousValue == null); 754 return descriptiveTexts; 755 } 756 757 /** 758 * Parses SASL authentication error packets. 759 * 760 * @param parser the XML parser. 761 * @return a SASL Failure packet. 762 * @throws IOException 763 * @throws XmlPullParserException 764 */ 765 public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException { 766 final int initialDepth = parser.getDepth(); 767 String condition = null; 768 Map<String, String> descriptiveTexts = null; 769 outerloop: while (true) { 770 int eventType = parser.next(); 771 switch (eventType) { 772 case XmlPullParser.START_TAG: 773 String name = parser.getName(); 774 if (name.equals("text")) { 775 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 776 } 777 else { 778 assert(condition == null); 779 condition = parser.getName(); 780 } 781 break; 782 case XmlPullParser.END_TAG: 783 if (parser.getDepth() == initialDepth) { 784 break outerloop; 785 } 786 break; 787 } 788 } 789 return new SASLFailure(condition, descriptiveTexts); 790 } 791 792 /** 793 * Parses stream error packets. 794 * 795 * @param parser the XML parser. 796 * @return an stream error packet. 797 * @throws XmlPullParserException if an exception occurs while parsing the packet. 798 * @throws SmackException 799 */ 800 public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException, 801 SmackException { 802 final int initialDepth = parser.getDepth(); 803 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 804 Map<String, String> descriptiveTexts = null; 805 StreamError.Condition condition = null; 806 String conditionText = null; 807 outerloop: while (true) { 808 int eventType = parser.next(); 809 switch (eventType) { 810 case XmlPullParser.START_TAG: 811 String name = parser.getName(); 812 String namespace = parser.getNamespace(); 813 switch (namespace) { 814 case StreamError.NAMESPACE: 815 switch (name) { 816 case "text": 817 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 818 break; 819 default: 820 // If it's not a text element, that is qualified by the StreamError.NAMESPACE, 821 // then it has to be the stream error code 822 condition = StreamError.Condition.fromString(name); 823 if (!parser.isEmptyElementTag()) { 824 conditionText = parser.nextText(); 825 } 826 break; 827 } 828 break; 829 default: 830 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 831 break; 832 } 833 break; 834 case XmlPullParser.END_TAG: 835 if (parser.getDepth() == initialDepth) { 836 break outerloop; 837 } 838 break; 839 } 840 } 841 return new StreamError(condition, conditionText, descriptiveTexts, extensions); 842 } 843 844 /** 845 * Parses error sub-packets. 846 * 847 * @param parser the XML parser. 848 * @return an error sub-packet. 849 * @throws IOException 850 * @throws XmlPullParserException 851 * @throws SmackException 852 */ 853 public static XMPPError parseError(XmlPullParser parser) 854 throws XmlPullParserException, IOException, SmackException { 855 final int initialDepth = parser.getDepth(); 856 Map<String, String> descriptiveTexts = null; 857 XMPPError.Condition condition = null; 858 String conditionText = null; 859 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 860 861 // Parse the error header 862 XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type")); 863 String errorGenerator = parser.getAttributeValue("", "by"); 864 865 outerloop: while (true) { 866 int eventType = parser.next(); 867 switch (eventType) { 868 case XmlPullParser.START_TAG: 869 String name = parser.getName(); 870 String namespace = parser.getNamespace(); 871 switch (namespace) { 872 case XMPPError.NAMESPACE: 873 switch (name) { 874 case Stanza.TEXT: 875 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 876 break; 877 default: 878 condition = XMPPError.Condition.fromString(name); 879 if (!parser.isEmptyElementTag()) { 880 conditionText = parser.nextText(); 881 } 882 break; 883 } 884 break; 885 default: 886 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 887 } 888 break; 889 case XmlPullParser.END_TAG: 890 if (parser.getDepth() == initialDepth) { 891 break outerloop; 892 } 893 } 894 } 895 return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions); 896 } 897 898 /** 899 * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead. 900 */ 901 @Deprecated 902 public static ExtensionElement parsePacketExtension(String elementName, String namespace, 903 XmlPullParser parser) throws XmlPullParserException, 904 IOException, SmackException { 905 return parseExtensionElement(elementName, namespace, parser); 906 } 907 908 /** 909 * Parses an extension element. 910 * 911 * @param elementName the XML element name of the extension element. 912 * @param namespace the XML namespace of the stanza(/packet) extension. 913 * @param parser the XML parser, positioned at the starting element of the extension. 914 * @return an extension element. 915 */ 916 public static ExtensionElement parseExtensionElement(String elementName, String namespace, 917 XmlPullParser parser) throws XmlPullParserException, 918 IOException, SmackException { 919 ParserUtils.assertAtStartTag(parser); 920 // See if a provider is registered to handle the extension. 921 ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace); 922 if (provider != null) { 923 return provider.parse(parser); 924 } 925 926 final int initialDepth = parser.getDepth(); 927 // No providers registered, so use a default extension. 928 DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace); 929 outerloop: while (true) { 930 int eventType = parser.next(); 931 switch (eventType) { 932 case XmlPullParser.START_TAG: 933 String name = parser.getName(); 934 // If an empty element, set the value with the empty string. 935 if (parser.isEmptyElementTag()) { 936 extension.setValue(name,""); 937 } 938 // Otherwise, get the the element text. 939 else { 940 eventType = parser.next(); 941 if (eventType == XmlPullParser.TEXT) { 942 String value = parser.getText(); 943 extension.setValue(name, value); 944 } 945 } 946 break; 947 case XmlPullParser.END_TAG: 948 if (parser.getDepth() == initialDepth) { 949 break outerloop; 950 } 951 } 952 } 953 return extension; 954 } 955 956 public static StartTls parseStartTlsFeature(XmlPullParser parser) 957 throws XmlPullParserException, IOException { 958 assert (parser.getEventType() == XmlPullParser.START_TAG); 959 assert (parser.getNamespace().equals(StartTls.NAMESPACE)); 960 int initalDepth = parser.getDepth(); 961 boolean required = false; 962 outerloop: while (true) { 963 int event = parser.next(); 964 switch (event) { 965 case XmlPullParser.START_TAG: 966 String name = parser.getName(); 967 switch (name) { 968 case "required": 969 required = true; 970 break; 971 } 972 break; 973 case XmlPullParser.END_TAG: 974 if (parser.getDepth() == initalDepth) { 975 break outerloop; 976 } 977 } 978 } 979 assert(parser.getEventType() == XmlPullParser.END_TAG); 980 return new StartTls(required); 981 } 982 983 public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException { 984 ParserUtils.assertAtStartTag(parser); 985 final int initialDepth = parser.getDepth(); 986 boolean optional = false; 987 if (!parser.isEmptyElementTag()) { 988 outerloop: while(true) { 989 int event = parser.next(); 990 switch (event) { 991 case XmlPullParser.START_TAG: 992 String name = parser.getName(); 993 switch (name) { 994 case Session.Feature.OPTIONAL_ELEMENT: 995 optional = true; 996 break; 997 } 998 break; 999 case XmlPullParser.END_TAG: 1000 if (parser.getDepth() == initialDepth) { 1001 break outerloop; 1002 } 1003 } 1004 } 1005 } 1006 return new Session.Feature(optional); 1007 1008 } 1009 private static String getLanguageAttribute(XmlPullParser parser) { 1010 for (int i = 0; i < parser.getAttributeCount(); i++) { 1011 String attributeName = parser.getAttributeName(i); 1012 if ( "xml:lang".equals(attributeName) || 1013 ("lang".equals(attributeName) && 1014 "xml".equals(parser.getAttributePrefix(i)))) { 1015 return parser.getAttributeValue(i); 1016 } 1017 } 1018 return null; 1019 } 1020 1021 @Deprecated 1022 public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException, 1023 IOException, SmackException { 1024 addExtensionElement(packet, parser); 1025 } 1026 1027 @Deprecated 1028 public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace) 1029 throws XmlPullParserException, IOException, SmackException { 1030 addExtensionElement(packet, parser, elementName, namespace); 1031 } 1032 1033 @Deprecated 1034 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser) 1035 throws XmlPullParserException, IOException, SmackException { 1036 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1037 } 1038 1039 @Deprecated 1040 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser, 1041 String elementName, String namespace) throws XmlPullParserException, IOException, SmackException { 1042 addExtensionElement(collection, parser, elementName, namespace); 1043 } 1044 1045 1046 public static void addExtensionElement(Stanza packet, XmlPullParser parser) 1047 throws XmlPullParserException, IOException, SmackException { 1048 ParserUtils.assertAtStartTag(parser); 1049 addExtensionElement(packet, parser, parser.getName(), parser.getNamespace()); 1050 } 1051 1052 public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName, 1053 String namespace) throws XmlPullParserException, IOException, SmackException { 1054 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1055 packet.addExtension(packetExtension); 1056 } 1057 1058 public static void addExtensionElement(Collection<ExtensionElement> collection, 1059 XmlPullParser parser) throws XmlPullParserException, IOException, 1060 SmackException { 1061 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1062 } 1063 1064 public static void addExtensionElement(Collection<ExtensionElement> collection, 1065 XmlPullParser parser, String elementName, String namespace) 1066 throws XmlPullParserException, IOException, SmackException { 1067 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1068 collection.add(packetExtension); 1069 } 1070}