diff --git a/autotests/providertest.cpp b/autotests/providertest.cpp index c24c0f7..706a8d3 100644 --- a/autotests/providertest.cpp +++ b/autotests/providertest.cpp @@ -1,147 +1,150 @@ /* This file is part of KDE. Copyright (c) 2018 Ralf Habacker 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include +#include + #include #include using namespace Attica; class ProviderTest : public QObject { Q_OBJECT public: ProviderTest(); virtual ~ProviderTest(); private: void initProvider(const QUrl &url); private Q_SLOTS: void testFetchValidProvider(); void testFetchInvalidProvider(); protected Q_SLOTS: void providerAdded(Attica::Provider p); void slotDefaultProvidersLoaded(); void slotConfigResult(Attica::BaseJob *j); void slotTimeout(); private: Attica::ProviderManager *m_manager; QEventLoop *m_eventloop; QTimer m_timer; bool m_checkFail; }; ProviderTest::ProviderTest() : m_manager(0), m_eventloop(new QEventLoop), m_checkFail(true) { + QLoggingCategory::setFilterRules(QStringLiteral("org.kde.attica.debug=true")); } ProviderTest::~ProviderTest() { delete m_manager; } void ProviderTest::slotDefaultProvidersLoaded() { qDebug() << "default providers loaded"; m_eventloop->exit(); } void ProviderTest::providerAdded(Attica::Provider p) { qDebug() << "got provider" << p.name(); m_eventloop->exit(); } void ProviderTest::initProvider(const QUrl &url) { delete m_manager; m_manager = new Attica::ProviderManager; m_manager->setAuthenticationSuppressed(true); connect(m_manager, SIGNAL(defaultProvidersLoaded()), this, SLOT(slotDefaultProvidersLoaded())); connect(m_manager, SIGNAL(providerAdded(Attica::Provider)), this, SLOT(providerAdded(Attica::Provider))); m_manager->addProviderFile(url); m_timer.singleShot(5000, this, SLOT(slotTimeout())); m_checkFail = true; m_eventloop->exec(); } void ProviderTest::testFetchValidProvider() { initProvider(QUrl(QLatin1String("https://autoconfig.kde.org/ocs/providers.xml"))); Attica::Provider provider = m_manager->providers().at(0); ItemJob* job = provider.requestConfig(); QVERIFY(job); connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(slotConfigResult(Attica::BaseJob*))); job->start(); m_eventloop->exec(); } void ProviderTest::slotConfigResult(Attica::BaseJob* j) { if (j->metadata().error() == Metadata::NoError) { Attica::ItemJob *itemJob = static_cast *>( j ); Attica::Config p = itemJob->result(); qDebug() << QLatin1String("Config loaded - Server has version") << p.version(); } else if (j->metadata().error() == Metadata::OcsError) { qDebug() << QString(QLatin1String("OCS Error: %1")).arg(j->metadata().message()); } else if (j->metadata().error() == Metadata::NetworkError) { qDebug() << QString(QLatin1String("Network Error: %1")).arg(j->metadata().message()); } else { qDebug() << QString(QLatin1String("Unknown Error: %1")).arg(j->metadata().message()); } m_eventloop->exit(); m_timer.stop(); QVERIFY(j->metadata().error() == Metadata::NoError); } void ProviderTest::slotTimeout() { if (m_eventloop->isRunning()) { m_eventloop->exit(); if (m_checkFail) QFAIL("Could not fetch provider"); } } void ProviderTest::testFetchInvalidProvider() { // TODO error state could only be checked indirectly by timeout initProvider(QUrl(QLatin1String("https://invalid-url.org/ocs/providers.xml"))); m_timer.singleShot(5000, this, SLOT(slotTimeout())); m_checkFail = false; m_eventloop->exec(); QVERIFY(m_manager->providers().size() == 0); } QTEST_MAIN(ProviderTest) #include "providertest.moc" diff --git a/src/atticabasejob.cpp b/src/atticabasejob.cpp index bbd02d8..765a79d 100644 --- a/src/atticabasejob.cpp +++ b/src/atticabasejob.cpp @@ -1,178 +1,180 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "atticabasejob.h" #include #include #include #include #include +#include #include "platformdependent.h" using namespace Attica; class BaseJob::Private { public: Metadata m_metadata; PlatformDependent *m_internals; QNetworkReply *m_reply; Private(PlatformDependent *internals) : m_internals(internals), m_reply(nullptr) { } bool redirection(QUrl &newUrl) const { if (m_reply == nullptr || m_reply->error() != QNetworkReply::NoError) { return false; } int httpStatusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (httpStatusCode == 301 || // Moved Permanently httpStatusCode == 302 || // Found httpStatusCode == 303 || // See Other httpStatusCode == 307) { // Temporary Redirect QNetworkRequest request = m_reply->request(); QUrl redirectUrl(m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()); if (redirectUrl.isRelative()) { QUrl baseUrl(request.url()); newUrl = baseUrl.resolved(redirectUrl); qCDebug(ATTICA) << "resolving relative URL redirection to" << newUrl.toString(); } else { newUrl = redirectUrl; qCDebug(ATTICA) << "resolving absolute URL redirection to" << newUrl.toString(); } return true; } return false; } }; BaseJob::BaseJob(PlatformDependent *internals) : d(new Private(internals)) { } BaseJob::~BaseJob() { delete d; } void BaseJob::dataFinished() { if (!d->m_reply) { return; } bool error = (d->m_reply->error() != QNetworkReply::NoError); // handle redirections automatically QUrl newUrl; if (d->redirection(newUrl)) { //qDebug() << "BaseJob::dataFinished" << newUrl; QNetworkRequest request = d->m_reply->request(); QNetworkAccessManager::Operation operation = d->m_reply->operation(); if (newUrl.isValid() && operation == QNetworkAccessManager::GetOperation) { d->m_reply->deleteLater(); // reissue same request with different Url request.setUrl(newUrl); d->m_reply = internals()->get(request); connect(d->m_reply, SIGNAL(finished()), SLOT(dataFinished())); return; } else { error = true; } } if (!error) { QByteArray data = d->m_reply->readAll(); //qDebug() << "XML Returned:\n" << data; parse(QString::fromUtf8(data.constData())); if (d->m_metadata.statusCode() == 100) { d->m_metadata.setError(Metadata::NoError); } else { d->m_metadata.setError(Metadata::OcsError); } } else { d->m_metadata.setError(Metadata::NetworkError); d->m_metadata.setStatusCode(d->m_reply->error()); d->m_metadata.setStatusString(d->m_reply->errorString()); } emit finished(this); d->m_reply->deleteLater(); deleteLater(); } void BaseJob::start() { QTimer::singleShot(0, this, SLOT(doWork())); } void BaseJob::doWork() { d->m_reply = executeRequest(); + qCDebug(ATTICA) << "executing" << Utils::toString(d->m_reply->operation()) << "request for" << d->m_reply->url(); connect(d->m_reply, SIGNAL(finished()), SLOT(dataFinished())); connect(d->m_reply->manager(), SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); connect(d->m_reply, static_cast(&QNetworkReply::error), [](QNetworkReply::NetworkError code){ qCDebug(ATTICA) << "error found" << code; }); } void BaseJob::authenticationRequired(QNetworkReply *reply, QAuthenticator *auth) { auth->setUser(reply->request().attribute((QNetworkRequest::Attribute) BaseJob::UserAttribute).toString()); auth->setPassword(reply->request().attribute((QNetworkRequest::Attribute) BaseJob::PasswordAttribute).toString()); } void BaseJob::abort() { if (d->m_reply) { d->m_reply->abort(); d->m_reply->deleteLater(); } deleteLater(); } PlatformDependent *BaseJob::internals() { return d->m_internals; } Metadata BaseJob::metadata() const { return d->m_metadata; } void BaseJob::setMetadata(const Attica::Metadata &data) const { d->m_metadata = data; } diff --git a/src/atticautils.cpp b/src/atticautils.cpp index e382bb2..27a9016 100644 --- a/src/atticautils.cpp +++ b/src/atticautils.cpp @@ -1,74 +1,88 @@ /* This file is part of KDE. Copyright (c) 2010 Intel Corporation Author: Mateu Batle Sastre 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "atticautils.h" #include using namespace Attica; QDateTime Utils::parseQtDateTimeIso8601(const QString &str) { QDateTime result; QStringList list; QString datetime; int tzsign = 0; if (str.indexOf(QLatin1String("+")) != -1) { list = str.split(QLatin1String("+")); datetime = list[0]; tzsign = 1; } else if (str.indexOf(QLatin1String("-")) != -1) { list = str.split(QLatin1String("-")); datetime = list[0]; tzsign = -1; } else { datetime = str; } // parse date time result = QDateTime::fromString(datetime, Qt::ISODate); result.setTimeSpec(Qt::UTC); // parse timezone if (list.count() == 2) { QString tz = list[1]; int hh = 0; int mm = 0; int tzsecs = 0; if (tz.indexOf(QLatin1String(":")) != -1) { QStringList tzlist = tz.split(QLatin1String(":")); if (tzlist.count() == 2) { hh = tzlist[0].toInt(); mm = tzlist[1].toInt(); } } else { hh = tz.leftRef(2).toInt(); mm = tz.midRef(2).toInt(); } tzsecs = 60 * 60 * hh + 60 * mm; result = result.addSecs(- tzsecs * tzsign); } return result; } + +const char *Utils::toString(QNetworkAccessManager::Operation operation) +{ + switch(operation) { + case QNetworkAccessManager::GetOperation : return "Get"; + case QNetworkAccessManager::HeadOperation : return "Head"; + case QNetworkAccessManager::PutOperation : return "Put"; + case QNetworkAccessManager::PostOperation : return "Post"; + case QNetworkAccessManager::DeleteOperation : return "Delete"; + case QNetworkAccessManager::CustomOperation : return "Custom"; + default: return "unknown"; + } + return "invalid"; +} diff --git a/src/atticautils.h b/src/atticautils.h index 1e21e49..f5c5e28 100644 --- a/src/atticautils.h +++ b/src/atticautils.h @@ -1,42 +1,44 @@ /* This file is part of KDE. Copyright (c) 2010 Intel Corporation Author: Mateu Batle Sastre 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef ATTICAUTILS_H #define ATTICAUTILS_H #include #include +#include namespace Attica { class Utils { public: /// parses the QtDateTime in ISO 8601 format correctly (recognizes TZ properly) static QDateTime parseQtDateTimeIso8601(const QString &str); + static const char *toString(QNetworkAccessManager::Operation operation); }; } #endif // ATTICAUTILS_H diff --git a/src/getjob.cpp b/src/getjob.cpp index aeafe68..5f82fd3 100644 --- a/src/getjob.cpp +++ b/src/getjob.cpp @@ -1,43 +1,42 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "getjob.h" #include #include "platformdependent.h" #include using namespace Attica; GetJob::GetJob(PlatformDependent *internals, const QNetworkRequest &request) : BaseJob(internals), m_request(request) { } QNetworkReply *GetJob::executeRequest() { - qCDebug(ATTICA) << "executing get request for url" << m_request.url().toString(); return internals()->get(m_request); } diff --git a/src/providermanager.cpp b/src/providermanager.cpp index d64880a..e8c5632 100644 --- a/src/providermanager.cpp +++ b/src/providermanager.cpp @@ -1,319 +1,322 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner Copyright (c) 2009 Frederik Gladhorn 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) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "providermanager.h" +#include "atticautils.h" + #include #include #include #include #include #include #include #include #include #include #include #include #include "platformdependent.h" #include "qtplatformdependent_p.h" #include using namespace Attica; class ProviderManager::Private { public: PlatformDependent *m_internals; QHash m_providers; QHash m_providerTargets; QSignalMapper m_downloadMapping; QHash m_downloads; bool m_authenticationSuppressed; Private() : m_internals(nullptr) , m_authenticationSuppressed(false) { } ~Private() { // do not delete m_internals: it is the root component of a plugin! } }; PlatformDependent *ProviderManager::loadPlatformDependent(const ProviderFlags &flags) { if (flags & ProviderManager::DisablePlugins) { return new QtPlatformDependent; } QPluginLoader loader(QStringLiteral("attica_kde")); PlatformDependent * ret = qobject_cast(loader.instance()); return ret ? ret : new QtPlatformDependent; } ProviderManager::ProviderManager(const ProviderFlags &flags) : d(new Private) { d->m_internals = loadPlatformDependent(flags); connect(d->m_internals->nam(), SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), SLOT(authenticate(QNetworkReply*,QAuthenticator*))); connect(&d->m_downloadMapping, SIGNAL(mapped(QString)), SLOT(fileFinished(QString))); } void ProviderManager::loadDefaultProviders() { QTimer::singleShot(0, this, SLOT(slotLoadDefaultProvidersInternal())); } void ProviderManager::setAuthenticationSuppressed(bool suppressed) { d->m_authenticationSuppressed = suppressed; } void ProviderManager::clear() { d->m_providerTargets.clear(); d->m_providers.clear(); } void ProviderManager::slotLoadDefaultProvidersInternal() { foreach (const QUrl &url, d->m_internals->getDefaultProviderFiles()) { addProviderFile(url); } if (d->m_downloads.isEmpty()) { emit defaultProvidersLoaded(); } } QList ProviderManager::defaultProviderFiles() { return d->m_internals->getDefaultProviderFiles(); } ProviderManager::~ProviderManager() { delete d; } void ProviderManager::addProviderFileToDefaultProviders(const QUrl &url) { d->m_internals->addDefaultProviderFile(url); addProviderFile(url); } void ProviderManager::removeProviderFileFromDefaultProviders(const QUrl &url) { d->m_internals->removeDefaultProviderFile(url); } void ProviderManager::addProviderFile(const QUrl &url) { if (url.isLocalFile()) { QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "ProviderManager::addProviderFile: could not open provider file: " << url.toString(); return; } parseProviderFile(QLatin1String(file.readAll()), url); } else { if (!d->m_downloads.contains(url.toString())) { QNetworkRequest req(url); req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); QNetworkReply *reply = d->m_internals->get(req); + qDebug() << "executing" << Utils::toString(reply->operation()) << "for" << reply->url(); connect(reply, SIGNAL(finished()), &d->m_downloadMapping, SLOT(map())); d->m_downloadMapping.setMapping(reply, url.toString()); d->m_downloads.insert(url.toString(), reply); } } } void ProviderManager::fileFinished(const QString &url) { QNetworkReply *reply = d->m_downloads.take(url); parseProviderFile(QLatin1String(reply->readAll()), QUrl(url)); reply->deleteLater(); } void ProviderManager::addProviderFromXml(const QString &providerXml) { parseProviderFile(providerXml, QUrl()); } void ProviderManager::parseProviderFile(const QString &xmlString, const QUrl &url) { QXmlStreamReader xml(xmlString); while (!xml.atEnd() && xml.readNext()) { if (xml.isStartElement() && xml.name() == QLatin1String("provider")) { QUrl baseUrl; QString name; QUrl icon; QString person; QString friendV; QString message; QString achievement; QString activity; QString content; QString fan; QString forum; QString knowledgebase; QString event; QString comment; QString registerUrl; while (!xml.atEnd() && xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == QLatin1String("location")) { baseUrl = QUrl(xml.readElementText()); } else if (xml.name() == QLatin1String("name")) { name = xml.readElementText(); } else if (xml.name() == QLatin1String("icon")) { icon = QUrl(xml.readElementText()); } else if (xml.name() == QLatin1String("person")) { person = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("friend")) { friendV = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("message")) { message = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("achievement")) { achievement = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("activity")) { activity = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("content")) { content = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("fan")) { fan = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("forum")) { forum = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("knowledgebase")) { knowledgebase = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("event")) { event = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("comment")) { comment = xml.attributes().value(QLatin1String("ocsversion")).toString(); } else if (xml.name() == QLatin1String("register")) { registerUrl = xml.readElementText(); } } else if (xml.isEndElement() && xml.name() == QLatin1String("provider")) { break; } } if (!baseUrl.isEmpty()) { //qDebug() << "Adding provider" << baseUrl; d->m_providers.insert(baseUrl, Provider(d->m_internals, baseUrl, name, icon, person, friendV, message, achievement, activity, content, fan, forum, knowledgebase, event, comment, registerUrl)); d->m_providerTargets[url] = baseUrl; emit providerAdded(d->m_providers.value(baseUrl)); } } } if (xml.error() != QXmlStreamReader::NoError) { qDebug() << "error:" << xml.errorString() << "in" << url; } if (d->m_downloads.isEmpty()) { emit defaultProvidersLoaded(); } } Provider ProviderManager::providerFor(const QUrl &url) const { return providerByUrl(d->m_providerTargets.value(url)); } Provider ProviderManager::providerByUrl(const QUrl &url) const { return d->m_providers.value(url); } QList ProviderManager::providers() const { return d->m_providers.values(); } bool ProviderManager::contains(const QString &provider) const { return d->m_providers.contains(QUrl(provider)); } QList ProviderManager::providerFiles() const { return d->m_providerTargets.keys(); } void ProviderManager::authenticate(QNetworkReply *reply, QAuthenticator *auth) { QUrl baseUrl; QList urls = d->m_providers.keys(); foreach (const QUrl &url, urls) { if (url.isParentOf(reply->url())) { baseUrl = url; break; } } //qDebug() << "ProviderManager::authenticate" << baseUrl; QString user; QString password; if (auth->user().isEmpty() && auth->password().isEmpty()) { if (d->m_internals->hasCredentials(baseUrl)) { if (d->m_internals->loadCredentials(baseUrl, user, password)) { //qDebug() << "ProviderManager::authenticate: loading authentication"; auth->setUser(user); auth->setPassword(password); return; } } } if (!d->m_authenticationSuppressed && d->m_internals->askForCredentials(baseUrl, user, password)) { //qDebug() << "ProviderManager::authenticate: asking internals for new credentials"; //auth->setUser(user); //auth->setPassword(password); return; } qWarning() << "ProviderManager::authenticate: No authentication credentials provided, aborting." << reply->url().toString(); emit authenticationCredentialsMissing(d->m_providers.value(baseUrl)); reply->abort(); } void ProviderManager::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) { Q_UNUSED(proxy) Q_UNUSED(authenticator) } void ProviderManager::initNetworkAccesssManager() { connect(d->m_internals->nam(), SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(authenticate(QNetworkReply*,QAuthenticator*))); connect(d->m_internals->nam(), SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); }