File indexing completed on 2024-04-28 15:27:27

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
0004     SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2009 Michael Pyne <michael.pyne@kdemail.net>
0006     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "krun.h"
0012 #include <kio/job.h>
0013 
0014 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0015 #include "kio_widgets_debug.h"
0016 #include "krun_p.h"
0017 
0018 #include <assert.h>
0019 #include <qplatformdefs.h>
0020 #include <string.h>
0021 #include <typeinfo>
0022 
0023 #include <QApplication>
0024 #include <QDebug>
0025 #include <QDesktopServices>
0026 #include <QDesktopWidget>
0027 #include <QDialog>
0028 #include <QDialogButtonBox>
0029 #include <QFile>
0030 #include <QFileInfo>
0031 #include <QHostInfo>
0032 #include <QMimeDatabase>
0033 #include <QStandardPaths>
0034 #include <QWidget>
0035 
0036 #include "applicationlauncherjob.h"
0037 #include "jobuidelegate.h"
0038 #include "kdesktopfileactions.h"
0039 #include "kio/global.h"
0040 #include "kopenwithdialog.h"
0041 #include "kprocessrunner_p.h" // for KIOGuiPrivate::checkStartupNotify
0042 #include "krecentdocument.h"
0043 #include "widgetsuntrustedprogramhandler.h"
0044 #include <kio/desktopexecparser.h>
0045 
0046 #include <KApplicationTrader>
0047 #include <KJobUiDelegate>
0048 #include <jobuidelegatefactory.h>
0049 
0050 #include <KAuthorized>
0051 #include <KConfigGroup>
0052 #include <KDesktopFile>
0053 #include <KGuiItem>
0054 #include <KJobWidgets>
0055 #include <KLocalizedString>
0056 #include <KMessageBox>
0057 #include <KProcess>
0058 #include <KSandbox>
0059 #include <KSharedConfig>
0060 #include <KShell>
0061 #include <KStandardGuiItem>
0062 
0063 #include <KIO/JobUiDelegate>
0064 #include <KIO/OpenUrlJob>
0065 #include <commandlauncherjob.h>
0066 #include <kprotocolmanager.h>
0067 #include <kurlauthorized.h>
0068 
0069 #ifdef Q_OS_WIN
0070 #include "widgetsopenwithhandler_win.cpp" // displayNativeOpenWithDialog
0071 #endif
0072 
0073 KRunPrivate::KRunPrivate(KRun *parent)
0074     : q(parent)
0075     , m_showingDialog(false)
0076 {
0077 }
0078 
0079 void KRunPrivate::startTimer()
0080 {
0081     m_timer->start(0);
0082 }
0083 
0084 // ---------------------------------------------------------------------------
0085 
0086 static KService::Ptr schemeService(const QString &protocol)
0087 {
0088     return KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + protocol);
0089 }
0090 
0091 qint64 KRunPrivate::runCommandLauncherJob(KIO::CommandLauncherJob *job, QWidget *widget)
0092 {
0093     QObject *receiver = widget ? static_cast<QObject *>(widget) : static_cast<QObject *>(qApp);
0094     QObject::connect(job, &KJob::result, receiver, [widget](KJob *job) {
0095         if (job->error()) {
0096             QEventLoopLocker locker;
0097             KMessageBox::error(widget, job->errorString());
0098         }
0099     });
0100     job->start();
0101     job->waitForStarted();
0102     return job->error() ? 0 : job->pid();
0103 }
0104 
0105 // ---------------------------------------------------------------------------
0106 
0107 // Helper function that returns whether a file has the execute bit set or not.
0108 static bool hasExecuteBit(const QString &fileName)
0109 {
0110     QFileInfo file(fileName);
0111     return file.isExecutable();
0112 }
0113 
0114 bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype)
0115 {
0116     if (!url.isLocalFile()) {
0117         return false;
0118     }
0119 
0120     // While isExecutable performs similar check to this one, some users depend on
0121     // this method not returning true for application/x-desktop
0122     QMimeDatabase db;
0123     QMimeType mimeType = db.mimeTypeForName(mimetype);
0124     if (!mimeType.inherits(QStringLiteral("application/x-executable")) /* clang-format off */
0125         && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"))
0126         && !mimeType.inherits(QStringLiteral("application/x-executable-script"))
0127         && !mimeType.inherits(QStringLiteral("application/x-sharedlib"))) { /* clang-format on */
0128         return false;
0129     }
0130 
0131     if (!hasExecuteBit(url.toLocalFile()) && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"))) {
0132         return false;
0133     }
0134 
0135     return true;
0136 }
0137 
0138 void KRun::handleInitError(int kioErrorCode, const QString &errorMsg)
0139 {
0140     Q_UNUSED(kioErrorCode);
0141     d->m_showingDialog = true;
0142     KMessageBox::error(d->m_window, errorMsg);
0143     d->m_showingDialog = false;
0144 }
0145 
0146 void KRun::handleError(KJob *job)
0147 {
0148     Q_ASSERT(job);
0149     if (job) {
0150         d->m_showingDialog = true;
0151         job->uiDelegate()->showErrorMessage();
0152         d->m_showingDialog = false;
0153     }
0154 }
0155 
0156 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 31)
0157 bool KRun::runUrl(const QUrl &url,
0158                   const QString &mimetype,
0159                   QWidget *window,
0160                   bool tempFile,
0161                   bool runExecutables,
0162                   const QString &suggestedFileName,
0163                   const QByteArray &asn)
0164 {
0165     RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags();
0166     if (runExecutables) {
0167         flags |= KRun::RunExecutables;
0168     }
0169 
0170     return runUrl(url, mimetype, window, flags, suggestedFileName, asn);
0171 }
0172 #endif
0173 
0174 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0175 // This is called by foundMimeType, since it knows the MIME type of the URL
0176 bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn)
0177 {
0178     const bool runExecutables = flags.testFlag(KRun::RunExecutables);
0179     const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles);
0180 
0181     KIO::OpenUrlJob *job = new KIO::OpenUrlJob(u, _mimetype);
0182     job->setSuggestedFileName(suggestedFileName);
0183     job->setStartupId(asn);
0184     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window));
0185     job->setDeleteTemporaryFile(tempFile);
0186     job->setRunExecutables(runExecutables);
0187     job->start();
0188     return true;
0189 }
0190 #endif
0191 
0192 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0193 bool KRun::displayOpenWithDialog(const QList<QUrl> &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn)
0194 {
0195     if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
0196         KMessageBox::information(window, i18n("You are not authorized to select an application to open this file."));
0197         return false;
0198     }
0199 
0200 #ifdef Q_OS_WIN
0201     KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("KOpenWithDialog Settings"));
0202     if (cfgGroup.readEntry("Native", true)) {
0203         return displayNativeOpenWithDialog(lst, window);
0204     }
0205 #endif
0206 
0207     // TODO : pass the MIME type as a parameter, to show it (comment field) in the dialog !
0208     // Note KOpenWithDialog::setMimeTypeFromUrls already guesses the MIME type if lst.size() == 1
0209     KOpenWithDialog dialog(lst, QString(), QString(), window);
0210     dialog.setWindowModality(Qt::WindowModal);
0211     if (dialog.exec()) {
0212         KService::Ptr service = dialog.service();
0213         if (!service) {
0214             // qDebug() << "No service set, running " << dialog.text();
0215             service = KService::Ptr(new KService(QString() /*name*/, dialog.text(), QString() /*icon*/));
0216         }
0217         const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags();
0218         return KRun::runApplication(*service, lst, window, flags, suggestedFileName, asn);
0219     }
0220     return false;
0221 }
0222 #endif
0223 
0224 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 0)
0225 void KRun::shellQuote(QString &_str)
0226 {
0227     // Credits to Walter, says Bernd G. :)
0228     if (_str.isEmpty()) { // Don't create an explicit empty parameter
0229         return;
0230     }
0231     const QChar q = QLatin1Char('\'');
0232     _str.replace(q, QLatin1String("'\\''")).prepend(q).append(q);
0233 }
0234 #endif
0235 
0236 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 0)
0237 QStringList KRun::processDesktopExec(const KService &_service, const QList<QUrl> &_urls, bool tempFiles, const QString &suggestedFileName)
0238 {
0239     KIO::DesktopExecParser parser(_service, _urls);
0240     parser.setUrlsAreTempFiles(tempFiles);
0241     parser.setSuggestedFileName(suggestedFileName);
0242     return parser.resultingArguments();
0243 }
0244 #endif
0245 
0246 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 0)
0247 QString KRun::binaryName(const QString &execLine, bool removePath)
0248 {
0249     return removePath ? KIO::DesktopExecParser::executableName(execLine) : KIO::DesktopExecParser::executablePath(execLine);
0250 }
0251 #endif
0252 
0253 // This code is also used in klauncher.
0254 // TODO: port klauncher to KIOGuiPrivate::checkStartupNotify once this lands
0255 // TODO: then deprecate this method, and remove in KF6
0256 bool KRun::checkStartupNotify(const QString & /*binName*/, const KService *service, bool *silent_arg, QByteArray *wmclass_arg)
0257 {
0258     return KIOGuiPrivate::checkStartupNotify(service, silent_arg, wmclass_arg);
0259 }
0260 
0261 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 6)
0262 bool KRun::run(const KService &_service, const QList<QUrl> &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn)
0263 {
0264     const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags();
0265     return runApplication(_service, _urls, window, flags, suggestedFileName, asn) != 0;
0266 }
0267 #endif
0268 
0269 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0270 qint64
0271 KRun::runApplication(const KService &service, const QList<QUrl> &urls, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn)
0272 {
0273     KService::Ptr servicePtr(new KService(service)); // clone
0274     // QTBUG-59017 Calling winId() on an embedded widget will break interaction
0275     // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using
0276     // its parent window instead
0277     if (window) {
0278         window = window->window();
0279     }
0280 
0281     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(servicePtr);
0282     job->setUrls(urls);
0283     if (flags & DeleteTemporaryFiles) {
0284         job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
0285     }
0286     job->setSuggestedFileName(suggestedFileName);
0287     job->setStartupId(asn);
0288     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window));
0289     job->start();
0290     job->waitForStarted();
0291     return job->error() ? 0 : job->pid();
0292 }
0293 #endif
0294 
0295 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0296 qint64
0297 KRun::runService(const KService &_service, const QList<QUrl> &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn)
0298 {
0299     return runApplication(_service, _urls, window, tempFiles ? RunFlags(DeleteTemporaryFiles) : RunFlags(), suggestedFileName, asn);
0300 }
0301 #endif
0302 
0303 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0304 bool KRun::run(const QString &_exec, const QList<QUrl> &_urls, QWidget *window, const QString &_name, const QString &_icon, const QByteArray &asn)
0305 {
0306     KService::Ptr service(new KService(_name, _exec, _icon));
0307 
0308     return runApplication(*service, _urls, window, RunFlags{}, QString(), asn);
0309 }
0310 #endif
0311 
0312 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0313 bool KRun::runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory)
0314 {
0315     if (cmd.isEmpty()) {
0316         qCWarning(KIO_WIDGETS) << "Command was empty, nothing to run";
0317         return false;
0318     }
0319 
0320     const QStringList args = KShell::splitArgs(cmd);
0321     if (args.isEmpty()) {
0322         qCWarning(KIO_WIDGETS) << "Command could not be parsed.";
0323         return false;
0324     }
0325 
0326     const QString &bin = args.first();
0327     return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory);
0328 }
0329 #endif
0330 
0331 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0332 bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn)
0333 {
0334     return runCommand(cmd, execName, iconName, window, asn, QString());
0335 }
0336 #endif
0337 
0338 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0339 bool KRun::runCommand(const QString &cmd,
0340                       const QString &execName,
0341                       const QString &iconName,
0342                       QWidget *window,
0343                       const QByteArray &asn,
0344                       const QString &workingDirectory)
0345 {
0346     auto *job = new KIO::CommandLauncherJob(cmd);
0347     job->setExecutable(execName);
0348     job->setIcon(iconName);
0349     job->setStartupId(asn);
0350     job->setWorkingDirectory(workingDirectory);
0351 
0352     if (window) {
0353         window = window->window();
0354     }
0355     return KRunPrivate::runCommandLauncherJob(job, window);
0356 }
0357 #endif
0358 
0359 KRun::KRun(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn)
0360     : d(new KRunPrivate(this))
0361 {
0362     d->m_timer = new QTimer(this);
0363     d->m_timer->setObjectName(QStringLiteral("KRun::timer"));
0364     d->m_timer->setSingleShot(true);
0365     d->init(url, window, showProgressInfo, asn);
0366 }
0367 
0368 void KRunPrivate::init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn)
0369 {
0370     m_bFault = false;
0371     m_bAutoDelete = true;
0372     m_bProgressInfo = showProgressInfo;
0373     m_bFinished = false;
0374     m_job = nullptr;
0375     m_strURL = url;
0376     m_bScanFile = false;
0377     m_bIsDirectory = false;
0378     m_runExecutables = true;
0379     m_followRedirections = true;
0380     m_window = window;
0381     m_asn = asn;
0382     q->setEnableExternalBrowser(true);
0383 
0384     // Start the timer. This means we will return to the event
0385     // loop and do initialization afterwards.
0386     // Reason: We must complete the constructor before we do anything else.
0387     m_bCheckPrompt = false;
0388     m_bInit = true;
0389     q->connect(m_timer, &QTimer::timeout, q, &KRun::slotTimeout);
0390     startTimer();
0391     // qDebug() << "new KRun" << q << url << "timer=" << m_timer;
0392 }
0393 
0394 void KRun::init()
0395 {
0396     // qDebug() << "INIT called";
0397     if (!d->m_strURL.isValid() || d->m_strURL.scheme().isEmpty()) {
0398         const QString error = !d->m_strURL.isValid() ? d->m_strURL.errorString() : d->m_strURL.toString();
0399         handleInitError(KIO::ERR_MALFORMED_URL, i18n("Malformed URL\n%1", error));
0400         qCWarning(KIO_WIDGETS) << "Malformed URL:" << error;
0401         d->m_bFault = true;
0402         d->m_bFinished = true;
0403         d->startTimer();
0404         return;
0405     }
0406     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_strURL)) {
0407         QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.toDisplayString());
0408         handleInitError(KIO::ERR_ACCESS_DENIED, msg);
0409         d->m_bFault = true;
0410         d->m_bFinished = true;
0411         d->startTimer();
0412         return;
0413     }
0414 
0415     if (d->m_externalBrowserEnabled && KSandbox::isInside()) {
0416         // use the function from QDesktopServices as it handles portals correctly
0417         d->m_bFault = !QDesktopServices::openUrl(d->m_strURL);
0418         d->m_bFinished = true;
0419         d->startTimer();
0420         return;
0421     }
0422 
0423     if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) {
0424         if (d->runExternalBrowser(d->m_externalBrowser)) {
0425             return;
0426         }
0427     } else if (d->m_strURL.isLocalFile()
0428                && (d->m_strURL.host().isEmpty() || (d->m_strURL.host() == QLatin1String("localhost"))
0429                    || (d->m_strURL.host().compare(QHostInfo::localHostName(), Qt::CaseInsensitive) == 0))) {
0430         const QString localPath = d->m_strURL.toLocalFile();
0431         if (!QFile::exists(localPath)) {
0432             handleInitError(KIO::ERR_DOES_NOT_EXIST,
0433                             i18n("<qt>Unable to run the command specified. "
0434                                  "The file or folder <b>%1</b> does not exist.</qt>",
0435                                  localPath.toHtmlEscaped()));
0436             d->m_bFault = true;
0437             d->m_bFinished = true;
0438             d->startTimer();
0439             return;
0440         }
0441 
0442         QMimeDatabase db;
0443         QMimeType mime = db.mimeTypeForUrl(d->m_strURL);
0444         // qDebug() << "MIME TYPE is " << mime.name();
0445         if (mime.isDefault() && !QFileInfo(localPath).isReadable()) {
0446             // Unknown MIME type because the file is unreadable, no point in showing an open-with dialog (#261002)
0447             const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, localPath);
0448             handleInitError(KIO::ERR_ACCESS_DENIED, msg);
0449             d->m_bFault = true;
0450             d->m_bFinished = true;
0451             d->startTimer();
0452             return;
0453         } else {
0454             mimeTypeDetermined(mime.name());
0455             return;
0456         }
0457     } else if (KIO::DesktopExecParser::hasSchemeHandler(d->m_strURL)) {
0458         // looks for an application associated with x-scheme-handler/<protocol>
0459         const KService::Ptr service = schemeService(d->m_strURL.scheme());
0460         if (service) {
0461             //  if there's one...
0462             if (runApplication(*service, QList<QUrl>() << d->m_strURL, d->m_window, RunFlags{}, QString(), d->m_asn)) {
0463                 d->m_bFinished = true;
0464                 d->startTimer();
0465                 return;
0466             }
0467         } else {
0468             // fallback, look for associated helper protocol
0469             Q_ASSERT(KProtocolInfo::isHelperProtocol(d->m_strURL.scheme()));
0470             const auto exec = KProtocolInfo::exec(d->m_strURL.scheme());
0471             if (exec.isEmpty()) {
0472                 // use default MIME type opener for file
0473                 mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
0474                 return;
0475             } else {
0476                 if (run(exec, QList<QUrl>() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) {
0477                     d->m_bFinished = true;
0478                     d->startTimer();
0479                     return;
0480                 }
0481             }
0482         }
0483     }
0484 
0485     // Let's see whether it is a directory
0486 
0487     if (!KProtocolManager::supportsListing(d->m_strURL)) {
0488         // No support for listing => it can't be a directory (example: http)
0489 
0490         if (!KProtocolManager::supportsReading(d->m_strURL)) {
0491             // No support for reading files either => we can't do anything (example: mailto URL, with no associated app)
0492             handleInitError(KIO::ERR_UNSUPPORTED_ACTION, i18n("Could not find any application or handler for %1", d->m_strURL.toDisplayString()));
0493             d->m_bFault = true;
0494             d->m_bFinished = true;
0495             d->startTimer();
0496             return;
0497         }
0498         scanFile();
0499         return;
0500     }
0501 
0502     // qDebug() << "Testing directory (stating)";
0503 
0504     // It may be a directory or a file, let's stat
0505     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
0506     KIO::StatJob *job = KIO::statDetails(d->m_strURL, KIO::StatJob::SourceSide, KIO::StatBasic, flags);
0507     KJobWidgets::setWindow(job, d->m_window);
0508     connect(job, &KJob::result, this, &KRun::slotStatResult);
0509     d->m_job = job;
0510     // qDebug() << "Job" << job << "is about stating" << d->m_strURL;
0511 }
0512 
0513 KRun::~KRun()
0514 {
0515     // qDebug() << this;
0516     d->m_timer->stop();
0517     killJob();
0518     // qDebug() << this << "done";
0519 }
0520 
0521 bool KRunPrivate::runExternalBrowser(const QString &_exec)
0522 {
0523     QList<QUrl> urls;
0524     urls.append(m_strURL);
0525     if (_exec.startsWith(QLatin1Char('!'))) {
0526         // Literal command
0527         const QString exec = QStringView(_exec).mid(1) + QLatin1String(" %u");
0528         if (KRun::run(exec, urls, m_window, QString(), QString(), m_asn)) {
0529             m_bFinished = true;
0530             startTimer();
0531             return true;
0532         }
0533     } else {
0534         KService::Ptr service = KService::serviceByStorageId(_exec);
0535         if (service && KRun::runApplication(*service, urls, m_window, KRun::RunFlags{}, QString(), m_asn)) {
0536             m_bFinished = true;
0537             startTimer();
0538             return true;
0539         }
0540     }
0541     return false;
0542 }
0543 
0544 void KRunPrivate::showPrompt()
0545 {
0546     ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(promptMode(), q->window());
0547     dialog->setAttribute(Qt::WA_DeleteOnClose);
0548     QObject::connect(dialog, &ExecutableFileOpenDialog::finished, q, [this, dialog](int result) {
0549         onDialogFinished(result, dialog->isDontAskAgainChecked());
0550     });
0551     dialog->show();
0552 }
0553 
0554 bool KRunPrivate::isPromptNeeded()
0555 {
0556     if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) {
0557         return false;
0558     }
0559     const QMimeDatabase db;
0560     const QMimeType mime = db.mimeTypeForUrl(m_strURL);
0561 
0562     const bool isFileExecutable = (KRun::isExecutableFile(m_strURL, mime.name()) || mime.inherits(QStringLiteral("application/x-desktop")));
0563 
0564     if (isFileExecutable) {
0565         KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts");
0566         const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk");
0567 
0568         if (value == QLatin1String("alwaysAsk")) {
0569             return true;
0570         } else {
0571             q->setRunExecutables(value == QLatin1String("execute"));
0572         }
0573     }
0574 
0575     return false;
0576 }
0577 
0578 ExecutableFileOpenDialog::Mode KRunPrivate::promptMode()
0579 {
0580     const QMimeDatabase db;
0581     const QMimeType mime = db.mimeTypeForUrl(m_strURL);
0582 
0583     if (mime.inherits(QStringLiteral("text/plain"))) {
0584         return ExecutableFileOpenDialog::OpenOrExecute;
0585     }
0586 #ifndef Q_OS_WIN
0587     if (mime.inherits(QStringLiteral("application/x-ms-dos-executable"))) {
0588         return ExecutableFileOpenDialog::OpenAsExecute;
0589     }
0590 #endif
0591     return ExecutableFileOpenDialog::OnlyExecute;
0592 }
0593 
0594 void KRunPrivate::onDialogFinished(int result, bool isDontAskAgainSet)
0595 {
0596     if (result == ExecutableFileOpenDialog::Rejected) {
0597         m_bFinished = true;
0598         m_bInit = false;
0599         startTimer();
0600         return;
0601     }
0602     q->setRunExecutables(result == ExecutableFileOpenDialog::ExecuteFile);
0603 
0604     if (isDontAskAgainSet) {
0605         QString output = result == ExecutableFileOpenDialog::OpenFile ? QStringLiteral("open") : QStringLiteral("execute");
0606         KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts");
0607         cfgGroup.writeEntry("behaviourOnLaunch", output);
0608     }
0609     startTimer();
0610 }
0611 
0612 void KRun::scanFile()
0613 {
0614     // qDebug() << d->m_strURL;
0615     // First, let's check for well-known extensions
0616     // Not when there is a query in the URL, in any case.
0617     if (!d->m_strURL.hasQuery()) {
0618         QMimeDatabase db;
0619         QMimeType mime = db.mimeTypeForUrl(d->m_strURL);
0620         if (!mime.isDefault() || d->m_strURL.isLocalFile()) {
0621             // qDebug() << "Scanfile: MIME TYPE is " << mime.name();
0622             mimeTypeDetermined(mime.name());
0623             return;
0624         }
0625     }
0626 
0627     // No MIME type found, and the URL is not local  (or fast mode not allowed).
0628     // We need to apply the 'KIO' method, i.e. either asking the server or
0629     // getting some data out of the file, to know what MIME type it is.
0630 
0631     if (!KProtocolManager::supportsReading(d->m_strURL)) {
0632         qCWarning(KIO_WIDGETS) << "#### NO SUPPORT FOR READING!";
0633         d->m_bFault = true;
0634         d->m_bFinished = true;
0635         d->startTimer();
0636         return;
0637     }
0638     // qDebug() << this << "Scanning file" << d->m_strURL;
0639 
0640     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
0641     KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
0642     KJobWidgets::setWindow(job, d->m_window);
0643     connect(job, &KJob::result, this, &KRun::slotScanFinished);
0644     connect(job, &KIO::TransferJob::mimeTypeFound, this, &KRun::slotScanMimeType);
0645     d->m_job = job;
0646     // qDebug() << "Job" << job << "is about getting from" << d->m_strURL;
0647 }
0648 
0649 // When arriving in that method there are 6 possible states:
0650 // must_show_prompt, must_init, must_scan_file, found_dir, done+error or done+success.
0651 void KRun::slotTimeout()
0652 {
0653     if (d->m_bCheckPrompt) {
0654         d->m_bCheckPrompt = false;
0655         if (d->isPromptNeeded()) {
0656             d->showPrompt();
0657             return;
0658         }
0659     }
0660     if (d->m_bInit) {
0661         d->m_bInit = false;
0662         init();
0663         return;
0664     }
0665 
0666     if (d->m_bFault) {
0667         Q_EMIT error();
0668     }
0669     if (d->m_bFinished) {
0670         Q_EMIT finished();
0671     } else {
0672         if (d->m_bScanFile) {
0673             d->m_bScanFile = false;
0674             scanFile();
0675             return;
0676         } else if (d->m_bIsDirectory) {
0677             d->m_bIsDirectory = false;
0678             mimeTypeDetermined(QStringLiteral("inode/directory"));
0679             return;
0680         }
0681     }
0682 
0683     if (d->m_bAutoDelete) {
0684         deleteLater();
0685         return;
0686     }
0687 }
0688 
0689 void KRun::slotStatResult(KJob *job)
0690 {
0691     d->m_job = nullptr;
0692     const int errCode = job->error();
0693     if (errCode) {
0694         // ERR_NO_CONTENT is not an error, but an indication no further
0695         // actions needs to be taken.
0696         if (errCode != KIO::ERR_NO_CONTENT) {
0697             qCWarning(KIO_WIDGETS) << this << "ERROR" << job->error() << job->errorString();
0698             handleError(job);
0699             // qDebug() << this << " KRun returning from showErrorDialog, starting timer to delete us";
0700             d->m_bFault = true;
0701         }
0702 
0703         d->m_bFinished = true;
0704 
0705         // will emit the error and autodelete this
0706         d->startTimer();
0707     } else {
0708         // qDebug() << "Finished";
0709 
0710         KIO::StatJob *statJob = qobject_cast<KIO::StatJob *>(job);
0711         if (!statJob) {
0712             qFatal("Fatal Error: job is a %s, should be a StatJob", typeid(*job).name());
0713         }
0714 
0715         // Update our URL in case of a redirection
0716         setUrl(statJob->url());
0717 
0718         const KIO::UDSEntry entry = statJob->statResult();
0719         const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
0720         if ((mode & QT_STAT_MASK) == QT_STAT_DIR) {
0721             d->m_bIsDirectory = true; // it's a dir
0722         } else {
0723             d->m_bScanFile = true; // it's a file
0724         }
0725 
0726         d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
0727 
0728         // MIME type already known? (e.g. print:/manager)
0729         const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
0730 
0731         if (!knownMimeType.isEmpty()) {
0732             mimeTypeDetermined(knownMimeType);
0733             d->m_bFinished = true;
0734         }
0735 
0736         // We should have found something
0737         assert(d->m_bScanFile || d->m_bIsDirectory);
0738 
0739         // Start the timer. Once we get the timer event this
0740         // protocol server is back in the pool and we can reuse it.
0741         // This gives better performance than starting a new slave
0742         d->startTimer();
0743     }
0744 }
0745 
0746 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
0747 {
0748     if (mimetype.isEmpty()) {
0749         qCWarning(KIO_WIDGETS) << "get() didn't emit a MIME type! Probably a KIO worker bug, please check the implementation of" << url().scheme();
0750     }
0751     mimeTypeDetermined(mimetype);
0752     d->m_job = nullptr;
0753 }
0754 
0755 void KRun::slotScanFinished(KJob *job)
0756 {
0757     d->m_job = nullptr;
0758     const int errCode = job->error();
0759     if (errCode) {
0760         // ERR_NO_CONTENT is not an error, but an indication no further
0761         // actions needs to be taken.
0762         if (errCode != KIO::ERR_NO_CONTENT) {
0763             qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
0764             handleError(job);
0765 
0766             d->m_bFault = true;
0767         }
0768 
0769         d->m_bFinished = true;
0770         // will emit the error and autodelete this
0771         d->startTimer();
0772     }
0773 }
0774 
0775 void KRun::mimeTypeDetermined(const QString &mimeType)
0776 {
0777     // foundMimeType reimplementations might show a dialog box;
0778     // make sure some timer doesn't kill us meanwhile (#137678, #156447)
0779     Q_ASSERT(!d->m_showingDialog);
0780     d->m_showingDialog = true;
0781 
0782     foundMimeType(mimeType);
0783 
0784     d->m_showingDialog = false;
0785 
0786     // We cannot assume that we're finished here. Some reimplementations
0787     // start a KIO job and call setFinished only later.
0788 }
0789 
0790 void KRun::foundMimeType(const QString &type)
0791 {
0792     // qDebug() << "Resulting MIME type is " << type;
0793 
0794     QMimeDatabase db;
0795 
0796     KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
0797     if (job) {
0798         // Update our URL in case of a redirection
0799         if (d->m_followRedirections) {
0800             setUrl(job->url());
0801         }
0802 
0803         job->putOnHold();
0804         d->m_job = nullptr;
0805     }
0806 
0807     Q_ASSERT(!d->m_bFinished);
0808 
0809     // Support for preferred service setting, see setPreferredService
0810     if (!d->m_preferredService.isEmpty()) {
0811         // qDebug() << "Attempting to open with preferred service: " << d->m_preferredService;
0812         KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
0813         if (serv && serv->hasMimeType(type)) {
0814             QList<QUrl> lst;
0815             lst.append(d->m_strURL);
0816             if (KRun::runApplication(*serv, lst, d->m_window, RunFlags{}, QString(), d->m_asn)) {
0817                 setFinished(true);
0818                 return;
0819             }
0820             /// Note: if that service failed, we'll go to runUrl below to
0821             /// maybe find another service, even though an error dialog box was
0822             /// already displayed. That's good if runUrl tries another service,
0823             /// but it's not good if it tries the same one :}
0824         }
0825     }
0826 
0827     // Resolve .desktop files from media:/, remote:/, applications:/ etc.
0828     QMimeType mime = db.mimeTypeForName(type);
0829     if (!mime.isValid()) {
0830         qCWarning(KIO_WIDGETS) << "Unknown MIME type" << type;
0831     } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) {
0832         d->m_strURL = QUrl::fromLocalFile(d->m_localPath);
0833     }
0834 
0835     KRun::RunFlags runFlags;
0836     if (d->m_runExecutables) {
0837         runFlags |= KRun::RunExecutables;
0838     }
0839     if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) {
0840         d->m_bFault = true;
0841     }
0842     setFinished(true);
0843 }
0844 
0845 void KRun::killJob()
0846 {
0847     if (d->m_job) {
0848         // qDebug() << this << "m_job=" << d->m_job;
0849         d->m_job->kill();
0850         d->m_job = nullptr;
0851     }
0852 }
0853 
0854 void KRun::abort()
0855 {
0856     if (d->m_bFinished) {
0857         return;
0858     }
0859     // qDebug() << this << "m_showingDialog=" << d->m_showingDialog;
0860     killJob();
0861     // If we're showing an error message box, the rest will be done
0862     // after closing the msgbox -> don't autodelete nor emit signals now.
0863     if (d->m_showingDialog) {
0864         return;
0865     }
0866     d->m_bFault = true;
0867     d->m_bFinished = true;
0868     d->m_bInit = false;
0869     d->m_bScanFile = false;
0870 
0871     // will emit the error and autodelete this
0872     d->startTimer();
0873 }
0874 
0875 QWidget *KRun::window() const
0876 {
0877     return d->m_window;
0878 }
0879 
0880 bool KRun::hasError() const
0881 {
0882     return d->m_bFault;
0883 }
0884 
0885 bool KRun::hasFinished() const
0886 {
0887     return d->m_bFinished;
0888 }
0889 
0890 bool KRun::autoDelete() const
0891 {
0892     return d->m_bAutoDelete;
0893 }
0894 
0895 void KRun::setAutoDelete(bool b)
0896 {
0897     d->m_bAutoDelete = b;
0898 }
0899 
0900 void KRun::setEnableExternalBrowser(bool b)
0901 {
0902     d->m_externalBrowserEnabled = b;
0903     if (d->m_externalBrowserEnabled) {
0904         d->m_externalBrowser = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication");
0905 
0906         // If a default browser isn't set in kdeglobals, fall back to mimeapps.list
0907         if (!d->m_externalBrowser.isEmpty()) {
0908             return;
0909         }
0910 
0911         KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
0912         KConfigGroup defaultApps(profile, "Default Applications");
0913 
0914         d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/https");
0915         if (d->m_externalBrowser.isEmpty()) {
0916             d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/http");
0917         }
0918     } else {
0919         d->m_externalBrowser.clear();
0920     }
0921 }
0922 
0923 void KRun::setPreferredService(const QString &desktopEntryName)
0924 {
0925     d->m_preferredService = desktopEntryName;
0926 }
0927 
0928 void KRun::setRunExecutables(bool b)
0929 {
0930     d->m_runExecutables = b;
0931 }
0932 
0933 void KRun::setSuggestedFileName(const QString &fileName)
0934 {
0935     d->m_suggestedFileName = fileName;
0936 }
0937 
0938 void KRun::setShowScriptExecutionPrompt(bool showPrompt)
0939 {
0940     d->m_bCheckPrompt = showPrompt;
0941 }
0942 
0943 void KRun::setFollowRedirections(bool followRedirections)
0944 {
0945     d->m_followRedirections = followRedirections;
0946 }
0947 
0948 QString KRun::suggestedFileName() const
0949 {
0950     return d->m_suggestedFileName;
0951 }
0952 
0953 bool KRun::isExecutable(const QString &mimeTypeName)
0954 {
0955     QMimeDatabase db;
0956     QMimeType mimeType = db.mimeTypeForName(mimeTypeName);
0957     return (mimeType.inherits(QStringLiteral("application/x-desktop")) || mimeType.inherits(QStringLiteral("application/x-executable")) ||
0958             /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */
0959             mimeType.inherits(QStringLiteral("application/x-sharedlib")) || mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"))
0960             || mimeType.inherits(QStringLiteral("application/x-shellscript")));
0961 }
0962 
0963 void KRun::setUrl(const QUrl &url)
0964 {
0965     d->m_strURL = url;
0966 }
0967 
0968 QUrl KRun::url() const
0969 {
0970     return d->m_strURL;
0971 }
0972 
0973 void KRun::setError(bool error)
0974 {
0975     d->m_bFault = error;
0976 }
0977 
0978 void KRun::setProgressInfo(bool progressInfo)
0979 {
0980     d->m_bProgressInfo = progressInfo;
0981 }
0982 
0983 bool KRun::progressInfo() const
0984 {
0985     return d->m_bProgressInfo;
0986 }
0987 
0988 void KRun::setFinished(bool finished)
0989 {
0990     d->m_bFinished = finished;
0991     if (finished) {
0992         d->startTimer();
0993     }
0994 }
0995 
0996 void KRun::setJob(KIO::Job *job)
0997 {
0998     d->m_job = job;
0999 }
1000 
1001 KIO::Job *KRun::job()
1002 {
1003     return d->m_job;
1004 }
1005 
1006 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 4)
1007 QTimer &KRun::timer()
1008 {
1009     return *d->m_timer;
1010 }
1011 #endif
1012 
1013 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1)
1014 void KRun::setDoScanFile(bool scanFile)
1015 {
1016     d->m_bScanFile = scanFile;
1017 }
1018 #endif
1019 
1020 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1)
1021 bool KRun::doScanFile() const
1022 {
1023     return d->m_bScanFile;
1024 }
1025 #endif
1026 
1027 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1)
1028 void KRun::setIsDirecory(bool isDirectory)
1029 {
1030     d->m_bIsDirectory = isDirectory;
1031 }
1032 #endif
1033 
1034 bool KRun::isDirectory() const
1035 {
1036     return d->m_bIsDirectory;
1037 }
1038 
1039 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1)
1040 void KRun::setInitializeNextAction(bool initialize)
1041 {
1042     d->m_bInit = initialize;
1043 }
1044 #endif
1045 
1046 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 1)
1047 bool KRun::initializeNextAction() const
1048 {
1049     return d->m_bInit;
1050 }
1051 #endif
1052 
1053 bool KRun::isLocalFile() const
1054 {
1055     return d->m_strURL.isLocalFile();
1056 }
1057 
1058 #include "moc_krun.cpp"
1059 #endif