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 }