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

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