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