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