@@ -43,6 +43,8 @@ StreamOpen StreamOpen::fromXml(QXmlStreamReader &reader)
43
43
44
44
out.from = attribute ({}, u" from" );
45
45
out.to = attribute ({}, u" to" );
46
+ out.id = attribute ({}, u" id" );
47
+ out.version = attribute ({}, u" version" );
46
48
47
49
const auto namespaceDeclarations = reader.namespaceDeclarations ();
48
50
for (const auto &ns : namespaceDeclarations) {
@@ -58,11 +60,10 @@ void StreamOpen::toXml(QXmlStreamWriter *writer) const
58
60
{
59
61
writer->writeStartDocument ();
60
62
writer->writeStartElement (QSL65 (" stream:stream" ));
61
- if (!from.isEmpty ()) {
62
- writer->writeAttribute (QSL65 (" from" ), from);
63
- }
64
- writer->writeAttribute (QSL65 (" to" ), to);
65
- writer->writeAttribute (QSL65 (" version" ), QSL65 (" 1.0" ));
63
+ writeOptionalXmlAttribute (writer, u" from" , from);
64
+ writeOptionalXmlAttribute (writer, u" to" , to);
65
+ writeOptionalXmlAttribute (writer, u" id" , id);
66
+ writeOptionalXmlAttribute (writer, u" version" , version);
66
67
writer->writeDefaultNamespace (xmlns);
67
68
writer->writeNamespace (toString65 (ns_stream), QSL65 (" stream" ));
68
69
writer->writeCharacters ({});
@@ -170,6 +171,88 @@ std::variant<StreamErrorElement, QXmppError> StreamErrorElement::fromDom(const Q
170
171
}
171
172
// / \endcond
172
173
174
+ DomReader::State DomReader::process (QXmlStreamReader &r)
175
+ {
176
+ while (true ) {
177
+ switch (r.tokenType ()) {
178
+ case QXmlStreamReader::Invalid:
179
+ // error received
180
+ if (r.error () == QXmlStreamReader::PrematureEndOfDocumentError) {
181
+ return Unfinished;
182
+ }
183
+ return ErrorOccurred;
184
+ case QXmlStreamReader::StartElement: {
185
+ qDebug () << " start element token" ;
186
+ auto child = r.prefix ().isNull ()
187
+ ? doc.createElement (r.name ().toString ())
188
+ : doc.createElementNS (r.namespaceUri ().toString (), r.qualifiedName ().toString ());
189
+
190
+ // xmlns attribute
191
+ const auto nsDeclarations = r.namespaceDeclarations ();
192
+ for (const auto &ns : nsDeclarations) {
193
+ if (ns.prefix ().isEmpty ()) {
194
+ child.setAttribute (QStringLiteral (" xmlns" ), ns.namespaceUri ().toString ());
195
+ } else {
196
+ // namespace declarations are not supported in XMPP
197
+ qDebug () << " err namespace decl" ;
198
+ return ErrorOccurred;
199
+ }
200
+ }
201
+
202
+ // other attributes
203
+ const auto attributes = r.attributes ();
204
+ for (const auto &a : attributes) {
205
+ child.setAttribute (a.name ().toString (), a.value ().toString ());
206
+ }
207
+
208
+ if (currentElement.isNull ()) {
209
+ doc.appendChild (child);
210
+ } else {
211
+ currentElement.appendChild (child);
212
+ }
213
+ depth++;
214
+ currentElement = child;
215
+ break ;
216
+ }
217
+ case QXmlStreamReader::EndElement:
218
+ qDebug () << " end element token" ;
219
+ if (depth == 0 ) {
220
+ qDebug () << " depth == 0" ;
221
+ return ErrorOccurred;
222
+ }
223
+
224
+ currentElement = currentElement.parentNode ().toElement ();
225
+ depth--;
226
+ qDebug () << " depth" << depth;
227
+ if (depth == 0 ) {
228
+ return Finished;
229
+ }
230
+ break ;
231
+ case QXmlStreamReader::Characters:
232
+ if (depth == 0 ) {
233
+ qDebug () << " depth == 0" ;
234
+ return ErrorOccurred;
235
+ }
236
+
237
+ currentElement.appendChild (doc.createTextNode (r.text ().toString ()));
238
+ break ;
239
+ case QXmlStreamReader::NoToken:
240
+ // skip
241
+ break ;
242
+ case QXmlStreamReader::StartDocument:
243
+ case QXmlStreamReader::EndDocument:
244
+ case QXmlStreamReader::Comment:
245
+ case QXmlStreamReader::DTD:
246
+ case QXmlStreamReader::EntityReference:
247
+ case QXmlStreamReader::ProcessingInstruction:
248
+ qDebug () << " not allowed" ;
249
+ // not allowed or unexpected
250
+ return ErrorOccurred;
251
+ }
252
+ r.readNext ();
253
+ }
254
+ }
255
+
173
256
XmppSocket::XmppSocket (QObject *parent)
174
257
: QXmppLoggable(parent)
175
258
{
@@ -189,16 +272,16 @@ void XmppSocket::setSocket(QSslSocket *socket)
189
272
190
273
// do not emit started() with direct TLS (this happens in encrypted())
191
274
if (!m_directTls) {
192
- m_dataBuffer .clear ();
193
- m_streamOpenElement. clear () ;
275
+ m_reader .clear ();
276
+ m_streamReceived = false ;
194
277
Q_EMIT started ();
195
278
}
196
279
});
197
280
QObject::connect (socket, &QSslSocket::encrypted, this , [this ]() {
198
281
debug (u" Socket encrypted" _s);
199
282
// this happens with direct TLS or STARTTLS
200
- m_dataBuffer .clear ();
201
- m_streamOpenElement. clear () ;
283
+ m_reader .clear ();
284
+ m_streamReceived = false ;
202
285
Q_EMIT started ();
203
286
});
204
287
QObject::connect (socket, &QSslSocket::errorOccurred, this , [this ](QAbstractSocket::SocketError) {
@@ -256,102 +339,99 @@ bool XmppSocket::sendData(const QByteArray &data)
256
339
257
340
void XmppSocket::processData (const QString &data)
258
341
{
259
- // As we may only have partial XML content, we need to cache the received
260
- // data until it has been successfully parsed. In case it can't be parsed,
261
- //
262
- // There are only two small problems with the current strategy:
263
- // * When we receive a full stanza + a partial one, we can't parse the
264
- // first stanza until another stanza arrives that is complete.
265
- // * We don't know when we received invalid XML (would cause a growing
266
- // cache and a timeout after some time).
267
- // However, both issues could only be solved using an XML stream reader
268
- // which would cause many other problems since we don't actually use it for
269
- // parsing the content.
270
- m_dataBuffer.append (data);
271
-
272
- //
273
342
// Check for whitespace pings
274
- //
275
- if (m_dataBuffer.isEmpty () || m_dataBuffer.trimmed ().isEmpty ()) {
276
- m_dataBuffer.clear ();
277
-
343
+ if (data.isEmpty ()) {
344
+ qDebug () << " empty ping received" ;
278
345
logReceived ({});
279
346
Q_EMIT stanzaReceived (QDomElement ());
280
347
return ;
281
348
}
282
349
283
- //
284
- // Check whether we received a stream open or closing tag
285
- //
286
- static const QRegularExpression streamStartRegex (uR"( ^(<\?xml.*\?>)?\s*<stream:stream[^>]*>)" _s);
287
- static const QRegularExpression streamEndRegex (u" </stream:stream>$" _s);
288
-
289
- auto streamOpenMatch = streamStartRegex.match (m_dataBuffer);
290
- bool hasStreamOpen = streamOpenMatch.hasMatch ();
291
-
292
- bool hasStreamClose = streamEndRegex.match (m_dataBuffer).hasMatch ();
293
-
294
- //
295
- // The stream start/end and stanza packets can't be parsed without any
296
- // modifications with QDomDocument. This is because of multiple reasons:
297
- // * The <stream:stream> open element is not considered valid without the
298
- // closing tag.
299
- // * Only the closing tag is of course not valid too.
300
- // * Stanzas/Nonzas need to have the correct stream namespaces set:
301
- // * For being able to parse <stream:features/>
302
- // * For having the correct namespace (e.g. 'jabber:client') set to
303
- // stanzas and their child elements (e.g. <body/> of a message).
304
- //
305
- // The wrapping strategy looks like this:
306
- // * The stream open tag is cached once it arrives, for later access
307
- // * Incoming XML that has no <stream> open tag will be prepended by the
308
- // cached <stream> tag.
309
- // * Incoming XML that has no <stream> close tag will be appended by a
310
- // generic string "</stream:stream>"
311
- //
312
- // The result is parsed by QDomDocument and the child elements of the stream
313
- // are processed. In case the received data contained a stream open tag,
314
- // the stream is processed (before the stanzas are processed). In case we
315
- // received a </stream> closing tag, the connection is closed.
316
- //
317
- auto wrappedStanzas = m_dataBuffer;
318
- if (!hasStreamOpen) {
319
- wrappedStanzas.prepend (m_streamOpenElement);
320
- }
321
- if (!hasStreamClose) {
322
- wrappedStanzas.append (u" </stream:stream>" _s);
323
- }
324
-
325
- //
326
- // Try to parse the wrapped XML
327
- //
328
- QDomDocument doc;
329
- if (!doc.setContent (wrappedStanzas, true )) {
330
- return ;
331
- }
332
-
333
- //
334
- // Success: We can clear the buffer and send a 'received' log message
335
- //
336
- logReceived (m_dataBuffer);
337
- m_dataBuffer.clear ();
338
-
339
- // process stream start
340
- if (hasStreamOpen) {
341
- m_streamOpenElement = streamOpenMatch.captured ();
342
- Q_EMIT streamReceived (doc.documentElement ());
343
- }
344
-
345
- // process stanzas
346
- auto stanza = doc.documentElement ().firstChildElement ();
347
- for (; !stanza.isNull (); stanza = stanza.nextSiblingElement ()) {
348
- Q_EMIT stanzaReceived (stanza);
350
+ // log data received and process
351
+ logReceived (data);
352
+ m_reader.addData (data);
353
+
354
+ // we're still reading a previously started top-level element
355
+ if (m_domReader) {
356
+ m_reader.readNext ();
357
+ switch (m_domReader->process (m_reader)) {
358
+ case DomReader::Finished:
359
+ Q_EMIT stanzaReceived (m_domReader->element ());
360
+ m_domReader.reset ();
361
+ break ;
362
+ case DomReader::Unfinished:
363
+ return ;
364
+ case DomReader::ErrorOccurred:
365
+ // emit error
366
+ break ;
367
+ }
349
368
}
350
369
351
- // process stream end
352
- if (hasStreamClose) {
353
- Q_EMIT streamClosed ();
354
- }
370
+ do {
371
+ switch (m_reader.readNext ()) {
372
+ case QXmlStreamReader::Invalid:
373
+ // error received
374
+ if (m_reader.error () != QXmlStreamReader::PrematureEndOfDocumentError) {
375
+ // emit error
376
+ }
377
+ break ;
378
+ case QXmlStreamReader::StartDocument:
379
+ // pre-stream open
380
+ break ;
381
+ case QXmlStreamReader::EndDocument:
382
+ // post-stream close
383
+ break ;
384
+ case QXmlStreamReader::StartElement:
385
+ // stream open or stream-level element
386
+ if (m_reader.name () == u" stream" && m_reader.namespaceUri () == ns_stream) {
387
+ m_streamReceived = true ;
388
+ Q_EMIT streamReceived (StreamOpen::fromXml (m_reader));
389
+ } else if (!m_streamReceived) {
390
+ // error: expected stream open element
391
+ qDebug () << " err no stream recevied" ;
392
+ } else {
393
+ qDebug () << " start el" ;
394
+ // parse top-level stream element
395
+ m_domReader = DomReader ();
396
+
397
+ switch (m_domReader->process (m_reader)) {
398
+ case DomReader::Finished:
399
+ Q_EMIT stanzaReceived (m_domReader->element ());
400
+ m_domReader.reset ();
401
+ break ;
402
+ case DomReader::Unfinished:
403
+ qDebug () << " unfi" ;
404
+ return ;
405
+ case DomReader::ErrorOccurred:
406
+ qDebug () << " el err" ;
407
+ // emit error
408
+ break ;
409
+ }
410
+ }
411
+ break ;
412
+ case QXmlStreamReader::EndElement:
413
+ // end of stream
414
+ Q_EMIT streamClosed ();
415
+ break ;
416
+ case QXmlStreamReader::Characters:
417
+ if (m_reader.isWhitespace ()) {
418
+ logReceived ({});
419
+ Q_EMIT stanzaReceived (QDomElement ());
420
+ } else {
421
+ // invalid: emit error
422
+ }
423
+ break ;
424
+ case QXmlStreamReader::NoToken:
425
+ // skip
426
+ break ;
427
+ case QXmlStreamReader::Comment:
428
+ case QXmlStreamReader::DTD:
429
+ case QXmlStreamReader::EntityReference:
430
+ case QXmlStreamReader::ProcessingInstruction:
431
+ // not allowed in XMPP: emit error
432
+ break ;
433
+ }
434
+ } while (!m_reader.hasError ());
355
435
}
356
436
357
437
} // namespace QXmpp::Private
0 commit comments