From 8ffa40054f3b57720e8dd2ed7868c79e5adf2552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Br=C3=BCchert?= Date: Fri, 13 Aug 2021 16:43:35 +0200 Subject: [PATCH] Import QXmppComponent --- QXmppConfig.cmake.in | 3 +- src/CMakeLists.txt | 1 + src/component/CMakeLists.txt | 47 ++++++ src/component/QXmppComponent.cpp | 166 ++++++++++++++++++++++ src/component/QXmppComponent.h | 90 ++++++++++++ src/component/QXmppComponentConfig.cpp | 85 +++++++++++ src/component/QXmppComponentConfig.h | 42 ++++++ src/component/QXmppComponentExtension.cpp | 24 ++++ src/component/QXmppComponentExtension.h | 37 +++++ src/component/QXmppOutgoingComponent.cpp | 150 +++++++++++++++++++ src/component/QXmppOutgoingComponent.h | 52 +++++++ 11 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 src/component/CMakeLists.txt create mode 100644 src/component/QXmppComponent.cpp create mode 100644 src/component/QXmppComponent.h create mode 100644 src/component/QXmppComponentConfig.cpp create mode 100644 src/component/QXmppComponentConfig.h create mode 100644 src/component/QXmppComponentExtension.cpp create mode 100644 src/component/QXmppComponentExtension.h create mode 100644 src/component/QXmppOutgoingComponent.cpp create mode 100644 src/component/QXmppOutgoingComponent.h diff --git a/QXmppConfig.cmake.in b/QXmppConfig.cmake.in index 07975c383..4ea9e3dc0 100644 --- a/QXmppConfig.cmake.in +++ b/QXmppConfig.cmake.in @@ -1,4 +1,5 @@ @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/QXmpp.cmake") -check_required_components(QXmpp) +include("${CMAKE_CURRENT_LIST_DIR}/QXmppComponent.cmake") +check_required_components(QXmpp) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccc3a2083..919dd9025 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -269,3 +269,4 @@ install( DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/qxmpp" ) +add_subdirectory(component) diff --git a/src/component/CMakeLists.txt b/src/component/CMakeLists.txt new file mode 100644 index 000000000..aa089faff --- /dev/null +++ b/src/component/CMakeLists.txt @@ -0,0 +1,47 @@ +add_library(QXmppComponent SHARED + QXmppComponent.cpp + QXmppComponentExtension.cpp + QXmppComponentConfig.cpp + QXmppOutgoingComponent.cpp +) + +set(COMPONENT_INSTALL_HEADER_FILES + QXmppComponent.h + QXmppComponentExtension.h + QXmppComponentConfig.h + QXmppOutgoingComponent.h +) + +target_link_libraries(QXmppComponent PUBLIC qxmpp Qt5::Core) +set(COMPONENT_HEADER_DIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}/qxmpp/component") +target_include_directories(QXmppComponent PUBLIC ${COMPONENT_HEADER_DIR}) + +set_target_properties(QXmppComponent PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${SO_VERSION} + EXPORT_NAME Component +) + +install( + TARGETS QXmppComponent + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + EXPORT QXmppComponentTargets +) + +install( + EXPORT QXmppComponentTargets + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/qxmpp" + FILE QXmppComponent.cmake + NAMESPACE QXmpp:: + COMPONENT Devel +) + +export( + TARGETS QXmppComponent + FILE QXmppComponent.cmake +) + +install( + FILES ${COMPONENT_INSTALL_HEADER_FILES} + DESTINATION ${COMPONENT_HEADER_DIR} +) diff --git a/src/component/QXmppComponent.cpp b/src/component/QXmppComponent.cpp new file mode 100644 index 000000000..d0a26946f --- /dev/null +++ b/src/component/QXmppComponent.cpp @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "QXmppComponent.h" +#include "QXmppOutgoingComponent.h" +#include "QXmppComponentConfig.h" +#include "QXmppComponentExtension.h" + +class QXmppComponentPrivate +{ +public: + QXmppComponentPrivate(QXmppComponent *parent); + + QXmppOutgoingComponent *component; + QVector extensions; + QXmppLogger *logger = nullptr; +}; + +QXmppComponentPrivate::QXmppComponentPrivate(QXmppComponent *parent) + : component(new QXmppOutgoingComponent(parent)) +{ +} + +QXmppComponent::QXmppComponent(QObject *parent) + : QXmppLoggable(parent), + d(new QXmppComponentPrivate(this)) +{ + connect(d->component, &QXmppOutgoingComponent::connected, this, &QXmppComponent::connected); + connect(d->component, &QXmppOutgoingComponent::disconnected, this, &QXmppComponent::disconnected); + + connect(d->component, &QXmppOutgoingComponent::iqReceived, this, &QXmppComponent::iqReceived); + connect(d->component, &QXmppOutgoingComponent::messageReceived, this, &QXmppComponent::messageReceived); + connect(d->component, &QXmppOutgoingComponent::presenceReceived, this, &QXmppComponent::presenceReceived); + connect(d->component, &QXmppOutgoingComponent::elementReceived, this, &QXmppComponent::onElementReceived); + + // logging + setLogger(QXmppLogger::getLogger()); +} + +QXmppComponent::~QXmppComponent() +{ +} + +/// +/// Registers a new \a extension with the component. +/// +/// \param extension +/// +bool QXmppComponent::addExtension(QXmppComponentExtension* extension) +{ + return insertExtension(d->extensions.size(), extension); +} + +/// +/// Registers a new \a extension with the component at the given \a index. +/// +/// \param index +/// \param extension +/// +bool QXmppComponent::insertExtension(int index, QXmppComponentExtension *extension) +{ + if (d->extensions.contains(extension)) { + qWarning("Cannot add extension, it has already been added"); + return false; + } + + extension->setParent(this); + extension->setComponent(this); + d->extensions.insert(index, extension); + return true; +} + +/// +/// Unregisters the given extension from the component. If the extension +/// is found, it will be destroyed. +/// +/// \param extension +/// +bool QXmppComponent::removeExtension(QXmppComponentExtension *extension) +{ + if (d->extensions.contains(extension)) { + d->extensions.removeAll(extension); + extension->deleteLater(); + return true; + } else { + warning("Cannot remove extension, it was never added"); + return false; + } +} + +/// +/// Returns a list containing all the component's extensions. +/// +auto QXmppComponent::extensions() -> QVector +{ + return d->extensions; +} + +/// +/// Returns the QXmppLogger associated with the current QXmppComponent. +/// +auto QXmppComponent::logger() const -> QXmppLogger* +{ + return d->logger; +} + +/// +/// Sets the QXmppLogger associated with the current QXmppComponent. +/// +void QXmppComponent::setLogger(QXmppLogger *logger) +{ + if (logger != d->logger) { + if (d->logger) { + disconnect(this, &QXmppLoggable::logMessage, + d->logger, &QXmppLogger::log); + disconnect(this, &QXmppLoggable::setGauge, + d->logger, &QXmppLogger::setGauge); + disconnect(this, &QXmppLoggable::updateCounter, + d->logger, &QXmppLogger::updateCounter); + } + + d->logger = logger; + if (d->logger) { + connect(this, &QXmppLoggable::logMessage, + d->logger, &QXmppLogger::log); + connect(this, &QXmppLoggable::setGauge, + d->logger, &QXmppLogger::setGauge); + connect(this, &QXmppLoggable::updateCounter, + d->logger, &QXmppLogger::updateCounter); + } + + emit loggerChanged(); + } +} + +QXmppComponentConfig &QXmppComponent::configuration() +{ + return d->component->config(); +} + +void QXmppComponent::connectToServer(const QXmppComponentConfig &config) +{ + d->component->config() = config; + d->component->connectToHost(); +} + +bool QXmppComponent::sendPacket(const QXmppStanza &packet) +{ + return d->component->sendPacket(packet); +} + +bool QXmppComponent::isConnected() +{ + return d->component->isConnected() && d->component->isAuthenticated(); +} + +void QXmppComponent::onElementReceived(const QDomElement &element, bool &handled) +{ + for (auto *extension : std::as_const(d->extensions)) { + if (extension->handleStanza(element)) { + handled = true; + return; + } + } +} diff --git a/src/component/QXmppComponent.h b/src/component/QXmppComponent.h new file mode 100644 index 000000000..1ff95e993 --- /dev/null +++ b/src/component/QXmppComponent.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#ifndef QXMPPCOMPONENT_H +#define QXMPPCOMPONENT_H + +#include +#include +#include + +class QXmppComponentPrivate; +class QXmppComponentConfig; +class QXmppComponentExtension; +class QDomElement; +class QXmppStanza; +class QXmppPresence; +class QXmppMessage; +class QXmppIq; + +class QXmppComponent : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppComponent(QObject *parent = nullptr); + ~QXmppComponent(); + + bool addExtension(QXmppComponentExtension *extension); + bool insertExtension(int index, QXmppComponentExtension *extension); + bool removeExtension(QXmppComponentExtension *extension); + auto extensions() -> QVector; + template + auto findExtension() -> T *; + + auto logger() const -> QXmppLogger*; + void setLogger(QXmppLogger* logger); + Q_SIGNAL void loggerChanged(); + + QXmppComponentConfig &configuration(); + + Q_SLOT void connectToServer(const QXmppComponentConfig &config); + Q_SLOT bool sendPacket(const QXmppStanza &); + + bool isConnected(); + Q_SIGNAL void connected(); + Q_SIGNAL void disconnected(); + + /// This signal is emitted when a presence is received. + Q_SIGNAL void presenceReceived(const QXmppPresence &); + + /// This signal is emitted when a message is received. + Q_SIGNAL void messageReceived(const QXmppMessage &); + + /// This signal is emitted when an IQ response (type result or error) has + /// been received that was not handled by elementReceived(). + Q_SIGNAL void iqReceived(const QXmppIq &); + +private: + void onElementReceived(const QDomElement &element, bool &handled); + + std::unique_ptr d; +}; + +/// +/// \brief Returns the extension which can be cast into type T*, or 0 +/// if there is no such extension. +/// +/// Usage example: +/// \code +/// QXmppDiscoveryManager* ext = client->findExtension(); +/// if(ext) +/// { +/// //extension found, do stuff... +/// } +/// \endcode +/// +template +T *QXmppComponent::findExtension() +{ + const auto list = extensions(); + for (auto ext : list) { + if (auto *extension = qobject_cast(ext)) { + return extension; + } + } + return nullptr; +} + +#endif // QXMPPCOMPONENT_H diff --git a/src/component/QXmppComponentConfig.cpp b/src/component/QXmppComponentConfig.cpp new file mode 100644 index 000000000..5bf6ceade --- /dev/null +++ b/src/component/QXmppComponentConfig.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "QXmppComponentConfig.h" + +class QXmppComponentConfigPrivate : public QSharedData +{ +public: + QString componentName; + QString host; + quint16 port; + QString secret; + bool parseAllMessages = true; + bool parseAllPresences = true; +}; + +QXmppComponentConfig::QXmppComponentConfig() + : d(new QXmppComponentConfigPrivate) +{ +} + +QXmppComponentConfig::QXmppComponentConfig(const QXmppComponentConfig &other) = default; +QXmppComponentConfig::~QXmppComponentConfig() = default; +QXmppComponentConfig &QXmppComponentConfig::operator=(const QXmppComponentConfig &other) = default; + +QString QXmppComponentConfig::componentName() const +{ + return d->componentName; +} + +void QXmppComponentConfig::setComponentName(const QString &componentName) +{ + d->componentName = componentName; +} + +QString QXmppComponentConfig::host() const +{ + return d->host; +} + +void QXmppComponentConfig::setHost(const QString &host) +{ + d->host = host; +} + +quint16 QXmppComponentConfig::port() const +{ + return d->port; +} + +void QXmppComponentConfig::setPort(const quint16 &port) +{ + d->port = port; +} + +QString QXmppComponentConfig::secret() const +{ + return d->secret; +} + +void QXmppComponentConfig::setSecret(const QString &secret) +{ + d->secret = secret; +} + +bool QXmppComponentConfig::parseAllMessages() const +{ + return d->parseAllMessages; +} + +void QXmppComponentConfig::setParseAllMessages(bool parseAllMessages) +{ + d->parseAllMessages = parseAllMessages; +} + +bool QXmppComponentConfig::parseAllPresences() const +{ + return d->parseAllPresences; +} + +void QXmppComponentConfig::setParseAllPresences(bool parseAllPresences) +{ + d->parseAllPresences = parseAllPresences; +} diff --git a/src/component/QXmppComponentConfig.h b/src/component/QXmppComponentConfig.h new file mode 100644 index 000000000..c96dd6581 --- /dev/null +++ b/src/component/QXmppComponentConfig.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#ifndef QXMPPCOMPONENTCONFIG_H +#define QXMPPCOMPONENTCONFIG_H + +#include + +class QXmppComponentConfigPrivate; + +class QXmppComponentConfig +{ +public: + QXmppComponentConfig(); + QXmppComponentConfig(const QXmppComponentConfig &other); + ~QXmppComponentConfig(); + QXmppComponentConfig &operator=(const QXmppComponentConfig &other); + + QString componentName() const; + void setComponentName(const QString &componentName); + + QString host() const; + void setHost(const QString &host); + + quint16 port() const; + void setPort(const quint16 &port); + + QString secret() const; + void setSecret(const QString &secret); + + bool parseAllMessages() const; + void setParseAllMessages(bool parseAllMessages); + + bool parseAllPresences() const; + void setParseAllPresences(bool parseAllPresences); + +private: + QSharedDataPointer d; +}; + +#endif // QXMPPCOMPONENTCONFIG_H diff --git a/src/component/QXmppComponentExtension.cpp b/src/component/QXmppComponentExtension.cpp new file mode 100644 index 000000000..15b64eef3 --- /dev/null +++ b/src/component/QXmppComponentExtension.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "QXmppComponentExtension.h" + +QXmppComponentExtension::QXmppComponentExtension() + : m_component(nullptr) +{ +} + +QXmppComponentExtension::~QXmppComponentExtension() +{ +} + +QXmppComponent *QXmppComponentExtension::component() +{ + return m_component; +} + +void QXmppComponentExtension::setComponent(QXmppComponent *component) +{ + m_component = component; +} diff --git a/src/component/QXmppComponentExtension.h b/src/component/QXmppComponentExtension.h new file mode 100644 index 000000000..b27c88774 --- /dev/null +++ b/src/component/QXmppComponentExtension.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#ifndef QXMPPCOMPONENTEXTENSION_H +#define QXMPPCOMPONENTEXTENSION_H + +#include + +class QDomElement; +class QXmppComponent; + +class QXmppComponentExtension : public QXmppLoggable +{ +public: + QXmppComponentExtension(); + ~QXmppComponentExtension() override; + + /// \brief You need to implement this method to process incoming XMPP + /// stanzas. + /// + /// You should return true if the stanza was handled and no further + /// processing should occur, or false to let other extensions process + /// the stanza. + virtual bool handleStanza(const QDomElement &stanza) = 0; + +protected: + QXmppComponent *component(); + virtual void setComponent(QXmppComponent *component); + +private: + QXmppComponent *m_component; + + friend class QXmppComponent; +}; + +#endif // QXMPPCOMPONENTEXTENSION_H diff --git a/src/component/QXmppOutgoingComponent.cpp b/src/component/QXmppOutgoingComponent.cpp new file mode 100644 index 000000000..4bb77a5bd --- /dev/null +++ b/src/component/QXmppOutgoingComponent.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "QXmppOutgoingComponent.h" + +#include +#include +#include + +#include +#include +#include + +#include "QXmppComponentConfig.h" + +constexpr auto ns_component = u"jabber:component:accept"; + +class Handshake +{ +public: + Handshake(const QString &secret, const QString &streamId) + { + const auto data = streamId.toUtf8() + secret.toUtf8(); + m_handshake = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex(); + } + + QByteArray serialize() const + { + return "" + m_handshake + ""; + } + +private: + QByteArray m_handshake; +}; + +class QXmppOutgoingComponentPrivate +{ +public: + QXmppComponentConfig config; + bool authenticated = false; +}; + +QXmppOutgoingComponent::QXmppOutgoingComponent(QObject *parent) + : QXmppStream(parent), + d(new QXmppOutgoingComponentPrivate) +{ + auto *socket = new QSslSocket(this); + setSocket(socket); +} + +QXmppOutgoingComponent::~QXmppOutgoingComponent() +{ +} + +QXmppComponentConfig &QXmppOutgoingComponent::config() +{ + return d->config; +} + +void QXmppOutgoingComponent::connectToHost() +{ + const auto host = d->config.host(); + const auto port = d->config.port(); + + if (host.isEmpty() || !port) { + warning(QStringLiteral("Cannot connect to server: Invalid host or port!")); + return; + } + + info(QStringLiteral("Connecting to %1:%2").arg(host, QString::number(port))); + + socket()->connectToHost(host, port); +} + +bool QXmppOutgoingComponent::isAuthenticated() const +{ + return d->authenticated; +} + +void QXmppOutgoingComponent::handleStart() +{ + QXmppStream::handleStart(); + + // clean up + d->authenticated = false; + + const auto component = d->config.componentName().toUtf8(); + const QByteArray data = + ""; + sendData(data); +} + +void QXmppOutgoingComponent::handleStanza(const QDomElement &element) +{ + if (!d->authenticated && element.tagName() == u"handshake") { + d->authenticated = true; + info(QStringLiteral("Successfully connected and authenticated!")); + emit connected(); + } + + bool handled = false; + emit elementReceived(element, handled); + if (handled) + return; + + const auto xmlns = element.namespaceURI(); + if (xmlns == ns_component) { + if (element.tagName() == u"iq") { + QXmppIq iqPacket; + iqPacket.parse(element); + + // if we didn't understant the iq, reply with error + // except for "result" and "error" iqs + if (iqPacket.type() != QXmppIq::Result && + iqPacket.type() != QXmppIq::Error) { + QXmppIq iq(QXmppIq::Error); + iq.setId(iqPacket.id()); + iq.setFrom(iqPacket.to()); + iq.setTo(iqPacket.from()); + iq.setError({ QXmppStanza::Error::Cancel, + QXmppStanza::Error::FeatureNotImplemented }); + sendPacket(iq); + } else { + emit iqReceived(iqPacket); + } + } else if (d->config.parseAllPresences() && element.tagName() == u"presence") { + QXmppPresence presence; + presence.parse(element); + + // emit presence + emit presenceReceived(presence); + } else if (d->config.parseAllMessages() && element.tagName() == u"message") { + QXmppMessage message; + message.parse(element); + + // emit message + emit messageReceived(message); + } + } +} + +void QXmppOutgoingComponent::handleStream(const QDomElement &element) +{ + const auto streamId = element.attribute("id"); + + sendData(Handshake(d->config.secret(), streamId).serialize()); +} diff --git a/src/component/QXmppOutgoingComponent.h b/src/component/QXmppOutgoingComponent.h new file mode 100644 index 000000000..aac20d7f5 --- /dev/null +++ b/src/component/QXmppOutgoingComponent.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.0-or-later + +#ifndef QXMPPOUTGOINGCOMPONENT_H +#define QXMPPOUTGOINGCOMPONENT_H + +#include + +#include + +class QXmppOutgoingComponentPrivate; +class QXmppComponentConfig; +class QXmppIq; +class QXmppMessage; +class QXmppPresence; + +class QXmppOutgoingComponent : public QXmppStream +{ + Q_OBJECT + +public: + QXmppOutgoingComponent(QObject *parent = nullptr); + ~QXmppOutgoingComponent() override; + + QXmppComponentConfig &config(); + void connectToHost(); + bool isAuthenticated() const; + + /// This signal is emitted when an element is received. + Q_SIGNAL void elementReceived(const QDomElement &element, bool &handled); + + /// This signal is emitted when a presence is received. + Q_SIGNAL void presenceReceived(const QXmppPresence &); + + /// This signal is emitted when a message is received. + Q_SIGNAL void messageReceived(const QXmppMessage &); + + /// This signal is emitted when an IQ response (type result or error) has + /// been received that was not handled by elementReceived(). + Q_SIGNAL void iqReceived(const QXmppIq &); + +protected: + void handleStart() override; + void handleStanza(const QDomElement &element) override; + void handleStream(const QDomElement &element) override; + +private: + std::unique_ptr d; +}; + +#endif // QXMPPOUTGOINGCOMPONENT_H