From cff38329aa8635ac8a0696b0aa78782fe5605d16 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 20 Oct 2011 16:23:41 +0200 Subject: Re-introduce support for QUrl::errorString() Note that QUrl can only remember one error. If the URL contains more than one error condition, only the latest (in whichever parsing order URL decides to use) will be reported. I don't want too keep too much data in QUrlPrivate for validation, so let's use 4 bytes only. Change-Id: I2afbf80734d3633f41f779984ab76b3a5ba293a2 Reviewed-by: Lars Knoll --- src/corelib/io/qurl.cpp | 168 ++++++++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 70 deletions(-) (limited to 'src/corelib/io/qurl.cpp') 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(""); } /*! -- cgit v1.2.3