From f89fd779fc4431f667d937ea502fd930e6425079 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 20 Sep 2012 17:22:01 +0200 Subject: Overhaul a little the QUrl error reporting. Keep the original QString that triggered the parsing error, instead of just one QChar. This provides more powerful error messages, like: Invalid IPv6 address; source was "http://[:::]"; scheme = "http", host = "" (QUrl cannot keep invalid hostnames) Invalid port or port number out of range; source was "http://example.com:abc"; scheme = "http", host = "example.com" (QUrl cannot keep a non-numeric port number) Invalid path (character '%' not permitted); source was "foo:/path%?"; scheme = "foo", path = "/path%25%1F" (the tolerant parser runs first, so the faulty component is fixed) This stores the error state in a special structure which is not allocated under normal conditions, keeping the memory consumption down. On 32-bit systems, QUrlPrivate does not increase in size; on 64-bit systems, it grows by 8 bytes. Change-Id: I93d798d43401dfeb9fca7b6eed7ea758da10136b Reviewed-by: David Faure --- src/corelib/io/qurl.cpp | 268 +++++++++++++++++++++----------- tests/auto/corelib/io/qurl/tst_qurl.cpp | 27 ++-- 2 files changed, 189 insertions(+), 106 deletions(-) diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 92f613d4c3..be87e1f34f 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -108,6 +108,39 @@ folding rules in QUrl conform to \l{RFC 3491} (Nameprep: A Stringprep Profile for Internationalized Domain Names (IDN)). + \section2 Error checking + + QUrl is capable of detecting many errors in URLs while parsing it or when + components of the URL are set with individual setter methods (like + setScheme(), setHost() or setPath()). If the parsing or setter function is + succesful, any previously recorded error conditions will be discarded. + + By default, QUrl setter methods operate in QUrl::TolerantMode, which means + they accept some common mistakes and mis-representation of data. An + alternate method of parsing is QUrl::StrictMode, which applies further + checks. See QUrl::ParsingMode for a description of the difference of the + parsing modes. + + QUrl only checks for conformance with the URL specification. It does not + try to verify that high-level protocol URLs are in the format they are + expected to be by handlers elsewhere. For example, the following URIs are + all considered valid by QUrl, even if they do not make sense when used: + + \list + \li "http:/filename.html" + \li "mailto://example.com" + \endlist + + When the parser encounters an error, it signals the event by making + isValid() return false and toString() / toEncoded() return an empty string. + If it is necessary to show the user the reason why the URL failed to parse, + the error condition can be obtained from QUrl by calling errorString(). + Note that this message is highly technical and may not make sense to + end-users. + + QUrl is capable of recording only one error condition. If more than one + error is found, it is undefined which error is reported. + \section2 Character Conversions Follow these rules to avoid erroneous character conversion when @@ -161,7 +194,7 @@ \endlist When in StrictMode, if a parsing error is found, isValid() will return \c - false and errorString() will return a simple message describing the error. + false and errorString() will return a message describing the error. If more than one error is detected, it is undefined which error gets reported. @@ -358,13 +391,24 @@ public: NoError = 0 }; + struct Error { + QString source; + ErrorCode code; + int position; + }; + QUrlPrivate(); QUrlPrivate(const QUrlPrivate ©); + ~QUrlPrivate(); void parse(const QString &url, QUrl::ParsingMode parsingMode); bool isEmpty() const { return sectionIsPresent == 0 && port == -1 && path.isEmpty(); } - ErrorCode validityError() const; + + Error *cloneError() const; + void clearError(); + void setError(ErrorCode errorCode, const QString &source, int supplement = -1); + ErrorCode validityError(QString *source = 0, int *position = 0) const; // no QString scheme() const; void appendAuthority(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const; @@ -377,7 +421,7 @@ public: void appendFragment(QString &appendTo, QUrl::FormattingOptions options) const; // the "end" parameters are like STL iterators: they point to one past the last valid element - bool setScheme(const QString &value, int len); + bool setScheme(const QString &value, int len, bool doSetError); void setAuthority(const QString &auth, int from, int end, QUrl::ParsingMode mode); void setUserInfo(const QString &userInfo, int from, int end); void setUserName(const QString &value, int from, int end); @@ -411,24 +455,19 @@ public: QString query; QString fragment; - ushort errorCode; - ushort errorSupplement; + Error *error; // not used for: // - Port (port == -1 means absence) // - Path (there's no path delimiter, so we optimize its use out of existence) // Schemes are never supposed to be empty, but we keep the flag anyway uchar sectionIsPresent; - - // UserName, Password, Path, Query, and Fragment never contain errors in TolerantMode. - // Those flags are set only by the strict parser. - uchar sectionHasError; }; inline QUrlPrivate::QUrlPrivate() : ref(1), port(-1), - errorCode(NoError), errorSupplement(0), - sectionIsPresent(0), sectionHasError(0) + error(0), + sectionIsPresent(0) { } @@ -441,11 +480,37 @@ inline QUrlPrivate::QUrlPrivate(const QUrlPrivate ©) path(copy.path), query(copy.query), fragment(copy.fragment), - errorCode(copy.errorCode), - errorSupplement(copy.errorSupplement), - sectionIsPresent(copy.sectionIsPresent), - sectionHasError(copy.sectionHasError) + error(copy.cloneError()), + sectionIsPresent(copy.sectionIsPresent) +{ +} + +inline QUrlPrivate::~QUrlPrivate() +{ + delete error; +} + +inline QUrlPrivate::Error *QUrlPrivate::cloneError() const +{ + return error ? new Error(*error) : 0; +} + +inline void QUrlPrivate::clearError() +{ + delete error; + error = 0; +} + +inline void QUrlPrivate::setError(ErrorCode errorCode, const QString &source, int supplement) { + if (error) { + // don't overwrite an error set in a previous section during parsing + return; + } + error = new Error; + error->code = errorCode; + error->source = source; + error->position = supplement; } // From RFC 3896, Appendix A Collected ABNF for URI @@ -789,7 +854,6 @@ inline bool QUrlPrivate::setScheme(const QString &value, int len, bool doSetErro sectionIsPresent |= Scheme; // validate it: - errorCode = InvalidSchemeError; int needsLowercasing = -1; const ushort *p = reinterpret_cast(value.constData()); for (int i = 0; i < len; ++i) { @@ -805,13 +869,14 @@ inline bool QUrlPrivate::setScheme(const QString &value, int len, bool doSetErro continue; // found something else - errorSupplement = p[i]; + // don't call setError needlessly: + // if we've been called from parse(), it will try to recover + if (doSetError) + setError(InvalidSchemeError, value, i); return false; } scheme = value.left(len); - sectionHasError &= ~Scheme; - errorCode = NoError; if (needsLowercasing != -1) { // schemes are ASCII only, so we don't need the full Unicode toLower @@ -827,7 +892,6 @@ inline bool QUrlPrivate::setScheme(const QString &value, int len, bool doSetErro inline void QUrlPrivate::setAuthority(const QString &auth, int from, int end, QUrl::ParsingMode mode) { - sectionHasError &= ~Authority; sectionIsPresent &= ~Authority; sectionIsPresent |= Host; if (from == end) { @@ -859,8 +923,7 @@ inline void QUrlPrivate::setAuthority(const QString &auth, int from, int end, QU if (colonIndex == end - 1) { // found a colon but no digits after it - sectionHasError |= Port; - errorCode = PortEmptyError; + setError(PortEmptyError, auth, colonIndex + 1); } else if (uint(colonIndex) < uint(end)) { unsigned long x = 0; for (int i = colonIndex + 1; i < end; ++i) { @@ -869,8 +932,6 @@ inline void QUrlPrivate::setAuthority(const QString &auth, int from, int end, QU x *= 10; x += c - '0'; } else { - sectionHasError |= Port; - errorCode = InvalidPortError; x = ulong(-1); // x != ushort(x) break; } @@ -878,8 +939,7 @@ inline void QUrlPrivate::setAuthority(const QString &auth, int from, int end, QU if (x == ushort(x)) { port = ushort(x); } else { - sectionHasError |= Port; - errorCode = InvalidPortError; + setError(InvalidPortError, auth, colonIndex + 1); } } else { port = -1; @@ -896,7 +956,6 @@ inline void QUrlPrivate::setUserInfo(const QString &userInfo, int from, int end) if (uint(delimIndex) >= uint(end)) { password.clear(); sectionIsPresent &= ~Password; - sectionHasError &= ~Password; } else { setPassword(userInfo, delimIndex + 1, end); } @@ -905,35 +964,30 @@ inline void QUrlPrivate::setUserInfo(const QString &userInfo, int from, int end) inline void QUrlPrivate::setUserName(const QString &value, int from, int end) { sectionIsPresent |= UserName; - sectionHasError &= ~UserName; userName = recodeFromUser(value, decodedUserNameInIsolationActions, from, end); } inline void QUrlPrivate::setPassword(const QString &value, int from, int end) { sectionIsPresent |= Password; - sectionHasError &= ~Password; password = recodeFromUser(value, decodedPasswordInIsolationActions, from, end); } inline void QUrlPrivate::setPath(const QString &value, int from, int end) { // sectionIsPresent |= Path; // not used, save some cycles - sectionHasError &= ~Path; path = recodeFromUser(value, decodedPathInIsolationActions, from, end); } inline void QUrlPrivate::setFragment(const QString &value, int from, int end) { sectionIsPresent |= Fragment; - sectionHasError &= ~Fragment; fragment = recodeFromUser(value, decodedFragmentInIsolationActions, from, end); } inline void QUrlPrivate::setQuery(const QString &value, int from, int iend) { sectionIsPresent |= Query; - sectionHasError &= ~Query; // use the default actions for the query (don't set QUrl::DecodeAllDelimiters) QString output; @@ -997,8 +1051,9 @@ inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions o } } -// the whole IPvFuture is passed and parsed here, including brackets -static int parseIpFuture(QString &host, const QChar *begin, const QChar *end) +// the whole IPvFuture is passed and parsed here, including brackets; +// returns null if the parsing was successful, or the QChar of the first failure +static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar *end) { // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) static const char acceptable[] = @@ -1008,7 +1063,7 @@ static int parseIpFuture(QString &host, const QChar *begin, const QChar *end) // the brackets and the "v" have been checked if (begin[3].unicode() != '.') - return begin[3].unicode(); + return &begin[3]; if ((begin[2].unicode() >= 'A' && begin[2].unicode() >= 'F') || (begin[2].unicode() >= 'a' && begin[2].unicode() <= 'f') || (begin[2].unicode() >= '0' && begin[2].unicode() <= '9')) { @@ -1034,12 +1089,12 @@ static int parseIpFuture(QString &host, const QChar *begin, const QChar *end) else if (begin->unicode() < 0x80 && strchr(acceptable, begin->unicode()) != 0) host += *begin; else - return begin->unicode(); + return begin; } host += QLatin1Char(']'); - return -1; + return 0; } - return begin[2].unicode(); + return &begin[2]; } // ONLY the IPv6 address is parsed here, WITHOUT the brackets @@ -1075,7 +1130,6 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: const int len = end - begin; host.clear(); sectionIsPresent |= Host; - sectionHasError &= ~Host; if (len == 0) return true; @@ -1084,27 +1138,22 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: // smallest IPv6 address is "[::]" (len = 4) // smallest IPvFuture address is "[v7.X]" (len = 6) if (end[-1].unicode() != ']') { - sectionHasError |= Host; - errorCode = HostMissingEndBracket; + setError(HostMissingEndBracket, value); return false; } if (len > 5 && begin[1].unicode() == 'v') { - int c = parseIpFuture(host, begin, end); - if (c != -1) { - sectionHasError |= Host; - errorCode = InvalidIPvFutureError; - errorSupplement = short(c); - } - return c == -1; + const QChar *c = parseIpFuture(host, begin, end); + if (c) + setError(InvalidIPvFutureError, value, c - value.constData()); + return !c; } if (parseIp6(host, begin + 1, end - 1)) return true; - sectionHasError |= Host; - errorCode = begin[1].unicode() == 'v' ? - InvalidIPvFutureError : InvalidIPv6AddressError; + setError(begin[1].unicode() == 'v' ? InvalidIPvFutureError : InvalidIPv6AddressError, + value, from); return false; } @@ -1113,7 +1162,6 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: if (QIPAddressUtils::parseIp4(ip4, begin, end)) { // yes, it was QIPAddressUtils::toString(host, ip4); - sectionHasError &= ~Host; return true; } @@ -1135,9 +1183,9 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: if (mode == QUrl::TolerantMode && qt_urlRecode(s, begin, end, QUrl::DecodeReserved, 0)) { // something was decoded // anything encoded left? - if (s.contains(QChar(0x25))) { // '%' - sectionHasError |= Host; - errorCode = InvalidRegNameError; + int pos = s.indexOf(QChar(0x25)); // '%' + if (pos != -1) { + setError(InvalidRegNameError, s, pos); return false; } @@ -1147,8 +1195,7 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: s = qt_ACE_do(QString::fromRawData(begin, len), NormalizeAce); if (s.isEmpty()) { - sectionHasError |= Host; - errorCode = InvalidRegNameError; + setError(InvalidRegNameError, value); return false; } @@ -1172,7 +1219,7 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode // / other path types here sectionIsPresent = 0; - sectionHasError = 0; + clearError(); // find the important delimiters int colon = -1; @@ -1201,12 +1248,11 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode // check if we have a scheme int hierStart; - if (colon != -1 && setScheme(url, colon)) { + if (colon != -1 && setScheme(url, colon, /* don't set error */ false)) { hierStart = colon + 1; } else { // recover from a failed scheme: it might not have been a scheme at all scheme.clear(); - sectionHasError = 0; sectionIsPresent = 0; hierStart = 0; } @@ -1247,7 +1293,7 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode if (hash != -1) setFragment(url, hash + 1, len); - if (sectionHasError || parsingMode == QUrl::TolerantMode) + if (error || parsingMode == QUrl::TolerantMode) return; // The parsing so far was tolerant of errors, so the StrictMode @@ -1279,19 +1325,16 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode if ((uc == '%' && (uint(len) < i + 2 || !isHex(data[i + 1]) || !isHex(data[i + 2]))) || uc <= 0x20 || strchr(forbidden, uc)) { // found an error - errorSupplement = uc; + ErrorCode errorCode; // where are we? if (i > uint(hash)) { errorCode = InvalidFragmentError; - sectionHasError |= Fragment; } else if (i > uint(question)) { errorCode = InvalidQueryError; - sectionHasError |= Query; } else if (i > uint(pathStart)) { // pathStart is never -1 errorCode = InvalidPathError; - sectionHasError |= Path; } else { // It must be in the authority, since the scheme is strict. // Since the port and hostname parsers are also strict, @@ -1299,12 +1342,13 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode int pos = url.indexOf(QLatin1Char(':'), hierStart); if (i > uint(pos)) { errorCode = InvalidPasswordError; - sectionHasError |= Password; } else { errorCode = InvalidUserNameError; - sectionHasError |= UserName; } } + + setError(errorCode, url, i); + return; } } } @@ -1421,10 +1465,16 @@ static void removeDotsFromPath(QString *path) path->truncate(out - path->constData()); } -QUrlPrivate::ErrorCode QUrlPrivate::validityError() const +inline QUrlPrivate::ErrorCode QUrlPrivate::validityError(QString *source, int *position) const { - if (sectionHasError) - return ErrorCode(errorCode); + Q_ASSERT(!source == !position); + if (error) { + if (source) { + *source = error->source; + *position = error->position; + } + return error->code; + } // There are two more cases of invalid URLs that QUrl recognizes and they // are only possible with constructed URLs (setXXX methods), not with @@ -1439,8 +1489,13 @@ QUrlPrivate::ErrorCode QUrlPrivate::validityError() const if (path.isEmpty() || path.at(0) == QLatin1Char('/')) return NoError; - if (sectionIsPresent & QUrlPrivate::Host) + if (sectionIsPresent & QUrlPrivate::Host) { + if (source) { + *source = path; + *position = 0; + } return AuthorityPresentAndPathIsRelative; + } if (sectionIsPresent & QUrlPrivate::Scheme) return NoError; @@ -1453,6 +1508,10 @@ QUrlPrivate::ErrorCode QUrlPrivate::validityError() const } if (c == ':') { // found the colon before the slash, it's invalid + if (source) { + *source = path; + *position = i; + } return RelativeUrlPathContainsColonBeforeSlash; } } @@ -1762,13 +1821,13 @@ void QUrl::setUrl(const QString &url, ParsingMode parsingMode) void QUrl::setScheme(const QString &scheme) { detach(); + d->clearError(); if (scheme.isEmpty()) { // schemes are not allowed to be empty d->sectionIsPresent &= ~QUrlPrivate::Scheme; - d->sectionHasError &= ~QUrlPrivate::Scheme; d->scheme.clear(); } else { - d->setScheme(scheme, scheme.length()); + d->setScheme(scheme, scheme.length(), /* do set error */ true); } } @@ -1819,6 +1878,7 @@ QString QUrl::scheme() const void QUrl::setAuthority(const QString &authority, ParsingMode mode) { detach(); + d->clearError(); QString data = authority; if (mode == DecodedMode) { parseDecodedComponent(data); @@ -1881,6 +1941,7 @@ QString QUrl::authority(ComponentFormattingOptions options) const void QUrl::setUserInfo(const QString &userInfo, ParsingMode mode) { detach(); + d->clearError(); QString trimmed = userInfo.trimmed(); if (mode == DecodedMode) { parseDecodedComponent(trimmed); @@ -1940,6 +2001,7 @@ QString QUrl::userInfo(ComponentFormattingOptions options) const void QUrl::setUserName(const QString &userName, ParsingMode mode) { detach(); + d->clearError(); QString data = userName; if (mode == DecodedMode) { @@ -2031,6 +2093,7 @@ QString QUrl::userName(ComponentFormattingOptions options) const void QUrl::setPassword(const QString &password, ParsingMode mode) { detach(); + d->clearError(); QString data = password; if (mode == DecodedMode) { @@ -2120,6 +2183,7 @@ QString QUrl::password(ComponentFormattingOptions options) const void QUrl::setHost(const QString &host, ParsingMode mode) { detach(); + d->clearError(); QString data = host; if (mode == DecodedMode) { @@ -2132,16 +2196,19 @@ void QUrl::setHost(const QString &host, ParsingMode mode) d->sectionIsPresent &= ~QUrlPrivate::Host; } else if (!data.startsWith(QLatin1Char('['))) { // setHost failed, it might be IPv6 or IPvFuture in need of bracketing - ushort oldCode = d->errorCode; - ushort oldSupplement = d->errorSupplement; + Q_ASSERT(d->error); + data.prepend(QLatin1Char('[')); data.append(QLatin1Char(']')); if (!d->setHost(data, 0, data.length(), mode)) { - // failed again: choose if this was an IPv6 error or not - if (!data.contains(QLatin1Char(':'))) { - d->errorCode = oldCode; - d->errorSupplement = oldSupplement; + // failed again + if (data.contains(QLatin1Char(':'))) { + // source data contains ':', so it's an IPv6 error + d->error->code = QUrlPrivate::InvalidIPv6AddressError; } + } else { + // succeeded + d->clearError(); } } } @@ -2219,13 +2286,11 @@ QString QUrl::host(ComponentFormattingOptions options) const void QUrl::setPort(int port) { detach(); + d->clearError(); if (port < -1 || port > 65535) { port = -1; - d->sectionHasError |= QUrlPrivate::Port; - d->errorCode = QUrlPrivate::InvalidPortError; - } else { - d->sectionHasError &= ~QUrlPrivate::Port; + d->setError(QUrlPrivate::InvalidPortError, QString::number(port), 0); } d->port = port; @@ -2275,6 +2340,7 @@ int QUrl::port(int defaultPort) const void QUrl::setPath(const QString &path, ParsingMode mode) { detach(); + d->clearError(); QString data = path; if (mode == DecodedMode) { @@ -2396,6 +2462,7 @@ bool QUrl::hasQuery() const void QUrl::setQuery(const QString &query, ParsingMode mode) { detach(); + d->clearError(); QString data = query; if (mode == DecodedMode) { @@ -2445,6 +2512,7 @@ void QUrl::setQuery(const QString &query, ParsingMode mode) void QUrl::setQuery(const QUrlQuery &query) { detach(); + d->clearError(); // we know the data is in the right format d->query = query.toString(); @@ -2755,6 +2823,7 @@ QString QUrl::query(ComponentFormattingOptions options) const void QUrl::setFragment(const QString &fragment, ParsingMode mode) { detach(); + d->clearError(); QString data = fragment; if (mode == DecodedMode) { @@ -3518,8 +3587,11 @@ QDebug operator<<(QDebug d, const QUrl &url) } #endif -static QString errorMessage(QUrlPrivate::ErrorCode errorCode, QChar c) +static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, int errorPosition) { + QChar c = uint(errorPosition) < uint(errorSource.length()) ? + errorSource.at(errorPosition) : QChar(QChar::Null); + switch (errorCode) { case QUrlPrivate::NoError: Q_ASSERT_X(false, "QUrl::errorString", @@ -3541,7 +3613,7 @@ static QString errorMessage(QUrlPrivate::ErrorCode errorCode, QChar c) .arg(c); case QUrlPrivate::InvalidRegNameError: - if (!c.isNull()) + if (errorPosition != -1) return QString(QStringLiteral("Invalid hostname (character '%1' not permitted)")) .arg(c); else @@ -3597,25 +3669,31 @@ static inline void appendComponentIfPresent(QString &msg, bool present, const ch /*! \since 4.2 - Returns a text string that explains why an URL is invalid in the case being; - otherwise returns an empty string. + Returns an error message if the last operation that modified this QUrl + object ran into a parsing error. If no error was detected, this function + returns an empty string and isValid() returns true. + + The error message returned by this function is technical in nature and may + not be understood by end users. It is mostly useful to developers trying to + understand why QUrl will not accept some input. + + \sa QUrl::ParsingMode */ QString QUrl::errorString() const { if (!d) return QString(); - QUrlPrivate::ErrorCode errorCode = d->validityError(); + QString errorSource; + int errorPosition; + QUrlPrivate::ErrorCode errorCode = d->validityError(&errorSource, &errorPosition); if (errorCode == QUrlPrivate::NoError) return QString(); - // check if the error code matches a section with error - // unless it's a compound error (0x10000) - if ((d->sectionHasError & (errorCode >> 8)) == 0 && (errorCode & 0x10000) == 0) - return QString(); - - QString msg = errorMessage(errorCode, d->errorSupplement); - msg += QLatin1Char(';'); + QString msg = errorMessage(errorCode, errorSource, errorPosition); + msg += QLatin1String("; source was \""); + msg += errorSource; + msg += QLatin1String("\";"); appendComponentIfPresent(msg, d->sectionIsPresent & QUrlPrivate::Scheme, " scheme = ", d->scheme); appendComponentIfPresent(msg, d->sectionIsPresent & QUrlPrivate::UserInfo, diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 25ff3155bc..361713bbc7 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -1894,32 +1894,32 @@ void tst_QUrl::strictParser_data() QTest::newRow("invalid-scheme") << "ht%://example.com" << "character '%' not permitted"; QTest::newRow("empty-scheme") << ":/" << "':' before any '/'"; - QTest::newRow("invalid-user1") << "http://bad@ok-hostname" << "Invalid user name"; - QTest::newRow("invalid-user2") << "http://bad%@ok-hostname" << "Invalid user name"; + QTest::newRow("invalid-user1") << "http://bad@ok-hostname" << "Invalid user name (character '<' not permitted)"; + QTest::newRow("invalid-user2") << "http://bad%@ok-hostname" << "Invalid user name (character '%' not permitted)"; - QTest::newRow("invalid-password") << "http://user:pass\x7F@ok-hostname" << "Invalid password"; + QTest::newRow("invalid-password") << "http://user:pass\x7F@ok-hostname" << "Invalid password (character '\x7F' not permitted)"; - QTest::newRow("invalid-regname") << "http://bad" << "Invalid hostname"; - QTest::newRow("invalid-regname-2") << "http://b%61d" << "Invalid hostname"; + QTest::newRow("invalid-regname") << "http://bad" << "Invalid hostname (contains invalid characters)"; + QTest::newRow("invalid-regname-2") << "http://b%61d" << "Invalid hostname (contains invalid characters)"; QTest::newRow("invalid-ipv6") << "http://[:::]" << "Invalid IPv6 address"; QTest::newRow("invalid-ipvfuture-1") << "http://[v7]" << "Invalid IPvFuture address"; QTest::newRow("invalid-ipvfuture-2") << "http://[v7.]" << "Invalid IPvFuture address"; QTest::newRow("invalid-ipvfuture-3") << "http://[v789]" << "Invalid IPvFuture address"; - QTest::newRow("unbalanced-brackets") << "http://[ff02::1" << "Expected ']'"; + QTest::newRow("unbalanced-brackets") << "http://[ff02::1" << "Expected ']' to match '[' in hostname"; // port errors happen in TolerantMode too - QTest::newRow("empty-port-1") << "http://example.com:" << "empty"; - QTest::newRow("empty-port-2") << "http://example.com:/" << "empty"; + QTest::newRow("empty-port-1") << "http://example.com:" << "Port field was empty"; + QTest::newRow("empty-port-2") << "http://example.com:/" << "Port field was empty"; QTest::newRow("invalid-port-1") << "http://example.com:-1" << "Invalid port"; QTest::newRow("invalid-port-2") << "http://example.com:abc" << "Invalid port"; QTest::newRow("invalid-port-3") << "http://example.com:9a" << "Invalid port"; QTest::newRow("port-range") << "http://example.com:65536" << "out of range"; - QTest::newRow("invalid-path") << "foo:/path%\x1F" << "Invalid path"; + QTest::newRow("invalid-path") << "foo:/path%\x1F" << "Invalid path (character '%' not permitted)"; - QTest::newRow("invalid-query") << "foo:?\\#" << "Invalid query"; + QTest::newRow("invalid-query") << "foo:?\\#" << "Invalid query (character '\\' not permitted)"; - QTest::newRow("invalid-fragment") << "#{}" << "Invalid fragment"; + QTest::newRow("invalid-fragment") << "#{}" << "Invalid fragment (character '{' not permitted)"; } void tst_QUrl::strictParser() @@ -1931,6 +1931,11 @@ void tst_QUrl::strictParser() QVERIFY(!url.isValid()); QVERIFY(url.toString().isEmpty()); QVERIFY(!url.errorString().isEmpty()); + QVERIFY2(url.errorString().contains(input), + "Error string does not contain the original input (\"" + + input.toLatin1() + "\"): " + url.errorString().toLatin1()); + + // Note: if the following fails due to changes in the parser, simply update the test data QVERIFY2(url.errorString().contains(needle), "Error string changed and does not contain \"" + needle.toLatin1() + "\" anymore: " + url.errorString().toLatin1()); -- cgit v1.2.3