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