aboutsummaryrefslogtreecommitdiff
path: root/src/client/QXmppAttentionManager.cpp
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2020-04-07 00:54:50 +0200
committerLinus Jahn <lnj@kaidan.im>2020-04-07 21:54:59 +0200
commit5dbca42cf501233dd09b32deb9b64d5862a48688 (patch)
treeb97045950dae0b8a9d43ab339a0963c4e6d0567b /src/client/QXmppAttentionManager.cpp
parentd7da60e0537c610257df1fcee5fac6cb2afc1b17 (diff)
downloadqxmpp-5dbca42cf501233dd09b32deb9b64d5862a48688.tar.gz
Add QXmppAttentionManager
Diffstat (limited to 'src/client/QXmppAttentionManager.cpp')
-rw-r--r--src/client/QXmppAttentionManager.cpp296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/client/QXmppAttentionManager.cpp b/src/client/QXmppAttentionManager.cpp
new file mode 100644
index 00000000..a4012ac7
--- /dev/null
+++ b/src/client/QXmppAttentionManager.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2008-2020 The QXmpp developers
+ *
+ * Author:
+ * Linus Jahn
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppAttentionManager.h"
+
+// Qt
+#include <QTimer>
+// QXmpp
+#include "QXmppClient.h"
+#include "QXmppConstants_p.h"
+#include "QXmppMessage.h"
+#include "QXmppRosterManager.h"
+#include "QXmppUtils.h"
+
+///
+/// \class QXmppAttentionManager
+///
+/// \brief The QXmppAttentionManager class manages attention requests as defined
+/// by \xep{0224}: Attention.
+///
+/// The manager also does some checks, including rate limiting and checking
+/// whether the senders are trusted (aka. in the roster).
+///
+/// Rate limited messages are not emitted on the normal attentionRequested()
+/// signal and are sent on the attentionRequestRateLimited() signal instead.
+///
+/// To use this manager you still need to instantiate it and register it with
+/// the QXmppClient:
+///
+/// \code
+/// auto *attentionManager = new QXmppAttentionManager();
+/// client->addExtension(attentionManager);
+/// \endcode
+///
+/// \since QXmpp 1.4
+///
+
+///
+/// \fn QXmppAttentionManager::attentionRequested
+///
+/// This signal is emitted when an attention request was received and it passed
+/// the rate limiter.
+///
+/// \param message The message with the attention request that was received.
+/// \param isTrusted Whether the sender of the message exists in the user's
+/// roster.
+///
+
+///
+/// \fn QXmppAttentionManager::attentionRequestRateLimited
+///
+/// This signal is emitted when an attention request did not pass the rate
+/// limiter.
+///
+/// \param message The message with the attention request that did not pass the
+/// rate limiter.
+///
+
+struct PastRequest {
+ QString bareJid;
+ QDateTime timestamp;
+};
+
+class QXmppAttentionManagerPrivate : public QObject
+{
+public:
+ QXmppAttentionManagerPrivate(QXmppAttentionManager *parent, quint8 allowedAttempts, QTime timeFrame);
+
+ bool checkRateLimit(const QString &bareJid);
+ void cleanUp();
+
+ quint8 allowedAttempts;
+ QTime allowedAttemptsTimeInterval;
+
+ // map of bare JIDs and time of previous requests
+ QVector<PastRequest> previousRequests;
+ QTimer *cleanUpTimer;
+};
+
+///
+/// \brief QXmppAttentionManager::QXmppAttentionManager
+/// \param allowedAttempts
+/// \param timeFrame
+///
+QXmppAttentionManager::QXmppAttentionManager(quint8 allowedAttempts, QTime timeFrame)
+ : d(new QXmppAttentionManagerPrivate(this, allowedAttempts, timeFrame))
+{
+}
+
+///
+/// Destructor
+///
+QXmppAttentionManager::~QXmppAttentionManager()
+{
+ delete d;
+}
+
+///
+/// Returns the \xep{0224}: Attention feature.
+///
+QStringList QXmppAttentionManager::discoveryFeatures() const
+{
+ return {
+ ns_attention
+ };
+}
+
+///
+/// Returns the number of allowed attempts of attentions from a bare JID in the
+/// set time frame.
+///
+/// \sa setAllowedAttempts()
+/// \sa allowedAttemptsTimeInterval()
+/// \sa setAllowedAttemptsTimeInterval()
+///
+quint8 QXmppAttentionManager::allowedAttempts() const
+{
+ return d->allowedAttempts;
+}
+
+///
+/// Sets the number of allowed attempts of attentions from a bare JID in the set
+/// time frame.
+///
+/// \sa allowedAttempts()
+/// \sa allowedAttemptsTimeInterval()
+/// \sa setAllowedAttemptsTimeInterval()
+///
+void QXmppAttentionManager::setAllowedAttempts(quint8 allowedAttempts)
+{
+ d->allowedAttempts = allowedAttempts;
+}
+
+///
+/// Returns the time interval for the allowed attempts for rate limiting.
+///
+/// \sa setAllowedAttemptsTimeInterval()
+/// \sa allowedAttempts()
+/// \sa setAllowedAttemptsTimeInterval()
+///
+QTime QXmppAttentionManager::allowedAttemptsTimeInterval() const
+{
+ return d->allowedAttemptsTimeInterval;
+}
+
+///
+/// Returns the time interval for the allowed attempts for rate limiting.
+///
+/// \sa allowedAttemptsTimeInterval()
+/// \sa allowedAttempts()
+/// \sa setAllowedAttempts()
+///
+void QXmppAttentionManager::setAllowedAttemptsTimeInterval(QTime interval)
+{
+ d->allowedAttemptsTimeInterval = interval;
+}
+
+///
+/// Sends a message of type chat with an attention request to the specified JID.
+///
+/// \xep{0224} allows to include other elements with an attention request, but
+/// the QXmppAttentionManager has no method for this purpose. However, such a
+/// request can still be made manually.
+///
+/// \param jid The address to which the request should be sent.
+/// \param message The message body to include in the attention request.
+///
+/// \return The ID of the sent message, if sent successfully, a null string
+/// otherwise. In case an ID is returned, it also corresponds to the origin ID
+/// of the message as defined by \xep{0359}: Unique and Stable Stanza IDs.
+///
+QString QXmppAttentionManager::requestAttention(const QString &jid, const QString &message)
+{
+ QXmppMessage msg;
+ // The XEP recommends to use type headline, but the message body might still
+ // be of interest later, so we use type chat to allow caching.
+ msg.setType(QXmppMessage::Chat);
+ msg.setId(QXmppUtils::generateStanzaUuid());
+ msg.setOriginId(msg.id());
+ msg.setTo(jid);
+ msg.setBody(message);
+ msg.setAttentionRequested(true);
+
+ if (client()->sendPacket(msg))
+ return msg.id();
+ return {};
+}
+
+void QXmppAttentionManager::setClient(QXmppClient *client)
+{
+ QXmppClientExtension::setClient(client);
+
+ connect(client, &QXmppClient::messageReceived,
+ this, &QXmppAttentionManager::handleMessageReceived);
+}
+
+///
+/// Empty reimplementation
+///
+bool QXmppAttentionManager::handleStanza(const QDomElement &)
+{
+ return false;
+}
+
+void QXmppAttentionManager::handleMessageReceived(const QXmppMessage &message)
+{
+ if (!message.isAttentionRequested() || !message.stamp().isNull())
+ return;
+
+ const QString bareJid = QXmppUtils::jidToBareJid(message.from());
+
+ // ignore messages from our own bare JID (e.g. carbon or IM-NG message)
+ if (bareJid == client()->configuration().jidBare())
+ return;
+
+ // check rate limit
+ if (!d->checkRateLimit(bareJid)) {
+ emit attentionRequestRateLimited(message);
+ return;
+ }
+
+ bool isTrusted = false;
+ if (auto *rosterManager = client()->findExtension<QXmppRosterManager>()) {
+ isTrusted = rosterManager->getRosterBareJids().contains(bareJid);
+ }
+
+ emit attentionRequested(message, isTrusted);
+}
+
+QXmppAttentionManagerPrivate::QXmppAttentionManagerPrivate(QXmppAttentionManager *parent, quint8 allowedAttempts, QTime timeFrame)
+ : allowedAttempts(allowedAttempts),
+ allowedAttemptsTimeInterval(timeFrame),
+ cleanUpTimer(new QTimer(parent))
+{
+ QObject::connect(cleanUpTimer, &QTimer::timeout, [this]() {
+ cleanUp();
+ });
+}
+
+///
+/// Returns true if the request passes the rate limit.
+///
+bool QXmppAttentionManagerPrivate::checkRateLimit(const QString &bareJid)
+{
+ // add to request to cache
+ previousRequests << PastRequest { bareJid, QDateTime::currentDateTimeUtc() };
+
+ // start timer to remove request again
+ if (!cleanUpTimer->isActive())
+ cleanUpTimer->start(allowedAttemptsTimeInterval.msecsSinceStartOfDay());
+
+ // check whether there are too many requests
+ int count = std::count_if(previousRequests.cbegin(), previousRequests.cend(), [=](const PastRequest &request) {
+ return request.bareJid == bareJid;
+ });
+ return count <= allowedAttempts;
+}
+
+///
+/// Removes the first entry and reschedules the timer to remove the next.
+///
+void QXmppAttentionManagerPrivate::cleanUp()
+{
+ previousRequests.removeFirst();
+
+ if (!previousRequests.isEmpty()) {
+ // reschedule timer for next removal
+ int next = allowedAttemptsTimeInterval.msecsSinceStartOfDay() -
+ previousRequests.first().timestamp.msecsTo(QDateTime::currentDateTimeUtc());
+
+ if (next < 1)
+ cleanUp();
+ else
+ cleanUpTimer->start(next);
+ }
+}