aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2019-05-05 14:58:42 +0200
committerLNJ <lnj@kaidan.im>2019-10-20 17:01:35 +0200
commit6b3e41ee933b260a80b3ec197c7e76a14eb4ba4a (patch)
tree35b4af6bf99d5c22e55984ac54508fef2938b970 /src/client
parenta4ac49e2e3c69ba2d42558fbfb52eaa931ca8e28 (diff)
downloadqxmpp-6b3e41ee933b260a80b3ec197c7e76a14eb4ba4a.tar.gz
Implement XEP-0363: HTTP File Upload: UploadRequestManager
This adds a manager to simplify service discovery and IQ sending for XEP-0363: HTTP File Upload.
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppUploadRequestManager.cpp250
-rw-r--r--src/client/QXmppUploadRequestManager.h141
2 files changed, 391 insertions, 0 deletions
diff --git a/src/client/QXmppUploadRequestManager.cpp b/src/client/QXmppUploadRequestManager.cpp
new file mode 100644
index 00000000..09268637
--- /dev/null
+++ b/src/client/QXmppUploadRequestManager.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2008-2019 The QXmpp developers
+ *
+ * Author:
+ * Linus Jahn <lnj@kaidan.im>
+ *
+ * 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 <QFileInfo>
+#include <QMimeDatabase>
+
+#include "QXmppClient.h"
+#include "QXmppConstants_p.h"
+#include "QXmppDiscoveryManager.h"
+#include "QXmppDiscoveryIq.h"
+#include "QXmppDataForm.h"
+#include "QXmppHttpUploadIq.h"
+
+#include "QXmppUploadRequestManager.h"
+
+class QXmppUploadServicePrivate : public QSharedData
+{
+public:
+ QString jid;
+ qint64 sizeLimit = -1;
+};
+
+QXmppUploadService::QXmppUploadService()
+ : d(new QXmppUploadServicePrivate)
+{
+}
+
+QXmppUploadService::QXmppUploadService(const QXmppUploadService &) = default;
+
+QXmppUploadService::~QXmppUploadService() = default;
+
+QXmppUploadService &QXmppUploadService::operator=(const QXmppUploadService &) = default;
+
+/// Returns the JID of the HTTP File Upload service.
+
+QString QXmppUploadService::jid() const
+{
+ return d->jid;
+}
+
+/// Sets the JID of the HTTP File Upload service.
+
+void QXmppUploadService::setJid(const QString &jid)
+{
+ d->jid = jid;
+}
+
+/// Returns the size limit of files that can be uploaded to this upload
+/// service.
+///
+/// A size limit of -1 means that there is no file size limit or it could not
+/// be determined.
+
+qint64 QXmppUploadService::sizeLimit() const
+{
+ return d->sizeLimit;
+}
+
+/// Sets the size limit of files that can be uploaded to this upload service.
+
+void QXmppUploadService::setSizeLimit(qint64 sizeLimit)
+{
+ d->sizeLimit = sizeLimit;
+}
+
+class QXmppUploadRequestManagerPrivate : public QSharedData
+{
+public:
+ QVector<QXmppUploadService> uploadServices;
+};
+
+QXmppUploadRequestManager::QXmppUploadRequestManager()
+ : d(new QXmppUploadRequestManagerPrivate)
+{
+}
+
+QXmppUploadRequestManager::~QXmppUploadRequestManager() = default;
+
+/// Requests an upload slot from the server.
+///
+/// \param file The info of the file to be uploaded.
+/// \param uploadService The HTTP File Upload service that is used to request
+/// the upload slot. If this is empty, the first
+/// discovered one is used.
+/// \return The id of the sent IQ. If sendPacket() isn't successful or no
+/// upload service has been discovered yet, an empty string will be
+/// returned.
+
+QString QXmppUploadRequestManager::requestUploadSlot(const QFileInfo &file,
+ const QString &uploadService)
+{
+ return requestUploadSlot(file, file.fileName(), uploadService);
+}
+
+/// Requests an upload slot from the server.
+///
+/// \param file The info of the file to be uploaded.
+/// \param customFileName This name is used instead of the file's actual name
+/// for requesting the upload slot.
+/// \param uploadService The HTTP File Upload service that is used to request
+/// the upload slot. If this is empty, the first
+/// discovered one is used.
+/// \return The id of the sent IQ. If sendPacket() isn't successful or no
+/// upload service has been discovered yet, an empty string will be
+/// returned.
+
+QString QXmppUploadRequestManager::requestUploadSlot(const QFileInfo &file,
+ const QString &customFileName,
+ const QString &uploadService)
+{
+ return requestUploadSlot(customFileName, file.size(),
+ QMimeDatabase().mimeTypeForFile(file),
+ uploadService);
+}
+
+/// Requests an upload slot from the server.
+///
+/// \param fileName The name of the file to be uploaded. This may be used by
+/// the server to generate the URL.
+/// \param fileSize The size of the file to be uploaded. The server may reject
+/// too large files.
+/// \param mimeType The content-type of the file. This can be used by the
+/// server to set the HTTP MIME-type of the URL.
+/// \param uploadService The HTTP File Upload service that is used to request
+/// the upload slot. If this is empty, the first
+/// discovered one is used.
+/// \return The id of the sent IQ. If sendPacket() isn't successful or no
+/// upload service has been discovered yet, an empty string will be
+/// returned.
+
+QString QXmppUploadRequestManager::requestUploadSlot(const QString &fileName,
+ qint64 fileSize,
+ const QMimeType &mimeType,
+ const QString &uploadService)
+{
+ if (!serviceFound() && uploadService.isEmpty())
+ return {};
+
+ QXmppHttpUploadRequestIq iq;
+ if (uploadService.isEmpty())
+ iq.setTo(d->uploadServices.first().jid());
+ else
+ iq.setTo(uploadService);
+ iq.setType(QXmppIq::Get);
+ iq.setFileName(fileName);
+ iq.setSize(fileSize);
+ iq.setContentType(mimeType);
+
+ if (client()->sendPacket(iq))
+ return iq.id();
+ return {};
+}
+
+/// Returns true, if an HTTP File Upload service has been discovered.
+
+bool QXmppUploadRequestManager::serviceFound() const
+{
+ return !d->uploadServices.isEmpty();
+}
+
+/// Returns all discovered HTTP File Upload services.
+
+QVector<QXmppUploadService> QXmppUploadRequestManager::uploadServices() const
+{
+ return d->uploadServices;
+}
+
+bool QXmppUploadRequestManager::handleStanza(const QDomElement &element)
+{
+ if (QXmppHttpUploadSlotIq::isHttpUploadSlotIq(element)) {
+ QXmppHttpUploadSlotIq slot;
+ slot.parse(element);
+
+ emit slotReceived(slot);
+ return true;
+ } else if (QXmppHttpUploadRequestIq::isHttpUploadRequestIq(element)) {
+ QXmppHttpUploadRequestIq requestError;
+ requestError.parse(element);
+
+ emit requestFailed(requestError);
+ return true;
+ }
+ return false;
+}
+
+void QXmppUploadRequestManager::handleDiscoInfo(const QXmppDiscoveryIq &iq)
+{
+ if (!iq.features().contains(ns_http_upload))
+ return;
+
+ for (const QXmppDiscoveryIq::Identity &identity : iq.identities()) {
+ if (identity.category() == QStringLiteral("store") &&
+ identity.type() == QStringLiteral("file")) {
+ QXmppUploadService service;
+ service.setJid(iq.from());
+
+ // get size limit
+ bool isFormNsCorrect = false;
+ for (const QXmppDataForm::Field &field : iq.form().fields()) {
+ if (field.key() == QStringLiteral("FORM_TYPE")) {
+ isFormNsCorrect = field.value() == ns_http_upload;
+ } else if (isFormNsCorrect && field.key() == QStringLiteral("max-file-size")) {
+ service.setSizeLimit(field.value().toLongLong());
+ }
+ }
+
+ d->uploadServices.append(service);
+ emit serviceFoundChanged();
+ }
+ }
+ return;
+}
+
+void QXmppUploadRequestManager::setClient(QXmppClient *client)
+{
+ QXmppClientExtension::setClient(client);
+ // connect to service discovery manager
+ auto *disco = client->findExtension<QXmppDiscoveryManager>();
+ if (disco) {
+ // scan info of all entities for upload services
+ connect(disco, &QXmppDiscoveryManager::infoReceived,
+ this, &QXmppUploadRequestManager::handleDiscoInfo);
+
+ // on client disconnect remove all upload services
+ connect(client, &QXmppClient::disconnected, [=] () {
+ d->uploadServices.clear();
+ emit serviceFoundChanged();
+ });
+ }
+}
diff --git a/src/client/QXmppUploadRequestManager.h b/src/client/QXmppUploadRequestManager.h
new file mode 100644
index 00000000..bebf9954
--- /dev/null
+++ b/src/client/QXmppUploadRequestManager.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008-2019 The QXmpp developers
+ *
+ * Author:
+ * Linus Jahn <lnj@kaidan.im>
+ *
+ * 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.
+ *
+ */
+
+#ifndef QXMPPUPLOADREQUESTMANAGER_H
+#define QXMPPUPLOADREQUESTMANAGER_H
+
+#include <QXmppClientExtension.h>
+#include <QSharedDataPointer>
+
+class QFileInfo;
+class QMimeType;
+class QXmppHttpUploadRequestIq;
+class QXmppHttpUploadSlotIq;
+class QXmppUploadServicePrivate;
+class QXmppUploadRequestManagerPrivate;
+
+/// \class QXmppUploadService The QXmppUploadService represents an HTTP File
+/// Upload service.
+///
+/// It is only used to store the JID and maximum file size for uploads.
+
+class QXMPP_EXPORT QXmppUploadService
+{
+public:
+ QXmppUploadService();
+ QXmppUploadService(const QXmppUploadService &);
+ ~QXmppUploadService();
+
+ QXmppUploadService &operator=(const QXmppUploadService &);
+
+ QString jid() const;
+ void setJid(const QString &jid);
+
+ qint64 sizeLimit() const;
+ void setSizeLimit(qint64 sizeLimit);
+
+private:
+ QSharedDataPointer<QXmppUploadServicePrivate> d;
+};
+
+/// \class QXmppUploadRequestManager This class implements the core of
+/// XEP-0369: HTTP File Upload.
+///
+/// It handles the discovery of QXmppUploadServices and can send upload
+/// requests and outputs the upload slots. It doesn't do the actual upload via.
+/// HTTP.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// auto *manager = new QXmppUploadRequestManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// Apart from that, you also need to discover HTTP File Upload service(s) by
+/// requesting the Service Discovery info for each Service Discovery item of
+/// the server. The QXmppUploadManager will then automatically recognize upload
+/// services and add them to the list of discovered services
+/// \c uploadServices().
+///
+/// Keep in mind that theoretically any XMPP entity could promote to be an
+/// upload service and so is recognized by this manager. A potential attacker
+/// could exploit this vulnerability, so the client could be uploading files to
+/// the attacker (e.g. a normal user JID) instead of the own server.
+///
+/// As soon as at least one upload service has been discovered, you can start
+/// to request upload slots by using \c requestUploadSlot(). Alternatively you
+/// can provide the JID of the upload service which should be used for
+/// uploading.
+///
+/// \ingroup Managers
+
+class QXMPP_EXPORT QXmppUploadRequestManager : public QXmppClientExtension
+{
+ Q_OBJECT
+ Q_PROPERTY(bool serviceFound READ serviceFound NOTIFY serviceFoundChanged)
+
+public:
+ QXmppUploadRequestManager();
+ ~QXmppUploadRequestManager();
+
+ QString requestUploadSlot(const QFileInfo &file,
+ const QString &uploadService = QString());
+ QString requestUploadSlot(const QFileInfo &file,
+ const QString &customFileName,
+ const QString &uploadService = QString());
+ QString requestUploadSlot(const QString &fileName,
+ qint64 fileSize,
+ const QMimeType &mimeType,
+ const QString &uploadService = QString());
+
+ bool serviceFound() const;
+
+ QVector<QXmppUploadService> uploadServices() const;
+
+ bool handleStanza(const QDomElement &stanza) override;
+
+signals:
+ /// Emitted when an upload slot was received.
+ void slotReceived(const QXmppHttpUploadSlotIq &slot);
+
+ /// Emitted when the slot request failed.
+ ///
+ /// You can get the reason from the QXmppStanza::Error of the returned
+ /// request.
+ void requestFailed(const QXmppHttpUploadRequestIq &request);
+
+ void serviceFoundChanged();
+
+protected:
+ void setClient(QXmppClient *client) override;
+ bool handleStanza(const QDomElement &stanza) override;
+
+private:
+ void handleDiscoInfo(const QXmppDiscoveryIq &iq);
+
+ QSharedDataPointer<QXmppUploadRequestManagerPrivate> d;
+};
+
+#endif // QXMPPUPLOADREQUESTMANAGER_H