diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 62d6092c9d..806edb8a13 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -234,6 +234,7 @@ static inline QString fileScheme() QUrlPrivate::QUrlPrivate() : ref(1), port(-1), + errorCode(NoError), errorSupplement(0), sectionIsPresent(0), sectionHasError(0) { } @@ -247,6 +248,8 @@ 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) { @@ -263,6 +266,8 @@ void QUrlPrivate::clear() query.clear(); fragment.clear(); + errorCode = NoError; + errorSupplement = 0; sectionIsPresent = 0; sectionHasError = 0; } @@ -520,10 +525,12 @@ bool QUrlPrivate::setScheme(const QString &value, int len, bool decoded) scheme.clear(); sectionIsPresent |= Scheme; sectionHasError |= Scheme; // assume it has errors, we'll clear before returning true + errorCode = SchemeEmptyError; if (len == 0) return false; // validate it: + errorCode = InvalidSchemeError; int needsLowercasing = -1; const ushort *p = reinterpret_cast(value.constData()); for (int i = 0; i < len; ++i) { @@ -541,6 +548,7 @@ bool QUrlPrivate::setScheme(const QString &value, int len, bool decoded) if (p[i] == '%') { // found a percent-encoded sign // if we haven't decoded yet, decode and try again + errorSupplement = '%'; if (decoded) return false; @@ -551,11 +559,13 @@ bool QUrlPrivate::setScheme(const QString &value, int len, bool decoded) } // found something else + errorSupplement = p[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 @@ -588,11 +598,6 @@ bool QUrlPrivate::setAuthority(const QString &auth, int from, int end) from = userInfoIndex + 1; } - if (userInfoIndex == end - 1) { - // authority without a hostname is invalid - return false; - } - int colonIndex = auth.lastIndexOf(QLatin1Char(':'), end - 1); if (colonIndex < from) colonIndex = -1; @@ -609,6 +614,7 @@ bool QUrlPrivate::setAuthority(const QString &auth, int from, int end) if (colonIndex == end - 1) { // found a colon but no digits after it sectionHasError |= Port; + errorCode = PortEmptyError; } else if (uint(colonIndex) < uint(end)) { unsigned long x = 0; for (int i = colonIndex + 1; i < end; ++i) { @@ -618,6 +624,7 @@ bool QUrlPrivate::setAuthority(const QString &auth, int from, int end) x += c - '0'; } else { sectionHasError |= Port; + errorCode = InvalidPortError; x = ulong(-1); // x != ushort(x) break; } @@ -757,7 +764,7 @@ inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions o } // the whole IPvFuture is passed and parsed here, including brackets -static bool parseIpFuture(QString &host, const QChar *begin, const QChar *end) +static int parseIpFuture(QString &host, const QChar *begin, const QChar *end) { // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) static const char acceptable[] = @@ -767,7 +774,7 @@ static bool parseIpFuture(QString &host, const QChar *begin, const QChar *end) // the brackets and the "v" have been checked if (begin[3].unicode() != '.') - return false; + return begin[3].unicode(); 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')) { @@ -793,12 +800,12 @@ static bool parseIpFuture(QString &host, const QChar *begin, const QChar *end) else if (begin->unicode() < 0x80 && strchr(acceptable, begin->unicode()) != 0) host += *begin; else - return false; + return begin->unicode(); } host += QLatin1Char(']'); - return true; + return -1; } - return false; + return begin[2].unicode(); } // ONLY the IPv6 address is parsed here, WITHOUT the brackets @@ -834,30 +841,36 @@ bool QUrlPrivate::setHost(const QString &value, int from, int iend, bool maybePe const int len = end - begin; host.clear(); sectionIsPresent |= Host; - if (len == 0) { - sectionHasError &= ~Host; + sectionHasError &= ~Host; + if (len == 0) return true; - } - - // we'll clear just before returning true - sectionHasError |= Host; if (begin[0].unicode() == '[') { // IPv6Address or IPvFuture // smallest IPv6 address is "[::]" (len = 4) // smallest IPvFuture address is "[v7.X]" (len = 6) - if (end[-1].unicode() != ']') + if (end[-1].unicode() != ']') { + sectionHasError |= Host; + errorCode = HostMissingEndBracket; return false; + } - bool ok; - if (len > 5 && begin[1].unicode() == 'v') - ok = parseIpFuture(host, begin, end); - else - ok = parseIp6(host, begin + 1, end - 1); + 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; + } - if (ok) - sectionHasError &= ~Host; - return ok; + if (parseIp6(host, begin + 1, end - 1)) + return true; + + sectionHasError |= Host; + errorCode = InvalidIPv6AddressError; + return false; } // check if it's an IPv4 address @@ -887,16 +900,22 @@ bool QUrlPrivate::setHost(const QString &value, int from, int iend, bool maybePe if (maybePercentEncoded && qt_urlRecode(s, begin, end, QUrl::MostDecoded, 0)) { // something was decoded // anything encoded left? - if (s.contains(QChar(0x25))) // '%' + if (s.contains(QChar(0x25))) { // '%' + sectionHasError |= Host; + errorCode = InvalidRegNameError; return false; + } // recurse return setHost(s, 0, s.length(), false); } s = qt_ACE_do(QString::fromRawData(begin, len), NormalizeAce); - if (s.isEmpty()) + if (s.isEmpty()) { + sectionHasError |= Host; + errorCode = InvalidRegNameError; return false; + } // check IPv4 again if (QIPAddressUtils::parseIp4(ip4, s.constBegin(), s.constEnd())) { @@ -904,7 +923,6 @@ bool QUrlPrivate::setHost(const QString &value, int from, int iend, bool maybePe } else { host = s; } - sectionHasError &= ~Host; return true; } @@ -1217,45 +1235,6 @@ const QByteArray &QUrlPrivate::normalized() const return encodedNormalized; } - -QString QUrlPrivate::createErrorString() -{ - if (isValid && isHostValid) - return QString(); - - QString errorString(QLatin1String(QT_TRANSLATE_NOOP(QUrl, "Invalid URL \""))); - errorString += QLatin1String(encodedOriginal.constData()); - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\"")); - - if (errorInfo._source) { - int position = encodedOriginal.indexOf(errorInfo._source) - 1; - if (position > 0) { - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": error at position ")); - errorString += QString::number(position); - } else { - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": ")); - errorString += QLatin1String(errorInfo._source); - } - } - - if (errorInfo._expected) { - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": expected \'")); - errorString += QLatin1Char(errorInfo._expected); - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\'")); - } else { - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ": ")); - if (isHostValid) - errorString += QLatin1String(errorInfo._message); - else - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "invalid hostname")); - } - if (errorInfo._found) { - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, ", but found \'")); - errorString += QLatin1Char(errorInfo._found); - errorString += QLatin1String(QT_TRANSLATE_NOOP(QUrl, "\'")); - } - return errorString; -} #endif /*! @@ -1591,9 +1570,17 @@ void QUrl::setHost(const QString &host) if (d->setHost(host, 0, host.length())) { if (host.isNull()) d->sectionIsPresent &= ~QUrlPrivate::Host; - } else { + } else if (!host.startsWith(QLatin1Char('['))) { // setHost failed, it might be IPv6 or IPvFuture in need of bracketing - d->setHost(QLatin1Char('[') + host + QLatin1Char(']'), 0, host.length() + 2); + ushort oldCode = d->errorCode; + ushort oldSupplement = d->errorSupplement; + if (!d->setHost(QLatin1Char('[') + host + QLatin1Char(']'), 0, host.length() + 2)) { + // failed again: choose if this was an IPv6 error or not + if (!host.contains(QLatin1Char(':'))) { + d->errorCode = oldCode; + d->errorSupplement = oldSupplement; + } + } } } @@ -1627,6 +1614,7 @@ void QUrl::setPort(int port) qWarning("QUrl::setPort: Out of range"); port = -1; d->sectionHasError |= QUrlPrivate::Port; + d->errorCode = QUrlPrivate::InvalidPortError; } else { d->sectionHasError &= ~QUrlPrivate::Port; } @@ -2413,7 +2401,47 @@ QDebug operator<<(QDebug d, const QUrl &url) */ QString QUrl::errorString() const { - return QString(); + if (!d) + return QString(); + + if (d->sectionHasError == 0) + return QString(); + + // check if the error code matches a section with error + if ((d->sectionHasError & (d->errorCode >> 8)) == 0) + return QString(); + + QChar c = d->errorSupplement; + switch (QUrlPrivate::ErrorCode(d->errorCode)) { + case QUrlPrivate::NoError: + return QString(); + + case QUrlPrivate::InvalidSchemeError: { + QString msg = QStringLiteral("Invalid scheme (character '%1' not permitted)"); + return msg.arg(c); + } + case QUrlPrivate::SchemeEmptyError: + return QStringLiteral("Empty scheme"); + + case QUrlPrivate::InvalidRegNameError: + return QStringLiteral("Hostname contains invalid characters"); + case QUrlPrivate::InvalidIPv4AddressError: + return QString(); // doesn't happen yet + case QUrlPrivate::InvalidIPv6AddressError: + return QStringLiteral("Invalid IPv6 address"); + case QUrlPrivate::InvalidIPvFutureError: + return QStringLiteral("Invalid IPvFuture address"); + case QUrlPrivate::HostMissingEndBracket: + return QStringLiteral("Expected '[' to match ']' in hostname"); + + case QUrlPrivate::InvalidPortError: + case QUrlPrivate::PortEmptyError: + return QStringLiteral("Invalid port or port number out of range"); + + case QUrlPrivate::PathContainsColonBeforeSlash: + return QStringLiteral("Path component contains ':' before any '/'"); + } + return QStringLiteral(""); } /*! diff --git a/src/corelib/io/qurl_p.h b/src/corelib/io/qurl_p.h index 6a0af1c90a..d9207bd809 100644 --- a/src/corelib/io/qurl_p.h +++ b/src/corelib/io/qurl_p.h @@ -57,24 +57,6 @@ QT_BEGIN_NAMESPACE -struct QUrlErrorInfo { - inline QUrlErrorInfo() : _source(0), _message(0), _expected(0), _found(0) - { } - - const char *_source; - const char *_message; - char _expected; - char _found; - - inline void setParams(const char *source, const char *message, char expected, char found) - { - _source = source; - _message = message; - _expected = expected; - _found = found; - } -}; - class QUrlPrivate { public: @@ -92,6 +74,24 @@ public: Fragment = 0x80 }; + enum ErrorCode { + InvalidSchemeError = 0x000, + SchemeEmptyError, + + InvalidRegNameError = 0x800, + InvalidIPv4AddressError, + InvalidIPv6AddressError, + InvalidIPvFutureError, + HostMissingEndBracket, + + InvalidPortError = 0x1000, + PortEmptyError, + + PathContainsColonBeforeSlash = 0x2000, + + NoError = 0xffff + }; + QUrlPrivate(); QUrlPrivate(const QUrlPrivate ©); @@ -143,6 +143,9 @@ public: QString query; QString fragment; + ushort errorCode; + ushort errorSupplement; + // not used for: // - Port (port == -1 means absence) // - Path (there's no path delimiter, so we optimize its use out of existence) @@ -152,9 +155,6 @@ public: // UserName, Password, Path, Query, and Fragment never contain errors in TolerantMode. // Those flags are set only by the strict parser. uchar sectionHasError; - - mutable QUrlErrorInfo errorInfo; - QString createErrorString(); }; diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 680cc3b137..259e7570fa 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -1615,8 +1615,7 @@ void tst_QUrl::isValid() QVERIFY(url.isValid()); url.setAuthority("strange;hostname"); QVERIFY(!url.isValid()); - QEXPECT_FAIL("", "QUrl::errorString not reimplemented", Continue); - QVERIFY(url.errorString().contains("invalid hostname")); + QVERIFY(url.errorString().contains("Hostname contains invalid characters")); } { @@ -1629,8 +1628,8 @@ void tst_QUrl::isValid() QVERIFY(url.isValid()); url.setHost("stuff;1"); QVERIFY(!url.isValid()); - QEXPECT_FAIL("", "QUrl::errorString not reimplemented", Continue); - QVERIFY(url.errorString().contains("invalid hostname")); + QVERIFY2(url.errorString().contains("Hostname contains invalid characters"), + qPrintable(url.errorString())); } } @@ -2164,6 +2163,7 @@ void tst_QUrl::setPort() QTest::ignoreMessage(QtWarningMsg, "QUrl::setPort: Out of range"); url.setPort(65536); QCOMPARE(url.port(), -1); + QVERIFY(url.errorString().contains("out of range")); } } @@ -2219,7 +2219,6 @@ void tst_QUrl::errorString() QVERIFY(!u.isValid()); QString errorString = "Invalid URL \"http://strange@bad_hostname/\": " "error at position 14: expected end of URL, but found '<'"; - QEXPECT_FAIL("", "errorString not implemented yet", Abort); QCOMPARE(u.errorString(), errorString); }