File indexing completed on 2023-11-26 07:34:14
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