QUrl::resolved: rewrite to fix some corner cases for relative URLs

Both issues reported in QTBUG-120396 came from the same dubious piece of
code, which predates the public Qt history

        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;

It makes no sense to check path->size() inside the loop, as the in
pointer will have advanced past the beginning and the remaining size of
the input will not be path->size().

It additionally had theoretical UB in expressions like
  in <= end - 4
for paths that were less than 4 characters long (it cannot happen with
current QString because of the QArrayData header before the payload).

So this commit rewrites the function to fix those issues and some others
found during the unit-testing. It gives the function a major
simplification.

Fixes: QTBUG-120396
Pick-to: 6.7 6.5
Change-Id: I46feca3a447244a8ba19fffd17e012c27e410056
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
(cherry picked from commit 4b1547adc9b195e6acc90471fc48dec7ee0c429d)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2024-07-07 16:41:24 -07:00 committed by Qt Cherry-pick Bot
parent 65144dba05
commit a1610c6c68
2 changed files with 199 additions and 54 deletions

View File

@ -1529,6 +1529,12 @@ 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)
{
@ -1539,71 +1545,87 @@ static void removeDotsFromPath(QString *path)
const QChar *in = out;
const QChar *end = out + path->size();
// 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:
// We implement a modified algorithm compared to RFC 3986, for efficiency.
while (in < end) {
#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++;
// 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;
// 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;
// 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 "/..", 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() != '/')
// 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'/')
;
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() == '/')
// 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;
in += 3;
break;
} 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;
}
continue;
}
// 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++;
// If it is neither, then we copy this segment.
while (in < end && in->unicode() != '/')
*out++ = *in++;
}
path->truncate(out - path->constData());
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);
}
inline QUrlPrivate::ErrorCode QUrlPrivate::validityError(QString *source, qsizetype *position) const
@ -2778,6 +2800,8 @@ 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

@ -839,7 +839,33 @@ 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");
@ -857,12 +883,62 @@ 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)
@ -870,8 +946,15 @@ 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
@ -917,11 +1000,49 @@ 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");