From 7d05f5ed7d3472028e28a09eeda175bb1b1eeb00 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 1 Feb 2025 14:10:12 -0700 Subject: [PATCH] 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.9 6.8 Fixes: QTBUG-133403 Change-Id: I3fe9d5fbd2efcaa66d66fffdc010e5a84066b641 Reviewed-by: David Faure --- src/corelib/io/qurl.cpp | 30 ++++++++++++++++++++----- tests/auto/corelib/io/qurl/tst_qurl.cpp | 3 +++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index d206df7c6c4..1e1a992719b 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -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; } diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 79c225ba64a..d47dfa2b08a 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -1500,10 +1500,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()