1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "openurljob.h"
10#include "commandlauncherjob.h"
11#include "desktopexecparser.h"
12#include "global.h"
13#include "job.h" // for buildErrorString
14#include "jobuidelegatefactory.h"
15#include "kiogui_debug.h"
16#include "openorexecutefileinterface.h"
17#include "openwithhandlerinterface.h"
18#include "untrustedprogramhandlerinterface.h"
19
20#include <KApplicationTrader>
21#include <KAuthorized>
22#include <KConfigGroup>
23#include <KDesktopFile>
24#include <KLocalizedString>
25#include <KSandbox>
26#include <KUrlAuthorized>
27#include <QFileInfo>
28
29#include <KProtocolManager>
30#include <KSharedConfig>
31#include <QDesktopServices>
32#include <QHostInfo>
33#include <QMimeDatabase>
34#include <QOperatingSystemVersion>
35#include <mimetypefinderjob.h>
36
37// For unit test purposes, to test both code paths in externalBrowser()
38KIOGUI_EXPORT bool openurljob_force_use_browserapp_kdeglobals = false;
39
40class KIO::OpenUrlJobPrivate
41{
42public:
43 explicit OpenUrlJobPrivate(const QUrl &url, OpenUrlJob *qq)
44 : m_url(url)
45 , q(qq)
46 {
47 q->setCapabilities(KJob::Killable);
48 }
49
50 void emitAccessDenied();
51 void runUrlWithMimeType();
52 QString externalBrowser() const;
53 bool runExternalBrowser(const QString &exe);
54 void useSchemeHandler();
55
56 QUrl m_url;
57 KIO::OpenUrlJob *const q;
58 QString m_suggestedFileName;
59 QByteArray m_startupId;
60 QString m_mimeTypeName;
61 KService::Ptr m_preferredService;
62 bool m_deleteTemporaryFile = false;
63 bool m_runExecutables = false;
64 bool m_showOpenOrExecuteDialog = false;
65 bool m_externalBrowserEnabled = true;
66 bool m_followRedirections = true;
67
68private:
69 void executeCommand();
70 void handleBinaries(const QMimeType &mimeType);
71 void handleBinariesHelper(const QString &localPath, bool isNativeBinary);
72 void handleDesktopFiles();
73 void handleScripts();
74 void openInPreferredApp();
75 void runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName);
76
77 void showOpenWithDialog();
78 void showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished);
79 void showUntrustedProgramWarningDialog(const QString &filePath);
80
81 void startService(const KService::Ptr &service, const QList<QUrl> &urls);
82 void startService(const KService::Ptr &service)
83 {
84 startService(service, urls: {m_url});
85 }
86};
87
88KIO::OpenUrlJob::OpenUrlJob(const QUrl &url, QObject *parent)
89 : KCompositeJob(parent)
90 , d(new OpenUrlJobPrivate(url, this))
91{
92}
93
94KIO::OpenUrlJob::OpenUrlJob(const QUrl &url, const QString &mimeType, QObject *parent)
95 : KCompositeJob(parent)
96 , d(new OpenUrlJobPrivate(url, this))
97{
98 d->m_mimeTypeName = mimeType;
99}
100
101KIO::OpenUrlJob::~OpenUrlJob()
102{
103}
104
105void KIO::OpenUrlJob::setDeleteTemporaryFile(bool b)
106{
107 d->m_deleteTemporaryFile = b;
108}
109
110void KIO::OpenUrlJob::setSuggestedFileName(const QString &suggestedFileName)
111{
112 d->m_suggestedFileName = suggestedFileName;
113}
114
115void KIO::OpenUrlJob::setStartupId(const QByteArray &startupId)
116{
117 d->m_startupId = startupId;
118}
119
120void KIO::OpenUrlJob::setRunExecutables(bool allow)
121{
122 d->m_runExecutables = allow;
123}
124
125void KIO::OpenUrlJob::setShowOpenOrExecuteDialog(bool b)
126{
127 d->m_showOpenOrExecuteDialog = b;
128}
129
130void KIO::OpenUrlJob::setEnableExternalBrowser(bool b)
131{
132 d->m_externalBrowserEnabled = b;
133}
134
135void KIO::OpenUrlJob::setFollowRedirections(bool b)
136{
137 d->m_followRedirections = b;
138}
139
140void KIO::OpenUrlJob::start()
141{
142 if (!d->m_url.isValid() || d->m_url.scheme().isEmpty()) {
143 const QString error = !d->m_url.isValid() ? d->m_url.errorString() : d->m_url.toDisplayString();
144 setError(KIO::ERR_MALFORMED_URL);
145 setErrorText(i18n("Malformed URL\n%1", error));
146 emitResult();
147 return;
148 }
149 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), baseUrl: QUrl(), destUrl: d->m_url)) {
150 d->emitAccessDenied();
151 return;
152 }
153
154 auto qtOpenUrl = [this]() {
155 if (!QDesktopServices::openUrl(url: d->m_url)) {
156 // Is this an actual error, or USER_CANCELED?
157 setError(KJob::UserDefinedError);
158 setErrorText(i18n("Failed to open %1", d->m_url.toDisplayString()));
159 }
160 emitResult();
161 };
162
163#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
164 if (d->m_externalBrowserEnabled) {
165 // For Windows and MacOS, the mimetypes handling is different, so use QDesktopServices
166 qtOpenUrl();
167 return;
168 }
169#endif
170
171 if (d->m_externalBrowserEnabled && KSandbox::isInside()) {
172 // Use the function from QDesktopServices as it handles portals correctly
173 // Note that it falls back to "normal way" if the portal service isn't running.
174 qtOpenUrl();
175 return;
176 }
177
178 // If we know the MIME type, proceed
179 if (!d->m_mimeTypeName.isEmpty()) {
180 d->runUrlWithMimeType();
181 return;
182 }
183
184 if (d->m_url.scheme().startsWith(s: QLatin1String("http"))) {
185 if (d->m_externalBrowserEnabled) {
186 const QString externalBrowser = d->externalBrowser();
187 if (!externalBrowser.isEmpty() && d->runExternalBrowser(exe: externalBrowser)) {
188 return;
189 }
190 }
191 } else {
192 if (KIO::DesktopExecParser::hasSchemeHandler(url: d->m_url)) {
193 d->useSchemeHandler();
194 return;
195 }
196 }
197
198 auto *job = new KIO::MimeTypeFinderJob(d->m_url, this);
199 job->setFollowRedirections(d->m_followRedirections);
200 job->setSuggestedFileName(d->m_suggestedFileName);
201 connect(sender: job, signal: &KJob::result, context: this, slot: [job, this]() {
202 const int errCode = job->error();
203 if (errCode) {
204 setError(errCode);
205 setErrorText(job->errorText());
206 emitResult();
207 } else {
208 d->m_suggestedFileName = job->suggestedFileName();
209 d->m_mimeTypeName = job->mimeType();
210 d->runUrlWithMimeType();
211 }
212 });
213 job->start();
214}
215
216bool KIO::OpenUrlJob::doKill()
217{
218 return true;
219}
220
221QString KIO::OpenUrlJobPrivate::externalBrowser() const
222{
223 if (!m_externalBrowserEnabled) {
224 return QString();
225 }
226
227 if (!openurljob_force_use_browserapp_kdeglobals) {
228 KService::Ptr externalBrowser = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/https"));
229 if (!externalBrowser) {
230 externalBrowser = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/http"));
231 }
232 if (externalBrowser) {
233 return externalBrowser->storageId();
234 }
235 }
236
237 const QString browserApp = KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")).readEntry(key: "BrowserApplication");
238 return browserApp;
239}
240
241bool KIO::OpenUrlJobPrivate::runExternalBrowser(const QString &exec)
242{
243 if (exec.startsWith(c: QLatin1Char('!'))) {
244 // Literal command
245 const QString command = QStringView(exec).mid(pos: 1) + QLatin1String(" %u");
246 KService::Ptr service(new KService(QString(), command, QString()));
247 startService(service);
248 return true;
249 } else {
250 // Name of desktop file
251 KService::Ptr service = KService::serviceByStorageId(storageId: exec);
252 if (service) {
253 startService(service);
254 return true;
255 }
256 }
257 return false;
258}
259
260void KIO::OpenUrlJobPrivate::useSchemeHandler()
261{
262 // look for an application associated with x-scheme-handler/<protocol>
263 const KService::Ptr service = KApplicationTrader::preferredService(mimeType: QLatin1String("x-scheme-handler/") + m_url.scheme());
264 if (service) {
265 startService(service);
266 return;
267 }
268 // fallback, look for associated helper protocol
269 Q_ASSERT(KProtocolInfo::isHelperProtocol(m_url.scheme()));
270 const auto exec = KProtocolInfo::exec(protocol: m_url.scheme());
271 if (exec.isEmpty()) {
272 // use default MIME type opener for file
273 m_mimeTypeName = KProtocolManager::defaultMimetype(url: m_url);
274 runUrlWithMimeType();
275 } else {
276 KService::Ptr servicePtr(new KService(QString(), exec, QString()));
277 startService(service: servicePtr);
278 }
279}
280
281void KIO::OpenUrlJobPrivate::startService(const KService::Ptr &service, const QList<QUrl> &urls)
282{
283 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
284 job->setUrls(urls);
285 job->setRunFlags(m_deleteTemporaryFile ? KIO::ApplicationLauncherJob::DeleteTemporaryFiles : KIO::ApplicationLauncherJob::RunFlags{});
286 job->setSuggestedFileName(m_suggestedFileName);
287 job->setStartupId(m_startupId);
288 q->addSubjob(job);
289 job->start();
290}
291
292void KIO::OpenUrlJobPrivate::runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName)
293{
294 if (urlStr.isEmpty()) {
295 q->setError(KJob::UserDefinedError);
296 q->setErrorText(i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", filePath));
297 q->emitResult();
298 return;
299 }
300
301 m_url = QUrl::fromUserInput(userInput: urlStr);
302 m_mimeTypeName.clear();
303
304 // X-KDE-LastOpenedWith holds the service desktop entry name that
305 // should be preferred for opening this URL if possible.
306 // This is used by the Recent Documents menu for instance.
307 if (!optionalServiceName.isEmpty()) {
308 m_preferredService = KService::serviceByDesktopName(name: optionalServiceName);
309 }
310
311 // Restart from scratch with the target of the link
312 q->start();
313}
314
315void KIO::OpenUrlJobPrivate::emitAccessDenied()
316{
317 q->setError(KIO::ERR_ACCESS_DENIED);
318 q->setErrorText(KIO::buildErrorString(errorCode: KIO::ERR_ACCESS_DENIED, errorText: m_url.toDisplayString()));
319 q->emitResult();
320}
321
322// was: KRun::isExecutable (minus application/x-desktop MIME type).
323// Feel free to make public if needed.
324static bool isBinary(const QMimeType &mimeType)
325{
326 // - Binaries could be e.g.:
327 // - application/x-executable
328 // - application/x-sharedlib e.g. /usr/bin/ls, see
329 // https://gitlab.freedesktop.org/xdg/shared-mime-info/-/issues/11
330 //
331 // - MIME types that inherit application/x-executable _and_ text/plain are scripts, these are
332 // handled by handleScripts()
333
334 return (mimeType.inherits(QStringLiteral("application/x-executable")) || mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")));
335}
336
337// Helper function that returns whether a file is a text-based script
338// e.g. ".sh", ".csh", ".py", ".js"
339static bool isTextScript(const QMimeType &mimeType)
340{
341 return (mimeType.inherits(QStringLiteral("application/x-executable")) && mimeType.inherits(QStringLiteral("text/plain")));
342}
343
344// Helper function that returns whether a file has the execute bit set or not.
345static bool hasExecuteBit(const QString &fileName)
346{
347 return QFileInfo(fileName).isExecutable();
348}
349
350bool KIO::OpenUrlJob::isExecutableFile(const QUrl &url, const QString &mimetypeString)
351{
352 if (!url.isLocalFile()) {
353 return false;
354 }
355
356 QMimeDatabase db;
357 QMimeType mimeType = db.mimeTypeForName(nameOrAlias: mimetypeString);
358 return (isBinary(mimeType) || isTextScript(mimeType)) && hasExecuteBit(fileName: url.toLocalFile());
359}
360
361// Handle native binaries (.e.g. /usr/bin/*); and .exe files
362void KIO::OpenUrlJobPrivate::handleBinaries(const QMimeType &mimeType)
363{
364 if (!KAuthorized::authorize(action: KAuthorized::SHELL_ACCESS)) {
365 emitAccessDenied();
366 return;
367 }
368
369 const bool isLocal = m_url.isLocalFile();
370 // Don't run remote executables
371 if (!isLocal) {
372 q->setError(KJob::UserDefinedError);
373 q->setErrorText(
374 i18n("The executable file \"%1\" is located on a remote filesystem. "
375 "For safety reasons it will not be started.",
376 m_url.toDisplayString()));
377 q->emitResult();
378 return;
379 }
380
381 const QString localPath = m_url.toLocalFile();
382
383 bool isNativeBinary = true;
384#ifndef Q_OS_WIN
385 isNativeBinary = !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"));
386#endif
387
388 if (m_showOpenOrExecuteDialog) {
389 auto dialogFinished = [this, localPath, isNativeBinary](bool shouldExecute) {
390 // shouldExecute is always true if we get here, because for binaries the
391 // dialog only offers Execute/Cancel
392 Q_UNUSED(shouldExecute)
393
394 handleBinariesHelper(localPath, isNativeBinary);
395 };
396
397 // Ask the user for confirmation before executing this binary (for binaries
398 // the dialog will only show Execute/Cancel)
399 showOpenOrExecuteFileDialog(dialogFinished);
400 return;
401 }
402
403 handleBinariesHelper(localPath, isNativeBinary);
404}
405
406void KIO::OpenUrlJobPrivate::handleBinariesHelper(const QString &localPath, bool isNativeBinary)
407{
408 if (!m_runExecutables) {
409 q->setError(KJob::UserDefinedError);
410 q->setErrorText(i18n("For security reasons, launching executables is not allowed in this context."));
411 q->emitResult();
412 return;
413 }
414
415 // For local .exe files, open in the default app (e.g. WINE)
416 if (!isNativeBinary) {
417 openInPreferredApp();
418 return;
419 }
420
421 // Native binaries
422 if (!hasExecuteBit(fileName: localPath)) {
423 // Show untrustedProgram dialog for local, native executables without the execute bit
424 showUntrustedProgramWarningDialog(filePath: localPath);
425 return;
426 }
427
428 // Local executable with execute bit, proceed
429 executeCommand();
430}
431
432// For local, native executables (i.e. not shell scripts) without execute bit,
433// show a prompt asking the user if he wants to run the program.
434void KIO::OpenUrlJobPrivate::showUntrustedProgramWarningDialog(const QString &filePath)
435{
436 auto *untrustedProgramHandler = KIO::delegateExtension<KIO::UntrustedProgramHandlerInterface *>(job: q);
437 if (!untrustedProgramHandler) {
438 // No way to ask the user to make it executable
439 q->setError(KJob::UserDefinedError);
440 q->setErrorText(i18n("The program \"%1\" needs to have executable permission before it can be launched.", filePath));
441 q->emitResult();
442 return;
443 }
444 QObject::connect(sender: untrustedProgramHandler, signal: &KIO::UntrustedProgramHandlerInterface::result, context: q, slot: [=, this](bool result) {
445 if (result) {
446 QString errorString;
447 if (untrustedProgramHandler->setExecuteBit(fileName: filePath, errorString)) {
448 executeCommand();
449 } else {
450 q->setError(KJob::UserDefinedError);
451 q->setErrorText(i18n("Unable to make file \"%1\" executable.\n%2.", filePath, errorString));
452 q->emitResult();
453 }
454 } else {
455 q->setError(KIO::ERR_USER_CANCELED);
456 q->emitResult();
457 }
458 });
459 untrustedProgramHandler->showUntrustedProgramWarning(job: q, programName: m_url.fileName());
460}
461
462void KIO::OpenUrlJobPrivate::executeCommand()
463{
464 // Execute the URL as a command. This is how we start scripts and executables
465 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(m_url.toLocalFile(), QStringList());
466 job->setStartupId(m_startupId);
467 job->setWorkingDirectory(m_url.adjusted(options: QUrl::RemoveFilename).toLocalFile());
468 q->addSubjob(job);
469 job->start();
470
471 // TODO implement deleting the file if tempFile==true
472 // CommandLauncherJob doesn't support that, unlike ApplicationLauncherJob
473 // We'd have to do it in KProcessRunner.
474}
475
476void KIO::OpenUrlJobPrivate::runUrlWithMimeType()
477{
478 // Tell the app, in case it wants us to stop here
479 Q_EMIT q->mimeTypeFound(mimeType: m_mimeTypeName);
480 if (q->error() == KJob::KilledJobError) {
481 q->emitResult();
482 return;
483 }
484
485 // Support for preferred service setting, see setPreferredService
486 if (m_preferredService && m_preferredService->hasMimeType(mimeType: m_mimeTypeName)) {
487 startService(service: m_preferredService);
488 return;
489 }
490
491 // Scripts and executables
492 QMimeDatabase db;
493 const QMimeType mimeType = db.mimeTypeForName(nameOrAlias: m_mimeTypeName);
494
495 // .desktop files
496 if (mimeType.inherits(QStringLiteral("application/x-desktop"))) {
497 handleDesktopFiles();
498 return;
499 }
500
501 // Scripts (e.g. .sh, .csh, .py, .js)
502 if (isTextScript(mimeType)) {
503 handleScripts();
504 return;
505 }
506
507 // Binaries (e.g. /usr/bin/{konsole,ls}) and .exe files
508 if (isBinary(mimeType)) {
509 handleBinaries(mimeType);
510 return;
511 }
512
513 // General case: look up associated application
514 openInPreferredApp();
515}
516
517void KIO::OpenUrlJobPrivate::handleDesktopFiles()
518{
519 // Open remote .desktop files in the default (text editor) app
520 if (!m_url.isLocalFile()) {
521 openInPreferredApp();
522 return;
523 }
524
525 if (m_url.fileName() == QLatin1String(".directory") || m_mimeTypeName == QLatin1String("application/x-theme")) {
526 // We cannot execute these files, open in the default app
527 m_mimeTypeName = QStringLiteral("text/plain");
528 openInPreferredApp();
529 return;
530 }
531
532 const QString filePath = m_url.toLocalFile();
533 KDesktopFile cfg(filePath);
534 KConfigGroup cfgGroup = cfg.desktopGroup();
535 if (!cfgGroup.hasKey(key: "Type")) {
536 q->setError(KJob::UserDefinedError);
537 q->setErrorText(i18n("The desktop entry file %1 has no Type=... entry.", filePath));
538 q->emitResult();
539 openInPreferredApp();
540 return;
541 }
542
543 if (cfg.hasLinkType()) {
544 runLink(filePath, urlStr: cfg.readUrl(), optionalServiceName: cfg.desktopGroup().readEntry(key: "X-KDE-LastOpenedWith"));
545 return;
546 }
547
548 if ((cfg.hasApplicationType() || cfg.readType() == QLatin1String("Service"))) { // kio_settings lets users run Type=Service desktop files
549 KService::Ptr service(new KService(filePath));
550 if (!service->exec().isEmpty()) {
551 if (m_showOpenOrExecuteDialog) { // Show the openOrExecute dialog
552 auto dialogFinished = [this, filePath, service](bool shouldExecute) {
553 if (shouldExecute) { // Run the file
554 startService(service, urls: {});
555 return;
556 }
557 // The user selected "open"
558 openInPreferredApp();
559 };
560
561 showOpenOrExecuteFileDialog(dialogFinished);
562 return;
563 }
564
565 if (m_runExecutables) {
566 startService(service, urls: {});
567 return;
568 }
569 } // exec is not empty
570 } // type Application or Service
571
572 // Fallback to opening in the default app
573 openInPreferredApp();
574}
575
576void KIO::OpenUrlJobPrivate::handleScripts()
577{
578 // Executable scripts of any type can run arbitrary shell commands
579 if (!KAuthorized::authorize(action: KAuthorized::SHELL_ACCESS)) {
580 emitAccessDenied();
581 return;
582 }
583
584 const bool isLocal = m_url.isLocalFile();
585 const QString localPath = m_url.toLocalFile();
586 if (!isLocal || !hasExecuteBit(fileName: localPath)) {
587 // Open remote scripts or ones without the execute bit, with the default application
588 openInPreferredApp();
589 return;
590 }
591
592 if (m_showOpenOrExecuteDialog) {
593 auto dialogFinished = [this](bool shouldExecute) {
594 if (shouldExecute) {
595 executeCommand();
596 } else {
597 openInPreferredApp();
598 }
599 };
600
601 showOpenOrExecuteFileDialog(dialogFinished);
602 return;
603 }
604
605 if (m_runExecutables) { // Local executable script, proceed
606 executeCommand();
607 } else { // Open in the default (text editor) app
608 openInPreferredApp();
609 }
610}
611
612void KIO::OpenUrlJobPrivate::openInPreferredApp()
613{
614 KService::Ptr service = KApplicationTrader::preferredService(mimeType: m_mimeTypeName);
615 if (service) {
616 startService(service);
617 } else {
618 // Avoid directly opening partial downloads and incomplete files
619 // This is done here in the off chance the user actually has a default handler for it
620 if (m_mimeTypeName == QLatin1String("application/x-partial-download")) {
621 q->setError(KJob::UserDefinedError);
622 q->setErrorText(
623 i18n("This file is incomplete and should not be opened.\n"
624 "Check your open applications and the notification area for any pending tasks or downloads."));
625 q->emitResult();
626 return;
627 }
628
629 showOpenWithDialog();
630 }
631}
632
633void KIO::OpenUrlJobPrivate::showOpenWithDialog()
634{
635 if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
636 q->setError(KJob::UserDefinedError);
637 q->setErrorText(i18n("You are not authorized to select an application to open this file."));
638 q->emitResult();
639 return;
640 }
641
642 auto *openWithHandler = KIO::delegateExtension<KIO::OpenWithHandlerInterface *>(job: q);
643 if (!openWithHandler || QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows) {
644 // As KDE on windows doesn't know about the windows default applications, offers will be empty in nearly all cases.
645 // So we use QDesktopServices::openUrl to let windows decide how to open the file.
646 // It's also our fallback if there's no handler to show an open-with dialog.
647 if (!QDesktopServices::openUrl(url: m_url)) {
648 q->setError(KJob::UserDefinedError);
649 q->setErrorText(i18n("Failed to open the file."));
650 }
651 q->emitResult();
652 return;
653 }
654
655 QObject::connect(sender: openWithHandler, signal: &KIO::OpenWithHandlerInterface::canceled, context: q, slot: [this]() {
656 q->setError(KIO::ERR_USER_CANCELED);
657 q->emitResult();
658 });
659
660 QObject::connect(sender: openWithHandler, signal: &KIO::OpenWithHandlerInterface::serviceSelected, context: q, slot: [this](const KService::Ptr &service) {
661 startService(service);
662 });
663
664 QObject::connect(sender: openWithHandler, signal: &KIO::OpenWithHandlerInterface::handled, context: q, slot: [this]() {
665 q->emitResult();
666 });
667
668 openWithHandler->promptUserForApplication(job: q, urls: {m_url}, mimeType: m_mimeTypeName);
669}
670
671void KIO::OpenUrlJobPrivate::showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished)
672{
673 QMimeDatabase db;
674 QMimeType mimeType = db.mimeTypeForName(nameOrAlias: m_mimeTypeName);
675
676 auto *openOrExecuteFileHandler = KIO::delegateExtension<KIO::OpenOrExecuteFileInterface *>(job: q);
677 if (!openOrExecuteFileHandler) {
678 // No way to ask the user whether to execute or open
679 if (isTextScript(mimeType) || mimeType.inherits(QStringLiteral("application/x-desktop"))) { // Open text-based ones in the default app
680 openInPreferredApp();
681 } else {
682 q->setError(KJob::UserDefinedError);
683 q->setErrorText(i18n("The program \"%1\" could not be launched.", m_url.toDisplayString(QUrl::PreferLocalFile)));
684 q->emitResult();
685 }
686 return;
687 }
688
689 QObject::connect(sender: openOrExecuteFileHandler, signal: &KIO::OpenOrExecuteFileInterface::canceled, context: q, slot: [this]() {
690 q->setError(KIO::ERR_USER_CANCELED);
691 q->emitResult();
692 });
693
694 QObject::connect(sender: openOrExecuteFileHandler, signal: &KIO::OpenOrExecuteFileInterface::executeFile, context: q, slot: [this, dialogFinished](bool shouldExecute) {
695 m_runExecutables = shouldExecute;
696 dialogFinished(shouldExecute);
697 });
698
699 openOrExecuteFileHandler->promptUserOpenOrExecute(job: q, mimetype: m_mimeTypeName);
700}
701
702void KIO::OpenUrlJob::slotResult(KJob *job)
703{
704 // This is only used for the final application/launcher job, so we're done when it's done
705 const int errCode = job->error();
706 if (errCode) {
707 setError(errCode);
708 // We're a KJob, not a KIO::Job, so build the error string here
709 setErrorText(KIO::buildErrorString(errorCode: errCode, errorText: job->errorText()));
710 }
711 emitResult();
712}
713
714#include "moc_openurljob.cpp"
715

source code of kio/src/gui/openurljob.cpp