File indexing completed on 2024-04-28 07:44:13

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "faviconrequestjob.h"
0010 #include <faviconscache_p.h>
0011 
0012 #include "favicons_debug.h"
0013 
0014 #include <KConfig>
0015 #include <KIO/TransferJob>
0016 #include <KLocalizedString>
0017 
0018 #include <QBuffer>
0019 #include <QCache>
0020 #include <QDate>
0021 #include <QFileInfo>
0022 #include <QImage>
0023 #include <QImageReader>
0024 #include <QSaveFile>
0025 #include <QStandardPaths>
0026 #include <QUrl>
0027 
0028 using namespace KIO;
0029 
0030 static bool isIconOld(const QString &icon)
0031 {
0032     const QFileInfo info(icon);
0033     if (!info.exists()) {
0034         qCDebug(FAVICONS_LOG) << "isIconOld" << icon << "yes, no such file";
0035         return true; // Trigger a new download on error
0036     }
0037     const QDate date = info.lastModified().date();
0038 
0039     qCDebug(FAVICONS_LOG) << "isIconOld" << icon << "?";
0040     return date.daysTo(QDate::currentDate()) > 7; // arbitrary value (one week)
0041 }
0042 
0043 class KIO::FavIconRequestJobPrivate
0044 {
0045 public:
0046     FavIconRequestJobPrivate(const QUrl &hostUrl, KIO::LoadType reload)
0047         : m_hostUrl(hostUrl)
0048         , m_reload(reload)
0049     {
0050     }
0051 
0052     // slots
0053     void slotData(KIO::Job *job, const QByteArray &data);
0054 
0055     QUrl m_hostUrl;
0056     QUrl m_iconUrl;
0057     QString m_iconFile;
0058     QByteArray m_iconData;
0059     KIO::LoadType m_reload;
0060 };
0061 
0062 FavIconRequestJob::FavIconRequestJob(const QUrl &hostUrl, LoadType reload, QObject *parent)
0063     : KCompositeJob(parent)
0064     , d(new FavIconRequestJobPrivate(hostUrl, reload))
0065 {
0066     QMetaObject::invokeMethod(this, &FavIconRequestJob::doStart, Qt::QueuedConnection);
0067 }
0068 
0069 FavIconRequestJob::~FavIconRequestJob() = default;
0070 
0071 void FavIconRequestJob::setIconUrl(const QUrl &iconUrl)
0072 {
0073     d->m_iconUrl = iconUrl;
0074 }
0075 
0076 QString FavIconRequestJob::iconFile() const
0077 {
0078     return d->m_iconFile;
0079 }
0080 
0081 QUrl FavIconRequestJob::hostUrl() const
0082 {
0083     return d->m_hostUrl;
0084 }
0085 
0086 void FavIconRequestJob::doStart()
0087 {
0088     KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
0089     QUrl iconUrl = d->m_iconUrl;
0090     const bool isNewIconUrl = !iconUrl.isEmpty();
0091     if (isNewIconUrl) {
0092         cache->setIconForUrl(d->m_hostUrl, d->m_iconUrl);
0093     } else {
0094         iconUrl = cache->iconUrlForUrl(d->m_hostUrl);
0095     }
0096     if (d->m_reload == NoReload) {
0097         const QString iconFile = cache->cachePathForIconUrl(iconUrl);
0098         if (!isIconOld(iconFile)) {
0099             qCDebug(FAVICONS_LOG) << "existing icon not old, reload not requested -> doing nothing";
0100             d->m_iconFile = iconFile;
0101             emitResult();
0102             return;
0103         }
0104 
0105         if (cache->isFailedDownload(iconUrl)) {
0106             qCDebug(FAVICONS_LOG) << iconUrl << "already in failedDownloads, emitting error";
0107             setError(KIO::ERR_DOES_NOT_EXIST);
0108             setErrorText(i18n("No favicon found for %1", d->m_hostUrl.host()));
0109             emitResult();
0110             return;
0111         }
0112     }
0113 
0114     qCDebug(FAVICONS_LOG) << "downloading" << iconUrl;
0115     KIO::TransferJob *job = KIO::get(iconUrl, d->m_reload, KIO::HideProgressInfo);
0116     QMap<QString, QString> metaData;
0117     metaData.insert(QStringLiteral("ssl_no_client_cert"), QStringLiteral("true"));
0118     metaData.insert(QStringLiteral("ssl_no_ui"), QStringLiteral("true"));
0119     metaData.insert(QStringLiteral("UseCache"), QStringLiteral("false"));
0120     metaData.insert(QStringLiteral("cookies"), QStringLiteral("none"));
0121     metaData.insert(QStringLiteral("no-www-auth"), QStringLiteral("true"));
0122     metaData.insert(QStringLiteral("errorPage"), QStringLiteral("false"));
0123     job->addMetaData(metaData);
0124     QObject::connect(job, &KIO::TransferJob::data, this, [this](KIO::Job *job, const QByteArray &data) {
0125         d->slotData(job, data);
0126     });
0127     addSubjob(job);
0128 }
0129 
0130 void FavIconRequestJob::slotResult(KJob *job)
0131 {
0132     KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
0133     const QUrl &iconUrl = tjob->url();
0134     KIO::FavIconsCache *cache = KIO::FavIconsCache::instance();
0135     if (!job->error()) {
0136         QBuffer buffer(&d->m_iconData);
0137         buffer.open(QIODevice::ReadOnly);
0138         QImageReader ir(&buffer);
0139         QSize desired(16, 16);
0140         if (ir.canRead()) {
0141             while (ir.imageCount() > 1 && ir.currentImageRect() != QRect(0, 0, desired.width(), desired.height())) {
0142                 if (!ir.jumpToNextImage()) {
0143                     break;
0144                 }
0145             }
0146             ir.setScaledSize(desired);
0147             const QImage img = ir.read();
0148             if (!img.isNull()) {
0149                 cache->ensureCacheExists();
0150                 const QString localPath = cache->cachePathForIconUrl(iconUrl);
0151                 qCDebug(FAVICONS_LOG) << "Saving image to" << localPath;
0152                 QSaveFile saveFile(localPath);
0153                 if (saveFile.open(QIODevice::WriteOnly) && img.save(&saveFile, "PNG") && saveFile.commit()) {
0154                     d->m_iconFile = localPath;
0155                 } else {
0156                     setError(KIO::ERR_CANNOT_WRITE);
0157                     setErrorText(i18n("Error saving image to %1", localPath));
0158                 }
0159             } else {
0160                 qCDebug(FAVICONS_LOG) << "QImageReader read() returned a null image";
0161             }
0162         } else {
0163             qCDebug(FAVICONS_LOG) << "QImageReader canRead returned false";
0164         }
0165     } else if (job->error() == KJob::KilledJobError) { // we killed it in slotData
0166         setError(KIO::ERR_WORKER_DEFINED);
0167         setErrorText(i18n("Icon file too big, download aborted"));
0168     } else {
0169         setError(job->error());
0170         setErrorText(job->errorString()); // not errorText(), because "this" is a KJob, with no errorString building logic
0171     }
0172     d->m_iconData.clear(); // release memory
0173     if (d->m_iconFile.isEmpty()) {
0174         qCDebug(FAVICONS_LOG) << "adding" << iconUrl << "to failed downloads due to error:" << errorString();
0175         cache->addFailedDownload(iconUrl);
0176     } else {
0177         cache->removeFailedDownload(iconUrl);
0178     }
0179     KCompositeJob::removeSubjob(job);
0180     emitResult();
0181 }
0182 
0183 void FavIconRequestJobPrivate::slotData(Job *job, const QByteArray &data)
0184 {
0185     KIO::TransferJob *tjob = static_cast<KIO::TransferJob *>(job);
0186     unsigned int oldSize = m_iconData.size();
0187     // Size limit. Stop downloading if the file is huge.
0188     // Testcase (as of june 2008, at least): http://planet-soc.com/favicon.ico, 136K and strange format.
0189     // Another case: sites which redirect from "/favicon.ico" to "/" and return the main page.
0190     if (oldSize > 0x10000) { // 65K
0191         qCDebug(FAVICONS_LOG) << "Favicon too big, aborting download of" << tjob->url();
0192         const QUrl iconUrl = tjob->url();
0193         KIO::FavIconsCache::instance()->addFailedDownload(iconUrl);
0194         tjob->kill(KJob::EmitResult);
0195     } else {
0196         m_iconData.resize(oldSize + data.size());
0197         memcpy(m_iconData.data() + oldSize, data.data(), data.size());
0198     }
0199 }
0200 
0201 #include "moc_faviconrequestjob.cpp"