summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2012-09-20 17:22:01 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2012-10-02 22:34:42 +0200
commitf89fd779fc4431f667d937ea502fd930e6425079 (patch)
tree2668e07f1471c9c03b6e9f4348767003ed1cffaf
parent4f52a9509995b1f5c834354ff6d02fa9b4f4396d (diff)
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 <faure@kde.org>
-rw-r--r--src/corelib/io/qurl.cpp268
-rw-r--r--tests/auto/corelib/io/qurl/tst_qurl.cpp27
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 &copy);
+ ~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 &copy)
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<const ushort *>(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<user_name>@ok-hostname" << "Invalid user name";
- QTest::newRow("invalid-user2") << "http://bad%@ok-hostname" << "Invalid user name";
+ QTest::newRow("invalid-user1") << "http://bad<user_name>@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<hostname>" << "Invalid hostname";
- QTest::newRow("invalid-regname-2") << "http://b%61d" << "Invalid hostname";
+ QTest::newRow("invalid-regname") << "http://bad<hostname>" << "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());