aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2021-09-29 13:15:53 +0200
committerLinus Jahn <lnj@kaidan.im>2021-09-29 16:37:41 +0200
commit9b21bef379d94876908cbb706c9ba2438779bf54 (patch)
tree38a7499f3e7bfc27339b2de2ef82775d6f92cde4
parentbb861994bdf79cb783a07cc79ee18e8711295a91 (diff)
downloadqxmpp-9b21bef379d94876908cbb706c9ba2438779bf54.tar.gz
PubSubManager: Add node configuration requests
-rw-r--r--src/base/QXmppPubSubIq.cpp13
-rw-r--r--src/client/QXmppPubSubManager.cpp146
-rw-r--r--src/client/QXmppPubSubManager.h7
-rw-r--r--tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp116
4 files changed, 272 insertions, 10 deletions
diff --git a/src/base/QXmppPubSubIq.cpp b/src/base/QXmppPubSubIq.cpp
index 367b0ec0..5ba2615b 100644
--- a/src/base/QXmppPubSubIq.cpp
+++ b/src/base/QXmppPubSubIq.cpp
@@ -607,8 +607,17 @@ void QXmppPubSubIqBase::toXmlElementFromChild(QXmlStreamWriter *writer) const
case OwnerDefault:
case Options:
if (auto form = d->dataForm) {
- // make sure data form type is submit
- form->setType(QXmppDataForm::Submit);
+ // make sure data form type is correct
+ switch (type()) {
+ case QXmppIq::Result:
+ form->setType(QXmppDataForm::Result);
+ break;
+ default:
+ if (form->type() != QXmppDataForm::Cancel) {
+ form->setType(QXmppDataForm::Submit);
+ }
+ break;
+ }
form->toXml(writer);
}
break;
diff --git a/src/client/QXmppPubSubManager.cpp b/src/client/QXmppPubSubManager.cpp
index c28df7f3..08fe75e7 100644
--- a/src/client/QXmppPubSubManager.cpp
+++ b/src/client/QXmppPubSubManager.cpp
@@ -24,19 +24,19 @@
#include "QXmppPubSubManager.h"
-#include <QDomElement>
-#include <QFutureInterface>
-
#include "QXmppClient.h"
#include "QXmppConstants_p.h"
-#include "QXmppPubSubItem.h"
-#include "QXmppPubSubEventManager.h"
-#include "QXmppPubSubSubscription.h"
#include "QXmppPubSubAffiliation.h"
+#include "QXmppPubSubEventManager.h"
+#include "QXmppPubSubItem.h"
#include "QXmppPubSubSubscribeOptions.h"
+#include "QXmppPubSubSubscription.h"
#include "QXmppStanza.h"
#include "QXmppUtils.h"
+#include <QDomElement>
+#include <QFutureInterface>
+
using namespace QXmpp::Private;
///
@@ -159,6 +159,13 @@ using namespace QXmpp::Private;
///
///
+/// \typedef QXmppPubSubManager::NodeConfigResult
+///
+/// Contains a node configuration (QXmppPubSubNodeConfig) or the returned IQ
+/// error (QXmppStanza::Error).
+///
+
+///
/// Default constructor.
///
QXmppPubSubManager::QXmppPubSubManager()
@@ -280,7 +287,7 @@ auto QXmppPubSubManager::retractItem(const QString &jid, const QString &nodeName
request.setType(QXmppIq::Set);
request.setQueryType(QXmppPubSubIq<>::Retract);
request.setQueryNode(nodeName);
- request.setItems({QXmppPubSubItem(itemId)});
+ request.setItems({ QXmppPubSubItem(itemId) });
request.setTo(jid);
return client()->sendGenericIq(std::move(request));
@@ -473,6 +480,87 @@ QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::setSubscribeOptions(cons
}
///
+/// Requests the node configuration and starts the configuration process.
+///
+/// Requires owner privileges. If the result is successful (a node config form
+/// has been returned) this starts the configuration process. The next step is
+/// to call configureNode() or cancelNodeConfiguration().
+///
+/// \param service JID of the pubsub service
+/// \param nodeName Name of the pubsub node on the service
+/// \return
+///
+/// \sa configureNode()
+/// \sa cancelNodeConfiguration()
+///
+QFuture<QXmppPubSubManager::NodeConfigResult> QXmppPubSubManager::requestNodeConfiguration(const QString &service, const QString &nodeName)
+{
+ using Error = QXmppStanza::Error;
+
+ QXmppPubSubIq request;
+ request.setType(QXmppIq::Get);
+ request.setTo(service);
+ request.setQueryNode(nodeName);
+ request.setQueryType(QXmppPubSubIq<>::Configure);
+
+ return chainIq(client()->sendIq(std::move(request)), this,
+ [](QXmppPubSubIq<> &&iq) -> NodeConfigResult {
+ if (const auto dataForm = iq.dataForm()) {
+ if (const auto config = QXmppPubSubNodeConfig::fromDataForm(*dataForm)) {
+ return *config;
+ }
+ return Error(Error::Cancel, Error::UndefinedCondition, QStringLiteral("Server returned invalid data form."));
+ }
+ return Error(Error::Cancel, Error::UndefinedCondition, QStringLiteral("Server returned no data form."));
+ });
+}
+
+///
+/// Sets a node configuration.
+///
+/// Requires owner privileges. You can use requestNodeConfiguration() to receive
+/// a data form with all valid options and default values.
+///
+/// \param service JID of the pubsub service
+/// \param nodeName Name of the pubsub node on the service
+/// \param config
+/// \return
+///
+/// \sa requestNodeConfiguration()
+///
+QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::configureNode(const QString &service, const QString &nodeName, const QXmppPubSubNodeConfig &config)
+{
+ QXmppPubSubIq request;
+ request.setType(QXmppIq::Set);
+ request.setTo(service);
+ request.setQueryNode(nodeName);
+ request.setQueryType(QXmppPubSubIq<>::Configure);
+ request.setDataForm(config);
+ return client()->sendGenericIq(std::move(request));
+}
+
+///
+/// Cancels the configuration process and uses the default or exisiting
+/// configuration.
+///
+/// \param service JID of the pubsub service
+/// \param nodeName Name of the pubsub node on the service
+/// \return
+///
+/// \sa requestNodeConfiguration()
+///
+QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::cancelNodeConfiguration(const QString &service, const QString &nodeName)
+{
+ QXmppPubSubIq request;
+ request.setType(QXmppIq::Set);
+ request.setTo(service);
+ request.setQueryNode(nodeName);
+ request.setQueryType(QXmppPubSubIq<>::Configure);
+ request.setDataForm(QXmppDataForm(QXmppDataForm::Cancel));
+ return client()->sendGenericIq(std::move(request));
+}
+
+///
/// \fn QXmppPubSubManager::createPepNode
///
/// Creates an empty PEP node with the default configuration.
@@ -527,6 +615,48 @@ QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::setSubscribeOptions(cons
/// \return
///
+///
+/// \fn QXmppPubSubManager::requestPepNodeConfiguration
+///
+/// Requests the node configuration and starts the configuration process.
+///
+/// This is a convenience method equivalent to calling
+/// requestNodeConfiguration() the current account's bare JID.
+///
+/// \param nodeName Name of the pubsub node on the service
+/// \return
+///
+/// \sa configurePepNode()
+/// \sa cancelPepNodeConfiguration()
+///
+
+///
+/// \fn QXmppPubSubManager::configurePepNode
+///
+/// Sets a node configuration.
+///
+/// This is a convenience method equivalent to calling configureNode() the
+/// current account's bare JID.
+///
+/// \param nodeName Name of the pubsub node on the service
+/// \param config
+/// \return
+///
+/// \sa requestPepNodeConfiguration()
+///
+
+///
+/// \fn QXmppPubSubManager::cancelPepNodeConfiguration
+///
+/// This is a convenience method equivalent to calling cancelNodeConfiguration()
+/// the current account's bare JID.
+///
+/// \param nodeName Name of the pubsub node on the service
+/// \return
+///
+/// \sa requestPepNodeConfiguration()
+///
+
/// \cond
QStringList QXmppPubSubManager::discoveryFeatures() const
{
@@ -549,7 +679,7 @@ bool QXmppPubSubManager::handleStanza(const QDomElement &element)
const auto extensions = client()->extensions();
for (auto *extension : extensions) {
- if (auto *eventManager = qobject_cast<QXmppPubSubEventManager*>(extension)) {
+ if (auto *eventManager = qobject_cast<QXmppPubSubEventManager *>(extension)) {
if (eventManager->handlePubSubEvent(element, service, node)) {
return true;
}
diff --git a/src/client/QXmppPubSubManager.h b/src/client/QXmppPubSubManager.h
index 26ff7aaf..f3798a3f 100644
--- a/src/client/QXmppPubSubManager.h
+++ b/src/client/QXmppPubSubManager.h
@@ -62,6 +62,7 @@ public:
using SubscriptionsResult = std::variant<QVector<QXmppPubSubSubscription>, QXmppStanza::Error>;
using AffiliationsResult = std::variant<QVector<QXmppPubSubAffiliation>, QXmppStanza::Error>;
using OptionsResult = std::variant<QXmppPubSubSubscribeOptions, QXmppStanza::Error>;
+ using NodeConfigResult = std::variant<QXmppPubSubNodeConfig, QXmppStanza::Error>;
QXmppPubSubManager();
~QXmppPubSubManager();
@@ -96,6 +97,9 @@ public:
QFuture<OptionsResult> requestSubscribeOptions(const QString &service, const QString &nodeName, const QString &subscriberJid);
QFuture<Result> setSubscribeOptions(const QString &service, const QString &nodeName, const QXmppPubSubSubscribeOptions &options);
QFuture<Result> setSubscribeOptions(const QString &service, const QString &nodeName, const QXmppPubSubSubscribeOptions &options, const QString &subscriberJid);
+ QFuture<NodeConfigResult> requestNodeConfiguration(const QString &service, const QString &nodeName);
+ QFuture<Result> configureNode(const QString &service, const QString &nodeName, const QXmppPubSubNodeConfig &config);
+ QFuture<Result> cancelNodeConfiguration(const QString &service, const QString &nodeName);
// PEP-specific (the PubSub service is the current account)
inline QFuture<Result> createPepNode(const QString &nodeName) { return createNode(client()->configuration().jidBare(), nodeName); }
@@ -110,6 +114,9 @@ public:
QFuture<PublishItemsResult> publishPepItems(const QString &nodeName, const QVector<T> &items);
inline QFuture<Result> retractPepItem(const QString &nodeName, const QString &itemId) { return retractItem(client()->configuration().jidBare(), nodeName, itemId); }
inline QFuture<Result> purgePepItems(const QString &nodeName) { return purgeItems(client()->configuration().jidBare(), nodeName); }
+ inline QFuture<NodeConfigResult> requestPepNodeConfiguration(const QString &nodeName) { return requestNodeConfiguration(client()->configuration().jidBare(), nodeName); }
+ inline QFuture<Result> configurePepNode(const QString &nodeName, const QXmppPubSubNodeConfig &config) { return configureNode(client()->configuration().jidBare(), nodeName, config); }
+ inline QFuture<Result> cancelPepNodeConfiguration(const QString &nodeName) { return cancelNodeConfiguration(client()->configuration().jidBare(), nodeName); }
/// \cond
QStringList discoveryFeatures() const override;
diff --git a/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp b/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp
index 9da709d8..9c80a1b8 100644
--- a/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp
+++ b/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp
@@ -67,6 +67,12 @@ public:
uint m_events = 0;
};
+struct Client
+{
+ TestClient test;
+ PSManager *psManager = test.addNewExtension<PSManager>();
+};
+
class tst_QXmppPubSubManager : public QObject
{
Q_OBJECT
@@ -94,6 +100,9 @@ private:
Q_SLOT void testRequestOptions();
Q_SLOT void testRequestOptionsError();
Q_SLOT void testSetOptions();
+ Q_SLOT void testRequestNodeConfig();
+ Q_SLOT void testConfigureNode();
+ Q_SLOT void testCancelConfig();
Q_SLOT void testEventNotifications_data();
Q_SLOT void testEventNotifications();
};
@@ -763,6 +772,113 @@ void tst_QXmppPubSubManager::testSetOptions()
expectFutureVariant<QXmpp::Success>(future);
}
+void tst_QXmppPubSubManager::testRequestNodeConfig()
+{
+ auto [test, psManager] = Client();
+
+ auto future = psManager->requestNodeConfiguration("pubsub.qxmpp.org", "tune");
+ test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='get'>"
+ "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>"
+ "<configure node='tune'/>"
+ "</pubsub>"
+ "</iq>");
+ test.inject<QString>("<iq id='qxmpp1' type='result'>"
+ "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>"
+ "<configure node='princely_musings'>"
+ "<x xmlns='jabber:x:data' type='form'>"
+ "<field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field>"
+ "<field var='pubsub#title' type='text-single' label='A friendly name for the node'/>"
+ "<field var='pubsub#deliver_notifications' type='boolean' label='Whether to deliver event notifications'><value>true</value></field>"
+ "<field var='pubsub#deliver_payloads' type='boolean' label='Whether to deliver payloads with event notifications'><value>true</value></field>"
+ "<field var='pubsub#dataform_xslt' type='text-single' label='Payload XSLT'/>"
+ "</x>"
+ "</configure>"
+ "</pubsub>"
+ "</iq>");
+
+ const auto form = expectFutureVariant<QXmppPubSubNodeConfig>(future);
+ QCOMPARE(form.title(), QString());
+ QVERIFY(form.notificationsEnabled().has_value());
+ QCOMPARE(form.notificationsEnabled().value_or(false), true);
+ QVERIFY(form.dataFormXslt().isNull());
+}
+
+void tst_QXmppPubSubManager::testConfigureNode()
+{
+ auto [test, psManager] = Client();
+
+ QXmppPubSubNodeConfig config;
+ config.setTitle("Princely Musings (Atom)");
+ config.setNotificationsEnabled(true);
+ config.setIncludePayloads(true);
+ config.setPersistItems(true);
+ config.setMaxItems(10ULL);
+ config.setItemExpiry(604800);
+ config.setAccessModel(QXmppPubSubNodeConfig::Roster);
+ config.setAllowedRosterGroups({ "friends", "servants", "courtiers" });
+ config.setPublishModel(QXmppPubSubNodeConfig::Publishers);
+ config.setPurgeWhenOffline(false);
+ config.setSendLastItem(QXmppPubSubNodeConfig::Never);
+ config.setPresenceBasedNotifications(false);
+ config.setNotificationType(QXmppPubSubNodeConfig::Headline);
+ config.setConfigNotificationsEnabled(false);
+ config.setDeleteNotificationsEnabled(false);
+ config.setRetractNotificationsEnabled(false);
+ config.setSubNotificationsEnabled(false);
+ config.setMaxPayloadSize(1028);
+ config.setPayloadType("http://www.w3.org/2005/Atom");
+ config.setBodyXslt("http://jabxslt.jabberstudio.org/atom_body.xslt");
+
+ auto future = psManager->configureNode("pubsub.qxmpp.org", "princely_musings", config);
+ test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='set'>"
+ "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>"
+ "<configure node='princely_musings'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field type='hidden' var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#node_config</value></field>"
+ "<field type=\"list-single\" var='pubsub#access_model'><value>roster</value></field>"
+ "<field type=\"text-single\" var='pubsub#body_xslt'><value>http://jabxslt.jabberstudio.org/atom_body.xslt</value></field>"
+ "<field type=\"boolean\" var='pubsub#deliver_notifications'><value>1</value></field>"
+ "<field type=\"boolean\" var='pubsub#deliver_payloads'><value>1</value></field>"
+ "<field type=\"text-single\" var='pubsub#item_expire'><value>604800</value></field>"
+ "<field type=\"text-single\" var='pubsub#max_items'><value>10</value></field>"
+ "<field type=\"text-single\" var='pubsub#max_payload_size'><value>1028</value></field>"
+ "<field type=\"list-single\" var='pubsub#notification_type'><value>headline</value></field>"
+ "<field type=\"boolean\" var='pubsub#notify_config'><value>0</value></field>"
+ "<field type=\"boolean\" var='pubsub#notify_delete'><value>0</value></field>"
+ "<field type=\"boolean\" var='pubsub#notify_retract'><value>0</value></field>"
+ "<field type=\"boolean\" var='pubsub#notify_sub'><value>0</value></field>"
+ "<field type=\"boolean\" var='pubsub#persist_items'><value>1</value></field>"
+ "<field type=\"boolean\" var='pubsub#presence_based_delivery'><value>0</value></field>"
+ "<field type=\"list-single\" var='pubsub#publish_model'><value>publishers</value></field>"
+ "<field type=\"boolean\" var='pubsub#purge_offline'><value>0</value></field>"
+ "<field type=\"list-multi\" var='pubsub#roster_groups_allowed'><value>friends</value><value>servants</value><value>courtiers</value></field>"
+ "<field type=\"list-single\" var='pubsub#send_last_published_item'><value>never</value></field>"
+ "<field type=\"text-single\" var='pubsub#title'><value>Princely Musings (Atom)</value></field>"
+ "<field type=\"text-single\" var='pubsub#type'><value>http://www.w3.org/2005/Atom</value></field>"
+ "</x>"
+ "</configure>"
+ "</pubsub>"
+ "</iq>");
+ test.inject<QString>("<iq type='result' from='pubsub.shakespeare.lit' id='qxmpp1'/>");
+
+ expectFutureVariant<QXmpp::Success>(future);
+}
+
+void tst_QXmppPubSubManager::testCancelConfig()
+{
+ auto [test, psManager] = Client();
+ auto future = psManager->cancelNodeConfiguration("pubsub.qxmpp.org", "nodeee");
+ test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='set'>"
+ "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>"
+ "<configure node='nodeee'>"
+ "<x xmlns='jabber:x:data' type='cancel'/>"
+ "</configure>"
+ "</pubsub>"
+ "</iq>");
+ test.inject<QString>("<iq id='qxmpp1' type='result'/>");
+ expectFutureVariant<QXmpp::Success>(future);
+}
+
void tst_QXmppPubSubManager::testEventNotifications_data()
{
QTest::addColumn<QString>("xml");