File indexing completed on 2024-04-28 15:26:51
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "applicationlauncherjob.h" 0009 #include "../core/global.h" 0010 #include "dbusactivationrunner_p.h" 0011 #include "jobuidelegatefactory.h" 0012 #include "kiogui_debug.h" 0013 #include "kprocessrunner_p.h" 0014 #include "mimetypefinderjob.h" 0015 #include "openwithhandlerinterface.h" 0016 #include "untrustedprogramhandlerinterface.h" 0017 0018 #include <KAuthorized> 0019 #include <KDesktopFile> 0020 #include <KLocalizedString> 0021 0022 #include <QFileInfo> 0023 #include <QPointer> 0024 0025 class KIO::ApplicationLauncherJobPrivate 0026 { 0027 public: 0028 explicit ApplicationLauncherJobPrivate(KIO::ApplicationLauncherJob *job, const KService::Ptr &service) 0029 : m_service(service) 0030 , q(job) 0031 { 0032 } 0033 0034 void slotStarted(qint64 pid) 0035 { 0036 m_pids.append(pid); 0037 if (--m_numProcessesPending == 0) { 0038 q->emitResult(); 0039 } 0040 } 0041 0042 void showOpenWithDialogForMimeType(); 0043 void showOpenWithDialog(); 0044 0045 KService::Ptr m_service; 0046 QString m_serviceEntryPath; 0047 QList<QUrl> m_urls; 0048 KIO::ApplicationLauncherJob::RunFlags m_runFlags; 0049 QString m_suggestedFileName; 0050 QString m_mimeTypeName; 0051 QByteArray m_startupId; 0052 QVector<qint64> m_pids; 0053 QVector<QPointer<KProcessRunner>> m_processRunners; 0054 int m_numProcessesPending = 0; 0055 KIO::ApplicationLauncherJob *q; 0056 }; 0057 0058 KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KService::Ptr &service, QObject *parent) 0059 : KJob(parent) 0060 , d(new ApplicationLauncherJobPrivate(this, service)) 0061 { 0062 if (d->m_service) { 0063 // Cache entryPath() because we may call KService::setExec() which will clear entryPath() 0064 d->m_serviceEntryPath = d->m_service->entryPath(); 0065 } 0066 } 0067 0068 KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KServiceAction &serviceAction, QObject *parent) 0069 : ApplicationLauncherJob(serviceAction.service(), parent) 0070 { 0071 Q_ASSERT(d->m_service); 0072 d->m_service.detach(); 0073 d->m_service->setExec(serviceAction.exec()); 0074 } 0075 0076 KIO::ApplicationLauncherJob::ApplicationLauncherJob(QObject *parent) 0077 : KJob(parent) 0078 , d(new ApplicationLauncherJobPrivate(this, {})) 0079 { 0080 } 0081 0082 KIO::ApplicationLauncherJob::~ApplicationLauncherJob() 0083 { 0084 // Do *NOT* delete the KProcessRunner instances here. 0085 // We need it to keep running so it can terminate startup notification on process exit. 0086 } 0087 0088 void KIO::ApplicationLauncherJob::setUrls(const QList<QUrl> &urls) 0089 { 0090 d->m_urls = urls; 0091 } 0092 0093 void KIO::ApplicationLauncherJob::setRunFlags(RunFlags runFlags) 0094 { 0095 d->m_runFlags = runFlags; 0096 } 0097 0098 void KIO::ApplicationLauncherJob::setSuggestedFileName(const QString &suggestedFileName) 0099 { 0100 d->m_suggestedFileName = suggestedFileName; 0101 } 0102 0103 void KIO::ApplicationLauncherJob::setStartupId(const QByteArray &startupId) 0104 { 0105 d->m_startupId = startupId; 0106 } 0107 0108 void KIO::ApplicationLauncherJob::emitUnauthorizedError() 0109 { 0110 setError(KJob::UserDefinedError); 0111 setErrorText(i18n("You are not authorized to execute this file.")); 0112 emitResult(); 0113 } 0114 0115 void KIO::ApplicationLauncherJob::start() 0116 { 0117 if (!d->m_service) { 0118 d->showOpenWithDialogForMimeType(); 0119 return; 0120 } 0121 0122 Q_EMIT description(this, i18nc("Launching application", "Launching %1", d->m_service->name()), {}, {}); 0123 0124 // First, the security checks 0125 if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { 0126 // KIOSK restriction, cannot be circumvented 0127 emitUnauthorizedError(); 0128 return; 0129 } 0130 0131 if (!d->m_serviceEntryPath.isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(d->m_serviceEntryPath)) { 0132 // We can use QStandardPaths::findExecutable to resolve relative pathnames 0133 // but that gets rid of the command line arguments. 0134 QString program = QFileInfo(d->m_service->exec()).canonicalFilePath(); 0135 if (program.isEmpty()) { // e.g. due to command line arguments 0136 program = d->m_service->exec(); 0137 } 0138 auto *untrustedProgramHandler = KIO::delegateExtension<KIO::UntrustedProgramHandlerInterface *>(this); 0139 if (!untrustedProgramHandler) { 0140 emitUnauthorizedError(); 0141 return; 0142 } 0143 connect(untrustedProgramHandler, &KIO::UntrustedProgramHandlerInterface::result, this, [this, untrustedProgramHandler](bool result) { 0144 if (result) { 0145 // Assume that service is an absolute path since we're being called (relative paths 0146 // would have been allowed unless Kiosk said no, therefore we already know where the 0147 // .desktop file is. Now add a header to it if it doesn't already have one 0148 // and add the +x bit. 0149 0150 QString errorString; 0151 if (untrustedProgramHandler->makeServiceFileExecutable(d->m_serviceEntryPath, errorString)) { 0152 proceedAfterSecurityChecks(); 0153 } else { 0154 QString serviceName = d->m_service->name(); 0155 if (serviceName.isEmpty()) { 0156 serviceName = d->m_service->genericName(); 0157 } 0158 setError(KJob::UserDefinedError); 0159 setErrorText(i18n("Unable to make the service %1 executable, aborting execution.\n%2.", serviceName, errorString)); 0160 emitResult(); 0161 } 0162 } else { 0163 setError(KIO::ERR_USER_CANCELED); 0164 emitResult(); 0165 } 0166 }); 0167 untrustedProgramHandler->showUntrustedProgramWarning(this, d->m_service->name()); 0168 return; 0169 } 0170 proceedAfterSecurityChecks(); 0171 } 0172 0173 void KIO::ApplicationLauncherJob::proceedAfterSecurityChecks() 0174 { 0175 if (d->m_urls.count() > 1 && !DBusActivationRunner::activationPossible(d->m_service, d->m_runFlags, d->m_suggestedFileName) 0176 && !d->m_service->allowMultipleFiles()) { 0177 // We need to launch the application N times. 0178 // We ignore the result for application 2 to N. 0179 // For the first file we launch the application in the 0180 // usual way. The reported result is based on this application. 0181 d->m_numProcessesPending = d->m_urls.count(); 0182 d->m_processRunners.reserve(d->m_numProcessesPending); 0183 for (int i = 1; i < d->m_urls.count(); ++i) { 0184 auto *processRunner = 0185 KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, {d->m_urls.at(i)}, d->m_runFlags, d->m_suggestedFileName, QByteArray{}); 0186 d->m_processRunners.push_back(processRunner); 0187 connect(processRunner, &KProcessRunner::processStarted, this, [this](qint64 pid) { 0188 d->slotStarted(pid); 0189 }); 0190 } 0191 d->m_urls = {d->m_urls.at(0)}; 0192 } else { 0193 d->m_numProcessesPending = 1; 0194 } 0195 0196 auto *processRunner = 0197 KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, d->m_urls, d->m_runFlags, d->m_suggestedFileName, d->m_startupId); 0198 d->m_processRunners.push_back(processRunner); 0199 connect(processRunner, &KProcessRunner::error, this, [this](const QString &errorText) { 0200 setError(KJob::UserDefinedError); 0201 setErrorText(errorText); 0202 emitResult(); 0203 }); 0204 connect(processRunner, &KProcessRunner::processStarted, this, [this](qint64 pid) { 0205 d->slotStarted(pid); 0206 }); 0207 } 0208 0209 // For KRun 0210 bool KIO::ApplicationLauncherJob::waitForStarted() 0211 { 0212 if (error() != KJob::NoError) { 0213 return false; 0214 } 0215 if (d->m_processRunners.isEmpty()) { 0216 // Maybe we're in the security prompt... 0217 // Can't avoid the nested event loop 0218 // This fork of KJob::exec doesn't set QEventLoop::ExcludeUserInputEvents 0219 const bool wasAutoDelete = isAutoDelete(); 0220 setAutoDelete(false); 0221 QEventLoop loop; 0222 connect(this, &KJob::result, this, [&](KJob *job) { 0223 loop.exit(job->error()); 0224 }); 0225 const int ret = loop.exec(); 0226 if (wasAutoDelete) { 0227 deleteLater(); 0228 } 0229 return ret != KJob::NoError; 0230 } 0231 const bool ret = std::all_of(d->m_processRunners.cbegin(), d->m_processRunners.cend(), [](QPointer<KProcessRunner> r) { 0232 return r.isNull() || r->waitForStarted(); 0233 }); 0234 for (const auto &r : std::as_const(d->m_processRunners)) { 0235 if (!r.isNull()) { 0236 qApp->sendPostedEvents(r); // so slotStarted gets called 0237 } 0238 } 0239 return ret; 0240 } 0241 0242 qint64 KIO::ApplicationLauncherJob::pid() const 0243 { 0244 return d->m_pids.at(0); 0245 } 0246 0247 QVector<qint64> KIO::ApplicationLauncherJob::pids() const 0248 { 0249 return d->m_pids; 0250 } 0251 0252 void KIO::ApplicationLauncherJobPrivate::showOpenWithDialogForMimeType() 0253 { 0254 if (m_urls.size() == 1) { 0255 auto job = new KIO::MimeTypeFinderJob(m_urls[0], q); 0256 job->setFollowRedirections(true); 0257 job->setSuggestedFileName(m_suggestedFileName); 0258 q->connect(job, &KJob::result, q, [this, job]() { 0259 if (!job->error()) { 0260 m_mimeTypeName = job->mimeType(); 0261 } 0262 showOpenWithDialog(); 0263 }); 0264 job->start(); 0265 } else { 0266 showOpenWithDialog(); 0267 } 0268 } 0269 0270 void KIO::ApplicationLauncherJobPrivate::showOpenWithDialog() 0271 { 0272 if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { 0273 q->setError(KJob::UserDefinedError); 0274 q->setErrorText(i18n("You are not authorized to select an application to open this file.")); 0275 q->emitResult(); 0276 return; 0277 } 0278 0279 auto *openWithHandler = KIO::delegateExtension<KIO::OpenWithHandlerInterface *>(q); 0280 if (!openWithHandler) { 0281 q->setError(KJob::UserDefinedError); 0282 q->setErrorText(i18n("Internal error: could not prompt the user for which application to start")); 0283 q->emitResult(); 0284 return; 0285 } 0286 0287 QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::canceled, q, [this]() { 0288 q->setError(KIO::ERR_USER_CANCELED); 0289 q->emitResult(); 0290 }); 0291 0292 QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::serviceSelected, q, [this](const KService::Ptr &service) { 0293 Q_ASSERT(service); 0294 m_service = service; 0295 q->start(); 0296 }); 0297 0298 QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::handled, q, [this]() { 0299 q->emitResult(); 0300 }); 0301 0302 openWithHandler->promptUserForApplication(q, m_urls, m_mimeTypeName); 0303 }