1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "faviconrequestjob.h"
10#include <faviconscache_p.h>
11
12#include "favicons_debug.h"
13
14#include <KConfig>
15#include <KIO/TransferJob>
16#include <KLocalizedString>
17
18#include <QBuffer>
19#include <QCache>
20#include <QDate>
21#include <QFileInfo>
22#include <QImage>
23#include <QImageReader>
24#include <QSaveFile>
25#include <QStandardPaths>
26#include <QUrl>
27
28using namespace KIO;
29
30static bool isIconOld(const QString &icon)
31{
32 const QFileInfo info(icon);
33 if (!info.exists()) {
34 qCDebug(FAVICONS_LOG) << "isIconOld" << icon << "yes, no such file";
35 return true; // Trigger a new download on error
36 }
37 const QDate date = info.lastModified().date();
38
39 qCDebug(FAVICONS_LOG) << "isIconOld" << icon << "?";
40 return date.daysTo(d: QDate::currentDate()) > 7; // arbitrary value (one week)
41}
42
43class KIO::FavIconRequestJobPrivate
44{
45public:
46 FavIconRequestJobPrivate(const QUrl &hostUrl, KIO::LoadType reload)
47 : m_hostUrl(hostUrl)
48 , m_reload(reload)
49 {
50 }
51
52 // slots
53 void slotData(KIO::Job *job, const QByteArray &data);
54
55 QUrl m_hostUrl;
56 QUrl m_iconUrl;
57 QString m_iconFile;
58 QByteArray m_iconData;
59 KIO::LoadType m_reload;
60};
61
62FavIconRequestJob::FavIconRequestJob(const QUrl &hostUrl, LoadType reload, QObject *parent)
63 : KCompositeJob(parent)
64 , d(new FavIconRequestJobPrivate(hostUrl, reload))
65{
66 QMetaObject::invokeMethod(object: this, function: &FavIconRequestJob::doStart, type: Qt::QueuedConnection);
67}
68
69FavIconRequestJob::~FavIconRequestJob() = default;
70
71void FavIconRequestJob::setIconUrl(const QUrl &iconUrl)
72{
73 d->m_iconUrl = iconUrl;
74}
75
76QString FavIconRequestJob::iconFile() const
77{
78 return d->m_iconFile;
79}
80
81QUrl FavIconRequestJob::hostUrl() const
82{
83 return d->m_hostUrl;
84}
85
86void FavIconRequestJob::doStart()
87{
88 KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
89 QUrl iconUrl = d->m_iconUrl;
90 const bool isNewIconUrl = !iconUrl.isEmpty();
91 if (isNewIconUrl) {
92 cache->setIconForUrl(url: d->m_hostUrl, iconUrl: d->m_iconUrl);
93 } else {
94 iconUrl = cache->iconUrlForUrl(url: d->m_hostUrl);
95 }
96 if (d->m_reload == NoReload) {
97 const QString iconFile = cache->cachePathForIconUrl(iconUrl);
98 if (!isIconOld(icon: iconFile)) {
99 qCDebug(FAVICONS_LOG) << "existing icon not old, reload not requested -> doing nothing";
100 d->m_iconFile = iconFile;
101 emitResult();
102 return;
103 }
104
105 if (cache->isFailedDownload(url: iconUrl)) {
106 qCDebug(FAVICONS_LOG) << iconUrl << "already in failedDownloads, emitting error";
107 setError(KIO::ERR_DOES_NOT_EXIST);
108 setErrorText(i18n("No favicon found for %1", d->m_hostUrl.host()));
109 emitResult();
110 return;
111 }
112 }
113
114 qCDebug(FAVICONS_LOG) << "downloading" << iconUrl;
115 KIO::TransferJob *job = KIO::get(url: iconUrl, reload: d->m_reload, flags: KIO::HideProgressInfo);
116 QMap<QString, QString> metaData;
117 metaData.insert(QStringLiteral("ssl_no_client_cert"), QStringLiteral("true"));
118 metaData.insert(QStringLiteral("ssl_no_ui"), QStringLiteral("true"));
119 metaData.insert(QStringLiteral("UseCache"), QStringLiteral("false"));
120 metaData.insert(QStringLiteral("cookies"), QStringLiteral("none"));
121 metaData.insert(QStringLiteral("no-www-auth"), QStringLiteral("true"));
122 metaData.insert(QStringLiteral("errorPage"), QStringLiteral("false"));
123 job->addMetaData(values: metaData);
124 QObject::connect(sender: job, signal: &KIO::TransferJob::data, context: this, slot: [this](KIO::Job *job, const QByteArray &data) {
125 d->slotData(job, data);
126 });
127 addSubjob(job);
128}
129
130void FavIconRequestJob::slotResult(KJob *job)
131{
132 KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
133 const QUrl &iconUrl = tjob->url();
134 KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
135 if (!job->error()) {
136 QBuffer buffer(&d->m_iconData);
137 buffer.open(openMode: QIODevice::ReadOnly);
138 QImageReader ir(&buffer);
139 QSize desired(16, 16);
140 if (ir.canRead()) {
141 while (ir.imageCount() > 1 && ir.currentImageRect() != QRect(0, 0, desired.width(), desired.height())) {
142 if (!ir.jumpToNextImage()) {
143 break;
144 }
145 }
146 ir.setScaledSize(desired);
147 const QImage img = ir.read();
148 if (!img.isNull()) {
149 cache->ensureCacheExists();
150 const QString localPath = cache->cachePathForIconUrl(iconUrl);
151 qCDebug(FAVICONS_LOG) << "Saving image to" << localPath;
152 QSaveFile saveFile(localPath);
153 if (saveFile.open(flags: QIODevice::WriteOnly) && img.save(device: &saveFile, format: "PNG") && saveFile.commit()) {
154 d->m_iconFile = localPath;
155 } else {
156 setError(KIO::ERR_CANNOT_WRITE);
157 setErrorText(i18n("Error saving image to %1", localPath));
158 }
159 } else {
160 qCDebug(FAVICONS_LOG) << "QImageReader read() returned a null image";
161 }
162 } else {
163 qCDebug(FAVICONS_LOG) << "QImageReader canRead returned false";
164 }
165 } else if (job->error() == KJob::KilledJobError) { // we killed it in slotData
166 setError(KIO::ERR_WORKER_DEFINED);
167 setErrorText(i18n("Icon file too big, download aborted"));
168 } else {
169 setError(job->error());
170 setErrorText(job->errorString()); // not errorText(), because "this" is a KJob, with no errorString building logic
171 }
172 d->m_iconData.clear(); // release memory
173 if (d->m_iconFile.isEmpty()) {
174 qCDebug(FAVICONS_LOG) << "adding" << iconUrl << "to failed downloads due to error:" << errorString();
175 cache->addFailedDownload(url: iconUrl);
176 } else {
177 cache->removeFailedDownload(url: iconUrl);
178 }
179 KCompositeJob::removeSubjob(job);
180 emitResult();
181}
182
183void FavIconRequestJobPrivate::slotData(Job *job, const QByteArray &data)
184{
185 KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
186 unsigned int oldSize = m_iconData.size();
187 // Size limit. Stop downloading if the file is huge.
188 // Testcase (as of june 2008, at least): http://planet-soc.com/favicon.ico, 136K and strange format.
189 // Another case: sites which redirect from "/favicon.ico" to "/" and return the main page.
190 if (oldSize > 0x10000) { // 65K
191 qCDebug(FAVICONS_LOG) << "Favicon too big, aborting download of" << tjob->url();
192 const QUrl iconUrl = tjob->url();
193 KIO::FavIconsCache::instance()->addFailedDownload(url: iconUrl);
194 tjob->kill(verbosity: KJob::EmitResult);
195 } else {
196 m_iconData.resize(size: oldSize + data.size());
197 memcpy(dest: m_iconData.data() + oldSize, src: data.data(), n: data.size());
198 }
199}
200
201#include "moc_faviconrequestjob.cpp"
202

source code of kio/src/gui/faviconrequestjob.cpp