Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore stream errors upon account successful account deletion and timeout during account creation #594

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/base/QXmppGlobal.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ struct Success { };
struct Cancelled { };

///
/// Unit struct indicating a timeout (or keep-alive) error.
/// Unit struct indicating a client-side timeout (or keep-alive) error.
///
/// It occurs if no response is received from the connected entity within a given timeout.
///
/// \since QXmpp 1.7
///
Expand Down
20 changes: 20 additions & 0 deletions src/client/QXmppClient.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <[email protected]>
// SPDX-FileCopyrightText: 2019 Linus Jahn <[email protected]>
// SPDX-FileCopyrightText: 2023 Melvin Keskin <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

Expand Down Expand Up @@ -123,6 +124,13 @@ QStringList QXmppClientPrivate::discoveryFeatures()

void QXmppClientPrivate::onErrorOccurred(const QString &text, const QXmppOutgoingClient::ConnectionError &err, QXmppClient::Error oldError)
{
// Skip stream errors that are valid during special procedures such as account
// creation/deletion.
if (const auto streamError = std::get_if<QXmpp::StreamError>(&err);
streamError && ignoredStreamErrors.contains(*streamError)) {
return;
}

if (q->configuration().autoReconnectionEnabled()) {
if (oldError == QXmppClient::XmppStreamError) {
// if we receive a resource conflict, inhibit reconnection
Expand Down Expand Up @@ -931,6 +939,18 @@ bool QXmppClient::injectMessage(QXmppMessage &&message)
return handled;
}

///
/// Sets stream errors that are ignored if they occur.
///
/// \param errors stream errors to be ignored
///
/// \since QXmpp 1.9
///
void QXmppClient::setIgnoredStreamErrors(const QVector<QXmpp::StreamError> &errors)
{
d->ignoredStreamErrors = errors;
}

///
/// Give extensions a chance to handle incoming stanzas.
///
Expand Down
4 changes: 4 additions & 0 deletions src/client/QXmppClient.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <[email protected]>
// SPDX-FileCopyrightText: 2019 Linus Jahn <[email protected]>
// SPDX-FileCopyrightText: 2023 Melvin Keskin <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

Expand All @@ -11,6 +12,7 @@
#include "QXmppPresence.h"
#include "QXmppSendResult.h"
#include "QXmppSendStanzaParams.h"
#include "QXmppStreamError.h"

#include <memory>
#include <variant>
Expand Down Expand Up @@ -321,6 +323,8 @@ public Q_SLOTS:
void injectIq(const QDomElement &element, const std::optional<QXmppE2eeMetadata> &e2eeMetadata);
bool injectMessage(QXmppMessage &&message);

void setIgnoredStreamErrors(const QVector<QXmpp::StreamError> &);

private Q_SLOTS:
void _q_elementReceived(const QDomElement &element, bool &handled);
void _q_reconnect();
Expand Down
2 changes: 2 additions & 0 deletions src/client/QXmppClient_p.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2020 Manjeet Dahiya <[email protected]>
// SPDX-FileCopyrightText: 2020 Linus Jahn <[email protected]>
// SPDX-FileCopyrightText: 2023 Melvin Keskin <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

Expand Down Expand Up @@ -40,6 +41,7 @@ class QXmppClientPrivate
QXmppLogger *logger;
/// Pointer to the XMPP stream
QXmppOutgoingClient *stream;
QVector<QXmpp::StreamError> ignoredStreamErrors;

QXmppE2eeExtension *encryptionExtension;

Expand Down
24 changes: 21 additions & 3 deletions src/client/QXmppRegistrationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void QXmppRegistrationManager::deleteAccount()
auto iq = QXmppRegisterIq::createUnregistrationRequest();
d->deleteAccountIqId = iq.id();

client()->setIgnoredStreamErrors({ QXmpp::StreamError::Conflict, QXmpp::StreamError::NotAuthorized });
client()->sendPacket(iq);
}

Expand Down Expand Up @@ -174,6 +175,12 @@ bool QXmppRegistrationManager::registerOnConnectEnabled() const
void QXmppRegistrationManager::setRegisterOnConnectEnabled(bool enabled)
{
d->registerOnConnectEnabled = enabled;

if (enabled) {
client()->setIgnoredStreamErrors({ QXmpp::StreamError::ConnectionTimeout });
} else {
client()->setIgnoredStreamErrors({});
}
}

/// \cond
Expand Down Expand Up @@ -258,8 +265,7 @@ bool QXmppRegistrationManager::handleStanza(const QDomElement &stanza)

switch (iq.type()) {
case QXmppIq::Result:
info(u"Account deleted successfully."_s);
Q_EMIT accountDeleted();
handleAccountDeleted();
client()->disconnectFromServer();
break;
case QXmppIq::Error:
Expand Down Expand Up @@ -291,8 +297,14 @@ void QXmppRegistrationManager::onRegistered(QXmppClient *client)
connect(disco, &QXmppDiscoveryManager::infoReceived, this, &QXmppRegistrationManager::handleDiscoInfo);
}

connect(client, &QXmppClient::disconnected, this, [this]() {
connect(client, &QXmppClient::disconnected, this, [this, client]() {
setSupportedByServer(false);
client->setIgnoredStreamErrors({});

if (!d->deleteAccountIqId.isEmpty()) {
handleAccountDeleted();
d->deleteAccountIqId.clear();
}
});
}

Expand All @@ -319,3 +331,9 @@ void QXmppRegistrationManager::setSupportedByServer(bool registrationSupported)
Q_EMIT supportedByServerChanged();
}
}

void QXmppRegistrationManager::handleAccountDeleted()
{
info(u"Account deleted successfully."_s);
Q_EMIT accountDeleted();
}
53 changes: 24 additions & 29 deletions src/client/QXmppRegistrationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,20 @@ class QXmppRegistrationManagerPrivate;
///
/// If you want to delete your account on the server, you can do that using
/// deleteAccount(). When the result is received either accountDeleted() or
/// accountDeletionFailed() is emitted. In case it was successful the manager
/// automatically disconnects from the client.
/// accountDeletionFailed() is emitted. In case it was successful, the manager
/// automatically disconnects from the client. If the server takes too much time to confirm the
/// account deletion, you may disconnect manually after a reasonable timeout.
///
/// QXmpp periodically sends pings to the server. If the server does not respond to it within
/// QXmppConfiguration::keepAliveInterval(), QXmpp disconnects from the server. Make sure to handle
/// that case if it happens during account deletion. E.g., you could try to connect to the server
/// again with the same account and check whether QXmpp::AuthenticationError::NotAuthorized occurs.
/// In that case, the account can be considered as deleted.
///
/// \code
/// auto *registrationManager = client->findExtension<QXmppRegistrationManager>();
/// connect(registrationManager, &QXmppRegistrationManager::accountDeleted, [=]() {
/// qDebug() << "Account deleted successfull, the client is disconnecting now";
/// qDebug() << "Account deleted successfully, the client is disconnecting now";
/// });
/// connect(registrationManager, &QXmppRegistrationManager::accountDeletionFailed, [=](QXmppStanza::Error error) {
/// qDebug() << "Couldn't delete account:" << error.text();
Expand Down Expand Up @@ -153,10 +160,11 @@ class QXmppRegistrationManagerPrivate;
///
/// <h4>Filling out the registration form</h4>
///
/// Now you need to fill out the registration form. If this requires user
/// interaction, it is recommended that you disconnect from the server at this
/// point now. This is required, because some servers will kick inactive, not
/// authorized clients after a few seconds.
/// Now you need to fill out the registration form. The server can close the connection during that
/// time. It is due to some servers kicking unauthorized clients after some time when the clients
/// are inactive. That is often the case when user interaction is required before the completed form
/// is submitted to the server. In order to support account creation for both servers closing the
/// connection and servers keeping it open, you need to handle those cases appropriately.
///
/// If the returned IQ contains a data form, that can be displayed to a user or
/// can be filled out in another way.
Expand All @@ -173,24 +181,20 @@ class QXmppRegistrationManagerPrivate;
///
/// <h4>Sending the completed form to the server</h4>
///
/// <b>Option A</b>: If filling out the form goes very quick, you can set the
/// filled out form directly using setRegistrationFormToSend() and then
/// directly trigger the form to be sent using sendCachedRegistrationForm().
/// <b>Option A</b>: If the connection is still open once the form is filled out, set the form using
/// setRegistrationFormToSend() and then trigger the form to be directly sent using
/// sendCachedRegistrationForm().
///
/// \code
/// registrationManager->setRegistrationFormToSend(completedForm);
/// registrationManager->sendCachedRegistrationForm();
/// \endcode
///
/// <b>Option B</b>: If filling out the form takes longer, i.e. because user
/// interaction is required, you should disconnect now. As soon as you have
/// completed the form, you can set it using setRegistrationFormToSend(). After
/// that you can reconnect to the server and the registration manager will
/// automatically send the set form.
/// <b>Option B</b>: If the connection is closed before the form is filled out, set the form using
/// setRegistrationFormToSend() and connect to the server again. The registration manager will
/// automatically send the set form once connected.
///
/// \code
/// client->disconnectFromServer();
/// // user fills out form ...
/// registrationManager->setRegistrationFormToSend(completedForm);
///
/// // As before, you only need to provide a domain to connectToServer()
Expand All @@ -207,8 +211,7 @@ class QXmppRegistrationManagerPrivate;
///
/// <h4>Connecting with the newly created account</h4>
///
/// You need to disconnect now. The user can then enter their credentials and
/// connect as usually.
/// You need to disconnect now. The user can then enter their credentials and connect as usual.
///
/// It is also possible to extract username and password from the sent form,
/// but that does not work always. There might also be forms that have no clear
Expand Down Expand Up @@ -289,13 +292,6 @@ class QXMPP_EXPORT QXmppRegistrationManager : public QXmppClientExtension
///
/// Emitted, when a registration form has been received.
///
/// When registering an account on the server and user interaction is
/// required now to complete the form, it is recommended to disconnect and
/// sending the completed registration form on reconnect using
/// QXmppRegistrationManager::setRegistrationFormToSend(). Some servers
/// (i.e. ejabberd) kick their clients after a timeout when they are not
/// active. This can be avoided this way.
///
/// \param iq The received form. If it does not contain a valid data form
/// (see QXmppRegisterIq::form()), the required fields should be marked by
/// empty (but not null) strings in the QXmppRegisterIq (i.e.
Expand Down Expand Up @@ -341,11 +337,10 @@ class QXMPP_EXPORT QXmppRegistrationManager : public QXmppClientExtension
void onRegistered(QXmppClient *client) override;
void onUnregistered(QXmppClient *client) override;

private Q_SLOTS:
void handleDiscoInfo(const QXmppDiscoveryIq &iq);

private:
void handleDiscoInfo(const QXmppDiscoveryIq &iq);
void setSupportedByServer(bool supportedByServer);
void handleAccountDeleted();

const std::unique_ptr<QXmppRegistrationManagerPrivate> d;
};
Expand Down
Loading