1/*
2 This file is part of KIO.
3 SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
4 SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7*/
8
9#include "faviconscache_p.h"
10
11#include <KConfig>
12#include <KConfigGroup>
13#include <QMutex>
14
15#include <QCache>
16#include <QDir>
17#include <QFile>
18#include <QSet>
19#include <QStandardPaths>
20#include <QUrl>
21
22using namespace KIO;
23
24static QString portForUrl(const QUrl &url)
25{
26 if (url.port() > 0) {
27 return QLatin1Char('_') + QString::number(url.port());
28 }
29 return QString();
30}
31
32static QString simplifyUrl(const QUrl &url)
33{
34 // splat any = in the URL so it can be safely used as a config key
35 QString result = url.host() + portForUrl(url) + url.path();
36 result.replace(before: QLatin1Char('='), after: QLatin1Char('_'));
37 while (result.endsWith(c: QLatin1Char('/'))) {
38 result.chop(n: 1);
39 }
40 return result;
41}
42
43static QString iconNameFromUrl(const QUrl &iconUrl)
44{
45 if (iconUrl.path() == QLatin1String("/favicon.ico")) {
46 return iconUrl.host() + portForUrl(url: iconUrl);
47 }
48
49 QString result = simplifyUrl(url: iconUrl);
50 // splat / so it can be safely used as a file name
51 result.replace(before: QLatin1Char('/'), after: QLatin1Char('_'));
52
53 const int dotExtLen = 4;
54 const QStringView ext = QStringView(result).right(n: dotExtLen);
55 if (ext == QLatin1String(".ico") || ext == QLatin1String(".png") || ext == QLatin1String(".xpm")) {
56 result.chop(n: dotExtLen);
57 }
58
59 return result;
60}
61
62////
63
64class KIO::FavIconsCachePrivate
65{
66public:
67 FavIconsCachePrivate()
68 : cacheDir(QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons/"))
69 , config(cacheDir + QStringLiteral("index"))
70 {
71 }
72
73 QString cachedIconUrlForUrl(const QUrl &url);
74
75 const QString cacheDir;
76 QMutex mutex; // protects all the member variables below
77 KConfig config;
78 QCache<QString, QString> faviconsCache;
79 QSet<QUrl> failedDownloads;
80};
81
82QString FavIconsCachePrivate::cachedIconUrlForUrl(const QUrl &url)
83{
84 Q_ASSERT(!mutex.tryLock());
85 const QString simplifiedUrl = simplifyUrl(url);
86 QString *cachedIconUrl = faviconsCache[simplifiedUrl];
87 return (cachedIconUrl ? *cachedIconUrl : config.group(group: QString()).readEntry(key: simplifiedUrl, aDefault: QString()));
88}
89
90FavIconsCache *FavIconsCache::instance()
91{
92 static FavIconsCache s_cache; // remind me why we need Q_GLOBAL_STATIC, again, now that C++11 guarantees thread safety?
93 return &s_cache;
94}
95
96FavIconsCache::FavIconsCache()
97 : d(new FavIconsCachePrivate)
98{
99}
100
101FavIconsCache::~FavIconsCache() = default;
102
103QString FavIconsCache::iconForUrl(const QUrl &url)
104{
105 if (url.host().isEmpty()) {
106 return QString();
107 }
108 QMutexLocker locker(&d->mutex);
109 const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
110 QString icon = d->cacheDir;
111 if (!cachedIconUrl.isEmpty()) {
112 icon += iconNameFromUrl(iconUrl: QUrl(cachedIconUrl));
113 } else {
114 icon += url.host();
115 }
116 icon += QStringLiteral(".png");
117 if (QFile::exists(fileName: icon)) {
118 return icon;
119 }
120 return QString();
121}
122
123QUrl FavIconsCache::iconUrlForUrl(const QUrl &url)
124{
125 QMutexLocker locker(&d->mutex);
126 const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
127 if (!cachedIconUrl.isEmpty()) {
128 return QUrl(cachedIconUrl);
129 } else {
130 QUrl iconUrl;
131 iconUrl.setScheme(url.scheme());
132 iconUrl.setHost(host: url.host());
133 iconUrl.setPort(url.port());
134 iconUrl.setPath(QStringLiteral("/favicon.ico"));
135 iconUrl.setUserInfo(userInfo: url.userInfo());
136 return iconUrl;
137 }
138}
139
140void FavIconsCache::setIconForUrl(const QUrl &url, const QUrl &iconUrl)
141{
142 QMutexLocker locker(&d->mutex);
143 const QString simplifiedUrl = simplifyUrl(url);
144 const QString iconUrlStr = iconUrl.url();
145 d->faviconsCache.insert(key: simplifiedUrl, object: new QString(iconUrlStr));
146 d->config.group(group: QString()).writeEntry(key: simplifiedUrl, value: iconUrlStr);
147 d->config.sync();
148}
149
150QString FavIconsCache::cachePathForIconUrl(const QUrl &iconUrl) const
151{
152 QMutexLocker locker(&d->mutex);
153 const QString iconName = iconNameFromUrl(iconUrl);
154 return d->cacheDir + iconName + QLatin1String(".png");
155}
156
157void FavIconsCache::ensureCacheExists()
158{
159 QMutexLocker locker(&d->mutex);
160 QDir().mkpath(dirPath: d->cacheDir);
161}
162
163void FavIconsCache::addFailedDownload(const QUrl &url)
164{
165 QMutexLocker locker(&d->mutex);
166 d->failedDownloads.insert(value: url);
167}
168
169void FavIconsCache::removeFailedDownload(const QUrl &url)
170{
171 QMutexLocker locker(&d->mutex);
172 d->failedDownloads.remove(value: url);
173}
174
175bool FavIconsCache::isFailedDownload(const QUrl &url) const
176{
177 QMutexLocker locker(&d->mutex);
178 return d->failedDownloads.contains(value: url);
179}
180
181#include "moc_faviconscache_p.cpp"
182

source code of kio/src/core/faviconscache.cpp