@@ -43,6 +43,8 @@ StreamOpen StreamOpen::fromXml(QXmlStreamReader &reader)
4343
4444 out.from = attribute ({}, u" from" );
4545 out.to = attribute ({}, u" to" );
46+ out.id = attribute ({}, u" id" );
47+ out.version = attribute ({}, u" version" );
4648
4749 const auto namespaceDeclarations = reader.namespaceDeclarations ();
4850 for (const auto &ns : namespaceDeclarations) {
@@ -58,11 +60,10 @@ void StreamOpen::toXml(QXmlStreamWriter *writer) const
5860{
5961 writer->writeStartDocument ();
6062 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);
6667 writer->writeDefaultNamespace (xmlns);
6768 writer->writeNamespace (toString65 (ns_stream), QSL65 (" stream" ));
6869 writer->writeCharacters ({});
@@ -170,6 +171,88 @@ std::variant<StreamErrorElement, QXmppError> StreamErrorElement::fromDom(const Q
170171}
171172// / \endcond
172173
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+
173256XmppSocket::XmppSocket (QObject *parent)
174257 : QXmppLoggable(parent)
175258{
@@ -189,16 +272,16 @@ void XmppSocket::setSocket(QSslSocket *socket)
189272
190273 // do not emit started() with direct TLS (this happens in encrypted())
191274 if (!m_directTls) {
192- m_dataBuffer .clear ();
193- m_streamOpenElement. clear () ;
275+ m_reader .clear ();
276+ m_streamReceived = false ;
194277 Q_EMIT started ();
195278 }
196279 });
197280 QObject::connect (socket, &QSslSocket::encrypted, this , [this ]() {
198281 debug (u" Socket encrypted" _s);
199282 // this happens with direct TLS or STARTTLS
200- m_dataBuffer .clear ();
201- m_streamOpenElement. clear () ;
283+ m_reader .clear ();
284+ m_streamReceived = false ;
202285 Q_EMIT started ();
203286 });
204287 QObject::connect (socket, &QSslSocket::errorOccurred, this , [this ](QAbstractSocket::SocketError) {
@@ -256,102 +339,99 @@ bool XmppSocket::sendData(const QByteArray &data)
256339
257340void XmppSocket::processData (const QString &data)
258341{
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- //
273342 // 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" ;
278345 logReceived ({});
279346 Q_EMIT stanzaReceived (QDomElement ());
280347 return ;
281348 }
282349
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+ }
349368 }
350369
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 ());
355435}
356436
357437} // namespace QXmpp::Private
0 commit comments