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