/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 | } |