QUrl: Keep one trailing slash when normalizing "file:///b/."

Removing the slash would be a drastic change in behavior. We can expect
many people to rely on the trailing slash to string-append further path
segments without inserting an extra slash.

Amends commit e7bcf41c0b23d83cfb31f966454945c705589a99.

Pick-to: 6.7 6.5
Fixes: QTBUG-128940
Change-Id: I71dfc957f383e4648b3b47a40aeb796d7237fb00
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 2addfa635d2f666d18ffc818f76eb673a6c787d1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Ulf Hermann 2024-09-17 10:11:06 +02:00 committed by Qt Cherry-pick Bot
parent fd3482ea97
commit a3df6c36db
4 changed files with 34 additions and 7 deletions

View File

@ -2215,11 +2215,17 @@ bool QDir::match(const QString &filter, const QString &fileName)
3) a sequence of "//" is treated as multiple path levels ("a/b//.." becomes
"a/b/" and "a/b//../.." becomes "a/"), which matches the behavior
observed in web browsers.
However, QUrl also uses local path mode for local URLs; with one exception:
Even in local mode we leave one trailing slash for paths ending in "/." if
the KeepLocalTrailingSlash flag is given. This reflects how QUrl needs to
treat local URLs due to compatibility constraints.
*/
bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations flags)
{
const bool allowUncPaths = flags.testAnyFlag(QDirPrivate::AllowUncPaths);
const bool isRemote = flags.testAnyFlag(QDirPrivate::RemotePath);
const bool keepLocalTrailingSlash = flags.testAnyFlags(QDirPrivate::KeepLocalTrailingSlash);
const qsizetype prefixLength = rootLength(*path, allowUncPaths);
// RFC 3986 says: "The input buffer is initialized with the now-appended
@ -2331,11 +2337,25 @@ bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations fla
}
if (out > start) {
// backtrack one or all the slashes (so "/tmp///" -> "/tmp/")
// Always backtrack one slash
if (out[-1] == u'/' && in != end)
--out;
while (!isRemote && out > start && out[-1] == u'/')
--out;
if (!isRemote) {
bool removedAnySlashes = false;
// Backtrack all slashes ...
while (out > start && out[-1] == u'/') {
--out;
removedAnySlashes = true;
}
// ... except a trailing one if it exists and flag given
if (removedAnySlashes && keepLocalTrailingSlash && out > start) {
++out;
break;
}
}
}
if (out == start) {
// We've reached the root. Make sure we don't turn a relative path

View File

@ -30,7 +30,8 @@ public:
enum PathNormalization {
DefaultNormalization = 0x00,
AllowUncPaths = 0x01,
RemotePath = 0x02
RemotePath = 0x02,
KeepLocalTrailingSlash = 0x04,
};
Q_DECLARE_FLAGS(PathNormalizations, PathNormalization)
Q_FLAGS(PathNormalizations)

View File

@ -911,7 +911,9 @@ inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions o
{
QString thePath = path;
if (options & QUrl::NormalizePathSegments) {
qt_normalizePathSegments(&thePath, isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
qt_normalizePathSegments(
&thePath,
isLocalFile() ? QDirPrivate::KeepLocalTrailingSlash : QDirPrivate::RemotePath);
}
QStringView thePathView(thePath);
@ -2712,7 +2714,9 @@ QUrl QUrl::resolved(const QUrl &relative) const
else
t.d->sectionIsPresent &= ~QUrlPrivate::Fragment;
qt_normalizePathSegments(&t.d->path, isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
qt_normalizePathSegments(
&t.d->path,
isLocalFile() ? QDirPrivate::KeepLocalTrailingSlash : QDirPrivate::RemotePath);
if (!t.d->hasAuthority())
fixupNonAuthorityPath(&t.d->path);

View File

@ -1497,7 +1497,9 @@ void tst_QUrl::fromLocalFileNormalize_data()
QTest::newRow("relative-dot-dot") << QString::fromLatin1("././a.txt") << QString::fromLatin1("file:././a.txt") << QString::fromLatin1("file:a.txt");
QTest::newRow("relative-path-dotdot") << QString::fromLatin1("b/../a.txt") << QString::fromLatin1("file:b/../a.txt") << QString::fromLatin1("file:a.txt");
QTest::newRow("absolute-path-dotdot") << QString::fromLatin1("/b/../a.txt") << QString::fromLatin1("file:///b/../a.txt") << QString::fromLatin1("file:///a.txt");
QTest::newRow("absolute-path-dot") << QString::fromLatin1("/b/.") << QString::fromLatin1("file:///b/.") << QString::fromLatin1("file:///b");
QTest::newRow("absolute-path-slash") << QString::fromLatin1("/b/") << QString::fromLatin1("file:///b/") << QString::fromLatin1("file:///b/");
QTest::newRow("absolute-path-slahs-dot") << QString::fromLatin1("/b/.") << QString::fromLatin1("file:///b/.") << QString::fromLatin1("file:///b/");
QTest::newRow("absolute-path-slahs-dot-slash") << QString::fromLatin1("/b/./") << QString::fromLatin1("file:///b/./") << QString::fromLatin1("file:///b/");
}
void tst_QUrl::fromLocalFileNormalize()