diff options
Diffstat (limited to 'tests/auto/network')
16 files changed, 944 insertions, 8 deletions
diff --git a/tests/auto/network/access/CMakeLists.txt b/tests/auto/network/access/CMakeLists.txt index 3ae66e1308..13332f0268 100644 --- a/tests/auto/network/access/CMakeLists.txt +++ b/tests/auto/network/access/CMakeLists.txt @@ -13,6 +13,10 @@ add_subdirectory(qnetworkreply) add_subdirectory(qnetworkcachemetadata) add_subdirectory(qabstractnetworkcache) if(QT_FEATURE_http) + add_subdirectory(qnetworkreply_local) + if(NOT WASM) # QTBUG-121822 + add_subdirectory(qformdatabuilder) + endif() add_subdirectory(qnetworkrequestfactory) add_subdirectory(qrestaccessmanager) endif() diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index d9e82330b2..396a6f2fda 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -1277,7 +1277,7 @@ void tst_Http2::unsupportedAuthenticateChallenge() bool authenticationRequested = false; connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(), - [&](QNetworkReply *, QAuthenticator *auth) { + [&](QNetworkReply *, QAuthenticator *) { authenticationRequested = true; }); diff --git a/tests/auto/network/access/qformdatabuilder/CMakeLists.txt b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt new file mode 100644 index 0000000000..59d1b54e2a --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qformdatabuilder LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qformdatabuilder + NO_BATCH # QTBUG-121815 + DEFINES + QTEST_THROW_ON_FAIL + QTEST_THROW_ON_SKIP + SOURCES + tst_qformdatabuilder.cpp + LIBRARIES + Qt::Core + Qt::CorePrivate + Qt::Network + Qt::NetworkPrivate + TESTDATA + rfc3252.txt + image1.jpg + document.docx + sheet.xlsx +) + +if(QT_FEATURE_sanitize_undefined) + qt_internal_extend_target(tst_qformdatabuilder + DEFINES + QT_SANITIZE_UNDEFINED # GCC (in)famously doesn't provide a predefined macro for this + ) +endif() diff --git a/tests/auto/network/access/qformdatabuilder/document.docx b/tests/auto/network/access/qformdatabuilder/document.docx Binary files differnew file mode 100644 index 0000000000..49005f14b6 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/document.docx diff --git a/tests/auto/network/access/qformdatabuilder/image1.jpg b/tests/auto/network/access/qformdatabuilder/image1.jpg Binary files differnew file mode 100644 index 0000000000..d1d27bf7cf --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/image1.jpg diff --git a/tests/auto/network/access/qformdatabuilder/rfc3252.txt b/tests/auto/network/access/qformdatabuilder/rfc3252.txt new file mode 100644 index 0000000000..0521a80d12 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/rfc3252.txt @@ -0,0 +1 @@ +some text for reference
\ No newline at end of file diff --git a/tests/auto/network/access/qformdatabuilder/sheet.xlsx b/tests/auto/network/access/qformdatabuilder/sheet.xlsx Binary files differnew file mode 100644 index 0000000000..2cb1ec7361 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/sheet.xlsx diff --git a/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp new file mode 100644 index 0000000000..bc6c4b44f0 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp @@ -0,0 +1,456 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/private/qhttpmultipart_p.h> +#include <QtNetwork/qformdatabuilder.h> + +#include <QtCore/qbuffer.h> +#include <QtCore/qfile.h> + +#include <QtTest/qtest.h> + +#ifndef QTEST_THROW_ON_FAIL +# error This test requires QTEST_THROW_ON_FAIL being active. +#endif + +using namespace Qt::StringLiterals; + +class tst_QFormDataBuilder : public QObject +{ + Q_OBJECT + + void checkBodyPartsAreEquivalent(QByteArrayView expected, QByteArrayView actual); + +private Q_SLOTS: + void generateQHttpPartWithDevice_data(); + void generateQHttpPartWithDevice(); + + void escapesBackslashAndQuotesInFilenameAndName_data(); + void escapesBackslashAndQuotesInFilenameAndName(); + + void picksUtf8FilenameEncodingIfAsciiDontSuffice_data(); + void picksUtf8FilenameEncodingIfAsciiDontSuffice(); + + void setHeadersDoesNotAffectHeaderFieldsManagedByBuilder_data(); + void setHeadersDoesNotAffectHeaderFieldsManagedByBuilder(); + + void specifyMimeType_data(); + void specifyMimeType(); + + void picksUtf8NameEncodingIfAsciiDoesNotSuffice_data(); + void picksUtf8NameEncodingIfAsciiDoesNotSuffice(); + + void moveSemantics(); +}; + +void tst_QFormDataBuilder::checkBodyPartsAreEquivalent(QByteArrayView expected, QByteArrayView actual) +{ + qsizetype expectedCrlfPos = expected.indexOf("\r\n"); + qsizetype expectedBoundaryPos = expected.lastIndexOf("--boundary_.oOo."); + + qsizetype actualCrlfPos = actual.indexOf("\r\n"); + qsizetype actualBoundaryPos = actual.lastIndexOf("--boundary_.oOo."); + + qsizetype start = expectedCrlfPos + 2; + qsizetype end = expectedBoundaryPos - expectedCrlfPos - 2; + + QCOMPARE(actualCrlfPos, expectedCrlfPos); + QCOMPARE(actualBoundaryPos, expectedBoundaryPos); + QCOMPARE(actual.sliced(start, end), expected.sliced(start, end)); +} + +void tst_QFormDataBuilder::generateQHttpPartWithDevice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("real_file_name"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QString>("expected_content_type_data"); + QTest::addColumn<QString>("expected_content_disposition_data"); + QTest::addColumn<QString>("content_disposition_must_not_contain_data"); + + QTest::newRow("txt-ascii") << "text"_L1 << u"rfc3252.txt"_s << u"rfc3252.txt"_s << u"text/plain"_s + << uR"(form-data; name="text"; filename="rfc3252.txt")"_s + << u"filename*"_s; + QTest::newRow("txt-latin") << "text"_L1 << u"rfc3252.txt"_s << u"szöveg.txt"_s << u"text/plain"_s + << uR"(form-data; name="text"; filename="szöveg.txt"; filename*=UTF-8''sz%C3%B6veg.txt)"_s + << u""_s; + QTest::newRow("txt-unicode") << "text"_L1 << u"rfc3252.txt"_s << u"テキスト.txt"_s << u"text/plain"_s + << uR"(form-data; name="text"; filename="テキスト.txt"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt)"_s + << u""_s; + + QTest::newRow("jpg-ascii") << "image"_L1 << u"image1.jpg"_s << u"image1.jpg"_s << u"image/jpeg"_s + << uR"(form-data; name="image"; filename="image1.jpg")"_s + << u"filename*"_s; + QTest::newRow("jpg-latin") << "image"_L1 << u"image1.jpg"_s << u"kép.jpg"_s << u"image/jpeg"_s + << uR"(form-data; name="image"; filename="kép.jpg"; filename*=UTF-8''k%C3%A9p.jpg)"_s + << u""_s; + QTest::newRow("jpg-unicode") << "image"_L1 << u"image1.jpg"_s << u"絵.jpg"_s << u"image/jpeg"_s + << uR"(form-data; name="image"; filename="絵.jpg"; filename*=UTF-8''%E7%B5%B5.jpg)"_s + << u""_s; + + QTest::newRow("doc-ascii") << "text"_L1 << u"document.docx"_s << u"word.docx"_s + << u"application/vnd.openxmlformats-officedocument.wordprocessingml.document"_s + << uR"(form-data; name="text"; filename="word.docx")"_s + << u"filename*"_s; + QTest::newRow("doc-latin") << "text"_L1 << u"document.docx"_s << u"szöveg.docx"_s + << u"application/vnd.openxmlformats-officedocument.wordprocessingml.document"_s + << uR"(form-data; name="text"; filename="szöveg.docx"; filename*=UTF-8''sz%C3%B6veg.docx)"_s + << u""_s; + QTest::newRow("doc-unicode") << "text"_L1 << u"document.docx"_s << u"テキスト.docx"_s + << u"application/vnd.openxmlformats-officedocument.wordprocessingml.document"_s + << uR"(form-data; name="text"; filename="テキスト.docx"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.docx)"_s + << u""_s; + + QTest::newRow("xls-ascii") << "spreadsheet"_L1 << u"sheet.xlsx"_s << u"sheet.xlsx"_s + << u"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_s + << uR"(form-data; name="spreadsheet"; filename="sheet.xlsx")"_s + << u"filename*"_s; + QTest::newRow("xls-latin") << "spreadsheet"_L1 << u"sheet.xlsx"_s << u"szöveg.xlsx"_s + << u"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_s + << uR"(form-data; name="spreadsheet"; filename="szöveg.xlsx"; filename*=UTF-8''sz%C3%B6veg.xlsx)"_s + << u""_s; + QTest::newRow("xls-unicode") << "spreadsheet"_L1 << u"sheet.xlsx"_s << u"テキスト.xlsx"_s + << u"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_s + << uR"(form-data; name="spreadsheet"; filename="テキスト.xlsx"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.xlsx)"_s + << u""_s; +} + +void tst_QFormDataBuilder::generateQHttpPartWithDevice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, real_file_name); + QFETCH(const QString, body_name_data); + QFETCH(const QString, expected_content_type_data); + QFETCH(const QString, expected_content_disposition_data); + QFETCH(const QString, content_disposition_must_not_contain_data); + + QString testData = QFileInfo(QFINDTESTDATA(real_file_name)).absoluteFilePath(); + QFile data_file(testData); + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBodyDevice(&data_file, body_name_data); + const QHttpPart httpPart = qfdpb.build(); + + const auto msg = QDebug::toString(httpPart); + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); + if (!content_disposition_must_not_contain_data.isEmpty()) + QVERIFY(!msg.contains(content_disposition_must_not_contain_data)); +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QString>("expected_content_type_data"); + QTest::addColumn<QString>("expected_content_disposition_data"); + + QTest::newRow("quote") << "t\"ext"_L1 << uR"(rfc32"52.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\"ext"; filename="rfc32\"52.txt")"_s; + + QTest::newRow("slash") << "t\\ext"_L1 << uR"(rfc32\52.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\\ext"; filename="rfc32\\52.txt")"_s; + + QTest::newRow("quotes") << "t\"e\"xt"_L1 << uR"(rfc3"25"2.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\"e\"xt"; filename="rfc3\"25\"2.txt")"_s; + + QTest::newRow("slashes") << "t\\\\ext"_L1 << uR"(rfc32\\52.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\\\\ext"; filename="rfc32\\\\52.txt")"_s; + + QTest::newRow("quote-slash") << "t\"ex\\t"_L1 << uR"(rfc"32\52.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\"ex\\t"; filename="rfc\"32\\52.txt")"_s; + + QTest::newRow("quotes-slashes") << "t\"e\"x\\t\\"_L1 << uR"(r"f"c3\2\52.txt)"_s << u"text/plain"_s + << uR"(form-data; name="t\"e\"x\\t\\"; filename="r\"f\"c3\\2\\52.txt")"_s; +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, body_name_data); + QFETCH(const QString, expected_content_type_data); + QFETCH(const QString, expected_content_disposition_data); + + QFile dummy_file(body_name_data); + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBodyDevice(&dummy_file, body_name_data); + const QHttpPart httpPart = qfdpb.build(); + + const auto msg = QDebug::toString(httpPart); + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::picksUtf8FilenameEncodingIfAsciiDontSuffice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QAnyStringView>("body_name_data"); + QTest::addColumn<QString>("expected_content_type_data"); + QTest::addColumn<QString>("expected_content_disposition_data"); + QTest::addColumn<QString>("content_disposition_must_not_contain_data"); + + QTest::newRow("latin1-ascii") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) << u"text/plain"_s + << uR"(form-data; name="text"; filename="rfc3252.txt")"_s + << u"filename*"_s; + QTest::newRow("u8-ascii") << "text"_L1 << QAnyStringView(u8"rfc3252.txt") << u"text/plain"_s + << uR"(form-data; name="text"; filename="rfc3252.txt")"_s + << u"filename*"_s; + QTest::newRow("u-ascii") << "text"_L1 << QAnyStringView(u"rfc3252.txt") << u"text/plain"_s + << uR"(form-data; name="text"; filename="rfc3252.txt")"_s + << u"filename*"_s; + + // 0xF6 is 'ö', use hex value with Latin-1 to avoid interpretation as UTF-8 (0xC3 0xB6) + QTest::newRow("latin1-latin") << "text"_L1 << QAnyStringView("sz\xF6veg.txt"_L1) << u"text/plain"_s + << uR"(form-data; name="text"; filename="szöveg.txt"; filename*=UTF-8''sz%C3%B6veg.txt)"_s + << u""_s; + QTest::newRow("u8-latin") << "text"_L1 << QAnyStringView(u8"szöveg.txt") << u"text/plain"_s + << uR"(form-data; name="text"; filename="szöveg.txt"; filename*=UTF-8''sz%C3%B6veg.txt)"_s + << u""_s; + QTest::newRow("u-latin") << "text"_L1 << QAnyStringView(u"szöveg.txt") << u"text/plain"_s + << uR"(form-data; name="text"; filename="szöveg.txt"; filename*=UTF-8''sz%C3%B6veg.txt)"_s + << u""_s; + + QTest::newRow("u8-u8") << "text"_L1 << QAnyStringView(u8"テキスト.txt") << u"text/plain"_s + << uR"(form-data; name="text"; filename="テキスト.txt"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt)"_s + << u""_s; +} + +void tst_QFormDataBuilder::picksUtf8FilenameEncodingIfAsciiDontSuffice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QAnyStringView, body_name_data); + QFETCH(const QString, expected_content_type_data); + QFETCH(const QString, expected_content_disposition_data); + QFETCH(const QString, content_disposition_must_not_contain_data); + + QBuffer buff; + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBodyDevice(&buff, body_name_data); + const QHttpPart httpPart = qfdpb.build(); + + const auto msg = QDebug::toString(httpPart); + QVERIFY2(msg.contains(expected_content_type_data), + qPrintable(u"content-type not found : "_s + expected_content_type_data)); + QVERIFY2(msg.contains(expected_content_disposition_data), + qPrintable(u"content-disposition not found : "_s + expected_content_disposition_data)); + if (!content_disposition_must_not_contain_data.isEmpty()) { + QVERIFY2(!msg.contains(content_disposition_must_not_contain_data), + qPrintable(u"content-disposition contained data it shouldn't : "_s + + content_disposition_must_not_contain_data)); + } +} + +void tst_QFormDataBuilder::setHeadersDoesNotAffectHeaderFieldsManagedByBuilder_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QAnyStringView>("body_name_data"); + QTest::addColumn<bool>("overwrite"); + QTest::addColumn<bool>("extra_headers"); + QTest::addColumn<QStringList>("expected_headers"); + + QTest::newRow("content-disposition-is-set-by-default") + << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << false << false + << QStringList{ + uR"("content-disposition":"form-data; name=\"text\"; filename=\"rfc3252.txt\"")"_s, + uR"("content-type":"text/plain")"_s}; + + QTest::newRow("default-overwrites-preset-content-disposition") + << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << true << false + << QStringList{ + uR"("content-disposition":"form-data; name=\"text\"; filename=\"rfc3252.txt\"")"_s, + uR"("content-type":"text/plain")"_s}; + + QTest::newRow("added-extra-header") + << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << false << true + << QStringList{ + uR"("content-disposition":"form-data; name=\"text\"; filename=\"rfc3252.txt\"")"_s, + uR"("content-type":"text/plain")"_s, + uR"("content-length":"70")"_s}; + + QTest::newRow("extra-header-and-overwrite") + << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << true << true + << QStringList{ + uR"("content-disposition":"form-data; name=\"text\"; filename=\"rfc3252.txt\"")"_s, + uR"("content-type":"text/plain")"_s, + uR"("content-length":"70")"_s}; +} + +void tst_QFormDataBuilder::setHeadersDoesNotAffectHeaderFieldsManagedByBuilder() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QAnyStringView, body_name_data); + QFETCH(const bool, overwrite); + QFETCH(const bool, extra_headers); + QFETCH(const QStringList, expected_headers); + + QBuffer buff; + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBodyDevice(&buff, body_name_data); + + if (overwrite || extra_headers) { + QHttpHeaders headers; + + if (overwrite) { + headers.append(QHttpHeaders::WellKnownHeader::ContentType, "attachment"); + qfdpb.setHeaders(headers); + } + + if (extra_headers) { + headers.append(QHttpHeaders::WellKnownHeader::ContentLength, "70"); + qfdpb.setHeaders(std::move(headers)); + } + } + + const QHttpPart httpPart = qfdpb.build(); + + const auto msg = QDebug::toString(httpPart); + for (const auto &header : expected_headers) + QVERIFY2(msg.contains(header), qPrintable(header)); +} + +void tst_QFormDataBuilder::specifyMimeType_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QAnyStringView>("body_name_data"); + QTest::addColumn<QAnyStringView>("mime_type"); + QTest::addColumn<QString>("expected_content_type_data"); + + QTest::newRow("not-specified") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << QAnyStringView("text/plain"_L1) << uR"("content-type":"text/plain")"_s; + QTest::newRow("mime-specified") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << QAnyStringView("text/plain"_L1) << uR"("content-type":"text/plain")"_s; + // wrong mime type specified but it is not overridden by the deduction + QTest::newRow("wrong-mime-specified") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) + << QAnyStringView("image/jpeg"_L1) << uR"("content-type":"image/jpeg)"_s; +} + +void tst_QFormDataBuilder::specifyMimeType() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QAnyStringView, body_name_data); + QFETCH(const QAnyStringView, mime_type); + QFETCH(const QString, expected_content_type_data); + + QBuffer buff; + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBodyDevice(&buff, body_name_data); + + if (!mime_type.empty()) + qfdpb.setBodyDevice(&buff, body_name_data, mime_type); + else + qfdpb.setBodyDevice(&buff, body_name_data); + + const QHttpPart httpPart = qfdpb.build(); + + const auto msg = QDebug::toString(httpPart); + QVERIFY(msg.contains(expected_content_type_data)); +} + +void tst_QFormDataBuilder::picksUtf8NameEncodingIfAsciiDoesNotSuffice_data() +{ + QTest::addColumn<QAnyStringView>("name_data"); + QTest::addColumn<QString>("expected_content_disposition_data"); + + QTest::newRow("latin1-ascii") << QAnyStringView("text"_L1) << uR"(form-data; name="text")"_s; + QTest::newRow("u8-ascii") << QAnyStringView(u8"text") << uR"(form-data; name="text")"_s; + QTest::newRow("u-ascii") << QAnyStringView(u"text") << uR"(form-data; name="text")"_s; + + // 0xF6 is 'ö', use hex value with Latin-1 to avoid interpretation as UTF-8 + QTest::newRow("latin1-latin") << QAnyStringView("t\xF6xt"_L1) << uR"(form-data; name="töxt")"_s; + QTest::newRow("u8-latin") << QAnyStringView(u8"töxt") << uR"(form-data; name="töxt")"_s; + QTest::newRow("u-latin") << QAnyStringView(u"töxt") << uR"(form-data; name="töxt")"_s; + + QTest::newRow("u8-u8") << QAnyStringView(u8"テキスト") << uR"(form-data; name="テキスト")"_s; +} + +void tst_QFormDataBuilder::picksUtf8NameEncodingIfAsciiDoesNotSuffice() +{ + QFETCH(const QAnyStringView, name_data); + QFETCH(const QString, expected_content_disposition_data); + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBody("some"_ba); + auto msg = QDebug::toString(qfdpb.build()); + + QVERIFY2(msg.contains(expected_content_disposition_data), + qPrintable(u"content-disposition not found : "_s + expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::moveSemantics() +{ +#if defined(QT_BUILD_INTERNAL) || !defined(QT_UNDEFINED_SANITIZER) + constexpr QByteArrayView expected = "--boundary_.oOo._4SUrZy7x9lPHMF3fbRSsE15hiWu5Sbmy\r\n" + "content-type: text/plain\r\ncontent-disposition: form-data; name=\"text\"; filename=\"rfc3252.txt\"\r\n\r\n" + "some text for reference\r\n" + "--boundary_.oOo._4SUrZy7x9lPHMF3fbRSsE15hiWu5Sbmy--\r\n"; + + const QString testData = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absoluteFilePath(); + + // We get the expected + { + QFile data_file(testData); + QVERIFY2(data_file.open(QIODeviceBase::ReadOnly), qPrintable(data_file.errorString())); + + QFormDataBuilder qfdb; + qfdb.part("text"_L1).setBodyDevice(&data_file, "rfc3252.txt"); + std::unique_ptr<QHttpMultiPart> mp = qfdb.buildMultiPart(); + + auto mp_priv = QHttpMultiPartPrivate::get(mp.get()); + mp_priv->device->open(QIODeviceBase::ReadOnly); + const QByteArray actual = mp_priv->device->readAll(); + + checkBodyPartsAreEquivalent(expected, actual); + } + + // We get the expected from a move constructed qfdb + { + QFile data_file(testData); + QVERIFY2(data_file.open(QIODeviceBase::ReadOnly), qPrintable(data_file.errorString())); + + QFormDataBuilder qfdb; + auto &p = qfdb.part("text"_L1); + auto qfdb_moved = std::move(qfdb); + + p.setBodyDevice(&data_file, "rfc3252.txt"); + std::unique_ptr<QHttpMultiPart> mp = qfdb_moved.buildMultiPart(); + + auto mp_priv = QHttpMultiPartPrivate::get(mp.get()); + mp_priv->device->open(QIODeviceBase::ReadOnly); + const QByteArray actual = mp_priv->device->readAll(); + + checkBodyPartsAreEquivalent(expected, actual); + } + + // We get the expected from a move assigned qfdb + { + QFile data_file(testData); + QVERIFY2(data_file.open(QIODeviceBase::ReadOnly), qPrintable(data_file.errorString())); + + QFormDataBuilder qfdb; + QFormDataBuilder qfdb_moved; + + qfdb.part("text"_L1).setBodyDevice(&data_file, "rfc3252.txt"); + + qfdb_moved = std::move(qfdb); + std::unique_ptr<QHttpMultiPart> mp = qfdb_moved.buildMultiPart(); + + auto mp_priv = QHttpMultiPartPrivate::get(mp.get()); + mp_priv->device->open(QIODeviceBase::ReadOnly); + const QByteArray actual = mp_priv->device->readAll(); + + checkBodyPartsAreEquivalent(expected, actual); + } +#else + QSKIP("This test requires -developer-build when --sanitize=undefined is active."); +#endif +} + +QTEST_MAIN(tst_QFormDataBuilder) +#include "tst_qformdatabuilder.moc" diff --git a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp index 457d30feeb..5b6be3c7b4 100644 --- a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp +++ b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp @@ -436,9 +436,6 @@ void tst_QHttpHeaders::headerValueField() QRegularExpression re("HTTP header value contained illegal character*"); TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x08" "bar"); // BS TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x1B" "bar"); // ESC - // Disallowed UTF-8 characters - TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"€"); - TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); // Disallowed UTF-16 characters TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"€"); TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); diff --git a/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa b/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa Binary files differindex 2bd4ca05f3..f891b16963 100644 --- a/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa +++ b/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index a26d2c809b..06d0847acd 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -6682,7 +6682,6 @@ void tst_QNetworkReply::httpConnectionCount() int pendingConnectionCount = 0; - using namespace std::chrono_literals; const auto newPendingConnection = [&server]() { return server->hasPendingConnections(); }; // If we have http2 enabled then the second connection will take a little // longer to be established because we will wait for the first one to finish @@ -10300,6 +10299,9 @@ void tst_QNetworkReply::contentEncoding() #if QT_CONFIG(http) void tst_QNetworkReply::contentEncodingBigPayload_data() { +#if Q_PROCESSOR_WORDSIZE <= 4 + QSKIP("Allocating 4GB leads to std::bad_alloc on a 32b architecture"); +#endif QTest::addColumn<QByteArray>("encoding"); QTest::addColumn<QString>("path"); QTest::addColumn<qint64>("expectedSize"); @@ -10337,7 +10339,7 @@ void tst_QNetworkReply::contentEncodingBigPayload() request.setDecompressedSafetyCheckThreshold(-1); QNetworkReplyPtr reply(manager.get(request)); - QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15s); QCOMPARE(reply->error(), QNetworkReply::NoError); QFETCH(qint64, expectedSize); @@ -10462,7 +10464,7 @@ void tst_QNetworkReply::contentEncodingError() QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); QNetworkReplyPtr reply(manager.get(request)); - QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15s); QTEST(reply->error(), "expectedError"); } @@ -10532,7 +10534,7 @@ void tst_QNetworkReply::notFoundWithCompression() QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); QNetworkReplyPtr reply(manager.get(request)); - QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15s); QCOMPARE(reply->error(), QNetworkReply::ContentNotFoundError); QFETCH(QByteArray, expected); diff --git a/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt b/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt new file mode 100644 index 0000000000..13a60afb13 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/CMakeLists.txt @@ -0,0 +1,9 @@ +qt_internal_add_test(tst_qnetworkreply_local + SOURCES + minihttpserver.h + tst_qnetworkreply_local.cpp + LIBRARIES + Qt::CorePrivate + Qt::NetworkPrivate + BUNDLE_ANDROID_OPENSSL_LIBS +) diff --git a/tests/auto/network/access/qnetworkreply_local/minihttpserver.h b/tests/auto/network/access/qnetworkreply_local/minihttpserver.h new file mode 100644 index 0000000000..eb0697a6f8 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/minihttpserver.h @@ -0,0 +1,246 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MINIHTTPSERVER_H +#define MINIHTTPSERVER_H + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QtNetwork/qtcpserver.h> +#include <QtNetwork/qtcpsocket.h> +#include <QtNetwork/qlocalsocket.h> +#if QT_CONFIG(ssl) +# include <QtNetwork/qsslsocket.h> +#endif +#if QT_CONFIG(localserver) +# include <QtNetwork/qlocalserver.h> +#endif + +#include <QtCore/qpointer.h> +#include <QtCore/qhash.h> + +#include <utility> + +static inline QByteArray default200Response() +{ + return QByteArrayLiteral("HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 12\r\n" + "\r\n" + "Hello World!"); +} +class MiniHttpServerV2 : public QObject +{ + Q_OBJECT + +public: + struct State; + +#if QT_CONFIG(localserver) + void bind(QLocalServer *server) + { + Q_ASSERT(!localServer); + localServer = server; + connect(server, &QLocalServer::newConnection, this, + &MiniHttpServerV2::incomingLocalConnection); + } +#endif + + void bind(QTcpServer *server) + { + Q_ASSERT(!tcpServer); + tcpServer = server; + connect(server, &QTcpServer::pendingConnectionAvailable, this, + &MiniHttpServerV2::incomingConnection); + } + + void setDataToTransmit(QByteArray data) { dataToTransmit = std::move(data); } + + void clearServerState() + { + auto copy = std::exchange(clientStates, {}); + for (auto [socket, _] : copy.asKeyValueRange()) { + if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket)) + tcpSocket->disconnectFromHost(); + else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket)) + localSocket->disconnectFromServer(); + else + Q_UNREACHABLE_RETURN(); + socket->deleteLater(); + } + } + + bool hasPendingConnections() const + { + return +#if QT_CONFIG(localserver) + (localServer && localServer->hasPendingConnections()) || +#endif + (tcpServer && tcpServer->hasPendingConnections()); + } + + QString addressForScheme(QStringView scheme) const + { + using namespace Qt::StringLiterals; + if (scheme.startsWith("unix"_L1) || scheme.startsWith("local"_L1)) { +#if QT_CONFIG(localserver) + if (localServer) + return localServer->serverName(); +#endif + } else if (scheme == "http"_L1) { + if (tcpServer) + return "%1:%2"_L1.arg(tcpServer->serverAddress().toString(), + QString::number(tcpServer->serverPort())); + } + return {}; + } + + QList<State> peerStates() const { return clientStates.values(); } + +protected: +#if QT_CONFIG(localserver) + void incomingLocalConnection() + { + auto *socket = localServer->nextPendingConnection(); + connectSocketSignals(socket); + } +#endif + + void incomingConnection() + { + auto *socket = tcpServer->nextPendingConnection(); + connectSocketSignals(socket); + } + + void reply(QIODevice *socket) + { + Q_ASSERT(socket); + if (dataToTransmit.isEmpty()) { + emit socket->bytesWritten(0); // emulate having written the data + return; + } + if (!stopTransfer) + socket->write(dataToTransmit); + } + +private: + void connectSocketSignals(QIODevice *socket) + { + connect(socket, &QIODevice::readyRead, this, [this, socket]() { readyReadSlot(socket); }); + connect(socket, &QIODevice::bytesWritten, this, + [this, socket]() { bytesWrittenSlot(socket); }); +#if QT_CONFIG(ssl) + if (auto *sslSocket = qobject_cast<QSslSocket *>(socket)) + connect(sslSocket, &QSslSocket::sslErrors, this, &MiniHttpServerV2::slotSslErrors); +#endif + + if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket)) { + connect(tcpSocket, &QAbstractSocket::errorOccurred, this, &MiniHttpServerV2::slotError); + } else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket)) { + connect(localSocket, &QLocalSocket::errorOccurred, this, + [this](QLocalSocket::LocalSocketError error) { + slotError(QAbstractSocket::SocketError(error)); + }); + } else { + Q_UNREACHABLE_RETURN(); + } + } + + void parseContentLength(State &st, QByteArrayView header) + { + qsizetype index = header.indexOf("\r\ncontent-length:"); + if (index == -1) + return; + st.foundContentLength = true; + + index += sizeof("\r\ncontent-length:") - 1; + const auto *end = std::find(header.cbegin() + index, header.cend(), '\r'); + QByteArrayView num = header.mid(index, std::distance(header.cbegin() + index, end)); + bool ok = false; + st.contentLength = num.toInt(&ok); + if (!ok) + st.contentLength = -1; + } + +private slots: +#if QT_CONFIG(ssl) + void slotSslErrors(const QList<QSslError> &errors) + { + QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); + Q_ASSERT(currentClient); + qDebug() << "slotSslErrors" << currentClient->errorString() << errors; + } +#endif + void slotError(QAbstractSocket::SocketError err) + { + QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); + Q_ASSERT(currentClient); + qDebug() << "slotError" << err << currentClient->errorString(); + } + +public slots: + + void readyReadSlot(QIODevice *socket) + { + if (stopTransfer) + return; + State &st = clientStates[socket]; + st.receivedData += socket->readAll(); + const qsizetype doubleEndlPos = st.receivedData.indexOf("\r\n\r\n"); + + if (doubleEndlPos != -1) { + const qsizetype endOfHeader = doubleEndlPos + 4; + st.contentRead = st.receivedData.size() - endOfHeader; + + if (!st.checkedContentLength) { + parseContentLength(st, QByteArrayView(st.receivedData).first(endOfHeader)); + st.checkedContentLength = true; + } + + if (st.contentRead < st.contentLength) + return; + + // multiple requests incoming, remove the bytes of the current one + if (multiple) + st.receivedData.remove(0, endOfHeader); + + reply(socket); + } + } + + void bytesWrittenSlot(QIODevice *socket) + { + // Disconnect and delete in next cycle (else Windows clients will fail with + // RemoteHostClosedError). + if (doClose && socket->bytesToWrite() == 0) { + disconnect(socket, nullptr, this, nullptr); + socket->deleteLater(); + } + } + +private: + QByteArray dataToTransmit = default200Response(); + + QTcpServer *tcpServer = nullptr; +#if QT_CONFIG(localserver) + QLocalServer *localServer = nullptr; +#endif + + QHash<QIODevice *, State> clientStates; + +public: + struct State + { + QByteArray receivedData; + qsizetype contentLength = 0; + qsizetype contentRead = 0; + bool checkedContentLength = false; + bool foundContentLength = false; + }; + + bool doClose = true; + bool multiple = false; + bool stopTransfer = false; +}; + +#endif // MINIHTTPSERVER_H diff --git a/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp b/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp new file mode 100644 index 0000000000..6d78c81593 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply_local/tst_qnetworkreply_local.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QtTest/qtest.h> + +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qnetworkaccessmanager.h> + +#include "minihttpserver.h" + +using namespace Qt::StringLiterals; + +/* + The tests here are meant to be self-contained, using servers in the same + process if needed. This enables externals to more easily run the tests too. +*/ +class tst_QNetworkReply_local : public QObject +{ + Q_OBJECT +private slots: + void initTestCase_data(); + + void get(); + void post(); + +#if QT_CONFIG(localserver) + void fullServerName_data(); + void fullServerName(); +#endif +}; + +void tst_QNetworkReply_local::initTestCase_data() +{ + QTest::addColumn<QString>("scheme"); + + QTest::newRow("http") << "http"; +#if QT_CONFIG(localserver) + QTest::newRow("unix") << "unix+http"; + QTest::newRow("local") << "local+http"; // equivalent to unix, but test that it works +#endif +} + +static std::unique_ptr<MiniHttpServerV2> getServerForCurrentScheme() +{ + auto server = std::make_unique<MiniHttpServerV2>(); + QFETCH_GLOBAL(QString, scheme); + if (scheme.startsWith("unix"_L1) || scheme.startsWith("local"_L1)) { +#if QT_CONFIG(localserver) + QLocalServer *localServer = new QLocalServer(server.get()); + localServer->listen(u"qt_networkreply_test_"_s + % QLatin1StringView(QTest::currentTestFunction()) + % QString::number(QCoreApplication::applicationPid())); + server->bind(localServer); +#endif + } else if (scheme == "http") { + QTcpServer *tcpServer = new QTcpServer(server.get()); + tcpServer->listen(QHostAddress::LocalHost, 0); + server->bind(tcpServer); + } + return server; +} + +static QUrl getUrlForCurrentScheme(MiniHttpServerV2 *server) +{ + QFETCH_GLOBAL(QString, scheme); + const QString address = server->addressForScheme(scheme); + const QString urlString = QLatin1StringView("%1://%2").arg(scheme, address); + return { urlString }; +} + +void tst_QNetworkReply_local::get() +{ + std::unique_ptr<MiniHttpServerV2> server = getServerForCurrentScheme(); + const QUrl url = getUrlForCurrentScheme(server.get()); + + QNetworkAccessManager manager; + std::unique_ptr<QNetworkReply> reply(manager.get(QNetworkRequest(url))); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); +} + +void tst_QNetworkReply_local::post() +{ + std::unique_ptr<MiniHttpServerV2> server = getServerForCurrentScheme(); + const QUrl url = getUrlForCurrentScheme(server.get()); + + QNetworkAccessManager manager; + const QByteArray payload = "Hello from the other side!"_ba; + QNetworkRequest req(url); + req.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + std::unique_ptr<QNetworkReply> reply(manager.post(req, payload)); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + auto states = server->peerStates(); + QCOMPARE(states.size(), 1); + + const auto &firstRequest = states.at(0); + + QVERIFY(firstRequest.checkedContentLength); + QCOMPARE(firstRequest.contentLength, payload.size()); + QCOMPARE_GT(firstRequest.receivedData.size(), payload.size() + 4); + QCOMPARE(firstRequest.receivedData.last(payload.size() + 4), "\r\n\r\n" + payload); +} + +#if QT_CONFIG(localserver) +void tst_QNetworkReply_local::fullServerName_data() +{ +#if defined(Q_OS_ANDROID) || defined(QT_PLATFORM_UIKIT) + QSKIP("While partially supported, the test as-is doesn't make sense on this platform."); +#else + + QTest::addColumn<QString>("hostAndPath"); + + QTest::newRow("dummy-host") << u"://irrelevant/test"_s; + QTest::newRow("no-host") << u":///test"_s; +#endif +} + +void tst_QNetworkReply_local::fullServerName() +{ + QFETCH_GLOBAL(QString, scheme); + if (!scheme.startsWith("unix"_L1) && !scheme.startsWith("local"_L1)) + return; // only relevant for local sockets + + MiniHttpServerV2 server; + QLocalServer localServer; + + QString path; +#ifdef Q_OS_WIN + path = uR"(\\.\pipe\qt_networkreply_test_fullServerName)"_s + % QString::number(QCoreApplication::applicationPid()); +#else + path = u"/tmp/qt_networkreply_test_fullServerName"_s + % QString::number(QCoreApplication::applicationPid()) % u".sock"_s; +#endif + + QVERIFY(localServer.listen(path)); + server.bind(&localServer); + + QFETCH(QString, hostAndPath); + QUrl url(scheme % hostAndPath); + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::FullLocalServerNameAttribute, path); + + QNetworkAccessManager manager; + std::unique_ptr<QNetworkReply> reply(manager.get(req)); + + const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); }); + QVERIFY(res); + + QCOMPARE(reply->readAll(), QByteArray("Hello World!")); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + const QByteArray receivedData = server.peerStates().at(0).receivedData; + const QByteArray expectedGet = "GET " % url.path().toUtf8() % " HTTP/1.1\r\n"; + QVERIFY(receivedData.startsWith(expectedGet)); + + const QByteArray expectedHost = "host: " % url.host().toUtf8() % "\r\n"; + QVERIFY(receivedData.contains(expectedHost)); +} +#endif + +QTEST_MAIN(tst_QNetworkReply_local) + +#include "tst_qnetworkreply_local.moc" +#include "moc_minihttpserver.cpp" diff --git a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp index 0b6d6f339b..f0b02ae91d 100644 --- a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp +++ b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp @@ -3,8 +3,10 @@ #include <QTest> +#if QT_CONFIG(http) #include <QtNetwork/QHttp1Configuration> #include <QtNetwork/QHttp2Configuration> +#endif #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkCookie> diff --git a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp index 141ca25021..e5bbf3467c 100644 --- a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp +++ b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp @@ -197,6 +197,14 @@ void tst_QNetworkInterface::localAddress_data() } else if (!ipv6 || entry.prefixLength() != 64) { continue; } else { +#ifdef Q_OS_ANDROID + // Android seem to not allow IPv6 connection from interfaces other than wlan, + // if it's connected, and wlan is connected by default on Android emulators, + // so prefer selecting wlan in this test. + const QString scopeId = addr.scopeId(); + if (!scopeId.isEmpty() && !scopeId.startsWith("wlan")) + continue; +#endif // add a random node in this IPv6 network quint64 randomid = qFromBigEndian(Q_UINT64_C(0x8f41f072e5733caa)); QIPv6Address ip6 = addr.toIPv6Address(); |