Skip to content

Commit

Permalink
QUrl: Keep one trailing slash when normalizing "file:///b/."
Browse files Browse the repository at this point in the history
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 e7bcf41.

Pick-to: 6.5
Fixes: QTBUG-128940
Change-Id: I71dfc957f383e4648b3b47a40aeb796d7237fb00
Reviewed-by: Thiago Macieira <[email protected]>
(cherry picked from commit 2addfa6)
Reviewed-by: Qt Cherry-pick Bot <[email protected]>
(cherry picked from commit a3df6c3)
  • Loading branch information
Ulf Hermann authored and Qt Cherry-pick Bot committed Sep 19, 2024
1 parent eed1302 commit 0fa94a6
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 7 deletions.
26 changes: 23 additions & 3 deletions src/corelib/io/qdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2213,11 +2213,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
Expand Down Expand Up @@ -2329,11 +2335,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
Expand Down
3 changes: 2 additions & 1 deletion src/corelib/io/qdir_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class QDirPrivate : public QSharedData
enum PathNormalization {
DefaultNormalization = 0x00,
AllowUncPaths = 0x01,
RemotePath = 0x02
RemotePath = 0x02,
KeepLocalTrailingSlash = 0x04,
};
Q_DECLARE_FLAGS(PathNormalizations, PathNormalization)
Q_FLAGS(PathNormalizations)
Expand Down
8 changes: 6 additions & 2 deletions src/corelib/io/qurl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,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);
Expand Down Expand Up @@ -2709,7 +2711,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);

Expand Down
4 changes: 3 additions & 1 deletion tests/auto/corelib/io/qurl/tst_qurl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,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()
Expand Down

0 comments on commit 0fa94a6

Please sign in to comment.