File indexing completed on 2024-05-05 16:13:08

0001 /*
0002     This file is part of KIO.
0003     SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
0004     SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "faviconscache_p.h"
0010 
0011 #include <KConfig>
0012 #include <KConfigGroup>
0013 #include <QMutex>
0014 
0015 #include <QCache>
0016 #include <QDir>
0017 #include <QFile>
0018 #include <QSet>
0019 #include <QStandardPaths>
0020 #include <QUrl>
0021 
0022 using namespace KIO;
0023 
0024 static QString portForUrl(const QUrl &url)
0025 {
0026     if (url.port() > 0) {
0027         return QLatin1Char('_') + QString::number(url.port());
0028     }
0029     return QString();
0030 }
0031 
0032 static QString simplifyUrl(const QUrl &url)
0033 {
0034     // splat any = in the URL so it can be safely used as a config key
0035     QString result = url.host() + portForUrl(url) + url.path();
0036     result.replace(QLatin1Char('='), QLatin1Char('_'));
0037     while (result.endsWith(QLatin1Char('/'))) {
0038         result.chop(1);
0039     }
0040     return result;
0041 }
0042 
0043 static QString iconNameFromUrl(const QUrl &iconUrl)
0044 {
0045     if (iconUrl.path() == QLatin1String("/favicon.ico")) {
0046         return iconUrl.host() + portForUrl(iconUrl);
0047     }
0048 
0049     QString result = simplifyUrl(iconUrl);
0050     // splat / so it can be safely used as a file name
0051     result.replace(QLatin1Char('/'), QLatin1Char('_'));
0052 
0053     const int dotExtLen = 4;
0054     const QStringView ext = QStringView(result).right(dotExtLen);
0055     if (ext == QLatin1String(".ico") || ext == QLatin1String(".png") || ext == QLatin1String(".xpm")) {
0056         result.chop(dotExtLen);
0057     }
0058 
0059     return result;
0060 }
0061 
0062 ////
0063 
0064 class KIO::FavIconsCachePrivate
0065 {
0066 public:
0067     FavIconsCachePrivate()
0068         : cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons/"))
0069         , config(cacheDir + QStringLiteral("index"))
0070     {
0071     }
0072 
0073     QString cachedIconUrlForUrl(const QUrl &url);
0074 
0075     const QString cacheDir;
0076     QMutex mutex; // protects all the member variables below
0077     KConfig config;
0078     QCache<QString, QString> faviconsCache;
0079     QSet<QUrl> failedDownloads;
0080 };
0081 
0082 QString FavIconsCachePrivate::cachedIconUrlForUrl(const QUrl &url)
0083 {
0084     Q_ASSERT(!mutex.tryLock());
0085     const QString simplifiedUrl = simplifyUrl(url);
0086     QString *cachedIconUrl = faviconsCache[simplifiedUrl];
0087     return (cachedIconUrl ? *cachedIconUrl : config.group(QString()).readEntry(simplifiedUrl, QString()));
0088 }
0089 
0090 FavIconsCache *FavIconsCache::instance()
0091 {
0092     static FavIconsCache s_cache; // remind me why we need Q_GLOBAL_STATIC, again, now that C++11 guarantees thread safety?
0093     return &s_cache;
0094 }
0095 
0096 FavIconsCache::FavIconsCache()
0097     : d(new FavIconsCachePrivate)
0098 {
0099 }
0100 
0101 FavIconsCache::~FavIconsCache() = default;
0102 
0103 QString FavIconsCache::iconForUrl(const QUrl &url)
0104 {
0105     if (url.host().isEmpty()) {
0106         return QString();
0107     }
0108     QMutexLocker locker(&d->mutex);
0109     const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
0110     QString icon = d->cacheDir;
0111     if (!cachedIconUrl.isEmpty()) {
0112         icon += iconNameFromUrl(QUrl(cachedIconUrl));
0113     } else {
0114         icon += url.host();
0115     }
0116     icon += QStringLiteral(".png");
0117     if (QFile::exists(icon)) {
0118         return icon;
0119     }
0120     return QString();
0121 }
0122 
0123 QUrl FavIconsCache::iconUrlForUrl(const QUrl &url)
0124 {
0125     QMutexLocker locker(&d->mutex);
0126     const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
0127     if (!cachedIconUrl.isEmpty()) {
0128         return QUrl(cachedIconUrl);
0129     } else {
0130         QUrl iconUrl;
0131         iconUrl.setScheme(url.scheme());
0132         iconUrl.setHost(url.host());
0133         iconUrl.setPort(url.port());
0134         iconUrl.setPath(QStringLiteral("/favicon.ico"));
0135         iconUrl.setUserInfo(url.userInfo());
0136         return iconUrl;
0137     }
0138 }
0139 
0140 void FavIconsCache::setIconForUrl(const QUrl &url, const QUrl &iconUrl)
0141 {
0142     QMutexLocker locker(&d->mutex);
0143     const QString simplifiedUrl = simplifyUrl(url);
0144     const QString iconUrlStr = iconUrl.url();
0145     d->faviconsCache.insert(simplifiedUrl, new QString(iconUrlStr));
0146     d->config.group(QString()).writeEntry(simplifiedUrl, iconUrlStr);
0147     d->config.sync();
0148 }
0149 
0150 QString FavIconsCache::cachePathForIconUrl(const QUrl &iconUrl) const
0151 {
0152     QMutexLocker locker(&d->mutex);
0153     const QString iconName = iconNameFromUrl(iconUrl);
0154     return d->cacheDir + iconName + QLatin1String(".png");
0155 }
0156 
0157 void FavIconsCache::ensureCacheExists()
0158 {
0159     QMutexLocker locker(&d->mutex);
0160     QDir().mkpath(d->cacheDir);
0161 }
0162 
0163 void FavIconsCache::addFailedDownload(const QUrl &url)
0164 {
0165     QMutexLocker locker(&d->mutex);
0166     d->failedDownloads.insert(url);
0167 }
0168 
0169 void FavIconsCache::removeFailedDownload(const QUrl &url)
0170 {
0171     QMutexLocker locker(&d->mutex);
0172     d->failedDownloads.remove(url);
0173 }
0174 
0175 bool FavIconsCache::isFailedDownload(const QUrl &url) const
0176 {
0177     QMutexLocker locker(&d->mutex);
0178     return d->failedDownloads.contains(url);
0179 }
0180 
0181 #include "moc_faviconscache_p.cpp"