diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b24873359..966ed88859 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,9 @@ set(keepassx_SOURCES core/Alloc.cpp core/AutoTypeAssociations.cpp core/AutoTypeMatch.cpp + core/Base32.cpp + core/Bootstrap.cpp + core/Clock.cpp core/Compare.cpp core/Config.cpp core/CsvParser.cpp @@ -39,7 +42,6 @@ set(keepassx_SOURCES core/EntrySearcher.cpp core/FilePath.cpp core/FileWatcher.cpp - core/Bootstrap.cpp core/Group.cpp core/HibpOffline.cpp core/InactivityTimer.cpp @@ -52,10 +54,8 @@ set(keepassx_SOURCES core/ScreenLockListenerPrivate.cpp core/TimeDelta.cpp core/TimeInfo.cpp - core/Clock.cpp core/Tools.cpp core/Translator.cpp - core/Base32.cpp cli/Utils.cpp cli/TextStream.cpp crypto/Crypto.cpp @@ -264,7 +264,12 @@ else() endif() if(WITH_XC_NETWORKING) - list(APPEND keepassx_SOURCES updatecheck/UpdateChecker.cpp gui/UpdateCheckDialog.cpp) + list(APPEND keepassx_SOURCES + core/IconDownloader.cpp + core/NetworkManager.cpp + gui/UpdateCheckDialog.cpp + gui/IconDownloaderDialog.cpp + updatecheck/UpdateChecker.cpp) endif() if(WITH_XC_TOUCHID) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 3922d3e27e..741a10d75f 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -194,6 +194,7 @@ void Config::init(const QString& fileName) m_defaults.insert("AutoTypeStartDelay", 500); m_defaults.insert("UseGroupIconOnEntryCreation", true); m_defaults.insert("IgnoreGroupExpansion", true); + m_defaults.insert("FaviconDownloadTimeout", 10); m_defaults.insert("security/clearclipboard", true); m_defaults.insert("security/clearclipboardtimeout", 10); m_defaults.insert("security/lockdatabaseidle", false); diff --git a/src/core/IconDownloader.cpp b/src/core/IconDownloader.cpp new file mode 100644 index 0000000000..36047ce2a7 --- /dev/null +++ b/src/core/IconDownloader.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "IconDownloader.h" +#include "core/Config.h" +#include "core/NetworkManager.h" + +#include +#include + +#define MAX_REDIRECTS 5 + +IconDownloader::IconDownloader(QObject* parent) + : QObject(parent) + , m_reply(nullptr) + , m_redirects(0) +{ + m_timeout.setSingleShot(true); + connect(&m_timeout, SIGNAL(timeout()), SLOT(abortDownload())); +} + +IconDownloader::~IconDownloader() +{ + abortDownload(); +} + +namespace +{ + // Try to get the 2nd level domain of the host part of a QUrl. For example, + // "foo.bar.example.com" would become "example.com", and "foo.bar.example.co.uk" + // would become "example.co.uk". + QString getSecondLevelDomain(const QUrl& url) + { + QString fqdn = url.host(); + fqdn.truncate(fqdn.length() - url.topLevelDomain().length()); + QStringList parts = fqdn.split('.'); + QString newdom = parts.takeLast() + url.topLevelDomain(); + return newdom; + } + + QUrl convertVariantToUrl(const QVariant& var) + { + QUrl url; + if (var.canConvert()) { + url = var.toUrl(); + } + return url; + } + + QUrl getRedirectTarget(QNetworkReply* reply) + { + QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + QUrl url = convertVariantToUrl(var); + return url; + } +} // namespace + +void IconDownloader::setUrl(const QString& entryUrl) +{ + m_url = entryUrl; + QUrl url(m_url); + if (!url.isValid()) { + return; + } + + m_redirects = 0; + m_urlsToTry.clear(); + + if (url.scheme().isEmpty()) { + url.setUrl(QString("https://%1").arg(url.toString())); + } + + QString fullyQualifiedDomain = url.host(); + + // Determine if host portion of URL is an IP address by resolving it and + // searching for a match with the returned address(es). + bool hostIsIp = false; + QList hostAddressess = QHostInfo::fromName(fullyQualifiedDomain).addresses(); + for (auto addr : hostAddressess) { + if (addr.toString() == fullyQualifiedDomain) { + hostIsIp = true; + } + } + + // Determine the second-level domain, if available + QString secondLevelDomain; + if (!hostIsIp) { + secondLevelDomain = getSecondLevelDomain(m_url); + } + + // Start with the "fallback" url (if enabled) to try to get the best favicon + if (config()->get("security/IconDownloadFallback", false).toBool()) { + QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); + fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); + m_urlsToTry.append(fallbackUrl); + + // Also try a direct pull of the second-level domain (if possible) + if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { + fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); + m_urlsToTry.append(fallbackUrl); + } + } + + // Add a direct pull of the website's own favicon.ico file + m_urlsToTry.append(QUrl(url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); + + // Also try a direct pull of the second-level domain (if possible) + if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { + m_urlsToTry.append(QUrl(url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); + } +} + +void IconDownloader::download() +{ + if (!m_timeout.isActive()) { + int timeout = config()->get("FaviconDownloadTimeout", 10).toInt(); + m_timeout.start(timeout * 1000); + + // Use the first URL to start the download process + // If a favicon is not found, the next URL will be tried + fetchFavicon(m_urlsToTry.takeFirst()); + } +} + +void IconDownloader::abortDownload() +{ + if (m_reply) { + m_reply->abort(); + } +} + +void IconDownloader::fetchFavicon(const QUrl& url) +{ + m_bytesReceived.clear(); + m_fetchUrl = url; + + QNetworkRequest request(url); + m_reply = getNetMgr()->get(request); + + connect(m_reply, &QNetworkReply::finished, this, &IconDownloader::fetchFinished); + connect(m_reply, &QIODevice::readyRead, this, &IconDownloader::fetchReadyRead); +} + +void IconDownloader::fetchReadyRead() +{ + m_bytesReceived += m_reply->readAll(); +} + +void IconDownloader::fetchFinished() +{ + QImage image; + QString url = m_url; + + bool error = (m_reply->error() != QNetworkReply::NoError); + QUrl redirectTarget = getRedirectTarget(m_reply); + + m_reply->deleteLater(); + m_reply = nullptr; + + if (!error) { + if (redirectTarget.isValid()) { + // Redirected, we need to follow it, or fall through if we have + // done too many redirects already. + if (m_redirects < MAX_REDIRECTS) { + m_redirects++; + if (redirectTarget.isRelative()) { + redirectTarget = m_fetchUrl.resolved(redirectTarget); + } + m_urlsToTry.prepend(redirectTarget); + } + } else { + // No redirect, and we theoretically have some icon data now. + image.loadFromData(m_bytesReceived); + } + } + + if (!image.isNull()) { + // Valid icon received + m_timeout.stop(); + emit finished(url, image); + } else if (!m_urlsToTry.empty()) { + // Try the next url + m_redirects = 0; + fetchFavicon(m_urlsToTry.takeFirst()); + } else { + // No icon found + m_timeout.stop(); + emit finished(url, image); + } +} diff --git a/src/core/IconDownloader.h b/src/core/IconDownloader.h new file mode 100644 index 0000000000..e2b8c4f2df --- /dev/null +++ b/src/core/IconDownloader.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_ICONDOWNLOADER_H +#define KEEPASSXC_ICONDOWNLOADER_H + +#include +#include +#include +#include + +#include "core/Global.h" + +class QNetworkReply; + +class IconDownloader : public QObject +{ + Q_OBJECT + +public: + explicit IconDownloader(QObject* parent = nullptr); + ~IconDownloader() override; + + void setUrl(const QString& entryUrl); + void download(); + +signals: + void finished(const QString& entryUrl, const QImage& image); + +public slots: + void abortDownload(); + +private slots: + void fetchFinished(); + void fetchReadyRead(); + +private: + void fetchFavicon(const QUrl& url); + + QString m_url; + QUrl m_fetchUrl; + QList m_urlsToTry; + QByteArray m_bytesReceived; + QNetworkReply* m_reply; + QTimer m_timeout; + int m_redirects; +}; + +#endif // KEEPASSXC_ICONDOWNLOADER_H \ No newline at end of file diff --git a/src/core/NetworkManager.cpp b/src/core/NetworkManager.cpp new file mode 100644 index 0000000000..52b54609fe --- /dev/null +++ b/src/core/NetworkManager.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config-keepassx.h" + +#ifdef WITH_XC_NETWORKING +#include "NetworkManager.h" + +#include + +QNetworkAccessManager* g_netMgr = nullptr; +QNetworkAccessManager* getNetMgr() +{ + if (!g_netMgr) { + g_netMgr = new QNetworkAccessManager(QCoreApplication::instance()); + } + return g_netMgr; +} +#endif diff --git a/src/core/NetworkManager.h b/src/core/NetworkManager.h new file mode 100644 index 0000000000..5616218749 --- /dev/null +++ b/src/core/NetworkManager.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_NETWORKMANAGER_H +#define KEEPASSXC_NETWORKMANAGER_H + +#include "config-keepassx.h" +#include + +#ifdef WITH_XC_NETWORKING +#include +#include +#include + +QNetworkAccessManager* getNetMgr(); +#else +Q_STATIC_ASSERT_X(false, "Qt Networking used when WITH_XC_NETWORKING is disabled!"); +#endif + +#endif // KEEPASSXC_NETWORKMANAGER_H diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index 832e73fa40..89c585fcb0 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -103,6 +103,8 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) #ifndef WITH_XC_NETWORKING m_secUi->privacy->setVisible(false); + m_generalUi->faviconTimeoutLabel->setVisible(false); + m_generalUi->faviconTimeoutSpinBox->setVisible(false); #endif #ifndef WITH_XC_TOUCHID @@ -156,6 +158,7 @@ void ApplicationSettingsWidget::loadSettings() m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); m_generalUi->autoTypeEntryURLMatchCheckBox->setChecked(config()->get("AutoTypeEntryURLMatch").toBool()); m_generalUi->ignoreGroupExpansionCheckBox->setChecked(config()->get("IgnoreGroupExpansion").toBool()); + m_generalUi->faviconTimeoutSpinBox->setValue(config()->get("FaviconDownloadTimeout").toInt()); if (!m_generalUi->hideWindowOnCopyCheckBox->isChecked()) { hideWindowOnCopyCheckBoxToggled(false); @@ -264,6 +267,7 @@ void ApplicationSettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); config()->set("AutoTypeEntryURLMatch", m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("FaviconDownloadTimeout", m_generalUi->faviconTimeoutSpinBox->value()); config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); diff --git a/src/gui/ApplicationSettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui index d1fb19c758..311b408b89 100644 --- a/src/gui/ApplicationSettingsWidgetGeneral.ui +++ b/src/gui/ApplicationSettingsWidgetGeneral.ui @@ -7,7 +7,7 @@ 0 0 684 - 1030 + 860 @@ -329,6 +329,55 @@ + + + + + + Favicon download timeout: + + + + + + + true + + + + 0 + 0 + + + + sec + + + 1 + + + 60 + + + 10 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 7d8c958025..4effa99f99 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -62,6 +62,10 @@ #include "keeshare/KeeShare.h" #include "touchid/TouchID.h" +#ifdef WITH_XC_NETWORKING +#include "gui/IconDownloaderDialog.h" +#endif + #ifdef Q_OS_LINUX #include #endif @@ -650,6 +654,41 @@ void DatabaseWidget::openUrl() } } +void DatabaseWidget::downloadSelectedFavicons() +{ +#ifdef WITH_XC_NETWORKING + QList selectedEntries; + for (const auto& index : m_entryView->selectionModel()->selectedRows()) { + selectedEntries.append(m_entryView->entryFromIndex(index)); + } + + // Force download even if icon already exists + performIconDownloads(selectedEntries, true); +#endif +} + +void DatabaseWidget::downloadAllFavicons() +{ +#ifdef WITH_XC_NETWORKING + auto currentGroup = m_groupView->currentGroup(); + if (currentGroup) { + performIconDownloads(currentGroup->entries()); + } +#endif +} + +void DatabaseWidget::performIconDownloads(const QList& entries, bool force) +{ +#ifdef WITH_XC_NETWORKING + auto* iconDownloaderDialog = new IconDownloaderDialog(this); + connect(this, SIGNAL(databaseLockRequested()), iconDownloaderDialog, SLOT(close())); + iconDownloaderDialog->downloadFavicons(m_db, entries, force); +#else + Q_UNUSED(entries); + Q_UNUSED(force); +#endif +} + void DatabaseWidget::openUrlForEntry(Entry* entry) { Q_ASSERT(entry); @@ -1275,6 +1314,8 @@ bool DatabaseWidget::lock() return true; } + emit databaseLockRequested(); + clipboard()->clearCopiedText(); if (isEditWidgetModified()) { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 7da6b7a40d..aeb6a02e1b 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -25,12 +25,11 @@ #include #include "DatabaseOpenDialog.h" +#include "config-keepassx.h" #include "gui/MessageWidget.h" #include "gui/csvImport/CsvImportWizard.h" #include "gui/entry/EntryModel.h" -#include "config-keepassx.h" - class DatabaseOpenWidget; class KeePass1OpenWidget; class OpVaultOpenWidget; @@ -124,6 +123,7 @@ class DatabaseWidget : public QStackedWidget void databaseModified(); void databaseSaved(); void databaseUnlocked(); + void databaseLockRequested(); void databaseLocked(); // Emitted in replaceDatabase, may be caused by lock, reload, unlock, load. @@ -169,6 +169,8 @@ public slots: void setupTotp(); void performAutoType(); void openUrl(); + void downloadSelectedFavicons(); + void downloadAllFavicons(); void openUrlForEntry(Entry* entry); void createGroup(); void deleteGroup(); @@ -233,6 +235,7 @@ private slots: void setClipboardTextAndMinimize(const QString& text); void processAutoOpen(); bool confirmDeleteEntries(QList entries, bool permanent); + void performIconDownloads(const QList& entries, bool force = false); QSharedPointer m_db; diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index fadfb1a1c0..83bc0fc350 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -28,11 +28,8 @@ #include "core/Tools.h" #include "gui/IconModels.h" #include "gui/MessageBox.h" - #ifdef WITH_XC_NETWORKING -#include -#include -#include +#include "core/IconDownloader.h" #endif IconStruct::IconStruct() @@ -46,12 +43,11 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) , m_ui(new Ui::EditWidgetIcons()) , m_db(nullptr) , m_applyIconTo(ApplyIconToOptions::THIS_ONLY) -#ifdef WITH_XC_NETWORKING - , m_netMgr(new QNetworkAccessManager(this)) - , m_reply(nullptr) -#endif , m_defaultIconModel(new DefaultIconModel(this)) , m_customIconModel(new CustomIconModel(this)) +#ifdef WITH_XC_NETWORKING + , m_downloader(new IconDownloader()) +#endif { m_ui->setupUi(this); @@ -75,6 +71,11 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) this, SIGNAL(widgetUpdated())); connect(m_ui->customIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(widgetUpdated())); +#ifdef WITH_XC_NETWORKING + connect(m_downloader.data(), + SIGNAL(finished(const QString&, const QImage&)), + SLOT(iconReceived(const QString&, const QImage&))); +#endif // clang-format on m_ui->faviconButton->setVisible(false); @@ -177,7 +178,7 @@ QMenu* EditWidgetIcons::createApplyIconToMenu() void EditWidgetIcons::setUrl(const QString& url) { #ifdef WITH_XC_NETWORKING - m_url = QUrl(url); + m_url = url; m_ui->faviconButton->setVisible(!url.isEmpty()); #else Q_UNUSED(url); @@ -185,223 +186,95 @@ void EditWidgetIcons::setUrl(const QString& url) #endif } -#ifdef WITH_XC_NETWORKING -namespace -{ - // Try to get the 2nd level domain of the host part of a QUrl. For example, - // "foo.bar.example.com" would become "example.com", and "foo.bar.example.co.uk" - // would become "example.co.uk". - QString getSecondLevelDomain(const QUrl& url) - { - QString fqdn = url.host(); - fqdn.truncate(fqdn.length() - url.topLevelDomain().length()); - QStringList parts = fqdn.split('.'); - QString newdom = parts.takeLast() + url.topLevelDomain(); - return newdom; - } - - QUrl convertVariantToUrl(const QVariant& var) - { - QUrl url; - if (var.canConvert()) - url = var.toUrl(); - return url; - } - - QUrl getRedirectTarget(QNetworkReply* reply) - { - QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - QUrl url = convertVariantToUrl(var); - return url; - } -} // namespace -#endif - void EditWidgetIcons::downloadFavicon() { #ifdef WITH_XC_NETWORKING - m_ui->faviconButton->setDisabled(true); - - m_redirects = 0; - m_urlsToTry.clear(); - - QString fullyQualifiedDomain = m_url.host(); - - // Determine if host portion of URL is an IP address by resolving it and - // searching for a match with the returned address(es). - bool hostIsIp = false; - QList hostAddressess = QHostInfo::fromName(fullyQualifiedDomain).addresses(); - for (auto addr : hostAddressess) { - if (addr.toString() == fullyQualifiedDomain) { - hostIsIp = true; - } - } - - // Determine the second-level domain, if available - QString secondLevelDomain; - if (!hostIsIp) { - secondLevelDomain = getSecondLevelDomain(m_url); - } - - // Start with the "fallback" url (if enabled) to try to get the best favicon - if (config()->get("security/IconDownloadFallback", false).toBool()) { - QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); - fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); - m_urlsToTry.append(fallbackUrl); - - // Also try a direct pull of the second-level domain (if possible) - if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { - fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); - m_urlsToTry.append(fallbackUrl); - } - } - - // Add a direct pull of the website's own favicon.ico file - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico")); - - // Also try a direct pull of the second-level domain (if possible) - if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) { - m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico")); + if (!m_url.isEmpty()) { + m_downloader->setUrl(m_url); + m_downloader->download(); } - - // Use the first URL to start the download process - // If a favicon is not found, the next URL will be tried - startFetchFavicon(m_urlsToTry.takeFirst()); #endif } -void EditWidgetIcons::fetchReadyRead() +void EditWidgetIcons::iconReceived(const QString& url, const QImage& icon) { #ifdef WITH_XC_NETWORKING - m_bytesReceived += m_reply->readAll(); -#endif -} - -void EditWidgetIcons::fetchFinished() -{ -#ifdef WITH_XC_NETWORKING - QImage image; - bool fallbackEnabled = config()->get("security/IconDownloadFallback", false).toBool(); - bool error = (m_reply->error() != QNetworkReply::NoError); - QUrl redirectTarget = getRedirectTarget(m_reply); - - m_reply->deleteLater(); - m_reply = nullptr; - - if (!error) { - if (redirectTarget.isValid()) { - // Redirected, we need to follow it, or fall through if we have - // done too many redirects already. - if (m_redirects < 5) { - m_redirects++; - if (redirectTarget.isRelative()) - redirectTarget = m_fetchUrl.resolved(redirectTarget); - startFetchFavicon(redirectTarget); - return; - } - } else { - // No redirect, and we theoretically have some icon data now. - image.loadFromData(m_bytesReceived); - } - } - - if (!image.isNull()) { - if (!addCustomIcon(image)) { - emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); - } else if (!isVisible()) { - // Show confirmation message if triggered from Entry tab download button - emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive); + Q_UNUSED(url); + if (icon.isNull()) { + QString message(tr("Unable to fetch favicon.")); + if (!config()->get("security/IconDownloadFallback", false).toBool()) { + message.append("\n").append( + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security")); } - } else if (!m_urlsToTry.empty()) { - m_redirects = 0; - startFetchFavicon(m_urlsToTry.takeFirst()); + emit messageEditEntry(message, MessageWidget::Error); return; - } else { - if (!fallbackEnabled) { - emit messageEditEntry( - tr("Unable to fetch favicon.") + "\n" - + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"), - MessageWidget::Error); - } else { - emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); - } } - m_ui->faviconButton->setDisabled(false); + if (!addCustomIcon(icon)) { + emit messageEditEntry(tr("Existing icon selected."), MessageWidget::Information); + } +#else + Q_UNUSED(url); + Q_UNUSED(icon); #endif } void EditWidgetIcons::abortRequests() { #ifdef WITH_XC_NETWORKING - if (m_reply) { - m_reply->abort(); + if (m_downloader) { + m_downloader->abortDownload(); } #endif } -void EditWidgetIcons::startFetchFavicon(const QUrl& url) -{ -#ifdef WITH_XC_NETWORKING - m_bytesReceived.clear(); - - m_fetchUrl = url; - - QNetworkRequest request(url); - - m_reply = m_netMgr->get(request); - connect(m_reply, &QNetworkReply::finished, this, &EditWidgetIcons::fetchFinished); - connect(m_reply, &QIODevice::readyRead, this, &EditWidgetIcons::fetchReadyRead); -#else - Q_UNUSED(url); -#endif -} - void EditWidgetIcons::addCustomIconFromFile() { - if (m_db) { - QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files")); - - auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter); - if (!filenames.empty()) { - QStringList errornames; - int numexisting = 0; - for (const auto& filename : filenames) { - if (!filename.isEmpty()) { - auto icon = QImage(filename); - if (icon.isNull()) { - errornames << filename; - } else if (!addCustomIcon(icon)) { - // Icon already exists in database - ++numexisting; - } + if (!m_db) { + return; + } + + QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files")); + + auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter); + if (!filenames.empty()) { + QStringList errornames; + int numexisting = 0; + for (const auto& filename : filenames) { + if (!filename.isEmpty()) { + auto icon = QImage(filename); + if (icon.isNull()) { + errornames << filename; + } else if (!addCustomIcon(icon)) { + // Icon already exists in database + ++numexisting; } } + } - int numloaded = filenames.size() - errornames.size() - numexisting; - QString msg; + int numloaded = filenames.size() - errornames.size() - numexisting; + QString msg; - if (numloaded > 0) { - msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded); - } else { - msg = tr("No icons were loaded"); - } + if (numloaded > 0) { + msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded); + } else { + msg = tr("No icons were loaded"); + } - if (numexisting > 0) { - msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting); - } + if (numexisting > 0) { + msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting); + } - if (!errornames.empty()) { - // Show the first 8 icons that failed to load - errornames = errornames.mid(0, 8); - emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n" - + errornames.join("\n"), - MessageWidget::Error); - } else if (numloaded > 0) { - emit messageEditEntry(msg, MessageWidget::Positive); - } else { - emit messageEditEntry(msg, MessageWidget::Information); - } + if (!errornames.empty()) { + // Show the first 8 icons that failed to load + errornames = errornames.mid(0, 8); + emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n" + + errornames.join("\n"), + MessageWidget::Error); + } else if (numloaded > 0) { + emit messageEditEntry(msg, MessageWidget::Positive); + } else { + emit messageEditEntry(msg, MessageWidget::Information); } } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index e7c1b3a16d..2a95445f97 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -25,6 +25,7 @@ #include #include "config-keepassx.h" +#include "core/Entry.h" #include "core/Global.h" #include "gui/MessageWidget.h" @@ -32,8 +33,7 @@ class Database; class DefaultIconModel; class CustomIconModel; #ifdef WITH_XC_NETWORKING -class QNetworkAccessManager; -class QNetworkReply; +class IconDownloader; #endif namespace Ui @@ -87,9 +87,7 @@ public slots: private slots: void downloadFavicon(); - void startFetchFavicon(const QUrl& url); - void fetchFinished(); - void fetchReadyRead(); + void iconReceived(const QString& url, const QImage& icon); void addCustomIconFromFile(); bool addCustomIcon(const QImage& icon); void removeCustomIcon(); @@ -106,17 +104,12 @@ private slots: QSharedPointer m_db; QUuid m_currentUuid; ApplyIconToOptions m_applyIconTo; -#ifdef WITH_XC_NETWORKING - QUrl m_url; - QUrl m_fetchUrl; - QList m_urlsToTry; - QByteArray m_bytesReceived; - QNetworkAccessManager* m_netMgr; - QNetworkReply* m_reply; - int m_redirects; -#endif DefaultIconModel* const m_defaultIconModel; CustomIconModel* const m_customIconModel; +#ifdef WITH_XC_NETWORKING + QScopedPointer m_downloader; + QString m_url; +#endif Q_DISABLE_COPY(EditWidgetIcons) }; diff --git a/src/gui/IconDownloaderDialog.cpp b/src/gui/IconDownloaderDialog.cpp new file mode 100644 index 0000000000..ebe6980a2d --- /dev/null +++ b/src/gui/IconDownloaderDialog.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "IconDownloaderDialog.h" +#include "ui_IconDownloaderDialog.h" + +#include "core/AsyncTask.h" +#include "core/Config.h" +#include "core/Entry.h" +#include "core/Global.h" +#include "core/Group.h" +#include "core/IconDownloader.h" +#include "core/Metadata.h" +#include "core/Tools.h" +#include "gui/IconModels.h" +#ifdef Q_OS_MACOS +#include "gui/macutils/MacUtils.h" +#endif + +#include + +IconDownloaderDialog::IconDownloaderDialog(QWidget* parent) + : QDialog(parent) + , m_ui(new Ui::IconDownloaderDialog()) + , m_dataModel(new QStandardItemModel(this)) +{ + setWindowFlags(Qt::Window); + setAttribute(Qt::WA_DeleteOnClose); + + m_ui->setupUi(this); + showFallbackMessage(false); + + m_dataModel->clear(); + m_dataModel->setHorizontalHeaderLabels({tr("URL"), tr("Status")}); + + m_ui->tableView->setModel(m_dataModel); + m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(abortDownloads())); + connect(m_ui->closeButton, SIGNAL(clicked()), SLOT(close())); +} + +IconDownloaderDialog::~IconDownloaderDialog() +{ + abortDownloads(); +} + +void IconDownloaderDialog::downloadFavicons(const QSharedPointer& database, + const QList& entries, + bool force) +{ + m_db = database; + m_urlToEntries.clear(); + abortDownloads(); + for (const auto& e : entries) { + // Only consider entries with a valid URL and without a custom icon + auto webUrl = e->webUrl(); + if (!webUrl.isEmpty() && (force || e->iconUuid().isNull())) { + m_urlToEntries.insert(webUrl, e); + } + } + + if (m_urlToEntries.count() > 0) { +#ifdef Q_OS_MACOS + macUtils()->raiseOwnWindow(); + Tools::wait(100); +#endif + showFallbackMessage(false); + m_ui->progressLabel->setText(tr("Please wait, processing entry list...")); + open(); + QApplication::processEvents(); + + for (const auto& url : m_urlToEntries.uniqueKeys()) { + m_dataModel->appendRow(QList() + << new QStandardItem(url) << new QStandardItem(tr("Downloading..."))); + m_activeDownloaders.append(createDownloader(url)); + } + + // Setup the dialog + updateProgressBar(); + updateCancelButton(); + QApplication::processEvents(); + + // Start the downloads + for (auto downloader : m_activeDownloaders) { + downloader->download(); + } + } +} + +IconDownloader* IconDownloaderDialog::createDownloader(const QString& url) +{ + auto downloader = new IconDownloader(); + connect(downloader, + SIGNAL(finished(const QString&, const QImage&)), + this, + SLOT(downloadFinished(const QString&, const QImage&))); + + downloader->setUrl(url); + return downloader; +} + +void IconDownloaderDialog::downloadFinished(const QString& url, const QImage& icon) +{ + // Prevent re-entrance from multiple calls finishing at the same time + QMutexLocker locker(&m_mutex); + + // Cleanup the icon downloader that sent this signal + auto downloader = qobject_cast(sender()); + if (downloader) { + downloader->deleteLater(); + m_activeDownloaders.removeAll(downloader); + } + + updateProgressBar(); + updateCancelButton(); + + if (m_db && !icon.isNull()) { + // Don't add an icon larger than 128x128, but retain original size if smaller + auto scaledicon = icon; + if (icon.width() > 128 || icon.height() > 128) { + scaledicon = icon.scaled(128, 128); + } + + QUuid uuid = m_db->metadata()->findCustomIcon(scaledicon); + if (uuid.isNull()) { + uuid = QUuid::createUuid(); + m_db->metadata()->addCustomIcon(uuid, scaledicon); + updateTable(url, tr("Ok")); + } else { + updateTable(url, tr("Already Exists")); + } + + // Set the icon on all the entries associated with this url + for (const auto entry : m_urlToEntries.values(url)) { + entry->setIcon(uuid); + } + } else { + showFallbackMessage(true); + updateTable(url, tr("Download Failed")); + return; + } +} + +void IconDownloaderDialog::showFallbackMessage(bool state) +{ + // Show fallback message if the option is not active + bool show = state && !config()->get("security/IconDownloadFallback").toBool(); + m_ui->fallbackLabel->setVisible(show); +} + +void IconDownloaderDialog::updateProgressBar() +{ + int total = m_urlToEntries.uniqueKeys().count(); + int value = total - m_activeDownloaders.count(); + m_ui->progressBar->setValue(value); + m_ui->progressBar->setMaximum(total); + m_ui->progressLabel->setText( + tr("Downloading favicons (%1/%2)...").arg(QString::number(value), QString::number(total))); +} + +void IconDownloaderDialog::updateCancelButton() +{ + m_ui->cancelButton->setEnabled(!m_activeDownloaders.isEmpty()); +} + +void IconDownloaderDialog::updateTable(const QString& url, const QString& message) +{ + for (int i = 0; i < m_dataModel->rowCount(); ++i) { + if (m_dataModel->item(i, 0)->text() == url) { + m_dataModel->item(i, 1)->setText(message); + } + } +} + +void IconDownloaderDialog::abortDownloads() +{ + for (auto* downloader : m_activeDownloaders) { + delete downloader; + } + m_activeDownloaders.clear(); + updateProgressBar(); + updateCancelButton(); +} diff --git a/src/gui/IconDownloaderDialog.h b/src/gui/IconDownloaderDialog.h new file mode 100644 index 0000000000..955e85a98a --- /dev/null +++ b/src/gui/IconDownloaderDialog.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_ICONDOWNLOADERDIALOG_H +#define KEEPASSX_ICONDOWNLOADERDIALOG_H + +#include +#include +#include + +#include "gui/MessageWidget.h" + +class Database; +class Entry; +class CustomIconModel; +class IconDownloader; + +namespace Ui +{ + class IconDownloaderDialog; +} + +class IconDownloaderDialog : public QDialog +{ + Q_OBJECT + +public: + explicit IconDownloaderDialog(QWidget* parent = nullptr); + ~IconDownloaderDialog() override; + + void downloadFavicons(const QSharedPointer& database, const QList& entries, bool force = false); + +private slots: + void downloadFinished(const QString& url, const QImage& icon); + void abortDownloads(); + +private: + IconDownloader* createDownloader(const QString& url); + + void showFallbackMessage(bool state); + void updateTable(const QString& url, const QString& message); + void updateProgressBar(); + void updateCancelButton(); + + QScopedPointer m_ui; + QStandardItemModel* m_dataModel; + QSharedPointer m_db; + QMultiMap m_urlToEntries; + QList m_activeDownloaders; + QMutex m_mutex; + + Q_DISABLE_COPY(IconDownloaderDialog) +}; + +#endif // KEEPASSX_ICONDOWNLOADERDIALOG_H diff --git a/src/gui/IconDownloaderDialog.ui b/src/gui/IconDownloaderDialog.ui new file mode 100644 index 0000000000..a657f7acbe --- /dev/null +++ b/src/gui/IconDownloaderDialog.ui @@ -0,0 +1,154 @@ + + + IconDownloaderDialog + + + + 0 + 0 + 453 + 339 + + + + + 0 + 0 + + + + Download Favicons + + + + + + + 75 + true + + + + Downloading favicon 0/0... + + + + + + + QLayout::SetDefaultConstraint + + + + + 0 + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + Cancel + + + + + + + + + + 75 + false + true + + + + Having trouble downloading icons? +You can enable the DuckDuckGo website icon service in the security section of the application settings. + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideNone + + + true + + + false + + + 20 + + + 20 + + + true + + + true + + + false + + + + + + + Close + + + true + + + + + + + + + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c0a4a04004..ad00a66211 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -246,6 +246,7 @@ MainWindow::MainWindow() m_ui->actionEntryDelete->setShortcut(Qt::Key_Delete); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T); + m_ui->actionEntryDownloadIcon->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D); m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T); m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B); m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C); @@ -261,6 +262,7 @@ MainWindow::MainWindow() m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true); m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true); m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true); m_ui->actionEntryCopyTotp->setShortcutVisibleInContextMenu(true); m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true); m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true); @@ -304,11 +306,13 @@ MainWindow::MainWindow() m_ui->actionEntryCopyUsername->setIcon(filePath()->icon("actions", "username-copy")); m_ui->actionEntryCopyPassword->setIcon(filePath()->icon("actions", "password-copy")); m_ui->actionEntryCopyURL->setIcon(filePath()->icon("actions", "url-copy")); + m_ui->actionEntryDownloadIcon->setIcon(filePath()->icon("actions", "favicon-download")); m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new")); m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit")); m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete")); m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash")); + m_ui->actionGroupDownloadFavicons->setIcon(filePath()->icon("actions", "favicon-download")); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); m_ui->actionPasswordGenerator->setIcon(filePath()->icon("actions", "password-generator")); @@ -376,6 +380,7 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes())); m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType())); m_actionMultiplexer.connect(m_ui->actionEntryOpenUrl, SIGNAL(triggered()), SLOT(openUrl())); + m_actionMultiplexer.connect(m_ui->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons())); m_actionMultiplexer.connect(m_ui->actionGroupNew, SIGNAL(triggered()), SLOT(createGroup())); m_actionMultiplexer.connect(m_ui->actionGroupEdit, SIGNAL(triggered()), SLOT(switchToGroupEdit())); @@ -383,6 +388,7 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()), SLOT(emptyRecycleBin())); m_actionMultiplexer.connect(m_ui->actionGroupSortAsc, SIGNAL(triggered()), SLOT(sortGroupsAsc())); m_actionMultiplexer.connect(m_ui->actionGroupSortDesc, SIGNAL(triggered()), SLOT(sortGroupsDesc())); + m_actionMultiplexer.connect(m_ui->actionGroupDownloadFavicons, SIGNAL(triggered()), SLOT(downloadAllFavicons())); connect(m_ui->actionSettings, SIGNAL(toggled(bool)), SLOT(switchToSettings(bool))); connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool))); @@ -419,6 +425,11 @@ MainWindow::MainWindow() m_ui->actionCheckForUpdates->setVisible(false); #endif +#ifndef WITH_XC_NETWORKING + m_ui->actionGroupDownloadFavicons->setVisible(false); + m_ui->actionEntryDownloadIcon->setVisible(false); +#endif + // clang-format off connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), @@ -577,6 +588,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && hasFocus; bool groupSelected = dbWidget->isGroupSelected(); bool currentGroupHasChildren = dbWidget->currentGroup()->hasChildren(); + bool currentGroupHasEntries = !dbWidget->currentGroup()->entries().isEmpty(); bool recycleBinSelected = dbWidget->isRecycleBinSelected(); m_ui->actionEntryNew->setEnabled(true); @@ -596,6 +608,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected); m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryDownloadIcon->setEnabled((entriesSelected && !singleEntrySelected) + || (singleEntrySelected && dbWidget->currentEntryHasUrl())); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); @@ -603,6 +617,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupSortDesc->setEnabled(groupSelected && currentGroupHasChildren); m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected); m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected); + m_ui->actionGroupDownloadFavicons->setVisible(!recycleBinSelected); + m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries + && !recycleBinSelected); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave()); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index de2867406a..807cdcf6e4 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -283,6 +283,8 @@ + + @@ -294,6 +296,8 @@ + + @@ -467,6 +471,14 @@ &Delete group + + + false + + + Downlo&ad all favicons + + false @@ -570,6 +582,11 @@ Perform &Auto-Type + + + Download favicon + + false diff --git a/src/updatecheck/UpdateChecker.cpp b/src/updatecheck/UpdateChecker.cpp index 1453129074..c36879707d 100644 --- a/src/updatecheck/UpdateChecker.cpp +++ b/src/updatecheck/UpdateChecker.cpp @@ -16,18 +16,21 @@ */ #include "UpdateChecker.h" + #include "config-keepassx.h" #include "core/Clock.h" #include "core/Config.h" +#include "core/NetworkManager.h" + +#include +#include #include -#include -#include +#include UpdateChecker* UpdateChecker::m_instance(nullptr); UpdateChecker::UpdateChecker(QObject* parent) : QObject(parent) - , m_netMgr(new QNetworkAccessManager(this)) , m_reply(nullptr) , m_isManuallyRequested(false) { @@ -56,7 +59,7 @@ void UpdateChecker::checkForUpdates(bool manuallyRequested) QNetworkRequest request(apiUrl); request.setRawHeader("Accept", "application/json"); - m_reply = m_netMgr->get(request); + m_reply = getNetMgr()->get(request); connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished); connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead); diff --git a/src/updatecheck/UpdateChecker.h b/src/updatecheck/UpdateChecker.h index 64430bda3b..9e804b2740 100644 --- a/src/updatecheck/UpdateChecker.h +++ b/src/updatecheck/UpdateChecker.h @@ -20,7 +20,6 @@ #include #include -class QNetworkAccessManager; class QNetworkReply; class UpdateChecker : public QObject @@ -42,7 +41,6 @@ private slots: void fetchReadyRead(); private: - QNetworkAccessManager* m_netMgr; QNetworkReply* m_reply; QByteArray m_bytesReceived; bool m_isManuallyRequested;