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 }