KIO

desktopexecparser.cpp
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
40class KRunMX1 : public KMacroExpanderBase
41{
42public:
43 explicit KRunMX1(const KService &_service)
45 , service(_service)
46 {
47 }
48
49 bool hasUrls = false;
50 bool hasSpec = false;
51 bool hasError = false;
52
53protected:
54 int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
55
56private:
57 const KService &service;
58};
59
60int 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(QLatin1Char('%'), QLatin1String("%%"));
73 break;
74 case 'k':
75 ret << service.entryPath().replace(QLatin1Char('%'), QLatin1String("%%"));
76 break;
77 case 'i':
78 ret << QStringLiteral("--icon") << service.icon().replace(QLatin1Char('%'), 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
105class KRunMX2 : public KMacroExpanderBase
106{
107public:
108 explicit KRunMX2(const QList<QUrl> &_urls)
110 , ignFile(false)
111 , urls(_urls)
112 {
113 }
114
115 bool ignFile;
116
117protected:
118 int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
119
120private:
121 void subst(int option, const QUrl &url, QStringList &ret);
122
123 const QList<QUrl> &urls;
124};
125
126void 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(url.toLocalFile()) : url.toString());
131 break;
132 case 'd':
133 ret << url.adjusted(QUrl::RemoveFilename).path();
134 break;
135 case 'f':
137 break;
138 case 'n':
139 ret << url.fileName();
140 break;
141 case 'v':
142 if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
143 ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry("Dev");
144 }
145 break;
146 }
147 return;
148}
149
150int 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, 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
188{
190
191 KRunMX1 mx1(service);
192 QString exec = service.exec();
193 if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
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 {
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(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
216bool KIO::DesktopExecParser::isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols)
217{
218 return url.isLocalFile() //
219 || supportedProtocols.contains(QLatin1String("KIO")) //
220 || supportedProtocols.contains(url.scheme(), 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
226bool KIO::DesktopExecParser::hasSchemeHandler(const QUrl &url) // KF6 TODO move to OpenUrlJob
227{
229 return true;
230 }
231 const KService::Ptr service = KApplicationTrader::preferredService(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
238class KIO::DesktopExecParserPrivate
239{
240public:
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
258 : d(new DesktopExecParserPrivate(service, urls))
259{
260}
261
265
267{
268 d->tempFiles = tempFiles;
269}
270
272{
273 d->suggestedFileName = suggestedFileName;
274}
275
276static const QString kioexecPath()
277{
279 if (!QFileInfo::exists(kioexec)) {
280 kioexec = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kioexec");
281 }
282 Q_ASSERT(QFileInfo::exists(kioexec));
283 return kioexec;
284}
285
286static 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(qgetenv("PATH")).split(QDir::listSeparator(), 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
313bool KIO::DesktopExecParserPrivate::isUrlSupported(const QUrl &url, const QStringList &protocols)
314{
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(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
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(exec);
342 QString executableFullPath;
343 if (!binary.isEmpty()) { // skip all this if the Exec line is a complex shell command
344 if (QDir::isRelativePath(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(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(binary);
359 if (nonExecutable.isEmpty()) {
360 d->m_errorString = i18n("Could not find the program '%1'", binary);
361 } else {
362 if (QDir::isRelativePath(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(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(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(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 {
411 int urlIndex;
412 };
413 QList<MountRequest> requests;
414 requests.reserve(d->urls.count());
415
416 const QStringList appSupportedProtocols = supportedProtocols(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, 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 // @see https://pointieststick.com/2018/01/17/videos-on-samba-shares/
429 // @see https://bugs.kde.org/show_bug.cgi?id=330192
430 if (!supported || (!url.userName().isEmpty() && url.password().isEmpty() && isNonKIO())) {
431 requests.push_back({kiofuse_iface.mountUrl(url.toString()), i});
432 }
433 }
434
435 for (auto &request : requests) {
436 request.reply.waitForFinished();
437 }
438 const bool fuseError = std::any_of(requests.cbegin(), requests.cend(), [](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(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(requests)) {
460 if (!request.reply.isError()) {
461 d->urls[request.urlIndex] = QUrl::fromLocalFile(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(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("TerminalApplication", QStringLiteral("konsole"));
500
501 const bool isKonsole = (terminal == QLatin1String("konsole"));
502 QStringList terminalParts = KShell::splitArgs(terminal);
503 QString terminalPath;
504 if (!terminalParts.isEmpty()) {
505 terminalPath = QStandardPaths::findExecutable(terminalParts.at(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(terminalParts);
515 if (isKonsole) {
516 if (!d->service.workingDirectory().isEmpty()) {
517 terminal += QLatin1String(" --workdir ") + KShell::quoteArg(d->service.workingDirectory());
518 }
519 terminal += QLatin1String(" -qwindowtitle '%c'");
520 if (!d->service.icon().isEmpty()) {
521 terminal += QLatin1String(" -qwindowicon ") + KShell::quoteArg(d->service.icon().replace(QLatin1Char('%'), QLatin1String("%%")));
522 }
523 }
524 terminal += QLatin1Char(' ') + d->service.terminalOptions();
525 if (!mx1.expandMacrosShellQuote(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(terminal);
531 result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell!
532 result << QStringLiteral("-e");
533 }
534
535 KShell::Errors 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(kdesu)) {
547 kdesu = QStandardPaths::findExecutable(QStringLiteral("kdesu"));
548 }
549 if (!QFile::exists(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(exec);
561 } else {
562 exec = KShell::joinArgs(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
577{
578 return d->m_errorString;
579}
580
581// static
583{
584 const QString bin = executablePath(execLine);
585 return bin.mid(bin.lastIndexOf(QLatin1Char('/')) + 1);
586}
587
588// static
590{
591 // Remove parameters and/or trailing spaces.
593 auto it = std::find_if(args.cbegin(), args.cend(), [](const QString &arg) {
594 return !arg.contains(QLatin1Char('='));
595 });
596 return it != args.cend() ? *it : QString{};
597}
QString readPathEntry(const char *key, const QString &aDefault) const
QString readEntry(const char *key, const char *aDefault=nullptr) const
KConfigGroup desktopGroup() const
static bool hasSchemeHandler(const QUrl &url)
Returns true if protocol should be opened by a "handler" application, i.e. an application associated ...
void setUrlsAreTempFiles(bool tempFiles)
If tempFiles is set to true and the urls given to the constructor are local files,...
QStringList resultingArguments() const
DesktopExecParser(const KService &service, const QList< QUrl > &urls)
Creates a parser for a desktop file Exec line.
static QString executablePath(const QString &execLine)
Given a full command line (e.g. the Exec= line from a .desktop file), extract the name of the executa...
static QString executableName(const QString &execLine)
Given a full command line (e.g. the Exec= line from a .desktop file), extract the name of the executa...
static QStringList supportedProtocols(const KService &service)
Returns the list of protocols which the application supports.
static bool isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols)
Returns true if protocol is in the list of protocols returned by supportedProtocols().
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...
bool expandMacrosShellQuote(QString &str)
static bool isHelperProtocol(const QUrl &url)
Returns whether the protocol can act as a helper protocol.
QString desktopEntryName() const
QStringList supportedProtocols() const
T property(const QString &name) const
QString exec() const
QString icon() const
bool isApplication() const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString name() const
QString entryPath() const
QString i18n(const char *text, const TYPE &arg...)
KSERVICE_EXPORT KService::List queryByMimeType(const QString &mimeType, FilterFunc filterFunc={})
KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType)
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
KCOREADDONS_EXPORT QString quoteArg(const QString &arg)
KCOREADDONS_EXPORT QString joinArgs(const QStringList &args)
QString applicationDirPath()
QDBusConnection sessionBus()
bool isRelativePath(const QString &path)
QChar listSeparator()
QString toNativeSeparators(const QString &pathName)
QString decodeName(const QByteArray &localFileName)
bool exists() const const
bool exists() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
const_iterator cbegin() const const
const_iterator cend() const const
qsizetype count() const const
T & first()
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const QChar * unicode() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
SkipEmptyParts
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
QString fragment(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isLocalFile() const const
QString password(ComponentFormattingOptions options) const const
QString path(ComponentFormattingOptions options) const const
QString query(ComponentFormattingOptions options) const const
QString scheme() const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
QStringList toStringList(const QList< QUrl > &urls, FormattingOptions options)
QString userName(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 20 2024 11:49:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.