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

source code of kconfig/src/core/kdesktopfile.cpp