001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.util; 018 019import java.io.IOException; 020import java.io.Reader; 021import java.io.StringReader; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031import org.jivesoftware.smack.compress.packet.Compress; 032import org.jivesoftware.smack.packet.EmptyResultIQ; 033import org.jivesoftware.smack.packet.ErrorIQ; 034import org.jivesoftware.smack.packet.IQ; 035import org.jivesoftware.smack.packet.Message; 036import org.jivesoftware.smack.packet.Stanza; 037import org.jivesoftware.smack.packet.ExtensionElement; 038import org.jivesoftware.smack.packet.Presence; 039import org.jivesoftware.smack.packet.Session; 040import org.jivesoftware.smack.packet.StartTls; 041import org.jivesoftware.smack.packet.StreamError; 042import org.jivesoftware.smack.packet.UnparsedIQ; 043import org.jivesoftware.smack.packet.XMPPError; 044import org.jivesoftware.smack.parsing.StandardExtensionElementProvider; 045import org.jivesoftware.smack.provider.IQProvider; 046import org.jivesoftware.smack.provider.ExtensionElementProvider; 047import org.jivesoftware.smack.provider.ProviderManager; 048import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; 049import org.jxmpp.jid.Jid; 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 @SuppressWarnings("unchecked") 132 public static <S extends Stanza> S parseStanza(String stanza) throws Exception { 133 return (S) parseStanza(getParserFor(stanza)); 134 } 135 136 /** 137 * Tries to parse and return either a Message, IQ or Presence stanza. 138 * 139 * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas. 140 * 141 * @param parser 142 * @return a stanza(/packet) which is either a Message, IQ or Presence. 143 * @throws Exception 144 */ 145 public static Stanza parseStanza(XmlPullParser parser) throws Exception { 146 ParserUtils.assertAtStartTag(parser); 147 final String name = parser.getName(); 148 switch (name) { 149 case Message.ELEMENT: 150 return parseMessage(parser); 151 case IQ.IQ_ELEMENT: 152 return parseIQ(parser); 153 case Presence.ELEMENT: 154 return parsePresence(parser); 155 default: 156 throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name); 157 } 158 } 159 160 /** 161 * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that 162 * FEATURE_PROCESS_NAMESPACES is enabled. 163 * <p> 164 * Note that not all XmlPullParser implementations will return a String on 165 * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this 166 * behavior when using the parser. 167 * </p> 168 * 169 * @return A suitable XmlPullParser for XMPP parsing 170 * @throws XmlPullParserException 171 */ 172 public static XmlPullParser newXmppParser() throws XmlPullParserException { 173 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 174 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 175 if (XML_PULL_PARSER_SUPPORTS_ROUNDTRIP) { 176 try { 177 parser.setFeature(FEATURE_XML_ROUNDTRIP, true); 178 } 179 catch (XmlPullParserException e) { 180 LOGGER.log(Level.SEVERE, 181 "XmlPullParser does not support XML_ROUNDTRIP, although it was first determined to be supported", 182 e); 183 } 184 } 185 return parser; 186 } 187 188 /** 189 * Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that 190 * FEATURE_PROCESS_NAMESPACES is enabled. 191 * <p> 192 * Note that not all XmlPullParser implementations will return a String on 193 * <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this 194 * behavior when using the parser. 195 * </p> 196 * 197 * @param reader 198 * @return A suitable XmlPullParser for XMPP parsing 199 * @throws XmlPullParserException 200 */ 201 public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException { 202 XmlPullParser parser = newXmppParser(); 203 parser.setInput(reader); 204 return parser; 205 } 206 207 /** 208 * Parses a message packet. 209 * 210 * @param parser the XML parser, positioned at the start of a message packet. 211 * @return a Message packet. 212 * @throws Exception 213 */ 214 public static Message parseMessage(XmlPullParser parser) 215 throws Exception { 216 ParserUtils.assertAtStartTag(parser); 217 assert(parser.getName().equals(Message.ELEMENT)); 218 219 final int initialDepth = parser.getDepth(); 220 Message message = new Message(); 221 message.setStanzaId(parser.getAttributeValue("", "id")); 222 message.setTo(ParserUtils.getJidAttribute(parser, "to")); 223 message.setFrom(ParserUtils.getJidAttribute(parser, "from")); 224 String typeString = parser.getAttributeValue("", "type"); 225 if (typeString != null) { 226 message.setType(Message.Type.fromString(typeString)); 227 } 228 String language = getLanguageAttribute(parser); 229 230 // determine message's default language 231 String defaultLanguage = null; 232 if (language != null && !"".equals(language.trim())) { 233 message.setLanguage(language); 234 defaultLanguage = language; 235 } 236 else { 237 defaultLanguage = Stanza.getDefaultLanguage(); 238 } 239 240 // Parse sub-elements. We include extra logic to make sure the values 241 // are only read once. This is because it's possible for the names to appear 242 // in arbitrary sub-elements. 243 String thread = null; 244 outerloop: while (true) { 245 int eventType = parser.next(); 246 switch (eventType) { 247 case XmlPullParser.START_TAG: 248 String elementName = parser.getName(); 249 String namespace = parser.getNamespace(); 250 switch(elementName) { 251 case "subject": 252 String xmlLangSubject = getLanguageAttribute(parser); 253 if (xmlLangSubject == null) { 254 xmlLangSubject = defaultLanguage; 255 } 256 257 String subject = parseElementText(parser); 258 259 if (message.getSubject(xmlLangSubject) == null) { 260 message.addSubject(xmlLangSubject, subject); 261 } 262 break; 263 case Message.BODY: 264 String xmlLang = getLanguageAttribute(parser); 265 if (xmlLang == null) { 266 xmlLang = defaultLanguage; 267 } 268 269 String body = parseElementText(parser); 270 271 if (message.getBody(xmlLang) == null) { 272 message.addBody(xmlLang, body); 273 } 274 break; 275 case "thread": 276 if (thread == null) { 277 thread = parser.nextText(); 278 } 279 break; 280 case "error": 281 message.setError(parseError(parser)); 282 break; 283 default: 284 PacketParserUtils.addExtensionElement(message, parser, elementName, namespace); 285 break; 286 } 287 break; 288 case XmlPullParser.END_TAG: 289 if (parser.getDepth() == initialDepth) { 290 break outerloop; 291 } 292 break; 293 } 294 } 295 296 message.setThread(thread); 297 return message; 298 } 299 300 /** 301 * Returns the textual content of an element as String. After this method returns the parser 302 * position will be END_TAG, following the established pull parser calling convention. 303 * <p> 304 * The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed 305 * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown. 306 * </p> 307 * This method is used for the parts where the XMPP specification requires elements that contain 308 * only text or are the empty element. 309 * 310 * @param parser 311 * @return the textual content of the element as String 312 * @throws XmlPullParserException 313 * @throws IOException 314 */ 315 public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException { 316 assert (parser.getEventType() == XmlPullParser.START_TAG); 317 String res; 318 if (parser.isEmptyElementTag()) { 319 res = ""; 320 } 321 else { 322 // Advance to the text of the Element 323 int event = parser.next(); 324 if (event != XmlPullParser.TEXT) { 325 if (event == XmlPullParser.END_TAG) { 326 // Assume this is the end tag of the start tag at the 327 // beginning of this method. Typical examples where this 328 // happens are body elements containing the empty string, 329 // ie. <body></body>, which appears to be valid XMPP, or a 330 // least it's not explicitly forbidden by RFC 6121 5.2.3 331 return ""; 332 } else { 333 throw new XmlPullParserException( 334 "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed"); 335 } 336 } 337 res = parser.getText(); 338 event = parser.next(); 339 if (event != XmlPullParser.END_TAG) { 340 throw new XmlPullParserException( 341 "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed"); 342 } 343 } 344 return res; 345 } 346 347 /** 348 * Returns the current element as string. 349 * <p> 350 * The parser must be positioned on START_TAG. 351 * </p> 352 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 353 * 354 * @param parser the XML pull parser 355 * @return the element as string 356 * @throws XmlPullParserException 357 * @throws IOException 358 */ 359 public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException { 360 return parseElement(parser, false); 361 } 362 363 public static CharSequence parseElement(XmlPullParser parser, 364 boolean fullNamespaces) throws XmlPullParserException, 365 IOException { 366 assert (parser.getEventType() == XmlPullParser.START_TAG); 367 return parseContentDepth(parser, parser.getDepth(), fullNamespaces); 368 } 369 370 /** 371 * Returns the content of a element. 372 * <p> 373 * The parser must be positioned on the START_TAG of the element which content is going to get 374 * returned. If the current element is the empty element, then the empty string is returned. If 375 * it is a element which contains just text, then just the text is returned. If it contains 376 * nested elements (and text), then everything from the current opening tag to the corresponding 377 * closing tag of the same depth is returned as String. 378 * </p> 379 * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones. 380 * 381 * @param parser the XML pull parser 382 * @return the content of a tag 383 * @throws XmlPullParserException if parser encounters invalid XML 384 * @throws IOException if an IO error occurs 385 */ 386 public static CharSequence parseContent(XmlPullParser parser) 387 throws XmlPullParserException, IOException { 388 assert(parser.getEventType() == XmlPullParser.START_TAG); 389 if (parser.isEmptyElementTag()) { 390 return ""; 391 } 392 // Advance the parser, since we want to parse the content of the current element 393 parser.next(); 394 return parseContentDepth(parser, parser.getDepth(), false); 395 } 396 397 public static CharSequence parseContentDepth(XmlPullParser parser, int depth) 398 throws XmlPullParserException, IOException { 399 return parseContentDepth(parser, depth, false); 400 } 401 402 /** 403 * Returns the content from the current position of the parser up to the closing tag of the 404 * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned, 405 * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of 406 * parent elements will be added to child elements that don't define a different namespace. 407 * <p> 408 * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support 409 * xml-roundtrip. i.e. return a String on getText() on START_TAG and END_TAG. We check for the 410 * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which 411 * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of 412 * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false. 413 * </p> 414 * <p> 415 * In particular Android's XmlPullParser does not support XML_ROUNDTRIP. 416 * </p> 417 * 418 * @param parser 419 * @param depth 420 * @param fullNamespaces 421 * @return the content of the current depth 422 * @throws XmlPullParserException 423 * @throws IOException 424 */ 425 public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException { 426 if (parser.getFeature(FEATURE_XML_ROUNDTRIP)) { 427 return parseContentDepthWithRoundtrip(parser, depth, fullNamespaces); 428 } else { 429 return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces); 430 } 431 } 432 433 private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth, 434 boolean fullNamespaces) throws XmlPullParserException, IOException { 435 XmlStringBuilder xml = new XmlStringBuilder(); 436 int event = parser.getEventType(); 437 boolean isEmptyElement = false; 438 // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines 439 // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again 440 // in a nested element. It's an ugly workaround that has the potential to break things. 441 String namespaceElement = null; 442 outerloop: while (true) { 443 switch (event) { 444 case XmlPullParser.START_TAG: 445 xml.halfOpenElement(parser.getName()); 446 if (namespaceElement == null || fullNamespaces) { 447 String namespace = parser.getNamespace(); 448 if (StringUtils.isNotEmpty(namespace)) { 449 xml.attribute("xmlns", namespace); 450 namespaceElement = parser.getName(); 451 } 452 } 453 for (int i = 0; i < parser.getAttributeCount(); i++) { 454 xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i)); 455 } 456 if (parser.isEmptyElementTag()) { 457 xml.closeEmptyElement(); 458 isEmptyElement = true; 459 } 460 else { 461 xml.rightAngleBracket(); 462 } 463 break; 464 case XmlPullParser.END_TAG: 465 if (isEmptyElement) { 466 // Do nothing as the element was already closed, just reset the flag 467 isEmptyElement = false; 468 } 469 else { 470 xml.closeElement(parser.getName()); 471 } 472 if (namespaceElement != null && namespaceElement.equals(parser.getName())) { 473 // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag' 474 namespaceElement = null; 475 } 476 if (parser.getDepth() <= depth) { 477 // Abort parsing, we are done 478 break outerloop; 479 } 480 break; 481 case XmlPullParser.TEXT: 482 xml.escape(parser.getText()); 483 break; 484 } 485 event = parser.next(); 486 } 487 return xml; 488 } 489 490 private static CharSequence parseContentDepthWithRoundtrip(XmlPullParser parser, int depth, boolean fullNamespaces) 491 throws XmlPullParserException, IOException { 492 StringBuilder sb = new StringBuilder(); 493 int event = parser.getEventType(); 494 outerloop: while (true) { 495 // Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported 496 // twice, so in order to prevent duplication we only add their text when we are on their end tag. 497 if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) { 498 CharSequence text = parser.getText(); 499 if (event == XmlPullParser.TEXT) { 500 // TODO the toString() can be removed in Smack 4.2. 501 text = StringUtils.escapeForXmlText(text.toString()); 502 } 503 sb.append(text); 504 } 505 if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) { 506 break outerloop; 507 } 508 event = parser.next(); 509 } 510 return sb; 511 } 512 513 /** 514 * Parses a presence packet. 515 * 516 * @param parser the XML parser, positioned at the start of a presence packet. 517 * @return a Presence packet. 518 * @throws Exception 519 */ 520 public static Presence parsePresence(XmlPullParser parser) 521 throws Exception { 522 ParserUtils.assertAtStartTag(parser); 523 final int initialDepth = parser.getDepth(); 524 525 Presence.Type type = Presence.Type.available; 526 String typeString = parser.getAttributeValue("", "type"); 527 if (typeString != null && !typeString.equals("")) { 528 type = Presence.Type.fromString(typeString); 529 } 530 Presence presence = new Presence(type); 531 presence.setTo(ParserUtils.getJidAttribute(parser, "to")); 532 presence.setFrom(ParserUtils.getJidAttribute(parser, "from")); 533 presence.setStanzaId(parser.getAttributeValue("", "id")); 534 535 String language = getLanguageAttribute(parser); 536 if (language != null && !"".equals(language.trim())) { 537 // CHECKSTYLE:OFF 538 presence.setLanguage(language); 539 // CHECKSTYLE:ON 540 } 541 542 // Parse sub-elements 543 outerloop: while (true) { 544 int eventType = parser.next(); 545 switch (eventType) { 546 case XmlPullParser.START_TAG: 547 String elementName = parser.getName(); 548 String namespace = parser.getNamespace(); 549 switch(elementName) { 550 case "status": 551 presence.setStatus(parser.nextText()); 552 break; 553 case "priority": 554 int priority = Integer.parseInt(parser.nextText()); 555 presence.setPriority(priority); 556 break; 557 case "show": 558 String modeText = parser.nextText(); 559 if (StringUtils.isNotEmpty(modeText)) { 560 presence.setMode(Presence.Mode.fromString(modeText)); 561 } else { 562 // Some implementations send presence stanzas with a 563 // '<show />' element, which is a invalid XMPP presence 564 // stanza according to RFC 6121 4.7.2.1 565 LOGGER.warning("Empty or null mode text in presence show element form " 566 + presence.getFrom() 567 + " with id '" 568 + presence.getStanzaId() 569 + "' which is invalid according to RFC6121 4.7.2.1"); 570 } 571 break; 572 case "error": 573 presence.setError(parseError(parser)); 574 break; 575 default: 576 // Otherwise, it must be a packet extension. 577 // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of 578 // failing completely here. See SMACK-390 for more information. 579 try { 580 PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace); 581 } catch (Exception e) { 582 LOGGER.warning("Failed to parse extension element in Presence stanza: \"" + e + "\" from: '" 583 + presence.getFrom() + " id: '" + presence.getStanzaId() + "'"); 584 } 585 break; 586 } 587 break; 588 case XmlPullParser.END_TAG: 589 if (parser.getDepth() == initialDepth) { 590 break outerloop; 591 } 592 break; 593 } 594 } 595 return presence; 596 } 597 598 /** 599 * Parses an IQ packet. 600 * 601 * @param parser the XML parser, positioned at the start of an IQ packet. 602 * @return an IQ object. 603 * @throws Exception 604 */ 605 public static IQ parseIQ(XmlPullParser parser) throws Exception { 606 ParserUtils.assertAtStartTag(parser); 607 final int initialDepth = parser.getDepth(); 608 IQ iqPacket = null; 609 XMPPError.Builder error = null; 610 611 final String id = parser.getAttributeValue("", "id"); 612 final Jid to = ParserUtils.getJidAttribute(parser, "to"); 613 final Jid from = ParserUtils.getJidAttribute(parser, "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 Exception if an exception occurs while parsing the packet. 798 */ 799 public static StreamError parseStreamError(XmlPullParser parser) throws Exception { 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 Exception 848 */ 849 public static XMPPError.Builder parseError(XmlPullParser parser) 850 throws Exception { 851 final int initialDepth = parser.getDepth(); 852 Map<String, String> descriptiveTexts = null; 853 List<ExtensionElement> extensions = new ArrayList<ExtensionElement>(); 854 XMPPError.Builder builder = XMPPError.getBuilder(); 855 856 // Parse the error header 857 builder.setType(XMPPError.Type.fromString(parser.getAttributeValue("", "type"))); 858 builder.setErrorGenerator(parser.getAttributeValue("", "by")); 859 860 outerloop: while (true) { 861 int eventType = parser.next(); 862 switch (eventType) { 863 case XmlPullParser.START_TAG: 864 String name = parser.getName(); 865 String namespace = parser.getNamespace(); 866 switch (namespace) { 867 case XMPPError.NAMESPACE: 868 switch (name) { 869 case Stanza.TEXT: 870 descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); 871 break; 872 default: 873 builder.setCondition(XMPPError.Condition.fromString(name)); 874 if (!parser.isEmptyElementTag()) { 875 builder.setConditionText(parser.nextText()); 876 } 877 break; 878 } 879 break; 880 default: 881 PacketParserUtils.addExtensionElement(extensions, parser, name, namespace); 882 } 883 break; 884 case XmlPullParser.END_TAG: 885 if (parser.getDepth() == initialDepth) { 886 break outerloop; 887 } 888 } 889 } 890 builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts); 891 return builder; 892 } 893 894 /** 895 * Parse an extension element. 896 * @deprecated use {@link #parseExtensionElement(String, String, XmlPullParser)} instead. 897 */ 898 @Deprecated 899 public static ExtensionElement parsePacketExtension(String elementName, String namespace, 900 XmlPullParser parser) throws Exception { 901 return parseExtensionElement(elementName, namespace, parser); 902 } 903 904 /** 905 * Parses an extension element. 906 * 907 * @param elementName the XML element name of the extension element. 908 * @param namespace the XML namespace of the stanza(/packet) extension. 909 * @param parser the XML parser, positioned at the starting element of the extension. 910 * @return an extension element. 911 */ 912 public static ExtensionElement parseExtensionElement(String elementName, String namespace, 913 XmlPullParser parser) throws Exception { 914 ParserUtils.assertAtStartTag(parser); 915 // See if a provider is registered to handle the extension. 916 ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace); 917 if (provider != null) { 918 return provider.parse(parser); 919 } 920 921 // No providers registered, so use a default extension. 922 return StandardExtensionElementProvider.INSTANCE.parse(parser); 923 } 924 925 public static StartTls parseStartTlsFeature(XmlPullParser parser) 926 throws XmlPullParserException, IOException { 927 assert (parser.getEventType() == XmlPullParser.START_TAG); 928 assert (parser.getNamespace().equals(StartTls.NAMESPACE)); 929 int initalDepth = parser.getDepth(); 930 boolean required = false; 931 outerloop: while (true) { 932 int event = parser.next(); 933 switch (event) { 934 case XmlPullParser.START_TAG: 935 String name = parser.getName(); 936 switch (name) { 937 case "required": 938 required = true; 939 break; 940 } 941 break; 942 case XmlPullParser.END_TAG: 943 if (parser.getDepth() == initalDepth) { 944 break outerloop; 945 } 946 } 947 } 948 assert(parser.getEventType() == XmlPullParser.END_TAG); 949 return new StartTls(required); 950 } 951 952 public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException { 953 ParserUtils.assertAtStartTag(parser); 954 final int initialDepth = parser.getDepth(); 955 boolean optional = false; 956 if (!parser.isEmptyElementTag()) { 957 outerloop: while(true) { 958 int event = parser.next(); 959 switch (event) { 960 case XmlPullParser.START_TAG: 961 String name = parser.getName(); 962 switch (name) { 963 case Session.Feature.OPTIONAL_ELEMENT: 964 optional = true; 965 break; 966 } 967 break; 968 case XmlPullParser.END_TAG: 969 if (parser.getDepth() == initialDepth) { 970 break outerloop; 971 } 972 } 973 } 974 } 975 return new Session.Feature(optional); 976 977 } 978 private static String getLanguageAttribute(XmlPullParser parser) { 979 // CHECKSTYLE:OFF 980 for (int i = 0; i < parser.getAttributeCount(); i++) { 981 String attributeName = parser.getAttributeName(i); 982 if ( "xml:lang".equals(attributeName) || 983 // CHECKSTYLE:ON 984 ("lang".equals(attributeName) && 985 "xml".equals(parser.getAttributePrefix(i)))) { 986 // CHECKSTYLE:OFF 987 return parser.getAttributeValue(i); 988 } 989 } 990 return null; 991 // CHECKSTYLE:ON 992 } 993 994 @Deprecated 995 public static void addPacketExtension(Stanza packet, XmlPullParser parser) throws Exception { 996 addExtensionElement(packet, parser); 997 } 998 999 @Deprecated 1000 public static void addPacketExtension(Stanza packet, XmlPullParser parser, String elementName, String namespace) 1001 throws Exception { 1002 addExtensionElement(packet, parser, elementName, namespace); 1003 } 1004 1005 @Deprecated 1006 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser) 1007 throws Exception { 1008 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1009 } 1010 1011 @Deprecated 1012 public static void addPacketExtension(Collection<ExtensionElement> collection, XmlPullParser parser, 1013 String elementName, String namespace) throws Exception { 1014 addExtensionElement(collection, parser, elementName, namespace); 1015 } 1016 1017 1018 public static void addExtensionElement(Stanza packet, XmlPullParser parser) 1019 throws Exception { 1020 ParserUtils.assertAtStartTag(parser); 1021 addExtensionElement(packet, parser, parser.getName(), parser.getNamespace()); 1022 } 1023 1024 public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName, 1025 String namespace) throws Exception{ 1026 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1027 packet.addExtension(packetExtension); 1028 } 1029 1030 public static void addExtensionElement(Collection<ExtensionElement> collection, 1031 XmlPullParser parser) throws Exception { 1032 addExtensionElement(collection, parser, parser.getName(), parser.getNamespace()); 1033 } 1034 1035 public static void addExtensionElement(Collection<ExtensionElement> collection, 1036 XmlPullParser parser, String elementName, String namespace) 1037 throws Exception { 1038 ExtensionElement packetExtension = parseExtensionElement(elementName, namespace, parser); 1039 collection.add(packetExtension); 1040 } 1041}