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
28 using namespace KIO;
29
isIconOld(const QString & icon)30 static 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(QDate::currentDate()) > 7; // arbitrary value (one week)
41 }
42
43 class KIO::FavIconRequestJobPrivate
44 {
45 public:
FavIconRequestJobPrivate(const QUrl & hostUrl,KIO::LoadType reload)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
FavIconRequestJob(const QUrl & hostUrl,LoadType reload,QObject * parent)62 FavIconRequestJob::FavIconRequestJob(const QUrl &hostUrl, LoadType reload, QObject *parent)
63 : KCompositeJob(parent)
64 , d(new FavIconRequestJobPrivate(hostUrl, reload))
65 {
66 QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection);
67 }
68
~FavIconRequestJob()69 FavIconRequestJob::~FavIconRequestJob()
70 {
71 delete d;
72 }
73
setIconUrl(const QUrl & iconUrl)74 void FavIconRequestJob::setIconUrl(const QUrl &iconUrl)
75 {
76 d->m_iconUrl = iconUrl;
77 }
78
iconFile() const79 QString FavIconRequestJob::iconFile() const
80 {
81 return d->m_iconFile;
82 }
83
hostUrl() const84 QUrl FavIconRequestJob::hostUrl() const
85 {
86 return d->m_hostUrl;
87 }
88
doStart()89 void FavIconRequestJob::doStart()
90 {
91 KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
92 QUrl iconUrl = d->m_iconUrl;
93 const bool isNewIconUrl = !iconUrl.isEmpty();
94 if (isNewIconUrl) {
95 cache->setIconForUrl(d->m_hostUrl, d->m_iconUrl);
96 } else {
97 iconUrl = cache->iconUrlForUrl(d->m_hostUrl);
98 }
99 if (d->m_reload == NoReload) {
100 const QString iconFile = cache->cachePathForIconUrl(iconUrl);
101 if (!isIconOld(iconFile)) {
102 qCDebug(FAVICONS_LOG) << "existing icon not old, reload not requested -> doing nothing";
103 d->m_iconFile = iconFile;
104 emitResult();
105 return;
106 }
107
108 if (cache->isFailedDownload(iconUrl)) {
109 qCDebug(FAVICONS_LOG) << iconUrl << "already in failedDownloads, emitting error";
110 setError(KIO::ERR_DOES_NOT_EXIST);
111 setErrorText(i18n("No favicon found for %1", d->m_hostUrl.host()));
112 emitResult();
113 return;
114 }
115 }
116
117 qCDebug(FAVICONS_LOG) << "downloading" << iconUrl;
118 KIO::TransferJob *job = KIO::get(iconUrl, d->m_reload, KIO::HideProgressInfo);
119 QMap<QString, QString> metaData;
120 metaData.insert(QStringLiteral("ssl_no_client_cert"), QStringLiteral("true"));
121 metaData.insert(QStringLiteral("ssl_no_ui"), QStringLiteral("true"));
122 metaData.insert(QStringLiteral("UseCache"), QStringLiteral("false"));
123 metaData.insert(QStringLiteral("cookies"), QStringLiteral("none"));
124 metaData.insert(QStringLiteral("no-www-auth"), QStringLiteral("true"));
125 metaData.insert(QStringLiteral("errorPage"), QStringLiteral("false"));
126 job->addMetaData(metaData);
127 QObject::connect(job, &KIO::TransferJob::data, this, [this](KIO::Job *job, const QByteArray &data) {
128 d->slotData(job, data);
129 });
130 addSubjob(job);
131 }
132
slotResult(KJob * job)133 void FavIconRequestJob::slotResult(KJob *job)
134 {
135 KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
136 const QUrl &iconUrl = tjob->url();
137 KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
138 if (!job->error()) {
139 QBuffer buffer(&d->m_iconData);
140 buffer.open(QIODevice::ReadOnly);
141 QImageReader ir(&buffer);
142 QSize desired(16, 16);
143 if (ir.canRead()) {
144 while (ir.imageCount() > 1 && ir.currentImageRect() != QRect(0, 0, desired.width(), desired.height())) {
145 if (!ir.jumpToNextImage()) {
146 break;
147 }
148 }
149 ir.setScaledSize(desired);
150 const QImage img = ir.read();
151 if (!img.isNull()) {
152 cache->ensureCacheExists();
153 const QString localPath = cache->cachePathForIconUrl(iconUrl);
154 qCDebug(FAVICONS_LOG) << "Saving image to" << localPath;
155 QSaveFile saveFile(localPath);
156 if (saveFile.open(QIODevice::WriteOnly) && img.save(&saveFile, "PNG") && saveFile.commit()) {
157 d->m_iconFile = localPath;
158 } else {
159 setError(KIO::ERR_CANNOT_WRITE);
160 setErrorText(i18n("Error saving image to %1", localPath));
161 }
162 } else {
163 qCDebug(FAVICONS_LOG) << "QImageReader read() returned a null image";
164 }
165 } else {
166 qCDebug(FAVICONS_LOG) << "QImageReader canRead returned false";
167 }
168 } else if (job->error() == KJob::KilledJobError) { // we killed it in slotData
169 setError(KIO::ERR_SLAVE_DEFINED);
170 setErrorText(i18n("Icon file too big, download aborted"));
171 } else {
172 setError(job->error());
173 setErrorText(job->errorString()); // not errorText(), because "this" is a KJob, with no errorString building logic
174 }
175 d->m_iconData.clear(); // release memory
176 if (d->m_iconFile.isEmpty()) {
177 qCDebug(FAVICONS_LOG) << "adding" << iconUrl << "to failed downloads due to error:" << errorString();
178 cache->addFailedDownload(iconUrl);
179 } else {
180 cache->removeFailedDownload(iconUrl);
181 }
182 KCompositeJob::removeSubjob(job);
183 emitResult();
184 }
185
slotData(Job * job,const QByteArray & data)186 void FavIconRequestJobPrivate::slotData(Job *job, const QByteArray &data)
187 {
188 KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
189 unsigned int oldSize = m_iconData.size();
190 // Size limit. Stop downloading if the file is huge.
191 // Testcase (as of june 2008, at least): http://planet-soc.com/favicon.ico, 136K and strange format.
192 // Another case: sites which redirect from "/favicon.ico" to "/" and return the main page.
193 if (oldSize > 0x10000) { // 65K
194 qCDebug(FAVICONS_LOG) << "Favicon too big, aborting download of" << tjob->url();
195 const QUrl iconUrl = tjob->url();
196 KIO::FavIconsCache::instance()->addFailedDownload(iconUrl);
197 tjob->kill(KJob::EmitResult);
198 } else {
199 m_iconData.resize(oldSize + data.size());
200 memcpy(m_iconData.data() + oldSize, data.data(), data.size());
201 }
202 }
203
204 #include "moc_faviconrequestjob.cpp"
205