1/*
2 This file is part of KDE.
3
4 SPDX-FileCopyrightText: 2009 Eckhart Wörner <ewoerner@kde.org>
5 SPDX-FileCopyrightText: 2009 Frederik Gladhorn <gladhorn@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
8*/
9
10#include "providermanager.h"
11
12#include "attica_debug.h"
13#include "atticautils.h"
14
15#include <QAuthenticator>
16#include <QCoreApplication>
17#include <QDebug>
18#include <QFile>
19#include <QNetworkProxy>
20#include <QPluginLoader>
21#include <QSet>
22#include <QSignalMapper>
23#include <QTimer>
24#include <QXmlStreamReader>
25
26#include "platformdependent.h"
27#include "platformdependent_v3.h"
28#include "qtplatformdependent_p.h"
29#include <QLibraryInfo>
30
31using namespace Attica;
32
33class Q_DECL_HIDDEN ProviderManager::Private
34{
35public:
36 PlatformDependent *m_internals;
37 QHash<QUrl, Provider> m_providers;
38 QHash<QUrl, QUrl> m_providerTargets;
39 QHash<QString, QNetworkReply *> m_downloads;
40 bool m_authenticationSuppressed;
41
42 Private()
43 : m_internals(nullptr)
44 , m_authenticationSuppressed(false)
45 {
46 }
47 ~Private()
48 {
49 // do not delete m_internals: it is the root component of a plugin!
50 }
51};
52
53PlatformDependent *ProviderManager::loadPlatformDependent(const ProviderFlags &flags)
54{
55 if (flags & ProviderManager::DisablePlugins) {
56 return new QtPlatformDependent;
57 }
58
59 QPluginLoader loader(QStringLiteral("attica_kde"));
60 PlatformDependent *ret = qobject_cast<PlatformDependent *>(object: loader.instance());
61
62 return ret ? ret : new QtPlatformDependent;
63}
64
65ProviderManager::ProviderManager(const ProviderFlags &flags)
66 : d(new Private)
67{
68 d->m_internals = loadPlatformDependent(flags);
69 connect(sender: d->m_internals->nam(), signal: &QNetworkAccessManager::authenticationRequired, context: this, slot: &ProviderManager::authenticate);
70}
71
72void ProviderManager::loadDefaultProviders()
73{
74 auto platformDependentV3 = dynamic_cast<Attica::PlatformDependentV3 *>(d->m_internals);
75 if (platformDependentV3 && !platformDependentV3->isReady()) {
76 connect(sender: platformDependentV3,
77 signal: &Attica::PlatformDependentV3::readyChanged,
78 context: this,
79 slot: &ProviderManager::slotLoadDefaultProvidersInternal,
80 type: Qt::QueuedConnection);
81 return;
82 }
83
84 QTimer::singleShot(interval: 0, receiver: this, slot: &ProviderManager::slotLoadDefaultProvidersInternal);
85}
86
87void ProviderManager::setAuthenticationSuppressed(bool suppressed)
88{
89 d->m_authenticationSuppressed = suppressed;
90}
91
92void ProviderManager::clear()
93{
94 d->m_providerTargets.clear();
95 d->m_providers.clear();
96}
97
98void ProviderManager::slotLoadDefaultProvidersInternal()
99{
100 const auto providerFiles = d->m_internals->getDefaultProviderFiles();
101 for (const QUrl &url : providerFiles) {
102 addProviderFile(file: url);
103 }
104 if (d->m_downloads.isEmpty()) {
105 Q_EMIT defaultProvidersLoaded();
106 }
107}
108
109QList<QUrl> ProviderManager::defaultProviderFiles()
110{
111 return d->m_internals->getDefaultProviderFiles();
112}
113
114ProviderManager::~ProviderManager()
115{
116 delete d;
117}
118
119void ProviderManager::addProviderFileToDefaultProviders(const QUrl &url)
120{
121 d->m_internals->addDefaultProviderFile(url);
122 addProviderFile(file: url);
123}
124
125void ProviderManager::removeProviderFileFromDefaultProviders(const QUrl &url)
126{
127 d->m_internals->removeDefaultProviderFile(url);
128}
129
130void ProviderManager::addProviderFile(const QUrl &url)
131{
132 if (url.isLocalFile()) {
133 QFile file(url.toLocalFile());
134 if (!file.open(flags: QIODevice::ReadOnly)) {
135 qWarning() << "ProviderManager::addProviderFile: could not open provider file: " << url.toString();
136 return;
137 }
138 parseProviderFile(xmlString: QLatin1String(file.readAll()), url);
139 } else {
140 if (!d->m_downloads.contains(key: url.toString())) {
141 QNetworkRequest req(url);
142 req.setAttribute(code: QNetworkRequest::RedirectPolicyAttribute, value: QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy);
143 QNetworkReply *reply = d->m_internals->get(request: req);
144 qCDebug(ATTICA) << "executing" << Utils::toString(operation: reply->operation()) << "for" << reply->url();
145 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, url]() {
146 fileFinished(url: url.toString());
147 });
148 d->m_downloads.insert(key: url.toString(), value: reply);
149 }
150 }
151}
152
153void ProviderManager::fileFinished(const QString &url)
154{
155 QNetworkReply *reply = d->m_downloads.take(key: url);
156 if (reply) {
157 if (reply->error()) {
158 Q_EMIT failedToLoad(provider: QUrl(url), error: reply->error());
159 } else {
160 parseProviderFile(xmlString: QLatin1String(reply->readAll()), url: QUrl(url));
161 }
162 reply->deleteLater();
163 } else {
164 Q_EMIT failedToLoad(provider: QUrl(url), error: QNetworkReply::UnknownNetworkError);
165 }
166}
167
168void ProviderManager::addProviderFromXml(const QString &providerXml)
169{
170 parseProviderFile(xmlString: providerXml, url: QUrl());
171}
172
173void ProviderManager::parseProviderFile(const QString &xmlString, const QUrl &url)
174{
175 QXmlStreamReader xml(xmlString);
176 while (!xml.atEnd() && xml.readNext()) {
177 if (xml.isStartElement() && xml.name() == QLatin1String("provider")) {
178 QUrl baseUrl;
179 QString name;
180 QUrl icon;
181 QString person;
182 QString friendV;
183 QString message;
184 QString achievement;
185 QString activity;
186 QString content;
187 QString fan;
188 QString forum;
189 QString knowledgebase;
190 QString event;
191 QString comment;
192 QString registerUrl;
193
194 while (!xml.atEnd() && xml.readNext()) {
195 if (xml.isStartElement()) {
196 if (xml.name() == QLatin1String("location")) {
197 baseUrl = QUrl(xml.readElementText());
198 } else if (xml.name() == QLatin1String("name")) {
199 name = xml.readElementText();
200 } else if (xml.name() == QLatin1String("icon")) {
201 icon = QUrl(xml.readElementText());
202 } else if (xml.name() == QLatin1String("person")) {
203 person = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
204 } else if (xml.name() == QLatin1String("friend")) {
205 friendV = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
206 } else if (xml.name() == QLatin1String("message")) {
207 message = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
208 } else if (xml.name() == QLatin1String("achievement")) {
209 achievement = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
210 } else if (xml.name() == QLatin1String("activity")) {
211 activity = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
212 } else if (xml.name() == QLatin1String("content")) {
213 content = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
214 } else if (xml.name() == QLatin1String("fan")) {
215 fan = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
216 } else if (xml.name() == QLatin1String("forum")) {
217 forum = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
218 } else if (xml.name() == QLatin1String("knowledgebase")) {
219 knowledgebase = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
220 } else if (xml.name() == QLatin1String("event")) {
221 event = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
222 } else if (xml.name() == QLatin1String("comment")) {
223 comment = xml.attributes().value(qualifiedName: QLatin1String("ocsversion")).toString();
224 } else if (xml.name() == QLatin1String("register")) {
225 registerUrl = xml.readElementText();
226 }
227 } else if (xml.isEndElement() && xml.name() == QLatin1String("provider")) {
228 break;
229 }
230 }
231 if (!baseUrl.isEmpty()) {
232 // qCDebug(ATTICA) << "Adding provider" << baseUrl;
233 d->m_providers.insert(key: baseUrl,
234 value: Provider(d->m_internals,
235 baseUrl,
236 name,
237 icon,
238 person,
239 friendV,
240 message,
241 achievement,
242 activity,
243 content,
244 fan,
245 forum,
246 knowledgebase,
247 event,
248 comment,
249 registerUrl));
250 d->m_providerTargets[url] = baseUrl;
251 Q_EMIT providerAdded(provider: d->m_providers.value(key: baseUrl));
252 }
253 }
254 }
255
256 if (xml.error() != QXmlStreamReader::NoError) {
257 qCDebug(ATTICA) << "error:" << xml.errorString() << "in" << url;
258 }
259
260 if (d->m_downloads.isEmpty()) {
261 Q_EMIT defaultProvidersLoaded();
262 }
263}
264
265Provider ProviderManager::providerFor(const QUrl &url) const
266{
267 return providerByUrl(url: d->m_providerTargets.value(key: url));
268}
269
270Provider ProviderManager::providerByUrl(const QUrl &url) const
271{
272 return d->m_providers.value(key: url);
273}
274
275QList<Provider> ProviderManager::providers() const
276{
277 return d->m_providers.values();
278}
279
280QList<QUrl> ProviderManager::providerFiles() const
281{
282 return d->m_providerTargets.keys();
283}
284
285void ProviderManager::authenticate(QNetworkReply *reply, QAuthenticator *auth)
286{
287 QUrl baseUrl;
288 const QList<QUrl> urls = d->m_providers.keys();
289 for (const QUrl &url : urls) {
290 if (url.isParentOf(url: reply->url())) {
291 baseUrl = url;
292 break;
293 }
294 }
295
296 // qCDebug(ATTICA) << "ProviderManager::authenticate" << baseUrl;
297
298 QString user;
299 QString password;
300 if (auth->user().isEmpty() && auth->password().isEmpty()) {
301 if (d->m_internals->hasCredentials(baseUrl)) {
302 if (d->m_internals->loadCredentials(baseUrl, user, password)) {
303 // qCDebug(ATTICA) << "ProviderManager::authenticate: loading authentication";
304 auth->setUser(user);
305 auth->setPassword(password);
306 return;
307 }
308 }
309 }
310
311 if (!d->m_authenticationSuppressed && d->m_internals->askForCredentials(baseUrl, user, password)) {
312 // qCDebug(ATTICA) << "ProviderManager::authenticate: asking internals for new credentials";
313 // auth->setUser(user);
314 // auth->setPassword(password);
315 return;
316 }
317
318 qWarning() << "ProviderManager::authenticate: No authentication credentials provided, aborting." << reply->url().toString();
319 Q_EMIT authenticationCredentialsMissing(provider: d->m_providers.value(key: baseUrl));
320 reply->abort();
321}
322
323void ProviderManager::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)
324{
325 Q_UNUSED(proxy)
326 Q_UNUSED(authenticator)
327}
328
329#include "moc_providermanager.cpp"
330

source code of attica/src/providermanager.cpp