Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kconfig/src/core/kdesktopfile.cpp
Line
Count
Source
1
/*
2
    This file is part of the KDE libraries
3
    SPDX-FileCopyrightText: 1999 Pietro Iglio <iglio@kde.org>
4
    SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
5
6
    SPDX-License-Identifier: LGPL-2.0-or-later
7
*/
8
9
#include "kdesktopfile.h"
10
11
#include "kauthorized.h"
12
#include "kconfig_core_log_settings.h"
13
#include "kconfig_p.h"
14
#include "kconfiggroup.h"
15
#include "kconfigini_p.h"
16
#include "kdesktopfileaction.h"
17
18
#include <QDir>
19
#include <QFileInfo>
20
#include <QStandardPaths>
21
#include <QUrl>
22
23
#ifndef Q_OS_WIN
24
#include <unistd.h>
25
#endif
26
27
#include <algorithm>
28
29
class KDesktopFilePrivate : public KConfigPrivate
30
{
31
public:
32
    KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName)
33
0
        : KConfigPrivate(KConfig::NoGlobals, resourceType)
34
0
    {
35
0
        changeFileName(fileName);
36
37
        // make sure the [Desktop Entry] group is always the first one, as required by the spec
38
0
        mBackend.setPrimaryGroup(QStringLiteral("Desktop Entry"));
39
0
    }
40
    KConfigGroup desktopGroup;
41
};
42
43
KDesktopFile::KDesktopFile(QStandardPaths::StandardLocation resourceType, const QString &fileName)
44
0
    : KConfig(*new KDesktopFilePrivate(resourceType, fileName))
45
0
{
46
0
    Q_D(KDesktopFile);
47
0
    reparseConfiguration();
48
0
    d->desktopGroup = KConfigGroup(this, QStringLiteral("Desktop Entry"));
49
0
}
50
51
KDesktopFile::KDesktopFile(const QString &fileName)
52
0
    : KDesktopFile(QStandardPaths::ApplicationsLocation, fileName)
53
0
{
54
0
}
55
56
0
KDesktopFile::~KDesktopFile() = default;
57
58
KConfigGroup KDesktopFile::desktopGroup() const
59
0
{
60
0
    Q_D(const KDesktopFile);
61
0
    return d->desktopGroup;
62
0
}
63
64
QString KDesktopFile::locateLocal(const QString &path)
65
0
{
66
0
    static const QLatin1Char slash('/');
67
68
    // Relative to config? (e.g. for autostart)
69
0
    const QStringList genericConfig = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation);
70
    // Iterate from the last item since some items may be subfolders of others.
71
0
    auto it = std::find_if(genericConfig.crbegin(), genericConfig.crend(), [&path](const QString &dir) {
72
0
        return path.startsWith(dir + slash);
73
0
    });
74
0
    if (it != genericConfig.crend()) {
75
0
        return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + slash + QStringView(path).mid(it->size() + 1);
76
0
    }
77
78
0
    QString relativePath;
79
    // Relative to xdg data dir? (much more common)
80
0
    const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
81
0
    for (const QString &dir : lstGenericDataLocation) {
82
0
        if (path.startsWith(dir + slash)) {
83
0
            relativePath = path.mid(dir.length() + 1);
84
0
        }
85
0
    }
86
0
    if (relativePath.isEmpty()) {
87
        // What now? The desktop file doesn't come from XDG_DATA_DIRS. Use filename only and hope for the best.
88
0
        relativePath = path.mid(path.lastIndexOf(slash) + 1);
89
0
    }
90
0
    return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + slash + relativePath;
91
0
}
92
93
bool KDesktopFile::isDesktopFile(const QString &path)
94
12.1k
{
95
12.1k
    return path.endsWith(QLatin1String(".desktop"));
96
12.1k
}
97
98
bool KDesktopFile::isAuthorizedDesktopFile(const QString &path)
99
0
{
100
0
    if (path.isEmpty()) {
101
0
        return false; // Empty paths are not ok.
102
0
    }
103
104
0
    if (QDir::isRelativePath(path)) {
105
0
        return true; // Relative paths are ok.
106
0
    }
107
108
0
    const QString realPath = QFileInfo(path).canonicalFilePath();
109
0
    if (realPath.isEmpty()) {
110
0
        return false; // File doesn't exist.
111
0
    }
112
113
0
#ifndef Q_OS_WIN
114
0
    static constexpr Qt::CaseSensitivity sensitivity = Qt::CaseSensitive;
115
#else
116
    static constexpr Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive;
117
#endif
118
119
    // Check if the .desktop file is installed as part of KDE or XDG.
120
0
    const QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
121
0
    auto it = std::find_if(appsDirs.cbegin(), appsDirs.cend(), [&realPath, &path](const QString &prefix) {
122
0
        QFileInfo info(prefix);
123
0
        return info.exists() && info.isDir() && (realPath.startsWith(info.canonicalFilePath(), sensitivity) || path.startsWith(info.canonicalFilePath()));
124
0
    });
125
0
    if (it != appsDirs.cend()) {
126
0
        return true;
127
0
    }
128
129
0
    const QString autostartDir = QStringLiteral("autostart/");
130
0
    const QStringList lstConfigPath = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation);
131
0
    auto configIt = std::find_if(lstConfigPath.cbegin(), lstConfigPath.cend(), [&realPath, &autostartDir](const QString &xdgDataPrefix) {
132
0
        QFileInfo info(xdgDataPrefix);
133
0
        if (info.exists() && info.isDir()) {
134
0
            const QString prefix = info.canonicalFilePath();
135
0
            return realPath.startsWith(prefix + QLatin1Char('/') + autostartDir, sensitivity);
136
0
        }
137
0
        return false;
138
0
    });
139
0
    if (configIt != lstConfigPath.cend()) {
140
0
        return true;
141
0
    }
142
143
    // Forbid desktop files outside of standard locations if kiosk is set so
144
0
    if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) {
145
0
        qCWarning(KCONFIG_CORE_LOG) << "Access to" << path << "denied because of 'run_desktop_files' restriction.";
146
0
        return false;
147
0
    }
148
149
    // Not otherwise permitted, so only allow if the file is executable, or if
150
    // owned by root (uid == 0)
151
0
    QFileInfo entryInfo(path);
152
0
    if (entryInfo.isExecutable() || entryInfo.ownerId() == 0) {
153
0
        return true;
154
0
    }
155
156
0
    qCInfo(KCONFIG_CORE_LOG) << "Access to" << path << "denied, not owned by root and executable flag not set.";
157
0
    return false;
158
0
}
159
160
QString KDesktopFile::readType() const
161
0
{
162
0
    Q_D(const KDesktopFile);
163
0
    return d->desktopGroup.readEntry("Type", QString());
164
0
}
165
166
QString KDesktopFile::readIcon() const
167
0
{
168
0
    Q_D(const KDesktopFile);
169
0
    return d->desktopGroup.readEntry("Icon", QString());
170
0
}
171
172
QString KDesktopFile::readName() const
173
0
{
174
0
    Q_D(const KDesktopFile);
175
0
    return d->desktopGroup.readEntry("Name", QString());
176
0
}
177
178
QString KDesktopFile::readComment() const
179
0
{
180
0
    Q_D(const KDesktopFile);
181
0
    return d->desktopGroup.readEntry("Comment", QString());
182
0
}
183
184
QString KDesktopFile::readGenericName() const
185
0
{
186
0
    Q_D(const KDesktopFile);
187
0
    return d->desktopGroup.readEntry("GenericName", QString());
188
0
}
189
190
QString KDesktopFile::readPath() const
191
0
{
192
0
    Q_D(const KDesktopFile);
193
    // NOT readPathEntry, it is not XDG-compliant: it performs
194
    // various expansions, like $HOME.  Note that the expansion
195
    // behaviour still happens if the "e" flag is set, maintaining
196
    // backwards compatibility.
197
0
    return d->desktopGroup.readEntry("Path", QString());
198
0
}
199
200
QString KDesktopFile::readUrl() const
201
0
{
202
0
    Q_D(const KDesktopFile);
203
0
    if (hasDeviceType()) {
204
0
        return d->desktopGroup.readEntry("MountPoint", QString());
205
0
    } else {
206
        // NOT readPathEntry (see readPath())
207
0
        QString url = d->desktopGroup.readEntry("URL", QString());
208
0
        if (!url.isEmpty() && !QDir::isRelativePath(url)) {
209
            // Handle absolute paths as such (i.e. we need to escape them)
210
0
            return QUrl::fromLocalFile(url).toString();
211
0
        }
212
0
        return url;
213
0
    }
214
0
}
215
216
QStringList KDesktopFile::readActions() const
217
0
{
218
0
    Q_D(const KDesktopFile);
219
0
    return d->desktopGroup.readXdgListEntry("Actions");
220
0
}
221
222
QStringList KDesktopFile::readMimeTypes() const
223
0
{
224
0
    Q_D(const KDesktopFile);
225
0
    return d->desktopGroup.readXdgListEntry("MimeType");
226
0
}
227
228
KConfigGroup KDesktopFile::actionGroup(const QString &group)
229
0
{
230
0
    return KConfigGroup(this, QLatin1String("Desktop Action ") + group);
231
0
}
232
233
KConfigGroup KDesktopFile::actionGroup(const QString &group) const
234
0
{
235
0
    return const_cast<KDesktopFile *>(this)->actionGroup(group);
236
0
}
237
238
bool KDesktopFile::hasActionGroup(const QString &group) const
239
0
{
240
0
    return hasGroup(QString(QLatin1String("Desktop Action ") + group));
241
0
}
242
243
bool KDesktopFile::hasLinkType() const
244
0
{
245
0
    return readType() == QLatin1String("Link");
246
0
}
247
248
bool KDesktopFile::hasApplicationType() const
249
0
{
250
0
    return readType() == QLatin1String("Application");
251
0
}
252
253
bool KDesktopFile::hasDeviceType() const
254
0
{
255
0
    return readType() == QLatin1String("FSDevice");
256
0
}
257
258
bool KDesktopFile::tryExec() const
259
0
{
260
0
    Q_D(const KDesktopFile);
261
    // Test for TryExec and "X-KDE-AuthorizeAction"
262
    // NOT readPathEntry (see readPath())
263
0
    const QString te = d->desktopGroup.readEntry("TryExec", QString());
264
0
    if (!te.isEmpty() && QStandardPaths::findExecutable(te).isEmpty()) {
265
0
        return false;
266
0
    }
267
0
    const QStringList list = d->desktopGroup.readEntry("X-KDE-AuthorizeAction", QStringList());
268
0
    const auto isNotAuthorized = std::any_of(list.cbegin(), list.cend(), [](const QString &action) {
269
0
        return !KAuthorized::authorize(action.trimmed());
270
0
    });
271
0
    if (isNotAuthorized) {
272
0
        return false;
273
0
    }
274
275
    // See also KService::username()
276
0
    if (d->desktopGroup.readEntry("X-KDE-SubstituteUID", false)) {
277
0
        QString user = d->desktopGroup.readEntry("X-KDE-Username", QString());
278
0
        if (user.isEmpty()) {
279
0
            user = qEnvironmentVariable("ADMIN_ACCOUNT"), QStringLiteral("root");
280
0
        }
281
0
        if (!KAuthorized::authorize(QLatin1String("user/") + user)) {
282
0
            return false;
283
0
        }
284
0
    }
285
286
0
    return true;
287
0
}
288
289
QString KDesktopFile::readDocPath() const
290
0
{
291
0
    Q_D(const KDesktopFile);
292
0
    return d->desktopGroup.readPathEntry("X-DocPath", QString());
293
0
}
294
295
KDesktopFile *KDesktopFile::copyTo(const QString &file) const
296
0
{
297
0
    KDesktopFile *config = new KDesktopFile(QString());
298
0
    this->KConfig::copyTo(file, config);
299
0
    return config;
300
0
}
301
302
QString KDesktopFile::fileName() const
303
0
{
304
0
    return name();
305
0
}
306
307
bool KDesktopFile::noDisplay() const
308
0
{
309
0
    Q_D(const KDesktopFile);
310
0
    return d->desktopGroup.readEntry("NoDisplay", false);
311
0
}
312
313
QList<KDesktopFileAction> KDesktopFile::actions() const
314
0
{
315
0
    QList<KDesktopFileAction> desktopFileActions;
316
0
    const QStringList actionKeys = readActions();
317
0
    for (const QString &actionKey : actionKeys) {
318
0
        const KConfigGroup grp = actionGroup(actionKey);
319
0
        desktopFileActions << KDesktopFileAction(actionKey, grp.readEntry("Name"), grp.readEntry("Icon"), grp.readEntry("Exec"), fileName());
320
0
    }
321
0
    return desktopFileActions;
322
0
}