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