File indexing completed on 2024-04-28 15:26:53

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "openurljob.h"
0010 #include "commandlauncherjob.h"
0011 #include "desktopexecparser.h"
0012 #include "global.h"
0013 #include "job.h" // for buildErrorString
0014 #include "jobuidelegatefactory.h"
0015 #include "kiogui_debug.h"
0016 #include "openorexecutefileinterface.h"
0017 #include "openwithhandlerinterface.h"
0018 #include "untrustedprogramhandlerinterface.h"
0019 
0020 #include <KApplicationTrader>
0021 #include <KAuthorized>
0022 #include <KConfigGroup>
0023 #include <KDesktopFile>
0024 #include <KLocalizedString>
0025 #include <KSandbox>
0026 #include <KUrlAuthorized>
0027 #include <QFileInfo>
0028 
0029 #include <KProtocolManager>
0030 #include <KSharedConfig>
0031 #include <QDesktopServices>
0032 #include <QHostInfo>
0033 #include <QMimeDatabase>
0034 #include <QOperatingSystemVersion>
0035 #include <mimetypefinderjob.h>
0036 
0037 // For unit test purposes, to test both code paths in externalBrowser()
0038 KIOGUI_EXPORT bool openurljob_force_use_browserapp_kdeglobals = false;
0039 
0040 class KIO::OpenUrlJobPrivate
0041 {
0042 public:
0043     explicit OpenUrlJobPrivate(const QUrl &url, OpenUrlJob *qq)
0044         : m_url(url)
0045         , q(qq)
0046     {
0047         q->setCapabilities(KJob::Killable);
0048     }
0049 
0050     void emitAccessDenied();
0051     void runUrlWithMimeType();
0052     QString externalBrowser() const;
0053     bool runExternalBrowser(const QString &exe);
0054     void useSchemeHandler();
0055 
0056     QUrl m_url;
0057     KIO::OpenUrlJob *const q;
0058     QString m_suggestedFileName;
0059     QByteArray m_startupId;
0060     QString m_mimeTypeName;
0061     KService::Ptr m_preferredService;
0062     bool m_deleteTemporaryFile = false;
0063     bool m_runExecutables = false;
0064     bool m_showOpenOrExecuteDialog = false;
0065     bool m_externalBrowserEnabled = true;
0066     bool m_followRedirections = true;
0067 
0068 private:
0069     void executeCommand();
0070     void handleBinaries(const QMimeType &mimeType);
0071     void handleBinariesHelper(const QString &localPath, bool isNativeBinary);
0072     void handleDesktopFiles();
0073     void handleScripts();
0074     void openInPreferredApp();
0075     void runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName);
0076 
0077     void showOpenWithDialog();
0078     void showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished);
0079     void showUntrustedProgramWarningDialog(const QString &filePath);
0080 
0081     void startService(const KService::Ptr &service, const QList<QUrl> &urls);
0082     void startService(const KService::Ptr &service)
0083     {
0084         startService(service, {m_url});
0085     }
0086 };
0087 
0088 KIO::OpenUrlJob::OpenUrlJob(const QUrl &url, QObject *parent)
0089     : KCompositeJob(parent)
0090     , d(new OpenUrlJobPrivate(url, this))
0091 {
0092 }
0093 
0094 KIO::OpenUrlJob::OpenUrlJob(const QUrl &url, const QString &mimeType, QObject *parent)
0095     : KCompositeJob(parent)
0096     , d(new OpenUrlJobPrivate(url, this))
0097 {
0098     d->m_mimeTypeName = mimeType;
0099 }
0100 
0101 KIO::OpenUrlJob::~OpenUrlJob()
0102 {
0103 }
0104 
0105 void KIO::OpenUrlJob::setDeleteTemporaryFile(bool b)
0106 {
0107     d->m_deleteTemporaryFile = b;
0108 }
0109 
0110 void KIO::OpenUrlJob::setSuggestedFileName(const QString &suggestedFileName)
0111 {
0112     d->m_suggestedFileName = suggestedFileName;
0113 }
0114 
0115 void KIO::OpenUrlJob::setStartupId(const QByteArray &startupId)
0116 {
0117     d->m_startupId = startupId;
0118 }
0119 
0120 void KIO::OpenUrlJob::setRunExecutables(bool allow)
0121 {
0122     d->m_runExecutables = allow;
0123 }
0124 
0125 void KIO::OpenUrlJob::setShowOpenOrExecuteDialog(bool b)
0126 {
0127     d->m_showOpenOrExecuteDialog = b;
0128 }
0129 
0130 void KIO::OpenUrlJob::setEnableExternalBrowser(bool b)
0131 {
0132     d->m_externalBrowserEnabled = b;
0133 }
0134 
0135 void KIO::OpenUrlJob::setFollowRedirections(bool b)
0136 {
0137     d->m_followRedirections = b;
0138 }
0139 
0140 void KIO::OpenUrlJob::start()
0141 {
0142     if (!d->m_url.isValid() || d->m_url.scheme().isEmpty()) {
0143         const QString error = !d->m_url.isValid() ? d->m_url.errorString() : d->m_url.toDisplayString();
0144         setError(KIO::ERR_MALFORMED_URL);
0145         setErrorText(i18n("Malformed URL\n%1", error));
0146         emitResult();
0147         return;
0148     }
0149     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_url)) {
0150         d->emitAccessDenied();
0151         return;
0152     }
0153 
0154     auto qtOpenUrl = [this]() {
0155         if (!QDesktopServices::openUrl(d->m_url)) {
0156             // Is this an actual error, or USER_CANCELED?
0157             setError(KJob::UserDefinedError);
0158             setErrorText(i18n("Failed to open %1", d->m_url.toDisplayString()));
0159         }
0160         emitResult();
0161     };
0162 
0163 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
0164     if (d->m_externalBrowserEnabled) {
0165         // For Windows and MacOS, the mimetypes handling is different, so use QDesktopServices
0166         qtOpenUrl();
0167         return;
0168     }
0169 #endif
0170 
0171     if (d->m_externalBrowserEnabled && KSandbox::isInside()) {
0172         // Use the function from QDesktopServices as it handles portals correctly
0173         // Note that it falls back to "normal way" if the portal service isn't running.
0174         qtOpenUrl();
0175         return;
0176     }
0177 
0178     // If we know the MIME type, proceed
0179     if (!d->m_mimeTypeName.isEmpty()) {
0180         d->runUrlWithMimeType();
0181         return;
0182     }
0183 
0184     if (d->m_url.scheme().startsWith(QLatin1String("http"))) {
0185         if (d->m_externalBrowserEnabled) {
0186             const QString externalBrowser = d->externalBrowser();
0187             if (!externalBrowser.isEmpty() && d->runExternalBrowser(externalBrowser)) {
0188                 return;
0189             }
0190         }
0191     } else {
0192         if (KIO::DesktopExecParser::hasSchemeHandler(d->m_url)) {
0193             d->useSchemeHandler();
0194             return;
0195         }
0196     }
0197 
0198     auto *job = new KIO::MimeTypeFinderJob(d->m_url, this);
0199     job->setFollowRedirections(d->m_followRedirections);
0200     job->setSuggestedFileName(d->m_suggestedFileName);
0201     connect(job, &KJob::result, this, [job, this]() {
0202         const int errCode = job->error();
0203         if (errCode) {
0204             setError(errCode);
0205             setErrorText(job->errorText());
0206             emitResult();
0207         } else {
0208             d->m_suggestedFileName = job->suggestedFileName();
0209             d->m_mimeTypeName = job->mimeType();
0210             d->runUrlWithMimeType();
0211         }
0212     });
0213     job->start();
0214 }
0215 
0216 bool KIO::OpenUrlJob::doKill()
0217 {
0218     return true;
0219 }
0220 
0221 QString KIO::OpenUrlJobPrivate::externalBrowser() const
0222 {
0223     if (!m_externalBrowserEnabled) {
0224         return QString();
0225     }
0226 
0227     if (!openurljob_force_use_browserapp_kdeglobals) {
0228         KService::Ptr externalBrowser = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/https"));
0229         if (!externalBrowser) {
0230             externalBrowser = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/http"));
0231         }
0232         if (externalBrowser) {
0233             return externalBrowser->storageId();
0234         }
0235     }
0236 
0237     const QString browserApp = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication");
0238     return browserApp;
0239 }
0240 
0241 bool KIO::OpenUrlJobPrivate::runExternalBrowser(const QString &exec)
0242 {
0243     if (exec.startsWith(QLatin1Char('!'))) {
0244         // Literal command
0245         const QString command = QStringView(exec).mid(1) + QLatin1String(" %u");
0246         KService::Ptr service(new KService(QString(), command, QString()));
0247         startService(service);
0248         return true;
0249     } else {
0250         // Name of desktop file
0251         KService::Ptr service = KService::serviceByStorageId(exec);
0252         if (service) {
0253             startService(service);
0254             return true;
0255         }
0256     }
0257     return false;
0258 }
0259 
0260 void KIO::OpenUrlJobPrivate::useSchemeHandler()
0261 {
0262     // look for an application associated with x-scheme-handler/<protocol>
0263     const KService::Ptr service = KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + m_url.scheme());
0264     if (service) {
0265         startService(service);
0266         return;
0267     }
0268     // fallback, look for associated helper protocol
0269     Q_ASSERT(KProtocolInfo::isHelperProtocol(m_url.scheme()));
0270     const auto exec = KProtocolInfo::exec(m_url.scheme());
0271     if (exec.isEmpty()) {
0272         // use default MIME type opener for file
0273         m_mimeTypeName = KProtocolManager::defaultMimetype(m_url);
0274         runUrlWithMimeType();
0275     } else {
0276         KService::Ptr servicePtr(new KService(QString(), exec, QString()));
0277         startService(servicePtr);
0278     }
0279 }
0280 
0281 void KIO::OpenUrlJobPrivate::startService(const KService::Ptr &service, const QList<QUrl> &urls)
0282 {
0283     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
0284     job->setUrls(urls);
0285     job->setRunFlags(m_deleteTemporaryFile ? KIO::ApplicationLauncherJob::DeleteTemporaryFiles : KIO::ApplicationLauncherJob::RunFlags{});
0286     job->setSuggestedFileName(m_suggestedFileName);
0287     job->setStartupId(m_startupId);
0288     q->addSubjob(job);
0289     job->start();
0290 }
0291 
0292 void KIO::OpenUrlJobPrivate::runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName)
0293 {
0294     if (urlStr.isEmpty()) {
0295         q->setError(KJob::UserDefinedError);
0296         q->setErrorText(i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", filePath));
0297         q->emitResult();
0298         return;
0299     }
0300 
0301     m_url = QUrl::fromUserInput(urlStr);
0302     m_mimeTypeName.clear();
0303 
0304     // X-KDE-LastOpenedWith holds the service desktop entry name that
0305     // should be preferred for opening this URL if possible.
0306     // This is used by the Recent Documents menu for instance.
0307     if (!optionalServiceName.isEmpty()) {
0308         m_preferredService = KService::serviceByDesktopName(optionalServiceName);
0309     }
0310 
0311     // Restart from scratch with the target of the link
0312     q->start();
0313 }
0314 
0315 void KIO::OpenUrlJobPrivate::emitAccessDenied()
0316 {
0317     q->setError(KIO::ERR_ACCESS_DENIED);
0318     q->setErrorText(KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, m_url.toDisplayString()));
0319     q->emitResult();
0320 }
0321 
0322 // was: KRun::isExecutable (minus application/x-desktop MIME type).
0323 // Feel free to make public if needed.
0324 static bool isBinary(const QMimeType &mimeType)
0325 {
0326     // - Binaries could be e.g.:
0327     //   - application/x-executable
0328     //   - application/x-sharedlib e.g. /usr/bin/ls, see
0329     //     https://gitlab.freedesktop.org/xdg/shared-mime-info/-/issues/11
0330     //
0331     // - MIME types that inherit application/x-executable _and_ text/plain are scripts, these are
0332     //   handled by handleScripts()
0333 
0334     return (mimeType.inherits(QStringLiteral("application/x-executable")) || mimeType.inherits(QStringLiteral("application/x-sharedlib"))
0335             || mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")));
0336 }
0337 
0338 // Helper function that returns whether a file is a text-based script
0339 // e.g. ".sh", ".csh", ".py", ".js"
0340 static bool isTextScript(const QMimeType &mimeType)
0341 {
0342     return (mimeType.inherits(QStringLiteral("application/x-executable")) && mimeType.inherits(QStringLiteral("text/plain")));
0343 }
0344 
0345 // Helper function that returns whether a file has the execute bit set or not.
0346 static bool hasExecuteBit(const QString &fileName)
0347 {
0348     return QFileInfo(fileName).isExecutable();
0349 }
0350 
0351 // Handle native binaries (.e.g. /usr/bin/*); and .exe files
0352 void KIO::OpenUrlJobPrivate::handleBinaries(const QMimeType &mimeType)
0353 {
0354     if (!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)) {
0355         emitAccessDenied();
0356         return;
0357     }
0358 
0359     const bool isLocal = m_url.isLocalFile();
0360     // Don't run remote executables
0361     if (!isLocal) {
0362         q->setError(KJob::UserDefinedError);
0363         q->setErrorText(
0364             i18n("The executable file \"%1\" is located on a remote filesystem. "
0365                  "For safety reasons it will not be started.",
0366                  m_url.toDisplayString()));
0367         q->emitResult();
0368         return;
0369     }
0370 
0371     const QString localPath = m_url.toLocalFile();
0372 
0373     bool isNativeBinary = true;
0374 #ifndef Q_OS_WIN
0375     isNativeBinary = !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"));
0376 #endif
0377 
0378     if (m_showOpenOrExecuteDialog) {
0379         auto dialogFinished = [this, localPath, isNativeBinary](bool shouldExecute) {
0380             // shouldExecute is always true if we get here, because for binaries the
0381             // dialog only offers Execute/Cancel
0382             Q_UNUSED(shouldExecute)
0383 
0384             handleBinariesHelper(localPath, isNativeBinary);
0385         };
0386 
0387         // Ask the user for confirmation before executing this binary (for binaries
0388         // the dialog will only show Execute/Cancel)
0389         showOpenOrExecuteFileDialog(dialogFinished);
0390         return;
0391     }
0392 
0393     handleBinariesHelper(localPath, isNativeBinary);
0394 }
0395 
0396 void KIO::OpenUrlJobPrivate::handleBinariesHelper(const QString &localPath, bool isNativeBinary)
0397 {
0398     if (!m_runExecutables) {
0399         q->setError(KJob::UserDefinedError);
0400         q->setErrorText(i18n("For security reasons, launching executables is not allowed in this context."));
0401         q->emitResult();
0402         return;
0403     }
0404 
0405     // For local .exe files, open in the default app (e.g. WINE)
0406     if (!isNativeBinary) {
0407         openInPreferredApp();
0408         return;
0409     }
0410 
0411     // Native binaries
0412     if (!hasExecuteBit(localPath)) {
0413         // Show untrustedProgram dialog for local, native executables without the execute bit
0414         showUntrustedProgramWarningDialog(localPath);
0415         return;
0416     }
0417 
0418     // Local executable with execute bit, proceed
0419     executeCommand();
0420 }
0421 
0422 // For local, native executables (i.e. not shell scripts) without execute bit,
0423 // show a prompt asking the user if he wants to run the program.
0424 void KIO::OpenUrlJobPrivate::showUntrustedProgramWarningDialog(const QString &filePath)
0425 {
0426     auto *untrustedProgramHandler = KIO::delegateExtension<KIO::UntrustedProgramHandlerInterface *>(q);
0427     if (!untrustedProgramHandler) {
0428         // No way to ask the user to make it executable
0429         q->setError(KJob::UserDefinedError);
0430         q->setErrorText(i18n("The program \"%1\" needs to have executable permission before it can be launched.", filePath));
0431         q->emitResult();
0432         return;
0433     }
0434     QObject::connect(untrustedProgramHandler, &KIO::UntrustedProgramHandlerInterface::result, q, [=](bool result) {
0435         if (result) {
0436             QString errorString;
0437             if (untrustedProgramHandler->setExecuteBit(filePath, errorString)) {
0438                 executeCommand();
0439             } else {
0440                 q->setError(KJob::UserDefinedError);
0441                 q->setErrorText(i18n("Unable to make file \"%1\" executable.\n%2.", filePath, errorString));
0442                 q->emitResult();
0443             }
0444         } else {
0445             q->setError(KIO::ERR_USER_CANCELED);
0446             q->emitResult();
0447         }
0448     });
0449     untrustedProgramHandler->showUntrustedProgramWarning(q, m_url.fileName());
0450 }
0451 
0452 void KIO::OpenUrlJobPrivate::executeCommand()
0453 {
0454     // Execute the URL as a command. This is how we start scripts and executables
0455     KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(m_url.toLocalFile(), QStringList());
0456     job->setStartupId(m_startupId);
0457     job->setWorkingDirectory(m_url.adjusted(QUrl::RemoveFilename).toLocalFile());
0458     q->addSubjob(job);
0459     job->start();
0460 
0461     // TODO implement deleting the file if tempFile==true
0462     // CommandLauncherJob doesn't support that, unlike ApplicationLauncherJob
0463     // We'd have to do it in KProcessRunner.
0464 }
0465 
0466 void KIO::OpenUrlJobPrivate::runUrlWithMimeType()
0467 {
0468     // Tell the app, in case it wants us to stop here
0469     Q_EMIT q->mimeTypeFound(m_mimeTypeName);
0470     if (q->error() == KJob::KilledJobError) {
0471         q->emitResult();
0472         return;
0473     }
0474 
0475     // Support for preferred service setting, see setPreferredService
0476     if (m_preferredService && m_preferredService->hasMimeType(m_mimeTypeName)) {
0477         startService(m_preferredService);
0478         return;
0479     }
0480 
0481     // Scripts and executables
0482     QMimeDatabase db;
0483     const QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
0484 
0485     // .desktop files
0486     if (mimeType.inherits(QStringLiteral("application/x-desktop"))) {
0487         handleDesktopFiles();
0488         return;
0489     }
0490 
0491     // Scripts (e.g. .sh, .csh, .py, .js)
0492     if (isTextScript(mimeType)) {
0493         handleScripts();
0494         return;
0495     }
0496 
0497     // Binaries (e.g. /usr/bin/{konsole,ls}) and .exe files
0498     if (isBinary(mimeType)) {
0499         handleBinaries(mimeType);
0500         return;
0501     }
0502 
0503     // General case: look up associated application
0504     openInPreferredApp();
0505 }
0506 
0507 void KIO::OpenUrlJobPrivate::handleDesktopFiles()
0508 {
0509     // Open remote .desktop files in the default (text editor) app
0510     if (!m_url.isLocalFile()) {
0511         openInPreferredApp();
0512         return;
0513     }
0514 
0515     if (m_url.fileName() == QLatin1String(".directory") || m_mimeTypeName == QLatin1String("application/x-theme")) {
0516         // We cannot execute these files, open in the default app
0517         m_mimeTypeName = QStringLiteral("text/plain");
0518         openInPreferredApp();
0519         return;
0520     }
0521 
0522     const QString filePath = m_url.toLocalFile();
0523     KDesktopFile cfg(filePath);
0524     KConfigGroup cfgGroup = cfg.desktopGroup();
0525     if (!cfgGroup.hasKey("Type")) {
0526         q->setError(KJob::UserDefinedError);
0527         q->setErrorText(i18n("The desktop entry file %1 has no Type=... entry.", filePath));
0528         q->emitResult();
0529         openInPreferredApp();
0530         return;
0531     }
0532 
0533     if (cfg.hasLinkType()) {
0534         runLink(filePath, cfg.readUrl(), cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith"));
0535         return;
0536     }
0537 
0538     if ((cfg.hasApplicationType() || cfg.readType() == QLatin1String("Service"))) { // kio_settings lets users run Type=Service desktop files
0539         KService::Ptr service(new KService(filePath));
0540         if (!service->exec().isEmpty()) {
0541             if (m_showOpenOrExecuteDialog) { // Show the openOrExecute dialog
0542                 auto dialogFinished = [this, filePath, service](bool shouldExecute) {
0543                     if (shouldExecute) { // Run the file
0544                         startService(service, {});
0545                         return;
0546                     }
0547                     // The user selected "open"
0548                     openInPreferredApp();
0549                 };
0550 
0551                 showOpenOrExecuteFileDialog(dialogFinished);
0552                 return;
0553             }
0554 
0555             if (m_runExecutables) {
0556                 startService(service, {});
0557                 return;
0558             }
0559         } // exec is not empty
0560     } // type Application or Service
0561 
0562     // Fallback to opening in the default app
0563     openInPreferredApp();
0564 }
0565 
0566 void KIO::OpenUrlJobPrivate::handleScripts()
0567 {
0568     // Executable scripts of any type can run arbitrary shell commands
0569     if (!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)) {
0570         emitAccessDenied();
0571         return;
0572     }
0573 
0574     const bool isLocal = m_url.isLocalFile();
0575     const QString localPath = m_url.toLocalFile();
0576     if (!isLocal || !hasExecuteBit(localPath)) {
0577         // Open remote scripts or ones without the execute bit, with the default application
0578         openInPreferredApp();
0579         return;
0580     }
0581 
0582     if (m_showOpenOrExecuteDialog) {
0583         auto dialogFinished = [this](bool shouldExecute) {
0584             if (shouldExecute) {
0585                 executeCommand();
0586             } else {
0587                 openInPreferredApp();
0588             }
0589         };
0590 
0591         showOpenOrExecuteFileDialog(dialogFinished);
0592         return;
0593     }
0594 
0595     if (m_runExecutables) { // Local executable script, proceed
0596         executeCommand();
0597     } else { // Open in the default (text editor) app
0598         openInPreferredApp();
0599     }
0600 }
0601 
0602 void KIO::OpenUrlJobPrivate::openInPreferredApp()
0603 {
0604     KService::Ptr service = KApplicationTrader::preferredService(m_mimeTypeName);
0605     if (service) {
0606         startService(service);
0607     } else {
0608         // Avoid directly opening partial downloads and incomplete files
0609         // This is done here in the off chance the user actually has a default handler for it
0610         if (m_mimeTypeName == QLatin1String("application/x-partial-download")) {
0611             q->setError(KJob::UserDefinedError);
0612             q->setErrorText(
0613                 i18n("This file is incomplete and should not be opened.\n"
0614                      "Check your open applications and the notification area for any pending tasks or downloads."));
0615             q->emitResult();
0616             return;
0617         }
0618 
0619         showOpenWithDialog();
0620     }
0621 }
0622 
0623 void KIO::OpenUrlJobPrivate::showOpenWithDialog()
0624 {
0625     if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
0626         q->setError(KJob::UserDefinedError);
0627         q->setErrorText(i18n("You are not authorized to select an application to open this file."));
0628         q->emitResult();
0629         return;
0630     }
0631 
0632     auto *openWithHandler = KIO::delegateExtension<KIO::OpenWithHandlerInterface *>(q);
0633     if (!openWithHandler || QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows) {
0634         // As KDE on windows doesn't know about the windows default applications, offers will be empty in nearly all cases.
0635         // So we use QDesktopServices::openUrl to let windows decide how to open the file.
0636         // It's also our fallback if there's no handler to show an open-with dialog.
0637         if (!QDesktopServices::openUrl(m_url)) {
0638             q->setError(KJob::UserDefinedError);
0639             q->setErrorText(i18n("Failed to open the file."));
0640         }
0641         q->emitResult();
0642         return;
0643     }
0644 
0645     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::canceled, q, [this]() {
0646         q->setError(KIO::ERR_USER_CANCELED);
0647         q->emitResult();
0648     });
0649 
0650     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::serviceSelected, q, [this](const KService::Ptr &service) {
0651         startService(service);
0652     });
0653 
0654     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::handled, q, [this]() {
0655         q->emitResult();
0656     });
0657 
0658     openWithHandler->promptUserForApplication(q, {m_url}, m_mimeTypeName);
0659 }
0660 
0661 void KIO::OpenUrlJobPrivate::showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished)
0662 {
0663     QMimeDatabase db;
0664     QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
0665 
0666     auto *openOrExecuteFileHandler = KIO::delegateExtension<KIO::OpenOrExecuteFileInterface *>(q);
0667     if (!openOrExecuteFileHandler) {
0668         // No way to ask the user whether to execute or open
0669         if (isTextScript(mimeType) || mimeType.inherits(QStringLiteral("application/x-desktop"))) { // Open text-based ones in the default app
0670             openInPreferredApp();
0671         } else {
0672             q->setError(KJob::UserDefinedError);
0673             q->setErrorText(i18n("The program \"%1\" could not be launched.", m_url.toDisplayString(QUrl::PreferLocalFile)));
0674             q->emitResult();
0675         }
0676         return;
0677     }
0678 
0679     QObject::connect(openOrExecuteFileHandler, &KIO::OpenOrExecuteFileInterface::canceled, q, [this]() {
0680         q->setError(KIO::ERR_USER_CANCELED);
0681         q->emitResult();
0682     });
0683 
0684     QObject::connect(openOrExecuteFileHandler, &KIO::OpenOrExecuteFileInterface::executeFile, q, [this, dialogFinished](bool shouldExecute) {
0685         m_runExecutables = shouldExecute;
0686         dialogFinished(shouldExecute);
0687     });
0688 
0689     openOrExecuteFileHandler->promptUserOpenOrExecute(q, m_mimeTypeName);
0690 }
0691 
0692 void KIO::OpenUrlJob::slotResult(KJob *job)
0693 {
0694     // This is only used for the final application/launcher job, so we're done when it's done
0695     const int errCode = job->error();
0696     if (errCode) {
0697         setError(errCode);
0698         // We're a KJob, not a KIO::Job, so build the error string here
0699         setErrorText(KIO::buildErrorString(errCode, job->errorText()));
0700     }
0701     emitResult();
0702 }
0703 
0704 #include "moc_openurljob.cpp"