QUrl: set the host to empty but present for "file" URLs

We set it when manipulating full URLs. If you're creating them from
parts, you may end up with one without a host. We will still fix that up
in QUrl::toString() ("manipulates full URL").

This allows the path normalization code to avoid accidentally creating a
URL with no host/authority and with a path that starts with double
slash.

[ChangeLog][QtCore][QUrl] Fixed a bug (regression from 6.7) where
QUrl::resolved() could create invalid URLs when the relative URI being
resolved contained a path with double slashes (e.g., combining
"scheme:a" with "..//b.txt")

Pick-to: 6.8
Fixes: QTBUG-133403
Change-Id: I3fe9d5fbd2efcaa66d66fffdc010e5a84066b641
Reviewed-by: David Faure <david.faure@kdab.com>
(cherry picked from commit 7d05f5ed7d3472028e28a09eeda175bb1b1eeb00)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2025-02-01 14:10:12 -07:00 committed by Qt Cherry-pick Bot
parent d37960b35a
commit 86597d2891
2 changed files with 27 additions and 6 deletions

View File

@ -2717,8 +2717,12 @@ QUrl QUrl::resolved(const QUrl &relative) const
qt_normalizePathSegments(
&t.d->path,
isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
if (!t.d->hasAuthority())
fixupNonAuthorityPath(&t.d->path);
if (!t.d->hasAuthority()) {
if (t.d->isLocalFile() && t.d->path.startsWith(u'/'))
t.d->sectionIsPresent |= QUrlPrivate::Host;
else
fixupNonAuthorityPath(&t.d->path);
}
#if defined(QURL_DEBUG)
qDebug("QUrl(\"%ls\").resolved(\"%ls\") = \"%ls\"",
@ -2893,6 +2897,11 @@ QUrl QUrl::adjusted(QUrl::FormattingOptions options) const
d->appendPath(path, options | FullyEncoded, QUrlPrivate::Path);
that.d->setPath(path, 0, path.size());
}
if (that.d->isLocalFile() && that.d->path.startsWith(u'/')) {
// ensure absolute file URLs have an empty authority to comply with the
// XDG file spec (note this may undo a RemoveAuthority)
that.d->sectionIsPresent |= QUrlPrivate::Host;
}
return that;
}
@ -3307,15 +3316,18 @@ static QString fromNativeSeparators(const QString &pathName)
QUrl QUrl::fromLocalFile(const QString &localFile)
{
QUrl url;
if (localFile.isEmpty())
QString deslashified = fromNativeSeparators(localFile);
if (deslashified.isEmpty())
return url;
QString scheme = fileScheme();
QString deslashified = fromNativeSeparators(localFile);
char16_t firstChar = deslashified.at(0).unicode();
char16_t secondChar = deslashified.size() > 1 ? deslashified.at(1).unicode() : u'\0';
// magic for drives on windows
if (deslashified.size() > 1 && deslashified.at(1) == u':' && deslashified.at(0) != u'/') {
if (firstChar != u'/' && secondChar == u':') {
deslashified.prepend(u'/');
} else if (deslashified.startsWith("//"_L1)) {
firstChar = u'/';
} else if (firstChar == u'/' && secondChar == u'/') {
// magic for shared drive on windows
qsizetype indexOfPath = deslashified.indexOf(u'/', 2);
QStringView hostSpec = QStringView{deslashified}.mid(2, indexOfPath - 2);
@ -3339,9 +3351,15 @@ QUrl QUrl::fromLocalFile(const QString &localFile)
deslashified.clear();
}
}
if (firstChar == u'/') {
// ensure absolute file URLs have an empty authority to comply with the XDG file spec
url.detach();
url.d->sectionIsPresent |= QUrlPrivate::Host;
}
url.setScheme(scheme);
url.setPath(deslashified, DecodedMode);
return url;
}

View File

@ -1502,10 +1502,13 @@ void tst_QUrl::fromLocalFileNormalize_data()
QTest::newRow("relative-dot") << QString::fromLatin1("./a.txt") << QString::fromLatin1("file:./a.txt") << QString::fromLatin1("file:a.txt");
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("relative-path-dotdot-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-dotdot-dotdot") << QString::fromLatin1("/b/../../a.txt") << QString::fromLatin1("file:///b/../../a.txt") << QString::fromLatin1("file:///../a.txt");
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/");
QTest::newRow("absolute-path-dotdot-slashslash") << QString::fromLatin1("/b/..//") << QString::fromLatin1("file:///b/..//") << QString::fromLatin1("file:////");
}
void tst_QUrl::fromLocalFileNormalize()