From e8122a0a9c6090c8ebe26d5d12320da3267a87c0 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 18 Sep 2024 09:48:24 -0700 Subject: [PATCH] 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 Reviewed-by: Ulf Hermann --- src/corelib/io/qdir.cpp | 279 ++++++++++++------------ src/corelib/io/qdir_p.h | 1 - src/corelib/io/qurl.cpp | 160 +++++--------- tests/auto/corelib/io/qdir/tst_qdir.cpp | 16 +- tests/auto/corelib/io/qurl/tst_qurl.cpp | 123 +---------- 5 files changed, 203 insertions(+), 376 deletions(-) diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp index 46aee5c895a..3e1c778e98b 100644 --- a/src/corelib/io/qdir.cpp +++ b/src/corelib/io/qdir.cpp @@ -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); - - // 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(); - - // 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'/'; + const qsizetype len = name.size(); + + if (ok) + *ok = false; + + if (len == 0) + return name; + + qsizetype i = len - 1; + QVarLengthArray outVector(len); + qsizetype used = len; + char16_t *out = outVector.data(); + const char16_t *p = reinterpret_cast(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; - - // 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 + 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] == '/'; + }; - // 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]; } + + --i; + continue; } - // 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; + // remove current directory + if (p[i] == '.' && (i == 0 || p[i-1] == '/')) { + --i; + continue; } - if (type == Nothing) { - // If it is neither, then we copy this segment. - while (in < end && in[0] != u'/') - *out++ = *in++; + + // 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; + } + } 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; + // 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; } - start = out; - in += 2; - continue; + + // in case of /./, jump over + if (isDot(p, i)) + i -= 2; + + break; } + + --i; + break; } - 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 - } - 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; - } - 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; + // actual copy + if (!up) + out[--used] = p[i]; + --i; } - } while (in < end); - - path->truncate(out - path->constBegin()); - if (!isRemote && path->isEmpty()) - *path = u"."_s; - // we return false only if the path was absolute - return ok || prefixLength == 0; -} + // decrement up after copying/skipping + if (up) + --up; + } -QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok) -{ - // temporary compat - QString copy = name; - bool r = qt_normalizePathSegments(©, 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) diff --git a/src/corelib/io/qdir_p.h b/src/corelib/io/qdir_p.h index 908c1e8ed5b..7dce69c1952 100644 --- a/src/corelib/io/qdir_p.h +++ b/src/corelib/io/qdir_p.h @@ -81,7 +81,6 @@ class QDirPrivate : public QSharedData 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 diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 407ae5ffce9..1599d25d345 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -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++; - - // 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. - } - // 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 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) { + + // 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; } - 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'/') + // 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() != '/') ; - - // 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; - } + 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; } - // If it is neither, then we copy this segment. + // 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\"", diff --git a/tests/auto/corelib/io/qdir/tst_qdir.cpp b/tests/auto/corelib/io/qdir/tst_qdir.cpp index 8dd5ebca3cd..7b05a88e811 100644 --- a/tests/auto/corelib/io/qdir/tst_qdir.cpp +++ b/tests/auto/corelib/io/qdir/tst_qdir.cpp @@ -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 diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index c4a5c077c99..fc191559557 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -839,33 +839,7 @@ void tst_QUrl::resolving_data() QTest::addColumn("relativeUrl"); QTest::addColumn("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");