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