• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igniterealtime / Smack / #2856

pending completion
#2856

push

github-actions

web-flow
Merge pull request #561 from Flowdalic/github-ci

[github ci] Java 15 → 17

16274 of 41793 relevant lines covered (38.94%)

0.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.86
/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
1
/**
2
 *
3
 * Copyright 2003-2007 Jive Software, 2019-2023 Florian Schmaus.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.jivesoftware.smack.util;
18

19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.InputStreamReader;
22
import java.io.Reader;
23
import java.io.StringReader;
24
import java.nio.charset.StandardCharsets;
25
import java.util.ArrayList;
26
import java.util.Collection;
27
import java.util.HashMap;
28
import java.util.LinkedList;
29
import java.util.List;
30
import java.util.Map;
31
import java.util.logging.Level;
32
import java.util.logging.Logger;
33

34
import org.jivesoftware.smack.compress.packet.Compress;
35
import org.jivesoftware.smack.packet.EmptyResultIQ;
36
import org.jivesoftware.smack.packet.ErrorIQ;
37
import org.jivesoftware.smack.packet.ExtensionElement;
38
import org.jivesoftware.smack.packet.IQ;
39
import org.jivesoftware.smack.packet.IqData;
40
import org.jivesoftware.smack.packet.Message;
41
import org.jivesoftware.smack.packet.MessageBuilder;
42
import org.jivesoftware.smack.packet.Presence;
43
import org.jivesoftware.smack.packet.PresenceBuilder;
44
import org.jivesoftware.smack.packet.Session;
45
import org.jivesoftware.smack.packet.Stanza;
46
import org.jivesoftware.smack.packet.StanzaBuilder;
47
import org.jivesoftware.smack.packet.StanzaError;
48
import org.jivesoftware.smack.packet.StartTls;
49
import org.jivesoftware.smack.packet.StreamError;
50
import org.jivesoftware.smack.packet.UnparsedIQ;
51
import org.jivesoftware.smack.packet.XmlElement;
52
import org.jivesoftware.smack.packet.XmlEnvironment;
53
import org.jivesoftware.smack.parsing.SmackParsingException;
54
import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
55
import org.jivesoftware.smack.provider.ExtensionElementProvider;
56
import org.jivesoftware.smack.provider.IqProvider;
57
import org.jivesoftware.smack.provider.ProviderManager;
58
import org.jivesoftware.smack.xml.SmackXmlParser;
59
import org.jivesoftware.smack.xml.XmlPullParser;
60
import org.jivesoftware.smack.xml.XmlPullParserException;
61

62
import org.jxmpp.jid.Jid;
63
import org.jxmpp.stringprep.XmppStringprepException;
64

65
/**
66
 * Utility class that helps to parse packets. Any parsing packets method that must be shared
67
 * between many clients must be placed in this utility class.
68
 *
69
 * @author Gaston Dombiak
70
 */
71
public class PacketParserUtils {
1✔
72
    private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());
1✔
73

74
    // TODO: Rename argument name from 'stanza' to 'element'.
75
    public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
76
        return getParserFor(new StringReader(stanza));
1✔
77
    }
78

79
    public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException, IOException {
80
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
1✔
81
        return getParserFor(inputStreamReader);
1✔
82
    }
83

84
    public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
85
        XmlPullParser parser = SmackXmlParser.newXmlParser(reader);
1✔
86
        ParserUtils.forwardToStartElement(parser);
1✔
87
        return parser;
1✔
88
    }
89

90
    @SuppressWarnings("unchecked")
91
    public static <S extends Stanza> S parseStanza(String stanza) throws XmlPullParserException, SmackParsingException, IOException {
92
        return (S) parseStanza(getParserFor(stanza), XmlEnvironment.EMPTY);
1✔
93
    }
94

95
    /**
96
     * Tries to parse and return either a Message, IQ or Presence stanza.
97
     *
98
     * connection is optional and is used to return feature-not-implemented errors for unknown IQ stanzas.
99
     *
100
     * @param parser TODO javadoc me please
101
     * @param outerXmlEnvironment the outer XML environment (optional).
102
     * @return a stanza which is either a Message, IQ or Presence.
103
     * @throws XmlPullParserException if an error in the XML parser occurred.
104
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
105
     * @throws IOException if an I/O error occurred.
106
     */
107
    public static Stanza parseStanza(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, SmackParsingException, IOException {
108
        ParserUtils.assertAtStartTag(parser);
1✔
109
        final String name = parser.getName();
1✔
110
        switch (name) {
1✔
111
        case Message.ELEMENT:
112
            return parseMessage(parser, outerXmlEnvironment);
1✔
113
        case IQ.IQ_ELEMENT:
114
            return parseIQ(parser, outerXmlEnvironment);
1✔
115
        case Presence.ELEMENT:
116
            return parsePresence(parser, outerXmlEnvironment);
×
117
        default:
118
            throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
×
119
        }
120
    }
121

122
    private interface StanzaBuilderSupplier<SB extends StanzaBuilder<?>> {
123
        SB get(String stanzaId);
124
    }
125

126
    private static <SB extends StanzaBuilder<?>> SB parseCommonStanzaAttributes(StanzaBuilderSupplier<SB> stanzaBuilderSupplier, XmlPullParser parser, XmlEnvironment xmlEnvironment) throws XmppStringprepException {
127
        String id = parser.getAttributeValue("id");
1✔
128

129
        SB stanzaBuilder = stanzaBuilderSupplier.get(id);
1✔
130

131
        Jid to = ParserUtils.getJidAttribute(parser, "to");
1✔
132
        stanzaBuilder.to(to);
1✔
133

134
        Jid from = ParserUtils.getJidAttribute(parser, "from");
1✔
135
        stanzaBuilder.from(from);
1✔
136

137
        String language = ParserUtils.getXmlLang(parser, xmlEnvironment);
1✔
138
        stanzaBuilder.setLanguage(language);
1✔
139

140
        return stanzaBuilder;
1✔
141
    }
142

143
    public static Message parseMessage(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
144
        return parseMessage(parser, XmlEnvironment.EMPTY);
1✔
145
    }
146

147
    /**
148
     * Parses a message packet.
149
     *
150
     * @param parser the XML parser, positioned at the start of a message packet.
151
     * @param outerXmlEnvironment the outer XML environment (optional).
152
     * @return a Message packet.
153
     * @throws XmlPullParserException if an error in the XML parser occurred.
154
     * @throws IOException if an I/O error occurred.
155
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
156
     */
157
    public static Message parseMessage(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
158
        ParserUtils.assertAtStartTag(parser);
1✔
159
        assert parser.getName().equals(Message.ELEMENT);
1✔
160

161
        XmlEnvironment messageXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
1✔
162
        final int initialDepth = parser.getDepth();
1✔
163

164
        MessageBuilder message = parseCommonStanzaAttributes(id -> {
1✔
165
            return StanzaBuilder.buildMessage(id);
1✔
166
        }, parser, outerXmlEnvironment);
167

168
        String typeString = parser.getAttributeValue("", "type");
1✔
169
        if (typeString != null) {
1✔
170
            message.ofType(Message.Type.fromString(typeString));
1✔
171
        }
172

173
        // Parse sub-elements. We include extra logic to make sure the values
174
        // are only read once. This is because it's possible for the names to appear
175
        // in arbitrary sub-elements.
176
        outerloop: while (true) {
177
            XmlPullParser.Event eventType = parser.next();
1✔
178
            switch (eventType) {
1✔
179
            case START_ELEMENT:
180
                String elementName = parser.getName();
1✔
181
                String namespace = parser.getNamespace();
1✔
182
                switch (elementName) {
1✔
183
                case "error":
184
                    message.setError(parseError(parser, messageXmlEnvironment));
1✔
185
                    break;
1✔
186
                 default:
187
                     XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, messageXmlEnvironment);
1✔
188
                    message.addExtension(extensionElement);
1✔
189
                    break;
190
                }
191
                break;
1✔
192
            case END_ELEMENT:
193
                if (parser.getDepth() == initialDepth) {
1✔
194
                    break outerloop;
1✔
195
                }
196
                break;
197
            default: // fall out
198
            }
199
        }
1✔
200

201
        // TODO check for duplicate body elements. This means we need to check for duplicate xml:lang pairs and for
202
        // situations where we have a body element with an explicit xml lang set and once where the value is inherited
203
        // and both values are equal.
204

205
        return message.build();
1✔
206
    }
207

208
    /**
209
     * Returns the textual content of an element as String. After this method returns the parser
210
     * position will be END_ELEMENT, following the established pull parser calling convention.
211
     * <p>
212
     * The parser must be positioned on a START_ELEMENT of an element which MUST NOT contain Mixed
213
     * Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
214
     * </p>
215
     * This method is used for the parts where the XMPP specification requires elements that contain
216
     * only text or are the empty element.
217
     *
218
     * @param parser TODO javadoc me please
219
     * @return the textual content of the element as String
220
     * @throws XmlPullParserException if an error in the XML parser occurred.
221
     * @throws IOException if an I/O error occurred.
222
     */
223
    public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
224
        assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
1✔
225
        String res;
226
        // Advance to the text of the Element
227
        XmlPullParser.Event event = parser.next();
1✔
228
        if (event != XmlPullParser.Event.TEXT_CHARACTERS) {
1✔
229
            if (event == XmlPullParser.Event.END_ELEMENT) {
1✔
230
                // Assume this is the end tag of the start tag at the
231
                // beginning of this method. Typical examples where this
232
                // happens are body elements containing the empty string,
233
                // ie. <body></body>, which appears to be valid XMPP, or a
234
                // least it's not explicitly forbidden by RFC 6121 5.2.3
235
                return "";
1✔
236
            } else {
237
                throw new XmlPullParserException(
1✔
238
                                "Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
239
            }
240
        }
241
        res = parser.getText();
1✔
242
        event = parser.next();
1✔
243
        if (event != XmlPullParser.Event.END_ELEMENT) {
1✔
244
            throw new XmlPullParserException(
×
245
                            "Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
246
        }
247
        return res;
1✔
248
    }
249

250
    /**
251
     * Returns the current element as string.
252
     * <p>
253
     * The parser must be positioned on START_ELEMENT.
254
     * </p>
255
     * Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
256
     *
257
     * @param parser the XML pull parser
258
     * @return the element as string
259
     * @throws XmlPullParserException if an error in the XML parser occurred.
260
     * @throws IOException if an I/O error occurred.
261
     */
262
    public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
263
        return parseElement(parser, false);
1✔
264
    }
265

266
    public static CharSequence parseElement(XmlPullParser parser,
267
                    boolean fullNamespaces) throws XmlPullParserException,
268
                    IOException {
269
        assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
1✔
270
        return parseContentDepth(parser, parser.getDepth(), fullNamespaces);
1✔
271
    }
272

273
    public static CharSequence parseContentDepth(XmlPullParser parser, int depth)
274
                    throws XmlPullParserException, IOException {
275
        return parseContentDepth(parser, depth, false);
×
276
    }
277

278
    /**
279
     * Returns the content from the current position of the parser up to the closing tag of the
280
     * given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
281
     * not nested ones, if <code>fullNamespaces</code> is false. If it is true, then namespaces of
282
     * parent elements will be added to child elements that don't define a different namespace.
283
     * <p>
284
     * This method is able to parse the content with MX- and KXmlParser. KXmlParser does not support
285
     * xml-roundtrip. i.e. return a String on getText() on START_ELEMENT and END_ELEMENT. We check for the
286
     * XML_ROUNDTRIP feature. If it's not found we are required to work around this limitation, which
287
     * results in only partial support for XML namespaces ("xmlns"): Only the outermost namespace of
288
     * elements will be included in the resulting String, if <code>fullNamespaces</code> is set to false.
289
     * </p>
290
     * <p>
291
     * In particular Android's XmlPullParser does not support XML_ROUNDTRIP.
292
     * </p>
293
     *
294
     * @param parser TODO javadoc me please
295
     * @param depth TODO javadoc me please
296
     * @param fullNamespaces TODO javadoc me please
297
     * @return the content of the current depth
298
     * @throws XmlPullParserException if an error in the XML parser occurred.
299
     * @throws IOException if an I/O error occurred.
300
     */
301
    public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
302
        if (parser.supportsRoundtrip()) {
1✔
303
            return parseContentDepthWithRoundtrip(parser, depth);
1✔
304
        } else {
305
            return parseContentDepthWithoutRoundtrip(parser, depth, fullNamespaces);
1✔
306
        }
307
    }
308

309
    private static CharSequence parseContentDepthWithoutRoundtrip(XmlPullParser parser, int depth,
310
                    boolean fullNamespaces) throws XmlPullParserException, IOException {
311
        XmlStringBuilder xml = new XmlStringBuilder();
1✔
312
        XmlPullParser.Event event = parser.getEventType();
1✔
313
        // XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
314
        // it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
315
        // in a nested element. It's an ugly workaround that has the potential to break things.
316
        String namespaceElement = null;
1✔
317
        boolean startElementJustSeen = false;
1✔
318
        outerloop: while (true) {
319
            switch (event) {
1✔
320
            case START_ELEMENT:
321
                if (startElementJustSeen) {
1✔
322
                    xml.rightAngleBracket();
1✔
323
                }
324
                else {
325
                    startElementJustSeen = true;
1✔
326
                }
327
                xml.halfOpenElement(parser.getName());
1✔
328
                if (namespaceElement == null || fullNamespaces) {
1✔
329
                    String namespace = parser.getNamespace();
1✔
330
                    if (StringUtils.isNotEmpty(namespace)) {
1✔
331
                        xml.attribute("xmlns", namespace);
1✔
332
                        namespaceElement = parser.getName();
1✔
333
                    }
334
                }
335
                for (int i = 0; i < parser.getAttributeCount(); i++) {
1✔
336
                    xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
1✔
337
                }
338
                break;
1✔
339
            case END_ELEMENT:
340
                if (startElementJustSeen) {
1✔
341
                    xml.closeEmptyElement();
1✔
342
                    startElementJustSeen = false;
1✔
343
                }
344
                else {
345
                    xml.closeElement(parser.getName());
1✔
346
                }
347
                if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
1✔
348
                    // We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
349
                    namespaceElement = null;
1✔
350
                }
351
                if (parser.getDepth() <= depth) {
1✔
352
                    // Abort parsing, we are done
353
                    break outerloop;
1✔
354
                }
355
                break;
356
            case TEXT_CHARACTERS:
357
                if (startElementJustSeen) {
1✔
358
                    startElementJustSeen = false;
1✔
359
                    xml.rightAngleBracket();
1✔
360
                }
361
                xml.escape(parser.getText());
1✔
362
                break;
1✔
363
            default:
364
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
365
                break;
366
            }
367
            event = parser.next();
1✔
368
        }
369
        return xml;
1✔
370
    }
371

372
    private static XmlStringBuilder parseContentDepthWithRoundtrip(XmlPullParser parser, int depth)
373
                    throws XmlPullParserException, IOException {
374
        XmlStringBuilder sb = new XmlStringBuilder();
1✔
375
        XmlPullParser.Event event = parser.getEventType();
1✔
376
        boolean startElementJustSeen = false;
1✔
377
        outerloop: while (true) {
378
            switch (event) {
1✔
379
            case START_ELEMENT:
380
                startElementJustSeen = true;
1✔
381
                String openElementTag = parser.getText();
1✔
382
                sb.append(openElementTag);
1✔
383
                break;
1✔
384
            case END_ELEMENT:
385
                boolean isEmptyElement = false;
1✔
386
                if (startElementJustSeen) {
1✔
387
                    isEmptyElement = true;
1✔
388
                    startElementJustSeen = false;
1✔
389
                }
390
                if (!isEmptyElement) {
1✔
391
                    String text = parser.getText();
1✔
392
                    sb.append(text);
1✔
393
                }
394
                if (parser.getDepth() <= depth) {
1✔
395
                    break outerloop;
1✔
396
                }
397
                break;
398
            default:
399
                startElementJustSeen = false;
1✔
400
                CharSequence text = parser.getText();
1✔
401
                if (event == XmlPullParser.Event.TEXT_CHARACTERS) {
1✔
402
                    text = StringUtils.escapeForXml(text);
1✔
403
                }
404
                sb.append(text);
1✔
405
                break;
406
            }
407
            event = parser.next();
1✔
408
        }
409
        return sb;
1✔
410
    }
411

412
    public static Presence parsePresence(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
413
        return parsePresence(parser, XmlEnvironment.EMPTY);
1✔
414
    }
415

416
    /**
417
     * Parses a presence packet.
418
     *
419
     * @param parser the XML parser, positioned at the start of a presence packet.
420
     * @param outerXmlEnvironment the outer XML environment (optional).
421
     * @return a Presence packet.
422
     * @throws IOException if an I/O error occurred.
423
     * @throws XmlPullParserException if an error in the XML parser occurred.
424
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
425
     */
426
    public static Presence parsePresence(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
427
        ParserUtils.assertAtStartTag(parser);
1✔
428
        final int initialDepth = parser.getDepth();
1✔
429
        XmlEnvironment presenceXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
1✔
430

431
        PresenceBuilder presence = parseCommonStanzaAttributes(
1✔
432
                        stanzaId -> StanzaBuilder.buildPresence(stanzaId), parser, outerXmlEnvironment);
1✔
433

434
        Presence.Type type = Presence.Type.available;
1✔
435
        String typeString = parser.getAttributeValue("", "type");
1✔
436
        if (typeString != null && !typeString.equals("")) {
1✔
437
            type = Presence.Type.fromString(typeString);
1✔
438
        }
439

440
        presence.ofType(type);
1✔
441

442
        // Parse sub-elements
443
        outerloop: while (true) {
444
            XmlPullParser.Event eventType = parser.next();
1✔
445
            switch (eventType) {
1✔
446
            case START_ELEMENT:
447
                String elementName = parser.getName();
1✔
448
                String namespace = parser.getNamespace();
1✔
449
                switch (elementName) {
1✔
450
                case "status":
451
                    presence.setStatus(parser.nextText());
1✔
452
                    break;
1✔
453
                case "priority":
454
                    Byte priority = ParserUtils.getByteAttributeFromNextText(parser);
1✔
455
                    presence.setPriority(priority);
1✔
456
                    break;
1✔
457
                case "show":
458
                    String modeText = parser.nextText();
1✔
459
                    if (StringUtils.isNotEmpty(modeText)) {
1✔
460
                        presence.setMode(Presence.Mode.fromString(modeText));
1✔
461
                    } else {
462
                        // Some implementations send presence stanzas with a
463
                        // '<show />' element, which is a invalid XMPP presence
464
                        // stanza according to RFC 6121 4.7.2.1
465
                        LOGGER.warning("Empty or null mode text in presence show element form "
×
466
                                        + presence
467
                                        + "' which is invalid according to RFC6121 4.7.2.1");
468
                    }
469
                    break;
×
470
                case "error":
471
                    presence.setError(parseError(parser, presenceXmlEnvironment));
×
472
                    break;
×
473
                default:
474
                // Otherwise, it must be a packet extension.
475
                    // Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
476
                    // failing completely here. See SMACK-390 for more information.
477
                    try {
478
                        XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, presenceXmlEnvironment);
1✔
479
                        presence.addExtension(extensionElement);
1✔
480
                    } catch (Exception e) {
1✔
481
                        LOGGER.log(Level.WARNING, "Failed to parse extension element in Presence stanza: " + presence, e);
1✔
482
                    }
1✔
483
                    break;
484
                }
485
                break;
1✔
486
            case END_ELEMENT:
487
                if (parser.getDepth() == initialDepth) {
1✔
488
                    break outerloop;
1✔
489
                }
490
                break;
491
            default:
492
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
493
                break;
494
            }
495
        }
1✔
496

497
        return presence.build();
1✔
498
    }
499

500
    public static IQ parseIQ(XmlPullParser parser) throws Exception {
501
        return parseIQ(parser, null);
1✔
502
    }
503

504
    public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException {
505
        final String id = parser.getAttributeValue("", "id");
1✔
506
        IqData iqData = StanzaBuilder.buildIqData(id);
1✔
507

508
        final Jid to = ParserUtils.getJidAttribute(parser, "to");
1✔
509
        iqData.to(to);
1✔
510

511
        final Jid from = ParserUtils.getJidAttribute(parser, "from");
1✔
512
        iqData.from(from);
1✔
513

514
        String typeString = parser.getAttributeValue("", "type");
1✔
515
        final IQ.Type type = IQ.Type.fromString(typeString);
1✔
516
        iqData.ofType(type);
1✔
517

518
        return iqData;
1✔
519
    }
520

521
    /**
522
     * Parses an IQ packet.
523
     *
524
     * @param parser the XML parser, positioned at the start of an IQ packet.
525
     * @param outerXmlEnvironment the outer XML environment (optional).
526
     * @return an IQ object.
527
     * @throws XmlPullParserException if an error in the XML parser occurred.
528
     * @throws XmppStringprepException if the provided string is invalid.
529
     * @throws IOException if an I/O error occurred.
530
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
531
     */
532
    public static IQ parseIQ(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, XmppStringprepException, IOException, SmackParsingException {
533
        ParserUtils.assertAtStartTag(parser);
1✔
534
        final int initialDepth = parser.getDepth();
1✔
535
        XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
1✔
536
        IQ iqPacket = null;
1✔
537
        StanzaError error = null;
1✔
538
        IqData iqData = parseIqData(parser);
1✔
539

540
        outerloop: while (true) {
541
            XmlPullParser.Event eventType = parser.next();
1✔
542

543
            switch (eventType) {
1✔
544
            case START_ELEMENT:
545
                String elementName = parser.getName();
1✔
546
                String namespace = parser.getNamespace();
1✔
547
                switch (elementName) {
1✔
548
                case "error":
549
                    error = PacketParserUtils.parseError(parser, iqXmlEnvironment);
1✔
550
                    break;
1✔
551
                // Otherwise, see if there is a registered provider for
552
                // this element name and namespace.
553
                default:
554
                    IqProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
1✔
555
                    if (provider != null) {
1✔
556
                            iqPacket = provider.parse(parser, iqData, outerXmlEnvironment);
1✔
557
                    }
558
                    // Note that if we reach this code, it is guaranteed that the result IQ contained a child element
559
                    // (RFC 6120 § 8.2.3 6) because otherwise we would have reached the END_ELEMENT first.
560
                    else {
561
                        // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
562
                        // so that the content of the IQ can be examined later on
563
                        iqPacket = new UnparsedIQ(elementName, namespace, parseElement(parser));
1✔
564
                    }
565
                    break;
566
                }
567
                break;
1✔
568
            case END_ELEMENT:
569
                if (parser.getDepth() == initialDepth) {
1✔
570
                    break outerloop;
1✔
571
                }
572
                break;
573
            default:
574
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
575
                break;
576
            }
577
        }
1✔
578
        // Decide what to do when an IQ packet was not understood
579
        if (iqPacket == null) {
1✔
580
            switch (iqData.getType()) {
×
581
            case error:
582
                // If an IQ packet wasn't created above, create an empty error IQ packet.
583
                iqPacket = ErrorIQ.builder(error, iqData).build();
×
584
                // The following return is simply to avoid setting iqData again below.
585
                return iqPacket;
×
586
            case result:
587
                iqPacket = new EmptyResultIQ();
×
588
                break;
×
589
            default:
590
                break;
591
            }
592
        }
593

594
        // Set basic values on the iq packet.
595
        iqPacket.setStanzaId(iqData.getStanzaId());
1✔
596
        iqPacket.setTo(iqData.getTo());
1✔
597
        iqPacket.setFrom(iqData.getFrom());
1✔
598
        iqPacket.setType(iqData.getType());
1✔
599
        iqPacket.setError(error);
1✔
600

601
        return iqPacket;
1✔
602
    }
603

604
    /**
605
     * Parse the available SASL mechanisms reported from the server.
606
     *
607
     * @param parser the XML parser, positioned at the start of the mechanisms stanza.
608
     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
609
     * @throws IOException if an I/O error occurred.
610
     * @throws XmlPullParserException if an error in the XML parser occurred.
611
     */
612
    public static Collection<String> parseMechanisms(XmlPullParser parser)
613
                    throws XmlPullParserException, IOException {
614
        List<String> mechanisms = new ArrayList<String>();
×
615
        boolean done = false;
×
616
        while (!done) {
×
617
            XmlPullParser.Event eventType = parser.next();
×
618

619
            if (eventType == XmlPullParser.Event.START_ELEMENT) {
×
620
                String elementName = parser.getName();
×
621
                if (elementName.equals("mechanism")) {
×
622
                    mechanisms.add(parser.nextText());
×
623
                }
624
            }
×
625
            else if (eventType == XmlPullParser.Event.END_ELEMENT) {
×
626
                if (parser.getName().equals("mechanisms")) {
×
627
                    done = true;
×
628
                }
629
            }
630
        }
×
631
        return mechanisms;
×
632
    }
633

634
    /**
635
     * Parse the Compression Feature reported from the server.
636
     *
637
     * @param parser the XML parser, positioned at the start of the compression stanza.
638
     * @return The CompressionFeature stream element
639
     * @throws IOException if an I/O error occurred.
640
     * @throws XmlPullParserException if an exception occurs while parsing the stanza.
641
     */
642
    public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
643
                    throws IOException, XmlPullParserException {
644
        assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
×
645
        String name;
646
        final int initialDepth = parser.getDepth();
×
647
        List<String> methods = new LinkedList<>();
×
648
        outerloop: while (true) {
649
            XmlPullParser.Event eventType = parser.next();
×
650
            switch (eventType) {
×
651
            case START_ELEMENT:
652
                name = parser.getName();
×
653
                switch (name) {
×
654
                case "method":
655
                    methods.add(parser.nextText());
×
656
                    break;
657
                }
658
                break;
×
659
            case END_ELEMENT:
660
                name = parser.getName();
×
661
                switch (name) {
×
662
                case Compress.Feature.ELEMENT:
663
                    if (parser.getDepth() == initialDepth) {
×
664
                        break outerloop;
×
665
                    }
666
                }
667
                break;
×
668
            default:
669
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
670
                break;
671
            }
672
        }
×
673
        assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT;
×
674
        assert parser.getDepth() == initialDepth;
×
675
        return new Compress.Feature(methods);
×
676
    }
677

678
    public static Map<String, String> parseDescriptiveTexts(XmlPullParser parser, Map<String, String> descriptiveTexts)
679
                    throws XmlPullParserException, IOException {
680
        if (descriptiveTexts == null) {
1✔
681
            descriptiveTexts = new HashMap<>();
1✔
682
        }
683
        String xmllang = ParserUtils.getXmlLang(parser);
1✔
684
        if (xmllang == null) {
1✔
685
            // XMPPError assumes the default locale, 'en', or the empty string.
686
            // Establish the invariant that there is never null as a key.
687
            xmllang = "";
1✔
688
        }
689

690
        String text = parser.nextText();
1✔
691
        String previousValue = descriptiveTexts.put(xmllang, text);
1✔
692
        assert previousValue == null;
1✔
693
        return descriptiveTexts;
1✔
694
    }
695

696
    public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
697
        return parseStreamError(parser, null);
1✔
698
    }
699

700
    /**
701
     * Parses stream error packets.
702
     *
703
     * @param parser the XML parser.
704
     * @param outerXmlEnvironment the outer XML environment (optional).
705
     * @return an stream error packet.
706
     * @throws IOException if an I/O error occurred.
707
     * @throws XmlPullParserException if an error in the XML parser occurred.
708
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
709
     */
710
    public static StreamError parseStreamError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
711
        final int initialDepth = parser.getDepth();
1✔
712
        List<XmlElement> extensions = new ArrayList<>();
1✔
713
        Map<String, String> descriptiveTexts = null;
1✔
714
        StreamError.Condition condition = null;
1✔
715
        String conditionText = null;
1✔
716
        XmlEnvironment streamErrorXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
1✔
717
        outerloop: while (true) {
718
            XmlPullParser.Event eventType = parser.next();
1✔
719
            switch (eventType) {
1✔
720
            case START_ELEMENT:
721
                String name = parser.getName();
1✔
722
                String namespace = parser.getNamespace();
1✔
723
                switch (namespace) {
1✔
724
                case StreamError.NAMESPACE:
725
                    switch (name) {
1✔
726
                    case "text":
727
                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
1✔
728
                        break;
1✔
729
                    default:
730
                        // If it's not a text element, that is qualified by the StreamError.NAMESPACE,
731
                        // then it has to be the stream error code
732
                        condition = StreamError.Condition.fromString(name);
1✔
733
                        conditionText = parser.nextText();
1✔
734
                        if (conditionText.isEmpty()) {
1✔
735
                            conditionText = null;
1✔
736
                        }
737
                        break;
738
                    }
739
                    break;
1✔
740
                default:
741
                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace, streamErrorXmlEnvironment);
1✔
742
                    break;
743
                }
744
                break;
1✔
745
            case END_ELEMENT:
746
                if (parser.getDepth() == initialDepth) {
1✔
747
                    break outerloop;
1✔
748
                }
749
                break;
750
            default:
751
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
752
                break;
753
            }
754
        }
1✔
755
        return new StreamError(condition, conditionText, descriptiveTexts, extensions);
1✔
756
    }
757

758
    public static StanzaError parseError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
759
        return parseError(parser, null);
1✔
760
    }
761

762
    /**
763
     * Parses error sub-packets.
764
     *
765
     * @param parser the XML parser.
766
     * @param outerXmlEnvironment the outer XML environment (optional).
767
     * @return an error sub-packet.
768
     * @throws IOException if an I/O error occurred.
769
     * @throws XmlPullParserException if an error in the XML parser occurred.
770
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
771
     */
772
    public static StanzaError parseError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
773
        final int initialDepth = parser.getDepth();
1✔
774
        Map<String, String> descriptiveTexts = null;
1✔
775
        XmlEnvironment stanzaErrorXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
1✔
776
        List<XmlElement> extensions = new ArrayList<>();
1✔
777
        StanzaError.Builder builder = StanzaError.getBuilder();
1✔
778

779
        // Parse the error header
780
        builder.setType(StanzaError.Type.fromString(parser.getAttributeValue("", "type")));
1✔
781
        builder.setErrorGenerator(parser.getAttributeValue("", "by"));
1✔
782

783
        outerloop: while (true) {
784
            XmlPullParser.Event eventType = parser.next();
1✔
785
            switch (eventType) {
1✔
786
            case START_ELEMENT:
787
                String name = parser.getName();
1✔
788
                String namespace = parser.getNamespace();
1✔
789
                switch (namespace) {
1✔
790
                case StanzaError.ERROR_CONDITION_AND_TEXT_NAMESPACE:
791
                    switch (name) {
1✔
792
                    case Stanza.TEXT:
793
                        descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
1✔
794
                        break;
1✔
795
                    default:
796
                        builder.setCondition(StanzaError.Condition.fromString(name));
1✔
797
                        String conditionText = parser.nextText();
1✔
798
                        if (!conditionText.isEmpty()) {
1✔
799
                            builder.setConditionText(conditionText);
×
800
                        }
801
                        break;
802
                    }
803
                    break;
1✔
804
                default:
805
                    PacketParserUtils.addExtensionElement(extensions, parser, name, namespace, stanzaErrorXmlEnvironment);
1✔
806
                }
807
                break;
1✔
808
            case END_ELEMENT:
809
                if (parser.getDepth() == initialDepth) {
1✔
810
                    break outerloop;
1✔
811
                }
812
                break;
813
            default:
814
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
815
                break;
816
            }
817
        }
1✔
818
        builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts);
1✔
819

820
        return builder.build();
1✔
821
    }
822

823
    /**
824
     * Parses an extension element.
825
     *
826
     * @param elementName the XML element name of the extension element.
827
     * @param namespace the XML namespace of the stanza extension.
828
     * @param parser the XML parser, positioned at the starting element of the extension.
829
     * @param outerXmlEnvironment the outer XML environment (optional).
830
     *
831
     * @return an extension element.
832
     * @throws XmlPullParserException if an error in the XML parser occurred.
833
     * @throws IOException if an I/O error occurred.
834
     * @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
835
     */
836
    public static XmlElement parseExtensionElement(String elementName, String namespace,
837
                    XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
838
        ParserUtils.assertAtStartTag(parser);
1✔
839
        // See if a provider is registered to handle the extension.
840
        ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getExtensionProvider(elementName, namespace);
1✔
841
        if (provider != null) {
1✔
842
                return provider.parse(parser, outerXmlEnvironment);
1✔
843
        }
844

845
        // No providers registered, so use a default extension.
846
        return StandardExtensionElementProvider.INSTANCE.parse(parser, outerXmlEnvironment);
1✔
847
    }
848

849
    public static StartTls parseStartTlsFeature(XmlPullParser parser)
850
                    throws XmlPullParserException, IOException {
851
        ParserUtils.assertAtStartTag(parser);
×
852
        assert parser.getNamespace().equals(StartTls.NAMESPACE);
×
853
        int initalDepth = parser.getDepth();
×
854
        boolean required = false;
×
855
        outerloop: while (true) {
856
            XmlPullParser.Event event = parser.next();
×
857
            switch (event) {
×
858
            case START_ELEMENT:
859
                String name = parser.getName();
×
860
                switch (name) {
×
861
                case "required":
862
                    required = true;
×
863
                    break;
864
                }
865
                break;
×
866
            case END_ELEMENT:
867
                if (parser.getDepth() == initalDepth) {
×
868
                    break outerloop;
×
869
                }
870
                break;
871
            default:
872
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
873
                break;
874
            }
875
        }
×
876
        ParserUtils.assertAtEndTag(parser);
×
877
        return new StartTls(required);
×
878
    }
879

880
    public static Session.Feature parseSessionFeature(XmlPullParser parser) throws XmlPullParserException, IOException {
881
        ParserUtils.assertAtStartTag(parser);
×
882
        final int initialDepth = parser.getDepth();
×
883
        boolean optional = false;
×
884

885
        outerloop: while (true) {
886
            XmlPullParser.Event event = parser.next();
×
887
            switch (event) {
×
888
            case START_ELEMENT:
889
                String name = parser.getName();
×
890
                switch (name) {
×
891
                    case Session.Feature.OPTIONAL_ELEMENT:
892
                        optional = true;
×
893
                        break;
894
                }
895
                break;
×
896
            case END_ELEMENT:
897
                if (parser.getDepth() == initialDepth) {
×
898
                    break outerloop;
×
899
                }
900
                break;
901
            default:
902
                // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
903
                break;
904
            }
905
        }
×
906

907
        return new Session.Feature(optional);
×
908
    }
909

910
    public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
911
                    throws XmlPullParserException, IOException, SmackParsingException {
912
        ParserUtils.assertAtStartTag(parser);
1✔
913
        addExtensionElement(stanzaBuilder, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
1✔
914
    }
1✔
915

916
    public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, String elementName,
917
            String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
918
        XmlElement extensionElement = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
1✔
919
        stanzaBuilder.addExtension(extensionElement);
1✔
920
    }
1✔
921

922
    public static void addExtensionElement(Stanza packet, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
923
                    throws XmlPullParserException, IOException, SmackParsingException {
924
        ParserUtils.assertAtStartTag(parser);
1✔
925
        addExtensionElement(packet, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
1✔
926
    }
1✔
927

928
    public static void addExtensionElement(Stanza packet, XmlPullParser parser, String elementName,
929
            String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
930
        XmlElement packetExtension = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
1✔
931
        packet.addExtension(packetExtension);
1✔
932
    }
1✔
933

934
    public static void addExtensionElement(Collection<XmlElement> collection, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
935
                    throws XmlPullParserException, IOException, SmackParsingException {
936
        addExtensionElement(collection, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
1✔
937
    }
1✔
938

939
    public static void addExtensionElement(Collection<XmlElement> collection, XmlPullParser parser,
940
                    String elementName, String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
941
        XmlElement packetExtension = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
1✔
942
        collection.add(packetExtension);
1✔
943
    }
1✔
944
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc