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