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