KIO

openurljob.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2020 David Faure <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include "openurljob.h"
9 #include "openwithhandlerinterface.h"
10 #include "openorexecutefileinterface.h"
11 #include "global.h"
12 #include "job.h" // for buildErrorString
13 #include "commandlauncherjob.h"
14 #include "desktopexecparser.h"
15 #include "untrustedprogramhandlerinterface.h"
16 #include "kiogui_debug.h"
17 
18 #include <KApplicationTrader>
19 #include <KAuthorized>
20 #include <KConfigGroup>
21 #include <KDesktopFile>
22 #include <KLocalizedString>
23 #include <KUrlAuthorized>
24 #include <QFileInfo>
25 
26 #include <KProtocolManager>
27 #include <KSharedConfig>
28 #include <QDesktopServices>
29 #include <QHostInfo>
30 #include <QMimeDatabase>
31 #include <QOperatingSystemVersion>
32 #include <QTimer>
33 
34 #include <kio/scheduler.h>
35 
36 KIO::OpenWithHandlerInterface *s_openWithHandler = nullptr;
37 KIO::OpenOrExecuteFileInterface *s_openOrExecuteFileHandler = nullptr;
38 namespace KIO {
39 // Hidden API because in KF6 we'll just check if the job's uiDelegate implements these interfaces
40 KIOGUI_EXPORT void setDefaultOpenWithHandler(KIO::OpenWithHandlerInterface *iface) { s_openWithHandler = iface; }
41 KIOGUI_EXPORT void setDefaultOpenOrExecuteFileHandler(KIO::OpenOrExecuteFileInterface *iface) { s_openOrExecuteFileHandler = iface; }
42 }
43 
44 class KIO::OpenUrlJobPrivate
45 {
46 public:
47  explicit OpenUrlJobPrivate(const QUrl &url, OpenUrlJob *qq)
48  : m_url(url), q(qq)
49  {
50  q->setCapabilities(KJob::Killable);
51  }
52 
53  void emitAccessDenied();
54  void runUrlWithMimeType();
55  QString externalBrowser() const;
56  bool runExternalBrowser(const QString &exe);
57  void useSchemeHandler();
58  void determineLocalMimeType();
59  void statFile();
60  void scanFileWithGet();
61 
62  QUrl m_url;
63  KIO::OpenUrlJob * const q;
64  QString m_suggestedFileName;
65  QByteArray m_startupId;
66  QString m_mimeTypeName;
67  KService::Ptr m_preferredService;
68  bool m_deleteTemporaryFile = false;
69  bool m_runExecutables = false;
70  bool m_showOpenOrExecuteDialog = false;
71  bool m_externalBrowserEnabled = true;
72  bool m_followRedirections = true;
73 
74 private:
75  void executeCommand();
76  void handleBinaries(const QMimeType &mimeType);
77  void handleBinariesHelper(const QString &localPath, bool isNativeBinary);
78  void handleDesktopFiles();
79  void handleScripts();
80  void openInPreferredApp();
81  void runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName);
82 
83  void showOpenWithDialog();
84  void showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished);
85  void showUntrustedProgramWarningDialog(const QString &filePath);
86 
87  void startService(const KService::Ptr &service, const QList<QUrl> &urls);
88  void startService(const KService::Ptr &service)
89  {
90  startService(service, {m_url});
91  }
92 };
93 
95  : KCompositeJob(parent), d(new OpenUrlJobPrivate(url, this))
96 {
97 }
98 
99 KIO::OpenUrlJob::OpenUrlJob(const QUrl &url, const QString &mimeType, QObject *parent)
100  : KCompositeJob(parent), d(new OpenUrlJobPrivate(url, this))
101 {
102  d->m_mimeTypeName = mimeType;
103 }
104 
106 {
107 }
108 
110 {
111  d->m_deleteTemporaryFile = b;
112 }
113 
114 void KIO::OpenUrlJob::setSuggestedFileName(const QString &suggestedFileName)
115 {
116  d->m_suggestedFileName = suggestedFileName;
117 }
118 
120 {
121  d->m_startupId = startupId;
122 }
123 
125 {
126  d->m_runExecutables = allow;
127 }
128 
130 {
131  d->m_showOpenOrExecuteDialog = b;
132 }
133 
135 {
136  d->m_externalBrowserEnabled = b;
137 }
138 
140 {
141  d->m_followRedirections = b;
142 }
143 
144 static bool checkNeedPortalSupport()
145 {
147  || qEnvironmentVariableIsSet("SNAP"));
148 }
149 
151 {
152  if (!d->m_url.isValid() || d->m_url.scheme().isEmpty()) {
153  const QString error = !d->m_url.isValid() ? d->m_url.errorString() : d->m_url.toDisplayString();
154  setError(KIO::ERR_MALFORMED_URL);
155  setErrorText(i18n("Malformed URL\n%1", error));
156  emitResult();
157  return;
158  }
159  if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_url)) {
160  d->emitAccessDenied();
161  return;
162  }
163  if (d->m_externalBrowserEnabled && checkNeedPortalSupport()) {
164  // Use the function from QDesktopServices as it handles portals correctly
165  // Note that it falls back to "normal way" if the portal service isn't running.
166  if (!QDesktopServices::openUrl(d->m_url)) {
167  // Is this an actual error, or USER_CANCELED?
168  setError(KJob::UserDefinedError);
169  setErrorText(i18n("Failed to open %1", d->m_url.toDisplayString()));
170  }
171  emitResult();
172  return;
173  }
174 
175  // If we know the mimetype, proceed
176  if (!d->m_mimeTypeName.isEmpty()) {
177  d->runUrlWithMimeType();
178  return;
179  }
180 
181  if (d->m_externalBrowserEnabled && d->m_url.scheme().startsWith(QLatin1String("http"))) {
182  const QString externalBrowser = d->externalBrowser();
183  if (!externalBrowser.isEmpty() && d->runExternalBrowser(externalBrowser)) {
184  return;
185  }
186  }
188  d->useSchemeHandler();
189  return;
190  }
191 
192  if (!KProtocolManager::supportsListing(d->m_url)) {
193  // No support for listing => it can't be a directory (example: http)
194  d->scanFileWithGet();
195  return;
196  }
197 
198  // It may be a directory or a file, let's use stat to find out
199  d->statFile();
200 }
201 
202 bool KIO::OpenUrlJob::doKill()
203 {
204  return true;
205 }
206 
207 QString KIO::OpenUrlJobPrivate::externalBrowser() const
208 {
209  if (!m_externalBrowserEnabled) {
210  return QString();
211  }
212 
213  const QString browserApp = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication");
214  if (!browserApp.isEmpty()) {
215  return browserApp;
216  }
217 
218  // If a default browser isn't set in kdeglobals, fall back to mimeapps.list
219  KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
220  const KConfigGroup defaultApps(profile, "Default Applications");
221 
222  QString externalBrowser = defaultApps.readEntry("x-scheme-handler/https");
223  if (externalBrowser.isEmpty()) {
224  externalBrowser = defaultApps.readEntry("x-scheme-handler/http");
225  }
226  return externalBrowser;
227 }
228 
229 bool KIO::OpenUrlJobPrivate::runExternalBrowser(const QString &exec)
230 {
231  if (exec.startsWith(QLatin1Char('!'))) {
232  // Literal command
233  const QString command = exec.midRef(1) + QLatin1String(" %u");
234  KService::Ptr service(new KService(QString(), command, QString()));
235  startService(service);
236  return true;
237  } else {
238  // Name of desktop file
240  if (service) {
241  startService(service);
242  return true;
243  }
244  }
245  return false;
246 }
247 
248 void KIO::OpenUrlJobPrivate::useSchemeHandler()
249 {
250  // look for an application associated with x-scheme-handler/<protocol>
251  const KService::Ptr service = KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + m_url.scheme());
252  if (service) {
253  startService(service);
254  return;
255  }
256  // fallback, look for associated helper protocol
257  Q_ASSERT(KProtocolInfo::isHelperProtocol(m_url.scheme()));
258  const auto exec = KProtocolInfo::exec(m_url.scheme());
259  if (exec.isEmpty()) {
260  // use default mimetype opener for file
261  m_mimeTypeName = KProtocolManager::defaultMimetype(m_url);
262  runUrlWithMimeType();
263  } else {
264  KService::Ptr service(new KService(QString(), exec, QString()));
265  startService(service);
266  }
267 }
268 
269 void KIO::OpenUrlJobPrivate::statFile()
270 {
271  Q_ASSERT(m_mimeTypeName.isEmpty());
272 
273  KIO::StatJob *job = KIO::statDetails(m_url, KIO::StatJob::SourceSide, KIO::StatBasic, KIO::HideProgressInfo);
274  job->setUiDelegate(nullptr);
276  q, [=]() {
277  const int errCode = job->error();
278  if (errCode) {
279  // ERR_NO_CONTENT is not an error, but an indication no further
280  // actions needs to be taken.
281  if (errCode != KIO::ERR_NO_CONTENT) {
282  q->setError(errCode);
283  q->setErrorText(KIO::buildErrorString(errCode, job->errorText()));
284  }
285  q->emitResult();
286  return;
287  }
288  if (m_followRedirections) { // Update our URL in case of a redirection
289  m_url = job->url();
290  }
291 
292  const KIO::UDSEntry entry = job->statResult();
293 
294  const QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
295  if (!localPath.isEmpty()) {
296  m_url = QUrl::fromLocalFile(localPath);
297  }
298 
299  // mimetype already known? (e.g. print:/manager)
300  m_mimeTypeName = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
301  if (!m_mimeTypeName.isEmpty()) {
302  runUrlWithMimeType();
303  return;
304  }
305 
306  if (entry.isDir()) {
307  m_mimeTypeName = QStringLiteral("inode/directory");
308  runUrlWithMimeType();
309  } else { // it's a file
310  // Start the timer. Once we get the timer event this
311  // protocol server is back in the pool and we can reuse it.
312  // This gives better performance than starting a new slave
313  QTimer::singleShot(0, q, [this] { scanFileWithGet(); });
314  }
315  });
316 }
317 
318 void KIO::OpenUrlJobPrivate::startService(const KService::Ptr &service, const QList<QUrl> &urls)
319 {
321  job->setUrls(urls);
323  job->setSuggestedFileName(m_suggestedFileName);
324  job->setStartupId(m_startupId);
325  job->setUiDelegate(q->uiDelegate());
326  q->addSubjob(job);
327  job->start();
328 }
329 
330 static QMimeType fixupMimeType(const QString &mimeType, const QString &fileName)
331 {
332  QMimeDatabase db;
333  QMimeType mime = db.mimeTypeForName(mimeType);
334  if ((!mime.isValid() || mime.isDefault()) && !fileName.isEmpty()) {
335  mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
336  }
337  return mime;
338 }
339 
340 void KIO::OpenUrlJobPrivate::scanFileWithGet()
341 {
342  Q_ASSERT(m_mimeTypeName.isEmpty());
343 
344  // First, let's check for well-known extensions
345  // Not over HTTP and not when there is a query in the URL, in any case.
346  if (!m_url.hasQuery() && !m_url.scheme().startsWith(QLatin1String("http"))) {
347  QMimeDatabase db;
348  QMimeType mime = db.mimeTypeForUrl(m_url);
349  if (!mime.isDefault()) {
350  //qDebug() << "Scanfile: MIME TYPE is " << mime.name();
351  m_mimeTypeName = mime.name();
352  runUrlWithMimeType();
353  return;
354  }
355  }
356 
357  // No mimetype found, and the URL is not local (or fast mode not allowed).
358  // We need to apply the 'KIO' method, i.e. either asking the server or
359  // getting some data out of the file, to know what mimetype it is.
360 
361  if (!KProtocolManager::supportsReading(m_url)) {
362  qCWarning(KIO_GUI) << "#### NO SUPPORT FOR READING!";
363  q->setError(KIO::ERR_CANNOT_READ);
364  q->setErrorText(m_url.toDisplayString());
365  q->emitResult();
366  return;
367  }
368  //qDebug() << this << "Scanning file" << url;
369 
370  KIO::TransferJob *job = KIO::get(m_url, KIO::NoReload /*reload*/, KIO::HideProgressInfo);
371  job->setUiDelegate(nullptr);
372  QObject::connect(job, &KJob::result, q, [=]() {
373  const int errCode = job->error();
374  if (errCode) {
375  // ERR_NO_CONTENT is not an error, but an indication no further
376  // actions needs to be taken.
377  if (errCode != KIO::ERR_NO_CONTENT) {
378  q->setError(errCode);
379  q->setErrorText(job->errorText());
380  }
381  q->emitResult();
382  }
383  // if the job succeeded, we certainly hope it emitted mimetype()...
384  });
385  QObject::connect(job, QOverload<KIO::Job*,const QString&>::of(&KIO::TransferJob::mimetype),
386  q, [=](KIO::Job *, const QString &mimetype) {
387  if (m_followRedirections) { // Update our URL in case of a redirection
388  m_url = job->url();
389  }
390  if (mimetype.isEmpty()) {
391  qCWarning(KIO_GUI) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << m_url.scheme();
392  }
393  m_mimeTypeName = mimetype;
394 
395  // If the current mime-type is the default mime-type, then attempt to
396  // determine the "real" mimetype from the file name (bug #279675)
397  const QMimeType mime = fixupMimeType(m_mimeTypeName, m_suggestedFileName.isEmpty() ? m_url.fileName() : m_suggestedFileName);
398  const QString mimeName = mime.name();
399  if (mime.isValid() && mimeName != m_mimeTypeName) {
400  m_mimeTypeName = mimeName;
401  }
402 
403  if (m_suggestedFileName.isEmpty()) {
404  m_suggestedFileName = job->queryMetaData(QStringLiteral("content-disposition-filename"));
405  }
406 
407  job->putOnHold();
409  runUrlWithMimeType();
410  });
411 }
412 
413 void KIO::OpenUrlJobPrivate::runLink(const QString &filePath, const QString &urlStr, const QString &optionalServiceName)
414 {
415  if (urlStr.isEmpty()) {
416  q->setError(KJob::UserDefinedError);
417  q->setErrorText(i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", filePath));
418  q->emitResult();
419  return;
420  }
421 
422  m_url = QUrl::fromUserInput(urlStr);
423  m_mimeTypeName.clear();
424 
425  // X-KDE-LastOpenedWith holds the service desktop entry name that
426  // should be preferred for opening this URL if possible.
427  // This is used by the Recent Documents menu for instance.
428  if (!optionalServiceName.isEmpty()) {
429  m_preferredService = KService::serviceByDesktopName(optionalServiceName);
430  }
431 
432  // Restart from scratch with the target of the link
433  q->start();
434 }
435 
436 void KIO::OpenUrlJobPrivate::emitAccessDenied()
437 {
438  q->setError(KIO::ERR_ACCESS_DENIED);
439  q->setErrorText(KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, m_url.toDisplayString()));
440  q->emitResult();
441 }
442 
443 // was: KRun::isExecutable (minus application/x-desktop mimetype).
444 // Feel free to make public if needed.
445 static bool isBinary(const QMimeType &mimeType)
446 {
447  // - Binaries could be e.g.:
448  // - application/x-executable
449  // - application/x-sharedlib e.g. /usr/bin/ls, see
450  // https://gitlab.freedesktop.org/xdg/shared-mime-info/-/issues/11
451  //
452  // - Mimetypes that inherit application/x-executable _and_ text/plain are scripts, these are
453  // handled by handleScripts()
454 
455  return (mimeType.inherits(QStringLiteral("application/x-executable"))
456  || mimeType.inherits(QStringLiteral("application/x-sharedlib"))
457  || mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")));
458 }
459 
460 // Helper function that returns whether a file is a text-based script
461 // e.g. ".sh", ".csh", ".py", ".js"
462 static bool isTextScript(const QMimeType &mimeType)
463 {
464  return (mimeType.inherits(QStringLiteral("application/x-executable"))
465  && mimeType.inherits(QStringLiteral("text/plain")));
466 }
467 
468 // Helper function that returns whether a file has the execute bit set or not.
469 static bool hasExecuteBit(const QString &fileName)
470 {
471  return QFileInfo(fileName).isExecutable();
472 }
473 
474 // Handle native binaries (.e.g. /usr/bin/*); and .exe files
475 void KIO::OpenUrlJobPrivate::handleBinaries(const QMimeType &mimeType)
476 {
477  if (!KAuthorized::authorize(QStringLiteral("shell_access"))) {
478  emitAccessDenied();
479  return;
480  }
481 
482  const bool isLocal = m_url.isLocalFile();
483  // Don't run remote executables
484  if (!isLocal) {
485  q->setError(KJob::UserDefinedError);
486  q->setErrorText(i18n("The executable file \"%1\" is located on a remote filesystem. "
487  "For safety reasons it will not be started.", m_url.toDisplayString()));
488  q->emitResult();
489  return;
490  }
491 
492  const QString localPath = m_url.toLocalFile();
493 
494  bool isNativeBinary = true;
495 #ifndef Q_OS_WIN
496  isNativeBinary = !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"));
497 #endif
498 
499  if (m_showOpenOrExecuteDialog) {
500  auto dialogFinished = [this, localPath, isNativeBinary](bool shouldExecute) {
501  // shouldExecute is always true if we get here, because for binaries the
502  // dialog only offers Execute/Cancel
503  Q_UNUSED(shouldExecute)
504 
505  handleBinariesHelper(localPath, isNativeBinary);
506  };
507 
508  // Ask the user for confirmation before executing this binary (for binaries
509  // the dialog will only show Execute/Cancel)
510  showOpenOrExecuteFileDialog(dialogFinished);
511  return;
512  }
513 
514  handleBinariesHelper(localPath, isNativeBinary);
515 }
516 
517 void KIO::OpenUrlJobPrivate::handleBinariesHelper(const QString &localPath, bool isNativeBinary)
518 {
519  // For local .exe files, open in the default app (e.g. WINE)
520  if (!isNativeBinary) {
521  openInPreferredApp();
522  return;
523  }
524 
525  // Native binaries
526 
527  if (!m_runExecutables) {
528  q->setError(KJob::UserDefinedError);
529  q->setErrorText(i18n("For security reasons, launching executables is not allowed in this context."));
530  q->emitResult();
531  return;
532  }
533 
534  if (!hasExecuteBit(localPath)) {
535  // Show untrustedProgram dialog for local, native executables without the execute bit
536  showUntrustedProgramWarningDialog(localPath);
537  return;
538  }
539 
540  // Local executable with execute bit, proceed
541  executeCommand();
542 }
543 
544 namespace KIO {
545 extern KIO::UntrustedProgramHandlerInterface *defaultUntrustedProgramHandler();
546 }
547 
548 // For local, native executables (i.e. not shell scripts) without execute bit,
549 // show a prompt asking the user if he wants to run the program.
550 void KIO::OpenUrlJobPrivate::showUntrustedProgramWarningDialog(const QString &filePath)
551 {
552  KIO::UntrustedProgramHandlerInterface *untrustedProgramHandler = defaultUntrustedProgramHandler();
553  if (!untrustedProgramHandler) {
554  // No way to ask the user to make it executable
555  q->setError(KJob::UserDefinedError);
556  q->setErrorText(i18n("The program \"%1\" needs to have executable permission before it can be launched.", filePath));
557  q->emitResult();
558  return;
559  }
560  QObject::connect(untrustedProgramHandler, &KIO::UntrustedProgramHandlerInterface::result, q, [=](bool result) {
561  if (result) {
563  if (untrustedProgramHandler->setExecuteBit(filePath, errorString)) {
564  executeCommand();
565  } else {
566  q->setError(KJob::UserDefinedError);
567  q->setErrorText(i18n("Unable to make file \"%1\" executable.\n%2.", filePath, errorString));
568  q->emitResult();
569  }
570  } else {
571  q->setError(KIO::ERR_USER_CANCELED);
572  q->emitResult();
573  }
574  });
575  untrustedProgramHandler->showUntrustedProgramWarning(q, m_url.fileName());
576 }
577 
578 void KIO::OpenUrlJobPrivate::executeCommand()
579 {
580  // Execute the URL as a command. This is how we start scripts and executables
582  job->setUiDelegate(q->uiDelegate());
583  job->setStartupId(m_startupId);
585  q->addSubjob(job);
586  job->start();
587 
588  // TODO implement deleting the file if tempFile==true
589  // CommandLauncherJob doesn't support that, unlike ApplicationLauncherJob
590  // We'd have to do it in KProcessRunner.
591 }
592 
593 void KIO::OpenUrlJobPrivate::runUrlWithMimeType()
594 {
595  // Tell the app, in case it wants us to stop here
596  Q_EMIT q->mimeTypeFound(m_mimeTypeName);
597  if (q->error() == KJob::KilledJobError) {
598  q->emitResult();
599  return;
600  }
601 
602  // Support for preferred service setting, see setPreferredService
603  if (m_preferredService && m_preferredService->hasMimeType(m_mimeTypeName)) {
604  startService(m_preferredService);
605  return;
606  }
607 
608 
609  // Scripts and executables
610  QMimeDatabase db;
611  const QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
612 
613  // .desktop files
614  if (mimeType.inherits(QStringLiteral("application/x-desktop"))) {
615  handleDesktopFiles();
616  return;
617  }
618 
619  // Scripts (e.g. .sh, .csh, .py, .js)
620  if (isTextScript(mimeType)) {
621  handleScripts();
622  return;
623  }
624 
625  // Binaries (e.g. /usr/bin/{konsole,ls}) and .exe files
626  if (isBinary(mimeType)) {
627  handleBinaries(mimeType);
628  return;
629  }
630 
631  // General case: look up associated application
632  openInPreferredApp();
633 }
634 
635 void KIO::OpenUrlJobPrivate::handleDesktopFiles()
636 {
637  // Open remote .desktop files in the default (text editor) app
638  if (!m_url.isLocalFile()) {
639  openInPreferredApp();
640  return;
641  }
642 
643  if (m_url.fileName() == QLatin1String(".directory")) {
644  // We cannot execute a .directory file, open in the default app
645  m_mimeTypeName = QStringLiteral("text/plain");
646  openInPreferredApp();
647  return;
648  }
649 
650  const QString filePath = m_url.toLocalFile();
651  KDesktopFile cfg(filePath);
652  KConfigGroup cfgGroup = cfg.desktopGroup();
653  if (!cfgGroup.hasKey("Type")) {
654  q->setError(KJob::UserDefinedError);
655  q->setErrorText(i18n("The desktop entry file %1 has no Type=... entry.", filePath));
656  q->emitResult();
657  openInPreferredApp();
658  return;
659  }
660 
661  if (cfg.hasLinkType()) {
662  runLink(filePath, cfg.readUrl(), cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith"));
663  return;
664  }
665 
666  if ((cfg.hasApplicationType() || cfg.readType() == QLatin1String("Service"))) { // kio_settings lets users run Type=Service desktop files
667  KService::Ptr service(new KService(filePath));
668  if (!service->exec().isEmpty()) {
669  if (m_showOpenOrExecuteDialog) { // Show the openOrExecute dialog
670  auto dialogFinished = [this, filePath, service](bool shouldExecute) {
671  if (shouldExecute) { // Run the file
672  startService(service, {});
673  return;
674  }
675  // The user selected "open"
676  openInPreferredApp();
677  };
678 
679  showOpenOrExecuteFileDialog(dialogFinished);
680  return;
681  }
682 
683  if (m_runExecutables) {
684  startService(service, {});
685  return;
686  }
687  } // exec is not empty
688  } // type Application or Service
689 
690  // Fallback to opening in the default app
691  openInPreferredApp();
692 }
693 
694 void KIO::OpenUrlJobPrivate::handleScripts()
695 {
696  // Executable scripts of any type can run arbitrary shell commands
697  if (!KAuthorized::authorize(QStringLiteral("shell_access"))) {
698  emitAccessDenied();
699  return;
700  }
701 
702  const bool isLocal = m_url.isLocalFile();
703  const QString localPath = m_url.toLocalFile();
704  if (!isLocal || !hasExecuteBit(localPath)) {
705  // Open remote scripts or ones without the execute bit, with the default application
706  openInPreferredApp();
707  return;
708  }
709 
710  if (m_showOpenOrExecuteDialog) {
711  auto dialogFinished = [this](bool shouldExecute) {
712  if (shouldExecute) {
713  executeCommand();
714  } else {
715  openInPreferredApp();
716  }
717  };
718 
719  showOpenOrExecuteFileDialog(dialogFinished);
720  return;
721  }
722 
723  if (m_runExecutables) { // Local executable script, proceed
724  executeCommand();
725  } else { // Open in the default (text editor) app
726  openInPreferredApp();
727  }
728 }
729 
730 void KIO::OpenUrlJobPrivate::openInPreferredApp()
731 {
732  KService::Ptr service = KApplicationTrader::preferredService(m_mimeTypeName);
733  if (service) {
734  startService(service);
735  } else {
736  showOpenWithDialog();
737  }
738 }
739 
740 void KIO::OpenUrlJobPrivate::showOpenWithDialog()
741 {
742  if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
743  q->setError(KJob::UserDefinedError);
744  q->setErrorText(i18n("You are not authorized to select an application to open this file."));
745  q->emitResult();
746  return;
747  }
748 
750  // As KDE on windows doesn't know about the windows default applications, offers will be empty in nearly all cases.
751  // So we use QDesktopServices::openUrl to let windows decide how to open the file.
752  // It's also our fallback if there's no handler to show an open-with dialog.
753  if (!QDesktopServices::openUrl(m_url)) {
754  q->setError(KJob::UserDefinedError);
755  q->setErrorText(i18n("Failed to open the file."));
756  }
757  q->emitResult();
758  return;
759  }
760 
761  QObject::connect(s_openWithHandler, &KIO::OpenWithHandlerInterface::canceled, q, [this]() {
762  q->setError(KIO::ERR_USER_CANCELED);
763  q->emitResult();
764  });
765 
766  QObject::connect(s_openWithHandler, &KIO::OpenWithHandlerInterface::serviceSelected, q, [this](const KService::Ptr &service) {
767  startService(service);
768  });
769 
770  QObject::connect(s_openWithHandler, &KIO::OpenWithHandlerInterface::handled, q, [this]() {
771  q->emitResult();
772  });
773 
774  s_openWithHandler->promptUserForApplication(q, {m_url}, m_mimeTypeName);
775 }
776 
777 void KIO::OpenUrlJobPrivate::showOpenOrExecuteFileDialog(std::function<void(bool)> dialogFinished)
778 {
779  QMimeDatabase db;
780  QMimeType mimeType = db.mimeTypeForName(m_mimeTypeName);
781 
782  if (!s_openOrExecuteFileHandler) {
783  // No way to ask the user whether to execute or open
784  if (isTextScript(mimeType)
785  || mimeType.inherits(QStringLiteral("application/x-desktop"))) { // Open text-based ones in the default app
786  openInPreferredApp();
787  } else {
788  q->setError(KJob::UserDefinedError);
789  q->setErrorText(i18n("The program \"%1\" could not be launched.", m_url.toDisplayString(QUrl::PreferLocalFile)));
790  q->emitResult();
791  }
792  return;
793  }
794 
795  QObject::connect(s_openOrExecuteFileHandler, &KIO::OpenOrExecuteFileInterface::canceled, q, [this]() {
796  q->setError(KIO::ERR_USER_CANCELED);
797  q->emitResult();
798  });
799 
801  q, [this, dialogFinished](bool shouldExecute) {
802  m_runExecutables = shouldExecute;
803  dialogFinished(shouldExecute);
804  });
805 
806  s_openOrExecuteFileHandler->promptUserOpenOrExecute(q, m_mimeTypeName);
807 }
808 
809 void KIO::OpenUrlJob::slotResult(KJob *job)
810 {
811  // This is only used for the final application/launcher job, so we're done when it's done
812  const int errCode = job->error();
813  if (errCode) {
814  setError(errCode);
815  setErrorText(KIO::buildErrorString(errCode, job->errorText()));
816  }
817  emitResult();
818 }
QString url(QUrl::FormattingOptions options) const const
RemoveFilename
virtual bool addSubjob(KJob *job)
KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType)
QOperatingSystemVersion::OSType currentType()
OpenUrlJob(const QUrl &url, QObject *parent=nullptr)
Creates an OpenUrlJob in order to open a URL.
Definition: openurljob.cpp:94
QString toDisplayString(QUrl::FormattingOptions options) const const
static bool supportsReading(const QUrl &url)
Returns whether the protocol can retrieve data from URLs.
void handled()
Emitted by promptUserForApplication() if it fully handled it including launching the app...
void emitResult()
OpenUrlJob finds out the right way to "open" a URL.
Definition: openurljob.h:38
virtual void promptUserOpenOrExecute(KJob *job, const QString &mimetype)
Show a dialog to ask the user how to handle various types of executable files, basically whether to r...
void setUiDelegate(KJobUiDelegate *delegate)
const QUrl & url() const
Returns the SimpleJob&#39;s URL.
Definition: simplejob.cpp:70
Universal Directory Service.
Definition: udsentry.h:77
static Ptr serviceByDesktopName(const QString &_name)
A namespace for KIO globals.
Definition: authinfo.h:21
void start() override
Starts the job.
Definition: openurljob.cpp:150
virtual QString errorString() const
Hide progress information dialog, i.e.
Definition: job_base.h:275
void setError(int errorCode)
bool exec()
bool isDir() const
Definition: udsentry.cpp:362
bool inherits(const QString &mimeTypeName) const const
bool hasApplicationType() const
A KIO job that retrieves information about a file or directory.
Definition: statjob.h:26
A local file path if the ioslave display files sitting on the local filesystem (but in another hierar...
Definition: udsentry.h:253
void setSuggestedFileName(const QString &suggestedFileName)
Sets the file name to use in the case of downloading the file to a tempfile in order to give to a non...
void start() override
Starts the job.
QString queryMetaData(const QString &key)
Query meta data received from the slave.
Definition: job.cpp:220
QUrl fromUserInput(const QString &userInput)
The UntrustedProgramHandlerInterface class allows ApplicationLauncherJob to prompt the user about an ...
void start() override
Starts the job.
QString exec() const
virtual void showUntrustedProgramWarning(KJob *job, const QString &programName)
Show a warning to the user about the program not being trusted for execution.
QMimeType mimeTypeForFile(const QString &fileName, QMimeDatabase::MatchMode mode) const const
void clear()
QString readType() const
void setErrorText(const QString &errorText)
QString mimetype() const
Call this in the slot connected to result, and only after making sure no error happened.
void setStartupId(const QByteArray &startupId)
Sets the startup notification id of the command launch.
virtual void promptUserForApplication(KJob *job, const QList< QUrl > &urls, const QString &mimeType)
Show the "Open With" dialog.
void setEnableExternalBrowser(bool b)
Sets whether the external webbrowser setting should be honoured.
Definition: openurljob.cpp:134
~OpenUrlJob() override
Destructor.
Definition: openurljob.cpp:105
void setStartupId(const QByteArray &startupId)
Sets the startup notification id of the application launch.
QMimeType mimeTypeForUrl(const QUrl &url) const const
static QString defaultMimetype(const QUrl &url)
Returns default mimetype for this URL based on the protocol.
bool hasMimeType(const QString &mimeType) const
Filename, access, type, size, linkdest.
Definition: global.h:314
static bool supportsListing(const QUrl &url)
Returns whether the protocol can list files/objects.
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
KIOCORE_EXPORT QString buildErrorString(int errorCode, const QString &errorText)
Returns a translated error message for errorCode using the additional error information provided by e...
Definition: job_error.cpp:27
QString stringValue(uint field) const
Definition: udsentry.cpp:352
void setFollowRedirections(bool b)
Sets whether the job should follow URL redirections.
Definition: openurljob.cpp:139
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
Find mimetype for one file or directory.
Definition: mimetypejob.cpp:81
void setRunExecutables(bool allow)
Set this to true if this class should allow the user to run executables.
Definition: openurljob.cpp:124
QString readUrl() const
KJobUiDelegate * uiDelegate() const
QString scheme() const const
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
void setWorkingDirectory(const QString &workingDirectory)
Sets the working directory from which to run the command.
QString toLocalFile() const const
void setRunFlags(RunFlags runFlags)
Specifies various flags.
bool hasLinkType() const
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
Get (means: read).
The OpenWithHandlerInterface class allows OpenUrlJob to prompt the user about which application to us...
bool isValid() const const
QStringRef midRef(int position, int n) const const
static QString exec(const QString &protocol)
Returns the library / executable to open for the protocol protocol Example : "kio_ftp", meaning either the executable "kio_ftp" or the library "kio_ftp.la" (recommended), whichever is available.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
the URLs passed to the service will be deleted when it exits (if the URLs are local files) ...
bool hasKey(const QString &key) const
QString i18n(const char *text, const TYPE &arg...)
void setStartupId(const QByteArray &startupId)
Sets the startup notification id of the application launch.
Definition: openurljob.cpp:119
The OpenOrExecuteFileInterface class allows OpenUrlJob to ask the user about how to handle various ty...
bool setExecuteBit(const QString &fileName, QString &errorString)
Helper function that attempts to set execute bit for given file.
QUrl adjusted(QUrl::FormattingOptions options) const const
void mimeTypeFound(const QString &mimeType)
Emitted when the mimeType is determined.
void setUrls(const QList< QUrl > &urls)
Specifies the URLs to be passed to the application.
static bool isHelperProtocol(const QUrl &url)
Returns whether the protocol can act as a helper protocol.
The base class for all jobs.
Definition: job_base.h:45
Action succeeded but no content will follow.
Definition: global.h:250
void canceled()
Emitted by promptUserOpenOrExecute() if user selects cancel.
void setSuggestedFileName(const QString &suggestedFileName)
Sets the file name to use in the case of downloading the file to a tempfile, in order to give it to a...
Definition: openurljob.cpp:114
CommandLauncherJob runs a command and watches it while running.
virtual void putOnHold()
Abort job.
Definition: simplejob.cpp:75
void result(bool confirmed)
Implementations of this interface must emit result in showUntrustedProgramWarning.
A mime type; the slave should set it if it&#39;s known.
Definition: udsentry.h:279
void result(KJob *job)
The transfer job pumps data into and/or out of a Slave.
Definition: transferjob.h:26
void serviceSelected(const KService::Ptr &service)
Emitted by promptUserForApplication() once the user chooses an application.
bool openUrl(const QUrl &url)
static void publishSlaveOnHold()
Send the slave that was put on hold back to KLauncher.
Definition: scheduler.cpp:836
ApplicationLauncherJob runs an application and watches it while running.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
bool hasQuery() const const
void setDeleteTemporaryFile(bool b)
Specifies that the URL passed to the application will be deleted when it exits (if the URL is a local...
Definition: openurljob.cpp:109
T readEntry(const QString &key, const T &aDefault) const
QString fileName(QUrl::ComponentFormattingOptions options) const const
KIOCORE_EXPORT StatJob * statDetails(const QUrl &url, KIO::StatJob::StatSide side, KIO::StatDetails details=KIO::StatDefaultDetails, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition: statjob.cpp:257
static bool hasSchemeHandler(const QUrl &url)
Returns true if protocol should be opened by a "handler" application, i.e.
Q_EMITQ_EMIT
bool isExecutable() const const
void canceled()
Emitted by promptUserForApplication() if the user canceled the application selection dialog...
QString errorText() const
void executeFile(bool enable)
Emitted by promptUserOpenOrExecute() once the user chooses an action.
QUrl fromLocalFile(const QString &localFile)
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
void setShowOpenOrExecuteDialog(bool b)
Set this to true if this class should show a dialog to ask the user about how to handle various types...
Definition: openurljob.cpp:129
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
int error() const
static Ptr serviceByStorageId(const QString &_storageId)
bool isLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Dec 3 2020 23:01:58 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.