QUrl: Support IPv6 addresses with zone id

Task-number: QTBUG-25550
Change-Id: I37ec02b655abe2779aa11945e20550ce00e43723
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Robbert Proost 2018-01-18 09:52:49 +01:00
parent 3f80783b11
commit 8a450f570b
2 changed files with 93 additions and 26 deletions

View File

@ -1203,16 +1203,18 @@ inline void QUrlPrivate::setQuery(const QString &value, int from, int iend)
inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions options) const
{
// EncodeUnicode is the only flag that matters
if ((options & QUrl::FullyDecoded) == QUrl::FullyDecoded)
options = 0;
else
options &= QUrl::EncodeUnicode;
if (host.isEmpty())
return;
if (host.at(0).unicode() == '[') {
// IPv6Address and IPvFuture address never require any transformation
appendTo += host;
// IPv6 addresses might contain a zone-id which needs to be recoded
QString hostInCorrectFormat;
if (options != 0)
qt_urlRecode(hostInCorrectFormat, host.constBegin(), host.constEnd(), options, 0);
if (hostInCorrectFormat.isEmpty())
hostInCorrectFormat = host;
appendTo += hostInCorrectFormat;
} else {
// this is either an IPv4Address or a reg-name
// if it is a reg-name, it is already stored in Unicode form
@ -1278,31 +1280,44 @@ static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar
// ONLY the IPv6 address is parsed here, WITHOUT the brackets
static const QChar *parseIp6(QString &host, const QChar *begin, const QChar *end, QUrl::ParsingMode mode)
{
QIPAddressUtils::IPv6Address address;
const QChar *ret = QIPAddressUtils::parseIp6(address, begin, end);
if (ret) {
// ### Update to use QStringView once QStringView::indexOf and QStringView::lastIndexOf exists
QString decoded;
if (mode == QUrl::TolerantMode) {
// this struct is kept in automatic storage because it's only 4 bytes
const ushort decodeColon[] = { decode(':'), 0 };
// IPv6 failed parsing, check if it was a percent-encoded character in
// the middle and try again
QString decoded;
if (mode == QUrl::TolerantMode && qt_urlRecode(decoded, begin, end, 0, decodeColon)) {
// recurse
// if the parsing fails again, the qt_urlRecode above will return 0
ret = parseIp6(host, decoded.constBegin(), decoded.constEnd(), mode);
// we can't return ret, otherwise it would be dangling
return ret ? end : 0;
}
// no transformation, nothing to re-parse
return ret;
if (qt_urlRecode(decoded, begin, end, QUrl::ComponentFormattingOption::PrettyDecoded, decodeColon) == 0)
decoded = QString(begin, end-begin);
} else {
decoded = QString(begin, end-begin);
}
host.reserve(host.size() + (end - begin));
const QLatin1String zoneIdIdentifier("%25");
QIPAddressUtils::IPv6Address address;
QString zoneId;
const QChar *endBeforeZoneId = decoded.constEnd();
int zoneIdPosition = decoded.indexOf(zoneIdIdentifier);
if ((zoneIdPosition != -1) && (decoded.lastIndexOf(zoneIdIdentifier) == zoneIdPosition)) {
zoneId = decoded.mid(zoneIdPosition + zoneIdIdentifier.size());
endBeforeZoneId = decoded.constBegin() + zoneIdPosition;
if (zoneId.isEmpty())
return end;
}
const QChar *ret = QIPAddressUtils::parseIp6(address, decoded.constBegin(), endBeforeZoneId);
if (ret)
return begin + (ret - decoded.constBegin());
host.reserve(host.size() + (decoded.constEnd() - decoded.constBegin()));
host += QLatin1Char('[');
QIPAddressUtils::toString(host, address);
if (!zoneId.isEmpty()) {
host += zoneIdIdentifier;
host += zoneId;
}
host += QLatin1Char(']');
return 0;
}

View File

@ -180,6 +180,8 @@ private slots:
void testThreading();
void matches_data();
void matches();
void ipv6_zoneId_data();
void ipv6_zoneId();
private:
void testThreadingHelper();
@ -1876,6 +1878,24 @@ void tst_QUrl::ipv6_data()
QTest::newRow("encoded-digit") << "//[::%31]" << true << "//[::1]";
QTest::newRow("encoded-colon") << "//[%3A%3A]" << true << "//[::]";
QTest::newRow("full ipv6 with zone id (decoded %)") << QString::fromLatin1("//[56:56:56:56:56:56:56:56%eth0]") << true
<< "//[56:56:56:56:56:56:56:56%25eth0]";
QTest::newRow("full ipv6 with zone id (encoded %)") << QString::fromLatin1("//[56:56:56:56:56:56:56:56%25eth0]") << true
<< "//[56:56:56:56:56:56:56:56%25eth0]";
QTest::newRow("full ipv6 with invalid zone id") << QString::fromLatin1("//[56:56:56:56:56:56:56:56%]") << false << "";
QTest::newRow("full ipv6 with invalid zone id (encoded)") << QString::fromLatin1("//[56:56:56:56:56:56:56:56%25]") << false << "";
QTest::newRow("full ipv6 with zone id 25 (encoded)") << QString::fromLatin1("//[56:56:56:56:56:56:56:56%2525]") << true << "//[56:56:56:56:56:56:56:56%2525]";
QTest::newRow("case 4 with less and ip4 and port and useinfo and zone id")
<< QString::fromLatin1("//user:pass@[56::56:56:56:127.0.0.1%ethernet_1]:99") << true
<< "//user:pass@[56::56:56:56:7f00:1%25ethernet_1]:99";
QTest::newRow("encoded-digit including zone id") << "//[::%31%25eth0]" << true << "//[::1%25eth0]";
}
void tst_QUrl::ipv6()
@ -4120,6 +4140,38 @@ void tst_QUrl::matches()
QCOMPARE(urlOne.matches(urlTwo, QUrl::FormattingOptions(options)), matches);
}
void tst_QUrl::ipv6_zoneId_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QString>("decodedHost");
QTest::addColumn<QString>("prettyHost");
QTest::addColumn<QString>("encodedHost");
QTest::newRow("digit") << QUrl("x://[::%251]") << "::%1" << "::%251" << "::%251";
QTest::newRow("eth0") << QUrl("x://[::%25eth0]") << "::%eth0" << "::%25eth0" << "::%25eth0";
QTest::newRow("space") << QUrl("x://[::%25%20]") << "::% " << "::%25 " << "::%25%20";
QTest::newRow("subdelims") << QUrl("x://[::%25eth%2B]") << "::%eth+" << "::%25eth%2B" << "::%25eth%2B";
QTest::newRow("other") << QUrl("x://[::%25^]") << "::%^" << "::%25%5E" << "::%25%5E";
QTest::newRow("control") << QUrl("x://[::%25%7F]") << "::%\x7f" << "::%25%7F" << "::%25%7F";
QTest::newRow("unicode") << QUrl("x://[::%25wlán0]") << "::%wlán0" << "::%25wlán0" << "::%25wl%C3%A1n0";
QTest::newRow("non-utf8") << QUrl("x://[::%25%80]") << QString("::%") + QChar(QChar::ReplacementCharacter) << "::%25%80" << "::%25%80";
}
void tst_QUrl::ipv6_zoneId()
{
QFETCH(QUrl, url);
QFETCH(QString, decodedHost);
QFETCH(QString, prettyHost);
QFETCH(QString, encodedHost);
QVERIFY2(url.isValid(), qPrintable(url.errorString()));
QCOMPARE(url.host(QUrl::FullyDecoded), decodedHost);
QCOMPARE(url.host(), decodedHost);
QCOMPARE(url.host(QUrl::FullyEncoded), encodedHost);
QCOMPARE(url.toString(), "x://[" + prettyHost + "]");
QCOMPARE(url.toString(QUrl::FullyEncoded), "x://[" + encodedHost + "]");
}
QTEST_MAIN(tst_QUrl)
#include "tst_qurl.moc"