aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2022-10-16 21:52:26 +0200
committerLinus Jahn <lnj@kaidan.im>2022-10-17 16:26:03 +0200
commit556442eab0bdbd5b30a5fad3112d88c4b124ff5e (patch)
treec82c8d27ed353f9cd79538a0c0e7e620a481ce10 /src
parent66e5f060abe831fa08a758b9de44b29bfec8b3ba (diff)
downloadqxmpp-556442eab0bdbd5b30a5fad3112d88c4b124ff5e.tar.gz
Add automated IQ request handling functions
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/client/QXmppIqHandling.cpp27
-rw-r--r--src/client/QXmppIqHandling.h279
3 files changed, 308 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1b4595f5..8aae863a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -117,6 +117,7 @@ set(INSTALL_HEADER_FILES
client/QXmppHttpFileSharingProvider.h
client/QXmppHttpUploadManager.h
client/QXmppInvokable.h
+ client/QXmppIqHandling.h
client/QXmppMamManager.h
client/QXmppMessageHandler.h
client/QXmppMessageReceiptManager.h
@@ -246,6 +247,7 @@ set(SOURCE_FILES
client/QXmppHttpUploadManager.cpp
client/QXmppInternalClientExtension.cpp
client/QXmppInvokable.cpp
+ client/QXmppIqHandling.cpp
client/QXmppMamManager.cpp
client/QXmppMessageReceiptManager.cpp
client/QXmppMucManager.cpp
diff --git a/src/client/QXmppIqHandling.cpp b/src/client/QXmppIqHandling.cpp
new file mode 100644
index 00000000..be164f0f
--- /dev/null
+++ b/src/client/QXmppIqHandling.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppIqHandling.h"
+
+void QXmpp::Private::sendIqReply(QXmppClient *client,
+ const QString &requestId,
+ const QString &requestFrom,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ QXmppIq &&iq)
+{
+ // default type is 'result'
+ switch (iq.type()) {
+ case QXmppIq::Get:
+ case QXmppIq::Set:
+ iq.setType(QXmppIq::Result);
+ break;
+ case QXmppIq::Error:
+ case QXmppIq::Result:
+ break;
+ }
+
+ iq.setTo(requestFrom);
+ iq.setId(requestId);
+ client->reply(std::move(iq), e2eeMetadata);
+}
diff --git a/src/client/QXmppIqHandling.h b/src/client/QXmppIqHandling.h
new file mode 100644
index 00000000..e7db2b5d
--- /dev/null
+++ b/src/client/QXmppIqHandling.h
@@ -0,0 +1,279 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPIQHANDLING_H
+#define QXMPPIQHANDLING_H
+
+#include "QXmppClient.h"
+#include "QXmppE2eeMetadata.h"
+#include "QXmppFutureUtils_p.h"
+#include "QXmppIq.h"
+
+#include <QDomElement>
+
+namespace QXmpp {
+
+namespace Private {
+
+ QXMPP_EXPORT void sendIqReply(QXmppClient *client,
+ const QString &requestId,
+ const QString &requestFrom,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ QXmppIq &&iq);
+
+ template<typename... VariantTypes>
+ void processHandleIqResult(QXmppClient *client,
+ const QString &requestId,
+ const QString &requestFrom,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ std::variant<VariantTypes...> &&result)
+ {
+ std::visit([&](auto &&value) {
+ using T = std::decay_t<decltype(value)>;
+
+ static_assert(
+ std::is_base_of_v<QXmppIq, T> || std::is_same_v<QXmppStanza::Error, T>,
+ "QXmppIq based type or QXmppStanza::Error required");
+
+ if constexpr (std::is_base_of_v<QXmppIq, T>) {
+ sendIqReply(client, requestId, requestFrom, e2eeMetadata, std::move(value));
+ } else if constexpr (std::is_same_v<QXmppStanza::Error, T>) {
+ QXmppIq iq;
+ iq.setType(QXmppIq::Error);
+ iq.setError(value);
+ sendIqReply(client, requestId, requestFrom, e2eeMetadata, std::move(iq));
+ }
+ },
+ std::move(result));
+ }
+
+ template<typename T>
+ void processHandleIqResult(QXmppClient *client,
+ const QString &requestId,
+ const QString &requestFrom,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ QFuture<T> future)
+ {
+ Private::await(future, client, [client, requestId, requestFrom, e2eeMetadata](T result) {
+ processHandleIqResult(client, requestId, requestFrom, e2eeMetadata, result);
+ });
+ }
+
+ template<typename T, typename = std::enable_if_t<std::is_base_of_v<QXmppIq, T>, void>>
+ void processHandleIqResult(QXmppClient *client,
+ const QString &requestId,
+ const QString &requestFrom,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ T &&result)
+ {
+ sendIqReply(client, requestId, requestFrom, e2eeMetadata, std::move(result));
+ }
+
+ template<typename Handler, typename IqType>
+ auto invokeIqHandler(Handler handler, IqType &&iq)
+ {
+ if constexpr (std::is_invocable<Handler, IqType &&>()) {
+ return handler(std::move(iq));
+ } else {
+ return handler->handleIq(std::move(iq));
+ }
+ }
+
+ template<typename IqType, typename Handler>
+ bool handleIqType(Handler handler,
+ QXmppClient *client,
+ const QDomElement &element,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ const QString &tagName,
+ const QString &xmlNamespace)
+ {
+ if (IqType::checkIqType(tagName, xmlNamespace)) {
+ IqType iq;
+ iq.parse(element);
+ iq.setE2eeMetadata(e2eeMetadata);
+
+ processHandleIqResult(
+ client,
+ iq.id(),
+ iq.from(),
+ invokeIqHandler(std::forward<Handler>(handler), std::move(iq)));
+ return true;
+ }
+ return false;
+ }
+
+} // namespace Private
+
+///
+/// Parses IQ requests, calls a handler and sends an IQ result or error.
+///
+/// It is the easiest to explain this function with a few examples.
+///
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq>(element, e2eeMetadata, client, [](QXmppVersionIq iq) -> std::variant<QXmppVersionIq, QXmppStanza::Error> {
+/// if (iq.type() == QXmppIq::Get) {
+/// QXmppVersionIq response;
+/// response.setName("MyApp");
+/// response.setVersion("1.0");
+/// // id, to and type of the IQ are set automatically.
+/// return response;
+/// } else if (iq.type() == QXmppIq::Set) {
+/// return QXmppStanza::Error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest, "IQ must be of type 'get'.");
+/// }
+/// });
+/// ```
+///
+/// It is also possible to handle multiple IQ types.
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(
+/// element, e2eeMetadata, client, [](std::variant<QXmppVersionIq, QXmppVCardIq> iqVariant) {
+/// // ...
+/// });
+/// ```
+/// It doesn't need to be a std::variant, it's only important that the object is callable with all
+/// the IQ types. You can for example use different lambdas per type using this 'overloaded' helper.
+/// ```
+/// template<class... Ts>
+/// struct overloaded : Ts... {
+/// using Ts::operator()...;
+/// };
+/// // explicit deduction guide (not needed as of C++20)
+/// template<class... Ts>
+/// overloaded(Ts...) -> overloaded<Ts...>;
+///
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(
+/// element, e2eeMetadata, client, overloaded {
+/// [](QXmppVersionIq iq) {
+/// // ...
+/// },
+/// [](QXmppVCardIq iq) {
+/// // ...
+/// }
+/// });
+/// ```
+///
+/// And another option is to an object with `handleIq()` functions.
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(element, e2eeMetadata, client, this);
+/// // will call this->handleIq(QXmppVersionIq) or this->handleIq(QXmppVCardIq)
+/// ```
+///
+/// The return type of the handler function can be:
+/// 1. an QXmppIq based type
+/// 2. a std::variant of QXmppIq based types (multiple are possible) and optionally also QXmppStanza::Error
+/// 3. a QFuture of 1. or 2.
+///
+/// You don't need to set the values for id or the to address on the IQ result because that's done
+/// automatically. Unless you want to return an error IQ you also don't need to set the IQ type.
+///
+/// If you return an QXmppStanza::Error, a normal QXmppIq with the error will be sent.
+///
+/// The provided optional QXmppE2eeMetadata is set on the parsed IQ and used to correctly encrypt
+/// the IQ response using QXmppClient::reply().
+///
+/// \param element The DOM element that might contain an IQ.
+/// \param e2eeMetadata The end-to-end encryption metadata that is used to encrypt the response
+/// correctly and to be set on the parsed IQ.
+/// \param client The client that should be used to send the response.
+/// \param handler Function that can handle the IQ types from the template parameter or an object
+/// that has handleIq() functions for each of the IQ types.
+/// \return Whether the IQ could be parsed, handled and a response was or will be sent.
+/// \since QXmpp 1.5
+///
+template<typename... IqTypes, typename Handler>
+bool handleIqRequests(const QDomElement &element,
+ const std::optional<QXmppE2eeMetadata> &e2eeMetadata,
+ QXmppClient *client,
+ Handler handler)
+{
+ if (element.tagName() == "iq") {
+ auto queryElement = element.firstChildElement();
+ auto tagName = queryElement.tagName();
+ auto xmlns = queryElement.namespaceURI();
+
+ return (Private::handleIqType<IqTypes>(handler, client, element, e2eeMetadata, tagName, xmlns) || ...);
+ }
+ return false;
+}
+
+///
+/// Parses IQ requests, calls a handler and sends an IQ result or error.
+///
+/// It is the easiest to explain this function with a few examples.
+///
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq>(element, client, [](QXmppVersionIq iq) -> std::variant<QXmppVersionIq, QXmppStanza::Error> {
+/// if (iq.type() == QXmppIq::Get) {
+/// QXmppVersionIq response;
+/// response.setName("MyApp");
+/// response.setVersion("1.0");
+/// // id, to and type of the IQ are set automatically.
+/// return response;
+/// } else if (iq.type() == QXmppIq::Set) {
+/// return QXmppStanza::Error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest, "IQ must be of type 'get'.");
+/// }
+/// });
+/// ```
+///
+/// It is also possible to handle multiple IQ types.
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(
+/// element, client, [](std::variant<QXmppVersionIq, QXmppVCardIq> iqVariant) {
+/// // ...
+/// });
+/// ```
+/// It doesn't need to be a std::variant, it's only important that the object is callable with all
+/// the IQ types. You can for example use different lambdas per type using this 'overloaded' helper.
+/// ```
+/// template<class... Ts>
+/// struct overloaded : Ts... {
+/// using Ts::operator()...;
+/// };
+/// // explicit deduction guide (not needed as of C++20)
+/// template<class... Ts>
+/// overloaded(Ts...) -> overloaded<Ts...>;
+///
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(
+/// element, client, overloaded {
+/// [](QXmppVersionIq iq) {
+/// // ...
+/// },
+/// [](QXmppVCardIq iq) {
+/// // ...
+/// }
+/// });
+/// ```
+///
+/// And another option is to an object with `handleIq()` functions.
+/// ```
+/// auto handled = QXmpp::handleIqElements<QXmppVersionIq, QXmppVCardIq>(element, client, this);
+/// // will call this->handleIq(QXmppVersionIq) or this->handleIq(QXmppVCardIq)
+/// ```
+///
+/// The return type of the handler function can be:
+/// 1. an QXmppIq based type
+/// 2. a std::variant of QXmppIq based types (multiple are possible) and optionally also QXmppStanza::Error
+/// 3. a QFuture of 1. or 2.
+///
+/// You don't need to set the values for id or the to address on the IQ result because that's done
+/// automatically. Unless you want to return an error IQ you also don't need to set the IQ type.
+///
+/// If you return an QXmppStanza::Error, a normal QXmppIq with the error will be sent.
+///
+/// \param element The DOM element that might contain an IQ.
+/// \param client The client that should be used to send the response.
+/// \param handler Function that can handle the IQ types from the template parameter or an object
+/// that has handleIq() functions for each of the IQ types.
+/// \return Whether the IQ could be parsed, handled and a response was or will be sent.
+/// \since QXmpp 1.5
+///
+template<typename... IqTypes, typename Handler>
+bool handleIqRequests(const QDomElement &element, QXmppClient *client, Handler handler)
+{
+ return handleIqRequests(element, std::nullopt, client, std::forward<Handler>(handler));
+}
+
+} // namespace QXmpp
+
+#endif // QXMPPIQHANDLING_H