KIO

kshorturifilter.cpp
1/*
2 kshorturifilter.h
3
4 This file is part of the KDE project
5 SPDX-FileCopyrightText: 2000 Dawit Alemayehu <adawit@kde.org>
6 SPDX-FileCopyrightText: 2000 Malte Starostik <starosti@zedat.fu-berlin.de>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include "kshorturifilter.h"
12#include "../utils_p.h"
13
14#include <QDBusConnection>
15#include <QDir>
16#include <QLoggingCategory>
17#include <qplatformdefs.h>
18
19#include <KApplicationTrader>
20#include <KConfig>
21#include <KConfigGroup>
22#include <KLocalizedString>
23#include <KPluginFactory>
24#include <KService>
25#include <KUser>
26#include <kprotocolinfo.h>
27#include <kurlauthorized.h>
28
29namespace
30{
31Q_LOGGING_CATEGORY(category, "kf.kio.urifilters.shorturi", QtWarningMsg)
32}
33
34static bool isPotentialShortURL(const QString &cmd)
35{
36 // Host names and IPv4 address...
37 // Exclude ".." and paths starting with "../", these are used to go up in a filesystem
38 // dir hierarchy
39 if (cmd != QLatin1String("..") && !cmd.startsWith(QLatin1String("../")) && cmd.contains(QLatin1Char('.'))) {
40 return true;
41 }
42
43 // IPv6 Address...
44 if (cmd.startsWith(QLatin1Char('[')) && cmd.contains(QLatin1Char(':'))) {
45 return true;
46 }
47
48 return false;
49}
50
51static QString removeArgs(const QString &_cmd)
52{
53 QString cmd(_cmd);
54
55 if (cmd.isEmpty()) {
56 return cmd;
57 }
58
59 if (cmd[0] != QLatin1Char('\'') && cmd[0] != QLatin1Char('"')) {
60 // Remove command-line options (look for first non-escaped space)
61 int spacePos = 0;
62
63 do {
64 spacePos = cmd.indexOf(QLatin1Char(' '), spacePos + 1);
65 } while (spacePos > 1 && cmd[spacePos - 1] == QLatin1Char('\\'));
66
67 if (spacePos > 0) {
68 cmd.truncate(spacePos);
69 qCDebug(category) << "spacePos=" << spacePos << " returning " << cmd;
70 }
71 }
72
73 return cmd;
74}
75
76static bool isKnownProtocol(const QString &protocol)
77{
78 if (KProtocolInfo::isKnownProtocol(protocol, false) || protocol == QLatin1String("mailto")) {
79 return true;
80 }
81 const KService::Ptr service = KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + protocol);
82 return service;
83}
84
85KShortUriFilter::KShortUriFilter(QObject *parent, const KPluginMetaData &data)
86 : KUriFilterPlugin(parent, data)
87{
89 .connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(configure()));
90 configure();
91}
92
94{
95 /*
96 * Here is a description of how the shortURI deals with the supplied
97 * data. First it expands any environment variable settings and then
98 * deals with special shortURI cases. These special cases are the "smb:"
99 * URL scheme which is very specific to KDE, "#" and "##" which are
100 * shortcuts for man:/ and info:/ protocols respectively. It then handles
101 * local files. Then it checks to see if the URL is valid and one that is
102 * supported by KDE's IO system. If all the above checks fails, it simply
103 * lookups the URL in the user-defined list and returns without filtering
104 * if it is not found. TODO: the user-defined table is currently only manually
105 * hackable and is missing a config dialog.
106 */
107
108 // QUrl url = data.uri();
109 QString cmd = data.typedString();
110
111 int firstNonSlash = 0;
112 while (firstNonSlash < cmd.length() && (cmd.at(firstNonSlash) == QLatin1Char('/'))) {
113 firstNonSlash++;
114 }
115 if (firstNonSlash > 1) {
116 cmd.remove(0, firstNonSlash - 1);
117 }
118
119 // Replicate what KUrl(cmd) did in KDE4. This could later be folded into the checks further down...
120 QUrl url;
121 if (Utils::isAbsoluteLocalPath(cmd)) {
122 url = QUrl::fromLocalFile(cmd);
123 } else {
124 url.setUrl(cmd);
125 }
126
127 // WORKAROUND: Allow the use of '@' in the username component of a URL since
128 // other browsers such as firefox in their infinite wisdom allow such blatant
129 // violations of RFC 3986. BR# 69326/118413.
130 if (cmd.count(QLatin1Char('@')) > 1) {
131 const int lastIndex = cmd.lastIndexOf(QLatin1Char('@'));
132 // Percent encode all but the last '@'.
133 const auto suffix = QStringView(cmd).mid(lastIndex);
134 cmd = QString::fromUtf8(QUrl::toPercentEncoding(cmd.left(lastIndex), QByteArrayLiteral(":/"))) + suffix;
135 url.setUrl(cmd);
136 }
137
138 const bool isMalformed = !url.isValid();
139 QString protocol = url.scheme();
140
141 qCDebug(category) << cmd;
142
143 // Fix misparsing of "foo:80", QUrl thinks "foo" is the protocol and "80" is the path.
144 // However, be careful not to do that for valid hostless URLs, e.g. file:///foo!
145 if (!protocol.isEmpty() && url.host().isEmpty() && !url.path().isEmpty() && cmd.contains(QLatin1Char(':')) && !isKnownProtocol(protocol)) {
146 protocol.clear();
147 }
148
149 qCDebug(category) << "url=" << url << "cmd=" << cmd << "isMalformed=" << isMalformed;
150
151 // TODO: Make this a bit more intelligent for Minicli! There
152 // is no need to make comparisons if the supplied data is a local
153 // executable and only the argument part, if any, changed! (Dawit)
154 // You mean caching the last filtering, to try and reuse it, to save stat()s? (David)
155
156 const QLatin1String starthere_proto("start-here:");
157 if (cmd.startsWith(starthere_proto)) {
158 setFilteredUri(data, QUrl(QStringLiteral("system:/")));
159 setUriType(data, KUriFilterData::LocalDir);
160 return true;
161 }
162
163 // Handle MAN & INFO pages shortcuts...
164 const QLatin1String man_proto("man:");
165 const QLatin1String info_proto("info:");
166 if (cmd.startsWith(QLatin1Char('#')) || cmd.startsWith(man_proto) || cmd.startsWith(info_proto)) {
167 QStringView sview(cmd);
168 if (cmd.startsWith(QLatin1String("##"))) {
169 cmd = QLatin1String("info:/") + sview.mid(2);
170 } else if (cmd.startsWith(QLatin1Char('#'))) {
171 cmd = QLatin1String("man:/") + sview.mid(1);
172 } else if (cmd == info_proto || cmd == man_proto) {
173 cmd += QLatin1Char('/');
174 }
175
176 setFilteredUri(data, QUrl(cmd));
177 setUriType(data, KUriFilterData::Help);
178 return true;
179 }
180
181 // Detect UNC style (aka windows SMB) URLs
182 if (cmd.startsWith(QLatin1String("\\\\"))) {
183 // make sure path is unix style
184 cmd.replace(QLatin1Char('\\'), QLatin1Char('/'));
185 cmd.prepend(QLatin1String("smb:"));
186 setFilteredUri(data, QUrl(cmd));
187 setUriType(data, KUriFilterData::NetProtocol);
188 return true;
189 }
190
191 bool expanded = false;
192
193 // Expanding shortcut to HOME URL...
194 QString path;
195 QString ref;
196 QString query;
197 QString nameFilter;
198
199 if (!Utils::isAbsoluteLocalPath(cmd) && QUrl(cmd).isRelative()) {
200 path = cmd;
201 qCDebug(category) << "path=cmd=" << path;
202 } else {
203 if (url.isLocalFile()) {
204 qCDebug(category) << "hasRef=" << url.hasFragment();
205 // Split path from ref/query
206 // but not for "/tmp/a#b", if "a#b" is an existing file,
207 // or for "/tmp/a?b" (#58990)
208 if ((url.hasFragment() || !url.query().isEmpty()) && !url.path().endsWith(QLatin1Char('/'))) { // /tmp/?foo is a namefilter, not a query
209 path = url.path();
210 ref = url.fragment();
211 qCDebug(category) << "isLocalFile set path to" << path << "and ref to" << ref;
212 query = url.query();
213 if (path.isEmpty() && !url.host().isEmpty()) {
214 path = QStringLiteral("/");
215 }
216 } else {
217 if (cmd.startsWith(QLatin1String("file://"))) {
218 path = cmd.mid(strlen("file://"));
219 } else {
220 path = cmd;
221 }
222 qCDebug(category) << "(2) path=cmd=" << path;
223 }
224 }
225 }
226
227 if (path.startsWith(QLatin1Char('~'))) {
228 int slashPos = path.indexOf(QLatin1Char('/'));
229 if (slashPos == -1) {
230 slashPos = path.length();
231 }
232 if (slashPos == 1) { // ~/
233 path.replace(0, 1, QDir::homePath());
234 } else { // ~username/
235 const QString userName(path.mid(1, slashPos - 1));
236 KUser user(userName);
237 if (user.isValid() && !user.homeDir().isEmpty()) {
238 path.replace(0, slashPos, user.homeDir());
239 } else {
240 if (user.isValid()) {
241 setErrorMsg(data, i18n("<qt><b>%1</b> does not have a home folder.</qt>", userName));
242 } else {
243 setErrorMsg(data, i18n("<qt>There is no user called <b>%1</b>.</qt>", userName));
244 }
245 setUriType(data, KUriFilterData::Error);
246 // Always return true for error conditions so
247 // that other filters will not be invoked !!
248 return true;
249 }
250 }
251 expanded = true;
252 } else if (path.startsWith(QLatin1Char('$'))) {
253 // Environment variable expansion.
254 static const QRegularExpression envVarExp(QStringLiteral("\\$[a-zA-Z_][a-zA-Z0-9_]*"));
255 const auto match = envVarExp.match(path);
256 if (match.hasMatch()) {
257 const QByteArray exp = qgetenv(QStringView(path).mid(1, match.capturedLength(0) - 1).toLocal8Bit().constData());
258 if (!exp.isEmpty()) {
259 path.replace(0, match.capturedLength(0), QFile::decodeName(exp));
260 expanded = true;
261 }
262 }
263 }
264
265 if (expanded || cmd.startsWith(QLatin1Char('/'))) {
266 // Look for #ref again, after $ and ~ expansion (testcase: $QTDIR/doc/html/functions.html#s)
267 // Can't use QUrl here, setPath would escape it...
268 const int pos = path.indexOf(QLatin1Char('#'));
269 if (pos > -1) {
270 const QString newPath = path.left(pos);
271 if (QFile::exists(newPath)) {
272 ref = path.mid(pos + 1);
273 path = newPath;
274 qCDebug(category) << "Extracted ref: path=" << path << " ref=" << ref;
275 }
276 }
277 }
278
279 bool isLocalFullPath = Utils::isAbsoluteLocalPath(path);
280
281 // Checking for local resource match...
282 // Determine if "uri" is an absolute path to a local resource OR
283 // A local resource with a supplied absolute path in KUriFilterData
284 const QString abs_path = data.absolutePath();
285
286 const bool canBeAbsolute = (protocol.isEmpty() && !abs_path.isEmpty());
287 const bool canBeLocalAbsolute = (canBeAbsolute && abs_path.startsWith(QLatin1Char('/')) && !isMalformed);
288 bool exists = false;
289
290 /*qCDebug(category) << "abs_path=" << abs_path
291 << "protocol=" << protocol
292 << "canBeAbsolute=" << canBeAbsolute
293 << "canBeLocalAbsolute=" << canBeLocalAbsolute
294 << "isLocalFullPath=" << isLocalFullPath;*/
295
296 QT_STATBUF buff;
297 if (canBeLocalAbsolute) {
298 QString abs = QDir::cleanPath(abs_path);
299 // combine absolute path (abs_path) and relative path (cmd) into abs_path
300 int len = path.length();
301 if ((len == 1 && path[0] == QLatin1Char('.')) || (len == 2 && path[0] == QLatin1Char('.') && path[1] == QLatin1Char('.'))) {
302 path += QLatin1Char('/');
303 }
304 qCDebug(category) << "adding " << abs << " and " << path;
305 abs = QDir::cleanPath(abs + QLatin1Char('/') + path);
306 qCDebug(category) << "checking whether " << abs << " exists.";
307 // Check if it exists
308 if (QT_STAT(QFile::encodeName(abs).constData(), &buff) == 0) {
309 path = abs; // yes -> store as the new cmd
310 exists = true;
311 isLocalFullPath = true;
312 }
313 }
314
315 if (isLocalFullPath && !exists && !isMalformed) {
316 exists = QT_STAT(QFile::encodeName(path).constData(), &buff) == 0;
317
318 if (!exists) {
319 // Support for name filter (/foo/*.txt), see also KonqMainWindow::detectNameFilter
320 // If the app using this filter doesn't support it, well, it'll simply error out itself
321 int lastSlash = path.lastIndexOf(QLatin1Char('/'));
322 if (lastSlash > -1
323 && path.indexOf(QLatin1Char(' '), lastSlash) == -1) { // no space after last slash, otherwise it's more likely command-line arguments
324 QString fileName = path.mid(lastSlash + 1);
325 QString testPath = path.left(lastSlash);
326 if ((fileName.indexOf(QLatin1Char('*')) != -1 || fileName.indexOf(QLatin1Char('[')) != -1 || fileName.indexOf(QLatin1Char('?')) != -1)
327 && QT_STAT(QFile::encodeName(testPath).constData(), &buff) == 0) {
328 nameFilter = fileName;
329 qCDebug(category) << "Setting nameFilter to" << nameFilter << "and path to" << testPath;
330 path = testPath;
331 exists = true;
332 }
333 }
334 }
335 }
336
337 qCDebug(category) << "path =" << path << " isLocalFullPath=" << isLocalFullPath << " exists=" << exists << " url=" << url;
338 if (exists) {
339 QUrl u = QUrl::fromLocalFile(path);
340 qCDebug(category) << "ref=" << ref << "query=" << query;
341 u.setFragment(ref);
342 u.setQuery(query);
343
344 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), u)) {
345 // No authorization, we pretend it's a file will get
346 // an access denied error later on.
347 setFilteredUri(data, u);
348 setUriType(data, KUriFilterData::LocalFile);
349 return true;
350 }
351
352 // Can be abs path to file or directory, or to executable with args
353 bool isDir = Utils::isDirMask(buff.st_mode);
354 if (!isDir && access(QFile::encodeName(path).data(), X_OK) == 0) {
355 qCDebug(category) << "Abs path to EXECUTABLE";
356 setFilteredUri(data, u);
357 setUriType(data, KUriFilterData::Executable);
358 return true;
359 }
360
361 // Open "uri" as file:/xxx if it is a non-executable local resource.
362 if (isDir || Utils::isRegFileMask(buff.st_mode)) {
363 qCDebug(category) << "Abs path as local file or directory";
364 if (!nameFilter.isEmpty()) {
365 u.setPath(Utils::concatPaths(u.path(), nameFilter));
366 }
367 setFilteredUri(data, u);
368 setUriType(data, (isDir) ? KUriFilterData::LocalDir : KUriFilterData::LocalFile);
369 return true;
370 }
371
372 // Should we return LOCAL_FILE for non-regular files too?
373 qCDebug(category) << "File found, but not a regular file nor dir... socket?";
374 }
375
376 if (data.checkForExecutables()) {
377 // Let us deal with possible relative URLs to see
378 // if it is executable under the user's $PATH variable.
379 // We try hard to avoid parsing any possible command
380 // line arguments or options that might have been supplied.
381 QString exe = removeArgs(cmd);
382 qCDebug(category) << "findExe with" << exe;
383
384 if (!QStandardPaths::findExecutable(exe).isNull()) {
385 qCDebug(category) << "EXECUTABLE exe=" << exe;
386 setFilteredUri(data, QUrl::fromLocalFile(exe));
387 // check if we have command line arguments
388 if (exe != cmd) {
389 setArguments(data, cmd.right(cmd.length() - exe.length()));
390 }
391 setUriType(data, KUriFilterData::Executable);
392 return true;
393 }
394 }
395
396 // Process URLs of known and supported protocols so we don't have
397 // to resort to the pattern matching scheme below which can possibly
398 // slow things down...
399 if (!isMalformed && !isLocalFullPath && !protocol.isEmpty()) {
400 qCDebug(category) << "looking for protocol" << protocol;
401 if (isKnownProtocol(protocol)) {
402 setFilteredUri(data, url);
403 if (protocol == QLatin1String("man") || protocol == QLatin1String("help")) {
404 setUriType(data, KUriFilterData::Help);
405 } else {
406 setUriType(data, KUriFilterData::NetProtocol);
407 }
408 return true;
409 }
410 }
411
412 // Short url matches
413 if (!cmd.contains(QLatin1Char(' '))) {
414 // Okay this is the code that allows users to supply custom matches for specific
415 // URLs using Qt's QRegularExpression class.
416 for (const URLHint &hint : std::as_const(m_urlHints)) {
417 qCDebug(category) << "testing regexp for" << hint.prepend;
418 if (hint.hintRe.match(cmd).capturedStart() == 0) {
419 const QString cmdStr = hint.prepend + cmd;
420 QUrl cmdUrl(cmdStr);
421 qCDebug(category) << "match - prepending" << hint.prepend << "->" << cmdStr << "->" << cmdUrl;
422 setFilteredUri(data, cmdUrl);
423 setUriType(data, hint.type);
424 return true;
425 }
426 }
427
428 // No protocol and not malformed means a valid short URL such as kde.org or
429 // user@192.168.0.1. However, it might also be valid only because it lacks
430 // the scheme component, e.g. www.kde,org (illegal ',' before 'org'). The
431 // check below properly deciphers the difference between the two and sends
432 // back the proper result.
433 if (protocol.isEmpty() && isPotentialShortURL(cmd)) {
434 QString urlStr = data.defaultUrlScheme();
435 if (urlStr.isEmpty()) {
436 urlStr = m_strDefaultUrlScheme;
437 }
438
439 const int index = urlStr.indexOf(QLatin1Char(':'));
440 if (index == -1 || !isKnownProtocol(urlStr.left(index))) {
441 urlStr += QStringLiteral("://");
442 }
443 urlStr += cmd;
444
445 QUrl fixedUrl(urlStr);
446 if (fixedUrl.isValid()) {
447 setFilteredUri(data, fixedUrl);
448 setUriType(data, KUriFilterData::NetProtocol);
449 } else if (isKnownProtocol(fixedUrl.scheme())) {
450 setFilteredUri(data, data.uri());
451 setUriType(data, KUriFilterData::Error);
452 }
453 return true;
454 }
455 }
456
457 // If we previously determined that the URL might be a file,
458 // and if it doesn't exist... we'll pretend it exists.
459 // This allows to use it for completion purposes.
460 // (If you change this logic again, look at the commit that was testing
461 // for KUrlAuthorized::authorizeUrlAction("open"))
462 if (isLocalFullPath && !exists) {
463 QUrl u = QUrl::fromLocalFile(path);
464 u.setFragment(ref);
465 setFilteredUri(data, u);
466 setUriType(data, KUriFilterData::LocalFile);
467 return true;
468 }
469
470 // If we reach this point, we cannot filter this thing so simply return false
471 // so that other filters, if present, can take a crack at it.
472 return false;
473}
474
475void KShortUriFilter::configure()
476{
477 KConfig config(objectName() + QStringLiteral("rc"), KConfig::NoGlobals);
478 KConfigGroup cg(config.group(QString()));
479
480 m_strDefaultUrlScheme = cg.readEntry("DefaultProtocol", QStringLiteral("https://"));
481 const QMap<QString, QString> patterns = config.entryMap(QStringLiteral("Pattern"));
482 const QMap<QString, QString> protocols = config.entryMap(QStringLiteral("Protocol"));
483 KConfigGroup typeGroup(&config, QStringLiteral("Type"));
484
485 for (auto it = patterns.begin(); it != patterns.end(); ++it) {
486 QString protocol = protocols[it.key()];
487 if (!protocol.isEmpty()) {
488 int type = typeGroup.readEntry(it.key(), -1);
489 if (type > -1 && type <= KUriFilterData::Unknown) {
490 m_urlHints.append(URLHint(it.value(), protocol, static_cast<KUriFilterData::UriTypes>(type)));
491 } else {
492 m_urlHints.append(URLHint(it.value(), protocol));
493 }
494 }
495 }
496}
497
498K_PLUGIN_CLASS_WITH_JSON(KShortUriFilter, "kshorturifilter.json")
499
500#include "kshorturifilter.moc"
501
502#include "moc_kshorturifilter.cpp"
#define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile)
static bool isKnownProtocol(const QUrl &url)
Returns whether a protocol is installed that is able to handle url.
This is short URL filter class.
bool filterUri(KUriFilterData &data) const override
Converts short URIs into fully qualified valid URIs whenever possible.
This class is a basic messaging class used to exchange filtering information between the filter plugi...
Definition kurifilter.h:153
QString absolutePath() const
Returns the absolute path if one has already been set.
QUrl uri() const
Returns the filtered or the original URL.
bool checkForExecutables() const
QString typedString() const
The string as typed by the user, before any URL processing is done.
UriTypes
Describes the type of the URI that was filtered.
Definition kurifilter.h:158
@ Error
An incorrect URI (ex: "~johndoe" when user johndoe does not exist in that system)
Definition kurifilter.h:166
@ NetProtocol
Any network protocol: http, ftp, nttp, pop3, etc...
Definition kurifilter.h:159
@ Unknown
A URI that is not identified. Default value when a KUriFilterData is first created.
Definition kurifilter.h:167
@ Executable
A local file whose executable flag is set.
Definition kurifilter.h:162
@ LocalFile
A local file whose executable flag is not set.
Definition kurifilter.h:160
@ Help
A man or info page.
Definition kurifilter.h:163
@ LocalDir
A local directory.
Definition kurifilter.h:161
QString defaultUrlScheme() const
Returns the default protocol to use when filtering potentially valid url inputs.
QString homeDir() const
bool isValid() const
QString i18n(const char *text, const TYPE &arg...)
KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType)
KGuiItem configure()
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
const char * constData() const const
bool isEmpty() const const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection sessionBus()
QString cleanPath(const QString &path)
QString homePath()
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists() const const
void append(QList< T > &&value)
iterator begin()
iterator end()
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QString findExecutable(const QString &executableName, const QStringList &paths)
qsizetype count() const const
const QChar at(qsizetype position) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & prepend(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString right(qsizetype n) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
void truncate(qsizetype position)
QStringView mid(qsizetype start, qsizetype length) const const
QByteArray toLocal8Bit() const const
QString fragment(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool hasFragment() const const
QString host(ComponentFormattingOptions options) const const
bool isLocalFile() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QString query(ComponentFormattingOptions options) const const
QString scheme() const const
void setFragment(const QString &fragment, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
void setUrl(const QString &url, ParsingMode parsingMode)
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.