From 8a450f570b8dc40f61a68db0ca5eb69a7a97272c Mon Sep 17 00:00:00 2001 From: Robbert Proost Date: Thu, 18 Jan 2018 09:52:49 +0100 Subject: [PATCH] QUrl: Support IPv6 addresses with zone id Task-number: QTBUG-25550 Change-Id: I37ec02b655abe2779aa11945e20550ce00e43723 Reviewed-by: Thiago Macieira --- src/corelib/io/qurl.cpp | 67 +++++++++++++++---------- tests/auto/corelib/io/qurl/tst_qurl.cpp | 52 +++++++++++++++++++ 2 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 4587b9fcd6..25881b9c62 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -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; } diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 20282068cb..7615ad4586 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -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("url"); + QTest::addColumn("decodedHost"); + QTest::addColumn("prettyHost"); + QTest::addColumn("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"