aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp12
-rw-r--r--lib/connection.h2
-rw-r--r--lib/jobs/basejob.cpp24
-rw-r--r--lib/jobs/basejob.h4
-rw-r--r--lib/logging.cpp1
-rw-r--r--lib/logging.h1
-rw-r--r--lib/mxcreply.cpp70
-rw-r--r--lib/mxcreply.h29
-rw-r--r--lib/networkaccessmanager.cpp78
-rw-r--r--lib/networkaccessmanager.h5
-rw-r--r--lib/room.cpp11
-rw-r--r--lib/room.h3
12 files changed, 221 insertions, 19 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 222c3b71..4abf5097 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -13,6 +13,7 @@
#include "room.h"
#include "settings.h"
#include "user.h"
+#include "accountregistry.h"
// NB: since Qt 6, moc_connection.cpp needs Room and User fully defined
#include "moc_connection.cpp"
@@ -258,6 +259,7 @@ Connection::~Connection()
{
qCDebug(MAIN) << "deconstructing connection object for" << userId();
stopSync();
+ AccountRegistry::instance().drop(this);
}
void Connection::resolveServer(const QString& mxid)
@@ -441,6 +443,7 @@ void Connection::Private::completeSetup(const QString& mxId)
qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString()
<< "by user" << data->userId()
<< "from device" << data->deviceId();
+ AccountRegistry::instance().add(q);
#ifndef Quotient_E2EE_ENABLED
qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off.";
#else // Quotient_E2EE_ENABLED
@@ -836,6 +839,15 @@ inline auto splitMediaId(const QString& mediaId)
return idParts;
}
+QUrl Connection::makeMediaUrl(QUrl mxcUrl) const
+{
+ Q_ASSERT(mxcUrl.scheme() == "mxc");
+ QUrlQuery q(mxcUrl.query());
+ q.addQueryItem(QStringLiteral("user_id"), userId());
+ mxcUrl.setQuery(q);
+ return mxcUrl;
+}
+
MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId,
QSize requestedSize,
RunningPolicy policy)
diff --git a/lib/connection.h b/lib/connection.h
index ecbb1a19..1a6ca9b0 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -529,6 +529,8 @@ public Q_SLOTS:
void stopSync();
QString nextBatchToken() const;
+ Q_INVOKABLE QUrl makeMediaUrl(QUrl mxcUrl) const;
+
virtual MediaThumbnailJob*
getThumbnail(const QString& mediaId, QSize requestedSize,
RunningPolicy policy = BackgroundRequest);
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index 6346db9d..85066024 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -194,12 +194,12 @@ QUrl BaseJob::requestUrl() const { return d->reply ? d->reply->url() : QUrl(); }
bool BaseJob::isBackground() const { return d->inBackground; }
-const QString& BaseJob::apiEndpoint() const { return d->apiEndpoint; }
+//const QString& BaseJob::apiEndpoint() const { return d->apiUrl.path(); }
-void BaseJob::setApiEndpoint(const QString& apiEndpoint)
-{
- d->apiEndpoint = apiEndpoint;
-}
+//void BaseJob::setApiEndpoint(const QString& apiEndpoint)
+//{
+// d->apiEndpoint = apiEndpoint;
+//}
const BaseJob::headers_t& BaseJob::requestHeaders() const
{
@@ -217,7 +217,7 @@ void BaseJob::setRequestHeaders(const BaseJob::headers_t& headers)
d->requestHeaders = headers;
}
-const QUrlQuery& BaseJob::query() const { return d->requestQuery; }
+QUrlQuery BaseJob::query() const { return d->requestQuery; }
void BaseJob::setRequestQuery(const QUrlQuery& query)
{
@@ -262,14 +262,10 @@ QNetworkReply* BaseJob::reply() { return d->reply.data(); }
QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path,
const QUrlQuery& query)
{
- auto pathBase = baseUrl.path();
- // QUrl::adjusted(QUrl::StripTrailingSlashes) doesn't help with root '/'
- while (pathBase.endsWith('/'))
- pathBase.chop(1);
- if (!path.startsWith('/')) // Normally API files do start with '/'
- pathBase.push_back('/'); // so this shouldn't be needed these days
-
- baseUrl.setPath(pathBase + path, QUrl::TolerantMode);
+ // Make sure the added path is relative even if it's not (the official
+ // API definitions have the leading slash though it's not really correct).
+ baseUrl = baseUrl.resolved(
+ QUrl(path.mid(path.startsWith('/')), QUrl::TolerantMode));
baseUrl.setQuery(query);
return baseUrl;
}
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h
index 29443c53..663c121c 100644
--- a/lib/jobs/basejob.h
+++ b/lib/jobs/basejob.h
@@ -325,13 +325,15 @@ Q_SIGNALS:
protected:
using headers_t = QHash<QByteArray, QByteArray>;
+ Q_DECL_DEPRECATED_X("Deprecated due to being unused")
const QString& apiEndpoint() const;
+ Q_DECL_DEPRECATED_X("Deprecated due to being unused")
void setApiEndpoint(const QString& apiEndpoint);
const headers_t& requestHeaders() const;
void setRequestHeader(const headers_t::key_type& headerName,
const headers_t::mapped_type& headerValue);
void setRequestHeaders(const headers_t& headers);
- const QUrlQuery& query() const;
+ QUrlQuery query() const;
void setRequestQuery(const QUrlQuery& query);
const RequestData& requestData() const;
void setRequestData(RequestData&& data);
diff --git a/lib/logging.cpp b/lib/logging.cpp
index ffcc851c..15eac69d 100644
--- a/lib/logging.cpp
+++ b/lib/logging.cpp
@@ -17,4 +17,5 @@ LOGGING_CATEGORY(E2EE, "quotient.e2ee")
LOGGING_CATEGORY(JOBS, "quotient.jobs")
LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync")
LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail")
+LOGGING_CATEGORY(NETWORK, "quotient.network")
LOGGING_CATEGORY(PROFILER, "quotient.profiler")
diff --git a/lib/logging.h b/lib/logging.h
index 1d1394e8..7e0da975 100644
--- a/lib/logging.h
+++ b/lib/logging.h
@@ -17,6 +17,7 @@ Q_DECLARE_LOGGING_CATEGORY(E2EE)
Q_DECLARE_LOGGING_CATEGORY(JOBS)
Q_DECLARE_LOGGING_CATEGORY(SYNCJOB)
Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB)
+Q_DECLARE_LOGGING_CATEGORY(NETWORK)
Q_DECLARE_LOGGING_CATEGORY(PROFILER)
namespace Quotient {
diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp
new file mode 100644
index 00000000..0b6643fc
--- /dev/null
+++ b/lib/mxcreply.cpp
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "mxcreply.h"
+
+#include "room.h"
+
+using namespace Quotient;
+
+class MxcReply::Private
+{
+public:
+ explicit Private(QNetworkReply* r = nullptr)
+ : m_reply(r)
+ {}
+ QNetworkReply* m_reply;
+};
+
+MxcReply::MxcReply(QNetworkReply* reply)
+ : d(std::make_unique<Private>(reply))
+{
+ reply->setParent(this);
+ connect(d->m_reply, &QNetworkReply::finished, this, [this]() {
+ setError(d->m_reply->error(), d->m_reply->errorString());
+ setOpenMode(ReadOnly);
+ Q_EMIT finished();
+ });
+}
+
+MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId)
+ : d(std::make_unique<Private>(reply))
+{
+ reply->setParent(this);
+ connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() {
+ setError(d->m_reply->error(), d->m_reply->errorString());
+ setOpenMode(ReadOnly);
+ emit finished();
+ });
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+#define ERROR_SIGNAL errorOccurred
+#else
+#define ERROR_SIGNAL error
+#endif
+
+MxcReply::MxcReply()
+{
+ static const auto BadRequestPhrase = tr("Bad Request");
+ QMetaObject::invokeMethod(this, [this]() {
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 400);
+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute,
+ BadRequestPhrase);
+ setError(QNetworkReply::ProtocolInvalidOperationError,
+ BadRequestPhrase);
+ setFinished(true);
+ emit ERROR_SIGNAL(QNetworkReply::ProtocolInvalidOperationError);
+ emit finished();
+ }, Qt::QueuedConnection);
+}
+
+qint64 MxcReply::readData(char *data, qint64 maxSize)
+{
+ return d->m_reply->read(data, maxSize);
+}
+
+void MxcReply::abort()
+{
+ d->m_reply->abort();
+}
diff --git a/lib/mxcreply.h b/lib/mxcreply.h
new file mode 100644
index 00000000..efaf01c6
--- /dev/null
+++ b/lib/mxcreply.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QtNetwork/QNetworkReply>
+#include <memory>
+
+namespace Quotient {
+class Room;
+
+class MxcReply : public QNetworkReply
+{
+public:
+ explicit MxcReply();
+ explicit MxcReply(QNetworkReply *reply);
+ MxcReply(QNetworkReply* reply, Room* room, const QString& eventId);
+
+public Q_SLOTS:
+ void abort() override;
+
+protected:
+ qint64 readData(char *data, qint64 maxSize) override;
+
+private:
+ class Private;
+ std::unique_ptr<Private> d;
+};
+}
diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp
index a94ead34..57618329 100644
--- a/lib/networkaccessmanager.cpp
+++ b/lib/networkaccessmanager.cpp
@@ -3,18 +3,43 @@
#include "networkaccessmanager.h"
+#include "connection.h"
+#include "room.h"
+#include "accountregistry.h"
+#include "mxcreply.h"
+
#include <QtCore/QCoreApplication>
+#include <QtCore/QThreadStorage>
+#include <QtCore/QSettings>
#include <QtNetwork/QNetworkReply>
using namespace Quotient;
class NetworkAccessManager::Private {
public:
+ explicit Private(NetworkAccessManager* q)
+ : q(q)
+ {}
+
+ QNetworkReply* createImplRequest(Operation op,
+ const QNetworkRequest& outerRequest,
+ Connection* connection)
+ {
+ Q_ASSERT(outerRequest.url().scheme() == "mxc");
+ QNetworkRequest r(outerRequest);
+ r.setUrl(QUrl(QStringLiteral("%1/_matrix/media/r0/download/%2")
+ .arg(connection->homeserver().toString(),
+ outerRequest.url().authority()
+ + outerRequest.url().path())));
+ return q->createRequest(op, r);
+ }
+
+ NetworkAccessManager* q;
QList<QSslError> ignoredSslErrors;
};
NetworkAccessManager::NetworkAccessManager(QObject* parent)
- : QNetworkAccessManager(parent), d(std::make_unique<Private>())
+ : QNetworkAccessManager(parent), d(std::make_unique<Private>(this))
{}
QList<QSslError> NetworkAccessManager::ignoredSslErrors() const
@@ -34,7 +59,7 @@ void NetworkAccessManager::clearIgnoredSslErrors()
static NetworkAccessManager* createNam()
{
- auto nam = new NetworkAccessManager(QCoreApplication::instance());
+ auto nam = new NetworkAccessManager();
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
// See #109; in newer Qt, bearer management is deprecated altogether
NetworkAccessManager::connect(nam,
@@ -47,8 +72,11 @@ static NetworkAccessManager* createNam()
NetworkAccessManager* NetworkAccessManager::instance()
{
- static auto* nam = createNam();
- return nam;
+ static QThreadStorage<NetworkAccessManager*> storage;
+ if(!storage.hasLocalData()) {
+ storage.setLocalData(createNam());
+ }
+ return storage.localData();
}
NetworkAccessManager::~NetworkAccessManager() = default;
@@ -56,7 +84,49 @@ NetworkAccessManager::~NetworkAccessManager() = default;
QNetworkReply* NetworkAccessManager::createRequest(
Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
{
+ const auto& mxcUrl = request.url();
+ if (mxcUrl.scheme() == "mxc") {
+ const QUrlQuery query(mxcUrl.query());
+ const auto accountId = query.queryItemValue(QStringLiteral("user_id"));
+ if (accountId.isEmpty()) {
+ // Using QSettings here because Quotient::NetworkSettings
+ // doesn't provide multithreading guarantees
+ static thread_local QSettings s;
+ if (!s.value("Network/allow_direct_media_requests").toBool()) {
+ qCWarning(NETWORK) << "No connection specified";
+ return new MxcReply();
+ }
+ // TODO: Make the best effort with a direct unauthenticated request
+ // to the media server
+ } else {
+ auto* const connection = AccountRegistry::instance().get(accountId);
+ if (!connection) {
+ qCWarning(NETWORK) << "Connection" << accountId << "not found";
+ return new MxcReply();
+ }
+ const auto roomId = query.queryItemValue(QStringLiteral("room_id"));
+ if (!roomId.isEmpty()) {
+ auto room = connection->room(roomId);
+ if (!room) {
+ qCWarning(NETWORK) << "Room" << roomId << "not found";
+ return new MxcReply();
+ }
+ return new MxcReply(
+ d->createImplRequest(op, request, connection), room,
+ query.queryItemValue(QStringLiteral("event_id")));
+ }
+ return new MxcReply(
+ d->createImplRequest(op, request, connection));
+ }
+ }
auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData);
reply->ignoreSslErrors(d->ignoredSslErrors);
return reply;
}
+
+QStringList NetworkAccessManager::supportedSchemesImplementation() const
+{
+ auto schemes = QNetworkAccessManager::supportedSchemesImplementation();
+ schemes += QStringLiteral("mxc");
+ return schemes;
+}
diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h
index 47729a1b..87bc12a1 100644
--- a/lib/networkaccessmanager.h
+++ b/lib/networkaccessmanager.h
@@ -8,6 +8,8 @@
#include <memory>
namespace Quotient {
+class Room;
+class Connection;
class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
@@ -21,6 +23,9 @@ public:
/** Get a pointer to the singleton */
static NetworkAccessManager* instance();
+public Q_SLOTS:
+ QStringList supportedSchemesImplementation() const;
+
private:
QNetworkReply* createRequest(Operation op, const QNetworkRequest& request,
QIODevice* outgoingData = Q_NULLPTR) override;
diff --git a/lib/room.cpp b/lib/room.cpp
index ced313a4..fb65fd84 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -1116,6 +1116,17 @@ QList<User*> Room::directChatUsers() const
return connection()->directChatUsers(this);
}
+QUrl Room::makeMediaUrl(const QString& eventId, const QUrl& mxcUrl) const
+{
+ auto url = connection()->makeMediaUrl(mxcUrl);
+ QUrlQuery q(url.query());
+ Q_ASSERT(q.hasQueryItem("user_id"));
+ q.addQueryItem("room_id", id());
+ q.addQueryItem("event_id", eventId);
+ url.setQuery(q);
+ return url;
+}
+
QString safeFileName(QString rawName)
{
return rawName.replace(QRegularExpression("[/\\<>|\"*?:]"), "_");
diff --git a/lib/room.h b/lib/room.h
index c18d0b36..c4d94c02 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -458,6 +458,9 @@ public:
/// Get the list of users this room is a direct chat with
QList<User*> directChatUsers() const;
+ Q_INVOKABLE QUrl makeMediaUrl(const QString& eventId,
+ const QUrl &mxcUrl) const;
+
Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const;
Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const;