Re-introduce support for QUrl::errorString()
authorThiago Macieira <thiago.macieira@intel.com>
Thu, 20 Oct 2011 14:23:41 +0000 (16:23 +0200)
committerQt by Nokia <qt-info@nokia.com>
Thu, 29 Mar 2012 23:19:59 +0000 (01:19 +0200)
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 <lars.knoll@nokia.com>
src/corelib/io/qurl.cpp
src/corelib/io/qurl_p.h
tests/auto/corelib/io/qurl/tst_qurl.cpp

index 62d6092..806edb8 100644 (file)
@@ -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 &copy)
       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<const ushort *>(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("<unknown error>");
 }
 
 /*!
index 6a0af1c..d9207bd 100644 (file)
 
 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 &copy);
 
@@ -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();
 };
 
 
index 680cc3b..259e757 100644 (file)
@@ -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<username>@bad_hostname/\": "
                           "error at position 14: expected end of URL, but found '<'";
-    QEXPECT_FAIL("", "errorString not implemented yet", Abort);
     QCOMPARE(u.errorString(), errorString);
 }