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, "Failed to parse extension packet in Presence packet.", e); 580 } 581 break; 582 } 583 break; 584 case XmlPullParser.END_TAG: 585 if (parser.getDepth() == initialDepth) { 586 break outerloop; 587 } 588 break; 589 } 590 } 591 return presence; 592 } 593 594 /** 595 * Parses an IQ packet. 596 * 597 * @param parser the XML parser, positioned at the start of an IQ packet. 598 * @return an IQ object. 599 * @throws XmlPullParserException 600 * @throws IOException 601 * @throws SmackException 602 */ 603 public static IQ parseIQ(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException { 604 ParserUtils.assertAtStartTag(parser); 605 final int initialDepth = parser.getDepth(); 606 IQ iqPacket = null; 607 XMPPError error = null; 608 609 final String id = parser.getAttributeValue("", "id"); 610 final String to = parser.getAttributeValue("", "to"); 611 final String from = parser.getAttributeValue("", "from"); 612 final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); 613 614 outerloop: while (true) { 615 int eventType = parser.next(); 616 617 switch (eventType) { 618 case XmlPullParser.START_TAG: 619 String elementName = parser.getName(); 620 String namespace = parser.getNamespace(); 621 switch(elementName) { 622 case "error": 623 error = PacketParserUtils.parseError(parser); 624 break; 625 // Otherwise, see if there is a registered provider for 626 // this element name and namespace. 627 default: 628 IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace); 629 if (provider != null) { 630 iqPacket = provider.parse(parser); 631 } 632 // Note that if we reach this code, it is guranteed that the result IQ contained a child element 633 // (RFC 6120 ยง 8.2.3 6) because otherwhise we would have reached the END_TAG first. 634 else { 635 // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance 636 // so that the content of the IQ can be examined later on 637 iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser)); 638 } 639 break; 640 } 641 break; 642 case XmlPullParser.END_TAG: 643 if (parser.getDepth() == initialDepth) { 644 break outerloop; 645 } 646 break; 647 } 648 } 649 // Decide what to do when an IQ packet was not understood 650 if (iqPacket == null) { 651 switch (type) { 652 case error: 653 // If an IQ packet wasn't created above, create an empty error IQ packet. 654 iqPacket = new ErrorIQ(error); 655 break; 656 case result: 657 iqPacket = new EmptyResultIQ(); 658 break; 659 default: 660 break; 661 } 662 } 663 664 // Set basic values on the iq packet. 665 iqPacket.setStanzaId(id); 666 iqPacket.setTo(to); 667 iqPacket.setFrom(from); 668 iqPacket.setType(type); 669 iqPacket.setError(error); 670 671 return iqPacket; 672 } 673 674 /** 675 * Parse the available SASL mechanisms reported from the server. 676 * 677 * @param parser the XML parser, positioned at the start of the mechanisms stanza. 678 * @return a collection of Stings with the mechanisms included in the mechanisms stanza. 679 * @throws IOException 680 * @throws XmlPullParserException 681 */ 682 public static Collection<String> parseMechanisms(XmlPullParser parser) 683 throws XmlPullParserException, IOException { 684 List<String> mechanisms = new ArrayList<String>(); 685 boolean done = false; 686 while (!done) { 687 int eventType = parser.next(); 688 689 if (eventType == XmlPullParser.START_TAG) { 690 String elementName = parser.getName(); 691 if (elementName.equals("mechanism")) { 692 mechanisms.add(parser.nextText()); 693 } 694 } 695 else if (eventType == XmlPullParser.END_TAG) { 696 if (parser.getName().equals("mechanisms")) { 697 done = true; 698 } 699 } 700 } 701 return mechanisms; 702 } 703 704 /** 705 * Parse the Compression Feature reported from the server. 706 * 707 * @param parser the XML parser, positioned at the start of the compression stanza. 708 * @return The CompressionFeature stream element 709 * @throws XmlPullParserException if an exception occurs while parsing the stanza. 710 */ 711 public static Compress.Feature parseCompressionFeature(XmlPullParser parser) 712 throws IOException, XmlPullParserException { 713 assert (parser.getEventType() == XmlPullParser.START_TAG); 714 String name; 715 final int initialDepth = parser.getDepth(); 716 List<String> methods = new LinkedList<String>(); 717 outerloop: while (true) { 718 int eventType = parser.next(); 719 switch (eventType) { 720 case XmlPullParser.START_TAG: 721 name = parser.getName(); 722 switch (name) { 723 case "method": 724 methods.add(parser.nextText()); 725 break; 726 } 727 break; 728 case XmlPullParser.END_TAG: 729 name = parser.getName(); 730 switch (name) { 731 case Compress.Feature.ELEMENT: 732 if (parser.getDepth() == initialDepth) { 733 break outerloop; 734 } 735 } 736 } 737 } 738 assert (parser.getEventType() == XmlPullParser.END_TAG); 739 assert (parser.getDepth() == initialDepth); 740 return new Compress.Feature(methods); 741 } 742 743 public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts) 744 throws XmlPullParserException, IOException { 745 if (descriptiveTexts == null) { 746 descriptiveTexts = new HashMap<String, String>(); 747 } 748 String xmllang = getLanguageAttribute(parser); 749 String text = parser.nextText(); 750 String previousValue = descriptiveTexts.put(xmllang, text); 751 assert (previousValue == null); 752 return descriptiveTexts; 753 } 754 755 /** 756 * Parses SASL authentication error packets. 757 * 758 * @param parser the XML parser. 759 * @return a SASL Failure packet. 760 * @throws IOException 761 * @throws XmlPullParserException 762 */ 763 public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException { 764 final int initialDepth = parser.getDepth(); 765 String condition = null; 766 Map<String, String> descriptiveTexts = null; 767 outerloop: while (true) { 768 int eventType = parser.next(); 769 switch (eventType) { 770 case XmlPullParser.START_TAG: 771 String name = parser.getName(); 772 if (name.equals("text")) { 773 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 774 } 775 else { 776 assert(condition == null); 777 condition = parser.getName(); 778 } 779 break; 780 case XmlPullParser.END_TAG: 781 if (parser.getDepth() == initialDepth) { 782 break outerloop; 783 } 784 break; 785 } 786 } 787 return new SASLFailure(condition, descriptiveTexts); 788 } 789 790 /** 791 * Parses stream error packets. 792 * 793 * @param parser the XML parser. 794 * @return an stream error packet. 795 * @throws XmlPullParserException if an exception occurs while parsing the packet. 796 * @throws SmackException 797 */ 798 public static StreamError parseStreamError(XmlPullParser parser) throws IOException, XmlPullParserException, 799 SmackException { 800 final int initialDepth = parser.getDepth(); 801 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 802 Map<String, String> descriptiveTexts = null; 803 StreamError.Condition condition = null; 804 String conditionText = null; 805 outerloop: while (true) { 806 int eventType = parser.next(); 807 switch (eventType) { 808 case XmlPullParser.START_TAG: 809 String name = parser.getName(); 810 String namespace = parser.getNamespace(); 811 switch (namespace) { 812 case StreamError.NAMESPACE: 813 switch (name) { 814 case "text": 815 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 816 break; 817 default: 818 // If it's not a text element, that is qualified by the StreamError.NAMESPACE, 819 // then it has to be the stream error code 820 condition = StreamError.Condition.fromString(name); 821 if (!parser.isEmptyElementTag()) { 822 conditionText = parser.nextText(); 823 } 824 break; 825 } 826 break; 827 default: 828 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 829 break; 830 } 831 break; 832 case XmlPullParser.END_TAG: 833 if (parser.getDepth() == initialDepth) { 834 break outerloop; 835 } 836 break; 837 } 838 } 839 return new StreamError(condition, conditionText, descriptiveTexts, extensions); 840 } 841 842 /** 843 * Parses error sub-packets. 844 * 845 * @param parser the XML parser. 846 * @return an error sub-packet. 847 * @throws IOException 848 * @throws XmlPullParserException 849 * @throws SmackException 850 */ 851 public static XMPPError parseError(XmlPullParser parser) 852 throws XmlPullParserException, IOException, SmackException { 853 final int initialDepth = parser.getDepth(); 854 Map<String, String> descriptiveTexts = null; 855 XMPPError.Condition condition = null; 856 String conditionText = null; 857 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 858 859 // Parse the error header 860 XMPPError.Type errorType = XMPPError.Type.fromString(parser.getAttributeValue("", "type")); 861 String errorGenerator = parser.getAttributeValue("", "by"); 862 863 outerloop: while (true) { 864 int eventType = parser.next(); 865 switch (eventType) { 866 case XmlPullParser.START_TAG: 867 String name = parser.getName(); 868 String namespace = parser.getNamespace(); 869 switch (namespace) { 870 case XMPPError.NAMESPACE: 871 switch (name) { 872 case Stanza.TEXT: 873 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 874 break; 875 default: 876 condition = XMPPError.Condition.fromString(name); 877 if (!parser.isEmptyElementTag()) { 878 conditionText = parser.nextText(); 879 } 880 break; 881 } 882 break; 883 default: 884 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 885 } 886 break; 887 case XmlPullParser.END_TAG: 888 if (parser.getDepth() == initialDepth) { 889 break outerloop; 890 } 891 } 892 } 893 return new XMPPError(condition, conditionText, errorGenerator, errorType, descriptiveTexts, extensions); 894 } 895 896 /** 897 * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead. 898 */ 899 @Deprecated 900 public static ExtensionElement parsePacketExtension(String elementName, String namespace, 901 XmlPullParser parser) throws XmlPullParserException, 902 IOException, SmackException { 903 return parseExtensionElement(elementName, namespace, parser); 904 } 905 906 /** 907 * Parses an extension element. 908 * 909 * @param elementName the XML element name of the extension element. 910 * @param namespace the XML namespace of the stanza(/packet) extension. 911 * @param parser the XML parser, positioned at the starting element of the extension. 912 * @return an extension element. 913 */ 914 public static ExtensionElement parseExtensionElement(String elementName, String namespace, 915 XmlPullParser parser) throws XmlPullParserException, 916 IOException, SmackException { 917 ParserUtils.assertAtStartTag(parser); 918 // See if a provider is registered to handle the extension. 919 ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace); 920 if (provider != null) { 921 return provider.parse(parser); 922 } 923 924 final int initialDepth = parser.getDepth(); 925 // No providers registered, so use a default extension. 926 DefaultExtensionElement extension = new DefaultExtensionElement(elementName, namespace); 927 outerloop: while (true) { 928 int eventType = parser.next(); 929 switch (eventType) { 930 case XmlPullParser.START_TAG: 931 String name = parser.getName(); 932 // If an empty element, set the value with the empty string. 933 if (parser.isEmptyElementTag()) { 934 extension.setValue(name,""); 935 } 936 // Otherwise, get the the element text. 937 else { 938 eventType = parser.next(); 939 if (eventType == XmlPullParser.TEXT) { 940 String value = parser.getText(); 941 extension.setValue(name, value); 942 } 943 } 944 break; 945 case XmlPullParser.END_TAG: 946 if (parser.getDepth() == initialDepth) { 947 break outerloop; 948 } 949 } 950 } 951 return extension; 952 } 953 954 public static StartTls parseStartTlsFeature(XmlPullParser parser) 955 throws XmlPullParserException, IOException { 956 assert (parser.getEventType() == XmlPullParser.START_TAG); 957 assert (parser.getNamespace().equals(StartTls.NAMESPACE)); 958 int initalDepth = parser.getDepth(); 959 boolean required = false; 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 "required": 967 required = true; 968 break; 969 } 970 break; 971 case XmlPullParser.END_TAG: 972 if (parser.getDepth() == initalDepth) { 973 break outerloop; 974 } 975 } 976 } 977 assert(parser.getEventType() == XmlPullParser.END_TAG); 978 return new StartTls(required); 979 } 980 981 public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException { 982 ParserUtils.assertAtStartTag(parser); 983 final int initialDepth = parser.getDepth(); 984 boolean optional = false; 985 if (!parser.isEmptyElementTag()) { 986 outerloop: while(true) { 987 int event = parser.next(); 988 switch (event) { 989 case XmlPullParser.START_TAG: 990 String name = parser.getName(); 991 switch (name) { 992 case Session.Feature.OPTIONAL_ELEMENT: 993 optional = true; 994 break; 995 } 996 break; 997 case XmlPullParser.END_TAG: 998 if (parser.getDepth() == initialDepth) { 999 break outerloop; 1000 } 1001 } 1002 } 1003 } 1004 return new Session.Feature(optional); 1005 1006 } 1007 private static String getLanguageAttribute(XmlPullParser parser) { 1008 for (int i = 0; i < parser.getAttributeCount(); i++) { 1009 String attributeName = parser.getAttributeName(i); 1010 if ( "xml:lang".equals(attributeName) || 1011 ("lang".equals(attributeName) && 1012 "xml".equals(parser.getAttributePrefix(i)))) { 1013 return parser.getAttributeValue(i); 1014 } 1015 } 1016 return null; 1017 } 1018 1019 @Deprecated 1020 public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws XmlPullParserException, 1021 IOException, SmackException { 1022 addExtensionElement(packet, parser); 1023 } 1024 1025 @Deprecated 1026 public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace) 1027 throws XmlPullParserException, IOException, SmackException { 1028 addExtensionElement(packet, parser, elementName, namespace); 1029 } 1030 1031 @Deprecated 1032 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser) 1033 throws XmlPullParserException, IOException, SmackException { 1034 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1035 } 1036 1037 @Deprecated 1038 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser, 1039 String elementName, String namespace) throws XmlPullParserException, IOException, SmackException { 1040 addExtensionElement(collection, parser, elementName, namespace); 1041 } 1042 1043 1044 public static void addExtensionElement(Stanza packet, XmlPullParser parser) 1045 throws XmlPullParserException, IOException, SmackException { 1046 ParserUtils.assertAtStartTag(parser); 1047 addExtensionElement(packet, parser, parser.getName(), parser.getNamespace()); 1048 } 1049 1050 public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName, 1051 String namespace) throws XmlPullParserException, IOException, SmackException { 1052 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1053 packet.addExtension(packetExtension); 1054 } 1055 1056 public static void addExtensionElement(Collection<ExtensionElement> collection, 1057 XmlPullParser parser) throws XmlPullParserException, IOException, 1058 SmackException { 1059 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1060 } 1061 1062 public static void addExtensionElement(Collection<ExtensionElement> collection, 1063 XmlPullParser parser, String elementName, String namespace) 1064 throws XmlPullParserException, IOException, SmackException { 1065 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1066 collection.add(packetExtension); 1067 } 1068}