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 | |
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(d: QDate::currentDate()) > 7; // arbitrary value (one week) |
41 | } |
42 | |
43 | class KIO::FavIconRequestJobPrivate |
44 | { |
45 | public: |
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 | |
62 | FavIconRequestJob::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 | |
69 | FavIconRequestJob::~FavIconRequestJob() = default; |
70 | |
71 | void FavIconRequestJob::setIconUrl(const QUrl &iconUrl) |
72 | { |
73 | d->m_iconUrl = iconUrl; |
74 | } |
75 | |
76 | QString FavIconRequestJob::iconFile() const |
77 | { |
78 | return d->m_iconFile; |
79 | } |
80 | |
81 | QUrl FavIconRequestJob::hostUrl() const |
82 | { |
83 | return d->m_hostUrl; |
84 | } |
85 | |
86 | void 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 | |
130 | void 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 | |
183 | void 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 | |