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>
This commit is contained in:
parent
4f52a95099
commit
f89fd779fc
@ -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,13 +480,39 @@ 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
|
||||
// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||
//[...]
|
||||
@ -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,
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user