File indexing completed on 2024-04-21 03:55:29

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(), QStringLiteral("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-ms-dos-executable")));
0335 }
0336 
0337 // Helper function that returns whether a file is a text-based script
0338 // e.g. ".sh", ".csh", ".py", ".js"
0339 static bool isTextScript(const QMimeType &mimeType)
0340 {
0341     return (mimeType.inherits(QStringLiteral("application/x-executable")) && mimeType.inherits(QStringLiteral("text/plain")));
0342 }
0343 
0344 // Helper function that returns whether a file has the execute bit set or not.
0345 static bool hasExecuteBit(const QString &fileName)
0346 {
0347     return QFileInfo(fileName).isExecutable();
0348 }
0349 
0350 bool KIO::OpenUrlJob::isExecutableFile(const QUrl &url, const QString &mimetypeString)
0351 {
0352     if (!url.isLocalFile()) {
0353         return false;
0354     }
0355 
0356     QMimeDatabase db;
0357     QMimeType mimeType = db.mimeTypeForName(mimetypeString);
0358     return (isBinary(mimeType) || isTextScript(mimeType)) && hasExecuteBit(url.toLocalFile());
0359 }
0360 
0361 // Handle native binaries (.e.g. /usr/bin/*); and .exe files
0362 void KIO::OpenUrlJobPrivate::handleBinaries(const QMimeType &mimeType)
0363 {
0364     if (!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)) {
0365         emitAccessDenied();
0366         return;
0367     }
0368 
0369     const bool isLocal = m_url.isLocalFile();
0370     // Don't run remote executables
0371     if (!isLocal) {
0372         q->setError(KJob::UserDefinedError);
0373         q->setErrorText(
0374             i18n("The executable file \"%1\" is located on a remote filesystem. "
0375                  "For safety reasons it will not be started.",
0376                  m_url.toDisplayString()));
0377         q->emitResult();
0378         return;
0379     }
0380 
0381     const QString localPath = m_url.toLocalFile();
0382 
0383     bool isNativeBinary = true;
0384 #ifndef Q_OS_WIN
0385     isNativeBinary = !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"));
0386 #endif
0387 
0388     if (m_showOpenOrExecuteDialog) {
0389         auto dialogFinished = [this, localPath, isNativeBinary](bool shouldExecute) {
0390             // shouldExecute is always true if we get here, because for binaries the
0391             // dialog only offers Execute/Cancel
0392             Q_UNUSED(shouldExecute)
0393 
0394             handleBinariesHelper(localPath, isNativeBinary);
0395         };
0396 
0397         // Ask the user for confirmation before executing this binary (for binaries
0398         // the dialog will only show Execute/Cancel)
0399         showOpenOrExecuteFileDialog(dialogFinished);
0400         return;
0401     }
0402 
0403     handleBinariesHelper(localPath, isNativeBinary);
0404 }
0405 
0406 void KIO::OpenUrlJobPrivate::handleBinariesHelper(const QString &localPath, bool isNativeBinary)
0407 {
0408     if (!m_runExecutables) {
0409         q->setError(KJob::UserDefinedError);
0410         q->setErrorText(i18n("For security reasons, launching executables is not allowed in this context."));
0411         q->emitResult();
0412         return;
0413     }
0414 
0415     // For local .exe files, open in the default app (e.g. WINE)
0416     if (!isNativeBinary) {
0417         openInPreferredApp();
0418         return;
0419     }
0420 
0421     // Native binaries
0422     if (!hasExecuteBit(localPath)) {
0423         // Show untrustedProgram dialog for local, native executables without the execute bit
0424         showUntrustedProgramWarningDialog(localPath);
0425         return;
0426     }
0427 
0428     // Local executable with execute bit, proceed
0429     executeCommand();
0430 }
0431 
0432 // For local, native executables (i.e. not shell scripts) without execute bit,
0433 // show a prompt asking the user if he wants to run the program.
0434 void KIO::OpenUrlJobPrivate::showUntrustedProgramWarningDialog(const QString &filePath)
0435 {
0436     auto *untrustedProgramHandler = KIO::delegateExtension<KIO::UntrustedProgramHandlerInterface *>(q);
0437     if (!untrustedProgramHandler) {
0438         // No way to ask the user to make it executable
0439         q->setError(KJob::UserDefinedError);
0440         q->setErrorText(i18n("The program \"%1\" needs to have executable permission before it can be launched.", filePath));
0441         q->emitResult();
0442         return;
0443     }
0444     QObject::connect(untrustedProgramHandler, &KIO::UntrustedProgramHandlerInterface::result, q, [=](bool result) {
0445         if (result) {
0446             QString errorString;
0447             if (untrustedProgramHandler->setExecuteBit(filePath, errorString)) {
0448                 executeCommand();
0449             } else {
0450                 q->setError(KJob::UserDefinedError);
0451                 q->setErrorText(i18n("Unable to make file \"%1\" executable.\n%2.", filePath, errorString));
0452                 q->emitResult();
0453             }
0454         } else {
0455             q->setError(KIO::ERR_USER_CANCELED);
0456             q->emitResult();
0457         }
0458     });
0459     untrustedProgramHandler->showUntrustedProgramWarning(q, m_url.fileName());
0460 }
0461 
0462 void KIO::OpenUrlJobPrivate::executeCommand()
0463 {
0464     // Execute the URL as a command. This is how we start scripts and executables
0465     KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(m_url.toLocalFile(), QStringList());
0466     job->setStartupId(m_startupId);
0467     job->setWorkingDirectory(m_url.adjusted(QUrl::RemoveFilename).toLocalFile());
0468     q->addSubjob(job);
0469     job->start();
0470 
0471     // TODO implement deleting the file if tempFile==true
0472     // CommandLauncherJob doesn't support that, unlike ApplicationLauncherJob
0473     // We'd have to do it in KProcessRunner.
0474 }
0475 
0476 void KIO::OpenUrlJobPrivate::runUrlWithMimeType()
0477 {
0478     // Tell the app, in case it wants us to stop here
0479     Q_EMIT q->mimeTypeFound(m_mimeTypeName);
0480     if (q->error() == KJob::KilledJobError) {
0481         q->emitResult();
0482         return;
0483     }
0484 
0485     // Support for preferred service setting, see setPreferredService
0486     if (m_preferredService && m_preferredService->hasMimeType(m_mimeTypeName)) {
0487         startService(m_preferredService);
0488         return;
0489     }
0490 
0491     // Scripts and executables
0492     QMimeDatabase db;
0493     const QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
0494 
0495     // .desktop files
0496     if (mimeType.inherits(QStringLiteral("application/x-desktop"))) {
0497         handleDesktopFiles();
0498         return;
0499     }
0500 
0501     // Scripts (e.g. .sh, .csh, .py, .js)
0502     if (isTextScript(mimeType)) {
0503         handleScripts();
0504         return;
0505     }
0506 
0507     // Binaries (e.g. /usr/bin/{konsole,ls}) and .exe files
0508     if (isBinary(mimeType)) {
0509         handleBinaries(mimeType);
0510         return;
0511     }
0512 
0513     // General case: look up associated application
0514     openInPreferredApp();
0515 }
0516 
0517 void KIO::OpenUrlJobPrivate::handleDesktopFiles()
0518 {
0519     // Open remote .desktop files in the default (text editor) app
0520     if (!m_url.isLocalFile()) {
0521         openInPreferredApp();
0522         return;
0523     }
0524 
0525     if (m_url.fileName() == QLatin1String(".directory") || m_mimeTypeName == QLatin1String("application/x-theme")) {
0526         // We cannot execute these files, open in the default app
0527         m_mimeTypeName = QStringLiteral("text/plain");
0528         openInPreferredApp();
0529         return;
0530     }
0531 
0532     const QString filePath = m_url.toLocalFile();
0533     KDesktopFile cfg(filePath);
0534     KConfigGroup cfgGroup = cfg.desktopGroup();
0535     if (!cfgGroup.hasKey("Type")) {
0536         q->setError(KJob::UserDefinedError);
0537         q->setErrorText(i18n("The desktop entry file %1 has no Type=... entry.", filePath));
0538         q->emitResult();
0539         openInPreferredApp();
0540         return;
0541     }
0542 
0543     if (cfg.hasLinkType()) {
0544         runLink(filePath, cfg.readUrl(), cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith"));
0545         return;
0546     }
0547 
0548     if ((cfg.hasApplicationType() || cfg.readType() == QLatin1String("Service"))) { // kio_settings lets users run Type=Service desktop files
0549         KService::Ptr service(new KService(filePath));
0550         if (!service->exec().isEmpty()) {
0551             if (m_showOpenOrExecuteDialog) { // Show the openOrExecute dialog
0552                 auto dialogFinished = [this, filePath, service](bool shouldExecute) {
0553                     if (shouldExecute) { // Run the file
0554                         startService(service, {});
0555                         return;
0556                     }
0557                     // The user selected "open"
0558                     openInPreferredApp();
0559                 };
0560 
0561                 showOpenOrExecuteFileDialog(dialogFinished);
0562                 return;
0563             }
0564 
0565             if (m_runExecutables) {
0566                 startService(service, {});
0567                 return;
0568             }
0569         } // exec is not empty
0570     } // type Application or Service
0571 
0572     // Fallback to opening in the default app
0573     openInPreferredApp();
0574 }
0575 
0576 void KIO::OpenUrlJobPrivate::handleScripts()
0577 {
0578     // Executable scripts of any type can run arbitrary shell commands
0579     if (!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)) {
0580         emitAccessDenied();
0581         return;
0582     }
0583 
0584     const bool isLocal = m_url.isLocalFile();
0585     const QString localPath = m_url.toLocalFile();
0586     if (!isLocal || !hasExecuteBit(localPath)) {
0587         // Open remote scripts or ones without the execute bit, with the default application
0588         openInPreferredApp();
0589         return;
0590     }
0591 
0592     if (m_showOpenOrExecuteDialog) {
0593         auto dialogFinished = [this](bool shouldExecute) {
0594             if (shouldExecute) {
0595                 executeCommand();
0596             } else {
0597                 openInPreferredApp();
0598             }
0599         };
0600 
0601         showOpenOrExecuteFileDialog(dialogFinished);
0602         return;
0603     }
0604 
0605     if (m_runExecutables) { // Local executable script, proceed
0606         executeCommand();
0607     } else { // Open in the default (text editor) app
0608         openInPreferredApp();
0609     }
0610 }
0611 
0612 void KIO::OpenUrlJobPrivate::openInPreferredApp()
0613 {
0614     KService::Ptr service = KApplicationTrader::preferredService(m_mimeTypeName);
0615     if (service) {
0616         startService(service);
0617     } else {
0618         // Avoid directly opening partial downloads and incomplete files
0619         // This is done here in the off chance the user actually has a default handler for it
0620         if (m_mimeTypeName == QLatin1String("application/x-partial-download")) {
0621             q->setError(KJob::UserDefinedError);
0622             q->setErrorText(
0623                 i18n("This file is incomplete and should not be opened.\n"
0624                      "Check your open applications and the notification area for any pending tasks or downloads."));
0625             q->emitResult();
0626             return;
0627         }
0628 
0629         showOpenWithDialog();
0630     }
0631 }
0632 
0633 void KIO::OpenUrlJobPrivate::showOpenWithDialog()
0634 {
0635     if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
0636         q->setError(KJob::UserDefinedError);
0637         q->setErrorText(i18n("You are not authorized to select an application to open this file."));
0638         q->emitResult();
0639         return;
0640     }
0641 
0642     auto *openWithHandler = KIO::delegateExtension<KIO::OpenWithHandlerInterface *>(q);
0643     if (!openWithHandler || QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows) {
0644         // As KDE on windows doesn't know about the windows default applications, offers will be empty in nearly all cases.
0645         // So we use QDesktopServices::openUrl to let windows decide how to open the file.
0646         // It's also our fallback if there's no handler to show an open-with dialog.
0647         if (!QDesktopServices::openUrl(m_url)) {
0648             q->setError(KJob::UserDefinedError);
0649             q->setErrorText(i18n("Failed to open the file."));
0650         }
0651         q->emitResult();
0652         return;
0653     }
0654 
0655     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::canceled, q, [this]() {
0656         q->setError(KIO::ERR_USER_CANCELED);
0657         q->emitResult();
0658     });
0659 
0660     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::serviceSelected, q, [this](const KService::Ptr &service) {
0661         startService(service);
0662     });
0663 
0664     QObject::connect(openWithHandler, &KIO::OpenWithHandlerInterface::handled, q, [this]() {
0665         q->emitResult();
0666     });
0667 
0668     openWithHandler->promptUserForApplication(q, {m_url}, m_mimeTypeName);
0669 }
0670 
0671 void KIO::OpenUrlJobPrivate::showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished)
0672 {
0673     QMimeDatabase db;
0674     QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
0675 
0676     auto *openOrExecuteFileHandler = KIO::delegateExtension<KIO::OpenOrExecuteFileInterface *>(q);
0677     if (!openOrExecuteFileHandler) {
0678         // No way to ask the user whether to execute or open
0679         if (isTextScript(mimeType) || mimeType.inherits(QStringLiteral("application/x-desktop"))) { // Open text-based ones in the default app
0680             openInPreferredApp();
0681         } else {
0682             q->setError(KJob::UserDefinedError);
0683             q->setErrorText(i18n("The program \"%1\" could not be launched.", m_url.toDisplayString(QUrl::PreferLocalFile)));
0684             q->emitResult();
0685         }
0686         return;
0687     }
0688 
0689     QObject::connect(openOrExecuteFileHandler, &KIO::OpenOrExecuteFileInterface::canceled, q, [this]() {
0690         q->setError(KIO::ERR_USER_CANCELED);
0691         q->emitResult();
0692     });
0693 
0694     QObject::connect(openOrExecuteFileHandler, &KIO::OpenOrExecuteFileInterface::executeFile, q, [this, dialogFinished](bool shouldExecute) {
0695         m_runExecutables = shouldExecute;
0696         dialogFinished(shouldExecute);
0697     });
0698 
0699     openOrExecuteFileHandler->promptUserOpenOrExecute(q, m_mimeTypeName);
0700 }
0701 
0702 void KIO::OpenUrlJob::slotResult(KJob *job)
0703 {
0704     // This is only used for the final application/launcher job, so we're done when it's done
0705     const int errCode = job->error();
0706     if (errCode) {
0707         setError(errCode);
0708         // We're a KJob, not a KIO::Job, so build the error string here
0709         setErrorText(KIO::buildErrorString(errCode, job->errorText()));
0710     }
0711     emitResult();
0712 }
0713 
0714 #include "moc_openurljob.cpp"