1// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#include "networkaccessmanager.h"
5
6#include "logging_categories_p.h"
7#include "mxcreply.h"
8
9#include "events/filesourceinfo.h"
10#include "jobs/downloadfilejob.h" // For DownloadFileJob::makeRequestUrl() only
11
12#include <QtCore/QCoreApplication>
13#include <QtCore/QReadWriteLock>
14#include <QtCore/QSettings>
15#include <QtCore/QStringBuilder>
16#include <QtCore/QThread>
17#include <QtNetwork/QNetworkReply>
18
19using namespace Quotient;
20
21namespace {
22class {
23public:
24 void addBaseUrl(const QString& accountId, const QUrl& baseUrl)
25 {
26 QWriteLocker{ &namLock }, baseUrls.insert(key: accountId, value: baseUrl);
27 }
28 void dropBaseUrl(const QString& accountId)
29 {
30 QWriteLocker{ &namLock }, baseUrls.remove(key: accountId);
31 }
32 QUrl getBaseUrl(const QString& accountId) const
33 {
34 return QReadLocker{ &namLock }, baseUrls.value(key: accountId);
35 }
36 void addIgnoredSslError(const QSslError& error)
37 {
38 QWriteLocker{ &namLock }, ignoredSslErrors.push_back(t: error);
39 }
40 void clearIgnoredSslErrors()
41 {
42 QWriteLocker{ &namLock }, ignoredSslErrors.clear();
43 }
44 QList<QSslError> getIgnoredSslErrors() const
45 {
46 return QReadLocker{ &namLock }, ignoredSslErrors;
47 }
48
49private:
50 mutable QReadWriteLock namLock{};
51 QHash<QString, QUrl> baseUrls{};
52 QList<QSslError> ignoredSslErrors{};
53} d;
54
55} // anonymous namespace
56
57void NetworkAccessManager::addBaseUrl(const QString& accountId,
58 const QUrl& homeserver)
59{
60 Q_ASSERT(!accountId.isEmpty() && homeserver.isValid());
61 d.addBaseUrl(accountId, baseUrl: homeserver);
62}
63
64void NetworkAccessManager::dropBaseUrl(const QString& accountId)
65{
66 d.dropBaseUrl(accountId);
67}
68
69QList<QSslError> NetworkAccessManager::ignoredSslErrors()
70{
71 return d.getIgnoredSslErrors();
72}
73
74void NetworkAccessManager::addIgnoredSslError(const QSslError& error)
75{
76 d.addIgnoredSslError(error);
77}
78
79void NetworkAccessManager::clearIgnoredSslErrors()
80{
81 d.clearIgnoredSslErrors();
82}
83
84NetworkAccessManager* NetworkAccessManager::instance()
85{
86 thread_local auto* nam = [] {
87 auto* namInit = new NetworkAccessManager();
88 connect(sender: QThread::currentThread(), signal: &QThread::finished, context: namInit,
89 slot: &QObject::deleteLater);
90 return namInit;
91 }();
92 return nam;
93}
94
95QNetworkReply* NetworkAccessManager::createRequest(
96 Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
97{
98 const auto url = request.url();
99 if (url.scheme() != "mxc"_ls) {
100 auto reply =
101 QNetworkAccessManager::createRequest(op, request, outgoingData);
102 reply->ignoreSslErrors(errors: d.getIgnoredSslErrors());
103 return reply;
104 }
105 const QUrlQuery query{ url.query() };
106 const auto accountId = query.queryItemValue(QStringLiteral("user_id"));
107 if (accountId.isEmpty()) {
108 // Using QSettings here because Quotient::NetworkSettings
109 // doesn't provide multi-threading guarantees
110 if (static thread_local const QSettings s;
111 s.value(key: "Network/allow_direct_media_requests"_ls).toBool()) //
112 {
113 // TODO: Make the best effort with a direct unauthenticated request
114 // to the media server
115 qCWarning(NETWORK)
116 << "Direct unauthenticated mxc requests are not implemented";
117 return new MxcReply();
118 }
119 qCWarning(NETWORK)
120 << "No connection specified, cannot convert mxc request";
121 return new MxcReply();
122 }
123 const auto& baseUrl = d.getBaseUrl(accountId);
124 if (!baseUrl.isValid()) {
125 // Strictly speaking, it should be an assert...
126 qCCritical(NETWORK) << "Homeserver for" << accountId
127 << "not found, cannot convert mxc request";
128 return new MxcReply();
129 }
130
131 // Convert mxc:// URL into normal http(s) for the given homeserver
132 QNetworkRequest rewrittenRequest(request);
133 rewrittenRequest.setUrl(DownloadFileJob::makeRequestUrl(baseUrl, mxcUri: url));
134
135 auto* implReply = QNetworkAccessManager::createRequest(op, request: rewrittenRequest);
136 implReply->ignoreSslErrors(errors: d.getIgnoredSslErrors());
137 const auto& fileMetadata = FileMetadataMap::lookup(
138 roomId: query.queryItemValue(QStringLiteral("room_id")),
139 eventId: query.queryItemValue(QStringLiteral("event_id")));
140 return new MxcReply(implReply, fileMetadata);
141}
142
143QStringList NetworkAccessManager::supportedSchemesImplementation() const
144{
145 return QNetworkAccessManager::supportedSchemesImplementation()
146 << QStringLiteral("mxc");
147}
148