QUrl: expand the square brackets encoding to decoded setPath() calls

Amends (reverts) commit 5e936b60fc921e21b8153a83113886a1de333b57, which
forced the encoding of the square brackets ("[]") into their percent-
encoded forms only in QUrl::fromLocalFile(). This commit expands the
functionality to all uses of decoded paths by applying the change
directly to recodeFromUser().

[ChangeLog][QtCore][QUrl] Square brackets ("[]") are now transformed to
their percent-encoded forms ("%5B" and "%5D") when present as inputs to
setPath(), setQuery(), and setFragment() if the parsing mode is
QUrl::DecodedMode (the default).

Pick-to: 6.8
Fixes: QTBUG-135433
Task-number: QTBUG-134073
See: https://bugs.kde.org/show_bug.cgi?id=502280
Change-Id: Id2b41559af08b5f228e4fffdb9c6f23120f856b5
Reviewed-by: David Faure <david.faure@kdab.com>
(cherry picked from commit f3da9d3c858e8dc28a5dc91047b592ed5becbd62)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2025-04-02 18:46:41 -07:00 committed by Qt Cherry-pick Bot
parent bbe0b18b38
commit 01ff6e19f7
2 changed files with 33 additions and 50 deletions

View File

@ -748,32 +748,6 @@ static const ushort * const pathInIsolation = userNameInIsolation + 5;
static const ushort * const queryInIsolation = userNameInIsolation + 6; static const ushort * const queryInIsolation = userNameInIsolation + 6;
static const ushort * const fragmentInIsolation = userNameInIsolation + 7; static const ushort * const fragmentInIsolation = userNameInIsolation + 7;
static const ushort localPathFromUser[] = {
// we force-decode some of the gen-delims, because
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
// the gen-delim lines are leave() in qt_urlRecode, so we don't need to
// repeat them if we want to keep them decoded
// decode(':'), // allowed
// decode('@'), // allowed
encode(']'),
encode('['),
// decode('/'), // special and allowed
// decode('?'), // handled by path() and others
// decode('#'), // ditto
// the rest is like pathInIsolation above
decode('"'),
decode('<'),
decode('>'),
decode('^'),
decode('\\'),
decode('|'),
decode('{'),
decode('}'),
0
};
static const ushort userNameInUserInfo[] = { static const ushort userNameInUserInfo[] = {
encode(':'), // 0 encode(':'), // 0
decode('@'), // 1 decode('@'), // 1
@ -833,9 +807,11 @@ static const ushort * const pathInUrl = userNameInUrl + 5;
static const ushort * const queryInUrl = userNameInUrl + 6; static const ushort * const queryInUrl = userNameInUrl + 6;
static const ushort * const fragmentInUrl = userNameInUrl + 6; static const ushort * const fragmentInUrl = userNameInUrl + 6;
static inline void parseDecodedComponent(QString &data) static inline void parseDecodedComponent(QString &data, QUrlPrivate::Section section)
{ {
data.replace(u'%', "%25"_L1); data.replace(u'%', "%25"_L1);
if (section != QUrlPrivate::Host)
data.replace(u'[', "%5B"_L1).replace(u']', "%5D"_L1);
} }
static inline QString static inline QString
@ -2135,7 +2111,7 @@ void QUrl::setUserName(const QString &userName, ParsingMode mode)
QString data = userName; QString data = userName;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::UserName);
mode = TolerantMode; mode = TolerantMode;
} }
@ -2198,7 +2174,7 @@ void QUrl::setPassword(const QString &password, ParsingMode mode)
QString data = password; QString data = password;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::Password);
mode = TolerantMode; mode = TolerantMode;
} }
@ -2260,7 +2236,7 @@ void QUrl::setHost(const QString &host, ParsingMode mode)
QString data = host; QString data = host;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::Host);
mode = TolerantMode; mode = TolerantMode;
} }
@ -2385,7 +2361,7 @@ void QUrl::setPath(const QString &path, ParsingMode mode)
QString data = path; QString data = path;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::Path);
mode = TolerantMode; mode = TolerantMode;
} }
@ -2521,7 +2497,7 @@ void QUrl::setQuery(const QString &query, ParsingMode mode)
QString data = query; QString data = query;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::Query);
mode = TolerantMode; mode = TolerantMode;
} }
@ -2619,7 +2595,7 @@ void QUrl::setFragment(const QString &fragment, ParsingMode mode)
QString data = fragment; QString data = fragment;
if (mode == DecodedMode) { if (mode == DecodedMode) {
parseDecodedComponent(data); parseDecodedComponent(data, QUrlPrivate::Fragment);
mode = TolerantMode; mode = TolerantMode;
} }
@ -3386,11 +3362,7 @@ QUrl QUrl::fromLocalFile(const QString &localFile)
} }
url.setScheme(scheme); url.setScheme(scheme);
url.setPath(deslashified, DecodedMode);
// not directly using setPath here, as we do a few more transforms
parseDecodedComponent(deslashified);
if (!qt_urlRecode(url.d->path, deslashified, {}, localPathFromUser))
url.d->path = std::move(deslashified);
return url; return url;
} }

View File

@ -1422,6 +1422,11 @@ void tst_QUrl::toLocalFile()
url.setPath(url.path(QUrl::PrettyDecoded), QUrl::TolerantMode); url.setPath(url.path(QUrl::PrettyDecoded), QUrl::TolerantMode);
QCOMPARE(url.toLocalFile(), theFile); QCOMPARE(url.toLocalFile(), theFile);
QCOMPARE(url.isLocalFile(), !theFile.isEmpty()); QCOMPARE(url.isLocalFile(), !theFile.isEmpty());
// local file paths can be fully decoded without loss
url.setPath(url.path());
QCOMPARE(url.toLocalFile(), theFile);
QCOMPARE(url.isLocalFile(), !theFile.isEmpty());
} }
void tst_QUrl::fromLocalFile_data() void tst_QUrl::fromLocalFile_data()
@ -1508,6 +1513,11 @@ void tst_QUrl::fromLocalFile()
url.setPath(url.path(QUrl::PrettyDecoded), QUrl::TolerantMode); url.setPath(url.path(QUrl::PrettyDecoded), QUrl::TolerantMode);
QCOMPARE(url.toString(QUrl::DecodeReserved), theUrl); QCOMPARE(url.toString(QUrl::DecodeReserved), theUrl);
QCOMPARE(url.path(), thePath); QCOMPARE(url.path(), thePath);
// local file paths can be fully decoded without loss
url.setPath(url.path());
QCOMPARE(url.toString(QUrl::DecodeReserved), theUrl);
QCOMPARE(url.path(), thePath);
} }
void tst_QUrl::fromLocalFileNormalize_data() void tst_QUrl::fromLocalFileNormalize_data()
@ -4150,24 +4160,25 @@ void tst_QUrl::setComponents_data()
<< int(Scheme) << "http%61" << Decoded << false << int(Scheme) << "http%61" << Decoded << false
<< PrettyDecoded << "" << ""; << PrettyDecoded << "" << "";
QTest::newRow("username-encode") << QUrl("http://example.com") QTest::newRow("username-encode") << QUrl("http://example.com")
<< int(UserName) << "h%61llo:world" << Decoded << true << int(UserName) << "h%61llo[:]world" << Decoded << true
<< PrettyDecoded << "h%2561llo:world" << "http://h%2561llo%3Aworld@example.com"; << PrettyDecoded << "h%2561llo[:]world" << "http://h%2561llo%5B%3A%5Dworld@example.com";
QTest::newRow("password-encode") << QUrl("http://example.com") QTest::newRow("password-encode") << QUrl("http://example.com")
<< int(Password) << "h%61llo:world@" << Decoded << true << int(Password) << "h%61llo[:]world@" << Decoded << true
<< PrettyDecoded << "h%2561llo:world@" << "http://:h%2561llo:world%40@example.com"; << PrettyDecoded << "h%2561llo[:]world@" << "http://:h%2561llo%5B:%5Dworld%40@example.com";
// '%' characters are not permitted in the hostname, these test that it fails to set anything // '%' characters are not permitted in the hostname, these test that it fails to set anything
QTest::newRow("invalid-host-encode") << QUrl("http://example.com") QTest::newRow("invalid-host-encode") << QUrl("http://example.com")
<< int(Host) << "ex%61mple.com" << Decoded << false << int(Host) << "ex%61mple.com" << Decoded << false
<< PrettyDecoded << QString() << QString(); << PrettyDecoded << QString() << QString();
// square brackets are force-encoded from decoded forms in the path, query, and fragment
QTest::newRow("path-encode") << QUrl("http://example.com/foo") QTest::newRow("path-encode") << QUrl("http://example.com/foo")
<< int(Path) << "/bar%23" << Decoded << true << int(Path) << "/ba[r]%23" << Decoded << true
<< PrettyDecoded << "/bar%2523" << "http://example.com/bar%2523"; << PrettyDecoded << "/ba%5Br%5D%2523" << "http://example.com/ba%5Br%5D%2523";
QTest::newRow("query-encode") << QUrl("http://example.com/foo?q") QTest::newRow("query-encode") << QUrl("http://example.com/foo?q")
<< int(Query) << "bar%23" << Decoded << true << int(Query) << "ba[r]%23" << Decoded << true
<< PrettyDecoded << "bar%2523" << "http://example.com/foo?bar%2523"; << PrettyDecoded << "ba%5Br%5D%2523" << "http://example.com/foo?ba%5Br%5D%2523";
QTest::newRow("fragment-encode") << QUrl("http://example.com/foo#z") QTest::newRow("fragment-encode") << QUrl("http://example.com/foo#z")
<< int(Fragment) << "bar%23" << Decoded << true << int(Fragment) << "ba[r]%23" << Decoded << true
<< PrettyDecoded << "bar%2523" << "http://example.com/foo#bar%2523"; << PrettyDecoded << "ba%5Br%5D%2523" << "http://example.com/foo#ba%5Br%5D%2523";
// force decoding // force decoding
QTest::newRow("username-decode") << QUrl("http://example.com") QTest::newRow("username-decode") << QUrl("http://example.com")
<< int(UserName) << "hello%3Aworld%25" << Tolerant << true << int(UserName) << "hello%3Aworld%25" << Tolerant << true
@ -4176,8 +4187,8 @@ void tst_QUrl::setComponents_data()
<< int(Password) << "}}>b9o%25kR(" << Tolerant << true << int(Password) << "}}>b9o%25kR(" << Tolerant << true
<< FullyDecoded << "}}>b9o%kR(" << "http://:%7D%7D%3Eb9o%25kR(@example.com"; << FullyDecoded << "}}>b9o%kR(" << "http://:%7D%7D%3Eb9o%25kR(@example.com";
QTest::newRow("path-decode") << QUrl("http://example.com/") QTest::newRow("path-decode") << QUrl("http://example.com/")
<< int(Path) << "/bar%25foo" << Tolerant << true << int(Path) << "/bar%25[foo]" << Tolerant << true
<< FullyDecoded << "/bar%foo" << "http://example.com/bar%25foo"; << FullyDecoded << "/bar%[foo]" << "http://example.com/bar%25[foo]";
QTest::newRow("query-decode") << QUrl("http://example.com/foo?qq") QTest::newRow("query-decode") << QUrl("http://example.com/foo?qq")
<< int(Query) << "bar%25foo" << Tolerant << true << int(Query) << "bar%25foo" << Tolerant << true
<< FullyDecoded << "bar%foo" << "http://example.com/foo?bar%25foo"; << FullyDecoded << "bar%foo" << "http://example.com/foo?bar%25foo";