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

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