1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org> |
4 | SPDX-FileCopyrightText: 2006-2013 David Faure <faure@kde.org> |
5 | SPDX-FileCopyrightText: 2009 Michael Pyne <michael.pyne@kdemail.net> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "desktopexecparser.h" |
11 | #ifndef Q_OS_ANDROID |
12 | #include "kiofuse_interface.h" |
13 | #endif |
14 | |
15 | #include <KApplicationTrader> |
16 | #include <KConfigGroup> |
17 | #include <KDesktopFile> |
18 | #include <KLocalizedString> |
19 | #include <KMacroExpander> |
20 | #include <KService> |
21 | #include <KSharedConfig> |
22 | #include <KShell> |
23 | #include <kprotocolinfo.h> // KF6 TODO remove after moving hasSchemeHandler to OpenUrlJob |
24 | |
25 | #ifndef Q_OS_ANDROID |
26 | #include <QDBusConnection> |
27 | #include <QDBusReply> |
28 | #endif |
29 | #include <QDir> |
30 | #include <QFile> |
31 | #include <QProcessEnvironment> |
32 | #include <QStandardPaths> |
33 | #include <QUrl> |
34 | |
35 | #include <config-kiocore.h> // KDE_INSTALL_FULL_LIBEXECDIR_KF |
36 | |
37 | #include "kiocoredebug.h" |
38 | |
39 | class KRunMX1 : public KMacroExpanderBase |
40 | { |
41 | public: |
42 | explicit KRunMX1(const KService &_service) |
43 | : KMacroExpanderBase(QLatin1Char('%')) |
44 | , hasUrls(false) |
45 | , hasSpec(false) |
46 | , service(_service) |
47 | { |
48 | } |
49 | |
50 | bool hasUrls; |
51 | bool hasSpec; |
52 | |
53 | protected: |
54 | int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; |
55 | |
56 | private: |
57 | const KService &service; |
58 | }; |
59 | |
60 | int KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret) |
61 | { |
62 | uint option = str[pos + 1].unicode(); |
63 | switch (option) { |
64 | case 'c': |
65 | ret << service.name().replace(c: QLatin1Char('%'), after: QLatin1String("%%" )); |
66 | break; |
67 | case 'k': |
68 | ret << service.entryPath().replace(c: QLatin1Char('%'), after: QLatin1String("%%" )); |
69 | break; |
70 | case 'i': |
71 | ret << QStringLiteral("--icon" ) << service.icon().replace(c: QLatin1Char('%'), after: QLatin1String("%%" )); |
72 | break; |
73 | case 'm': |
74 | // ret << "-miniicon" << service.icon().replace( '%', "%%" ); |
75 | qCWarning(KIO_CORE) << "-miniicon isn't supported anymore (service" << service.name() << ')'; |
76 | break; |
77 | case 'u': |
78 | case 'U': |
79 | hasUrls = true; |
80 | Q_FALLTHROUGH(); |
81 | /* fallthrough */ |
82 | case 'f': |
83 | case 'F': |
84 | case 'n': |
85 | case 'N': |
86 | case 'd': |
87 | case 'D': |
88 | case 'v': |
89 | hasSpec = true; |
90 | Q_FALLTHROUGH(); |
91 | /* fallthrough */ |
92 | default: |
93 | return -2; // subst with same and skip |
94 | } |
95 | return 2; |
96 | } |
97 | |
98 | class KRunMX2 : public KMacroExpanderBase |
99 | { |
100 | public: |
101 | explicit KRunMX2(const QList<QUrl> &_urls) |
102 | : KMacroExpanderBase(QLatin1Char('%')) |
103 | , ignFile(false) |
104 | , urls(_urls) |
105 | { |
106 | } |
107 | |
108 | bool ignFile; |
109 | |
110 | protected: |
111 | int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; |
112 | |
113 | private: |
114 | void subst(int option, const QUrl &url, QStringList &ret); |
115 | |
116 | const QList<QUrl> &urls; |
117 | }; |
118 | |
119 | void KRunMX2::subst(int option, const QUrl &url, QStringList &ret) |
120 | { |
121 | switch (option) { |
122 | case 'u': |
123 | ret << ((url.isLocalFile() && url.fragment().isNull() && url.query().isNull()) ? QDir::toNativeSeparators(pathName: url.toLocalFile()) : url.toString()); |
124 | break; |
125 | case 'd': |
126 | ret << url.adjusted(options: QUrl::RemoveFilename).path(); |
127 | break; |
128 | case 'f': |
129 | ret << QDir::toNativeSeparators(pathName: url.toLocalFile()); |
130 | break; |
131 | case 'n': |
132 | ret << url.fileName(); |
133 | break; |
134 | case 'v': |
135 | if (url.isLocalFile() && QFile::exists(fileName: url.toLocalFile())) { |
136 | ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry(key: "Dev" ); |
137 | } |
138 | break; |
139 | } |
140 | return; |
141 | } |
142 | |
143 | int KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret) |
144 | { |
145 | uint option = str[pos + 1].unicode(); |
146 | switch (option) { |
147 | case 'f': |
148 | case 'u': |
149 | case 'n': |
150 | case 'd': |
151 | case 'v': |
152 | if (urls.isEmpty()) { |
153 | if (!ignFile) { |
154 | // qCDebug(KIO_CORE) << "No URLs supplied to single-URL service" << str; |
155 | } |
156 | } else if (urls.count() > 1) { |
157 | qCWarning(KIO_CORE) << urls.count() << "URLs supplied to single-URL service" << str; |
158 | } else { |
159 | subst(option, url: urls.first(), ret); |
160 | } |
161 | break; |
162 | case 'F': |
163 | case 'U': |
164 | case 'N': |
165 | case 'D': |
166 | option += 'a' - 'A'; |
167 | for (const QUrl &url : urls) { |
168 | subst(option, url, ret); |
169 | } |
170 | break; |
171 | case '%': |
172 | ret = QStringList(QStringLiteral("%" )); |
173 | break; |
174 | default: |
175 | return -2; // subst with same and skip |
176 | } |
177 | return 2; |
178 | } |
179 | |
180 | QStringList KIO::DesktopExecParser::supportedProtocols(const KService &service) |
181 | { |
182 | QStringList supportedProtocols = service.supportedProtocols(); |
183 | |
184 | KRunMX1 mx1(service); |
185 | QString exec = service.exec(); |
186 | if (mx1.expandMacrosShellQuote(str&: exec) && !mx1.hasUrls) { |
187 | if (!supportedProtocols.isEmpty()) { |
188 | qCWarning(KIO_CORE) << service.entryPath() << "contains supported protocols but doesn't use %u or %U in its Exec line! This is inconsistent." ; |
189 | } |
190 | return QStringList(); |
191 | } else { |
192 | if (supportedProtocols.isEmpty()) { |
193 | // compat mode: assume KIO if not set and it's a KDE app (or a KDE service) |
194 | const QStringList categories = service.property<QStringList>(QStringLiteral("Categories" )); |
195 | if (categories.contains(str: QLatin1String("KDE" )) || !service.isApplication() || service.entryPath().isEmpty() /*temp service*/) { |
196 | supportedProtocols.append(QStringLiteral("KIO" )); |
197 | } else { // if no KDE app, be a bit over-generic |
198 | supportedProtocols.append(QStringLiteral("http" )); |
199 | supportedProtocols.append(QStringLiteral("https" )); // #253294 |
200 | supportedProtocols.append(QStringLiteral("ftp" )); |
201 | } |
202 | } |
203 | } |
204 | |
205 | // qCDebug(KIO_CORE) << "supportedProtocols:" << supportedProtocols; |
206 | return supportedProtocols; |
207 | } |
208 | |
209 | bool KIO::DesktopExecParser::isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols) |
210 | { |
211 | return url.isLocalFile() // |
212 | || supportedProtocols.contains(str: QLatin1String("KIO" )) // |
213 | || supportedProtocols.contains(str: url.scheme(), cs: Qt::CaseInsensitive); |
214 | } |
215 | |
216 | // We have up to two sources of data, for protocols not handled by KIO workers (so called "helper") : |
217 | // 1) the exec line of the .protocol file, if there's one |
218 | // 2) the application associated with x-scheme-handler/<protocol> if there's one |
219 | bool KIO::DesktopExecParser::hasSchemeHandler(const QUrl &url) // KF6 TODO move to OpenUrlJob |
220 | { |
221 | if (KProtocolInfo::isHelperProtocol(url)) { |
222 | return true; |
223 | } |
224 | const KService::Ptr service = KApplicationTrader::preferredService(mimeType: QLatin1String("x-scheme-handler/" ) + url.scheme()); |
225 | if (service) { |
226 | qCDebug(KIO_CORE) << QLatin1String("preferred service for x-scheme-handler/" ) + url.scheme() << service->desktopEntryName(); |
227 | } |
228 | return service; |
229 | } |
230 | |
231 | class KIO::DesktopExecParserPrivate |
232 | { |
233 | public: |
234 | DesktopExecParserPrivate(const KService &_service, const QList<QUrl> &_urls) |
235 | : service(_service) |
236 | , urls(_urls) |
237 | , tempFiles(false) |
238 | { |
239 | } |
240 | |
241 | bool isUrlSupported(const QUrl &url, const QStringList &supportedProtocols); |
242 | |
243 | const KService &service; |
244 | QList<QUrl> urls; |
245 | bool tempFiles; |
246 | QString suggestedFileName; |
247 | QString m_errorString; |
248 | }; |
249 | |
250 | KIO::DesktopExecParser::DesktopExecParser(const KService &service, const QList<QUrl> &urls) |
251 | : d(new DesktopExecParserPrivate(service, urls)) |
252 | { |
253 | } |
254 | |
255 | KIO::DesktopExecParser::~DesktopExecParser() |
256 | { |
257 | } |
258 | |
259 | void KIO::DesktopExecParser::setUrlsAreTempFiles(bool tempFiles) |
260 | { |
261 | d->tempFiles = tempFiles; |
262 | } |
263 | |
264 | void KIO::DesktopExecParser::setSuggestedFileName(const QString &suggestedFileName) |
265 | { |
266 | d->suggestedFileName = suggestedFileName; |
267 | } |
268 | |
269 | static const QString kioexecPath() |
270 | { |
271 | QString kioexec = QCoreApplication::applicationDirPath() + QLatin1String("/kioexec" ); |
272 | if (!QFileInfo::exists(file: kioexec)) { |
273 | kioexec = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kioexec" ); |
274 | } |
275 | Q_ASSERT(QFileInfo::exists(kioexec)); |
276 | return kioexec; |
277 | } |
278 | |
279 | static QString findNonExecutableProgram(const QString &executable) |
280 | { |
281 | // Relative to current dir, or absolute path |
282 | const QFileInfo fi(executable); |
283 | if (fi.exists() && !fi.isExecutable()) { |
284 | return executable; |
285 | } |
286 | |
287 | #ifdef Q_OS_UNIX |
288 | // This is a *very* simplified version of QStandardPaths::findExecutable |
289 | const QStringList searchPaths = QString::fromLocal8Bit(ba: qgetenv(varName: "PATH" )).split(sep: QDir::listSeparator(), behavior: Qt::SkipEmptyParts); |
290 | for (const QString &searchPath : searchPaths) { |
291 | const QString candidate = searchPath + QLatin1Char('/') + executable; |
292 | const QFileInfo fileInfo(candidate); |
293 | if (fileInfo.exists()) { |
294 | if (fileInfo.isExecutable()) { |
295 | qWarning() << "Internal program error. QStandardPaths::findExecutable couldn't find" << executable << "but our own logic found it at" |
296 | << candidate << ". Please report a bug at https://bugs.kde.org" ; |
297 | } else { |
298 | return candidate; |
299 | } |
300 | } |
301 | } |
302 | #endif |
303 | return QString(); |
304 | } |
305 | |
306 | bool KIO::DesktopExecParserPrivate::isUrlSupported(const QUrl &url, const QStringList &protocols) |
307 | { |
308 | if (KIO::DesktopExecParser::isProtocolInSupportedList(url, supportedProtocols: protocols)) { |
309 | return true; |
310 | } |
311 | |
312 | // supportedProtocols() only checks whether the .desktop file has MimeType=x-scheme-handler/xxx |
313 | // We also want to check whether the app has been set as default/associated in mimeapps.list |
314 | const auto handlers = KApplicationTrader::queryByMimeType(mimeType: QLatin1String("x-scheme-handler/" ) + url.scheme()); |
315 | for (const KService::Ptr &handler : handlers) { |
316 | if (handler->desktopEntryName() == service.desktopEntryName()) { |
317 | return true; |
318 | } |
319 | } |
320 | |
321 | return false; |
322 | } |
323 | |
324 | QStringList KIO::DesktopExecParser::resultingArguments() const |
325 | { |
326 | QString exec = d->service.exec(); |
327 | if (exec.isEmpty()) { |
328 | d->m_errorString = i18n("No Exec field in %1" , d->service.entryPath()); |
329 | qCWarning(KIO_CORE) << "No Exec field in" << d->service.entryPath(); |
330 | return QStringList(); |
331 | } |
332 | |
333 | // Extract the name of the binary to execute from the full Exec line, to see if it exists |
334 | const QString binary = executablePath(execLine: exec); |
335 | QString executableFullPath; |
336 | if (!binary.isEmpty()) { // skip all this if the Exec line is a complex shell command |
337 | if (QDir::isRelativePath(path: binary)) { |
338 | // Resolve the executable to ensure that helpers in libexec are found. |
339 | // Too bad for commands that need a shell - they must reside in $PATH. |
340 | executableFullPath = QStandardPaths::findExecutable(executableName: binary); |
341 | if (executableFullPath.isEmpty()) { |
342 | executableFullPath = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/" ) + binary; |
343 | } |
344 | } else { |
345 | executableFullPath = binary; |
346 | } |
347 | |
348 | // Now check that the binary exists and has the executable flag |
349 | if (!QFileInfo(executableFullPath).isExecutable()) { |
350 | // Does it really not exist, or is it non-executable (on Unix)? (bug #415567) |
351 | const QString nonExecutable = findNonExecutableProgram(executable: binary); |
352 | if (nonExecutable.isEmpty()) { |
353 | d->m_errorString = i18n("Could not find the program '%1'" , binary); |
354 | } else { |
355 | if (QDir::isRelativePath(path: binary)) { |
356 | d->m_errorString = i18n("The program '%1' was found at '%2' but it is missing executable permissions." , binary, nonExecutable); |
357 | } else { |
358 | d->m_errorString = i18n("The program '%1' is missing executable permissions." , nonExecutable); |
359 | } |
360 | } |
361 | return QStringList(); |
362 | } |
363 | } |
364 | |
365 | QStringList result; |
366 | bool appHasTempFileOption; |
367 | |
368 | KRunMX1 mx1(d->service); |
369 | KRunMX2 mx2(d->urls); |
370 | |
371 | if (!mx1.expandMacrosShellQuote(str&: exec)) { // Error in shell syntax |
372 | d->m_errorString = i18n("Syntax error in command %1 coming from %2" , exec, d->service.entryPath()); |
373 | qCWarning(KIO_CORE) << "Syntax error in command" << d->service.exec() << ", service" << d->service.name(); |
374 | return QStringList(); |
375 | } |
376 | |
377 | // FIXME: the current way of invoking kioexec disables term and su use |
378 | |
379 | // Check if we need "tempexec" (kioexec in fact) |
380 | appHasTempFileOption = d->tempFiles && d->service.property<bool>(QStringLiteral("X-KDE-HasTempFileOption" )); |
381 | if (d->tempFiles && !appHasTempFileOption && d->urls.size()) { |
382 | result << kioexecPath() << QStringLiteral("--tempfiles" ) << exec; |
383 | if (!d->suggestedFileName.isEmpty()) { |
384 | result << QStringLiteral("--suggestedfilename" ); |
385 | result << d->suggestedFileName; |
386 | } |
387 | result += QUrl::toStringList(uris: d->urls); |
388 | return result; |
389 | } |
390 | |
391 | // Return true for non-KIO desktop files with explicit X-KDE-Protocols list, like vlc, for the special case below |
392 | auto isNonKIO = [this]() { |
393 | const QStringList protocols = d->service.property<QStringList>(QStringLiteral("X-KDE-Protocols" )); |
394 | return !protocols.isEmpty() && !protocols.contains(str: QLatin1String("KIO" )); |
395 | }; |
396 | |
397 | // Check if we need kioexec, or KIOFuse |
398 | bool useKioexec = false; |
399 | #ifndef Q_OS_ANDROID |
400 | org::kde::KIOFuse::VFS kiofuse_iface(QStringLiteral("org.kde.KIOFuse" ), QStringLiteral("/org/kde/KIOFuse" ), QDBusConnection::sessionBus()); |
401 | struct MountRequest { |
402 | QDBusPendingReply<QString> reply; |
403 | int urlIndex; |
404 | }; |
405 | QList<MountRequest> requests; |
406 | requests.reserve(asize: d->urls.count()); |
407 | |
408 | const QStringList appSupportedProtocols = supportedProtocols(service: d->service); |
409 | for (int i = 0; i < d->urls.count(); ++i) { |
410 | const QUrl url = d->urls.at(i); |
411 | const bool supported = mx1.hasUrls ? d->isUrlSupported(url, protocols: appSupportedProtocols) : url.isLocalFile(); |
412 | if (!supported) { |
413 | // If FUSE fails, and there is no scheme handler, we'll have to fallback to kioexec |
414 | useKioexec = true; |
415 | } |
416 | |
417 | // NOTE: Some non-KIO apps may support the URLs (e.g. VLC supports smb://) |
418 | // but will not have the password if they are not in the URL itself. |
419 | // Hence convert URL to KIOFuse equivalent in case there is a password. |
420 | // @see https://pointieststick.com/2018/01/17/videos-on-samba-shares/ |
421 | // @see https://bugs.kde.org/show_bug.cgi?id=330192 |
422 | if (!supported || (!url.userName().isEmpty() && url.password().isEmpty() && isNonKIO())) { |
423 | requests.push_back(t: {.reply: kiofuse_iface.mountUrl(remoteUrl: url.toString()), .urlIndex: i}); |
424 | } |
425 | } |
426 | |
427 | for (auto &request : requests) { |
428 | request.reply.waitForFinished(); |
429 | } |
430 | const bool fuseError = std::any_of(first: requests.cbegin(), last: requests.cend(), pred: [](const MountRequest &request) { |
431 | return request.reply.isError(); |
432 | }); |
433 | |
434 | if (fuseError && useKioexec) { |
435 | // We need to run the app through kioexec |
436 | result << kioexecPath(); |
437 | if (d->tempFiles) { |
438 | result << QStringLiteral("--tempfiles" ); |
439 | } |
440 | if (!d->suggestedFileName.isEmpty()) { |
441 | result << QStringLiteral("--suggestedfilename" ); |
442 | result << d->suggestedFileName; |
443 | } |
444 | result << exec; |
445 | result += QUrl::toStringList(uris: d->urls); |
446 | return result; |
447 | } |
448 | |
449 | // At this point we know we're not using kioexec, so feel free to replace |
450 | // KIO URLs with their KIOFuse local path. |
451 | for (const auto &request : std::as_const(t&: requests)) { |
452 | if (!request.reply.isError()) { |
453 | d->urls[request.urlIndex] = QUrl::fromLocalFile(localfile: request.reply.value()); |
454 | } |
455 | } |
456 | #endif |
457 | |
458 | if (appHasTempFileOption) { |
459 | exec += QLatin1String(" --tempfile" ); |
460 | } |
461 | |
462 | // Did the user forget to append something like '%f'? |
463 | // If so, then assume that '%f' is the right choice => the application |
464 | // accepts only local files. |
465 | if (!mx1.hasSpec) { |
466 | exec += QLatin1String(" %f" ); |
467 | mx2.ignFile = true; |
468 | } |
469 | |
470 | mx2.expandMacrosShellQuote(str&: exec); // syntax was already checked, so don't check return value |
471 | |
472 | /* |
473 | 1 = need_shell, 2 = terminal, 4 = su |
474 | |
475 | 0 << split(cmd) |
476 | 1 << "sh" << "-c" << cmd |
477 | 2 << split(term) << "-e" << split(cmd) |
478 | 3 << split(term) << "-e" << "sh" << "-c" << cmd |
479 | |
480 | 4 << "kdesu" << "-u" << user << "-c" << cmd |
481 | 5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd)) |
482 | 6 << split(term) << "-e" << "su" << user << "-c" << cmd |
483 | 7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd)) |
484 | |
485 | "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh. |
486 | this could be optimized with the -s switch of some su versions (e.g., debian linux). |
487 | */ |
488 | |
489 | if (d->service.terminal()) { |
490 | KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General" )); |
491 | QString terminal = cg.readPathEntry(key: "TerminalApplication" , QStringLiteral("konsole" )); |
492 | |
493 | const bool isKonsole = (terminal == QLatin1String("konsole" )); |
494 | QStringList terminalParts = KShell::splitArgs(cmd: terminal); |
495 | QString terminalPath; |
496 | if (!terminalParts.isEmpty()) { |
497 | terminalPath = QStandardPaths::findExecutable(executableName: terminalParts.at(i: 0)); |
498 | } |
499 | |
500 | if (terminalPath.isEmpty()) { |
501 | d->m_errorString = i18n("Terminal %1 not found while trying to run %2" , terminal, d->service.entryPath()); |
502 | qCWarning(KIO_CORE) << "Terminal" << terminal << "not found, service" << d->service.name(); |
503 | return QStringList(); |
504 | } |
505 | terminalParts[0] = terminalPath; |
506 | terminal = KShell::joinArgs(args: terminalParts); |
507 | if (isKonsole) { |
508 | if (!d->service.workingDirectory().isEmpty()) { |
509 | terminal += QLatin1String(" --workdir " ) + KShell::quoteArg(arg: d->service.workingDirectory()); |
510 | } |
511 | terminal += QLatin1String(" -qwindowtitle '%c'" ); |
512 | if (!d->service.icon().isEmpty()) { |
513 | terminal += QLatin1String(" -qwindowicon " ) + KShell::quoteArg(arg: d->service.icon().replace(c: QLatin1Char('%'), after: QLatin1String("%%" ))); |
514 | } |
515 | } |
516 | terminal += QLatin1Char(' ') + d->service.terminalOptions(); |
517 | if (!mx1.expandMacrosShellQuote(str&: terminal)) { |
518 | d->m_errorString = i18n("Syntax error in command %1 while trying to run %2" , terminal, d->service.entryPath()); |
519 | qCWarning(KIO_CORE) << "Syntax error in command" << terminal << ", service" << d->service.name(); |
520 | return QStringList(); |
521 | } |
522 | mx2.expandMacrosShellQuote(str&: terminal); |
523 | result = KShell::splitArgs(cmd: terminal); // assuming that the term spec never needs a shell! |
524 | result << QStringLiteral("-e" ); |
525 | } |
526 | |
527 | KShell::Errors err; |
528 | QStringList execlist = KShell::splitArgs(cmd: exec, flags: KShell::AbortOnMeta | KShell::TildeExpand, err: &err); |
529 | if (!executableFullPath.isEmpty()) { |
530 | execlist[0] = executableFullPath; |
531 | } |
532 | |
533 | if (d->service.substituteUid()) { |
534 | if (d->service.terminal()) { |
535 | result << QStringLiteral("su" ); |
536 | } else { |
537 | QString kdesu = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kdesu" ); |
538 | if (!QFile::exists(fileName: kdesu)) { |
539 | kdesu = QStandardPaths::findExecutable(QStringLiteral("kdesu" )); |
540 | } |
541 | if (!QFile::exists(fileName: kdesu)) { |
542 | // Insert kdesu as string so we show a nice warning: 'Could not launch kdesu' |
543 | result << QStringLiteral("kdesu" ); |
544 | return result; |
545 | } else { |
546 | result << kdesu << QStringLiteral("-u" ); |
547 | } |
548 | } |
549 | |
550 | result << d->service.username() << QStringLiteral("-c" ); |
551 | if (err == KShell::FoundMeta) { |
552 | exec = QLatin1String("/bin/sh -c " ) + KShell::quoteArg(arg: exec); |
553 | } else { |
554 | exec = KShell::joinArgs(args: execlist); |
555 | } |
556 | result << exec; |
557 | } else { |
558 | if (err == KShell::FoundMeta) { |
559 | result << QStringLiteral("/bin/sh" ) << QStringLiteral("-c" ) << exec; |
560 | } else { |
561 | result += execlist; |
562 | } |
563 | } |
564 | |
565 | return result; |
566 | } |
567 | |
568 | QString KIO::DesktopExecParser::errorMessage() const |
569 | { |
570 | return d->m_errorString; |
571 | } |
572 | |
573 | // static |
574 | QString KIO::DesktopExecParser::executableName(const QString &execLine) |
575 | { |
576 | const QString bin = executablePath(execLine); |
577 | return bin.mid(position: bin.lastIndexOf(c: QLatin1Char('/')) + 1); |
578 | } |
579 | |
580 | // static |
581 | QString KIO::DesktopExecParser::executablePath(const QString &execLine) |
582 | { |
583 | // Remove parameters and/or trailing spaces. |
584 | const QStringList args = KShell::splitArgs(cmd: execLine, flags: KShell::AbortOnMeta | KShell::TildeExpand); |
585 | auto it = std::find_if(first: args.cbegin(), last: args.cend(), pred: [](const QString &arg) { |
586 | return !arg.contains(c: QLatin1Char('=')); |
587 | }); |
588 | return it != args.cend() ? *it : QString{}; |
589 | } |
590 | |