Skip to content

Commit

Permalink
Download all favicons
Browse files Browse the repository at this point in the history
  • Loading branch information
varjolintu committed May 21, 2019
1 parent 7ce6f9d commit 3935a8f
Show file tree
Hide file tree
Showing 12 changed files with 783 additions and 179 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ set(keepassx_SOURCES
gui/DatabaseTabWidget.cpp
gui/DatabaseWidget.cpp
gui/DatabaseWidgetStateSync.cpp
gui/DownloadIcon.cpp
gui/EntryPreviewWidget.cpp
gui/DialogyWidget.cpp
gui/DragTabBar.cpp
Expand All @@ -96,6 +97,7 @@ set(keepassx_SOURCES
gui/EditWidgetProperties.cpp
gui/FileDialog.cpp
gui/Font.cpp
gui/IconDownloaderDialog.cpp
gui/IconModels.cpp
gui/KeePass1OpenWidget.cpp
gui/KMessageWidget.cpp
Expand Down
62 changes: 62 additions & 0 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <QSplitter>

#include "autotype/AutoType.h"
#include "core/AsyncTask.h"
#include "core/Config.h"
#include "core/Database.h"
#include "core/EntrySearcher.h"
Expand Down Expand Up @@ -61,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 <sys/vfs.h>
#endif
Expand Down Expand Up @@ -89,6 +94,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
, m_saveAttempts(0)
, m_fileWatcher(new DelayingFileWatcher(this))
, m_iconDownloaderState(IconDownloaderState::Idle)
{
m_messageWidget->setHidden(true);

Expand Down Expand Up @@ -650,6 +656,52 @@ void DatabaseWidget::openUrl()
}
}

void DatabaseWidget::downloadFavicon()
{
#ifdef WITH_XC_NETWORKING
auto* iconDownloader = new IconDownloaderDialog(this);
connect(iconDownloader, SIGNAL(entryUpdated()), SIGNAL(databaseModified()));
connect(iconDownloader,
SIGNAL(messageEditEntry(QString,MessageWidget::MessageType)),
SLOT(showMessage(QString,MessageWidget::MessageType)));

Entry* currentEntry = m_entryView->currentEntry();
if (currentEntry) {
iconDownloader->downloadFavicon(m_db, currentEntry);
}
#endif
}

void DatabaseWidget::downloadAllFavicons()
{
#ifdef WITH_XC_NETWORKING
if (m_iconDownloaderState == IconDownloaderState::Downloading) {
return;
}
m_iconDownloaderState = IconDownloaderState::Downloading;

auto* iconDownloader = new IconDownloaderDialog(this);
connect(iconDownloader, SIGNAL(entryUpdated()), SIGNAL(databaseModified()));
connect(iconDownloader,
SIGNAL(messageEditEntry(QString,MessageWidget::MessageType)),
SLOT(showMessage(QString,MessageWidget::MessageType)));

Group* currentGroup = m_groupView->currentGroup();
Q_ASSERT(currentGroup);
if (currentGroup) {
auto entries = currentGroup->entries();
iconDownloader->raiseWindow();
iconDownloader->show();
iconDownloader->activateWindow();
iconDownloader->raise();
auto result = AsyncTask::runAndWaitForFuture(
[&]() { return iconDownloader->downloadAllFavicons(m_db, entries); });
Q_UNUSED(result);
}
m_iconDownloaderState = IconDownloaderState::Idle;
#endif
}

void DatabaseWidget::openUrlForEntry(Entry* entry)
{
Q_ASSERT(entry);
Expand Down Expand Up @@ -1489,6 +1541,16 @@ bool DatabaseWidget::currentEntryHasTotp()
return currentEntry->hasTotp();
}

bool DatabaseWidget::currentEntryHasIconSet()
{
Entry* currentEntry = m_entryView->currentEntry();
Q_ASSERT(currentEntry);
if (!currentEntry) {
return false;
}
return !currentEntry->iconUuid().isNull();
}

bool DatabaseWidget::currentEntryHasNotes()
{
Entry* currentEntry = m_entryView->currentEntry();
Expand Down
12 changes: 11 additions & 1 deletion src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include "gui/MessageWidget.h"
#include "gui/csvImport/CsvImportWizard.h"
#include "gui/entry/EntryModel.h"

#include "config-keepassx.h"

class DatabaseOpenWidget;
Expand Down Expand Up @@ -107,6 +106,7 @@ class DatabaseWidget : public QStackedWidget
bool currentEntryHasUrl();
bool currentEntryHasNotes();
bool currentEntryHasTotp();
bool currentEntryHasIconSet();

void blockAutoReload(bool block = true);

Expand Down Expand Up @@ -164,6 +164,8 @@ public slots:
void setupTotp();
void performAutoType();
void openUrl();
void downloadFavicon();
void downloadAllFavicons();
void openUrlForEntry(Entry* entry);
void createGroup();
void deleteGroup();
Expand Down Expand Up @@ -221,6 +223,12 @@ private slots:
void restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& EntryUuid);

private:
enum IconDownloaderState
{
Idle,
Downloading
};

int addChildWidget(QWidget* w);
void setClipboardTextAndMinimize(const QString& text);
void setIconFromParent();
Expand Down Expand Up @@ -263,6 +271,8 @@ private slots:
// Autoreload
QPointer<DelayingFileWatcher> m_fileWatcher;
bool m_blockAutoSave;

IconDownloaderState m_iconDownloaderState;
};

#endif // KEEPASSX_DATABASEWIDGET_H
217 changes: 217 additions & 0 deletions src/gui/DownloadIcon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2019 KeePassXC Team <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/

#include "DownloadIcon.h"
#include "core/Config.h"

#ifdef WITH_XC_NETWORKING
#include <QHostInfo>
#include <QNetworkAccessManager>
#include <QtNetwork>
#endif

DownloadIcon::DownloadIcon(QObject* parent)
: QObject(parent)
#ifdef WITH_XC_NETWORKING
, m_netMgr(new QNetworkAccessManager(this))
, m_reply(nullptr)
#endif
{
}

DownloadIcon::~DownloadIcon()
{
}

#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<QUrl>())
url = var.toUrl();
return url;
}

QUrl getRedirectTarget(QNetworkReply* reply)
{
QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
QUrl url = convertVariantToUrl(var);
return url;
}
} // namespace
#endif

void DownloadIcon::downloadFavicon(Entry* entry)
{
#ifdef WITH_XC_NETWORKING
m_entry = entry;
m_url = entry->url();
m_redirects = 0;
m_urlsToTry.clear();

if (!m_url.scheme().startsWith("http")) {
m_url.setUrl(QString("https://%1").arg(m_url.toString()));
}

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<QHostAddress> 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"));
}

// 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 DownloadIcon::fetchReadyRead()
{
#ifdef WITH_XC_NETWORKING
m_bytesReceived += m_reply->readAll();
#endif
}

void DownloadIcon::fetchFinished()
{
#ifdef WITH_XC_NETWORKING
QImage image;
bool fallbackEnabled = config()->get("security/IconDownloadFallback", false).toBool();
bool error = (m_reply->error() != QNetworkReply::NoError);
if (m_reply->error() == QNetworkReply::HostNotFoundError || m_reply->error() == QNetworkReply::TimeoutError) {
emit iconReceived(image, m_entry);
return;
}
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()) {
emit iconReceived(image, m_entry);
return;
} else if (!m_urlsToTry.empty()) {
m_redirects = 0;
startFetchFavicon(m_urlsToTry.takeFirst());
return;
} else {
if (!fallbackEnabled) {
emit fallbackNotEnabled();
} else {
emit iconError();
}
}
emit iconReceived(image, m_entry);
#endif
}

void DownloadIcon::fetchErrored()
{
#ifdef WITH_XC_NETWORKING
emit iconError();
emit transferDone();
#endif
}

void DownloadIcon::abortRequest()
{
#ifdef WITH_XC_NETWORKING
if (m_reply) {
m_reply->abort();
}
#endif
}

void DownloadIcon::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, &DownloadIcon::fetchFinished);
connect(m_reply, &QIODevice::readyRead, this, &DownloadIcon::fetchReadyRead);
#else
Q_UNUSED(url);
#endif
}
Loading

0 comments on commit 3935a8f

Please sign in to comment.