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
22 using namespace KIO;
23
portForUrl(const QUrl & url)24 static QString portForUrl(const QUrl &url)
25 {
26 if (url.port() > 0) {
27 return QLatin1Char('_') + QString::number(url.port());
28 }
29 return QString();
30 }
31
simplifyUrl(const QUrl & url)32 static 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(QLatin1Char('='), QLatin1Char('_'));
37 while (result.endsWith(QLatin1Char('/'))) {
38 result.chop(1);
39 }
40 return result;
41 }
42
iconNameFromUrl(const QUrl & iconUrl)43 static QString iconNameFromUrl(const QUrl &iconUrl)
44 {
45 if (iconUrl.path() == QLatin1String("/favicon.ico")) {
46 return iconUrl.host() + portForUrl(iconUrl);
47 }
48
49 QString result = simplifyUrl(iconUrl);
50 // splat / so it can be safely used as a file name
51 result.replace(QLatin1Char('/'), QLatin1Char('_'));
52
53 const QStringView ext = QStringView(result).right(4);
54 if (ext == QLatin1String(".ico") || ext == QLatin1String(".png") || ext == QLatin1String(".xpm")) {
55 result.chop(4);
56 }
57
58 return result;
59 }
60
61 ////
62
63 class KIO::FavIconsCachePrivate
64 {
65 public:
FavIconsCachePrivate()66 FavIconsCachePrivate()
67 : cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons/"))
68 , config(cacheDir + QStringLiteral("index"))
69 {
70 }
71
72 QString cachedIconUrlForUrl(const QUrl &url);
73
74 const QString cacheDir;
75 QMutex mutex; // protects all the member variables below
76 KConfig config;
77 QCache<QString, QString> faviconsCache;
78 QSet<QUrl> failedDownloads;
79 };
80
cachedIconUrlForUrl(const QUrl & url)81 QString FavIconsCachePrivate::cachedIconUrlForUrl(const QUrl &url)
82 {
83 Q_ASSERT(!mutex.tryLock());
84 const QString simplifiedUrl = simplifyUrl(url);
85 QString *cachedIconUrl = faviconsCache[simplifiedUrl];
86 return (cachedIconUrl ? *cachedIconUrl : config.group(QString()).readEntry(simplifiedUrl, QString()));
87 }
88
instance()89 FavIconsCache *FavIconsCache::instance()
90 {
91 static FavIconsCache s_cache; // remind me why we need Q_GLOBAL_STATIC, again, now that C++11 guarantees thread safety?
92 return &s_cache;
93 }
94
FavIconsCache()95 FavIconsCache::FavIconsCache()
96 : d(new FavIconsCachePrivate)
97 {
98 }
99
~FavIconsCache()100 FavIconsCache::~FavIconsCache()
101 {
102 delete d;
103 }
104
iconForUrl(const QUrl & url)105 QString FavIconsCache::iconForUrl(const QUrl &url)
106 {
107 if (url.host().isEmpty()) {
108 return QString();
109 }
110 QMutexLocker locker(&d->mutex);
111 const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
112 QString icon = d->cacheDir;
113 if (!cachedIconUrl.isEmpty()) {
114 icon += iconNameFromUrl(QUrl(cachedIconUrl));
115 } else {
116 icon += url.host();
117 }
118 icon += QStringLiteral(".png");
119 if (QFile::exists(icon)) {
120 return icon;
121 }
122 return QString();
123 }
124
iconUrlForUrl(const QUrl & url)125 QUrl FavIconsCache::iconUrlForUrl(const QUrl &url)
126 {
127 QMutexLocker locker(&d->mutex);
128 const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
129 if (!cachedIconUrl.isEmpty()) {
130 return QUrl(cachedIconUrl);
131 } else {
132 QUrl iconUrl;
133 iconUrl.setScheme(url.scheme());
134 iconUrl.setHost(url.host());
135 iconUrl.setPort(url.port());
136 iconUrl.setPath(QStringLiteral("/favicon.ico"));
137 iconUrl.setUserInfo(url.userInfo());
138 return iconUrl;
139 }
140 }
141
setIconForUrl(const QUrl & url,const QUrl & iconUrl)142 void FavIconsCache::setIconForUrl(const QUrl &url, const QUrl &iconUrl)
143 {
144 QMutexLocker locker(&d->mutex);
145 const QString simplifiedUrl = simplifyUrl(url);
146 const QString iconUrlStr = iconUrl.url();
147 d->faviconsCache.insert(simplifiedUrl, new QString(iconUrlStr));
148 d->config.group(QString()).writeEntry(simplifiedUrl, iconUrlStr);
149 d->config.sync();
150 }
151
cachePathForIconUrl(const QUrl & iconUrl) const152 QString FavIconsCache::cachePathForIconUrl(const QUrl &iconUrl) const
153 {
154 QMutexLocker locker(&d->mutex);
155 const QString iconName = iconNameFromUrl(iconUrl);
156 return d->cacheDir + iconName + QLatin1String(".png");
157 }
158
ensureCacheExists()159 void FavIconsCache::ensureCacheExists()
160 {
161 QMutexLocker locker(&d->mutex);
162 QDir().mkpath(d->cacheDir);
163 }
164
addFailedDownload(const QUrl & url)165 void FavIconsCache::addFailedDownload(const QUrl &url)
166 {
167 QMutexLocker locker(&d->mutex);
168 d->failedDownloads.insert(url);
169 }
170
removeFailedDownload(const QUrl & url)171 void FavIconsCache::removeFailedDownload(const QUrl &url)
172 {
173 QMutexLocker locker(&d->mutex);
174 d->failedDownloads.remove(url);
175 }
176
isFailedDownload(const QUrl & url) const177 bool FavIconsCache::isFailedDownload(const QUrl &url) const
178 {
179 QMutexLocker locker(&d->mutex);
180 return d->failedDownloads.contains(url);
181 }
182