QUrl: avoid going up from the drive path on Windows file URLs
On Windows, using a URL of "file:///c:/" as a base to be resolved with "../" should not result in the Windows drive being removed. [ChangeLog][QtCore][QUrl] Fixed a bug (regression from 6.7) where resolving a base URL of an absolute file path containing a Windows drive could result in said drive being removed (e.g., resolving "file:///c:/" with "../" would result in "file:///"). Fixes: QTBUG-133402 Pick-to: 6.8 Change-Id: I58286b9c5e5d02363f0efffdb06f983b560340df Reviewed-by: David Faure <david.faure@kdab.com> (cherry picked from commit 340c9d88ab353e201f117d64609fa5f7d2fa2b21) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
86597d2891
commit
8c5736e68f
@ -52,23 +52,40 @@ static QString driveSpec(const QString &path)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Return the length of the root part of an absolute path, for use by cleanPath(), cd().
|
// Return the length of the root part of an absolute path, for use by cleanPath(), cd().
|
||||||
static qsizetype rootLength(QStringView name)
|
static qsizetype rootLength(QStringView name, QDirPrivate::PathNormalizations flags)
|
||||||
{
|
{
|
||||||
|
constexpr bool UseWindowsRules = false // So we don't #include <QOperatingSystemVersion>
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
const qsizetype len = name.size();
|
|| true
|
||||||
// Handle possible UNC paths which start with double slash
|
|
||||||
if (name.startsWith("//"_L1)) {
|
|
||||||
// Server name '//server/path' is part of the prefix.
|
|
||||||
const qsizetype nextSlash = name.indexOf(u'/', 2);
|
|
||||||
return nextSlash >= 0 ? nextSlash + 1 : len;
|
|
||||||
}
|
|
||||||
if (len >= 2 && name.at(1) == u':') {
|
|
||||||
// Handle a possible drive letter
|
|
||||||
return len > 2 && name.at(2) == u'/' ? 3 : 2;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
;
|
||||||
|
const qsizetype len = name.size();
|
||||||
|
char16_t firstChar = len > 0 ? name.at(0).unicode() : u'\0';
|
||||||
|
char16_t secondChar = len > 1 ? name.at(1).unicode() : u'\0';
|
||||||
|
if constexpr (UseWindowsRules) {
|
||||||
|
// Handle possible UNC paths which start with double slash
|
||||||
|
bool urlMode = flags.testAnyFlags(QDirPrivate::UrlNormalizationMode);
|
||||||
|
if (firstChar == u'/' && secondChar == u'/' && !urlMode) {
|
||||||
|
// Server name '//server/path' is part of the prefix.
|
||||||
|
const qsizetype nextSlash = name.indexOf(u'/', 2);
|
||||||
|
return nextSlash >= 0 ? nextSlash + 1 : len;
|
||||||
|
}
|
||||||
|
|
||||||
return name.startsWith(u'/') ? 1 : 0;
|
// Handle a possible drive letter
|
||||||
|
qsizetype driveLength = 2;
|
||||||
|
if (firstChar == u'/' && urlMode && len > 2 && name.at(2) == u':') {
|
||||||
|
// Drive-in-URL-Path mode, e.g. "/c:" or "/c:/autoexec.bat"
|
||||||
|
++driveLength;
|
||||||
|
secondChar = u':';
|
||||||
|
}
|
||||||
|
if (secondChar == u':') {
|
||||||
|
if (len > driveLength && name.at(driveLength) == u'/')
|
||||||
|
return driveLength + 1; // absolute drive path, e.g. "c:/config.sys"
|
||||||
|
return driveLength; // relative drive path, e.g. "c:" or "d:swapfile.sys"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstChar == u'/' ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//************* QDirPrivate
|
//************* QDirPrivate
|
||||||
@ -2217,7 +2234,7 @@ bool QDir::match(const QString &filter, const QString &fileName)
|
|||||||
bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations flags)
|
bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations flags)
|
||||||
{
|
{
|
||||||
const bool isRemote = flags.testAnyFlag(QDirPrivate::RemotePath);
|
const bool isRemote = flags.testAnyFlag(QDirPrivate::RemotePath);
|
||||||
const qsizetype prefixLength = rootLength(*path);
|
const qsizetype prefixLength = rootLength(*path, flags);
|
||||||
|
|
||||||
// RFC 3986 says: "The input buffer is initialized with the now-appended
|
// RFC 3986 says: "The input buffer is initialized with the now-appended
|
||||||
// path components and the output buffer is initialized to the empty
|
// path components and the output buffer is initialized to the empty
|
||||||
|
@ -29,6 +29,7 @@ class QDirPrivate : public QSharedData
|
|||||||
public:
|
public:
|
||||||
enum PathNormalization {
|
enum PathNormalization {
|
||||||
DefaultNormalization = 0x00,
|
DefaultNormalization = 0x00,
|
||||||
|
UrlNormalizationMode = 0x01,
|
||||||
RemotePath = 0x02,
|
RemotePath = 0x02,
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(PathNormalizations, PathNormalization)
|
Q_DECLARE_FLAGS(PathNormalizations, PathNormalization)
|
||||||
|
@ -550,6 +550,13 @@ public:
|
|||||||
inline bool isLocalFile() const { return flags & IsLocalFile; }
|
inline bool isLocalFile() const { return flags & IsLocalFile; }
|
||||||
QString toLocalFile(QUrl::FormattingOptions options) const;
|
QString toLocalFile(QUrl::FormattingOptions options) const;
|
||||||
|
|
||||||
|
bool normalizePathSegments(QString *path) const
|
||||||
|
{
|
||||||
|
QDirPrivate::PathNormalizations mode = QDirPrivate::UrlNormalizationMode;
|
||||||
|
if (!isLocalFile())
|
||||||
|
mode |= QDirPrivate::RemotePath;
|
||||||
|
return qt_normalizePathSegments(path, mode);
|
||||||
|
}
|
||||||
QString mergePaths(const QString &relativePath) const;
|
QString mergePaths(const QString &relativePath) const;
|
||||||
|
|
||||||
QAtomicInt ref;
|
QAtomicInt ref;
|
||||||
@ -910,11 +917,8 @@ inline void QUrlPrivate::appendPassword(QString &appendTo, QUrl::FormattingOptio
|
|||||||
inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const
|
inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const
|
||||||
{
|
{
|
||||||
QString thePath = path;
|
QString thePath = path;
|
||||||
if (options & QUrl::NormalizePathSegments) {
|
if (options & QUrl::NormalizePathSegments)
|
||||||
qt_normalizePathSegments(
|
normalizePathSegments(&thePath);
|
||||||
&thePath,
|
|
||||||
isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringView thePathView(thePath);
|
QStringView thePathView(thePath);
|
||||||
if (options & QUrl::RemoveFilename) {
|
if (options & QUrl::RemoveFilename) {
|
||||||
@ -2714,9 +2718,7 @@ QUrl QUrl::resolved(const QUrl &relative) const
|
|||||||
else
|
else
|
||||||
t.d->sectionIsPresent &= ~QUrlPrivate::Fragment;
|
t.d->sectionIsPresent &= ~QUrlPrivate::Fragment;
|
||||||
|
|
||||||
qt_normalizePathSegments(
|
t.d->normalizePathSegments(&t.d->path);
|
||||||
&t.d->path,
|
|
||||||
isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
|
|
||||||
if (!t.d->hasAuthority()) {
|
if (!t.d->hasAuthority()) {
|
||||||
if (t.d->isLocalFile() && t.d->path.startsWith(u'/'))
|
if (t.d->isLocalFile() && t.d->path.startsWith(u'/'))
|
||||||
t.d->sectionIsPresent |= QUrlPrivate::Host;
|
t.d->sectionIsPresent |= QUrlPrivate::Host;
|
||||||
|
@ -61,6 +61,8 @@ private slots:
|
|||||||
void fromLocalFile();
|
void fromLocalFile();
|
||||||
void fromLocalFileNormalize_data();
|
void fromLocalFileNormalize_data();
|
||||||
void fromLocalFileNormalize();
|
void fromLocalFileNormalize();
|
||||||
|
void fromLocalFileNormalizeNonRoundtrip_data();
|
||||||
|
void fromLocalFileNormalizeNonRoundtrip();
|
||||||
void macTypes();
|
void macTypes();
|
||||||
void relative();
|
void relative();
|
||||||
void compat_legacy();
|
void compat_legacy();
|
||||||
@ -1424,6 +1426,10 @@ void tst_QUrl::fromLocalFile_data()
|
|||||||
// Windows absolute details
|
// Windows absolute details
|
||||||
QTest::newRow("windows-drive") << QString::fromLatin1("c:/a.txt") << QString::fromLatin1("file:///c:/a.txt") << QString::fromLatin1("/c:/a.txt");
|
QTest::newRow("windows-drive") << QString::fromLatin1("c:/a.txt") << QString::fromLatin1("file:///c:/a.txt") << QString::fromLatin1("/c:/a.txt");
|
||||||
|
|
||||||
|
// Handling of Windows roots with relative - note, no normalization!
|
||||||
|
QTest::newRow("windows-drive-above-root")
|
||||||
|
<< QString::fromLatin1("c:/../a.txt") << QString::fromLatin1("file:///c:/../a.txt") << QString::fromLatin1("/c:/../a.txt");
|
||||||
|
|
||||||
// Windows UNC paths
|
// Windows UNC paths
|
||||||
for (const char *suffix : { "", "/", "/somedir/somefile" }) {
|
for (const char *suffix : { "", "/", "/somedir/somefile" }) {
|
||||||
const char *pathDescription =
|
const char *pathDescription =
|
||||||
@ -1525,6 +1531,69 @@ void tst_QUrl::fromLocalFileNormalize()
|
|||||||
QCOMPARE(url.toString(QUrl::NormalizePathSegments), urlWithNormalizedPath);
|
QCOMPARE(url.toString(QUrl::NormalizePathSegments), urlWithNormalizedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QUrl::fromLocalFileNormalizeNonRoundtrip_data()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
static constexpr bool IsWindows = true;
|
||||||
|
#else
|
||||||
|
static constexpr bool IsWindows = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QTest::addColumn<QString>("input");
|
||||||
|
QTest::addColumn<QString>("theUrl");
|
||||||
|
QTest::addColumn<QString>("thePath");
|
||||||
|
QTest::addColumn<QString>("urlWithNormalizedPath");
|
||||||
|
|
||||||
|
QTest::newRow("server") << u"//server"_s << u"file://server"_s << QString() << u"file://server"_s;
|
||||||
|
QTest::newRow("server/..") << u"//server/.."_s << u"file://server/.."_s << u"/.."_s << u"file://server/.."_s;
|
||||||
|
QTest::newRow("server/share") << u"//server/share"_s << u"file://server/share"_s << u"/share"_s << u"file://server/share"_s;
|
||||||
|
QTest::newRow("server/share/..") << u"//server/share/.."_s << u"file://server/share/.."_s << u"/share/.."_s << u"file://server/"_s;
|
||||||
|
|
||||||
|
auto addAbsoluteWindowsPathRow = [](const char *name, const QString &input,
|
||||||
|
const QString &unixNormalized, const QString &windowsNormalized) {
|
||||||
|
QString thePath = '/' + input; // fromPercentEncoding, but works for now
|
||||||
|
QString theUrl = "file://" + thePath;
|
||||||
|
const QString &normalized = IsWindows ? windowsNormalized : unixNormalized;
|
||||||
|
QTest::newRow(name) << input << theUrl << thePath << normalized;
|
||||||
|
};
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive", "c:", "file:///c:", "file:///c:");
|
||||||
|
addAbsoluteWindowsPathRow("absolute-drive", "c:/", "file:///c:/", "file:///c:/");
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive/path", "c:autoexec.bat",
|
||||||
|
"file:///c:autoexec.bat", "file:///c:autoexec.bat");
|
||||||
|
addAbsoluteWindowsPathRow("absolute-drive/path", "c:/config.sys",
|
||||||
|
"file:///c:/config.sys", "file:///c:/config.sys");
|
||||||
|
addAbsoluteWindowsPathRow("absolute-drive/path/..", "c:/dos/..",
|
||||||
|
"file:///c:/", "file:///c:/");
|
||||||
|
addAbsoluteWindowsPathRow("absolute-drive/path/../", "c:/dos/../",
|
||||||
|
"file:///c:/", "file:///c:/");
|
||||||
|
|
||||||
|
// The drive root should remain for the normalized URLs on Windows
|
||||||
|
addAbsoluteWindowsPathRow("absolute-drive/..", "c:/..",
|
||||||
|
"file:///", "file:///c:/..");
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive/path/..", "c:dos/..",
|
||||||
|
"file:///", "file:///c:");
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive/path/../", "c:dos/../",
|
||||||
|
"file:///", "file:///c:"); // Note: trailing / would change meaning!
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive/path/../..", "c:dos/../..",
|
||||||
|
"file:///..", "file:///c:..");
|
||||||
|
addAbsoluteWindowsPathRow("relative-drive/path/../../", "c:dos/../../",
|
||||||
|
"file:///../", "file:///c:../");
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QUrl::fromLocalFileNormalizeNonRoundtrip()
|
||||||
|
{
|
||||||
|
QFETCH(QString, input);
|
||||||
|
QFETCH(QString, theUrl);
|
||||||
|
QFETCH(QString, thePath);
|
||||||
|
QFETCH(QString, urlWithNormalizedPath);
|
||||||
|
|
||||||
|
QUrl url = QUrl::fromLocalFile(input);
|
||||||
|
|
||||||
|
QCOMPARE(url.toString(QUrl::DecodeReserved), theUrl);
|
||||||
|
QCOMPARE(url.path(), thePath);
|
||||||
|
QCOMPARE(url.toString(QUrl::NormalizePathSegments), urlWithNormalizedPath);
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QUrl::macTypes()
|
void tst_QUrl::macTypes()
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_DARWIN
|
#ifndef Q_OS_DARWIN
|
||||||
|
Loading…
x
Reference in New Issue
Block a user