QUrl/QDir: revert all recent changes to path normalization

This amends commit c023016ffdda9e2a27c2257ca85a8ffd384e8c08 to revert
all of the following commits:
214fbc658bc78ad39e0fb33daf2a3a3fa621b02b
492c646735faa2cad6d0459066dfd723b7ee99b0
804b0996746daae9cf3c901ce353261e1edd7728
e4576684681df8fe6d65a2f72379968c187db108
a1610c6c68ca2c9a3855a4e5e947033e6410d7c9

The reason for this is that they introduce a behavior change in QUrl,
causing the normalization/resolution of file:// directory paths not to
end in "/" and this behavior change revealed buggy code in QML that
concatenated strings expecting the slash to be there. Commit
c023016ffdda9e2 attempted to do that too, but it left QUrl in a state
that introduced a regression from 6.7.

Discussion on whether we will accept this behavior change (and require
fixes to buggy code) or keep the old behavior to avoid breakages will
happen for 6.8.x.

This commit reintroduces bug QTBUG-120396 to 6.8.0.

Task-number: QTBUG-120396
Fixes: QTBUG-128940
Change-Id: Id78899df1cea4321d9e1fffdcc0f406bbfd534b8
Reviewed-by: Jani Heikkinen <jani.heikkinen@qt.io>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Thiago Macieira 2024-09-18 09:48:24 -07:00
parent c023016ffd
commit e8122a0a9c
5 changed files with 203 additions and 376 deletions

View File

@ -2199,173 +2199,168 @@ bool QDir::match(const QString &filter, const QString &fileName)
/*!
\internal
Updates \a path with redundant directory separators removed, and "."s and
".."s resolved (as far as possible). It returns \c false if there were ".."
segments left over, attempt to go up past the root (only applies to
absolute paths), or \c true otherwise.
Returns \a path with redundant directory separators removed,
and "."s and ".."s resolved (as far as possible).
This method is shared with QUrl, so it doesn't deal with QDir::separator(),
nor does it remove the trailing slash, if any.
When dealing with URLs, we are following section 5.2.4 (Remove dot
segments) from http://www.ietf.org/rfc/rfc3986.txt. URL mode differs from
from local path mode in these ways:
1) it can set *path to empty ("." becomes "")
2) directory path outputs end in / ("a/.." becomes "a/" instead of "a")
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.
*/
bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations flags)
QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok)
{
const bool allowUncPaths = flags.testAnyFlag(QDirPrivate::AllowUncPaths);
const bool isRemote = flags.testAnyFlag(QDirPrivate::RemotePath);
const qsizetype prefixLength = rootLength(*path, allowUncPaths);
const qsizetype len = name.size();
// RFC 3986 says: "The input buffer is initialized with the now-appended
// path components and the output buffer is initialized to the empty
// string."
const QChar *in = path->constBegin();
if (ok)
*ok = false;
// Scan the input for a "." or ".." segment. If there isn't any, we may not
// need to modify this path at all. Also scan for "//" segments, which
// will be normalized if the path is local.
qsizetype i = prefixLength;
qsizetype n = path->size();
for (bool lastWasSlash = true; i < n; ++i) {
if (lastWasSlash && in[i] == u'.') {
if (i + 1 == n || in[i + 1] == u'/')
break;
if (in[i + 1] == u'.' && (i + 2 == n || in[i + 2] == u'/'))
break;
}
if (!isRemote && lastWasSlash && in[i] == u'/' && i > 0) {
// backtrack one, so the algorithm below gobbles up the remaining
// slashes
--i;
break;
}
lastWasSlash = in[i] == u'/';
if (len == 0)
return name;
qsizetype i = len - 1;
QVarLengthArray<char16_t> outVector(len);
qsizetype used = len;
char16_t *out = outVector.data();
const char16_t *p = reinterpret_cast<const char16_t *>(name.data());
const char16_t *prefix = p;
qsizetype up = 0;
const qsizetype prefixLength = rootLength(name, allowUncPaths);
p += prefixLength;
i -= prefixLength;
// replicate trailing slash (i > 0 checks for emptiness of input string p)
// except for remote paths because there can be /../ or /./ ending
if (i > 0 && p[i] == '/' && !isRemote) {
out[--used] = '/';
--i;
}
if (i == n)
return true;
QChar *out = path->data(); // detaches
const QChar *start = out + prefixLength;
const QChar *end = out + path->size();
out += i;
in = out;
auto isDot = [](const char16_t *p, qsizetype i) {
return i > 1 && p[i - 1] == '.' && p[i - 2] == '/';
};
auto isDotDot = [](const char16_t *p, qsizetype i) {
return i > 2 && p[i - 1] == '.' && p[i - 2] == '.' && p[i - 3] == '/';
};
// We implement a modified algorithm compared to RFC 3986, for efficiency.
bool ok = true;
do {
#if 0 // to see in the debugger
QString output = QStringView(path->constBegin(), out).toString();
QStringView input(in, end);
#endif
// First, copy the preceding slashes, so we can look at the segment's
// content. If the path is part of a URL, we copy all slashes, otherwise
// just one.
if (in[0] == u'/') {
*out++ = *in++;
while (in < end && in[0] == u'/') {
if (isRemote)
*out++ = *in++;
else
++in;
// Note: we may exit this loop with in == end, in which case we
// *shouldn't* dereference *in. But since we are pointing to a
// detached, non-empty QString, we know there's a u'\0' at the
// end, so dereferencing is safe.
while (i >= 0) {
// copy trailing slashes for remote urls
if (p[i] == '/') {
if (isRemote && !up) {
if (isDot(p, i)) {
i -= 2;
continue;
}
out[--used] = p[i];
}
}
// Is this path segment either "." or ".."?
enum { Nothing, Dot, DotDot } type = Nothing;
if (in[0] == u'.') {
if (in + 1 == end || in[1] == u'/')
type = Dot;
else if (in[1] == u'.' && (in + 2 == end || in[2] == u'/'))
type = DotDot;
}
if (type == Nothing) {
// If it is neither, then we copy this segment.
while (in < end && in[0] != u'/')
*out++ = *in++;
--i;
continue;
}
// Otherwise, we skip it and remove preceding slashes (if
// any, exactly one if part of a URL, all otherwise) from the
// output. If it is "..", we remove the segment before that and
// preceding slashes too in a similar fashion, if they are there.
if (type == DotDot) {
if (Q_UNLIKELY(out == start)) {
// we can't go further up from here, so we "re-root"
// without cleaning this segment
ok = false;
if (!isRemote) {
*out++ = u'.';
*out++ = u'.';
if (in + 2 != end) {
Q_ASSERT(in[2] == u'/');
*out++ = u'/';
++in;
}
start = out;
in += 2;
continue;
// remove current directory
if (p[i] == '.' && (i == 0 || p[i-1] == '/')) {
--i;
continue;
}
// detect up dir
if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) {
++up;
i -= i >= 2 ? 3 : 2;
if (isRemote) {
// moving up should consider empty path segments too (/path//../ -> /path/)
while (i > 0 && up && p[i] == '/') {
--up;
--i;
}
}
while (out > start && *--out != u'/')
;
while (!isRemote && out > start && out[-1] == u'/')
--out;
while (out > start && out[-1] != u'/')
--out;
in += 2; // the two dots
} else {
++in; // the one dot
continue;
}
if (out > start) {
// backtrack one or all the slashes (so "/tmp///" -> "/tmp/")
if (out[-1] == u'/' && in != end)
--out;
while (!isRemote && out > start && out[-1] == u'/')
--out;
// prepend a slash before copying when not empty
if (!up && used != len && out[used] != '/')
out[--used] = '/';
// skip or copy
while (i >= 0) {
if (p[i] == '/') {
// copy all slashes as is for remote urls if they are not part of /./ or /../
if (isRemote && !up) {
while (i > 0 && p[i] == '/' && !isDotDot(p, i)) {
if (isDot(p, i)) {
i -= 2;
continue;
}
out[--used] = p[i];
--i;
}
// in case of /./, jump over
if (isDot(p, i))
i -= 2;
break;
}
--i;
break;
}
// actual copy
if (!up)
out[--used] = p[i];
--i;
}
if (out == start) {
// We've reached the root. Make sure we don't turn a relative path
// to absolute or, in the case of local paths that are already
// absolute, into UNC.
// Note: this will turn ".//a" into "a" even for URLs!
if (in != end && in[0] == u'/')
++in;
while (prefixLength == 0 && in != end && in[0] == u'/')
++in;
}
} while (in < end);
path->truncate(out - path->constBegin());
if (!isRemote && path->isEmpty())
*path = u"."_s;
// decrement up after copying/skipping
if (up)
--up;
}
// we return false only if the path was absolute
return ok || prefixLength == 0;
}
QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok)
{
// temporary compat
QString copy = name;
bool r = qt_normalizePathSegments(&copy, flags);
// Indicate failure when ".." are left over for an absolute path.
if (ok)
*ok = r;
return copy;
*ok = prefixLength == 0 || up == 0;
// add remaining '..'
while (up && !isRemote) {
if (used != len && out[used] != '/') // is not empty and there isn't already a '/'
out[--used] = '/';
out[--used] = '.';
out[--used] = '.';
--up;
}
bool isEmpty = used == len;
if (prefixLength) {
if (!isEmpty && out[used] == '/') {
// Even though there is a prefix the out string is a slash. This happens, if the input
// string only consists of a prefix followed by one or more slashes. Just skip the slash.
++used;
}
for (qsizetype i = prefixLength - 1; i >= 0; --i)
out[--used] = prefix[i];
} else {
if (isEmpty) {
// After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return
// a dot in that case.
out[--used] = '.';
} else if (out[used] == '/') {
// After parsing the input string, out only contains a slash. That happens whenever all
// parts are resolved and there is a trailing slash ("./" or "foo/../" for example).
// Prepend a dot to have the correct return value.
out[--used] = '.';
}
}
// If path was not modified return the original value
if (used == 0)
return name;
return QStringView(out + used, len - used).toString();
}
static QString qt_cleanPath(const QString &path, bool *ok)

View File

@ -81,7 +81,6 @@ public:
Q_DECLARE_OPERATORS_FOR_FLAGS(QDirPrivate::PathNormalizations)
Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok = nullptr);
bool qt_normalizePathSegments(QString *path, QDirPrivate::PathNormalizations flags);
QT_END_NAMESPACE

View File

@ -911,7 +911,7 @@ inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions o
{
QString thePath = path;
if (options & QUrl::NormalizePathSegments) {
qt_normalizePathSegments(&thePath, isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
thePath = qt_normalizePathSegments(path, isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
}
QStringView thePathView(thePath);
@ -1529,121 +1529,81 @@ inline QString QUrlPrivate::mergePaths(const QString &relativePath) const
Removes unnecessary ../ and ./ from the path. Used for normalizing
the URL.
This code has a Qt-specific extension to handle empty path segments (a.k.a.
multiple slashes like "a//b"). We try to keep them wherever possible
because with some protocols they are meaningful, but we still consider them
to be a single directory transition for "." or ".." (e.g., "a/b//c" +
"../" is "a/"). See tst_QUrl::resolved() for the expected behavior.
*/
static void removeDotsFromPath(QString *path)
{
// The input buffer is initialized with the now-appended path
// components and the output buffer is initialized to the empty
// string.
const QChar *in = path->constBegin();
// Scan the input for a "." or ".." segment. If there isn't any, then we
// don't need to modify this path at all.
qsizetype i = 0, n = path->size();
for (bool lastWasSlash = true; i < n; ++i) {
if (lastWasSlash && in[i] == u'.') {
if (i + 1 == n || in[i + 1] == u'/')
break;
if (in[i + 1] == u'.' && (i + 2 == n || in[i + 2] == u'/'))
break;
}
lastWasSlash = in[i] == u'/';
}
if (i == n)
return;
QChar *out = path->data();
const QChar *in = out;
const QChar *end = out + path->size();
out += i;
in = out;
// We implement a modified algorithm compared to RFC 3986, for efficiency.
do {
#if 0 // to see in the debugger
QStringView output(path->constBegin(), out);
QStringView input(in, end);
#endif
// First, copy any preceding slashes, so we can look at the segment's
// content.
while (in < end && in[0] == u'/') {
*out++ = *in++;
// If the input buffer consists only of
// "." or "..", then remove that from the input
// buffer;
if (path->size() == 1 && in[0].unicode() == '.')
++in;
else if (path->size() == 2 && in[0].unicode() == '.' && in[1].unicode() == '.')
in += 2;
// While the input buffer is not empty, loop:
while (in < end) {
// Note: we may exit this loop with in == end, in which case we
// *shouldn't* dereference *in. But since we are pointing to a
// detached, non-empty QString, we know there's a u'\0' at the end.
}
// otherwise, if the input buffer begins with a prefix of "../" or "./",
// then remove that prefix from the input buffer;
if (path->size() >= 2 && in[0].unicode() == '.' && in[1].unicode() == '/')
in += 2;
else if (path->size() >= 3 && in[0].unicode() == '.'
&& in[1].unicode() == '.' && in[2].unicode() == '/')
in += 3;
// Is this path segment either "." or ".."?
enum { Nothing, Dot, DotDot } type = Nothing;
if (in[0] == u'.') {
if (in + 1 == end || in[1] == u'/')
type = Dot;
else if (in[1] == u'.' && (in + 2 == end || in[2] == u'/'))
type = DotDot;
}
if (type != Nothing) {
// If it is either, we skip it and remove any preceding slashes (if
// any) from the output. If it is "..", we remove the segment
// before that and its preceding slashes (if any) too.
const QChar *start = path->constBegin();
if (type == DotDot) {
while (out > start && *--out != u'/')
;
while (out > start && *--out == u'/')
;
++in; // the first dot
}
in += 2; // one dot and either one slash or the terminating null
while (out > start && *--out != u'/')
;
// And then replace the segment with "/", unless it would make a
// relative path become absolute.
if (out != start) {
// Replacing with a slash won't make the path absolute.
*out++ = u'/';
} else if (*start == u'/') {
// The path is already absolute.
++out;
} else {
// The path is relative, so we must skip any follow-on slashes
// to make sure the next iteration of the loop won't copy them,
// which would make the path become absolute.
while (in < end && *in == u'/')
++in;
}
// otherwise, if the input buffer begins with a prefix of
// "/./" or "/.", where "." is a complete path segment,
// then replace that prefix with "/" in the input buffer;
if (in <= end - 3 && in[0].unicode() == '/' && in[1].unicode() == '.'
&& in[2].unicode() == '/') {
in += 2;
continue;
} else if (in == end - 2 && in[0].unicode() == '/' && in[1].unicode() == '.') {
*out++ = u'/';
in += 2;
break;
}
// If it is neither, then we copy this segment.
// otherwise, if the input buffer begins with a prefix
// of "/../" or "/..", where ".." is a complete path
// segment, then replace that prefix with "/" in the
// input buffer and remove the last //segment and its
// preceding "/" (if any) from the output buffer;
if (in <= end - 4 && in[0].unicode() == '/' && in[1].unicode() == '.'
&& in[2].unicode() == '.' && in[3].unicode() == '/') {
while (out > path->constData() && (--out)->unicode() != '/')
;
if (out == path->constData() && out->unicode() != '/')
++in;
in += 3;
continue;
} else if (in == end - 3 && in[0].unicode() == '/' && in[1].unicode() == '.'
&& in[2].unicode() == '.') {
while (out > path->constData() && (--out)->unicode() != '/')
;
if (out->unicode() == '/')
++out;
in += 3;
break;
}
// otherwise move the first path segment in
// the input buffer to the end of the output
// buffer, including the initial "/" character
// (if any) and any subsequent characters up
// to, but not including, the next "/"
// character or the end of the input buffer.
*out++ = *in++;
while (in < end && in->unicode() != '/')
*out++ = *in++;
} while (in < end);
path->truncate(out - path->constBegin());
}
// Authority-less URLs cannot have paths starting with double slashes (see
// QUrlPrivate::validityError). We refuse to turn a valid URL into invalid by
// way of QUrl::resolved().
static void fixupNonAuthorityPath(QString *path)
{
if (path->isEmpty() || path->at(0) != u'/')
return;
// Find the first non-slash character, because its position is equal to the
// number of slashes. We'll remove all but one of them.
qsizetype i = 0;
while (i + 1 < path->size() && path->at(i + 1) == u'/')
++i;
if (i)
path->remove(0, i);
}
path->truncate(out - path->constData());
}
inline QUrlPrivate::ErrorCode QUrlPrivate::validityError(QString *source, qsizetype *position) const
@ -2818,8 +2778,6 @@ QUrl QUrl::resolved(const QUrl &relative) const
t.d->sectionIsPresent &= ~QUrlPrivate::Fragment;
removeDotsFromPath(&t.d->path);
if (!t.d->hasAuthority())
fixupNonAuthorityPath(&t.d->path);
#if defined(QURL_DEBUG)
qDebug("QUrl(\"%ls\").resolved(\"%ls\") = \"%ls\"",

View File

@ -1385,22 +1385,17 @@ void tst_QDir::normalizePathSegments_data()
QTest::newRow("data6") << "/./" << HandleUnc << "/";
QTest::newRow("data7") << "/.." << HandleUnc << "/..";
QTest::newRow("data8") << "/../" << HandleUnc << "/../";
QTest::newRow("/../.") << "/../." << HandleUnc << "/../";
QTest::newRow("/.././") << "/.././" << HandleUnc << "/../";
QTest::newRow("/../..") << "/../.." << HandleUnc << "/../..";
QTest::newRow("data9") << "." << HandleUnc << ".";
QTest::newRow("data10") << "./" << HandleUnc << ".";
QTest::newRow("data10") << "./" << HandleUnc << "./";
QTest::newRow("data11") << "./." << HandleUnc << ".";
QTest::newRow("data12") << "././" << HandleUnc << ".";
QTest::newRow("data12") << "././" << HandleUnc << "./";
QTest::newRow("data13") << ".." << HandleUnc << "..";
QTest::newRow("data14") << "../" << HandleUnc << "../";
QTest::newRow("data15") << "../." << HandleUnc << "../";
QTest::newRow("data15") << "../." << HandleUnc << "..";
QTest::newRow("data16") << ".././" << HandleUnc << "../";
QTest::newRow("data17") << "../.." << HandleUnc << "../..";
QTest::newRow("data18") << "../../" << HandleUnc << "../../";
QTest::newRow("./file1.txt") << "./file1.txt" << HandleUnc << "file1.txt";
QTest::newRow("data19") << ".//file1.txt" << HandleUnc << "file1.txt";
QTest::newRow("/foo/bar//file1.txt") << "/foo/bar//file1.txt" << HandleUnc << "/foo/bar/file1.txt";
QTest::newRow("data20") << "/foo/bar/..//file1.txt" << HandleUnc << "/foo/file1.txt";
QTest::newRow("data21") << "foo/.." << HandleUnc << ".";
QTest::newRow("data22") << "./foo/.." << HandleUnc << ".";
@ -1425,7 +1420,7 @@ void tst_QDir::normalizePathSegments_data()
#else
QTest::newRow("data37") << "c:/." << HandleUnc << "c:";
QTest::newRow("data38") << "c:/.." << HandleUnc << ".";
QTest::newRow("data39") << "c:/../" << HandleUnc << ".";
QTest::newRow("data39") << "c:/../" << HandleUnc << "./";
#endif
QTest::newRow("data40") << "c:/./" << HandleUnc << "c:/";
QTest::newRow("data41") << "foo/../foo/.." << HandleUnc << ".";
@ -1452,9 +1447,10 @@ void tst_QDir::normalizePathSegments()
QFETCH(QString, path);
QFETCH(UncHandling, uncHandling);
QFETCH(QString, expected);
// for QDirPrivate::RemotePath, see tst_QUrl::resolving
QString cleaned = qt_normalizePathSegments(path, uncHandling == HandleUnc ? QDirPrivate::AllowUncPaths : QDirPrivate::DefaultNormalization);
QCOMPARE(cleaned, expected);
if (path == expected)
QVERIFY2(path.isSharedWith(cleaned), "Strings are same but data is not shared");
}
# endif //QT_BUILD_INTERNAL

View File

@ -839,33 +839,7 @@ void tst_QUrl::resolving_data()
QTest::addColumn<QString>("relativeUrl");
QTest::addColumn<QString>("resolvedUrl");
// boundary cases
QTest::newRow("empty-on-empty") << "http://a" << "" << "http://a";
QTest::newRow("empty-on-/") << "http://a/" << "" << "http://a/";
QTest::newRow("empty-on-//") << "http://a//" << "" << "http://a//";
QTest::newRow("empty-on-/.") << "http://a/." << "" << "http://a/";
QTest::newRow("empty-on-/./") << "http://a/./" << "" << "http://a/";
QTest::newRow("empty-on-/..") << "http://a/.." << "" << "http://a/";
QTest::newRow("empty-on-/../") << "http://a/../" << "" << "http://a/";
QTest::newRow("/-on-empty-with-authority") << "http://a" << "/" << "http://a/";
QTest::newRow(".-on-empty-with-authority") << "http://a" << "." << "http://a/";
QTest::newRow("./-on-empty-with-authority") << "http://a" << "./" << "http://a/";
QTest::newRow(".//-on-empty-with-authority") << "http://a" << ".//" << "http://a//";
QTest::newRow("..-on-empty-with-authority") << "http://a" << ".." << "http://a/";
QTest::newRow("../-on-empty-with-authority") << "http://a" << "../" << "http://a/";
QTest::newRow("/-on-empty-no-authority") << "scheme:" << "/" << "scheme:/";
QTest::newRow(".-on-empty-no-authority") << "scheme:" << "." << "scheme:";
QTest::newRow("./-on-empty-no-authority") << "scheme:" << "./" << "scheme:";
QTest::newRow(".//-on-empty-no-authority") << "scheme:" << "./" << "scheme:";
QTest::newRow("..-on-empty-no-authority") << "scheme:" << ".." << "scheme:";
QTest::newRow("../-on-empty-no-authority") << "scheme:" << "../" << "scheme:";
QTest::newRow("scheme-change") << "http://a" << "https://b" << "https://b";
QTest::newRow("scheme-change-path") << "http://a/" << "scheme:" << "scheme:";
// 5.4.1 Normal Examples (http://www.ietf.org/rfc/rfc3986.txt)
// URL paths not ending in /
QTest::newRow("g:h") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("g:h") << QString::fromLatin1("g:h");
QTest::newRow("g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("g") << QString::fromLatin1("http://a/b/c/g");
QTest::newRow("./g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("./g") << QString::fromLatin1("http://a/b/c/g");
@ -883,62 +857,12 @@ void tst_QUrl::resolving_data()
QTest::newRow("[empty]") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("") << QString::fromLatin1("http://a/b/c/d;p?q");
QTest::newRow(".") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1(".") << QString::fromLatin1("http://a/b/c/");
QTest::newRow("./") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("./") << QString::fromLatin1("http://a/b/c/");
QTest::newRow(".//") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1(".//") << QString::fromLatin1("http://a/b/c//");
QTest::newRow("..") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("..") << QString::fromLatin1("http://a/b/");
QTest::newRow("../") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../") << QString::fromLatin1("http://a/b/");
QTest::newRow("..//") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("..//") << QString::fromLatin1("http://a/b//");
QTest::newRow("../g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../g") << QString::fromLatin1("http://a/b/g");
QTest::newRow("..//g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("..//g") << QString::fromLatin1("http://a/b//g");
QTest::newRow("../..") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../..") << QString::fromLatin1("http://a/");
QTest::newRow("../../") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../") << QString::fromLatin1("http://a/");
QTest::newRow("../..//") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../..//") << QString::fromLatin1("http://a//");
QTest::newRow("../../g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../g") << QString::fromLatin1("http://a/g");
QTest::newRow("../..//g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../..//g") << QString::fromLatin1("http://a//g");
// URL paths ending in /
QTest::newRow("g:h-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g:h") << QString::fromLatin1("g:h");
QTest::newRow("g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g") << QString::fromLatin1("http://a/b/c/g");
QTest::newRow("./g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("./g") << QString::fromLatin1("http://a/b/c/g");
QTest::newRow("g/-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g/") << QString::fromLatin1("http://a/b/c/g/");
QTest::newRow("/g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("/g") << QString::fromLatin1("http://a/g");
QTest::newRow("//g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("//g") << QString::fromLatin1("http://g");
QTest::newRow("?y-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("?y") << QString::fromLatin1("http://a/b/c/;p?y");
QTest::newRow("g?y-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g?y") << QString::fromLatin1("http://a/b/c/g?y");
QTest::newRow("#s-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("#s") << QString::fromLatin1("http://a/b/c/;p?q#s");
QTest::newRow("g#s-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g#s") << QString::fromLatin1("http://a/b/c/g#s");
QTest::newRow("g?y#s-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g?y#s") << QString::fromLatin1("http://a/b/c/g?y#s");
QTest::newRow(";x-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1(";x") << QString::fromLatin1("http://a/b/c/;x");
QTest::newRow("g;x-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g;x") << QString::fromLatin1("http://a/b/c/g;x");
QTest::newRow("g;x?y#s-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("g;x?y#s") << QString::fromLatin1("http://a/b/c/g;x?y#s");
QTest::newRow("[empty]-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("") << QString::fromLatin1("http://a/b/c/;p?q");
QTest::newRow(".-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1(".") << QString::fromLatin1("http://a/b/c/");
QTest::newRow("./-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("./") << QString::fromLatin1("http://a/b/c/");
QTest::newRow(".//-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1(".//") << QString::fromLatin1("http://a/b/c//");
QTest::newRow("..-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("..") << QString::fromLatin1("http://a/b/");
QTest::newRow("../-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../") << QString::fromLatin1("http://a/b/");
QTest::newRow("..//-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("..//") << QString::fromLatin1("http://a/b//");
QTest::newRow("../g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../g") << QString::fromLatin1("http://a/b/g");
QTest::newRow("..//g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("..//g") << QString::fromLatin1("http://a/b//g");
QTest::newRow("../..-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../..") << QString::fromLatin1("http://a/");
QTest::newRow("../../-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../../") << QString::fromLatin1("http://a/");
QTest::newRow("../..//-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../..//") << QString::fromLatin1("http://a//");
QTest::newRow("../../g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../../g") << QString::fromLatin1("http://a/g");
QTest::newRow("../..//g-on-/") << QString::fromLatin1("http://a/b/c/;p?q") << QString::fromLatin1("../..//g") << QString::fromLatin1("http://a//g");
// URL paths ending in //
QTest::newRow(".-on-//") << "http://a/b/c//" << "." << "http://a/b/c//";
QTest::newRow("./-on-//") << "http://a/b/c//" << "./" << "http://a/b/c//";
QTest::newRow(".//-on-//") << "http://a/b/c//" << ".//" << "http://a/b/c///"; // weird but correct
QTest::newRow("..-on-//") << "http://a/b/c//" << ".." << "http://a/b/";
QTest::newRow("../-on-//") << "http://a/b/c//" << "../" << "http://a/b/";
QTest::newRow("..//-on-//") << "http://a/b/c//" << "..//" << "http://a/b//";
QTest::newRow("../g-on-//") << "http://a/b/c//" << "../g" << "http://a/b/g";
QTest::newRow("..//g-on-//") << "http://a/b/c//" << "..//g" << "http://a/b//g";
QTest::newRow("../..-on-//") << "http://a/b/c//" << "../.." << "http://a/";
QTest::newRow("../../-on-//") << "http://a/b/c//" << "../../" << "http://a/";
QTest::newRow("../..//-on-//") << "http://a/b/c//" << "../..//" << "http://a//";
QTest::newRow("../../g-on-//") << "http://a/b/c//" << "../../g" << "http://a/g";
QTest::newRow("../..//g-on-//") << "http://a/b/c//" << "../..//g" << "http://a//g";
// 5.4.2 Abnormal Examples (http://www.ietf.org/rfc/rfc3986.txt)
@ -946,15 +870,8 @@ void tst_QUrl::resolving_data()
// relative path ".." segments than there are hierarchical levels in the
// base URI's path. Note that the ".." syntax cannot be used to change
// the authority component of a URI.
QTest::newRow("../../../") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../") << QString::fromLatin1("http://a/");
QTest::newRow("../../../..") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../..") << QString::fromLatin1("http://a/");
QTest::newRow("../../../..//") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../..//") << QString::fromLatin1("http://a//");
QTest::newRow("../../../../..") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../../..") << QString::fromLatin1("http://a/");
QTest::newRow("../../../../../") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../../../") << QString::fromLatin1("http://a/");
QTest::newRow("../../../g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../g") << QString::fromLatin1("http://a/g");
QTest::newRow("../../..//g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../..//g") << QString::fromLatin1("http://a//g");
QTest::newRow("../../../../g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../../g") << QString::fromLatin1("http://a/g");
QTest::newRow("../../../..//g") << QString::fromLatin1("http://a/b/c/d;p?q") << QString::fromLatin1("../../../..//g") << QString::fromLatin1("http://a//g");
// Similarly, parsers must remove the dot-segments "." and ".." when
// they are complete components of a path, but not when they are only
@ -1000,49 +917,11 @@ void tst_QUrl::resolving_data()
QTest::newRow("../a (2)") << QString::fromLatin1("b/a") << QString::fromLatin1("../a") << QString::fromLatin1("a");
QTest::newRow("../a (3)") << QString::fromLatin1("b/c/a") << QString::fromLatin1("../a") << QString::fromLatin1("b/a");
QTest::newRow("../a (4)") << QString::fromLatin1("b") << QString::fromLatin1("/a") << QString::fromLatin1("/a");
QTest::newRow("relative+.") << "scheme:" << "." << "scheme:";
QTest::newRow("relative+./") << "scheme:" << "./" << "scheme:";
QTest::newRow("relative+.//") << "scheme:" << ".//" << "scheme:";
QTest::newRow("relative+.///") << "scheme:" << ".///" << "scheme:";
QTest::newRow("relative+./.") << "scheme:" << "./." << "scheme:";
QTest::newRow("relative+././") << "scheme:" << "././" << "scheme:";
QTest::newRow("relative+..") << "scheme:" << ".." << "scheme:";
QTest::newRow("relative+../") << "scheme:" << "../" << "scheme:";
QTest::newRow("relative+..//") << "scheme:" << "..//" << "scheme:";
QTest::newRow("relative+..///") << "scheme:" << "..///" << "scheme:";
QTest::newRow("relative+../.") << "scheme:" << "../." << "scheme:";
QTest::newRow("relative+.././") << "scheme:" << ".././" << "scheme:";
QTest::newRow("relative+.././/") << "scheme:" << ".././/" << "scheme:";
QTest::newRow("relative+.././//") << "scheme:" << ".././//" << "scheme:";
QTest::newRow("relative+../../../..") << "scheme:b/c/d" << "../../../.." << "scheme:";
QTest::newRow("relative+../../../../") << "scheme:b/c/d" << "../../../../" << "scheme:";
QTest::newRow("relative+../../../..//") << "scheme:b/c/d" << "../../../..//" << "scheme:";
QTest::newRow("relative+../../d/../..") << "scheme:b/c/d" << "../../d/../.." << "scheme:";
QTest::newRow("relative+../../d/../../") << "scheme:b/c/d" << "../../d/../../" << "scheme:";
QTest::newRow("relative+endslash+../../../..") << "scheme:b/c/d/" << "../../../.." << "scheme:";
QTest::newRow("relative+endslash+../../../../") << "scheme:b/c/d/" << "../../../../" << "scheme:";
QTest::newRow("relative+endslash+../../../..//") << "scheme:b/c/d/" << "../../../..//" << "scheme:";
// Resolve absolute without authority with relative
QTest::newRow("../a (5)") << QString::fromLatin1("/b") << QString::fromLatin1("../a") << QString::fromLatin1("/a");
QTest::newRow("../a (6)") << QString::fromLatin1("/b/a") << QString::fromLatin1("../a") << QString::fromLatin1("/a");
QTest::newRow("../a (7)") << QString::fromLatin1("/b/c/a") << QString::fromLatin1("../a") << QString::fromLatin1("/b/a");
QTest::newRow("../a (8)") << QString::fromLatin1("/b") << QString::fromLatin1("/a") << QString::fromLatin1("/a");
QTest::newRow("noauthority+.") << "scheme:/a/b" << "." << "scheme:/a/";
QTest::newRow("noauthority+./") << "scheme:/a/b" << "./" << "scheme:/a/";
QTest::newRow("noauthority+.//") << "scheme:/a/b" << ".//" << "scheme:/a//";
QTest::newRow("noauthority+./d") << "scheme:/a/b" << "./d" << "scheme:/a/d";
QTest::newRow("noauthority+.//d") << "scheme:/a/b" << ".//d" << "scheme:/a//d";
QTest::newRow("noauthority+..") << "scheme:/a/b" << ".." << "scheme:/";
QTest::newRow("noauthority+../") << "scheme:/a/b" << "../" << "scheme:/";
QTest::newRow("noauthority+..//") << "scheme:/a/b" << "..//" << "scheme:/";
QTest::newRow("noauthority+../d") << "scheme:/a/b" << "../d" << "scheme:/d";
QTest::newRow("noauthority+..//d") << "scheme:/a/b" << "..//d" << "scheme:/d"; // no double slash!
QTest::newRow("noauthority+../..") << "scheme:/a/b" << "../.." << "scheme:/";
QTest::newRow("noauthority+../../") << "scheme:/a/b" << "../../" << "scheme:/";
QTest::newRow("noauthority+../..//") << "scheme:/a/b" << "../..//" << "scheme:/";
QTest::newRow("noauthority+../../d") << "scheme:/a/b" << "../../d" << "scheme:/d";
QTest::newRow("noauthority+../..//d") << "scheme:/a/b" << "../..//d" << "scheme:/d"; // no double slash!
// More tests from KDE
QTest::newRow("brackets") << QString::fromLatin1("http://www.calorieking.com/personal/diary/") << QString::fromLatin1("/personal/diary/rpc.php?C=jsrs1&F=getDiaryDay&P0=[2006-3-8]&U=1141858921458") << QString::fromLatin1("http://www.calorieking.com/personal/diary/rpc.php?C=jsrs1&F=getDiaryDay&P0=[2006-3-8]&U=1141858921458");