diff options
| author | Linus Jahn <lnj@kaidan.im> | 2019-05-05 14:58:42 +0200 |
|---|---|---|
| committer | LNJ <lnj@kaidan.im> | 2019-10-20 17:01:35 +0200 |
| commit | 6b3e41ee933b260a80b3ec197c7e76a14eb4ba4a (patch) | |
| tree | 35b4af6bf99d5c22e55984ac54508fef2938b970 /src/client | |
| parent | a4ac49e2e3c69ba2d42558fbfb52eaa931ca8e28 (diff) | |
| download | qxmpp-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.cpp | 250 | ||||
| -rw-r--r-- | src/client/QXmppUploadRequestManager.h | 141 |
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 |
