File indexing completed on 2024-05-12 15:34:12
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1999 Pietro Iglio <iglio@kde.org> 0004 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kdesktopfile.h" 0010 0011 #include "kauthorized.h" 0012 #include "kconfig_core_log_settings.h" 0013 #include "kconfig_p.h" 0014 #include "kconfiggroup.h" 0015 #include "kconfigini_p.h" 0016 0017 #include <QDir> 0018 #include <QFileInfo> 0019 #include <QStandardPaths> 0020 #include <QUrl> 0021 0022 #ifndef Q_OS_WIN 0023 #include <unistd.h> 0024 #endif 0025 0026 #include <algorithm> 0027 0028 class KDesktopFilePrivate : public KConfigPrivate 0029 { 0030 public: 0031 KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName); 0032 KConfigGroup desktopGroup; 0033 }; 0034 0035 KDesktopFilePrivate::KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName) 0036 : KConfigPrivate(KConfig::NoGlobals, resourceType) 0037 { 0038 mBackend = new KConfigIniBackend(); 0039 bDynamicBackend = false; 0040 changeFileName(fileName); 0041 } 0042 0043 KDesktopFile::KDesktopFile(QStandardPaths::StandardLocation resourceType, const QString &fileName) 0044 : KConfig(*new KDesktopFilePrivate(resourceType, fileName)) 0045 { 0046 Q_D(KDesktopFile); 0047 reparseConfiguration(); 0048 d->desktopGroup = KConfigGroup(this, "Desktop Entry"); 0049 } 0050 0051 KDesktopFile::KDesktopFile(const QString &fileName) 0052 : KConfig(*new KDesktopFilePrivate(QStandardPaths::ApplicationsLocation, fileName)) 0053 { 0054 Q_D(KDesktopFile); 0055 reparseConfiguration(); 0056 d->desktopGroup = KConfigGroup(this, "Desktop Entry"); 0057 } 0058 0059 KDesktopFile::~KDesktopFile() 0060 { 0061 } 0062 0063 KConfigGroup KDesktopFile::desktopGroup() const 0064 { 0065 Q_D(const KDesktopFile); 0066 return d->desktopGroup; 0067 } 0068 0069 QString KDesktopFile::locateLocal(const QString &path) 0070 { 0071 static const QLatin1Char slash('/'); 0072 0073 // Relative to config? (e.g. for autostart) 0074 const QStringList genericConfig = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); 0075 // Iterate from the last item since some items may be subfolders of others. 0076 auto it = std::find_if(genericConfig.crbegin(), genericConfig.crend(), [&path](const QString &dir) { 0077 return path.startsWith(dir + slash); 0078 }); 0079 if (it != genericConfig.crend()) { 0080 return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + slash + QStringView(path).mid(it->size() + 1); 0081 } 0082 0083 QString relativePath; 0084 // Relative to xdg data dir? (much more common) 0085 const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0086 for (const QString &dir : lstGenericDataLocation) { 0087 if (path.startsWith(dir + slash)) { 0088 relativePath = path.mid(dir.length() + 1); 0089 } 0090 } 0091 if (relativePath.isEmpty()) { 0092 // What now? The desktop file doesn't come from XDG_DATA_DIRS. Use filename only and hope for the best. 0093 relativePath = path.mid(path.lastIndexOf(slash) + 1); 0094 } 0095 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + slash + relativePath; 0096 } 0097 0098 bool KDesktopFile::isDesktopFile(const QString &path) 0099 { 0100 return path.endsWith(QLatin1String(".desktop")); 0101 } 0102 0103 bool KDesktopFile::isAuthorizedDesktopFile(const QString &path) 0104 { 0105 if (path.isEmpty()) { 0106 return false; // Empty paths are not ok. 0107 } 0108 0109 if (QDir::isRelativePath(path)) { 0110 return true; // Relative paths are ok. 0111 } 0112 0113 const QString realPath = QFileInfo(path).canonicalFilePath(); 0114 if (realPath.isEmpty()) { 0115 return false; // File doesn't exist. 0116 } 0117 0118 #ifndef Q_OS_WIN 0119 const Qt::CaseSensitivity sensitivity = Qt::CaseSensitive; 0120 #else 0121 const Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive; 0122 #endif 0123 0124 // Check if the .desktop file is installed as part of KDE or XDG. 0125 const QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0126 auto it = std::find_if(appsDirs.cbegin(), appsDirs.cend(), [&realPath, sensitivity](const QString &prefix) { 0127 QFileInfo info(prefix); 0128 return info.exists() && info.isDir() && realPath.startsWith(info.canonicalFilePath(), sensitivity); 0129 }); 0130 if (it != appsDirs.cend()) { 0131 return true; 0132 } 0133 0134 const QString servicesDir = QStringLiteral("kservices5/"); // KGlobal::dirs()->xdgDataRelativePath("services") 0135 const QStringList genericData = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0136 auto genericIt = std::find_if(genericData.cbegin(), genericData.cend(), [&realPath, &servicesDir, sensitivity](const QString &xdgDataPrefix) { 0137 QFileInfo info(xdgDataPrefix); 0138 if (info.exists() && info.isDir()) { 0139 const QString prefix = info.canonicalFilePath(); 0140 return realPath.startsWith(prefix + QLatin1Char('/') + servicesDir, sensitivity); 0141 } 0142 return false; 0143 }); 0144 if (genericIt != genericData.cend()) { 0145 return true; 0146 } 0147 0148 const QString autostartDir = QStringLiteral("autostart/"); 0149 const QStringList lstConfigPath = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); 0150 auto configIt = std::find_if(lstConfigPath.cbegin(), lstConfigPath.cend(), [&realPath, &autostartDir, sensitivity](const QString &xdgDataPrefix) { 0151 QFileInfo info(xdgDataPrefix); 0152 if (info.exists() && info.isDir()) { 0153 const QString prefix = info.canonicalFilePath(); 0154 return realPath.startsWith(prefix + QLatin1Char('/') + autostartDir, sensitivity); 0155 } 0156 return false; 0157 }); 0158 if (configIt != lstConfigPath.cend()) { 0159 return true; 0160 } 0161 0162 // Forbid desktop files outside of standard locations if kiosk is set so 0163 if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { 0164 qCWarning(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied because of 'run_desktop_files' restriction."; 0165 return false; 0166 } 0167 0168 // Not otherwise permitted, so only allow if the file is executable, or if 0169 // owned by root (uid == 0) 0170 QFileInfo entryInfo(path); 0171 if (entryInfo.isExecutable() || entryInfo.ownerId() == 0) { 0172 return true; 0173 } 0174 0175 qCInfo(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied, not owned by root and executable flag not set."; 0176 return false; 0177 } 0178 0179 QString KDesktopFile::readType() const 0180 { 0181 Q_D(const KDesktopFile); 0182 return d->desktopGroup.readEntry("Type", QString()); 0183 } 0184 0185 QString KDesktopFile::readIcon() const 0186 { 0187 Q_D(const KDesktopFile); 0188 return d->desktopGroup.readEntry("Icon", QString()); 0189 } 0190 0191 QString KDesktopFile::readName() const 0192 { 0193 Q_D(const KDesktopFile); 0194 return d->desktopGroup.readEntry("Name", QString()); 0195 } 0196 0197 QString KDesktopFile::readComment() const 0198 { 0199 Q_D(const KDesktopFile); 0200 return d->desktopGroup.readEntry("Comment", QString()); 0201 } 0202 0203 QString KDesktopFile::readGenericName() const 0204 { 0205 Q_D(const KDesktopFile); 0206 return d->desktopGroup.readEntry("GenericName", QString()); 0207 } 0208 0209 QString KDesktopFile::readPath() const 0210 { 0211 Q_D(const KDesktopFile); 0212 // NOT readPathEntry, it is not XDG-compliant: it performs 0213 // various expansions, like $HOME. Note that the expansion 0214 // behaviour still happens if the "e" flag is set, maintaining 0215 // backwards compatibility. 0216 return d->desktopGroup.readEntry("Path", QString()); 0217 } 0218 0219 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 82) 0220 QString KDesktopFile::readDevice() const 0221 { 0222 Q_D(const KDesktopFile); 0223 return d->desktopGroup.readEntry("Dev", QString()); 0224 } 0225 #endif 0226 0227 QString KDesktopFile::readUrl() const 0228 { 0229 Q_D(const KDesktopFile); 0230 if (hasDeviceType()) { 0231 return d->desktopGroup.readEntry("MountPoint", QString()); 0232 } else { 0233 // NOT readPathEntry (see readPath()) 0234 QString url = d->desktopGroup.readEntry("URL", QString()); 0235 if (!url.isEmpty() && !QDir::isRelativePath(url)) { 0236 // Handle absolute paths as such (i.e. we need to escape them) 0237 return QUrl::fromLocalFile(url).toString(); 0238 } 0239 return url; 0240 } 0241 } 0242 0243 QStringList KDesktopFile::readActions() const 0244 { 0245 Q_D(const KDesktopFile); 0246 return d->desktopGroup.readXdgListEntry("Actions"); 0247 } 0248 0249 QStringList KDesktopFile::readMimeTypes() const 0250 { 0251 Q_D(const KDesktopFile); 0252 return d->desktopGroup.readXdgListEntry("MimeType"); 0253 } 0254 0255 KConfigGroup KDesktopFile::actionGroup(const QString &group) 0256 { 0257 return KConfigGroup(this, QLatin1String("Desktop Action ") + group); 0258 } 0259 0260 const KConfigGroup KDesktopFile::actionGroup(const QString &group) const 0261 { 0262 return const_cast<KDesktopFile *>(this)->actionGroup(group); 0263 } 0264 0265 bool KDesktopFile::hasActionGroup(const QString &group) const 0266 { 0267 return hasGroup(QString(QLatin1String("Desktop Action ") + group).toUtf8().constData()); 0268 } 0269 0270 bool KDesktopFile::hasLinkType() const 0271 { 0272 return readType() == QLatin1String("Link"); 0273 } 0274 0275 bool KDesktopFile::hasApplicationType() const 0276 { 0277 return readType() == QLatin1String("Application"); 0278 } 0279 0280 bool KDesktopFile::hasDeviceType() const 0281 { 0282 return readType() == QLatin1String("FSDevice"); 0283 } 0284 0285 bool KDesktopFile::tryExec() const 0286 { 0287 Q_D(const KDesktopFile); 0288 // Test for TryExec and "X-KDE-AuthorizeAction" 0289 // NOT readPathEntry (see readPath()) 0290 QString te = d->desktopGroup.readEntry("TryExec", QString()); 0291 0292 if (!te.isEmpty()) { 0293 if (QStandardPaths::findExecutable(te).isEmpty()) { 0294 return false; 0295 } 0296 } 0297 const QStringList list = d->desktopGroup.readEntry("X-KDE-AuthorizeAction", QStringList()); 0298 const auto isNotAuthorized = std::any_of(list.cbegin(), list.cend(), [](const QString &action) { 0299 return !KAuthorized::authorize(action.trimmed()); 0300 }); 0301 if (isNotAuthorized) { 0302 return false; 0303 } 0304 0305 // See also KService::username() 0306 const bool su = d->desktopGroup.readEntry("X-KDE-SubstituteUID", false); 0307 if (su) { 0308 QString user = d->desktopGroup.readEntry("X-KDE-Username", QString()); 0309 if (user.isEmpty()) { 0310 user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT")); 0311 } 0312 if (user.isEmpty()) { 0313 user = QStringLiteral("root"); 0314 } 0315 if (!KAuthorized::authorize(QLatin1String("user/") + user)) { 0316 return false; 0317 } 0318 } 0319 0320 return true; 0321 } 0322 0323 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 42) 0324 QStringList KDesktopFile::sortOrder() const 0325 { 0326 Q_D(const KDesktopFile); 0327 return d->desktopGroup.readXdgListEntry("SortOrder"); 0328 } 0329 #endif 0330 0331 QString KDesktopFile::readDocPath() const 0332 { 0333 Q_D(const KDesktopFile); 0334 return d->desktopGroup.readPathEntry("X-DocPath", QString()); 0335 } 0336 0337 KDesktopFile *KDesktopFile::copyTo(const QString &file) const 0338 { 0339 KDesktopFile *config = new KDesktopFile(QString()); 0340 this->KConfig::copyTo(file, config); 0341 // config->setDesktopGroup(); 0342 return config; 0343 } 0344 0345 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 89) 0346 QStandardPaths::StandardLocation KDesktopFile::resource() const 0347 { 0348 Q_D(const KDesktopFile); 0349 return d->resourceType; 0350 } 0351 #endif 0352 0353 QString KDesktopFile::fileName() const 0354 { 0355 return name(); 0356 } 0357 0358 bool KDesktopFile::noDisplay() const 0359 { 0360 Q_D(const KDesktopFile); 0361 if (d->desktopGroup.readEntry("NoDisplay", false)) { 0362 return true; 0363 } 0364 if (d->desktopGroup.hasKey("OnlyShowIn")) { 0365 if (!d->desktopGroup.readXdgListEntry("OnlyShowIn").contains(QLatin1String("KDE"))) { 0366 return true; 0367 } 0368 } 0369 if (d->desktopGroup.hasKey("NotShowIn")) { 0370 if (d->desktopGroup.readXdgListEntry("NotShowIn").contains(QLatin1String("KDE"))) { 0371 return true; 0372 } 0373 } 0374 return false; 0375 }