diff options
| author | Linus Jahn <lnj@kaidan.im> | 2020-04-07 00:54:50 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2020-04-07 21:54:59 +0200 |
| commit | 5dbca42cf501233dd09b32deb9b64d5862a48688 (patch) | |
| tree | b97045950dae0b8a9d43ab339a0963c4e6d0567b /src/client/QXmppAttentionManager.cpp | |
| parent | d7da60e0537c610257df1fcee5fac6cb2afc1b17 (diff) | |
| download | qxmpp-5dbca42cf501233dd09b32deb9b64d5862a48688.tar.gz | |
Add QXmppAttentionManager
Diffstat (limited to 'src/client/QXmppAttentionManager.cpp')
| -rw-r--r-- | src/client/QXmppAttentionManager.cpp | 296 |
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); + } +} |
