aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorJonah Brüchert <jbb@kaidan.im>2022-09-09 23:15:10 +0200
committerLinus Jahn <lnj@kaidan.im>2022-09-29 23:46:36 +0200
commit68f167995e7ba71a6f2e556a7a0eab3d234e2d1a (patch)
treede645b606d96f01da7ea6db63e90224519a89de3 /src/client
parent7b02df3ef42ccb2d8c40eea901c5c6dd4b140204 (diff)
downloadqxmpp-68f167995e7ba71a6f2e556a7a0eab3d234e2d1a.tar.gz
Implement XEP-0448: Stateless File Sharing
This adds a file sharing manager that is capable of using multiple back ends. Currently implemented are a normal HTTP File Upload backend and an encrypted HTTP File Upload. Jingle File Transfer could be implemented later. Co-authored-by: Linus Jahn <lnj@kaidan.im>
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppDownload.cpp26
-rw-r--r--src/client/QXmppDownload.h23
-rw-r--r--src/client/QXmppEncryptedHttpFileSharingProvider.cpp123
-rw-r--r--src/client/QXmppEncryptedHttpFileSharingProvider.h33
-rw-r--r--src/client/QXmppFileSharingManager.cpp210
-rw-r--r--src/client/QXmppFileSharingManager.h77
-rw-r--r--src/client/QXmppFileSharingProvider.h52
-rw-r--r--src/client/QXmppFileTransfer.cpp43
-rw-r--r--src/client/QXmppFileTransfer.h31
-rw-r--r--src/client/QXmppHttpFileSharingProvider.cpp207
-rw-r--r--src/client/QXmppHttpFileSharingProvider.h40
-rw-r--r--src/client/QXmppHttpUploadManager.cpp9
-rw-r--r--src/client/QXmppUpload.cpp47
-rw-r--r--src/client/QXmppUpload.h47
14 files changed, 961 insertions, 7 deletions
diff --git a/src/client/QXmppDownload.cpp b/src/client/QXmppDownload.cpp
new file mode 100644
index 00000000..0ece1906
--- /dev/null
+++ b/src/client/QXmppDownload.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppDownload.h"
+
+///
+/// \class QXmppDownload
+///
+/// \brief Provides progress of stateless file sharing uploads.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \typedef QXmppDownload::Result
+///
+/// \brief Contains QXmpp::Success (successfully finished), QXmpp::Cancelled (manually cancelled)
+/// or QXmppError (an error occured while downloading).
+///
+
+///
+/// \fn QXmppDownload::finished
+///
+/// Emitted when the download has finished.
+///
diff --git a/src/client/QXmppDownload.h b/src/client/QXmppDownload.h
new file mode 100644
index 00000000..3256aaea
--- /dev/null
+++ b/src/client/QXmppDownload.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPDOWNLOAD_H
+#define QXMPPDOWNLOAD_H
+
+#include "QXmppError.h"
+#include "QXmppFileTransfer.h"
+
+#include <variant>
+
+class QXMPP_EXPORT QXmppDownload : public QXmppFileTransfer
+{
+ Q_OBJECT
+public:
+ using Result = std::variant<QXmpp::Success, QXmpp::Cancelled, QXmppError>;
+ Q_SIGNAL void finished(QXmppDownload::Result);
+};
+
+Q_DECLARE_METATYPE(QXmppDownload::Result);
+
+#endif // QXMPPDOWNLOAD_H
diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.cpp b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp
new file mode 100644
index 00000000..b6e82621
--- /dev/null
+++ b/src/client/QXmppEncryptedHttpFileSharingProvider.cpp
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppEncryptedHttpFileSharingProvider.h"
+
+#include "QXmppClient.h"
+#include "QXmppFileEncryption.h"
+#include "QXmppHttpUploadManager.h"
+#include "QXmppUpload.h"
+#include "QXmppUtils.h"
+
+#include "QcaInitializer_p.h"
+#include <QMimeDatabase>
+
+using namespace QXmpp;
+using namespace QXmpp::Private;
+
+class QXmppSfsEncryptedHttpUpload : public QXmppUpload
+{
+ Q_OBJECT
+
+public:
+ QXmppSfsEncryptedHttpUpload(std::shared_ptr<QXmppHttpUpload> &&httpUpload, const QByteArray &key, const QByteArray &iv)
+ : inner(std::move(httpUpload))
+ {
+ connect(inner.get(), &QXmppHttpUpload::finished, this, [=](const QXmppHttpUpload::Result &result) {
+ Q_EMIT uploadFinished(std::visit([=](auto &&value) -> QXmpp::Private::UploadResult {
+ using T = std::decay_t<decltype(value)>;
+ if constexpr (std::is_same_v<T, QUrl>) {
+ QXmppEncryptedFileSource encryptedSource;
+ encryptedSource.setKey(key);
+ encryptedSource.setIv(iv);
+ encryptedSource.setHttpSources({ QXmppHttpFileSource(value) });
+ return std::any(encryptedSource);
+ } else if constexpr (std::is_same_v<T, Cancelled>) {
+ return Cancelled {};
+ } else if constexpr (std::is_same_v<T, QXmppError>) {
+ return value;
+ }
+ },
+ result));
+ });
+ }
+
+ float progress() override { return inner->progress(); }
+ void cancel() override { inner->cancel(); }
+ bool isFinished() override { return inner->isFinished(); }
+ quint64 bytesTransferred() override { return inner->bytesSent(); }
+ quint64 bytesTotal() override { return inner->bytesTotal(); }
+
+private:
+ std::shared_ptr<QXmppHttpUpload> inner;
+};
+
+///
+/// \class QXmppEncryptedHttpFileSharingProvider
+///
+/// Support for storing files encrypted on an HTTP server
+///
+/// \since QXmpp 1.5
+///
+
+class QXmppEncryptedHttpFileSharingProviderPrivate
+{
+public:
+ QXmpp::Private::QcaInitializer init;
+ QXmppHttpUploadManager *manager;
+ QXmppHttpFileSharingProvider *httpProvider;
+};
+
+///
+/// \brief Create a new QXmppEncryptedHttpFileSharingProvider
+/// \param client
+/// \param netManager QNetworkAccessManager that can be reused all over your application.
+///
+QXmppEncryptedHttpFileSharingProvider::QXmppEncryptedHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager)
+ : d(std::make_unique<QXmppEncryptedHttpFileSharingProviderPrivate>())
+{
+ qRegisterMetaType<QXmpp::Private::UploadResult>();
+ Q_ASSERT(client);
+ d->manager = client->findExtension<QXmppHttpUploadManager>();
+ Q_ASSERT(d->manager);
+ d->httpProvider = new QXmppHttpFileSharingProvider(client, netManager);
+}
+
+QXmppEncryptedHttpFileSharingProvider::~QXmppEncryptedHttpFileSharingProvider() = default;
+
+std::shared_ptr<QXmppDownload> QXmppEncryptedHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr<QIODevice> &&target)
+{
+ QXmppEncryptedFileSource encryptedSource;
+ try {
+ encryptedSource = std::any_cast<QXmppEncryptedFileSource>(source);
+ } catch (const std::bad_any_cast &) {
+ qFatal("QXmppEncryptedHttpFileSharingProvider::downloadFile can only handle QXmppEncryptedFileSource sources");
+ }
+
+ auto httpSource = encryptedSource.httpSources().front();
+ auto output = std::make_unique<Encryption::DecryptionDevice>(std::move(target), encryptedSource.cipher(), encryptedSource.iv(), encryptedSource.key());
+ return d->httpProvider->downloadFile(httpSource, std::move(output));
+}
+
+std::shared_ptr<QXmppUpload> QXmppEncryptedHttpFileSharingProvider::uploadFile(
+ std::unique_ptr<QIODevice> data,
+ const QXmppFileMetadata &info)
+{
+ auto cipher = Aes256CbcPkcs7;
+ auto key = Encryption::generateKey(cipher);
+ auto iv = Encryption::generateInitializationVector(cipher);
+
+ auto encDevice = std::make_unique<Encryption::EncryptionDevice>(std::move(data), cipher, key, iv);
+ auto encryptedSize = encDevice->size();
+
+ auto upload = d->manager->uploadFile(
+ std::move(encDevice),
+ QXmppUtils::generateStanzaHash(10),
+ QMimeDatabase().mimeTypeForName("application/octet-stream"),
+ encryptedSize);
+
+ return std::make_shared<QXmppSfsEncryptedHttpUpload>(std::move(upload), key, iv);
+}
+
+#include "QXmppEncryptedHttpFileSharingProvider.moc"
diff --git a/src/client/QXmppEncryptedHttpFileSharingProvider.h b/src/client/QXmppEncryptedHttpFileSharingProvider.h
new file mode 100644
index 00000000..6c43d8cd
--- /dev/null
+++ b/src/client/QXmppEncryptedHttpFileSharingProvider.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H
+#define QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H
+
+#include "QXmppEncryptedFileSource.h"
+#include "QXmppHttpFileSharingProvider.h"
+
+class QXmppEncryptedHttpFileSharingProviderPrivate;
+
+class QXMPP_EXPORT QXmppEncryptedHttpFileSharingProvider : public QXmppFileSharingProvider
+{
+public:
+ /// \cond
+ using SourceType = QXmppEncryptedFileSource;
+ /// \endcond
+
+ QXmppEncryptedHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager);
+ ~QXmppEncryptedHttpFileSharingProvider() override;
+
+ auto downloadFile(const std::any &source,
+ std::unique_ptr<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> override;
+ auto uploadFile(
+ std::unique_ptr<QIODevice> data,
+ const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> override;
+
+private:
+ std::unique_ptr<QXmppEncryptedHttpFileSharingProviderPrivate> d;
+};
+
+#endif // QXMPPENCRYPTEDHTTPFILESHARINGPROVIDER_H
diff --git a/src/client/QXmppFileSharingManager.cpp b/src/client/QXmppFileSharingManager.cpp
new file mode 100644
index 00000000..2daf980a
--- /dev/null
+++ b/src/client/QXmppFileSharingManager.cpp
@@ -0,0 +1,210 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppFileSharingManager.h"
+
+#include "QXmppBitsOfBinaryContentId.h"
+#include "QXmppBitsOfBinaryData.h"
+#include "QXmppClient.h"
+#include "QXmppDownload.h"
+#include "QXmppFileMetadata.h"
+#include "QXmppFileShare.h"
+#include "QXmppFutureUtils_p.h"
+#include "QXmppHashing_p.h"
+#include "QXmppThumbnail.h"
+#include "QXmppUpload.h"
+#include "QXmppUploadRequestManager.h"
+
+#include <any>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QFutureInterface>
+#include <QMimeDatabase>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+
+using namespace QXmpp;
+using namespace QXmpp::Private;
+
+///
+/// \class QXmppFileSharingProvider
+///
+/// Base class for Stateless File Sharing providers
+///
+/// A provider defines the way that files can be uploaded and downloaded.
+///
+/// An example is the QXmppHttpFileSharingProvider, which uses HTTP File Upload.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \class QXmppFileSharingManager
+///
+/// The file sharing manager allows to easily send and retrieve files in a chat.
+///
+/// \since QXmpp 1.5
+///
+
+QXmppFileSharingManager::QXmppFileSharingManager()
+ : m_metadataGenerator([](std::unique_ptr<QIODevice> &&) -> QFuture<std::shared_ptr<MetadataGeneratorResult>> {
+ return makeReadyFuture(std::make_shared<MetadataGeneratorResult>());
+ })
+{
+}
+
+///
+/// \typedef QXmppFileSharingManager::MetadataGenerator
+///
+/// The function signature of a metadata generator function
+///
+
+///
+/// \brief Register a function that is called when metadata needs to be gererated for a file.
+///
+/// The function is passed a QIODevice, so if you need the path of the file on disk,
+/// you can dynamically cast it to a QFile and access the fileName.
+/// When doing that, make sure to check the result,
+/// as in the future this function might be passed other QIODevices than QFile.
+///
+void QXmppFileSharingManager::setMetadataGenerator(MetadataGenerator &&generator)
+{
+ m_metadataGenerator = std::move(generator);
+}
+
+///
+/// \brief Upload a file in a way that it can be attached to a message.
+/// \param provider The provider class decides how the file is uploaded
+/// \param filePath Path to a file that should be uploaded
+/// \param description Optional description of the file
+/// \return An object that allows to track the progress of the upload.
+/// Once the upload is finished, the finished signal is emitted on it.
+///
+std::shared_ptr<QXmppUpload> QXmppFileSharingManager::sendFile(std::shared_ptr<QXmppFileSharingProvider> provider,
+ const QString &filePath,
+ const std::optional<QString> &description)
+{
+ std::shared_ptr<QXmppUpload> upload;
+
+ QFileInfo fileInfo(filePath);
+ auto metadata = QXmppFileMetadata::fromFileInfo(fileInfo);
+ if (description) {
+ metadata.setDescription(description);
+ }
+
+ auto openFile = [=]() -> std::unique_ptr<QIODevice> {
+ auto device = std::make_unique<QFile>(fileInfo.absoluteFilePath());
+ if (!device->open(QIODevice::ReadOnly)) {
+ Q_EMIT upload->finished(QXmppError::fromIoDevice(*device));
+ }
+ return device;
+ };
+
+ auto metadataFuture = m_metadataGenerator(openFile());
+ auto hashesFuture = calculateHashes(openFile(), { HashAlgorithm::Sha256 });
+
+ upload = provider->uploadFile(openFile(), metadata);
+ upload->metadata = metadata;
+
+ connect(upload.get(), &QXmppUpload::uploadFinished, this, [=](auto &&uploadResult) {
+ if (std::holds_alternative<std::any>(uploadResult)) {
+ auto source = std::get<std::any>(uploadResult);
+ await(metadataFuture, this, [=](auto &&result) mutable {
+ if (result->dimensions) {
+ upload->metadata.setWidth(result->dimensions->width());
+ upload->metadata.setHeight(result->dimensions->height());
+ }
+ if (result->length) {
+ upload->metadata.setLength(*result->length);
+ }
+
+ QVector<QXmppThumbnail> thumbnails;
+ thumbnails.reserve(result->thumbnails.size());
+ QXmppBitsOfBinaryDataList dataBlobs;
+ dataBlobs.reserve(result->thumbnails.size());
+
+ for (const auto &metadataThumb : result->thumbnails) {
+ auto bobData = QXmppBitsOfBinaryData::fromByteArray(metadataThumb.data);
+ bobData.setContentType(metadataThumb.mimeType);
+
+ QXmppThumbnail thumbnail;
+ thumbnail.setHeight(metadataThumb.height);
+ thumbnail.setWidth(metadataThumb.width);
+ thumbnail.setMediaType(metadataThumb.mimeType);
+ thumbnail.setUri(bobData.cid().toCidUrl());
+
+ thumbnails.append(std::move(thumbnail));
+ dataBlobs.append(std::move(bobData));
+ }
+ upload->metadata.setThumbnails(thumbnails);
+
+ await(hashesFuture, this, [=, dataBlobs = std::move(dataBlobs)](const auto &hashResult) mutable {
+ QVector<QXmppHash> hashes;
+ const auto hashValue = hashResult->result;
+ if (std::holds_alternative<Cancelled>(hashValue)) {
+ Q_EMIT upload->finished(Cancelled {});
+ } else if (std::holds_alternative<std::vector<QXmppHash>>(hashValue)) {
+ auto hashesVector = std::get<std::vector<QXmppHash>>(hashValue);
+ std::transform(hashesVector.begin(), hashesVector.end(),
+ std::back_inserter(hashes), [](auto &&hash) {
+ return hash;
+ });
+
+ upload->metadata.setHashes(hashes);
+
+ QXmppFileShare fs;
+ fs.setMetadata(upload->metadata);
+ fs.addSource(std::move(source));
+
+ Q_EMIT upload->finished(QXmppUpload::FileResult { fs, std::move(dataBlobs) });
+ } else if (std::holds_alternative<QXmppError>(hashValue)) {
+ Q_EMIT upload->finished(std::get<QXmppError>(hashValue));
+ }
+ });
+ });
+ } else if (std::holds_alternative<QXmppError>(uploadResult)) {
+ Q_EMIT upload->finished(std::get<QXmppError>(uploadResult));
+ }
+ });
+
+ return upload;
+}
+
+///
+/// \brief Download a file from a QXmppFileShare
+///
+/// \warning This function currently does not check the hash of the downloaded file.
+///
+/// Make sure to register the provider
+/// that handles the sources used in this file share before calling this function.
+///
+/// \param fileShare The file share object which you want to download
+/// \param output An open QIODevice that the data should be written into.
+/// In most cases, a QFile
+/// \return An object that allows to track the progress of the download.
+///
+std::shared_ptr<QXmppDownload> QXmppFileSharingManager::downloadFile(
+ const QXmppFileShare &fileShare,
+ std::unique_ptr<QIODevice> &&output)
+{
+ std::shared_ptr<QXmppDownload> download;
+ fileShare.visitSources([&](const std::any &source) {
+ std::type_index index(source.type());
+ try {
+ auto provider = m_providers.at(index);
+ download = provider->downloadFile(source, std::move(output));
+ return true;
+ } catch (const std::out_of_range &) {
+ return false;
+ }
+ });
+
+ return download;
+}
+
+void QXmppFileSharingManager::internalRegisterProvider(std::type_index index, std::shared_ptr<QXmppFileSharingProvider> provider)
+{
+ m_providers.insert_or_assign(index, provider);
+}
diff --git a/src/client/QXmppFileSharingManager.h b/src/client/QXmppFileSharingManager.h
new file mode 100644
index 00000000..a7f4c5dd
--- /dev/null
+++ b/src/client/QXmppFileSharingManager.h
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPFILESHARINGMANAGER_H
+#define QXMPPFILESHARINGMANAGER_H
+
+#include "QXmppClientExtension.h"
+#include "QXmppFileShare.h"
+#include "QXmppFileSharingProvider.h"
+#include "QXmppGlobal.h"
+
+#include <any>
+#include <functional>
+#include <memory>
+#include <typeindex>
+#include <variant>
+
+#include <QFuture>
+#include <QMimeType>
+#include <QObject>
+#include <QSize>
+
+class QIODevice;
+class QXmppFileMetadata;
+
+class QXMPP_EXPORT QXmppFileSharingManager : public QXmppClientExtension
+{
+public:
+ struct MetadataThumbnail
+ {
+ uint32_t width;
+ uint32_t height;
+ QByteArray data;
+ QMimeType mimeType;
+ };
+
+ struct MetadataGeneratorResult
+ {
+ std::optional<QSize> dimensions;
+ std::optional<uint32_t> length;
+ QVector<MetadataThumbnail> thumbnails;
+ std::unique_ptr<QIODevice> dataDevice;
+ };
+
+ using MetadataGenerator = std::function<QFuture<std::shared_ptr<MetadataGeneratorResult>>(std::unique_ptr<QIODevice>)>;
+
+ QXmppFileSharingManager();
+
+ void setMetadataGenerator(MetadataGenerator &&generator);
+
+ ///
+ /// \brief Register a provider for automatic downloads
+ /// \param manager A shared_ptr to a QXmppFileSharingProvider subclass
+ ///
+ template<typename ProviderType>
+ void registerProivder(std::shared_ptr<ProviderType> manager)
+ {
+ std::type_index index(typeid(typename ProviderType::SourceType));
+ internalRegisterProvider(index, manager);
+ }
+
+ std::shared_ptr<QXmppUpload> sendFile(std::shared_ptr<QXmppFileSharingProvider> provider,
+ const QString &filePath,
+ const std::optional<QString> &description = {});
+
+ std::shared_ptr<QXmppDownload> downloadFile(const QXmppFileShare &fileShare,
+ std::unique_ptr<QIODevice> &&output);
+
+private:
+ void internalRegisterProvider(std::type_index, std::shared_ptr<QXmppFileSharingProvider> provider);
+
+ MetadataGenerator m_metadataGenerator;
+ std::unordered_map<std::type_index, std::shared_ptr<QXmppFileSharingProvider>> m_providers;
+};
+
+#endif // QXMPPFILESHARINGMANAGER_H
diff --git a/src/client/QXmppFileSharingProvider.h b/src/client/QXmppFileSharingProvider.h
new file mode 100644
index 00000000..cde719db
--- /dev/null
+++ b/src/client/QXmppFileSharingProvider.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPFILESHARINGPROVIDER_H
+#define QXMPPFILESHARINGPROVIDER_H
+
+#include "QXmppGlobal.h"
+
+#include <any>
+#include <memory>
+
+class QIODevice;
+class QXmppFileMetadata;
+class QXmppUpload;
+class QXmppDownload;
+
+///
+/// \brief The interface of a provider for the FileSharingManager
+///
+/// To use it, implement all the pure virtual functions,
+/// and add a using declaration for the type of source you want to handle.
+/// ```
+/// using SourceType = QXmppHttpFileSource;
+/// ```
+///
+class QXMPP_EXPORT QXmppFileSharingProvider
+{
+public:
+ /// \cond
+ virtual ~QXmppFileSharingProvider() = default;
+ /// \endcond
+
+ ///
+ /// \brief Handles the download of files for this provider
+ /// \param source A type-erased source object. The provider will only ever have to handle its own sources,
+ /// so this can be safely casted to a concrete type.
+ /// \param target QIODevice into which the received data should be written
+ /// \return A subclass of QXmppDownload
+ ///
+ virtual auto downloadFile(const std::any &source, std::unique_ptr<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> = 0;
+
+ ///
+ /// \brief Handles the upload of a file for this provider
+ /// \param source A QIODevice from which data for uploading should be read.
+ /// \param info Metadata of the file
+ /// \return A subclass of QXmppUpload
+ ///
+ virtual auto uploadFile(std::unique_ptr<QIODevice> source, const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> = 0;
+};
+
+#endif // QXMPPFILESHARINGPROVIDER_H
diff --git a/src/client/QXmppFileTransfer.cpp b/src/client/QXmppFileTransfer.cpp
new file mode 100644
index 00000000..f26c3a89
--- /dev/null
+++ b/src/client/QXmppFileTransfer.cpp
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppFileTransfer.h"
+
+///
+/// \class QXmppFileTransfer
+///
+/// \brief Provides progress information about ongoing file uploads and downloads.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \fn QXmppFileTransfer::cancel()
+///
+/// \brief Cancels the file transfer. finished() will be emitted.
+///
+
+///
+/// \fn QXmppFileTransfer::isFinished()
+///
+/// \brief Returns whether the file transfer is finished.
+///
+
+///
+/// \fn QXmppFileTransfer::bytesTransferred()
+///
+/// \brief Returns the number of bytes that have been uploaded or downloaded.
+///
+
+///
+/// \fn QXmppFileTransfer::bytesTotal()
+///
+/// \brief Returns the number of bytes that totally need to be transferred.
+///
+
+///
+/// \fn QXmppFileTransfer::progressChanged()
+///
+/// \brief Emitted when new bytes have been transferred.
+///
diff --git a/src/client/QXmppFileTransfer.h b/src/client/QXmppFileTransfer.h
new file mode 100644
index 00000000..30a91aa9
--- /dev/null
+++ b/src/client/QXmppFileTransfer.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPFILETRANSFER_H
+#define QXMPPFILETRANSFER_H
+
+#include "QXmppGlobal.h"
+
+#include <QObject>
+
+class QXMPP_EXPORT QXmppFileTransfer : public QObject
+{
+ Q_OBJECT
+ /// Progress of the file transfer between 0.0 and 1.0.
+ Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
+
+public:
+ /// Returns the current progress between 0.0 and 1.0.
+ virtual float progress() = 0;
+ virtual void cancel() = 0;
+ virtual bool isFinished() = 0;
+ virtual quint64 bytesTransferred() = 0;
+ virtual quint64 bytesTotal() = 0;
+
+ // TODO consider adding speed getter
+
+ Q_SIGNAL void progressChanged();
+};
+
+#endif // QXMPPFILETRANSFER_H
diff --git a/src/client/QXmppHttpFileSharingProvider.cpp b/src/client/QXmppHttpFileSharingProvider.cpp
new file mode 100644
index 00000000..5c3e356c
--- /dev/null
+++ b/src/client/QXmppHttpFileSharingProvider.cpp
@@ -0,0 +1,207 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppHttpFileSharingProvider.h"
+
+#include "QXmppClient.h"
+#include "QXmppDownload.h"
+#include "QXmppHttpUploadManager.h"
+#include "QXmppUpload.h"
+#include "QXmppUtils.h"
+#include "QXmppUtils_p.h"
+
+#include <QMimeDatabase>
+#include <QNetworkReply>
+
+using namespace QXmpp;
+using namespace QXmpp::Private;
+
+///
+/// \class QXmppHttpFileSharingProvider
+///
+/// A file sharing provider that uses HTTP File Upload to upload the file.
+///
+/// \since QXmpp 1.5
+///
+
+class QXmppSfsHttpUpload : public QXmppUpload
+{
+ Q_OBJECT
+
+public:
+ QXmppSfsHttpUpload(std::shared_ptr<QXmppHttpUpload> &&httpUpload)
+ : inner(std::move(httpUpload))
+ {
+ connect(inner.get(), &QXmppHttpUpload::finished, this, [this](const QXmppHttpUpload::Result &result) {
+ Q_EMIT uploadFinished(std::visit([](auto &&value) -> QXmpp::Private::UploadResult {
+ using T = std::decay_t<decltype(value)>;
+ if constexpr (std::is_same_v<T, QUrl>) {
+ return std::any(QXmppHttpFileSource(value));
+ } else if constexpr (std::is_same_v<T, Cancelled>) {
+ return Cancelled {};
+ } else if constexpr (std::is_same_v<T, QXmppError>) {
+ return value;
+ }
+ },
+ result));
+ });
+ connect(inner.get(), &QXmppHttpUpload::progressChanged,
+ this, &QXmppSfsHttpUpload::progressChanged);
+ }
+
+ float progress() override { return inner->progress(); }
+ void cancel() override { inner->cancel(); }
+ bool isFinished() override { return inner->isFinished(); }
+ quint64 bytesTransferred() override { return inner->bytesSent(); }
+ quint64 bytesTotal() override { return inner->bytesTotal(); }
+
+private:
+ std::shared_ptr<QXmppHttpUpload> inner;
+};
+
+class QXmppHttpDownload : public QXmppDownload
+{
+ float progress() override
+ {
+ return calculateProgress(m_bytesSent, m_bytesTotal);
+ }
+
+ void cancel() override
+ {
+ m_aborted = true;
+ reply->abort();
+ }
+
+ bool isFinished() override
+ {
+ return m_isFinished;
+ }
+
+ quint64 bytesTransferred() override
+ {
+ return m_bytesSent;
+ }
+
+ quint64 bytesTotal() override
+ {
+ return m_bytesTotal;
+ }
+
+public:
+ void reportProgress(quint64 bytesSent, quint64 bytesTotal)
+ {
+ m_bytesSent = bytesSent;
+ m_bytesTotal = bytesTotal;
+ Q_EMIT progressChanged();
+ }
+
+ void reportFinished(Result &&result)
+ {
+ Q_EMIT finished(result);
+ m_isFinished = true;
+ }
+
+ [[nodiscard]] bool aborted() const
+ {
+ return m_aborted;
+ }
+
+private:
+ QNetworkReply *reply = nullptr;
+ quint64 m_bytesSent = 0;
+ quint64 m_bytesTotal = 0;
+ bool m_isFinished = false;
+ bool m_aborted = false;
+};
+
+class QXmppHttpFileSharingProviderPrivate
+{
+public:
+ QXmppHttpUploadManager *manager;
+ QNetworkAccessManager *netManager;
+};
+
+///
+/// \brief Create a QXmppHttpFileSharingProvider
+/// \param client
+/// \param netManager QNetworkAccessManager that can be reused all over your application.
+///
+QXmppHttpFileSharingProvider::QXmppHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager)
+ : d(std::make_unique<QXmppHttpFileSharingProviderPrivate>())
+{
+ qRegisterMetaType<QXmpp::Private::UploadResult>();
+ Q_ASSERT(client);
+ d->manager = client->findExtension<QXmppHttpUploadManager>();
+ Q_ASSERT(d->manager);
+ d->netManager = netManager;
+}
+
+QXmppHttpFileSharingProvider::~QXmppHttpFileSharingProvider() = default;
+
+auto QXmppHttpFileSharingProvider::downloadFile(const std::any &source, std::unique_ptr<QIODevice> &&target)
+ -> std::shared_ptr<QXmppDownload>
+{
+ QXmppHttpFileSource httpSource;
+ try {
+ httpSource = std::any_cast<QXmppHttpFileSource>(source);
+ } catch (const std::bad_any_cast &) {
+ qFatal("QXmppHttpFileSharingProvider::downloadFile can only handle QXmppHttpFileSharingProvider sources");
+ }
+
+ auto *reply = d->netManager->get(QNetworkRequest(httpSource.url()));
+
+ auto download = std::make_shared<QXmppHttpDownload>();
+
+ QObject::connect(reply, &QNetworkReply::finished, [=]() {
+ if (reply->error() != QNetworkReply::NoError) {
+ download->reportFinished(QXmppError::fromIoDevice(*reply));
+ } else if (download->aborted()) {
+ download->reportFinished(Cancelled());
+ } else {
+ download->reportFinished(Success());
+ }
+ reply->deleteLater();
+ });
+
+ QObject::connect(reply, &QNetworkReply::readyRead, [file = std::move(target), reply]() {
+ file->write(reply->readAll());
+ });
+
+ QObject::connect(reply, &QNetworkReply::downloadProgress, [=](qint64 bytesReceived, qint64 bytesTotal) {
+ download->reportProgress(bytesReceived, bytesTotal);
+ });
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ QObject::connect(reply, &QNetworkReply::errorOccurred,
+#else
+ QObject::connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
+#endif
+ [download, reply]() {
+ download->reportFinished(QXmppError::fromNetworkReply(*reply));
+ });
+
+ QObject::connect(reply, &QNetworkReply::uploadProgress, [download](qint64 sent, qint64 total) {
+ quint64 sentBytes = sent < 0 ? 0 : quint64(sent);
+ quint64 totalBytes = total < 0 ? 0 : quint64(total);
+ download->reportProgress(sentBytes, totalBytes);
+ });
+
+ return download;
+}
+
+auto QXmppHttpFileSharingProvider::uploadFile(std::unique_ptr<QIODevice> data,
+ const QXmppFileMetadata &info)
+ -> std::shared_ptr<QXmppUpload>
+{
+ Q_ASSERT(d->manager);
+ auto upload = d->manager->uploadFile(
+ std::move(data),
+ QXmppUtils::generateStanzaHash(10),
+ info.mediaType().value_or(QMimeDatabase().mimeTypeForName("application/octet-stream")),
+ info.size() ? info.size().value() : -1);
+
+ return std::make_shared<QXmppSfsHttpUpload>(std::move(upload));
+}
+
+#include "QXmppHttpFileSharingProvider.moc"
diff --git a/src/client/QXmppHttpFileSharingProvider.h b/src/client/QXmppHttpFileSharingProvider.h
new file mode 100644
index 00000000..748c1277
--- /dev/null
+++ b/src/client/QXmppHttpFileSharingProvider.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPHTTPFILESHARINGPROVIDER_H
+#define QXMPPHTTPFILESHARINGPROVIDER_H
+
+#include "QXmppFileSharingProvider.h"
+#include "QXmppHttpFileSource.h"
+
+#include <any>
+#include <memory>
+
+class QXmppClient;
+class QIODevice;
+class QXmppHttpUploadManager;
+class QNetworkAccessManager;
+
+class QXmppHttpFileSharingProviderPrivate;
+
+class QXMPP_EXPORT QXmppHttpFileSharingProvider : public QXmppFileSharingProvider
+{
+public:
+ /// \cond
+ using SourceType = QXmppHttpFileSource;
+ /// \endcond
+
+ QXmppHttpFileSharingProvider(QXmppClient *client, QNetworkAccessManager *netManager);
+ ~QXmppHttpFileSharingProvider() override;
+
+ auto downloadFile(const std::any &source,
+ std::unique_ptr<QIODevice> &&target) -> std::shared_ptr<QXmppDownload> override;
+ auto uploadFile(std::unique_ptr<QIODevice> data,
+ const QXmppFileMetadata &info) -> std::shared_ptr<QXmppUpload> override;
+
+private:
+ std::unique_ptr<QXmppHttpFileSharingProviderPrivate> d;
+};
+
+#endif // QXMPPHTTPFILESHARINGPROVIDER_H
diff --git a/src/client/QXmppHttpUploadManager.cpp b/src/client/QXmppHttpUploadManager.cpp
index f6261773..7feac738 100644
--- a/src/client/QXmppHttpUploadManager.cpp
+++ b/src/client/QXmppHttpUploadManager.cpp
@@ -8,6 +8,7 @@
#include "QXmppFutureUtils_p.h"
#include "QXmppHttpUploadIq.h"
#include "QXmppUploadRequestManager.h"
+#include "QXmppUtils_p.h"
#include <QFile>
#include <QFileInfo>
@@ -149,13 +150,7 @@ QXmppHttpUpload::~QXmppHttpUpload() = default;
///
float QXmppHttpUpload::progress() const
{
- if (d->bytesTotal > 0) {
- if (d->bytesSent > d->bytesTotal) {
- return 1;
- }
- return float(d->bytesSent) / d->bytesTotal;
- }
- return 0;
+ return calculateProgress(d->bytesSent, d->bytesTotal);
}
///
diff --git a/src/client/QXmppUpload.cpp b/src/client/QXmppUpload.cpp
new file mode 100644
index 00000000..e7b876e5
--- /dev/null
+++ b/src/client/QXmppUpload.cpp
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppUpload.h"
+
+///
+/// \class QXmppUpload
+///
+/// \brief Provides progress of stateless file sharing uploads.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \class QXmppUpload::FileResult
+///
+/// \brief Contains QXmppFileShare of the uploaded file and possible data blobs containing
+/// referenced thumbnails.
+///
+
+///
+/// \var QXmppUpload::FileResult::fileShare
+///
+/// \brief File share with file metadata and file shares of the uploaded file.
+///
+
+///
+/// \var QXmppUpload::FileResult::dataBlobs
+///
+/// \brief Data blobs of possibly in the metadata referenced thumbnails.
+///
+/// The QXmppFileSharingManager may generate file thumbnails.
+///
+
+///
+/// \typedef QXmppUpload::Result
+///
+/// \brief Contains FileResult (successfully finished), QXmpp::Cancelled (manually cancelled)
+/// or QXmppError (an error occured while downloading).
+///
+
+///
+/// \fn QXmppUpload::finished
+///
+/// Emitted when the upload has finished.
+///
diff --git a/src/client/QXmppUpload.h b/src/client/QXmppUpload.h
new file mode 100644
index 00000000..c948496f
--- /dev/null
+++ b/src/client/QXmppUpload.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert <jbb@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPUPLOAD_H
+#define QXMPPUPLOAD_H
+
+#include "QXmppBitsOfBinaryDataList.h"
+#include "QXmppError.h"
+#include "QXmppFileMetadata.h"
+#include "QXmppFileShare.h"
+#include "QXmppFileTransfer.h"
+
+#include <variant>
+
+namespace QXmpp::Private {
+using UploadResult = std::variant<std::any /* source */, QXmpp::Cancelled, QXmppError>;
+}
+
+class QXMPP_EXPORT QXmppUpload : public QXmppFileTransfer
+{
+ Q_OBJECT
+public:
+ struct FileResult
+ {
+ QXmppFileShare fileShare;
+ QXmppBitsOfBinaryDataList dataBlobs;
+ };
+
+ using Result = std::variant<FileResult, QXmpp::Cancelled, QXmppError>;
+ Q_SIGNAL void finished(QXmppUpload::Result);
+
+protected:
+ /// \cond
+ Q_SIGNAL void uploadFinished(QXmpp::Private::UploadResult);
+ /// \endcond
+
+private:
+ QXmppFileMetadata metadata;
+
+ friend class QXmppFileSharingManager;
+};
+
+Q_DECLARE_METATYPE(QXmppUpload::Result);
+Q_DECLARE_METATYPE(QXmpp::Private::UploadResult);
+
+#endif // QXMPPUPLOAD_H