diff options
Diffstat (limited to 'tests/auto/network/access')
91 files changed, 8560 insertions, 1789 deletions
diff --git a/tests/auto/network/access/CMakeLists.txt b/tests/auto/network/access/CMakeLists.txt index d225eb1299..ed99aa8746 100644 --- a/tests/auto/network/access/CMakeLists.txt +++ b/tests/auto/network/access/CMakeLists.txt @@ -1,6 +1,10 @@ -# Generated from access.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause -add_subdirectory(qnetworkdiskcache) +add_subdirectory(qhttpheaders) +if(QT_FEATURE_networkdiskcache) + add_subdirectory(qnetworkdiskcache) +endif() add_subdirectory(qnetworkcookiejar) add_subdirectory(qnetworkaccessmanager) add_subdirectory(qnetworkcookie) @@ -8,7 +12,16 @@ add_subdirectory(qnetworkrequest) add_subdirectory(qnetworkreply) add_subdirectory(qnetworkcachemetadata) add_subdirectory(qabstractnetworkcache) +if(QT_FEATURE_http) + add_subdirectory(qnetworkreply_local) + add_subdirectory(qformdatabuilder) + add_subdirectory(qnetworkrequestfactory) + add_subdirectory(qrestaccessmanager) +endif() if(QT_FEATURE_private_tests) + add_subdirectory(qhttp2connection) + add_subdirectory(qhttpheaderparser) + add_subdirectory(qhttpheadershelper) add_subdirectory(qhttpnetworkconnection) add_subdirectory(qhttpnetworkreply) add_subdirectory(hpack) diff --git a/tests/auto/network/access/access.pro b/tests/auto/network/access/access.pro deleted file mode 100644 index 45d7b0cda5..0000000000 --- a/tests/auto/network/access/access.pro +++ /dev/null @@ -1,26 +0,0 @@ -TEMPLATE=subdirs -QT_FOR_CONFIG += network - -SUBDIRS=\ - qnetworkdiskcache \ - qnetworkcookiejar \ - qnetworkaccessmanager \ - qnetworkcookie \ - qnetworkrequest \ - qhttpnetworkconnection \ - qnetworkreply \ - qnetworkcachemetadata \ - qhttpnetworkreply \ - qabstractnetworkcache \ - hpack \ - http2 \ - hsts \ - qdecompresshelper - -!qtConfig(private_tests): SUBDIRS -= \ - qhttpnetworkconnection \ - qhttpnetworkreply \ - hpack \ - http2 \ - hsts \ - qdecompresshelper diff --git a/tests/auto/network/access/hpack/CMakeLists.txt b/tests/auto/network/access/hpack/CMakeLists.txt index 421a60c17d..32cd4b2f06 100644 --- a/tests/auto/network/access/hpack/CMakeLists.txt +++ b/tests/auto/network/access/hpack/CMakeLists.txt @@ -1,17 +1,21 @@ -# Generated from hpack.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_hpack Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_hpack LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_hpack SOURCES tst_hpack.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Network Qt::NetworkPrivate ) - -#### Keys ignored in scope 1:.:.:hpack.pro:<TRUE>: -# TEMPLATE = "app" diff --git a/tests/auto/network/access/hpack/hpack.pro b/tests/auto/network/access/hpack/hpack.pro deleted file mode 100644 index 2823ae4d0c..0000000000 --- a/tests/auto/network/access/hpack/hpack.pro +++ /dev/null @@ -1,6 +0,0 @@ -QT = core core-private network network-private testlib -CONFIG += testcase parallel_test c++11 -TEMPLATE = app -TARGET = tst_hpack - -SOURCES += tst_hpack.cpp diff --git a/tests/auto/network/access/hpack/tst_hpack.cpp b/tests/auto/network/access/hpack/tst_hpack.cpp index d5e359db57..e6b43eaed4 100644 --- a/tests/auto/network/access/hpack/tst_hpack.cpp +++ b/tests/auto/network/access/hpack/tst_hpack.cpp @@ -1,33 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2014 Governikus GmbH & Co. KG. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2014 Governikus GmbH & Co. KG. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QRandomGenerator> #include <QtNetwork/private/bitstreams_p.h> #include <QtNetwork/private/hpack_p.h> diff --git a/tests/auto/network/access/hsts/CMakeLists.txt b/tests/auto/network/access/hsts/CMakeLists.txt index ba3b110f59..821a034940 100644 --- a/tests/auto/network/access/hsts/CMakeLists.txt +++ b/tests/auto/network/access/hsts/CMakeLists.txt @@ -1,17 +1,23 @@ -# Generated from hsts.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qhsts Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qhsts LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qhsts SOURCES tst_qhsts.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Network Qt::NetworkPrivate ) -#### Keys ignored in scope 1:.:.:hsts.pro:<TRUE>: # TEMPLATE = "app" diff --git a/tests/auto/network/access/hsts/hsts.pro b/tests/auto/network/access/hsts/hsts.pro deleted file mode 100644 index dad6638364..0000000000 --- a/tests/auto/network/access/hsts/hsts.pro +++ /dev/null @@ -1,6 +0,0 @@ -QT = core core-private network network-private testlib -CONFIG += testcase parallel_test c++11 -TEMPLATE = app -TARGET = tst_qhsts - -SOURCES += tst_qhsts.cpp diff --git a/tests/auto/network/access/hsts/tst_qhsts.cpp b/tests/auto/network/access/hsts/tst_qhsts.cpp index b81ea31c03..4e9a5cc53f 100644 --- a/tests/auto/network/access/hsts/tst_qhsts.cpp +++ b/tests/auto/network/access/hsts/tst_qhsts.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtCore/qdatetime.h> #include <QtCore/qdir.h> @@ -34,6 +9,7 @@ #include <QtCore/qpair.h> #include <QtCore/qurl.h> +#include <QtNetwork/qhttpheaders.h> #include <QtNetwork/private/qhstsstore_p.h> #include <QtNetwork/private/qhsts_p.h> @@ -214,104 +190,108 @@ void tst_QHsts::testPolicyExpiration() void tst_QHsts::testSTSHeaderParser() { QHstsHeaderParser parser; - using Header = QPair<QByteArray, QByteArray>; - using Headers = QList<Header>; QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - Headers list; - QVERIFY(!parser.parse(list)); + QHttpHeaders headers; + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list << Header("Strict-Transport-security", "200"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-security", "200"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); // This header is missing REQUIRED max-age directive, so we'll ignore it: - list << Header("Strict-Transport-Security", "includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); - list << Header("Strict-Transport-Security", "includeSubDomains;max-age=1000"); - QVERIFY(parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "includeSubDomains;max-age=1000"); + QVERIFY(parser.parse(headers)); + QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc()); + QVERIFY(parser.includeSubDomains()); + + headers.removeAt(headers.size() - 1); + headers.append("strict-transport-security", "includeSubDomains;max-age=1000"); + QVERIFY(parser.parse(headers)); QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc()); QVERIFY(parser.includeSubDomains()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Invalid (includeSubDomains twice): - list << Header("Strict-Transport-Security", "max-age = 1000 ; includeSubDomains;includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age = 1000 ; includeSubDomains;includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Invalid (weird number of seconds): - list << Header("Strict-Transport-Security", "max-age=-1000 ; includeSubDomains"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age=-1000 ; includeSubDomains"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Note, directives are case-insensitive + we should ignore unknown directive. - list << Header("Strict-Transport-Security", ";max-age=1000 ;includesubdomains;;" + headers.append("Strict-Transport-Security", ";max-age=1000 ;includesubdomains;;" "nowsomeunknownheader=\"somevaluewithescapes\\;\""); - QVERIFY(parser.parse(list)); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // Check that we know how to unescape max-age: - list << Header("Strict-Transport-Security", "max-age=\"1000\""); - QVERIFY(parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age=\"1000\""); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); + headers.removeAt(headers.size() - 1); // The only STS header, with invalid syntax though, to be ignored: - list << Header("Strict-Transport-Security", "max-age; max-age=15768000"); - QVERIFY(!parser.parse(list)); + headers.append("Strict-Transport-Security", "max-age; max-age=15768000"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); // Now we check that our parse chosses the first valid STS header and ignores // others: - list.clear(); - list << Header("Strict-Transport-Security", "includeSubdomains; max-age=\"hehehe\";"); - list << Header("Strict-Transport-Security", "max-age=10101"); - QVERIFY(parser.parse(list)); + headers.clear(); + headers.append("Strict-Transport-Security", "includeSubdomains; max-age=\"hehehe\";"); + headers.append("Strict-Transport-Security", "max-age=10101"); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.clear(); - list << Header("Strict-Transport-Security", "max-age=0"); - QVERIFY(parser.parse(list)); + headers.clear(); + headers.append("Strict-Transport-Security", "max-age=0"); + QVERIFY(parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(parser.expirationDate() <= QDateTime::currentDateTimeUtc()); // Parsing is case-insensitive: - list.pop_back(); - list << Header("Strict-Transport-Security", "Max-aGE=1000; InclUdesUbdomains"); - QVERIFY(parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "Max-aGE=1000; InclUdesUbdomains"); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); // Grammar of STS header is quite permissive, let's check we can parse // some weird but valid header: - list.pop_back(); - list << Header("Strict-Transport-Security", ";;; max-age = 17; ; ; ; ;;; ;;" + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", ";;; max-age = 17; ; ; ; ;;; ;;" ";;; ; includeSubdomains ;;thisIsUnknownDirective;;;;"); - QVERIFY(parser.parse(list)); + QVERIFY(parser.parse(headers)); QVERIFY(parser.includeSubDomains()); QVERIFY(parser.expirationDate().isValid()); - list.pop_back(); - list << Header("Strict-Transport-Security", "max-age=1000; includeSubDomains bogon"); - QVERIFY(!parser.parse(list)); + headers.removeAt(headers.size() - 1); + headers.append("Strict-Transport-Security", "max-age=1000; includeSubDomains bogon"); + QVERIFY(!parser.parse(headers)); QVERIFY(!parser.includeSubDomains()); QVERIFY(!parser.expirationDate().isValid()); } diff --git a/tests/auto/network/access/http2/BLACKLIST b/tests/auto/network/access/http2/BLACKLIST new file mode 100644 index 0000000000..3de8d6d448 --- /dev/null +++ b/tests/auto/network/access/http2/BLACKLIST @@ -0,0 +1,2 @@ +[duplicateRequestsWithAborts] +qnx ci # QTBUG-119616 diff --git a/tests/auto/network/access/http2/CMakeLists.txt b/tests/auto/network/access/http2/CMakeLists.txt index 7ac1c8001c..7ea559940b 100644 --- a/tests/auto/network/access/http2/CMakeLists.txt +++ b/tests/auto/network/access/http2/CMakeLists.txt @@ -1,20 +1,24 @@ -# Generated from http2.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_http2 Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_http2 LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_http2 SOURCES - ../../../../shared/emulationdetector.h http2srv.cpp http2srv.h tst_http2.cpp - DEFINES - SRCDIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/\\\" - INCLUDE_DIRECTORIES - ../../../../shared - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Network Qt::NetworkPrivate + Qt::TestPrivate + BUNDLE_ANDROID_OPENSSL_LIBS ) diff --git a/tests/auto/network/access/http2/http2.pro b/tests/auto/network/access/http2/http2.pro deleted file mode 100644 index 646ea117f7..0000000000 --- a/tests/auto/network/access/http2/http2.pro +++ /dev/null @@ -1,9 +0,0 @@ -QT = core core-private network network-private testlib - -CONFIG += testcase parallel_test c++11 -TARGET = tst_http2 -INCLUDEPATH += ../../../../shared/ -HEADERS += http2srv.h ../../../../shared/emulationdetector.h -SOURCES += tst_http2.cpp http2srv.cpp - -DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp index 9513744476..b52ea5527b 100644 --- a/tests/auto/network/access/http2/http2srv.cpp +++ b/tests/auto/network/access/http2/http2srv.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtNetwork/private/http2protocol_p.h> #include <QtNetwork/private/bitstreams_p.h> @@ -109,6 +84,12 @@ Http2Server::~Http2Server() { } +void Http2Server::setInformationalStatusCode(int code) +{ + if (code == 100 || (102 <= code && code <= 199)) + informationalStatusCode = code; +} + void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path) { pushPromiseEnabled = pushEnabled; @@ -125,6 +106,28 @@ void Http2Server::setContentEncoding(const QByteArray &encoding) contentEncoding = encoding; } +void Http2Server::setAuthenticationHeader(const QByteArray &authentication) +{ + authenticationHeader = authentication; +} + +void Http2Server::setAuthenticationRequired(bool enable) +{ + Q_ASSERT(!enable || authenticationHeader.isEmpty()); + authenticationRequired = enable; +} + +void Http2Server::setRedirect(const QByteArray &url, int count) +{ + redirectUrl = url; + redirectCount = count; +} + +void Http2Server::setSendTrailingHEADERS(bool enable) +{ + sendTrailingHEADERS = enable; +} + void Http2Server::emulateGOAWAY(int timeout) { Q_ASSERT(timeout >= 0); @@ -143,6 +146,17 @@ bool Http2Server::isClearText() const return connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect; } +QByteArray Http2Server::requestAuthorizationHeader() +{ + const auto isAuthHeader = [](const HeaderField &field) { + return field.name == "authorization"; + }; + const auto requestHeaders = decoder.decodedHeader(); + const auto authentication = + std::find_if(requestHeaders.cbegin(), requestHeaders.cend(), isAuthHeader); + return authentication == requestHeaders.cend() ? QByteArray() : authentication->value; +} + void Http2Server::startServer() { if (listen()) { @@ -251,9 +265,20 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize) return; if (last) { - writer.start(FrameType::DATA, FrameFlag::END_STREAM, streamID); - writer.setPayloadSize(0); - writer.write(*socket); + if (sendTrailingHEADERS) { + writer.start(FrameType::HEADERS, + FrameFlag::PRIORITY | FrameFlag::END_HEADERS | FrameFlag::END_STREAM, streamID); + const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID, + Http2::maxPayloadSize)); + // 5 bytes for PRIORITY data: + writer.append(quint32(0)); // streamID 0 (32-bit) + writer.append(quint8(0)); // + weight 0 (8-bit) + writer.writeHEADERS(*socket, maxFrameSize); + } else { + writer.start(FrameType::DATA, FrameFlag::END_STREAM, streamID); + writer.setPayloadSize(0); + writer.write(*socket); + } suspendedStreams.erase(it); activeRequests.erase(streamID); @@ -302,11 +327,12 @@ void Http2Server::incomingConnection(qintptr socketDescriptor) sslSocket->setProtocol(QSsl::TlsV1_2OrLater); connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); - QFile file(SRCDIR "certs/fluke.key"); - file.open(QIODevice::ReadOnly); + QFile file(QT_TESTCASE_SOURCEDIR "/certs/fluke.key"); + if (!file.open(QIODevice::ReadOnly)) + qFatal("Cannot open certificate file %s", qPrintable(file.fileName())); QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); sslSocket->setPrivateKey(key); - auto localCert = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert"); + auto localCert = QSslCertificate::fromPath(QT_TESTCASE_SOURCEDIR "/certs/fluke.cert"); sslSocket->setLocalCertificateChain(localCert); sslSocket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState); // Stop listening. @@ -364,16 +390,12 @@ bool Http2Server::verifyProtocolUpgradeRequest() bool settingsOk = false; QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func(); + const auto headers = firstRequestReader->headers(); // That's how we append them, that's what I expect to find: - for (const auto &header : firstRequestReader->fields) { - if (header.first == "Connection") - connectionOk = header.second.contains("Upgrade, HTTP2-Settings"); - else if (header.first == "Upgrade") - upgradeOk = header.second.contains("h2c"); - else if (header.first == "HTTP2-Settings") - settingsOk = true; - } + connectionOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Connection).contains("Upgrade, HTTP2-Settings"); + upgradeOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Upgrade).contains("h2c"); + settingsOk = headers.contains("HTTP2-Settings"); return connectionOk && upgradeOk && settingsOk; } @@ -737,10 +759,15 @@ void Http2Server::handleDATA() } if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) { - closedStreams.insert(streamID); // Enter "half-closed remote" state. - streamWindows.erase(it); + if (responseBody.isEmpty()) { + closedStreams.insert(streamID); // Enter "half-closed remote" state. + streamWindows.erase(it); + } emit receivedData(streamID); } + emit receivedDATAFrame(streamID, + QByteArray(reinterpret_cast<const char *>(inboundFrame.dataBegin()), + inboundFrame.dataSize())); } void Http2Server::handleWINDOW_UPDATE() @@ -817,10 +844,32 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) // Now we'll continue with _normal_ response. } + // Create a header with an informational status code and some random header + // fields. The setter ensures that the value is 100 or is between 102 and 199 + // (inclusive) if set - otherwise it is 0 + + if (informationalStatusCode > 0) { + writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID); + + HttpHeader informationalHeader; + informationalHeader.push_back({":status", QByteArray::number(informationalStatusCode)}); + informationalHeader.push_back(HeaderField("a_random_header_field", "it_will_be_dropped")); + informationalHeader.push_back(HeaderField("another_random_header_field", "drop_this_too")); + + HPack::BitOStream ostream(writer.outboundFrame().buffer); + const bool result = encoder.encodeResponse(ostream, informationalHeader); + Q_ASSERT(result); + + writer.writeHEADERS(*socket, maxFrameSize); + } + writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID); if (emptyBody) writer.addFlag(FrameFlag::END_STREAM); + // We assume any auth is correct. Leaves the checking to the test itself + const bool hasAuth = !requestAuthorizationHeader().isEmpty(); + HttpHeader header; if (redirectWhileReading) { if (redirectSent) { @@ -836,7 +885,15 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) const QString url("%1://localhost:%2/"); header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"), QString::number(targetPort)).toLatin1()}); - + } else if (redirectCount > 0) { // Not redirecting while reading, unlike above + --redirectCount; + header.push_back({":status", "308"}); + header.push_back({"location", redirectUrl}); + } else if (!authenticationHeader.isEmpty() && !hasAuth) { + header.push_back({ ":status", "401" }); + header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader)); + } else if (authenticationRequired) { + header.push_back({ ":status", "401" }); } else { header.push_back({":status", "200"}); } diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h index baf0155988..dc94318527 100644 --- a/tests/auto/network/access/http2/http2srv.h +++ b/tests/auto/network/access/http2/http2srv.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef HTTP2SRV_H #define HTTP2SRV_H @@ -83,16 +58,29 @@ public: ~Http2Server(); + // To send responses with status code 1xx + void setInformationalStatusCode(int code); // To be called before server started: void enablePushPromise(bool enabled, const QByteArray &path = QByteArray()); void setResponseBody(const QByteArray &body); // No content encoding is actually performed, call setResponseBody with already encoded data void setContentEncoding(const QByteArray &contentEncoding); + // No authentication data is generated for the method, the full header value must be set + void setAuthenticationHeader(const QByteArray &authentication); + // Authentication always required, no challenge provided + void setAuthenticationRequired(bool enable); + // Set the redirect URL and count. The server will return a redirect response with the url + // 'count' amount of times + void setRedirect(const QByteArray &redirectUrl, int count); + // Send a trailing HEADERS frame with PRIORITY and END_STREAM flag + void setSendTrailingHEADERS(bool enable); void emulateGOAWAY(int timeout); void redirectOpenStream(quint16 targetPort); bool isClearText() const; + QByteArray requestAuthorizationHeader(); + // Invokables, since we can call them from the main thread, // but server (can) work on its own thread. Q_INVOKABLE void startServer(); @@ -129,6 +117,8 @@ Q_SIGNALS: void decompressionFailed(quint32 streamID); void receivedRequest(quint32 streamID); void receivedData(quint32 streamID); + // Emitted for every DATA frame. Includes the content of the frame as \a body. + void receivedDATAFrame(quint32 streamID, const QByteArray &body); void windowUpdate(quint32 streamID); void sendingData(); @@ -215,6 +205,14 @@ private: QAtomicInt interrupted; QByteArray contentEncoding; + QByteArray authenticationHeader; + bool authenticationRequired = false; + + QByteArray redirectUrl; + int redirectCount = 0; + + bool sendTrailingHEADERS = false; + int informationalStatusCode = 0; protected slots: void ignoreErrorSlot(); }; diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 0282942225..396a6f2fda 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -1,32 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QTest> +#include <QTestEventLoop> +#include <QScopeGuard> +#include <QRandomGenerator> +#include <QSignalSpy> #include "http2srv.h" @@ -36,38 +17,29 @@ #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> +#if QT_CONFIG(ssl) +#include <QtNetwork/qsslsocket.h> +#endif + #include <QtCore/qglobal.h> #include <QtCore/qobject.h> #include <QtCore/qthread.h> #include <QtCore/qurl.h> - -#ifndef QT_NO_SSL -#ifndef QT_NO_OPENSSL -#include <QtNetwork/private/qsslsocket_openssl_symbols_p.h> -#endif // NO_OPENSSL -#endif // NO_SSL +#include <QtCore/qset.h> #include <cstdlib> #include <memory> #include <string> -#include "emulationdetector.h" - -#if (!defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)) \ - || QT_CONFIG(schannel) -// HTTP/2 over TLS requires ALPN/NPN to negotiate the protocol version. -const bool clearTextHTTP2 = false; -#else -// No ALPN/NPN support to negotiate HTTP/2, we'll use cleartext 'h2c' with -// a protocol upgrade procedure. -const bool clearTextHTTP2 = true; -#endif +#include <QtTest/private/qemulationdetector_p.h> Q_DECLARE_METATYPE(H2Type) Q_DECLARE_METATYPE(QNetworkRequest::Attribute) QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + QHttp2Configuration qt_defaultH2Configuration() { QHttp2Configuration config; @@ -98,8 +70,11 @@ public slots: void init(); private slots: // Tests: + void defaultQnamHttp2Configuration(); void singleRequest_data(); void singleRequest(); + void informationalRequest_data(); + void informationalRequest(); void multipleRequests(); void flowControlClientSide(); void flowControlServerSide(); @@ -110,10 +85,29 @@ private slots: void connectToHost_data(); void connectToHost(); void maxFrameSize(); + void http2DATAFrames(); + + void moreActivitySignals_data(); + void moreActivitySignals(); void contentEncoding_data(); void contentEncoding(); + void authenticationRequired_data(); + void authenticationRequired(); + + void unsupportedAuthenticateChallenge(); + + void h2cAllowedAttribute_data(); + void h2cAllowedAttribute(); + + void redirect_data(); + void redirect(); + + void trailingHEADERS(); + + void duplicateRequestsWithAborts(); + protected slots: // Slots to listen to our in-process server: void serverStarted(quint16 port); @@ -157,6 +151,7 @@ private: int windowUpdates = 0; bool prefaceOK = false; bool serverGotSettingsACK = false; + bool POSTResponseHEADOnly = true; static const RawSettings defaultServerSettings; }; @@ -182,6 +177,8 @@ struct ServerDeleter } }; +bool clearTextHTTP2 = false; + using ServerPtr = QScopedPointer<Http2Server, ServerDeleter>; H2Type defaultConnectionType() @@ -194,6 +191,12 @@ H2Type defaultConnectionType() tst_Http2::tst_Http2() : workerThread(new QThread) { +#if QT_CONFIG(ssl) + const auto features = QSslSocket::supportedFeatures(); + clearTextHTTP2 = !features.contains(QSsl::SupportedFeature::ServerSideAlpn); +#else + clearTextHTTP2 = true; +#endif workerThread->start(); } @@ -215,6 +218,12 @@ void tst_Http2::init() manager.reset(new QNetworkAccessManager); } +void tst_Http2::defaultQnamHttp2Configuration() +{ + // The configuration we also implicitly use in QNAM. + QCOMPARE(qt_defaultH2Configuration(), QNetworkRequest().http2Configuration()); +} + void tst_Http2::singleRequest_data() { QTest::addColumn<QNetworkRequest::Attribute>("h2Attribute"); @@ -245,7 +254,7 @@ void tst_Http2::singleRequest() // we have to use TLS sockets (== private key) and thus suppress a // keychain UI asking for permission to use a private key. // Our CI has this, but somebody testing locally - will have a problem. - qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); auto envRollback = qScopeGuard([](){ qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); }); @@ -266,10 +275,15 @@ void tst_Http2::singleRequest() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QFETCH(const QNetworkRequest::Attribute, h2Attribute); request.setAttribute(h2Attribute, QVariant(true)); auto reply = manager->get(request); +#if QT_CONFIG(ssl) + QSignalSpy encSpy(reply, &QNetworkReply::encrypted); +#endif // QT_CONFIG(ssl) + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); // Since we're using self-signed certificates, // ignore SSL errors: @@ -278,12 +292,81 @@ void tst_Http2::singleRequest() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); QCOMPARE(reply->error(), QNetworkReply::NoError); QVERIFY(reply->isFinished()); + +#if QT_CONFIG(ssl) + if (connectionType == H2Type::h2Alpn || connectionType == H2Type::h2Direct) + QCOMPARE(encSpy.size(), 1); +#endif // QT_CONFIG(ssl) +} + +void tst_Http2::informationalRequest_data() +{ + QTest::addColumn<int>("statusCode"); + + // 'Clear text' that should always work, either via the protocol upgrade + // or as direct. + QTest::addRow("statusCode-100") << 100; + QTest::addRow("statusCode-125") << 125; + QTest::addRow("statusCode-150") << 150; + QTest::addRow("statusCode-175") << 175; +} + +void tst_Http2::informationalRequest() +{ + clearHTTP2State(); + + serverPort = 0; + nRequests = 1; + + ServerPtr srv(newServer(defaultServerSettings, defaultConnectionType())); + + QFETCH(const int, statusCode); + srv->setInformationalStatusCode(statusCode); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + auto url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + auto reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(reply->isFinished()); + + const QVariant code(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)); + + // We are discarding informational headers if the status code is in the range of + // 102-199 or if it is 100. As these header fields were part of the informational + // header used for this test case, we should not see them at this point and the + // status code should be 200. + + QCOMPARE(code.value<int>(), 200); + QVERIFY(!reply->hasRawHeader("a_random_header_field")); + QVERIFY(!reply->hasRawHeader("another_random_header_field")); } void tst_Http2::multipleRequests() @@ -314,7 +397,7 @@ void tst_Http2::multipleRequests() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -359,7 +442,7 @@ void tst_Http2::flowControlClientSide() runEventLoop(120000); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); QVERIFY(windowUpdates > 0); @@ -375,7 +458,7 @@ void tst_Http2::flowControlServerSide() // to let all replies finish without any error. using namespace Http2; - if (EmulationDetector::isRunningArmOnX86()) + if (QTestPrivate::isRunningArmOnX86()) QSKIP("Test is too slow to run on emulator"); clearHTTP2State(); @@ -400,7 +483,7 @@ void tst_Http2::flowControlServerSide() runEventLoop(120000); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -432,6 +515,7 @@ void tst_Http2::pushPromise() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); request.setHttp2Configuration(params); @@ -443,7 +527,7 @@ void tst_Http2::pushPromise() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -458,6 +542,7 @@ void tst_Http2::pushPromise() url.setPath("/script.js"); QNetworkRequest promisedRequest(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); promisedRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); reply = manager->get(promisedRequest); connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); @@ -512,6 +597,7 @@ void tst_Http2::goaway() for (int i = 0; i < nRequests; ++i) { url.setPath(QString("/%1").arg(i)); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); replies[i] = manager->get(request); QCOMPARE(replies[i]->error(), QNetworkReply::NoError); @@ -567,7 +653,7 @@ void tst_Http2::earlyResponse() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } @@ -624,7 +710,7 @@ void tst_Http2::connectToHost() // we have to use TLS sockets (== private key) and thus suppress a // keychain UI asking for permission to use a private key. // Our CI has this, but somebody testing locally - will have a problem. - qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); auto envRollback = qScopeGuard([](){ qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); }); @@ -653,6 +739,7 @@ void tst_Http2::connectToHost() auto copyUrl = url; copyUrl.setScheme(QLatin1String("preconnect-https")); QNetworkRequest request(copyUrl); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, true); reply = manager->get(request); // Since we're using self-signed certificates, ignore SSL errors: @@ -665,6 +752,7 @@ void tst_Http2::connectToHost() auto copyUrl = url; copyUrl.setScheme(QLatin1String("preconnect-http")); QNetworkRequest request(copyUrl); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, true); reply = manager->get(request); } @@ -685,6 +773,7 @@ void tst_Http2::connectToHost() QCOMPARE(nRequests, 1); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, QVariant(true)); reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); @@ -695,7 +784,7 @@ void tst_Http2::connectToHost() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -719,7 +808,7 @@ void tst_Http2::maxFrameSize() // we have to use TLS sockets (== private key) and thus suppress a // keychain UI asking for permission to use a private key. // Our CI has this, but somebody testing locally - will have a problem. - qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); auto envRollback = qScopeGuard([](){ qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); }); @@ -750,6 +839,7 @@ void tst_Http2::maxFrameSize() url.setPath(QString("/stream1.html")); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(attribute, QVariant(true)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); request.setHttp2Configuration(h2Config); @@ -763,13 +853,168 @@ void tst_Http2::maxFrameSize() // Normally, with a 16kb limit, our server would split such // a response into 3 'DATA' frames (16kb + 16kb + 0|END_STREAM). - QCOMPARE(frameCounter.count(), 1); + QCOMPARE(frameCounter.size(), 1); - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); } +void tst_Http2::http2DATAFrames() +{ + using namespace Http2; + + { + // 0. DATA frame with payload, no padding. + + FrameWriter writer(FrameType::DATA, FrameFlag::EMPTY, 1); + writer.append('a'); + writer.append('b'); + writer.append('c'); + + const Frame frame = writer.outboundFrame(); + const auto &buffer = frame.buffer; + // Frame's header is 9 bytes + 3 bytes of payload + // (+ 0 bytes of padding and no padding length): + QCOMPARE(int(buffer.size()), 12); + + QVERIFY(!frame.padding()); + QCOMPARE(int(frame.payloadSize()), 3); + QCOMPARE(int(frame.dataSize()), 3); + QCOMPARE(frame.dataBegin() - buffer.data(), 9); + QCOMPARE(char(*frame.dataBegin()), 'a'); + } + + { + // 1. DATA with padding. + + const int padLength = 10; + FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1); + writer.append(uchar(padLength)); // The length of padding is 1 byte long. + writer.append('a'); + for (int i = 0; i < padLength; ++i) + writer.append('b'); + + const Frame frame = writer.outboundFrame(); + const auto &buffer = frame.buffer; + // Frame's header is 9 bytes + 1 byte for padding length + // + 1 byte of data + 10 bytes of padding: + QCOMPARE(int(buffer.size()), 21); + + QCOMPARE(frame.padding(), padLength); + QCOMPARE(int(frame.payloadSize()), 12); // Includes padding, its length + data. + QCOMPARE(int(frame.dataSize()), 1); + + // Skipping 9 bytes long header and padding length: + QCOMPARE(frame.dataBegin() - buffer.data(), 10); + + QCOMPARE(char(frame.dataBegin()[0]), 'a'); + QCOMPARE(char(frame.dataBegin()[1]), 'b'); + + QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM)); + QVERIFY(frame.flags().testFlag(FrameFlag::PADDED)); + } + { + // 2. DATA with PADDED flag, but 0 as padding length. + + FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1); + + writer.append(uchar(0)); // Number of padding bytes is 1 byte long. + writer.append('a'); + + const Frame frame = writer.outboundFrame(); + const auto &buffer = frame.buffer; + + // Frame's header is 9 bytes + 1 byte for padding length + 1 byte of data + // + 0 bytes of padding: + QCOMPARE(int(buffer.size()), 11); + + QCOMPARE(frame.padding(), 0); + QCOMPARE(int(frame.payloadSize()), 2); // Includes padding (0 bytes), its length + data. + QCOMPARE(int(frame.dataSize()), 1); + + // Skipping 9 bytes long header and padding length: + QCOMPARE(frame.dataBegin() - buffer.data(), 10); + + QCOMPARE(char(*frame.dataBegin()), 'a'); + + QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM)); + QVERIFY(frame.flags().testFlag(FrameFlag::PADDED)); + } +} + +void tst_Http2::moreActivitySignals_data() +{ + QTest::addColumn<QNetworkRequest::Attribute>("h2Attribute"); + QTest::addColumn<H2Type>("connectionType"); + + QTest::addRow("h2c-upgrade") + << QNetworkRequest::Http2AllowedAttribute << H2Type::h2c; + QTest::addRow("h2c-direct") + << QNetworkRequest::Http2DirectAttribute << H2Type::h2cDirect; + + if (!clearTextHTTP2) + QTest::addRow("h2-ALPN") + << QNetworkRequest::Http2AllowedAttribute << H2Type::h2Alpn; + +#if QT_CONFIG(ssl) + QTest::addRow("h2-direct") + << QNetworkRequest::Http2DirectAttribute << H2Type::h2Direct; +#endif +} + +void tst_Http2::moreActivitySignals() +{ + clearHTTP2State(); + +#if QT_CONFIG(securetransport) + // Normally on macOS we use plain text only for SecureTransport + // does not support ALPN on the server side. With 'direct encrytped' + // we have to use TLS sockets (== private key) and thus suppress a + // keychain UI asking for permission to use a private key. + // Our CI has this, but somebody testing locally - will have a problem. + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); + auto envRollback = qScopeGuard([]() { qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); }); +#endif + + serverPort = 0; + QFETCH(H2Type, connectionType); + ServerPtr srv(newServer(defaultServerSettings, connectionType)); + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(100); + QVERIFY(serverPort != 0); + auto url = requestUrl(connectionType); + url.setPath(QString("/stream1.html")); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + QFETCH(const QNetworkRequest::Attribute, h2Attribute); + request.setAttribute(h2Attribute, QVariant(true)); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + QSharedPointer<QNetworkReply> reply(manager->get(request)); + nRequests = 1; + connect(reply.data(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + QSignalSpy spy1(reply.data(), SIGNAL(socketStartedConnecting())); + QSignalSpy spy2(reply.data(), SIGNAL(requestSent())); + QSignalSpy spy3(reply.data(), SIGNAL(metaDataChanged())); + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + spy1.wait(); + spy2.wait(); + spy3.wait(); + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + + QVERIFY(reply->error() == QNetworkReply::NoError); + QVERIFY(reply->isFinished()); +} + void tst_Http2::contentEncoding_data() { QTest::addColumn<QByteArray>("encoding"); @@ -839,7 +1084,7 @@ void tst_Http2::contentEncoding() // we have to use TLS sockets (== private key) and thus suppress a // keychain UI asking for permission to use a private key. // Our CI has this, but somebody testing locally - will have a problem. - qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); auto envRollback = qScopeGuard([]() { qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); }); #endif @@ -862,6 +1107,7 @@ void tst_Http2::contentEncoding() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QFETCH(const QNetworkRequest::Attribute, h2Attribute); request.setAttribute(h2Attribute, QVariant(true)); @@ -874,7 +1120,7 @@ void tst_Http2::contentEncoding() runEventLoop(); STOP_ON_FAILURE - QVERIFY(nRequests == 0); + QCOMPARE(nRequests, 0); QVERIFY(prefaceOK); QVERIFY(serverGotSettingsACK); @@ -883,6 +1129,422 @@ void tst_Http2::contentEncoding() QTEST(reply->readAll(), "expected"); } +void tst_Http2::authenticationRequired_data() +{ + QTest::addColumn<bool>("success"); + QTest::addColumn<bool>("responseHEADOnly"); + QTest::addColumn<bool>("withChallenge"); + + QTest::addRow("failed-auth") << false << true << true; + QTest::addRow("successful-auth") << true << true << true; + // Include a DATA frame in the response from the remote server. An example would be receiving a + // JSON response on a request along with the 401 error. + QTest::addRow("failed-auth-with-response") << false << false << true; + QTest::addRow("successful-auth-with-response") << true << false << true; + + // Don't provide a challenge header. This is valid if you are actually just + // denied access for whatever reason. + QTest::addRow("no-challenge") << false << false << false; +} + +void tst_Http2::authenticationRequired() +{ + clearHTTP2State(); + serverPort = 0; + QFETCH(const bool, responseHEADOnly); + POSTResponseHEADOnly = responseHEADOnly; + + QFETCH(const bool, success); + QFETCH(const bool, withChallenge); + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + QByteArray responseBody = "Hello"_ba; + targetServer->setResponseBody(responseBody); + if (withChallenge) + targetServer->setAuthenticationHeader("Basic realm=\"Shadow\""); + else + targetServer->setAuthenticationRequired(true); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + auto url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + QByteArray expectedBody = "Hello, World!"; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->post(request, expectedBody)); + + bool authenticationRequested = false; + connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(), + [&](QNetworkReply *, QAuthenticator *auth) { + authenticationRequested = true; + if (success) { + auth->setUser("admin"); + auth->setPassword("admin"); + } + }); + + QByteArray receivedBody; + connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(), + [&receivedBody](quint32 streamID, const QByteArray &body) { + if (streamID == 3) // The expected body is on the retry, so streamID == 3 + receivedBody += body; + }); + + if (success) { + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + } else { + // Use queued connection so that the finished signal can be emitted and the isFinished + // property can be set. + connect(reply.get(), &QNetworkReply::errorOccurred, this, + &tst_Http2::replyFinishedWithError, Qt::QueuedConnection); + } + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + QVERIFY2(reply->isFinished(), + "The reply should error out if authentication fails, or finish if it succeeds"); + + if (!success) + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); + // else: no error (is checked in tst_Http2::replyFinished) + + QVERIFY(authenticationRequested || !withChallenge); + + const auto isAuthenticated = [](const QByteArray &bv) { + return bv == "Basic YWRtaW46YWRtaW4="; // admin:admin + }; + // Get the "authorization" header out from the server and make sure it's as expected: + auto reqAuthHeader = targetServer->requestAuthorizationHeader(); + QCOMPARE(isAuthenticated(reqAuthHeader), success); + if (success) + QCOMPARE(receivedBody, expectedBody); + if (responseHEADOnly) { + const QVariant contentLenHeader = reply->header(QNetworkRequest::ContentLengthHeader); + QVERIFY2(!contentLenHeader.isValid(), "We expect no DATA frames to be received"); + QCOMPARE(reply->readAll(), QByteArray()); + } else { + const qint32 contentLen = reply->header(QNetworkRequest::ContentLengthHeader).toInt(); + QCOMPARE(contentLen, responseBody.length()); + QCOMPARE(reply->bytesAvailable(), responseBody.length()); + QCOMPARE(reply->readAll(), QByteArray("Hello")); + } + // In the `!success` case we need to wait for the server to emit this or it might cause issues + // in the next test running after this. In the `success` case we anyway expect it to have been + // received. + QTRY_VERIFY(serverGotSettingsACK); +} + +void tst_Http2::unsupportedAuthenticateChallenge() +{ + clearHTTP2State(); + serverPort = 0; + + if (defaultConnectionType() == H2Type::h2c) + QSKIP("This test requires TLS with ALPN to work"); + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + QByteArray responseBody = "Hello"_ba; + targetServer->setResponseBody(responseBody); + targetServer->setAuthenticationHeader("Bearer realm=\"qt.io accounts\""); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + QUrl url = requestUrl(defaultConnectionType()); + url.setPath("/index.html"); + QNetworkRequest request(url); + + QByteArray expectedBody = "Hello, World!"; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->post(request, expectedBody)); + + bool authenticationRequested = false; + connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(), + [&](QNetworkReply *, QAuthenticator *) { + authenticationRequested = true; + }); + + bool finishedReceived = false; + connect(reply.get(), &QNetworkReply::finished, reply.get(), + [&]() { finishedReceived = true; }); + bool errorReceived = false; + connect(reply.get(), &QNetworkReply::errorOccurred, reply.get(), + [&]() { errorReceived = true; }); + + QSet<quint32> receivedDataOnStreams; + connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(), + [&receivedDataOnStreams](quint32 streamID, const QByteArray &body) { + Q_UNUSED(body); + receivedDataOnStreams.insert(streamID); + }); + + // Use queued connection so that the finished signal can be emitted and the + // isFinished property can be set. + connect(reply.get(), &QNetworkReply::errorOccurred, this, + &tst_Http2::replyFinishedWithError, Qt::QueuedConnection); + + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + QVERIFY2(reply->isFinished(), + "The reply should error out if authentication fails, or finish if it succeeds"); + + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); + QVERIFY(reply->isFinished()); + QVERIFY(errorReceived); + QVERIFY(finishedReceived); + QCOMPARE(receivedDataOnStreams.size(), 1); + QVERIFY(receivedDataOnStreams.contains(1)); // the original, failed, request + + QVERIFY(!authenticationRequested); + + // We should not have sent any authentication headers to the server, since + // we don't support the challenge. + const QByteArray reqAuthHeader = targetServer->requestAuthorizationHeader(); + QVERIFY(reqAuthHeader.isEmpty()); + + // In the `!success` case we need to wait for the server to emit this or it might cause issues + // in the next test running after this. In the `success` case we anyway expect it to have been + // received. + QTRY_VERIFY(serverGotSettingsACK); + +} + +void tst_Http2::h2cAllowedAttribute_data() +{ + QTest::addColumn<bool>("h2cAllowed"); + QTest::addColumn<bool>("useAttribute"); // true: use attribute, false: use environment variable + QTest::addColumn<bool>("success"); + + QTest::addRow("h2c-not-allowed") << false << false << false; + // Use the attribute to enable/disable the H2C: + QTest::addRow("attribute") << true << true << true; + // Use the QT_NETWORK_H2C_ALLOWED environment variable to enable/disable the H2C: + QTest::addRow("environment-variable") << true << false << true; +} + +void tst_Http2::h2cAllowedAttribute() +{ + QFETCH(const bool, h2cAllowed); + QFETCH(const bool, useAttribute); + QFETCH(const bool, success); + + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, H2Type::h2c)); + targetServer->setResponseBody("Hello"); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + auto url = requestUrl(H2Type::h2c); + url.setPath("/index.html"); + QNetworkRequest request(url); + if (h2cAllowed) { + if (useAttribute) + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + else + qputenv("QT_NETWORK_H2C_ALLOWED", "1"); + } + auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); }); + + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->get(request)); + + if (success) + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + else + connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError); + + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + if (!success) { + QCOMPARE(reply->error(), QNetworkReply::ConnectionRefusedError); + } else { + QCOMPARE(reply->readAll(), QByteArray("Hello")); + QTRY_VERIFY(serverGotSettingsACK); + } +} + +void tst_Http2::redirect_data() +{ + QTest::addColumn<int>("maxRedirects"); + QTest::addColumn<int>("redirectCount"); + QTest::addColumn<bool>("success"); + + QTest::addRow("1-redirects-none-allowed-failure") << 0 << 1 << false; + QTest::addRow("1-redirects-success") << 1 << 1 << true; + QTest::addRow("2-redirects-1-allowed-failure") << 1 << 2 << false; +} + +void tst_Http2::redirect() +{ + QFETCH(const int, maxRedirects); + QFETCH(const int, redirectCount); + QFETCH(const bool, success); + const QByteArray redirectUrl = "/b.html"_ba; + + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + targetServer->setRedirect(redirectUrl, redirectCount); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + auto originalUrl = requestUrl(defaultConnectionType()); + auto url = originalUrl; + url.setPath("/index.html"); + QNetworkRequest request(url); + request.setMaximumRedirectsAllowed(maxRedirects); + // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN + qputenv("QT_NETWORK_H2C_ALLOWED", "1"); + auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); }); + + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->get(request)); + + if (success) { + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + } else { + connect(reply.get(), &QNetworkReply::errorOccurred, this, + &tst_Http2::replyFinishedWithError); + } + + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + if (success) { + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->url().toString(), + originalUrl.resolved(QString::fromLatin1(redirectUrl)).toString()); + } else if (maxRedirects < redirectCount) { + QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError); + } + QTRY_VERIFY(serverGotSettingsACK); +} + +void tst_Http2::trailingHEADERS() +{ + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + targetServer->setSendTrailingHEADERS(true); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + const auto url = requestUrl(defaultConnectionType()); + QNetworkRequest request(url); + // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + std::unique_ptr<QNetworkReply> reply{ manager->get(request) }; + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QTRY_VERIFY(serverGotSettingsACK); +} + +void tst_Http2::duplicateRequestsWithAborts() +{ + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType())); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + constexpr int ExpectedSuccessfulRequests = 1; + nRequests = ExpectedSuccessfulRequests; + + const auto url = requestUrl(defaultConnectionType()); + QNetworkRequest request(url); + // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + + qint32 finishedCount = 0; + auto connectToSlots = [this, &finishedCount](QNetworkReply *reply){ + const auto onFinished = [&finishedCount, reply, this]() { + ++finishedCount; + if (reply->error() == QNetworkReply::NoError) + replyFinished(); + }; + connect(reply, &QNetworkReply::finished, reply, onFinished); + }; + + std::vector<QNetworkReply *> replies; + for (qint32 i = 0; i < 3; ++i) { + auto &reply = replies.emplace_back(manager->get(request)); + connectToSlots(reply); + if (i < 2) // Delete and abort all-but-one: + reply->deleteLater(); + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + } + + runEventLoop(); + STOP_ON_FAILURE + + QCOMPARE(nRequests, 0); + QCOMPARE(finishedCount, ExpectedSuccessfulRequests); +} + void tst_Http2::serverStarted(quint16 port) { serverPort = port; @@ -894,6 +1556,7 @@ void tst_Http2::clearHTTP2State() windowUpdates = 0; prefaceOK = false; serverGotSettingsACK = false; + POSTResponseHEADOnly = true; } void tst_Http2::runEventLoop(int ms) @@ -939,6 +1602,7 @@ void tst_Http2::sendRequest(int streamNumber, url.setPath(QString("/stream%1.html").arg(streamNumber)); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); @@ -1026,7 +1690,7 @@ void tst_Http2::receivedData(quint32 streamID) Q_ASSERT(srv); QMetaObject::invokeMethod(srv, "sendResponse", Qt::QueuedConnection, Q_ARG(quint32, streamID), - Q_ARG(bool, true /*HEADERS only*/)); + Q_ARG(bool, POSTResponseHEADOnly /*true = HEADERS only*/)); } void tst_Http2::windowUpdated(quint32 streamID) diff --git a/tests/auto/network/access/qabstractnetworkcache/.prev_CMakeLists.txt b/tests/auto/network/access/qabstractnetworkcache/.prev_CMakeLists.txt deleted file mode 100644 index bf163c3883..0000000000 --- a/tests/auto/network/access/qabstractnetworkcache/.prev_CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Generated from qabstractnetworkcache.pro. - -##################################################################### -## tst_qabstractnetworkcache Test: -##################################################################### - -# Collect test data -file(GLOB_RECURSE test_data_glob - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - tests/*) -list(APPEND test_data ${test_data_glob}) - -qt_internal_add_test(tst_qabstractnetworkcache - SOURCES - tst_qabstractnetworkcache.cpp - PUBLIC_LIBRARIES - Qt::Network - TESTDATA ${test_data} -) - -#### Keys ignored in scope 1:.:.:qabstractnetworkcache.pro:<TRUE>: -# QT_TEST_SERVER_LIST = "apache2" diff --git a/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt b/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt index 3958916232..37c3dbda8a 100644 --- a/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt +++ b/tests/auto/network/access/qabstractnetworkcache/CMakeLists.txt @@ -1,9 +1,16 @@ -# Generated from qabstractnetworkcache.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qabstractnetworkcache Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qabstractnetworkcache LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -13,11 +20,10 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qabstractnetworkcache SOURCES tst_qabstractnetworkcache.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network TESTDATA ${test_data} - QT_TEST_SERVER_LIST "apache2" # special case + QT_TEST_SERVER_LIST "apache2" ) -#### Keys ignored in scope 1:.:.:qabstractnetworkcache.pro:<TRUE>: # QT_TEST_SERVER_LIST = "apache2" diff --git a/tests/auto/network/access/qabstractnetworkcache/qabstractnetworkcache.pro b/tests/auto/network/access/qabstractnetworkcache/qabstractnetworkcache.pro deleted file mode 100644 index c722100ead..0000000000 --- a/tests/auto/network/access/qabstractnetworkcache/qabstractnetworkcache.pro +++ /dev/null @@ -1,9 +0,0 @@ -CONFIG += testcase -TARGET = tst_qabstractnetworkcache -QT = core network testlib -SOURCES += tst_qabstractnetworkcache.cpp - -TESTDATA += tests/* - -CONFIG += unsupported/testserver -QT_TEST_SERVER_LIST = apache2 diff --git a/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp b/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp index 84a9787493..95f067a66e 100644 --- a/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp +++ b/tests/auto/network/access/qabstractnetworkcache/tst_qabstractnetworkcache.cpp @@ -1,34 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTemporaryDir> -#include <QtTest/QtTest> +#include <QTest> #include <QtNetwork/QtNetwork> +#include <QSignalSpy> + #include "../../../network-settings.h" #include <algorithm> @@ -73,6 +50,7 @@ private: }; +#if QT_CONFIG(networkdiskcache) class NetworkDiskCache : public QNetworkDiskCache { Q_OBJECT @@ -95,6 +73,7 @@ public: QTemporaryDir tempDir; bool gotData; }; +#endif tst_QAbstractNetworkCache::tst_QAbstractNetworkCache() @@ -277,10 +256,12 @@ void tst_QAbstractNetworkCache::runTest() QFETCH(bool, fetchFromCache); QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); QCOMPARE(diskCache->gotData, false); +#endif QUrl realUrl = url.contains("://") ? url : TESTFILE + url; QNetworkRequest request(realUrl); @@ -288,8 +269,10 @@ void tst_QAbstractNetworkCache::runTest() // prime the cache QNetworkReply *reply = manager.get(request); QSignalSpy downloaded1(reply, SIGNAL(finished())); - QTRY_COMPARE(downloaded1.count(), 1); + QTRY_COMPARE(downloaded1.size(), 1); +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, false); +#endif QByteArray goodData = reply->readAll(); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl); @@ -297,7 +280,7 @@ void tst_QAbstractNetworkCache::runTest() // should be in the cache now QNetworkReply *reply2 = manager.get(request); QSignalSpy downloaded2(reply2, SIGNAL(finished())); - QTRY_COMPARE(downloaded2.count(), 1); + QTRY_COMPARE(downloaded2.size(), 1); QByteArray secondData = reply2->readAll(); if (!fetchFromCache && cacheLoadControl == QNetworkRequest::AlwaysCache) { @@ -316,7 +299,9 @@ void tst_QAbstractNetworkCache::runTest() std::sort(rawHeaderList.begin(), rawHeaderList.end()); std::sort(rawHeaderList2.begin(), rawHeaderList2.end()); } +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, fetchFromCache); +#endif } void tst_QAbstractNetworkCache::checkSynchronous() @@ -328,10 +313,12 @@ void tst_QAbstractNetworkCache::checkSynchronous() QFETCH(bool, fetchFromCache); QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); QCOMPARE(diskCache->gotData, false); +#endif QUrl realUrl = url.contains("://") ? url : TESTFILE + url; QNetworkRequest request(realUrl); @@ -343,7 +330,9 @@ void tst_QAbstractNetworkCache::checkSynchronous() // prime the cache QNetworkReply *reply = manager.get(request); QVERIFY(reply->isFinished()); // synchronous +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, false); +#endif QByteArray goodData = reply->readAll(); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl); @@ -371,22 +360,26 @@ void tst_QAbstractNetworkCache::checkSynchronous() std::sort(rawHeaderList.begin(), rawHeaderList.end()); std::sort(rawHeaderList2.begin(), rawHeaderList2.end()); } +#if QT_CONFIG(networkdiskcache) QCOMPARE(diskCache->gotData, fetchFromCache); +#endif } void tst_QAbstractNetworkCache::deleteCache() { QNetworkAccessManager manager; +#if QT_CONFIG(networkdiskcache) NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString())); manager.setCache(diskCache); +#endif QString url = "httpcachetest_cachecontrol.cgi?max-age=1000"; QNetworkRequest request(QUrl(TESTFILE + url)); QNetworkReply *reply = manager.get(request); QSignalSpy downloaded1(reply, SIGNAL(finished())); manager.setCache(0); - QTRY_COMPARE(downloaded1.count(), 1); + QTRY_COMPARE(downloaded1.size(), 1); } diff --git a/tests/auto/network/access/qdecompresshelper/.prev_CMakeLists.txt b/tests/auto/network/access/qdecompresshelper/.prev_CMakeLists.txt deleted file mode 100644 index 8f01ee712d..0000000000 --- a/tests/auto/network/access/qdecompresshelper/.prev_CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Generated from qdecompresshelper.pro. - -##################################################################### -## tst_qdecompresshelper Test: -##################################################################### - -qt_internal_add_test(tst_qdecompresshelper - SOURCES - gzip.rcc.cpp - inflate.rcc.cpp - tst_qdecompresshelper.cpp - zstandard.rcc.cpp - DEFINES - SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}" - PUBLIC_LIBRARIES - Qt::NetworkPrivate -) - -#### Keys ignored in scope 1:.:.:qdecompresshelper.pro:<TRUE>: -# TEMPLATE = "app" diff --git a/tests/auto/network/access/qdecompresshelper/10K.gz b/tests/auto/network/access/qdecompresshelper/10K.gz Binary files differnew file mode 100644 index 0000000000..c5c4959763 --- /dev/null +++ b/tests/auto/network/access/qdecompresshelper/10K.gz diff --git a/tests/auto/network/access/qdecompresshelper/BLACKLIST b/tests/auto/network/access/qdecompresshelper/BLACKLIST new file mode 100644 index 0000000000..d189fd9e00 --- /dev/null +++ b/tests/auto/network/access/qdecompresshelper/BLACKLIST @@ -0,0 +1,2 @@ +[bigZlib] +macos arm diff --git a/tests/auto/network/access/qdecompresshelper/CMakeLists.txt b/tests/auto/network/access/qdecompresshelper/CMakeLists.txt index 49fd91db0a..09317ca3eb 100644 --- a/tests/auto/network/access/qdecompresshelper/CMakeLists.txt +++ b/tests/auto/network/access/qdecompresshelper/CMakeLists.txt @@ -1,9 +1,16 @@ -# Generated from qdecompresshelper.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qdecompresshelper Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdecompresshelper LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdecompresshelper SOURCES gzip.rcc.cpp @@ -11,10 +18,7 @@ qt_internal_add_test(tst_qdecompresshelper tst_qdecompresshelper.cpp zstandard.rcc.cpp DEFINES - SRC_DIR=${CMAKE_CURRENT_SOURCE_DIR} # special case - PUBLIC_LIBRARIES + SRC_DIR=${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES Qt::NetworkPrivate ) - -#### Keys ignored in scope 1:.:.:qdecompresshelper.pro:<TRUE>: -# TEMPLATE = "app" diff --git a/tests/auto/network/access/qdecompresshelper/qdecompresshelper.pro b/tests/auto/network/access/qdecompresshelper/qdecompresshelper.pro deleted file mode 100644 index 254f04a707..0000000000 --- a/tests/auto/network/access/qdecompresshelper/qdecompresshelper.pro +++ /dev/null @@ -1,12 +0,0 @@ -QT = network-private testlib -CONFIG += testcase parallel_test -TEMPLATE = app -TARGET = tst_qdecompresshelper - -SOURCES += \ - tst_qdecompresshelper.cpp \ - gzip.rcc.cpp \ - inflate.rcc.cpp \ - zstandard.rcc.cpp \ - -DEFINES += SRC_DIR="$$PWD" diff --git a/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp b/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp index 23040b7624..cd5a52c209 100644 --- a/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp +++ b/tests/auto/network/access/qdecompresshelper/tst_qdecompresshelper.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtNetwork/private/qdecompresshelper_p.h> @@ -64,9 +39,10 @@ private Q_SLOTS: void decompressBigData_data(); void decompressBigData(); -#if QT_POINTER_SIZE >= 8 + void archiveBomb_data(); + void archiveBomb(); + void bigZlib(); -#endif }; void tst_QDecompressHelper::initTestCase() @@ -343,23 +319,30 @@ void tst_QDecompressHelper::countAheadPartialRead() void tst_QDecompressHelper::decompressBigData_data() { +#if defined(QT_ASAN_ENABLED) + QSKIP("Tests are too slow with asan enabled"); +#endif QTest::addColumn<QByteArray>("encoding"); QTest::addColumn<QString>("path"); QTest::addColumn<qint64>("size"); + QTest::addColumn<bool>("countAhead"); qint64 fourGiB = 4ll * 1024ll * 1024ll * 1024ll; qint64 fiveGiB = 5ll * 1024ll * 1024ll * 1024ll; - QTest::newRow("gzip-4G") << QByteArray("gzip") << QString(":/4G.gz") << fourGiB; + // Only use countAhead on one of these since they share codepath anyway + QTest::newRow("gzip-counted-4G") << QByteArray("gzip") << QString(":/4G.gz") << fourGiB << true; QTest::newRow("deflate-5G") << QByteArray("deflate") << QString(":/5GiB.txt.inflate") - << fiveGiB; + << fiveGiB << false; #if QT_CONFIG(brotli) - QTest::newRow("brotli-4G") << QByteArray("br") << (srcDir + "/4G.br") << fourGiB; + QTest::newRow("brotli-4G") << QByteArray("br") << (srcDir + "/4G.br") << fourGiB << false; + QTest::newRow("brotli-counted-4G") << QByteArray("br") << (srcDir + "/4G.br") << fourGiB << true; #endif #if QT_CONFIG(zstd) - QTest::newRow("zstandard-4G") << QByteArray("zstd") << (":/4G.zst") << fourGiB; + QTest::newRow("zstandard-4G") << QByteArray("zstd") << (":/4G.zst") << fourGiB << false; + QTest::newRow("zstandard-counted-4G") << QByteArray("zstd") << (":/4G.zst") << fourGiB << true; #endif } @@ -372,16 +355,20 @@ void tst_QDecompressHelper::decompressBigData() const qint64 third = file.bytesAvailable() / 3; QDecompressHelper helper; - helper.setArchiveBombDetectionEnabled(false); + QFETCH(bool, countAhead); + helper.setCountingBytesEnabled(countAhead); + helper.setDecompressedSafetyCheckThreshold(-1); QFETCH(QByteArray, encoding); helper.setEncoding(encoding); - QByteArray output(32 * 1024, Qt::Uninitialized); + // The size of 'output' should be at least QDecompressHelper::MaxDecompressedDataBufferSize + 1 + QByteArray output(10 * 1024 * 1024 + 1, Qt::Uninitialized); qint64 totalSize = 0; while (!file.atEnd()) { helper.feed(file.read(third)); while (helper.hasData()) { qsizetype bytesRead = helper.read(output.data(), output.size()); + QVERIFY(bytesRead >= 0); QVERIFY(bytesRead <= output.size()); totalSize += bytesRead; const auto isZero = [](char c) { return c == '\0'; }; @@ -392,9 +379,56 @@ void tst_QDecompressHelper::decompressBigData() QTEST(totalSize, "size"); } -#if QT_POINTER_SIZE >= 8 +void tst_QDecompressHelper::archiveBomb_data() +{ + QTest::addColumn<QByteArray>("encoding"); + QTest::addColumn<QString>("path"); + QTest::addColumn<bool>("shouldFail"); + + QTest::newRow("gzip-10K") << QByteArray("gzip") << (srcDir + "/10K.gz") << false; + QTest::newRow("gzip-4G") << QByteArray("gzip") << QString(":/4G.gz") << true; +} + +void tst_QDecompressHelper::archiveBomb() +{ + QFETCH(bool, shouldFail); + QFETCH(QString, path); + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + + QDecompressHelper helper; + QFETCH(QByteArray, encoding); + helper.setEncoding(encoding); + QVERIFY(helper.isValid()); + + constexpr qint64 SafeSizeLimit = 10 * 1024 * 1024; + constexpr qint64 RatioLimit = 40; + qint64 bytesToRead = std::min(SafeSizeLimit / RatioLimit, file.bytesAvailable()); + QByteArray output(1 + bytesToRead * RatioLimit, Qt::Uninitialized); + helper.feed(file.read(bytesToRead)); + qsizetype bytesRead = helper.read(output.data(), output.size()); + QVERIFY(bytesRead <= output.size()); + QVERIFY(helper.isValid()); + + if (shouldFail) { + QCOMPARE(bytesRead, -1); + QVERIFY(!helper.errorString().isEmpty()); + } else { + QVERIFY(bytesRead > 0); + QVERIFY(helper.errorString().isEmpty()); + } +} + void tst_QDecompressHelper::bigZlib() { +#if QT_POINTER_SIZE < 8 + QSKIP("This cannot be tested on 32-bit systems"); +#elif defined(QT_ASAN_ENABLED) + QSKIP("Test is too slow with asan enabled"); +#else +# ifndef QT_NO_EXCEPTIONS + try { +# endif // ZLib uses unsigned integers as their size type internally which creates some special // cases in the internal code that should be tested! QFile file(":/5GiB.txt.inflate"); @@ -402,20 +436,25 @@ void tst_QDecompressHelper::bigZlib() QByteArray compressedData = file.readAll(); QDecompressHelper helper; - helper.setArchiveBombDetectionEnabled(false); + helper.setDecompressedSafetyCheckThreshold(-1); helper.setEncoding("deflate"); auto firstHalf = compressedData.left(compressedData.size() - 2); helper.feed(firstHalf); helper.feed(compressedData.mid(firstHalf.size())); // We need the whole thing in one go... which is why this test is not available for 32-bit - qint64 expected = 5ll * 1024ll * 1024ll * 1024ll; - // This can be replaced with QByteArray after the qsizetype change is merged - std::unique_ptr<char[]> output(new char[expected]); - qsizetype size = helper.read(output.get(), expected); + const qint64 expected = 5ll * 1024ll * 1024ll * 1024ll; + // Request a few more byte than what is available, to verify exact size + QByteArray output(expected + 42, Qt::Uninitialized); + const qsizetype size = helper.read(output.data(), output.size()); QCOMPARE(size, expected); -} +# ifndef QT_NO_EXCEPTIONS + } catch (const std::bad_alloc &) { + QSKIP("Encountered most likely OOM."); + } +# endif #endif +} QTEST_MAIN(tst_QDecompressHelper) diff --git a/tests/auto/network/access/qformdatabuilder/CMakeLists.txt b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt new file mode 100644 index 0000000000..dde2dc10e0 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 + SOURCES + tst_qformdatabuilder.cpp + LIBRARIES + Qt::Core + Qt::Network + TESTDATA + rfc3252.txt + image1.jpg + document.docx + sheet.xlsx +) + 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..5436ce5b26 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/rfc3252.txt @@ -0,0 +1 @@ +some text for reference 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..ad40c79bf9 --- /dev/null +++ b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp @@ -0,0 +1,203 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qformdatabuilder.h> + +#include <QtCore/qbuffer.h> +#include <QtCore/qfile.h> + +#include <QtTest/qtest.h> + +using namespace Qt::StringLiterals; + +class tst_QFormDataBuilder : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void generateQHttpPartWithDevice_data(); + void generateQHttpPartWithDevice(); + + void escapesBackslashAndQuotesInFilenameAndName_data(); + void escapesBackslashAndQuotesInFilenameAndName(); + + void picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice_data(); + void picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice(); +}; + +void tst_QFormDataBuilder::generateQHttpPartWithDevice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("real_file_name"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("txt-ascii") << "text"_L1 << "rfc3252.txt" << "rfc3252.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("txt-latin") << "text"_L1 << "rfc3252.txt" << "szöveg.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("txt-unicode") << "text"_L1 << "rfc3252.txt" << "テキスト.txt" << "text/plain"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt"_ba; + + QTest::newRow("jpg-ascii") << "image"_L1 << "image1.jpg" << "image1.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename=image1.jpg"_ba; + QTest::newRow("jpg-latin") << "image"_L1 << "image1.jpg" << "kép.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename*=ISO-8859-1''k%E9p.jpg"_ba; + QTest::newRow("jpg-unicode") << "image"_L1 << "image1.jpg" << "絵.jpg" << "image/jpeg"_ba + << "form-data; name=\"image\"; filename*=UTF-8''%E7%B5%B5"_ba; + + QTest::newRow("doc-ascii") << "text"_L1 << "document.docx" << "word.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename=word.docx"_ba; + QTest::newRow("doc-latin") << "text"_L1 << "document.docx" << "szöveg.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.docx"_ba; + QTest::newRow("doc-unicode") << "text"_L1 << "document.docx" << "テキスト.docx" + << "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.docx"_ba; + + QTest::newRow("xls-ascii") << "spreadsheet"_L1 << "sheet.xlsx" << "sheet.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename=sheet.xlsx"_ba; + QTest::newRow("xls-latin") << "spreadsheet"_L1 << "sheet.xlsx" << "szöveg.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename*=ISO-8859-1''sz%F6veg.xlsx"_ba; + QTest::newRow("xls-unicode") << "spreadsheet"_L1 << "sheet.xlsx" << "テキスト.xlsx" + << "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_ba + << "form-data; name=\"spreadsheet\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.xlsx"_ba; + +} + +void tst_QFormDataBuilder::generateQHttpPartWithDevice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, real_file_name); + QFETCH(const QString, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QString testData = QFileInfo(QFINDTESTDATA(real_file_name)).absoluteFilePath(); + QFile data_file(testData); + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&data_file, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QString>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("quote") << "t\"ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("slash") << "t\\ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\\ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quotes") << "t\"e\"xt"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"e\"xt"; filename=rfc3252.txt)"_ba; + + QTest::newRow("slashes") << "t\\\\ext"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\\\\ext"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quote-slash") << "t\"ex\\t"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"ex\\t"; filename=rfc3252.txt)"_ba; + + QTest::newRow("quotes-slashes") << "t\"e\"x\\t\\"_L1 << "rfc3252.txt" << "text/plain"_ba + << R"(form-data; name="t\"e\"x\\t\\"; filename=rfc3252.txt)"_ba; +} + +void tst_QFormDataBuilder::escapesBackslashAndQuotesInFilenameAndName() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QString, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QFile dummy_file(body_name_data); + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&dummy_file, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + +void tst_QFormDataBuilder::picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice_data() +{ + QTest::addColumn<QLatin1StringView>("name_data"); + QTest::addColumn<QAnyStringView>("body_name_data"); + QTest::addColumn<QByteArray>("expected_content_type_data"); + QTest::addColumn<QByteArray>("expected_content_disposition_data"); + + QTest::newRow("latin1-ascii") << "text"_L1 << QAnyStringView("rfc3252.txt"_L1) << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("u8-ascii") << "text"_L1 << QAnyStringView(u8"rfc3252.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + QTest::newRow("u-ascii") << "text"_L1 << QAnyStringView(u"rfc3252.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename=rfc3252.txt"_ba; + + + QTest::newRow("latin1-latin") << "text"_L1 << QAnyStringView("sz\366veg.txt"_L1) << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("u8-latin") << "text"_L1 << QAnyStringView(u8"szöveg.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + QTest::newRow("u-latin") << "text"_L1 << QAnyStringView(u"szöveg.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=ISO-8859-1''sz%F6veg.txt"_ba; + + QTest::newRow("u8-u8") << "text"_L1 << QAnyStringView(u8"テキスト.txt") << "text/plain"_ba + << "form-data; name=\"text\"; filename*=UTF-8''%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.txt"_ba; +} + +void tst_QFormDataBuilder::picksUtf8EncodingOnlyIfL1OrAsciiDontSuffice() +{ + QFETCH(const QLatin1StringView, name_data); + QFETCH(const QAnyStringView, body_name_data); + QFETCH(const QByteArray, expected_content_type_data); + QFETCH(const QByteArray, expected_content_disposition_data); + + QBuffer buff; + + QHttpPart httpPart = QFormDataPartBuilder(name_data, QFormDataPartBuilder::PrivateConstructor()) + .setBodyDevice(&buff, body_name_data) + .build(); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + QVERIFY(msg.contains(expected_content_type_data)); + QVERIFY(msg.contains(expected_content_disposition_data)); +} + + +QTEST_MAIN(tst_QFormDataBuilder) +#include "tst_qformdatabuilder.moc" diff --git a/tests/auto/network/access/qhttp2connection/CMakeLists.txt b/tests/auto/network/access/qhttp2connection/CMakeLists.txt new file mode 100644 index 0000000000..9a6e7a064e --- /dev/null +++ b/tests/auto/network/access/qhttp2connection/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2023 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_qhttp2connection LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttp2connection + SOURCES + tst_qhttp2connection.cpp + LIBRARIES + Qt::NetworkPrivate + Qt::Test +) diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp new file mode 100644 index 0000000000..b9d5219ae9 --- /dev/null +++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp @@ -0,0 +1,397 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QTest> +#include <QtTest/QSignalSpy> + +#include <QtNetwork/private/qhttp2connection_p.h> +#include <QtNetwork/private/hpack_p.h> +#include <QtNetwork/private/bitstreams_p.h> + +#include <limits> + +using namespace Qt::StringLiterals; + +class tst_QHttp2Connection : public QObject +{ + Q_OBJECT + +private slots: + void construct(); + void constructStream(); + void testSETTINGSFrame(); + void testPING(); + void connectToServer(); + void WINDOW_UPDATE(); + +private: + enum PeerType { Client, Server }; + [[nodiscard]] auto makeFakeConnectedSockets(); + [[nodiscard]] auto getRequiredHeaders(); + [[nodiscard]] QHttp2Connection *makeHttp2Connection(QIODevice *socket, + QHttp2Configuration config, PeerType type); + [[nodiscard]] bool waitForSettingsExchange(QHttp2Connection *client, QHttp2Connection *server); +}; + +class IOBuffer : public QIODevice +{ + Q_OBJECT +public: + IOBuffer(QObject *parent, std::shared_ptr<QBuffer> _in, std::shared_ptr<QBuffer> _out) + : QIODevice(parent), in(std::move(_in)), out(std::move(_out)) + { + connect(in.get(), &QIODevice::readyRead, this, &IOBuffer::readyRead); + connect(out.get(), &QIODevice::bytesWritten, this, &IOBuffer::bytesWritten); + connect(out.get(), &QIODevice::aboutToClose, this, &IOBuffer::readChannelFinished); + connect(out.get(), &QIODevice::aboutToClose, this, &IOBuffer::aboutToClose); + } + + bool open(OpenMode mode) override + { + QIODevice::open(mode); + Q_ASSERT(in->isOpen()); + Q_ASSERT(out->isOpen()); + return false; + } + + bool isSequential() const override { return true; } + + qint64 bytesAvailable() const override { return in->pos() - readHead; } + qint64 bytesToWrite() const override { return 0; } + + qint64 readData(char *data, qint64 maxlen) override + { + qint64 temp = in->pos(); + in->seek(readHead); + qint64 res = in->read(data, std::min(maxlen, temp - readHead)); + readHead += res; + if (readHead == temp) { + // Reached end of buffer, reset + in->seek(0); + in->buffer().resize(0); + readHead = 0; + } else { + in->seek(temp); + } + return res; + } + + qint64 writeData(const char *data, qint64 len) override + { + return out->write(data, len); + } + + std::shared_ptr<QBuffer> in; + std::shared_ptr<QBuffer> out; + + qint64 readHead = 0; +}; + +auto tst_QHttp2Connection::makeFakeConnectedSockets() +{ + auto clientIn = std::make_shared<QBuffer>(); + auto serverIn = std::make_shared<QBuffer>(); + clientIn->open(QIODevice::ReadWrite); + serverIn->open(QIODevice::ReadWrite); + + auto client = std::make_unique<IOBuffer>(this, clientIn, serverIn); + auto server = std::make_unique<IOBuffer>(this, std::move(serverIn), std::move(clientIn)); + + client->open(QIODevice::ReadWrite); + server->open(QIODevice::ReadWrite); + + return std::pair{ std::move(client), std::move(server) }; +} + +auto tst_QHttp2Connection::getRequiredHeaders() +{ + return HPack::HttpHeader{ + { ":authority", "example.com" }, + { ":method", "GET" }, + { ":path", "/" }, + { ":scheme", "https" }, + }; +} + +QHttp2Connection *tst_QHttp2Connection::makeHttp2Connection(QIODevice *socket, + QHttp2Configuration config, + PeerType type) +{ + QHttp2Connection *connection = nullptr; + if (type == PeerType::Server) + connection = QHttp2Connection::createDirectServerConnection(socket, config); + else + connection = QHttp2Connection::createDirectConnection(socket, config); + connect(socket, &QIODevice::readyRead, connection, &QHttp2Connection::handleReadyRead); + return connection; +} + +bool tst_QHttp2Connection::waitForSettingsExchange(QHttp2Connection *client, + QHttp2Connection *server) +{ + bool settingsFrameReceived = false; + bool serverSettingsFrameReceived = false; + + QMetaObject::Connection c = connect(client, &QHttp2Connection::settingsFrameReceived, client, + [&settingsFrameReceived]() { + settingsFrameReceived = true; + }); + QMetaObject::Connection s = connect(server, &QHttp2Connection::settingsFrameReceived, server, + [&serverSettingsFrameReceived]() { + serverSettingsFrameReceived = true; + }); + + client->handleReadyRead(); // handle incoming frames, send response + + bool success = QTest::qWaitFor([&]() { + return settingsFrameReceived && serverSettingsFrameReceived; + }); + + disconnect(c); + disconnect(s); + + return success; +} + +void tst_QHttp2Connection::construct() +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + auto *connection = QHttp2Connection::createDirectConnection(&buffer, {}); + QVERIFY(!connection->isGoingAway()); + QCOMPARE(connection->maxConcurrentStreams(), 100u); + QCOMPARE(connection->maxHeaderListSize(), std::numeric_limits<quint32>::max()); + QVERIFY(!connection->isUpgradedConnection()); + QVERIFY(!connection->getStream(1)); // No stream has been created yet + + auto *upgradedConnection = QHttp2Connection::createUpgradedConnection(&buffer, {}); + QVERIFY(upgradedConnection->isUpgradedConnection()); + // Stream 1 is created by default for an upgraded connection + QVERIFY(upgradedConnection->getStream(1)); +} + +void tst_QHttp2Connection::constructStream() +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + auto connection = QHttp2Connection::createDirectConnection(&buffer, {}); + QHttp2Stream *stream = connection->createStream().unwrap(); + QVERIFY(stream); + QCOMPARE(stream->isPromisedStream(), false); + QCOMPARE(stream->isActive(), false); + QCOMPARE(stream->RST_STREAM_code(), 0u); + QCOMPARE(stream->streamID(), 1u); + QCOMPARE(stream->receivedHeaders(), {}); + QCOMPARE(stream->state(), QHttp2Stream::State::Idle); + QCOMPARE(stream->isUploadBlocked(), false); + QCOMPARE(stream->isUploadingDATA(), false); +} + +void tst_QHttp2Connection::testSETTINGSFrame() +{ + constexpr qint32 PrefaceLength = 24; + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QHttp2Configuration config; + constexpr quint32 MaxFrameSize = 16394; + constexpr bool ServerPushEnabled = false; + constexpr quint32 StreamReceiveWindowSize = 50000; + constexpr quint32 SessionReceiveWindowSize = 50001; + config.setMaxFrameSize(MaxFrameSize); + config.setServerPushEnabled(ServerPushEnabled); + config.setStreamReceiveWindowSize(StreamReceiveWindowSize); + config.setSessionReceiveWindowSize(SessionReceiveWindowSize); + auto connection = QHttp2Connection::createDirectConnection(&buffer, config); + Q_UNUSED(connection); + QCOMPARE_GE(buffer.size(), PrefaceLength); + + // Preface + QByteArray preface = buffer.data().first(PrefaceLength); + QCOMPARE(preface, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + + // SETTINGS + buffer.seek(PrefaceLength); + const quint32 maxSize = buffer.size() - PrefaceLength; + Http2::FrameReader reader; + Http2::FrameStatus status = reader.read(buffer); + QCOMPARE(status, Http2::FrameStatus::goodFrame); + Http2::Frame f = reader.inboundFrame(); + QCOMPARE(f.type(), Http2::FrameType::SETTINGS); + QCOMPARE_LT(f.payloadSize(), maxSize); + + const qint32 settingsReceived = f.dataSize() / 6; + QCOMPARE_GT(settingsReceived, 0); + QCOMPARE_LE(settingsReceived, 6); + + struct ExpectedSetting + { + Http2::Settings identifier; + quint32 value; + }; + // Commented-out settings are not sent since they are defaults + ExpectedSetting expectedSettings[]{ + // { Http2::Settings::HEADER_TABLE_SIZE_ID, HPack::FieldLookupTable::DefaultSize }, + { Http2::Settings::ENABLE_PUSH_ID, ServerPushEnabled ? 1 : 0 }, + // { Http2::Settings::MAX_CONCURRENT_STREAMS_ID, Http2::maxConcurrentStreams }, + { Http2::Settings::INITIAL_WINDOW_SIZE_ID, StreamReceiveWindowSize }, + { Http2::Settings::MAX_FRAME_SIZE_ID, MaxFrameSize }, + // { Http2::Settings::MAX_HEADER_LIST_SIZE_ID, ??? }, + }; + + QCOMPARE(quint32(settingsReceived), std::size(expectedSettings)); + for (qint32 i = 0; i < settingsReceived; ++i) { + const uchar *it = f.dataBegin() + i * 6; + const quint16 ident = qFromBigEndian<quint16>(it); + const quint32 intVal = qFromBigEndian<quint32>(it + 2); + + ExpectedSetting expectedSetting = expectedSettings[i]; + QVERIFY2(ident == quint16(expectedSetting.identifier), + qPrintable("ident: %1, expected: %2, index: %3"_L1 + .arg(QString::number(ident), + QString::number(quint16(expectedSetting.identifier)), + QString::number(i)))); + QVERIFY2(intVal == expectedSetting.value, + qPrintable("intVal: %1, expected: %2, index: %3"_L1 + .arg(QString::number(intVal), + QString::number(expectedSetting.value), + QString::number(i)))); + } +} + +void tst_QHttp2Connection::testPING() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + auto serverConnection = makeHttp2Connection(server.get(), {}, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy serverPingSpy{ serverConnection, &QHttp2Connection::pingFrameRecived }; + QSignalSpy clientPingSpy{ connection, &QHttp2Connection::pingFrameRecived }; + + QByteArray data{"pingpong"}; + connection->sendPing(data); + + QVERIFY(serverPingSpy.wait()); + QVERIFY(clientPingSpy.wait()); + + QCOMPARE(serverPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::Ping)); + QCOMPARE(clientPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::PongSignatureIdentical)); + + serverConnection->sendPing(); + + QVERIFY(clientPingSpy.wait()); + QVERIFY(serverPingSpy.wait()); + + QCOMPARE(clientPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::Ping)); + QCOMPARE(serverPingSpy.last().at(0).toInt(), int(QHttp2Connection::PingState::PongSignatureIdentical)); +} + +void tst_QHttp2Connection::connectToServer() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + auto serverConnection = makeHttp2Connection(server.get(), {}, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream }; + QSignalSpy clientIncomingStreamSpy{ connection, &QHttp2Connection::newIncomingStream }; + + QHttp2Stream *clientStream = connection->createStream().unwrap(); + QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived }; + QVERIFY(clientStream); + HPack::HttpHeader headers = getRequiredHeaders(); + clientStream->sendHEADERS(headers, false); + + QVERIFY(newIncomingStreamSpy.wait()); + auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>(); + QVERIFY(serverStream); + const HPack::HttpHeader ExpectedResponseHeaders{ { ":status", "200" } }; + serverStream->sendHEADERS(ExpectedResponseHeaders, true); + + QVERIFY(clientHeaderReceivedSpy.wait()); + const HPack::HttpHeader + headersReceived = clientHeaderReceivedSpy.front().front().value<HPack::HttpHeader>(); + QCOMPARE(headersReceived, ExpectedResponseHeaders); + + QCOMPARE(clientIncomingStreamSpy.count(), 0); +} + +void tst_QHttp2Connection::WINDOW_UPDATE() +{ + auto [client, server] = makeFakeConnectedSockets(); + auto connection = makeHttp2Connection(client.get(), {}, Client); + + QHttp2Configuration config; + config.setStreamReceiveWindowSize(1024); // Small window on server to provoke WINDOW_UPDATE + auto serverConnection = makeHttp2Connection(server.get(), config, Server); + + QVERIFY(waitForSettingsExchange(connection, serverConnection)); + + QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream }; + + QHttp2Stream *clientStream = connection->createStream().unwrap(); + QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived }; + QSignalSpy clientDataReceivedSpy{ clientStream, &QHttp2Stream::dataReceived }; + QVERIFY(clientStream); + HPack::HttpHeader expectedRequestHeaders = HPack::HttpHeader{ + { ":authority", "example.com" }, + { ":method", "POST" }, + { ":path", "/" }, + { ":scheme", "https" }, + }; + clientStream->sendHEADERS(expectedRequestHeaders, false); + + QVERIFY(newIncomingStreamSpy.wait()); + auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>(); + QVERIFY(serverStream); + QSignalSpy serverDataReceivedSpy{ serverStream, &QHttp2Stream::dataReceived }; + + // Since a stream is only opened on the remote side when the header is received, + // we can check the headers now immediately + QCOMPARE(serverStream->receivedHeaders(), expectedRequestHeaders); + + QBuffer *buffer = new QBuffer(clientStream); + QByteArray uploadedData = "Hello World"_ba.repeated(1000); + buffer->setData(uploadedData); + buffer->open(QIODevice::ReadWrite); + clientStream->sendDATA(buffer, true); + + bool streamEnd = false; + QByteArray serverReceivedData; + while (!streamEnd) { // The window is too small to receive all data at once, so loop + QVERIFY(serverDataReceivedSpy.wait()); + auto latestEmission = serverDataReceivedSpy.back(); + serverReceivedData += latestEmission.front().value<QByteArray>(); + streamEnd = latestEmission.back().value<bool>(); + } + QCOMPARE(serverReceivedData.size(), uploadedData.size()); + QCOMPARE(serverReceivedData, uploadedData); + + QCOMPARE(clientStream->state(), QHttp2Stream::State::HalfClosedLocal); + QCOMPARE(serverStream->state(), QHttp2Stream::State::HalfClosedRemote); + + const HPack::HttpHeader ExpectedResponseHeaders{ { ":status", "200" } }; + serverStream->sendHEADERS(ExpectedResponseHeaders, false); + QBuffer *serverBuffer = new QBuffer(serverStream); + serverBuffer->setData(uploadedData); + serverBuffer->open(QIODevice::ReadWrite); + serverStream->sendDATA(serverBuffer, true); + + QVERIFY(clientHeaderReceivedSpy.wait()); + const HPack::HttpHeader + headersReceived = clientHeaderReceivedSpy.front().front().value<HPack::HttpHeader>(); + QCOMPARE(headersReceived, ExpectedResponseHeaders); + + QTRY_COMPARE_GT(clientDataReceivedSpy.count(), 0); + QCOMPARE(clientDataReceivedSpy.count(), 1); // Only one DATA frame since our window is larger + QCOMPARE(clientDataReceivedSpy.front().front().value<QByteArray>(), uploadedData); + + QCOMPARE(clientStream->state(), QHttp2Stream::State::Closed); + QCOMPARE(serverStream->state(), QHttp2Stream::State::Closed); +} + +QTEST_MAIN(tst_QHttp2Connection) + +#include "tst_qhttp2connection.moc" diff --git a/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt b/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt new file mode 100644 index 0000000000..50deeb3e56 --- /dev/null +++ b/tests/auto/network/access/qhttpheaderparser/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 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_qhttpheaderparser LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +if(NOT QT_FEATURE_private_tests) + return() +endif() + +qt_internal_add_test(tst_qhttpheaderparser + SOURCES + tst_qhttpheaderparser.cpp + LIBRARIES + Qt::NetworkPrivate +) diff --git a/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp b/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp new file mode 100644 index 0000000000..9ba889fdb3 --- /dev/null +++ b/tests/auto/network/access/qhttpheaderparser/tst_qhttpheaderparser.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/qtest.h> +#include <QObject> +#include <QtNetwork/private/qhttpheaderparser_p.h> + +class tst_QHttpHeaderParser : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void constructor(); + void limitsSetters(); + + void adjustableLimits_data(); + void adjustableLimits(); + + // general parsing tests can be found in tst_QHttpNetworkReply +}; + +void tst_QHttpHeaderParser::constructor() +{ + QHttpHeaderParser parser; + QCOMPARE(parser.getStatusCode(), 100); + QCOMPARE(parser.getMajorVersion(), 0); + QCOMPARE(parser.getMinorVersion(), 0); + QCOMPARE(parser.getReasonPhrase(), QByteArray()); + QCOMPARE(parser.combinedHeaderValue("Location"), QByteArray()); + QCOMPARE(parser.maxHeaderFields(), HeaderConstants::MAX_HEADER_FIELDS); + QCOMPARE(parser.maxHeaderFieldSize(), HeaderConstants::MAX_HEADER_FIELD_SIZE); + QCOMPARE(parser.maxTotalHeaderSize(), HeaderConstants::MAX_TOTAL_HEADER_SIZE); +} + +void tst_QHttpHeaderParser::limitsSetters() +{ + QHttpHeaderParser parser; + parser.setMaxHeaderFields(10); + QCOMPARE(parser.maxHeaderFields(), 10); + parser.setMaxHeaderFieldSize(10); + QCOMPARE(parser.maxHeaderFieldSize(), 10); + parser.setMaxTotalHeaderSize(10); + QCOMPARE(parser.maxTotalHeaderSize(), 10); +} + +void tst_QHttpHeaderParser::adjustableLimits_data() +{ + QTest::addColumn<qsizetype>("maxFieldCount"); + QTest::addColumn<qsizetype>("maxFieldSize"); + QTest::addColumn<qsizetype>("maxTotalSize"); + QTest::addColumn<QByteArray>("headers"); + QTest::addColumn<bool>("success"); + + // We pretend -1 means to not set a new limit. + + QTest::newRow("maxFieldCount-pass") << qsizetype(10) << qsizetype(-1) << qsizetype(-1) + << QByteArray("Location: hi\r\n\r\n") << true; + QTest::newRow("maxFieldCount-fail") << qsizetype(1) << qsizetype(-1) << qsizetype(-1) + << QByteArray("Location: hi\r\nCookie: a\r\n\r\n") << false; + + QTest::newRow("maxFieldSize-pass") << qsizetype(-1) << qsizetype(50) << qsizetype(-1) + << QByteArray("Location: hi\r\n\r\n") << true; + constexpr char cookieHeader[] = "Cookie: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + static_assert(sizeof(cookieHeader) - 1 == 51); + QByteArray fullHeader = QByteArray("Location: hi\r\n") + cookieHeader; + QTest::newRow("maxFieldSize-fail") << qsizetype(-1) << qsizetype(50) << qsizetype(-1) + << (fullHeader + "\r\n\r\n") << false; + + QTest::newRow("maxTotalSize-pass") << qsizetype(-1) << qsizetype(-1) << qsizetype(50) + << QByteArray("Location: hi\r\n\r\n") << true; + QTest::newRow("maxTotalSize-fail") << qsizetype(-1) << qsizetype(-1) << qsizetype(10) + << QByteArray("Location: hi\r\n\r\n") << false; +} + +void tst_QHttpHeaderParser::adjustableLimits() +{ + QFETCH(qsizetype, maxFieldCount); + QFETCH(qsizetype, maxFieldSize); + QFETCH(qsizetype, maxTotalSize); + QFETCH(QByteArray, headers); + QFETCH(bool, success); + + QHttpHeaderParser parser; + if (maxFieldCount != qsizetype(-1)) + parser.setMaxHeaderFields(maxFieldCount); + if (maxFieldSize != qsizetype(-1)) + parser.setMaxHeaderFieldSize(maxFieldSize); + if (maxTotalSize != qsizetype(-1)) + parser.setMaxTotalHeaderSize(maxTotalSize); + + QCOMPARE(parser.parseHeaders(headers), success); +} + +QTEST_MAIN(tst_QHttpHeaderParser) +#include "tst_qhttpheaderparser.moc" diff --git a/tests/auto/network/access/qhttpheaders/CMakeLists.txt b/tests/auto/network/access/qhttpheaders/CMakeLists.txt new file mode 100644 index 0000000000..0de1f96c67 --- /dev/null +++ b/tests/auto/network/access/qhttpheaders/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2023 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_qhttpheaders LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttpheaders + SOURCES + tst_qhttpheaders.cpp + LIBRARIES + Qt::Core + Qt::Network +) diff --git a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp new file mode 100644 index 0000000000..457d30feeb --- /dev/null +++ b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp @@ -0,0 +1,552 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qhttpheaders.h> + +#include <QtTest/qtest.h> + +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +using namespace Qt::StringLiterals; + +class tst_QHttpHeaders : public QObject +{ + Q_OBJECT + +private slots: + void constructors(); + void accessors(); + void wellKnownHeader(); + void headerNameField(); + void headerValueField(); + void valueEncoding(); + void replaceOrAppend(); + +private: + static constexpr QAnyStringView n1{"name1"}; + static constexpr QAnyStringView n2{"name2"}; + static constexpr QAnyStringView n3{"name3"}; + static constexpr QAnyStringView v1{"value1"}; + static constexpr QAnyStringView v2{"value2"}; + static constexpr QAnyStringView v3{"value3"}; + static constexpr QAnyStringView N1{"NAME1"}; + static constexpr QAnyStringView N2{"NAME2"}; + static constexpr QAnyStringView N3{"NAME3"}; + static constexpr QAnyStringView V1{"VALUE1"}; + static constexpr QAnyStringView V2{"VALUE2"}; + static constexpr QAnyStringView V3{"VALUE3"}; +}; + +void tst_QHttpHeaders::constructors() +{ + // Default ctor + QHttpHeaders h1; + QVERIFY(h1.isEmpty()); + + // Copy ctor + QHttpHeaders h2(h1); + QCOMPARE(h2.toListOfPairs(), h1.toListOfPairs()); + + // Copy assignment + QHttpHeaders h3; + h3 = h1; + QCOMPARE(h3.toListOfPairs(), h1.toListOfPairs()); + + // Move assignment + QHttpHeaders h4; + h4 = std::move(h2); + QCOMPARE(h4.toListOfPairs(), h1.toListOfPairs()); + + // Move ctor + QHttpHeaders h5(std::move(h4)); + QCOMPARE(h5.toListOfPairs(), h1.toListOfPairs()); + + // Constructors that are counterparts to 'toXXX()' conversion getters + const QByteArray nb1{"name1"}; + const QByteArray nb2{"name2"}; + const QByteArray nv1{"value1"}; + const QByteArray nv2{"value2"}; + // Initialize three QHttpHeaders with similar content, and verify that they have + // similar header entries +#define CONTAINS_HEADER(NAME, VALUE) \ + QVERIFY(hlist.contains(NAME) && hmap.contains(NAME) && hhash.contains(NAME)); \ + QCOMPARE(hlist.combinedValue(NAME), VALUE); \ + QCOMPARE(hmap.combinedValue(NAME), VALUE); \ + QCOMPARE(hhash.combinedValue(NAME), VALUE); \ + + QList<std::pair<QByteArray, QByteArray>> list{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QMultiMap<QByteArray, QByteArray> map{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QMultiHash<QByteArray, QByteArray> hash{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}}; + QHttpHeaders hlist = QHttpHeaders::fromListOfPairs(list); + QHttpHeaders hmap = QHttpHeaders::fromMultiMap(map); + QHttpHeaders hhash = QHttpHeaders::fromMultiHash(hash); + CONTAINS_HEADER(nb1, v1); + CONTAINS_HEADER(nb2, nv2 + ", " + nv2) +#undef CONTAINS_HEADER +} + +void tst_QHttpHeaders::accessors() +{ + QHttpHeaders h1; + + // isEmpty(), clear(), size() + h1.append(n1,v1); + QVERIFY(!h1.isEmpty()); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.append(n1, v1)); + QCOMPARE(h1.size(), 2); + h1.insert(0, n1, v1); + QCOMPARE(h1.size(), 3); + h1.clear(); + QVERIFY(h1.isEmpty()); + + // contains() + h1.append(n1, v1); + QVERIFY(h1.contains(n1)); + QVERIFY(h1.contains(N1)); + QVERIFY(!h1.contains(n2)); + QVERIFY(!h1.contains(QHttpHeaders::WellKnownHeader::Allow)); + h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing"); + QVERIFY(h1.contains(QHttpHeaders::WellKnownHeader::Accept)); + QVERIFY(h1.contains("accept")); + + // values()/value() +#define EXISTS_NOT(H, N) do { \ + QVERIFY(!H.contains(N)); \ + QCOMPARE(H.value(N, "ENOENT"), "ENOENT"); \ + const auto values = H.values(N); \ + QVERIFY(values.isEmpty()); \ + QVERIFY(H.combinedValue(N).isNull()); \ + } while (false) + +#define EXISTS_N_TIMES(X, H, N, ...) do { \ + const std::array expected = { __VA_ARGS__ }; \ + static_assert(std::tuple_size_v<decltype(expected)> == X); \ + QVERIFY(H.contains(N)); \ + QCOMPARE(H.value(N, "ENOENT"), expected.front()); \ + const auto values = H.values(N); \ + QCOMPARE(values.size(), X); \ + QCOMPARE(values.front(), expected.front()); \ + /* ignore in-between */ \ + QCOMPARE(values.back(), expected.back()); \ + QCOMPARE(H.combinedValue(N), values.join(", ")); \ + } while (false) + +#define EXISTS_ONCE(H, N, V) EXISTS_N_TIMES(1, H, N, V) + + EXISTS_ONCE(h1, n1, v1); + EXISTS_ONCE(h1, N1, v1); + EXISTS_ONCE(h1, QHttpHeaders::WellKnownHeader::Accept, "nothing"); + EXISTS_ONCE(h1, "Accept", "nothing"); + + EXISTS_NOT(h1, N2); + EXISTS_NOT(h1, QHttpHeaders::WellKnownHeader::Allow); + + h1.clear(); + + EXISTS_NOT(h1, n1); + + h1.append(n1, v1); + h1.append(n1, v2); + h1.append(n1, v3); + h1.append(n2, v2); + h1.append(n3, ""); // empty value + + EXISTS_N_TIMES(3, h1, n1, v1, v2, v3); + EXISTS_N_TIMES(3, h1, N1, v1, v2, v3); + EXISTS_ONCE(h1, n3, ""); // empty value + + h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing"); + h1.append(QHttpHeaders::WellKnownHeader::Accept, "ever"); + + EXISTS_N_TIMES(2, h1, QHttpHeaders::WellKnownHeader::Accept, "nothing", "ever"); + EXISTS_NOT(h1, "nonexistent"); + +#undef EXISTS_ONCE +#undef EXISTS_N_TIMES +#undef EXISTS_NOT + + // valueAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + QCOMPARE(h1.valueAt(0), v1); + QCOMPARE(h1.valueAt(1), v2); + QCOMPARE(h1.valueAt(2), v3); + + // nameAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + QCOMPARE(h1.nameAt(0), n1); + QCOMPARE(h1.nameAt(1), n2); + QCOMPARE(h1.nameAt(2), n3); + + // removeAll() + h1.clear(); + QVERIFY(h1.append(n1, v1)); + QVERIFY(h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QVERIFY(h1.append(n1, v1)); + QCOMPARE(h1.size(), 3); + h1.removeAll(n1); + QVERIFY(!h1.contains(n1)); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains("accept")); + h1.removeAll(QHttpHeaders::WellKnownHeader::Accept); + QVERIFY(!h1.contains(QHttpHeaders::WellKnownHeader::Accept)); + + // removeAt() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(n3, v3); + + // Valid removals + QVERIFY(h1.contains(n3)); + h1.removeAt(2); + QVERIFY(!h1.contains(n3)); + QVERIFY(h1.contains(n1)); + h1.removeAt(0); + QVERIFY(!h1.contains(n1)); + QVERIFY(h1.contains(n2)); + h1.removeAt(0); + QVERIFY(!h1.contains(n2)); + QVERIFY(h1.isEmpty()); + + // toListOfPairs() + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + h1.append(N3, V3); // uppercase of n3 + auto list = h1.toListOfPairs(); + QCOMPARE(list.size(), h1.size()); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n2); + QCOMPARE(list.at(1).second, v2); + QCOMPARE(list.at(2).first, n3); // N3 has been lower-cased + QCOMPARE(list.at(2).second, V3); + + // toMultiMap() + auto map = h1.toMultiMap(); + QCOMPARE(map.size(), h1.size()); + QCOMPARE(map.value(n1.toString().toLatin1()), v1); + QCOMPARE(map.value(n2.toString().toLatin1()), v2); + QCOMPARE(map.value(n3.toString().toLatin1()), V3); + + // toMultiHash() + auto hash = h1.toMultiHash(); + QCOMPARE(hash.size(), h1.size()); + QCOMPARE(hash.value(n1.toString().toLatin1()), v1); + QCOMPARE(hash.value(n2.toString().toLatin1()), v2); + QCOMPARE(hash.value(n3.toString().toLatin1()), V3); + + // insert() + h1.clear(); + h1.append(n3, v3); + QVERIFY(h1.insert(0, n1, v1)); + list = h1.toListOfPairs(); + QCOMPARE(list.size(), 2); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n3); + QCOMPARE(list.at(1).second, v3); + QVERIFY(h1.insert(1, n2, v2)); + list = h1.toListOfPairs(); + QCOMPARE(list.size(), 3); + QCOMPARE(list.at(0).first, n1); + QCOMPARE(list.at(0).second, v1); + QCOMPARE(list.at(1).first, n2); + QCOMPARE(list.at(1).second, v2); + QCOMPARE(list.at(2).first, n3); + QCOMPARE(list.at(2).second, v3); + QVERIFY(h1.insert(1, QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QCOMPARE(h1.size(), 4); + list = h1.toListOfPairs(); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "nothing"); + QVERIFY(h1.insert(list.size(), "LastName", "lastValue")); + QCOMPARE(h1.size(), 5); + list = h1.toListOfPairs(); + QCOMPARE(list.last().first, "lastname"); + QCOMPARE(list.last().second, "lastValue"); + // Failed insert + QRegularExpression re("HTTP header name contained*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.insert(0, "a€", "b")); + + // replace + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.replace(0, n3, v3)); + QVERIFY(h1.replace(1, QHttpHeaders::WellKnownHeader::Accept, "nothing")); + QCOMPARE(h1.size(), 2); + list = h1.toListOfPairs(); + QCOMPARE(list.at(0).first, n3); + QCOMPARE(list.at(0).second, v3); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "nothing"); + QVERIFY(h1.replace(1, "ACCEPT", "NOTHING")); + QCOMPARE(h1.size(), 2); + list = h1.toListOfPairs(); + QCOMPARE(list.at(0).first, n3); + QCOMPARE(list.at(0).second, v3); + QCOMPARE(list.at(1).first, "accept"); + QCOMPARE(list.at(1).second, "NOTHING"); + // Failed replace + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replace(0, "a€", "b")); + +} + +void tst_QHttpHeaders::wellKnownHeader() +{ + QByteArrayView view = QHttpHeaders::wellKnownHeaderName(QHttpHeaders::WellKnownHeader::AIM); + QCOMPARE(view, "a-im"); +} + +#define TEST_ILLEGAL_HEADER_NAME_CHARACTER(NAME) \ + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \ + QVERIFY(!h1.append(NAME, v1)); \ + QVERIFY(h1.isEmpty()); \ + +void tst_QHttpHeaders::headerNameField() +{ + QHttpHeaders h1; + + // All allowed characters in different encodings and types + // const char[] + h1.append("abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1); + QCOMPARE(h1.size(), 1); + // UTF-8 + h1.append(u8"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", + v1); + QCOMPARE(h1.size(), 2); + // UTF-16 + h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1); + QCOMPARE(h1.size(), 3); + // QString (UTF-16) + h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"_s, + v1); + QCOMPARE(h1.size(), 4); + QCOMPARE(h1.nameAt(0), h1.nameAt(1)); + QCOMPARE(h1.nameAt(1), h1.nameAt(2)); + QCOMPARE(h1.nameAt(2), h1.nameAt(3)); + h1.clear(); + + // Error cases + // Header name must contain at least 1 character + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "HTTP header name cannot be empty"); + h1.append("", v1); + QVERIFY(h1.isEmpty()); + // Disallowed ASCII/extended ASCII characters (not exhaustive list) + QRegularExpression re("HTTP header name contained illegal character*"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x08" "bar"); // BS + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x7F" "bar"); // DEL + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo()" "bar"); // parantheses + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foobar" "¿"); // extended ASCII + TEST_ILLEGAL_HEADER_NAME_CHARACTER("©" "foobar"); // extended ASCII + TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo,bar"); // comma + // Disallowed UTF-8 characters + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"€"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + // Disallowed UTF-16 characters + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"€"); + TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + + // Non-null-terminated name. The 'x' below is to make sure the strings don't + // null-terminate by happenstance + h1.clear(); + constexpr char L1Array[] = {'a','b','c','x'}; + const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc + + constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78}; + const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def + + constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'}; + QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi + + h1.append(nonNullLatin1, v1); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains(nonNullLatin1)); + QCOMPARE(h1.combinedValue(nonNullLatin1), v1); + + h1.append(nonNullUTF8, v2); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.contains(nonNullUTF8)); + QCOMPARE(h1.combinedValue(nonNullUTF8), v2); + + h1.append(nonNullUTF16, v3); + QCOMPARE(h1.size(), 3); + QVERIFY(h1.contains(nonNullUTF16)); + QCOMPARE(h1.combinedValue(nonNullUTF16), v3); +} + +#define TEST_ILLEGAL_HEADER_VALUE_CHARACTER(VALUE) \ +QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \ + QVERIFY(!h1.append(n1, VALUE)); \ + QVERIFY(h1.isEmpty()); \ + +void tst_QHttpHeaders::headerValueField() +{ + QHttpHeaders h1; + + // Visible ASCII characters and space and horizontal tab + // const char[] + h1.append(n1, "!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 1); + // UTF-8 + h1.append(n1, u8"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 2); + // UTF-16 + h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"); + QCOMPARE(h1.size(), 3); + // QString / UTF-16 + h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"_s); + QCOMPARE(h1.size(), 4); + const auto values = h1.values(n1); + QVERIFY(!values.isEmpty() && values.size() == 4); + QVERIFY(values[0] == values[1] + && values[1] == values[2] + && values[2] == values[3]); + // Extended ASCII (explicit on Latin-1 to avoid UTF-8 interpretation) + h1.append(n1, "\x80\x09\xB2\xFF"_L1); + QCOMPARE(h1.size(), 5); + // Empty value + h1.append(n1, ""); + QCOMPARE(h1.size(), 6); + // Leading and trailing space + h1.clear(); + h1.append(n1, " foo "); + QCOMPARE(h1.combinedValue(n1), "foo"); + h1.append(n1, "\tbar\t"); + QCOMPARE(h1.combinedValue(n1), "foo, bar"); + QCOMPARE(h1.size(), 2); + + h1.clear(); + 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"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏"); + + // Non-null-terminated value. The 'x' below is to make sure the strings don't + // null-terminate by happenstance + h1.clear(); + constexpr char L1Array[] = {'a','b','c','x'}; + const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc + + constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78}; + const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def + + constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'}; + QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi + + h1.append(n1, nonNullLatin1); + QCOMPARE(h1.size(), 1); + QVERIFY(h1.contains(n1)); + QCOMPARE(h1.combinedValue(n1), "abc"); + + h1.append(n2, nonNullUTF8); + QCOMPARE(h1.size(), 2); + QVERIFY(h1.contains(n2)); + QCOMPARE(h1.combinedValue(n2), "def"); + + h1.append(n3, nonNullUTF16); + QCOMPARE(h1.size(), 3); + QVERIFY(h1.contains(n3)); + QCOMPARE(h1.combinedValue(n3), "ghi"); +} + +void tst_QHttpHeaders::valueEncoding() +{ + // Test that common encodings are possible to set and not blocked by + // header value character filter (ie. don't contain disallowed characters as per RFC 9110) + QHttpHeaders h1; + // Within visible ASCII range + QVERIFY(h1.append(n1, "foo"_ba.toBase64())); + QCOMPARE(h1.values(n1).at(0), "Zm9v"); + h1.replace(0, n1, "foo"_ba.toPercentEncoding()); + QCOMPARE(h1.values(n1).at(0), "foo"); + + // Outside of ASCII/Latin-1 range (€) + h1.replace(0, n1, "foo€"_ba.toBase64()); + QCOMPARE(h1.values(n1).at(0), "Zm9v4oKs"); + h1.replace(0, n1, "foo€"_ba.toPercentEncoding()); + QCOMPARE(h1.values(n1).at(0), "foo%E2%82%AC"); +} + +void tst_QHttpHeaders::replaceOrAppend() +{ + QHttpHeaders h1; + +#define REPLACE_OR_APPEND(NAME, VALUE, INDEX, TOTALSIZE) \ + do { \ + QVERIFY(h1.replaceOrAppend(NAME, VALUE)); \ + QCOMPARE(h1.size(), TOTALSIZE); \ + QCOMPARE(h1.nameAt(INDEX), NAME); \ + QCOMPARE(h1.valueAt(INDEX), VALUE); \ + } while (false) + + // Append to empty container and replace it + REPLACE_OR_APPEND(n1, v1, 0, 1); // Appends + REPLACE_OR_APPEND(n1, v2, 0, 1); // Replaces + + // Replace at beginning, middle, and end + h1.clear(); + REPLACE_OR_APPEND(n1, v1, 0, 1); // Appends + REPLACE_OR_APPEND(n2, v2, 1, 2); // Appends + REPLACE_OR_APPEND(n3, v3, 2, 3); // Appends + REPLACE_OR_APPEND(n1, V1, 0, 3); // Replaces at beginning + REPLACE_OR_APPEND(n2, V2, 1, 3); // Replaces at middle + REPLACE_OR_APPEND(n3, V3, 2, 3); // Replaces at end + + // Pre-existing multiple values (n2) are removed + h1.clear(); + h1.append(n1, v1); + h1.append(n2, v2); // First n2 is at index 1 + h1.append(n2, v2); + h1.append(n3, v3); + h1.append(n2, v2); + QCOMPARE(h1.size(), 5); + QCOMPARE(h1.combinedValue(n2), "value2, value2, value2"); + REPLACE_OR_APPEND(n2, V2, 1, 3); // Replaces value at index 1, and removes the rest + QCOMPARE(h1.combinedValue(n2), "VALUE2"); +#undef REPLACE_OR_APPEND + + // Implicit sharing / detaching + h1.clear(); + h1.append(n1, v1); + QHttpHeaders h2 = h1; + QCOMPARE(h1.size(), h2.size()); + QCOMPARE(h1.valueAt(0), h2.valueAt(0)); // Iniially values are equal + h1.replaceOrAppend(n1, v2); // Change value in h1 => detaches h1 + QCOMPARE_NE(h1.valueAt(0), h2.valueAt(0)); // Values are no more equal + QCOMPARE(h1.valueAt(0), v2); // Value in h1 changed + QCOMPARE(h2.valueAt(0), v1); // Value in h2 remained + + // Failed attempts + h1.clear(); + h1.append(n1, v1); + QRegularExpression re("HTTP header*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replaceOrAppend("", V1)); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QVERIFY(!h1.replaceOrAppend(v1, "foo\x08")); +} + +QTEST_MAIN(tst_QHttpHeaders) +#include "tst_qhttpheaders.moc" diff --git a/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt b/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt new file mode 100644 index 0000000000..75935d2844 --- /dev/null +++ b/tests/auto/network/access/qhttpheadershelper/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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_qhttpheadershelper LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qhttpheadershelper + SOURCES + tst_qhttpheadershelper.cpp + LIBRARIES + Qt::NetworkPrivate +) diff --git a/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp b/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp new file mode 100644 index 0000000000..b204d0cbe3 --- /dev/null +++ b/tests/auto/network/access/qhttpheadershelper/tst_qhttpheadershelper.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qhttpheaders.h> +#include <QtNetwork/private/qhttpheadershelper_p.h> + +#include <QtTest/qtest.h> + +using namespace Qt::StringLiterals; + +class tst_QHttpHeadersHelper : public QObject +{ + Q_OBJECT + +private slots: + void testCompareStrict(); + +private: + static constexpr QAnyStringView n1{"name1"}; + static constexpr QAnyStringView n2{"name2"}; + static constexpr QAnyStringView v1{"value1"}; + static constexpr QAnyStringView v2{"value2"}; + static constexpr QAnyStringView N1{"NAME1"}; + static constexpr QAnyStringView N2{"NAME2"}; + static constexpr QAnyStringView V1{"VALUE1"}; + static constexpr QAnyStringView V2{"VALUE2"}; +}; + +void tst_QHttpHeadersHelper::testCompareStrict() +{ + using namespace QHttpHeadersHelper; + + // Basic comparisons + QHttpHeaders h1; + QHttpHeaders h2; + QVERIFY(compareStrict(h1, h2)); // empties + h1.append(n1, v1); + QVERIFY(compareStrict(h1, h1)); // self + h2.append(n1, v1); + QVERIFY(compareStrict(h1, h2)); + h1.append(n2, v2); + QVERIFY(!compareStrict(h1, h2)); + h1.removeAll(n2); + QVERIFY(compareStrict(h1, h2)); + + // Order-sensitivity + h1.clear(); + h2.clear(); + // Same headers but in different order + h1.append(n1, v1); + h1.append(n2, v2); + h2.append(n2, v2); + h2.append(n1, v1); + QVERIFY(!compareStrict(h1, h2)); + + // Different number of headers + h1.clear(); + h2.clear(); + h1.append(n1, v1); + h2.append(n1, v1); + h2.append(n2, v2); + QVERIFY(!compareStrict(h1, h2)); + + // Same header name, multiple values + h1.clear(); + h2.clear(); + h1.append(n1, v1); + h1.append(n1, v2); + h2.append(n1, v1); + QVERIFY(!compareStrict(h1, h2)); + h2.append(n1, v2); + QVERIFY(compareStrict(h1, h2)); +} + +QTEST_MAIN(tst_QHttpHeadersHelper) +#include "tst_qhttpheadershelper.moc" diff --git a/tests/auto/network/access/qhttpnetworkconnection/.prev_CMakeLists.txt b/tests/auto/network/access/qhttpnetworkconnection/.prev_CMakeLists.txt deleted file mode 100644 index 4c23a4f66b..0000000000 --- a/tests/auto/network/access/qhttpnetworkconnection/.prev_CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Generated from qhttpnetworkconnection.pro. - -if(NOT QT_FEATURE_private_tests) - return() -endif() - -##################################################################### -## tst_qhttpnetworkconnection Test: -##################################################################### - -qt_internal_add_test(tst_qhttpnetworkconnection - SOURCES - tst_qhttpnetworkconnection.cpp - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::NetworkPrivate -) - -#### Keys ignored in scope 1:.:.:qhttpnetworkconnection.pro:<TRUE>: -# QT_TEST_SERVER_LIST = "apache2" -# _REQUIREMENTS = "qtConfig(private_tests)" diff --git a/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt b/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt index 29b345b0db..679990062f 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt +++ b/tests/auto/network/access/qhttpnetworkconnection/CMakeLists.txt @@ -1,4 +1,11 @@ -# Generated from qhttpnetworkconnection.pro. +# Copyright (C) 2022 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_qhttpnetworkconnection LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() if(NOT QT_FEATURE_private_tests) return() @@ -11,12 +18,8 @@ endif() qt_internal_add_test(tst_qhttpnetworkconnection SOURCES tst_qhttpnetworkconnection.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::NetworkPrivate - QT_TEST_SERVER_LIST "apache2" # special case + QT_TEST_SERVER_LIST "apache2" ) - -#### Keys ignored in scope 1:.:.:qhttpnetworkconnection.pro:<TRUE>: -# QT_TEST_SERVER_LIST = "apache2" -# _REQUIREMENTS = "qtConfig(private_tests)" diff --git a/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro b/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro deleted file mode 100644 index 84e6f857a1..0000000000 --- a/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro +++ /dev/null @@ -1,9 +0,0 @@ -CONFIG += testcase -TARGET = tst_qhttpnetworkconnection -SOURCES += tst_qhttpnetworkconnection.cpp -requires(qtConfig(private_tests)) - -QT = core-private network-private testlib - -CONFIG += unsupported/testserver -QT_TEST_SERVER_LIST = apache2 diff --git a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp index 2204e9f2f4..decd442164 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp +++ b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp @@ -1,38 +1,15 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> -#include "private/qhttpnetworkconnection_p.h" -#include "private/qnoncontiguousbytedevice_p.h" +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> +#include <QTestEventLoop> #include <QAuthenticator> #include <QTcpServer> +#include "private/qhttpnetworkconnection_p.h" +#include "private/qnoncontiguousbytedevice_p.h" + #include "../../../network-settings.h" class tst_QHttpNetworkConnection: public QObject @@ -148,7 +125,7 @@ void tst_QHttpNetworkConnection::head() QFETCH(QString, statusString); QFETCH(int, contentLength); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -198,7 +175,7 @@ void tst_QHttpNetworkConnection::get() QFETCH(int, contentLength); QFETCH(int, downloadSize); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -264,7 +241,7 @@ void tst_QHttpNetworkConnection::put() QFETCH(QString, data); QFETCH(bool, succeed); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -346,7 +323,7 @@ void tst_QHttpNetworkConnection::post() QFETCH(int, contentLength); QFETCH(int, downloadSize); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -473,7 +450,7 @@ void tst_QHttpNetworkConnection::get401() QFETCH(QString, password); QFETCH(int, statusCode); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -533,7 +510,7 @@ void tst_QHttpNetworkConnection::compression() QFETCH(bool, autoCompress); QFETCH(QString, contentCoding); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); QCOMPARE(connection.isSsl(), encrypt); @@ -607,7 +584,7 @@ void tst_QHttpNetworkConnection::ignoresslerror() QFETCH(bool, ignoreFromSignal); QFETCH(int, statusCode); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); if (ignoreInit) @@ -652,7 +629,7 @@ void tst_QHttpNetworkConnection::nossl() QFETCH(bool, encrypt); QFETCH(QNetworkReply::NetworkError, networkError); - QHttpNetworkConnection connection(host, port, encrypt); + QHttpNetworkConnection connection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, host, port, encrypt); QCOMPARE(connection.port(), port); QCOMPARE(connection.hostName(), host); @@ -689,7 +666,7 @@ void tst_QHttpNetworkConnection::getMultiple_data() static bool allRepliesFinished(const QList<QHttpNetworkReply*> *_replies) { const QList<QHttpNetworkReply*> &replies = *_replies; - for (int i = 0; i < replies.length(); i++) + for (int i = 0; i < replies.size(); i++) if (!replies.at(i)->isFinished()) return false; return true; @@ -758,7 +735,7 @@ void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities( QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000); int pipelinedCount = 0; - for (int i = 0; i < replies.length(); i++) { + for (int i = 0; i < replies.size(); i++) { QVERIFY (!(replies.at(i)->request().isPipeliningAllowed() == false && replies.at(i)->isPipeliningUsed())); @@ -942,7 +919,7 @@ void tst_QHttpNetworkConnection::getAndThenDeleteObject_data() void tst_QHttpNetworkConnection::getAndThenDeleteObject() { // yes, this will leak if the testcase fails. I don't care. It must not fail then :P - QHttpNetworkConnection *connection = new QHttpNetworkConnection(httpServerName()); + QHttpNetworkConnection *connection = new QHttpNetworkConnection(QHttpNetworkConnectionPrivate::defaultHttpChannelCount, httpServerName()); QHttpNetworkRequest request("http://" + httpServerName() + "/qtest/bigfile"); QHttpNetworkReply *reply = connection->sendRequest(request); reply->setDownstreamLimited(true); diff --git a/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt b/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt index 4d35cc8a2b..b4e4a822ee 100644 --- a/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt +++ b/tests/auto/network/access/qhttpnetworkreply/CMakeLists.txt @@ -1,4 +1,11 @@ -# Generated from qhttpnetworkreply.pro. +# Copyright (C) 2022 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_qhttpnetworkreply LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() if(NOT QT_FEATURE_private_tests) return() @@ -11,10 +18,7 @@ endif() qt_internal_add_test(tst_qhttpnetworkreply SOURCES tst_qhttpnetworkreply.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::NetworkPrivate ) - -#### Keys ignored in scope 1:.:.:qhttpnetworkreply.pro:<TRUE>: -# _REQUIREMENTS = "qtConfig(private_tests)" diff --git a/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro b/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro deleted file mode 100644 index 31570e6f01..0000000000 --- a/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro +++ /dev/null @@ -1,6 +0,0 @@ -CONFIG += testcase -TARGET = tst_qhttpnetworkreply -SOURCES += tst_qhttpnetworkreply.cpp -requires(qtConfig(private_tests)) - -QT = core-private network-private testlib diff --git a/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp b/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp index d230fcad4b..e83d15fdc3 100644 --- a/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp +++ b/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp @@ -1,35 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> #include <QtCore/QBuffer> #include <QtCore/QByteArray> +#include <QtCore/QStringBuilder> #include "private/qhttpnetworkconnection_p.h" @@ -40,6 +16,9 @@ private Q_SLOTS: void parseHeader_data(); void parseHeader(); + void parseHeaderVerification_data(); + void parseHeaderVerification(); + void parseEndOfHeader_data(); void parseEndOfHeader(); }; @@ -50,6 +29,7 @@ void tst_QHttpNetworkReply::parseHeader_data() QTest::addColumn<QStringList>("fields"); QTest::addColumn<QStringList>("values"); + QTest::newRow("no-fields") << QByteArray("\r\n") << QStringList() << QStringList(); QTest::newRow("empty-field") << QByteArray("Set-Cookie: \r\n") << (QStringList() << "Set-Cookie") << (QStringList() << ""); @@ -60,6 +40,9 @@ void tst_QHttpNetworkReply::parseHeader_data() " charset=utf-8\r\n") << (QStringList() << "Content-Type") << (QStringList() << "text/html; charset=utf-8"); + QTest::newRow("single-field-on-five-lines") + << QByteArray("Name:\r\n first\r\n \r\n \r\n last\r\n") << (QStringList() << "Name") + << (QStringList() << "first last"); QTest::newRow("multi-field") << QByteArray("Content-Type: text/html; charset=utf-8\r\n" "Content-Length: 1024\r\n" @@ -94,13 +77,100 @@ void tst_QHttpNetworkReply::parseHeader() QHttpNetworkReply reply; reply.parseHeader(headers); - for (int i = 0; i < fields.count(); ++i) { + for (int i = 0; i < fields.size(); ++i) { //qDebug() << "field" << fields.at(i) << "value" << reply.headerField(fields.at(i)) << "expected" << values.at(i); QString field = reply.headerField(fields.at(i).toLatin1()); QCOMPARE(field, values.at(i)); } } +void tst_QHttpNetworkReply::parseHeaderVerification_data() +{ + QTest::addColumn<QByteArray>("headers"); + QTest::addColumn<bool>("success"); + + QTest::newRow("no-header-fields") << QByteArray("\r\n") << true; + QTest::newRow("starting-with-space") << QByteArray(" Content-Encoding: gzip\r\n") << false; + QTest::newRow("starting-with-tab") << QByteArray("\tContent-Encoding: gzip\r\n") << false; + QTest::newRow("only-colon") << QByteArray(":\r\n") << false; + QTest::newRow("colon-and-value") << QByteArray(": only-value\r\n") << false; + QTest::newRow("name-with-space") << QByteArray("Content Length: 10\r\n") << false; + QTest::newRow("missing-colon-1") << QByteArray("Content-Encoding\r\n") << false; + QTest::newRow("missing-colon-2") + << QByteArray("Content-Encoding\r\nContent-Length: 10\r\n") << false; + QTest::newRow("missing-colon-3") + << QByteArray("Content-Encoding: gzip\r\nContent-Length\r\n") << false; + QTest::newRow("header-field-too-long") + << (QByteArray("Content-Type: ") + + QByteArray(HeaderConstants::MAX_HEADER_FIELD_SIZE, 'a') + QByteArray("\r\n")) + << false; + + QByteArray name = "Content-Type: "; + QTest::newRow("max-header-field-size") + << (name + QByteArray(HeaderConstants::MAX_HEADER_FIELD_SIZE - name.size(), 'a') + + QByteArray("\r\n")) + << true; + + QByteArray tooManyHeaders = QByteArray("Content-Type: text/html; charset=utf-8\r\n") + .repeated(HeaderConstants::MAX_HEADER_FIELDS + 1); + QTest::newRow("too-many-headers") << tooManyHeaders << false; + + QByteArray maxHeaders = QByteArray("Content-Type: text/html; charset=utf-8\r\n") + .repeated(HeaderConstants::MAX_HEADER_FIELDS); + QTest::newRow("max-headers") << maxHeaders << true; + + QByteArray firstValue(HeaderConstants::MAX_HEADER_FIELD_SIZE / 2, 'a'); + constexpr int obsFold = 1; + QTest::newRow("max-continuation-size") + << (name + firstValue + QByteArray("\r\n ") + + QByteArray(HeaderConstants::MAX_HEADER_FIELD_SIZE - name.size() + - firstValue.size() - obsFold, + 'b') + + QByteArray("\r\n")) + << true; + QTest::newRow("too-long-continuation-size") + << (name + firstValue + QByteArray("\r\n ") + + QByteArray(HeaderConstants::MAX_HEADER_FIELD_SIZE - name.size() + - firstValue.size() - obsFold + 1, + 'b') + + QByteArray("\r\n")) + << false; + + auto appendLongHeaderElement = [](QByteArray &result, QByteArrayView name) { + const qsizetype size = result.size(); + result += name; + result += ": "; + result.resize(size + HeaderConstants::MAX_HEADER_FIELD_SIZE, 'a'); + }; + QByteArray longHeader; + constexpr qsizetype TrailerLength = sizeof("\r\n\r\n") - 1; // we ignore the trailing newlines + longHeader.reserve(HeaderConstants::MAX_TOTAL_HEADER_SIZE + TrailerLength + 1); + appendLongHeaderElement(longHeader, "Location"); + longHeader += "\r\n"; + appendLongHeaderElement(longHeader, "WWW-Authenticate"); + longHeader += "\r\nProxy-Authenticate: "; + longHeader.resize(HeaderConstants::MAX_TOTAL_HEADER_SIZE, 'a'); + longHeader += "\r\n\r\n"; + + // Test with headers which are just large enough to fit our MAX_TOTAL_HEADER_SIZE limit: + QTest::newRow("total-header-close-to-max-size") << longHeader << true; + // Now add another character to make the total header size exceed the limit: + longHeader.insert(HeaderConstants::MAX_TOTAL_HEADER_SIZE - TrailerLength, 'a'); + QTest::newRow("total-header-too-large") << longHeader << false; +} + +void tst_QHttpNetworkReply::parseHeaderVerification() +{ + QFETCH(QByteArray, headers); + QFETCH(bool, success); + QHttpNetworkReply reply; + reply.parseHeader(headers); + if (success && QByteArrayView(headers).trimmed().size()) + QVERIFY(reply.header().size() > 0); + else + QCOMPARE(reply.header().size(), 0); +} + class TestHeaderSocket : public QAbstractSocket { public: diff --git a/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt b/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt index 1d819c3784..b0fe6eda46 100644 --- a/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkaccessmanager/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qnetworkaccessmanager.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkaccessmanager Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkaccessmanager LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkaccessmanager SOURCES tst_qnetworkaccessmanager.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network ) diff --git a/tests/auto/network/access/qnetworkaccessmanager/qnetworkaccessmanager.pro b/tests/auto/network/access/qnetworkaccessmanager/qnetworkaccessmanager.pro deleted file mode 100644 index e84f9f7dba..0000000000 --- a/tests/auto/network/access/qnetworkaccessmanager/qnetworkaccessmanager.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkaccessmanager -SOURCES += tst_qnetworkaccessmanager.cpp -QT = core network testlib diff --git a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp index 78919e8af6..43db6d5841 100644 --- a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp +++ b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> diff --git a/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt b/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt index 3a8bef0aa2..2aa918c49c 100644 --- a/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcachemetadata/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qnetworkcachemetadata.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkcachemetadata Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcachemetadata LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkcachemetadata SOURCES tst_qnetworkcachemetadata.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network ) diff --git a/tests/auto/network/access/qnetworkcachemetadata/qnetworkcachemetadata.pro b/tests/auto/network/access/qnetworkcachemetadata/qnetworkcachemetadata.pro deleted file mode 100644 index 0e942cd4f4..0000000000 --- a/tests/auto/network/access/qnetworkcachemetadata/qnetworkcachemetadata.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkcachemetadata -QT = core network testlib -SOURCES += tst_qnetworkcachemetadata.cpp diff --git a/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp b/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp index 49c3f1b17f..d49195efc6 100644 --- a/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp +++ b/tests/auto/network/access/qnetworkcachemetadata/tst_qnetworkcachemetadata.cpp @@ -1,33 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> +#include <QBuffer> + #include <qabstractnetworkcache.h> #define EXAMPLE_URL "http://user:pass@www.example.com/#foo" @@ -52,6 +29,8 @@ private slots: void operatorEqualEqual(); void rawHeaders_data(); void rawHeaders(); + void headers_data(); + void headers(); void saveToDisk_data(); void saveToDisk(); void url_data(); @@ -137,6 +116,12 @@ void tst_QNetworkCacheMetaData::isValid_data() QNetworkCacheMetaData data5; data5.setSaveToDisk(false); QTest::newRow("valid-5") << data5 << true; + + QNetworkCacheMetaData data6; + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data6.setHeaders(httpHeaders); + QTest::newRow("valid-6") << data6 << true; } // public bool isValid() const @@ -176,6 +161,9 @@ void tst_QNetworkCacheMetaData::operatorEqual_data() QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); data.setRawHeaders(headers); + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data.setHeaders(httpHeaders); data.setLastModified(QDateTime::currentDateTime()); data.setExpirationDate(QDateTime::currentDateTime()); data.setSaveToDisk(false); @@ -235,6 +223,18 @@ void tst_QNetworkCacheMetaData::operatorEqualEqual_data() QTest::newRow("valid-5-4") << data5 << data2 << false; QTest::newRow("valid-5-5") << data5 << data3 << false; QTest::newRow("valid-5-6") << data5 << data4 << false; + + QNetworkCacheMetaData data6; + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data6.setHeaders(httpHeaders); + QTest::newRow("valid-6-1") << data6 << QNetworkCacheMetaData() << false; + QTest::newRow("valid-6-2") << data6 << data6 << true; + QTest::newRow("valid-6-3") << data6 << data1 << false; + QTest::newRow("valid-6-4") << data6 << data2 << false; + QTest::newRow("valid-6-5") << data6 << data3 << false; + QTest::newRow("valid-6-6") << data6 << data4 << false; + QTest::newRow("valid-6-7") << data6 << data5 << false; } // public bool operator==(QNetworkCacheMetaData const& other) const @@ -254,7 +254,11 @@ void tst_QNetworkCacheMetaData::rawHeaders_data() QTest::newRow("null") << QNetworkCacheMetaData::RawHeaderList(); QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); - QTest::newRow("valie") << headers; + QTest::newRow("valid") << headers; + headers.append(QNetworkCacheMetaData::RawHeader("n1", "V1, v2, v3")); + headers.append(QNetworkCacheMetaData::RawHeader("n2", "V2")); + headers.append(QNetworkCacheMetaData::RawHeader("set-cookie", "v1\nV2\nV3")); + QTest::newRow("valid-2") << headers; } // public QNetworkCacheMetaData::RawHeaderList rawHeaders() const @@ -268,6 +272,25 @@ void tst_QNetworkCacheMetaData::rawHeaders() QCOMPARE(data.rawHeaders(), rawHeaders); } +void tst_QNetworkCacheMetaData::headers_data() +{ + QTest::addColumn<QHttpHeaders>("httpHeaders"); + QTest::newRow("null") << QHttpHeaders(); + QHttpHeaders headers; + headers.append("foo", "Bar"); + QTest::newRow("valid") << headers; +} + +void tst_QNetworkCacheMetaData::headers() +{ + QFETCH(QHttpHeaders, httpHeaders); + + SubQNetworkCacheMetaData data; + + data.setHeaders(httpHeaders); + QCOMPARE(data.headers().toListOfPairs(), httpHeaders.toListOfPairs()); +} + void tst_QNetworkCacheMetaData::saveToDisk_data() { QTest::addColumn<bool>("saveToDisk"); @@ -312,6 +335,9 @@ void tst_QNetworkCacheMetaData::stream() QNetworkCacheMetaData::RawHeaderList headers; headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar")); data.setRawHeaders(headers); + QHttpHeaders httpHeaders; + httpHeaders.append("name", "value"); + data.setHeaders(httpHeaders); data.setLastModified(QDateTime::currentDateTime()); data.setExpirationDate(QDateTime::currentDateTime()); data.setSaveToDisk(false); diff --git a/tests/auto/network/access/qnetworkcookie/CMakeLists.txt b/tests/auto/network/access/qnetworkcookie/CMakeLists.txt index 0460ff3235..91773a83fd 100644 --- a/tests/auto/network/access/qnetworkcookie/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcookie/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qnetworkcookie.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkcookie Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcookie LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkcookie SOURCES tst_qnetworkcookie.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network ) diff --git a/tests/auto/network/access/qnetworkcookie/qnetworkcookie.pro b/tests/auto/network/access/qnetworkcookie/qnetworkcookie.pro deleted file mode 100644 index 320e3a81c5..0000000000 --- a/tests/auto/network/access/qnetworkcookie/qnetworkcookie.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkcookie -SOURCES += tst_qnetworkcookie.cpp - -QT = core network testlib diff --git a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp index 96c4917473..438c5e6983 100644 --- a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp +++ b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp @@ -1,36 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> -#include <QtCore/QUrl> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> #include <QtNetwork/QNetworkCookie> +#include <QtCore/QDateTime> +#include <QtCore/QTimeZone> +#include <QtCore/QUrl> class tst_QNetworkCookie: public QObject { @@ -44,6 +21,8 @@ private slots: void parseMultipleCookies_data(); void parseMultipleCookies(); + + void sameSite(); }; void tst_QNetworkCookie::getterSetter() @@ -108,6 +87,12 @@ void tst_QNetworkCookie::parseSingleCookie_data() { QTest::addColumn<QString>("cookieString"); QTest::addColumn<QNetworkCookie>("expectedCookie"); + const auto utc = [](int year, int month, int day, + int hour = 0, int minute = 0, int second = 0, int millis = 0) { + return QDateTime(QDate(year, month, day), + QTime(hour, minute, second, millis), + QTimeZone::UTC); + }; QNetworkCookie cookie; cookie.setName("a"); @@ -252,140 +237,140 @@ void tst_QNetworkCookie::parseSingleCookie_data() cookie = QNetworkCookie(); cookie.setName("a"); cookie.setValue("b"); - cookie.setExpirationDate(QDateTime(QDate(2012, 1, 29), QTime(23, 59, 59), Qt::UTC)); + cookie.setExpirationDate(utc(2012, 1, 29, 23, 59, 59)); QTest::newRow("broken-expiration1") << "a=b; expires=Sun, 29-Jan-2012 23:59:59;" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1999, 11, 9), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(1999, 11, 9, 23, 12, 40)); QTest::newRow("expiration1") << "a=b;expires=Wednesday, 09-Nov-1999 23:12:40 GMT" << cookie; QTest::newRow("expiration2") << "a=b;expires=Wed, 09-Nov-1999 23:12:40 GMT" << cookie; QTest::newRow("expiration3") << "a=b; expires=Wednesday, 09-Nov-1999 23:12:40 GMT " << cookie; QTest::newRow("expiration-utc") << "a=b;expires=Wednesday, 09-Nov-1999 23:12:40 UTC" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20)); QTest::newRow("time-0") << "a=b;expires=14 Apr 89 03:20" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 12, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20, 12)); QTest::newRow("time-1") << "a=b;expires=14 Apr 89 03:20:12" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20, 12, 88)); QTest::newRow("time-2") << "a=b;expires=14 Apr 89 03:20:12.88" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20, 12, 88)); QTest::newRow("time-3") << "a=b;expires=14 Apr 89 03:20:12.88am" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(15, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 15, 20, 12, 88)); QTest::newRow("time-4") << "a=b;expires=14 Apr 89 03:20:12.88pm" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20, 12, 88)); QTest::newRow("time-5") << "a=b;expires=14 Apr 89 03:20:12.88 Am" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(15, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 15, 20, 12, 88)); QTest::newRow("time-6") << "a=b;expires=14 Apr 89 03:20:12.88 PM" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(15, 20, 12, 88), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 15, 20, 12, 88)); QTest::newRow("time-7") << "a=b;expires=14 Apr 89 3:20:12.88 PM" << cookie; // normal months - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1, 1)); QTest::newRow("months-1") << "a=b;expires=Jan 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 2, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 2, 1, 1, 1)); QTest::newRow("months-2") << "a=b;expires=Feb 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 3, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 3, 1, 1, 1)); QTest::newRow("months-3") << "a=b;expires=mar 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 1, 1, 1)); QTest::newRow("months-4") << "a=b;expires=Apr 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 5, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 5, 1, 1, 1)); QTest::newRow("months-5") << "a=b;expires=May 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 6, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 6, 1, 1, 1)); QTest::newRow("months-6") << "a=b;expires=Jun 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 7, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 7, 1, 1, 1)); QTest::newRow("months-7") << "a=b;expires=Jul 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 8, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 8, 1, 1, 1)); QTest::newRow("months-8") << "a=b;expires=Aug 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 9, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 9, 1, 1, 1)); QTest::newRow("months-9") << "a=b;expires=Sep 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 10, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 10, 1, 1, 1)); QTest::newRow("months-10") << "a=b;expires=Oct 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 11, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 11, 1, 1, 1)); QTest::newRow("months-11") << "a=b;expires=Nov 1 89 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 12, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 12, 1, 1, 1)); QTest::newRow("months-12") << "a=b;expires=Dec 1 89 1:1" << cookie; // extra months - cookie.setExpirationDate(QDateTime(QDate(1989, 12, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 12, 1, 1, 1)); QTest::newRow("months-13") << "a=b;expires=December 1 89 1:1" << cookie; QTest::newRow("months-14") << "a=b;expires=1 89 1:1 Dec" << cookie; //cookie.setExpirationDate(QDateTime()); //QTest::newRow("months-15") << "a=b;expires=1 89 1:1 De" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2024, 2, 29), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(2024, 2, 29, 1, 1)); QTest::newRow("months-16") << "a=b;expires=2024 29 Feb 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2024, 2, 29), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(2024, 2, 29, 1, 1)); QTest::newRow("months-17") << "a=b;expires=Fri, 29-Feb-2024 01:01:00 GMT" << cookie; QTest::newRow("months-18") << "a=b;expires=2024 29 Feb 1:1 GMT" << cookie; // normal offsets - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-0") << "a=b;expires=Jan 1 89 8:0 PST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-1") << "a=b;expires=Jan 1 89 8:0 PDT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-2") << "a=b;expires=Jan 1 89 7:0 MST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-3") << "a=b;expires=Jan 1 89 7:0 MDT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-4") << "a=b;expires=Jan 1 89 6:0 CST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-5") << "a=b;expires=Jan 1 89 6:0 CDT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-6") << "a=b;expires=Jan 1 89 5:0 EST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-7") << "a=b;expires=Jan 1 89 5:0 EDT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-8") << "a=b;expires=Jan 1 89 4:0 AST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-9") << "a=b;expires=Jan 1 89 3:0 NST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-10") << "a=b;expires=Jan 1 89 0:0 GMT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-11") << "a=b;expires=Jan 1 89 0:0 BST" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 2)); QTest::newRow("zoneoffset-12") << "a=b;expires=Jan 1 89 23:0 MET" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 2)); QTest::newRow("zoneoffset-13") << "a=b;expires=Jan 1 89 22:0 EET" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 2)); QTest::newRow("zoneoffset-14") << "a=b;expires=Jan 1 89 15:0 JST" << cookie; // extra offsets - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 2)); QTest::newRow("zoneoffset-15") << "a=b;expires=Jan 1 89 15:0 JST+1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1)); QTest::newRow("zoneoffset-16") << "a=b;expires=Jan 1 89 0:0 GMT+1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-17") << "a=b;expires=Jan 1 89 1:0 GMT-1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1)); QTest::newRow("zoneoffset-18") << "a=b;expires=Jan 1 89 0:0 GMT+01" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 5), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1, 5)); QTest::newRow("zoneoffset-19") << "a=b;expires=Jan 1 89 0:0 GMT+0105" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-20") << "a=b;expires=Jan 1 89 0:0 GMT+015" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-21") << "a=b;expires=Jan 1 89 0:0 GM" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-22") << "a=b;expires=Jan 1 89 0:0 GMT" << cookie; // offsets from gmt - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1)); QTest::newRow("zoneoffset-23") << "a=b;expires=Jan 1 89 0:0 +1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1)); QTest::newRow("zoneoffset-24") << "a=b;expires=Jan 1 89 0:0 +01" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(1, 1), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1, 1, 1)); QTest::newRow("zoneoffset-25") << "a=b;expires=Jan 1 89 0:0 +0101" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 1)); QTest::newRow("zoneoffset-26") << "a=b;expires=Jan 1 89 1:0 -1" << cookie; // Y2k - cookie.setExpirationDate(QDateTime(QDate(2000, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2000, 1, 1)); QTest::newRow("year-0") << "a=b;expires=Jan 1 00 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1970, 1, 1)); QTest::newRow("year-1") << "a=b;expires=Jan 1 70 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1971, 1, 1), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1971, 1, 1)); QTest::newRow("year-2") << "a=b;expires=Jan 1 71 0:0" << cookie; // Day, month, year - cookie.setExpirationDate(QDateTime(QDate(2013, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2013, 1, 2)); QTest::newRow("date-0") << "a=b;expires=Jan 2 13 0:0" << cookie; QTest::newRow("date-1") << "a=b;expires=1-2-13 0:0" << cookie; QTest::newRow("date-2") << "a=b;expires=1/2/13 0:0" << cookie; @@ -395,141 +380,141 @@ void tst_QNetworkCookie::parseSingleCookie_data() QTest::newRow("date-6") << "a=b;expires=1/2/13 0:0" << cookie; // Known Year, determine month and day - cookie.setExpirationDate(QDateTime(QDate(1995, 1, 13), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 1, 13)); QTest::newRow("knownyear-0") << "a=b;expires=13/1/95 0:0" << cookie; QTest::newRow("knownyear-1") << "a=b;expires=95/13/1 0:0" << cookie; QTest::newRow("knownyear-2") << "a=b;expires=1995/1/13 0:0" << cookie; QTest::newRow("knownyear-3") << "a=b;expires=1995/13/1 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1995, 1, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 1, 2)); QTest::newRow("knownyear-4") << "a=b;expires=1/2/95 0:0" << cookie; QTest::newRow("knownyear-5") << "a=b;expires=95/1/2 0:0" << cookie; // Known Year, Known day, determining month - cookie.setExpirationDate(QDateTime(QDate(1995, 1, 13), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 1, 13)); QTest::newRow("knownYD-0") << "a=b;expires=13/1/95 0:0" << cookie; QTest::newRow("knownYD-1") << "a=b;expires=1/13/95 0:0" << cookie; QTest::newRow("knownYD-2") << "a=b;expires=95/13/1 0:0" << cookie; QTest::newRow("knownYD-3") << "a=b;expires=95/1/13 0:0" << cookie; // Month comes before Year - cookie.setExpirationDate(QDateTime(QDate(2021, 03, 26), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2021, 03, 26)); QTest::newRow("month-0") << "a=b;expires=26/03/21 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2015, 12, 30), QTime(16, 25, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2015, 12, 30, 16, 25)); QTest::newRow("month-1") << "a=b;expires=wed 16:25pm December 2015 30" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2031, 11, 11), QTime(16, 25, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 11, 11, 16, 25)); QTest::newRow("month-2") << "a=b;expires=16:25 11 31 11" << cookie; // The very ambiguous cases // Matching Firefox's behavior of guessing month, day, year in those cases - cookie.setExpirationDate(QDateTime(QDate(2013, 10, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2013, 10, 2)); QTest::newRow("ambiguousd-0") << "a=b;expires=10/2/13 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2013, 2, 10), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2013, 2, 10)); QTest::newRow("ambiguousd-1") << "a=b;expires=2/10/13 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2010, 2, 3), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2010, 2, 3)); QTest::newRow("ambiguousd-2") << "a=b;expires=2/3/10 0:0" << cookie; // FYI If you try these in Firefox it won't set a cookie for the following two string // because 03 is turned into the year at which point it is expired - cookie.setExpirationDate(QDateTime(QDate(2003, 2, 10), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2003, 2, 10)); QTest::newRow("ambiguousd-3") << "a=b;expires=2/10/3 0:0" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2003, 10, 2), QTime(0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2003, 10, 2)); QTest::newRow("ambiguousd-4") << "a=b;expires=10/2/3 0:0" << cookie; // These are the cookies that firefox's source says it can parse - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20)); QTest::newRow("firefox-0") << "a=b;expires=14 Apr 89 03:20" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 4, 14), QTime(3, 20, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 4, 14, 3, 20)); QTest::newRow("firefox-1") << "a=b;expires=14 Apr 89 03:20 GMT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 3, 17), QTime(4, 1, 33, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 3, 17, 4, 1, 33)); QTest::newRow("firefox-2") << "a=b;expires=Fri, 17 Mar 89 4:01:33" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 3, 17), QTime(4, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 3, 17, 4, 1)); QTest::newRow("firefox-3") << "a=b;expires=Fri, 17 Mar 89 4:01 GMT" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 16), QTime(16-8, 12, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 16, 16-8, 12)); QTest::newRow("firefox-4") << "a=b;expires=Mon Jan 16 16:12 PDT 1989" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1989, 1, 16), QTime(17, 42, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1989, 1, 16, 17, 42)); QTest::newRow("firefox-5") << "a=b;expires=Mon Jan 16 16:12 +0130 1989" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1992, 5, 6), QTime(16-9, 41, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1992, 5, 6, 16-9, 41)); QTest::newRow("firefox-6") << "a=b;expires=6 May 1992 16:41-JST (Wednesday)" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1993, 8, 22), QTime(10, 59, 12, 82), Qt::UTC)); + cookie.setExpirationDate(utc(1993, 8, 22, 10, 59, 12, 82)); QTest::newRow("firefox-7") << "a=b;expires=22-AUG-1993 10:59:12.82" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1993, 8, 22), QTime(22, 59, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1993, 8, 22, 22, 59)); QTest::newRow("firefox-8") << "a=b;expires=22-AUG-1993 10:59pm" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1993, 8, 22), QTime(12, 59, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1993, 8, 22, 12, 59)); QTest::newRow("firefox-9") << "a=b;expires=22-AUG-1993 12:59am" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1993, 8, 22), QTime(12, 59, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1993, 8, 22, 12, 59)); QTest::newRow("firefox-10") << "a=b;expires=22-AUG-1993 12:59 PM" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1995, 8, 4), QTime(15, 54, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 8, 4, 15, 54)); QTest::newRow("firefox-11") << "a=b;expires=Friday, August 04, 1995 3:54 PM" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1995, 6, 21), QTime(16, 24, 34, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 6, 21, 16, 24, 34)); QTest::newRow("firefox-12") << "a=b;expires=06/21/95 04:24:34 PM" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1995, 6, 20), QTime(21, 7, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 6, 20, 21, 7)); QTest::newRow("firefox-13") << "a=b;expires=20/06/95 21:07" << cookie; - cookie.setExpirationDate(QDateTime(QDate(1995, 6, 8), QTime(19-5, 32, 48, 0), Qt::UTC)); + cookie.setExpirationDate(utc(1995, 6, 8, 19-5, 32, 48)); QTest::newRow("firefox-14") << "a=b;expires=95-06-08 19:32:48 EDT" << cookie; // Edge cases caught by fuzzing // These are about the default cause creates dates that don't exits - cookie.setExpirationDate(QDateTime(QDate(2030, 2, 25), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2030, 2, 25, 1, 1)); QTest::newRow("fuzz-0") << "a=b; expires=30 -000002 1:1 25;" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2031, 11, 20), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 11, 20, 1, 1)); QTest::newRow("fuzz-1") << "a=b; expires=31 11 20 1:1;" << cookie; // April only has 30 days - cookie.setExpirationDate(QDateTime(QDate(2031, 4, 30), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 4, 30, 1, 1)); QTest::newRow("fuzz-2") << "a=b; expires=31 30 4 1:1" << cookie; // 9 must be the month so 31 can't be the day - cookie.setExpirationDate(QDateTime(QDate(2031, 9, 21), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 9, 21, 1, 1)); QTest::newRow("fuzz-3") << "a=b; expires=31 21 9 1:1" << cookie; // Year is known, then fallback to defaults of filling in month and day - cookie.setExpirationDate(QDateTime(QDate(2031, 11, 1), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 11, 1, 1, 1)); QTest::newRow("fuzz-4") << "a=b; expires=31 11 01 1:1" << cookie; // 2 must be the month so 30 can't be the day - cookie.setExpirationDate(QDateTime(QDate(2030, 2, 20), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2030, 2, 20, 1, 1)); QTest::newRow("fuzz-5") << "a=b; expires=30 02 20 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2021, 12, 22), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2021, 12, 22, 1, 1)); QTest::newRow("fuzz-6") << "a=b; expires=2021 12 22 1:1" << cookie; - cookie.setExpirationDate(QDateTime(QDate(2029, 2, 23), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2029, 2, 23, 1, 1)); QTest::newRow("fuzz-7") << "a=b; expires=29 23 Feb 1:1" << cookie; // 11 and 6 don't have 31 days - cookie.setExpirationDate(QDateTime(QDate(2031, 11, 06), QTime(1, 1, 0, 0), Qt::UTC)); + cookie.setExpirationDate(utc(2031, 11, 06, 1, 1)); QTest::newRow("fuzz-8") << "a=b; expires=31 11 06 1:1" << cookie; // two-digit years: // from 70 until 99, we assume 20th century - cookie.setExpirationDate(QDateTime(QDate(1999, 11, 9), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(1999, 11, 9, 23, 12, 40)); QTest::newRow("expiration-2digit1") << "a=b; expires=Wednesday, 09-Nov-99 23:12:40 GMT " << cookie; - cookie.setExpirationDate(QDateTime(QDate(1970, 1, 1), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(1970, 1, 1, 23, 12, 40)); QTest::newRow("expiration-2digit2") << "a=b; expires=Thursday, 01-Jan-70 23:12:40 GMT " << cookie; // from 00 until 69, we assume 21st century - cookie.setExpirationDate(QDateTime(QDate(2000, 1, 1), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(2000, 1, 1, 23, 12, 40)); QTest::newRow("expiration-2digit3") << "a=b; expires=Saturday, 01-Jan-00 23:12:40 GMT " << cookie; - cookie.setExpirationDate(QDateTime(QDate(2020, 1, 1), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(2020, 1, 1, 23, 12, 40)); QTest::newRow("expiration-2digit4") << "a=b; expires=Wednesday, 01-Jan-20 23:12:40 GMT " << cookie; - cookie.setExpirationDate(QDateTime(QDate(2069, 1, 1), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(2069, 1, 1, 23, 12, 40)); QTest::newRow("expiration-2digit5") << "a=b; expires=Wednesday, 01-Jan-69 23:12:40 GMT " << cookie; - cookie.setExpirationDate(QDateTime(QDate(1999, 11, 9), QTime(23, 12, 40), Qt::UTC)); + cookie.setExpirationDate(utc(1999, 11, 9, 23, 12, 40)); cookie.setPath("/"); QTest::newRow("expires+path") << "a=b; expires=Wed, 09-Nov-1999 23:12:40 GMT; path=/" << cookie; @@ -542,7 +527,7 @@ void tst_QNetworkCookie::parseSingleCookie_data() // cookies obtained from the network: cookie = QNetworkCookie("__siteid", "1"); cookie.setPath("/"); - cookie.setExpirationDate(QDateTime(QDate(9999, 12, 31), QTime(23, 59, 59), Qt::UTC)); + cookie.setExpirationDate(utc(9999, 12, 31, 23, 59, 59)); QTest::newRow("network2") << "__siteid=1; expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/" << cookie; cookie = QNetworkCookie("YM.LC", "v=2&m=9993_262838_159_1558_1063_0_5649_4012_3776161073,9426_260205_549_1295_1336_0_5141_4738_3922731647,6733_258196_952_1364_643_0_3560_-1_0,3677_237633_1294_1294_19267_0_3244_29483_4102206176,1315_235149_1693_1541_941_0_3224_1691_1861378060,1858_214311_2100_1298_19538_0_2873_30900_716411652,6258_212007_2506_1285_1017_0_2868_3606_4288540264,3743_207884_2895_1362_2759_0_2545_7114_3388520216,2654_205253_3257_1297_1332_0_2504_4682_3048534803,1891_184881_3660_1291_19079_0_978_29178_2592538685&f=1&n=20&s=date&o=down&e=1196548712&b=Inbox&u=removed"); @@ -552,13 +537,13 @@ void tst_QNetworkCookie::parseSingleCookie_data() cookie = QNetworkCookie("__ac", "\"c2hhdXNtYW46U2FTYW80Wm8%3D\""); cookie.setPath("/"); - cookie.setExpirationDate(QDateTime(QDate(2008, 8, 30), QTime(20, 21, 49), Qt::UTC)); + cookie.setExpirationDate(utc(2008, 8, 30, 20, 21, 49)); QTest::newRow("network4") << "__ac=\"c2hhdXNtYW46U2FTYW80Wm8%3D\"; Path=/; Expires=Sat, 30 Aug 2008 20:21:49 +0000" << cookie; // linkedin.com sends cookies in quotes and expects the cookie in quotes cookie = QNetworkCookie("leo_auth_token", "\"GST:UroVXaxYA3sVSkoVjMNH9bj4dZxVzK2yekgrAUxMfUsyLTNyPjoP60:1298974875:b675566ae32ab36d7a708c0efbf446a5c22b9fca\""); cookie.setPath("/"); - cookie.setExpirationDate(QDateTime(QDate(2011, 3, 1), QTime(10, 51, 14), Qt::UTC)); + cookie.setExpirationDate(utc(2011, 3, 1, 10, 51, 14)); QTest::newRow("network5") << "leo_auth_token=\"GST:UroVXaxYA3sVSkoVjMNH9bj4dZxVzK2yekgrAUxMfUsyLTNyPjoP60:1298974875:b675566ae32ab36d7a708c0efbf446a5c22b9fca\"; Version=1; Max-Age=1799; Expires=Tue, 01-Mar-2011 10:51:14 GMT; Path=/" << cookie; // cookie containing JSON data (illegal for server, client should accept) - QTBUG-26002 @@ -576,11 +561,11 @@ void tst_QNetworkCookie::parseSingleCookie() QList<QNetworkCookie> result = QNetworkCookie::parseCookies(cookieString.toUtf8()); //QEXPECT_FAIL("network2", "QDateTime parsing problem: the date is beyond year 8000", Abort); - QCOMPARE(result.count(), 1); + QCOMPARE(result.size(), 1); QCOMPARE(result.at(0), expectedCookie); result = QNetworkCookie::parseCookies(result.at(0).toRawForm()); - QCOMPARE(result.count(), 1); + QCOMPARE(result.size(), 1); // Drop any millisecond information, if there's any QDateTime dt = expectedCookie.expirationDate(); @@ -634,7 +619,7 @@ void tst_QNetworkCookie::parseMultipleCookies_data() cookie = QNetworkCookie("id", "51706646077999719"); cookie.setDomain(".bluestreak.com"); cookie.setPath("/"); - cookie.setExpirationDate(QDateTime(QDate(2017, 12, 05), QTime(9, 11, 7), Qt::UTC)); + cookie.setExpirationDate(QDateTime(QDate(2017, 12, 05), QTime(9, 11, 7), QTimeZone::UTC)); list << cookie; cookie.setName("bb"); cookie.setValue("\\\"K14144t\\\"_AAQ\\\"ototrK_A_ttot44AQ4KwoRQtoto|"); @@ -653,8 +638,8 @@ void tst_QNetworkCookie::parseMultipleCookies_data() cookieB.setValue("d"); // NewLine - cookieA.setExpirationDate(QDateTime(QDate(2009, 3, 10), QTime(7, 0, 0, 0), Qt::UTC)); - cookieB.setExpirationDate(QDateTime(QDate(2009, 3, 20), QTime(7, 0, 0, 0), Qt::UTC)); + cookieA.setExpirationDate(QDateTime(QDate(2009, 3, 10), QTime(7, 0), QTimeZone::UTC)); + cookieB.setExpirationDate(QDateTime(QDate(2009, 3, 20), QTime(7, 0), QTimeZone::UTC)); list = QList<QNetworkCookie>() << cookieA << cookieB; QTest::newRow("real-0") << "a=b; expires=Tue Mar 10 07:00:00 2009 GMT\nc=d; expires=Fri Mar 20 07:00:00 2009 GMT" << list; QTest::newRow("real-1") << "a=b; expires=Tue Mar 10 07:00:00 2009 GMT\n\nc=d; expires=Fri Mar 20 07:00:00 2009 GMT" << list; @@ -683,5 +668,16 @@ void tst_QNetworkCookie::parseMultipleCookies() QCOMPARE(result, expectedCookies); } +void tst_QNetworkCookie::sameSite() +{ + QList<QNetworkCookie> result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org")); + QCOMPARE(result.first().sameSitePolicy(), QNetworkCookie::SameSite::Default); + result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org;samesite=strict")); + QCOMPARE(result.first().sameSitePolicy(), QNetworkCookie::SameSite::Strict); + result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org;samesite=none;secure")); + QCOMPARE(result.first().sameSitePolicy(), QNetworkCookie::SameSite::None); + QCOMPARE(result.first().toRawForm(), QByteArrayLiteral("a=b; secure; SameSite=None; domain=qt-project.org")); + +} QTEST_MAIN(tst_QNetworkCookie) #include "tst_qnetworkcookie.moc" diff --git a/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt b/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt index 55f30633ee..0d74a1d84d 100644 --- a/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkcookiejar/CMakeLists.txt @@ -1,16 +1,23 @@ -# Generated from qnetworkcookiejar.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkcookiejar Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkcookiejar LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data -list(APPEND test_data "parser.json") +list(APPEND test_data "parser.json" "testdata/publicsuffix/public_suffix_list.dafsa") qt_internal_add_test(tst_qnetworkcookiejar SOURCES tst_qnetworkcookiejar.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Network Qt::NetworkPrivate diff --git a/tests/auto/network/access/qnetworkcookiejar/qnetworkcookiejar.pro b/tests/auto/network/access/qnetworkcookiejar/qnetworkcookiejar.pro deleted file mode 100644 index e679f8a930..0000000000 --- a/tests/auto/network/access/qnetworkcookiejar/qnetworkcookiejar.pro +++ /dev/null @@ -1,6 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkcookiejar -SOURCES += tst_qnetworkcookiejar.cpp - -QT = core core-private network network-private testlib -TESTDATA = parser.json 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 differnew file mode 100644 index 0000000000..f891b16963 --- /dev/null +++ b/tests/auto/network/access/qnetworkcookiejar/testdata/publicsuffix/public_suffix_list.dafsa diff --git a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp index 0257884b65..9460060dbf 100644 --- a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp +++ b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp @@ -1,33 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtCore/QJsonArray> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> @@ -37,14 +11,17 @@ #include <QtNetwork/QNetworkRequest> #if QT_CONFIG(topleveldomain) #include "private/qtldurl_p.h" -#include "private/qurltlds_p.h" #endif +#include <memory> + class tst_QNetworkCookieJar: public QObject { Q_OBJECT private slots: + void initTestCase(); + void getterSetter(); void setCookiesFromUrl_data(); void setCookiesFromUrl(); @@ -56,6 +33,8 @@ private slots: #endif void rfc6265_data(); void rfc6265(); +private: + QSharedPointer<QTemporaryDir> m_dataDir; }; class MyCookieJar: public QNetworkCookieJar @@ -83,6 +62,22 @@ void tst_QNetworkCookieJar::getterSetter() QCOMPARE(jar.allCookies(), list); } +void tst_QNetworkCookieJar::initTestCase() +{ +#if QT_CONFIG(topleveldomain) && QT_CONFIG(publicsuffix_system) + QString testDataDir; +#ifdef BUILTIN_TESTDATA + m_dataDir = QEXTRACTTESTDATA("/testdata"); + QVERIFY(m_dataDir); + testDataDir = m_dataDir->path() + "/testdata"; +#else + testDataDir = QFINDTESTDATA("testdata"); +#endif + qDebug() << "Test data dir:" << testDataDir; + qputenv("XDG_DATA_DIRS", QFile::encodeName(testDataDir)); +#endif +} + void tst_QNetworkCookieJar::setCookiesFromUrl_data() { QTest::addColumn<QList<QNetworkCookie> >("preset"); @@ -241,7 +236,7 @@ void tst_QNetworkCookieJar::setCookiesFromUrl() QFETCH(QList<QNetworkCookie>, preset); QFETCH(QNetworkCookie, newCookie); QFETCH(QString, referenceUrl); - QFETCH(QList<QNetworkCookie>, expectedResult); + QFETCH(const QList<QNetworkCookie>, expectedResult); QFETCH(bool, setCookies); QList<QNetworkCookie> cookieList; @@ -251,11 +246,11 @@ void tst_QNetworkCookieJar::setCookiesFromUrl() QCOMPARE(jar.setCookiesFromUrl(cookieList, referenceUrl), setCookies); QList<QNetworkCookie> result = jar.allCookies(); - foreach (QNetworkCookie cookie, expectedResult) { + for (const QNetworkCookie &cookie : expectedResult) { QVERIFY2(result.contains(cookie), cookie.toRawForm()); result.removeAll(cookie); } - QVERIFY2(result.isEmpty(), QTest::toString(result)); + QVERIFY2(result.isEmpty(), std::unique_ptr<char[]>(QTest::toString(result)).get()); } void tst_QNetworkCookieJar::cookiesForUrl_data() @@ -414,13 +409,11 @@ void tst_QNetworkCookieJar::effectiveTLDs_data() QTest::newRow("yes1") << "com" << true; QTest::newRow("yes2") << "de" << true; - QTest::newRow("yes3") << "ulm.museum" << true; QTest::newRow("yes4") << "krodsherad.no" << true; QTest::newRow("yes5") << "1.bg" << true; QTest::newRow("yes6") << "com.cn" << true; QTest::newRow("yes7") << "org.ws" << true; QTest::newRow("yes8") << "co.uk" << true; - QTest::newRow("yes9") << "wallonie.museum" << true; QTest::newRow("yes10") << "hk.com" << true; QTest::newRow("yes11") << "hk.org" << true; @@ -437,33 +430,23 @@ void tst_QNetworkCookieJar::effectiveTLDs_data() QTest::newRow("no11") << "mosreg.ru" << false; const char16_t s1[] = {0x74, 0x72, 0x61, 0x6e, 0xf8, 0x79, 0x2e, 0x6e, 0x6f, 0x00}; // xn--trany-yua.no - const char16_t s2[] = {0x5d9, 0x5e8, 0x5d5, 0x5e9, 0x5dc, 0x5d9, 0x5dd, 0x2e, 0x6d, 0x75, 0x73, 0x65, 0x75, 0x6d, 0x00}; // xn--9dbhblg6di.museum const char16_t s3[] = {0x7ec4, 0x7e54, 0x2e, 0x68, 0x6b, 0x00}; // xn--mk0axi.hk const char16_t s4[] = {0x7f51, 0x7edc, 0x2e, 0x63, 0x6e, 0x00}; // xn--io0a7i.cn const char16_t s5[] = {0x72, 0xe1, 0x68, 0x6b, 0x6b, 0x65, 0x72, 0xe1, 0x76, 0x6a, 0x75, 0x2e, 0x6e, 0x6f, 0x00}; // xn--rhkkervju-01af.no const char16_t s6[] = {0xb9a, 0xbbf, 0xb99, 0xbcd, 0xb95, 0xbaa, 0xbcd, 0xbaa, 0xbc2, 0xbb0, 0xbcd, 0x00}; // xn--clchc0ea0b2g2a9gcd const char16_t s7[] = {0x627, 0x644, 0x627, 0x631, 0x62f, 0x646, 0x00}; // xn--mgbayh7gpa - const char16_t s8[] = {0x63, 0x6f, 0x72, 0x72, 0x65, 0x69, 0x6f, 0x73, 0x2d, 0x65, 0x2d, 0x74, 0x65, 0x6c, 0x65, - 0x63, 0x6f, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0xe7, 0xf5, 0x65, 0x73, 0x2e, 0x6d, 0x75, - 0x73, 0x65, 0x75, 0x6d, 0x00}; // xn--correios-e-telecomunicaes-ghc29a.museum QTest::newRow("yes-specialchars1") << QString::fromUtf16(s1) << true; - QTest::newRow("yes-specialchars2") << QString::fromUtf16(s2) << true; QTest::newRow("yes-specialchars3") << QString::fromUtf16(s3) << true; QTest::newRow("yes-specialchars4") << QString::fromUtf16(s4) << true; QTest::newRow("yes-specialchars5") << QString::fromUtf16(s5) << true; QTest::newRow("yes-specialchars6") << QString::fromUtf16(s6) << true; QTest::newRow("yes-specialchars7") << QString::fromUtf16(s7) << true; - QTest::newRow("yes-specialchars8") << QString::fromUtf16(s8) << true; QTest::newRow("no-specialchars1") << QString::fromUtf16(s1).prepend("something") << false; - QTest::newRow("no-specialchars2") << QString::fromUtf16(s2).prepend(QString::fromUtf16(s2)) << false; - QTest::newRow("no-specialchars2.5") << QString::fromUtf16(s2).prepend("whatever") << false; QTest::newRow("no-specialchars3") << QString::fromUtf16(s3).prepend("foo") << false; QTest::newRow("no-specialchars4") << QString::fromUtf16(s4).prepend("bar") << false; - QTest::newRow("no-specialchars5") << QString::fromUtf16(s5).prepend(QString::fromUtf16(s2)) << false; QTest::newRow("no-specialchars6") << QString::fromUtf16(s6).prepend(QLatin1Char('.') + QString::fromUtf16(s6)) << false; QTest::newRow("no-specialchars7") << QString::fromUtf16(s7).prepend("bla") << false; - QTest::newRow("no-specialchars8") << QString::fromUtf16(s8).append("foo") << false; QTest::newRow("exception1") << "pref.iwate.jp" << false; QTest::newRow("exception2") << "omanpost.om" << false; @@ -479,23 +462,6 @@ void tst_QNetworkCookieJar::effectiveTLDs_data() QTest::newRow("yes-wildcard5") << "foo.sch.uk" << true; QTest::newRow("yes-platform.sh") << "eu.platform.sh" << true; QTest::newRow("no-platform.sh") << "something.platform.sh" << false; - - int i; - for (i = 0; tldIndices[i] < tldChunks[0]; i++) { } - Q_ASSERT(i < tldCount); - int TLDsInFirstChunk = i; - - const char *lastGroupFromFirstChunk = &tldData[0][tldIndices[TLDsInFirstChunk - 1]]; - QTest::addRow("lastGroupFromFirstChunk: %s", lastGroupFromFirstChunk) - << lastGroupFromFirstChunk - << true; - - Q_ASSERT(tldChunkCount > 1); // There are enough TLDs to fill 64K bytes - const char *lastGroupFromLastChunk = - &tldData[tldChunkCount-1][tldIndices[tldCount - 1] - tldChunks[tldChunkCount - 2]]; - QTest::addRow("lastGroupFromLastChunk: %s", lastGroupFromLastChunk) - << lastGroupFromLastChunk - << true; } void tst_QNetworkCookieJar::effectiveTLDs() @@ -551,7 +517,7 @@ void tst_QNetworkCookieJar::rfc6265_data() void tst_QNetworkCookieJar::rfc6265() { - QFETCH(QStringList, received); + QFETCH(const QStringList, received); QFETCH(QList<QNetworkCookie>, sent); QFETCH(QString, sentTo); @@ -562,16 +528,16 @@ void tst_QNetworkCookieJar::rfc6265() QNetworkCookieJar jar; QList<QNetworkCookie> receivedCookies; - foreach (const QString &cookieLine, received) + for (const QString &cookieLine : received) receivedCookies.append(QNetworkCookie::parseCookies(cookieLine.toUtf8())); jar.setCookiesFromUrl(receivedCookies, receivedUrl); QList<QNetworkCookie> cookiesToSend = jar.cookiesForUrl(sentUrl); //compare cookies only using name/value, as the metadata isn't sent over the network - QCOMPARE(cookiesToSend.count(), sent.count()); + QCOMPARE(cookiesToSend.size(), sent.size()); bool ok = true; - for (int i = 0; i < cookiesToSend.count(); i++) { + for (int i = 0; i < cookiesToSend.size(); i++) { if (cookiesToSend.at(i).name() != sent.at(i).name()) { ok = false; break; diff --git a/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt b/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt index af58132fb3..023868f57e 100644 --- a/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkdiskcache/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qnetworkdiskcache.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkdiskcache Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkdiskcache LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkdiskcache SOURCES tst_qnetworkdiskcache.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network ) diff --git a/tests/auto/network/access/qnetworkdiskcache/qnetworkdiskcache.pro b/tests/auto/network/access/qnetworkdiskcache/qnetworkdiskcache.pro deleted file mode 100644 index 4a78544bf8..0000000000 --- a/tests/auto/network/access/qnetworkdiskcache/qnetworkdiskcache.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkdiskcache -QT = core network testlib -SOURCES += tst_qnetworkdiskcache.cpp diff --git a/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp b/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp index cb94900660..ec32c780cd 100644 --- a/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp +++ b/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp @@ -1,42 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + #include <QtNetwork/QtNetwork> +#include <QTest> +#include <QTestEventLoop> #include <qnetworkdiskcache.h> #include <qrandom.h> #include <algorithm> #define EXAMPLE_URL "http://user:pass@localhost:4/#foo" +#define EXAMPLE_URL2 "http://user:pass@localhost:4/bar" //cached objects are organized into these many subdirs -#define NUM_SUBDIRECTORIES 16 +#define NUM_SUBDIRECTORIES 15 class tst_QNetworkDiskCache : public QObject { @@ -141,7 +117,7 @@ class SubQNetworkDiskCache : public QNetworkDiskCache public: ~SubQNetworkDiskCache() { - if (!cacheDirectory().isEmpty()) + if (!cacheDirectory().isEmpty() && clearOnDestruction) clear(); } @@ -170,6 +146,11 @@ public: d->write("Hello World!"); insert(d); } + + void setClearCacheOnDestruction(bool value) { clearOnDestruction = value; } + +private: + bool clearOnDestruction = true; }; tst_QNetworkDiskCache::tst_QNetworkDiskCache() @@ -241,17 +222,39 @@ void tst_QNetworkDiskCache::prepare() // public qint64 cacheSize() const void tst_QNetworkDiskCache::cacheSize() { - SubQNetworkDiskCache cache; - cache.setCacheDirectory(tempDir.path()); - QCOMPARE(cache.cacheSize(), qint64(0)); + qint64 cacheSize = 0; + { + SubQNetworkDiskCache cache; + cache.setCacheDirectory(tempDir.path()); + QCOMPARE(cache.cacheSize(), qint64(0)); - QUrl url(EXAMPLE_URL); - QNetworkCacheMetaData metaData; - metaData.setUrl(url); - QIODevice *d = cache.prepare(metaData); - cache.insert(d); - QVERIFY(cache.cacheSize() > qint64(0)); + { + QUrl url(EXAMPLE_URL); + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + QIODevice *d = cache.prepare(metaData); + cache.insert(d); + cacheSize = cache.cacheSize(); + QVERIFY(cacheSize > qint64(0)); + } + // Add a second item, some difference in behavior when the cache is not empty + { + QUrl url(EXAMPLE_URL2); + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + QIODevice *d = cache.prepare(metaData); + cache.insert(d); + QVERIFY(cache.cacheSize() > cacheSize); + cacheSize = cache.cacheSize(); + } + // Don't clear the cache on destruction so we can re-open the cache and test its size. + cache.setClearCacheOnDestruction(false); + } + + SubQNetworkDiskCache cache; + cache.setCacheDirectory(tempDir.path()); + QCOMPARE(cache.cacheSize(), cacheSize); cache.clear(); QCOMPARE(cache.cacheSize(), qint64(0)); } @@ -275,17 +278,17 @@ void tst_QNetworkDiskCache::clear() QVERIFY(cache.cacheSize() > qint64(0)); QString cacheDirectory = cache.cacheDirectory(); - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3); cache.clear(); - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 2); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 2); // don't delete files that it didn't create QTemporaryFile file(cacheDirectory + "/XXXXXX"); if (file.open()) { file.fileName(); // make sure it exists with a name - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3); cache.clear(); - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3); } } @@ -352,9 +355,9 @@ void tst_QNetworkDiskCache::remove() QUrl url(EXAMPLE_URL); cache.setupWithOne(tempDir.path(), url); QString cacheDirectory = cache.cacheDirectory(); - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3); cache.remove(url); - QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 2); + QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 2); } void tst_QNetworkDiskCache::accessAfterRemove() // QTBUG-17400 @@ -473,9 +476,9 @@ void tst_QNetworkDiskCache::fileMetaData() url.setFragment(QString()); QString cacheDirectory = cache.cacheDirectory(); - QStringList list = countFiles(cacheDirectory); - QCOMPARE(list.count(), NUM_SUBDIRECTORIES + 3); - foreach(QString fileName, list) { + const QStringList list = countFiles(cacheDirectory); + QCOMPARE(list.size(), NUM_SUBDIRECTORIES + 3); + for (const QString &fileName : list) { QFileInfo info(fileName); if (info.isFile()) { QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName); @@ -518,9 +521,9 @@ void tst_QNetworkDiskCache::expire() } QString cacheDirectory = cache.cacheDirectory(); - QStringList list = countFiles(cacheDirectory); + const QStringList list = countFiles(cacheDirectory); QStringList cacheList; - foreach(QString fileName, list) { + for (const QString &fileName : list) { QFileInfo info(fileName); if (info.isFile()) { QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName); @@ -528,7 +531,7 @@ void tst_QNetworkDiskCache::expire() } } std::sort(cacheList.begin(), cacheList.end()); - for (int i = 0; i < cacheList.count(); ++i) { + for (int i = 0; i < cacheList.size(); ++i) { QString fileName = cacheList[i]; QCOMPARE(fileName, QLatin1String("http://localhost:4/") + QString::number(i + 6)); } @@ -566,11 +569,11 @@ void tst_QNetworkDiskCache::oldCacheVersionFile() QVERIFY(!metaData.isValid()); QVERIFY(!QFile::exists(name)); } else { - QStringList files = countFiles(cache.cacheDirectory()); - QCOMPARE(files.count(), NUM_SUBDIRECTORIES + 3); + const QStringList files = countFiles(cache.cacheDirectory()); + QCOMPARE(files.size(), NUM_SUBDIRECTORIES + 3); // find the file QString cacheFile; - foreach (QString file, files) { + for (const QString &file : files) { QFileInfo info(file); if (info.isFile()) cacheFile = file; @@ -607,8 +610,8 @@ void tst_QNetworkDiskCache::streamVersion() QString cacheFile; // find the file - QStringList files = countFiles(cache.cacheDirectory()); - foreach (const QString &file, files) { + const QStringList files = countFiles(cache.cacheDirectory()); + for (const QString &file : files) { QFileInfo info(file); if (info.isFile()) { cacheFile = file; @@ -654,6 +657,7 @@ void tst_QNetworkDiskCache::streamVersion() QIODevice *dataDevice = cache.data(url); QVERIFY(dataDevice != 0); QByteArray cachedData = dataDevice->readAll(); + delete dataDevice; QCOMPARE(cachedData, data); } } @@ -690,8 +694,6 @@ public: QNetworkDiskCache cache; cache.setCacheDirectory(cachePath); - int read = 0; - int i = 0; for (; i < 5000; ++i) { if (other && other->isFinished()) @@ -733,7 +735,7 @@ public: if (d) { QByteArray x = d->readAll(); if (x != longString && x != longString2) { - qDebug() << x.length() << QString(x); + qDebug() << x.size() << QString(x); gotMetaData = cache.metaData(url); qDebug() << (gotMetaData.url().toString()) << gotMetaData.lastModified() @@ -742,7 +744,6 @@ public: } if (gotMetaData.isValid()) QVERIFY(x == longString || x == longString2); - read++; delete d; } } @@ -750,9 +751,8 @@ public: cache.remove(url); if (QRandomGenerator::global()->bounded(5) == 1) cache.clear(); - sleep(0); + sleep(std::chrono::seconds{0}); } - //qDebug() << "read!" << read << i; } QDateTime dt; diff --git a/tests/auto/network/access/qnetworkreply/4G.br b/tests/auto/network/access/qnetworkreply/4G.br Binary files differnew file mode 100644 index 0000000000..dabd553ed7 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply/4G.br diff --git a/tests/auto/network/access/qnetworkreply/BLACKLIST b/tests/auto/network/access/qnetworkreply/BLACKLIST index 4f356a8596..a4c7c1ee30 100644 --- a/tests/auto/network/access/qnetworkreply/BLACKLIST +++ b/tests/auto/network/access/qnetworkreply/BLACKLIST @@ -1,34 +1,11 @@ # See qtbase/src/testlib/qtestblacklist.cpp for format -[backgroundRequestInterruption] -opensuse-leap -windows-10 msvc-2015 -b2qt -ubuntu -osx -[backgroundRequestInterruption:ftp, bg, nobg] -* [getErrors:ftp-host] linux -[getFromHttpIntoBuffer] -osx -[getFromHttpIntoBuffer2] -windows-10 -[headFromHttp] -windows-10 msvc-2017 [ioPostToHttpFromSocket] -# QTBUG-66247 -windows-7sp1 -windows-10 msvc-2017 osx -[ioHttpRedirect] -# QTBUG-66602 -windows-10 [ioHttpRedirectMultipartPost] -# QTBUG-66247 b2qt -windows-10 msvc-2015 -ubuntu -rhel +linux [ioHttpRedirectPolicy] opensuse-leap b2qt @@ -36,18 +13,10 @@ ubuntu windows-10 [putToFtp] windows-10 -[putWithServerClosingConnectionImmediately] -windows-7sp1 -windows-10 -osx [backgroundRequest] macos -[connectToIPv6Address] -macos [deleteFromHttp] macos -[downloadProgress] -macos [httpCanReadLine] macos [httpRecursiveCreation] @@ -56,8 +25,6 @@ osx macos [ioGetFromBuiltinHttp] osx -[ioGetFromHttp] -macos [ioPostToHttpFromFile] macos [ioPostToHttpFromSocketSynchronous] @@ -68,35 +35,18 @@ osx macos [lastModifiedHeaderForHttp] macos -[multipartSkipIndices] -macos -[nestedEventLoops] -osx -[postToHttp] -macos -[postToHttpMultipart] -macos [postToHttpSynchronous] macos -[putGetDeleteGetFromHttp] -macos [putToHttpSynchronous] macos [putToHttpsSynchronous] osx -[putWithRateLimiting] -macos -[qtbug13431replyThrottling] -macos -[receiveCookiesFromHttp] -osx [receiveCookiesFromHttpSynchronous] osx -[sendCookies] -osx [sendCookiesSynchronous] osx -[sendCustomRequestToHttp] -macos [backgroundRequestConnectInBackground] osx +#QTBUG-103055 +[ioGetFromHttpWithProxyAuth] +qnx diff --git a/tests/auto/network/access/qnetworkreply/CMakeLists.txt b/tests/auto/network/access/qnetworkreply/CMakeLists.txt index 3b138b3918..9bfd90cd56 100644 --- a/tests/auto/network/access/qnetworkreply/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkreply/CMakeLists.txt @@ -1,4 +1,11 @@ -# Generated from qnetworkreply.pro. +# Copyright (C) 2022 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_qnetworkreply LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() add_subdirectory(echo) add_subdirectory(test) diff --git a/tests/auto/network/access/qnetworkreply/certs/qt-test-server-cacert.pem b/tests/auto/network/access/qnetworkreply/certs/qt-test-server-cacert.pem index c5aea0d7c9..29fd755de7 100644 --- a/tests/auto/network/access/qnetworkreply/certs/qt-test-server-cacert.pem +++ b/tests/auto/network/access/qnetworkreply/certs/qt-test-server-cacert.pem @@ -1,17 +1,17 @@ -----BEGIN CERTIFICATE----- -MIICpzCCAhACCQCzAF1hyRVzAjANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC -Tk8xDTALBgNVBAgTBE9zbG8xDTALBgNVBAcTBE9zbG8xDjAMBgNVBAoTBU5va2lh -MTUwMwYDVQQLFCxRdCBTb2Z0d2FyZS9lbWFpbEFkZHJlc3M9bm9ib2R5QG5vZG9t -YWluLm9yZzEjMCEGA1UEAxMacXQtdGVzdC1zZXJ2ZXIucXQtdGVzdC1uZXQwHhcN -MTkwNjI0MTI0OTIxWhcNMjIwNjIzMTI0OTIxWjCBlzELMAkGA1UEBhMCTk8xDTAL -BgNVBAgTBE9zbG8xDTALBgNVBAcTBE9zbG8xDjAMBgNVBAoTBU5va2lhMTUwMwYD -VQQLFCxRdCBTb2Z0d2FyZS9lbWFpbEFkZHJlc3M9bm9ib2R5QG5vZG9tYWluLm9y -ZzEjMCEGA1UEAxMacXQtdGVzdC1zZXJ2ZXIucXQtdGVzdC1uZXQwgZ8wDQYJKoZI -hvcNAQEBBQADgY0AMIGJAoGBAM2q22/WNMmn8cC+5EEYGeICySLmp9W6Ay6eKHr0 -Xxp3X3epETuPfvAuxp7rOtkS18EMUegkUj8jw0IMEcbyHKFC/rTCaYOt93CxGBXM -IChiMPAsFeYzGa/D6xzAkfcRaJRQ+Ek3CDLXPnXfo7xpABXezYcPXAJrgsgBfWrw -HdxzAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEASCKbqEX5ysC549mq90ydk4jyDW3m -PUyet01fKpcRqVs+OJxdExFBTra3gho6WzzpTSPsuX2ZKOLF5k6KkCvdCGvhC1Kv -HHPIExurfzvdlSRzj6HbKyPuSfxyOloH0bBp7/Gg5RIuBPKlbmfbnTLtwEjhhbMU -SoYI8HZd3HfY87c= +MIICyjCCAjMCFHPGDqJR+klHni4XbETMk6GLn/UEMA0GCSqGSIb3DQEBDQUAMIGj +MRcwFQYDVQQKEw5UaGUgUXQgQ29tcGFueTEUMBIGA1UECxMLUXQgU29mdHdhcmUx +IjAgBgkqhkiG9w0BCQEWE25vYm9keUBub2RvbWFpbi5vcmcxDTALBgNVBAcTBE9z +bG8xDTALBgNVBAgTBE9zbG8xCzAJBgNVBAYTAk5PMSMwIQYDVQQDExpxdC10ZXN0 +LXNlcnZlci5xdC10ZXN0LW5ldDAeFw0yMjA2MjQxMTU4NDlaFw0zMjA2MjExMTU4 +NDlaMIGjMRcwFQYDVQQKEw5UaGUgUXQgQ29tcGFueTEUMBIGA1UECxMLUXQgU29m +dHdhcmUxIjAgBgkqhkiG9w0BCQEWE25vYm9keUBub2RvbWFpbi5vcmcxDTALBgNV +BAcTBE9zbG8xDTALBgNVBAgTBE9zbG8xCzAJBgNVBAYTAk5PMSMwIQYDVQQDExpx +dC10ZXN0LXNlcnZlci5xdC10ZXN0LW5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAzarbb9Y0yafxwL7kQRgZ4gLJIuan1boDLp4oevRfGndfd6kRO49+8C7G +nus62RLXwQxR6CRSPyPDQgwRxvIcoUL+tMJpg633cLEYFcwgKGIw8CwV5jMZr8Pr +HMCR9xFolFD4STcIMtc+dd+jvGkAFd7Nhw9cAmuCyAF9avAd3HMCAwEAATANBgkq +hkiG9w0BAQ0FAAOBgQCZyRe25WqOjrNS6BKPs7ep7eyCON3NKdWnfABZrSjGJQ87 +PoFKl6+9YBSlSpl8qk7c29ic+wA4qFQzPJkrbYIXjwVMAr+cC1kVrlUVqcwmvnKo +5vj57/v8S0Uc4/GesIsxZR7QM+3diPDyk7Bsc3IkpINb31Dl0mlg25nztg8NxA== -----END CERTIFICATE----- diff --git a/tests/auto/network/access/qnetworkreply/certs/server.key b/tests/auto/network/access/qnetworkreply/certs/server.key index 9d1664d609..b8d0d0449b 100644 --- a/tests/auto/network/access/qnetworkreply/certs/server.key +++ b/tests/auto/network/access/qnetworkreply/certs/server.key @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCnyKBKxBkFG2a6MuLS8RxvF4LkOS4BUZDbBDQyESHCDW9Z2FOQ -VD+Dj6nTs9XuGpuArsMlyV6lr0tgBaqg0ZEBH8oEg+NYHJkyRYRwclgDmEpji0H1 -CEnSkQJga+Rk/t2gqnQI6TRMkV8SPTdNVCytf1uYYDYCjDv2RfMnapuUnQIDAQAB -AoGANFzLkanTeSGNFM0uttBipFT9F4a00dqHz6JnO7zXAT26I5r8sU1pqQBb6uLz -/+Qz5Zwk8RUAQcsMRgJetuPQUb0JZjF6Duv24hNazqXBCu7AZzUenjafwmKC/8ri -KpX3fTwqzfzi//FKGgbXQ80yykSSliDL3kn/drATxsLCgQECQQDXhEFWLJ0vVZ1s -1Ekf+3NITE+DR16X+LQ4W6vyEHAjTbaNWtcTKdAWLA2l6N4WAAPYSi6awm+zMxx4 -VomVTsjdAkEAx0z+e7natLeFcrrq8pbU+wa6SAP1VfhQWKitxL1e7u/QO90NCpxE -oQYKzMkmmpOOFjQwEMAy1dvFMbm4LHlewQJAC/ksDBaUcQHHqjktCtrUb8rVjAyW -A8lscckeB2fEYyG5J6dJVaY4ClNOOs5yMDS2Afk1F6H/xKvtQ/5CzInA/QJATDub -K+BPU8jO9q+gpuIi3VIZdupssVGmCgObVCHLakG4uO04y9IyPhV9lA9tALtoIf4c -VIvv5fWGXBrZ48kZAQJBAJmVCdzQxd9LZI5vxijUCj5EI4e+x5DRqVUvyP8KCZrC -AiNyoDP85T+hBZaSXK3aYGpVwelyj3bvo1GrTNwNWLw= +MIIEowIBAAKCAQEAyinVk3QBbjS+UczWP+jnugFn5YZuOnCPlPK0SmeUiZW0x4PA +kXoks7LSra+XT2hg07rPBhEyQUE13qYw+RVBSexvhw2RDg76oV17jt7jVjb04hhK +bSBKisW4UHF0rvyzoWzJzVxaqxfcFcYT7uE+t0cnCHi/MGX+9gUI8Dz46IopCA5k +fKmA+XnF//Ov8wokIN4Wk0lqkAyWDCg/O5Av6H/zbr/U3CCI5eI5cRRIwSMDxbPX +v9b+dgvxhMJGMku6UMMhSfk9ac6FCSNghYB7w3C9zIGiA/tOHysujwGzpzRKDzT5 +P/qNqLkLOxvspUh32BD/jgopAhoNi9pDm60iLQIDAQABAoIBAA/E45v02Ie4JYBL +8gpaKHkh0vDcY4y7ave7VsTW/4cb3lYRuNugI2zA7h4OLEdNZQAe+jcG8FyWsZUE +cZ18QvN5Ndna/Q2TrYkYuaKTUDhRYRihvGx2sFnSwmXD884SeBCHY9ZY9dmSquAn +6zYe671wF2NZx9AGpLSb/+59Uw0QVkCDf23tb7ey5vHXJnNq5NINOnv1sNH/zbYR +hJnUEVgRLkpda0r2LqIHbrCpcgjWQeoKscTzxTI016LAozBSqAvoLt4QYuvY8kI7 +boK8KF49HEwTydjgDI/W3Xa0YEzbXVLEReuWoMKFeayNp+GSFy0SwkzjY4zpUP1N +xX6/2CECgYEA83fifDH8e4g1y8MuI8LzDRsMPOsvl3ZnB31JAcvUlLheVF1H3Slt +NEGSKYtx4zb0o+5VKy6k6dqF8VXDcPDyyvItyZYZtv7YLIhu/HBJypr9NfdICNnK +aPQWRZ/piAEi73vxx1qwIZapz1cJWg95mRv/QYVf8Xb7PgKcu5UL+ysCgYEA1JGv +t1gNsKc4BtHmYmTnzdxz4GhkgJY1y/XfGzc2CfRPxo16Fob1WqQCTf1DtsCm0zTi +sJdUBq/acMeeyTA6eA4LyfgEVVRVY4+kurW7JNGkR6xbWtWQX11jkVGOZ65MIvtY +ZMg3xo3w+hYvMhK0ZC9aSXgnl0crvGAJtd8ZzAcCgYAwnTmODvUZPYNwYlKuNVkO +vt3ctCFWnv/HkQ6o2yhhYccEFXQqBwGVM5qZzQw6kFic+xPqgW/Qeh/Qpo1V2ebA ++0aFQAF2dsB3c+6lXU5+tB/nTK8HhWVTO5nO4TViQMfXBeqrIcKVkl3p1rk5UGm5 +VsvLK3SS5G0aXq8pDYPM7QKBgQChsQvjP8RyGlCAx4siTzUQH1+5VE8WjKvxMF58 +OjwNyFwiYR18Iz5gqx7hqgOm8NY1FCZXQ1T0HTHg1cdPrDLdfXm0MMdDDPpC2FHq +gDARarI2nsGCz66ZC9WgBVR4Q1nAxkXPq4jZrMCfyt4tjZLQHkDkX9RluwpmqPrZ +8BGUYwKBgB1u8mPXIxyGSHYitqf40eIr5yzlrCgDWoqRQf4jFSUNyB/+YT2VqIXu +WfixkX9WW0sx/c79c9791Sf+vp9+DPPtMYDGc6y0xrbxyC+yT7Uo4Azbs/g3Kftl +WhYt/L1CB5oOcilYGR+YodN0l2tV1WrCNSNdtbPyoDHrM9S22Rjm -----END RSA PRIVATE KEY----- diff --git a/tests/auto/network/access/qnetworkreply/certs/server.pem b/tests/auto/network/access/qnetworkreply/certs/server.pem index 67eb495319..47c22d9f29 100644 --- a/tests/auto/network/access/qnetworkreply/certs/server.pem +++ b/tests/auto/network/access/qnetworkreply/certs/server.pem @@ -1,24 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIEEzCCAvugAwIBAgIBADANBgkqhkiG9w0BAQUFADCBnDELMAkGA1UEBhMCTk8x -DTALBgNVBAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xs -dGVjaCBBU0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50 -cm9sbC5ubzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbTAe -Fw0wNzEyMDQwMTEwMzJaFw0zNTA0MjEwMTEwMzJaMGMxCzAJBgNVBAYTAk5PMQ0w -CwYDVQQIEwRPc2xvMRYwFAYDVQQKEw1Ucm9sbHRlY2ggQVNBMRQwEgYDVQQLEwtE -ZXZlbG9wbWVudDEXMBUGA1UEAxMOZmx1a2UudHJvbGwubm8wgZ8wDQYJKoZIhvcN -AQEBBQADgY0AMIGJAoGBAKfIoErEGQUbZroy4tLxHG8XguQ5LgFRkNsENDIRIcIN -b1nYU5BUP4OPqdOz1e4am4CuwyXJXqWvS2AFqqDRkQEfygSD41gcmTJFhHByWAOY -SmOLQfUISdKRAmBr5GT+3aCqdAjpNEyRXxI9N01ULK1/W5hgNgKMO/ZF8ydqm5Sd -AgMBAAGjggEaMIIBFjAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM -IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUIYUEPSMBZuX3nxqEJIqv -Cnn05awwgbsGA1UdIwSBszCBsKGBoqSBnzCBnDELMAkGA1UEBhMCTk8xDTALBgNV -BAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xsdGVjaCBB -U0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50cm9sbC5u -bzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbYIJAI6otOiR -t1QuMA0GCSqGSIb3DQEBBQUAA4IBAQBtV1/RBUPwYgXsKnGl3BkI8sSmvbsl2cqJ -AQ7kzx/BjMgkGDVTWXvAQ7Qy5piypu8VBQtIX+GgDJepoXfYNRgwvKmP07dUx/Gp -nl3mGb/2PFsr2OQ+YhiIi9Mk4UCbDOYpFmKr6gUkcDaqVZPvAoEbIxCiBOtWlXX8 -+JSxXULFPzZEhV06LpBGiqK5b4euDBVAGTGQ/Dslu67xZhMNhZDZSTSP8l35ettN -XSf2dp01jAamTKOxsrZvHdejAP1y657qRKGvITR9x0LiSZEZi8CtuoKAqHFw9DUx -kWOEIJXpYK9ki8z/PYp2dD3IVW3kjsMrHOhCGK6f5mucNAbsavLD +MIIDrzCCApcCFGtyyXYbHuuIIseKAiLxL5nMKEnwMA0GCSqGSIb3DQEBCwUAMIGT +MQswCQYDVQQGEwJOTzENMAsGA1UECAwET3NsbzEQMA4GA1UEBwwHTnlkYWxlbjEN +MAsGA1UECgwEVFF0QzEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxFzAVBgNVBAMMDmZs +dWtlLnRyb2xsLm5vMSUwIwYJKoZIhvcNAQkBFhZhaGFuc3NlbkB0cm9sbHRlY2gu +Y29tMB4XDTIyMDcwODA2Mjc1OVoXDTMyMDcwNTA2Mjc1OVowgZMxCzAJBgNVBAYT +Ak5PMQ0wCwYDVQQIDARPc2xvMRAwDgYDVQQHDAdOeWRhbGVuMQ0wCwYDVQQKDARU +UXRDMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEXMBUGA1UEAwwOZmx1a2UudHJvbGwu +bm8xJTAjBgkqhkiG9w0BCQEWFmFoYW5zc2VuQHRyb2xsdGVjaC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKKdWTdAFuNL5RzNY/6Oe6AWflhm46 +cI+U8rRKZ5SJlbTHg8CReiSzstKtr5dPaGDTus8GETJBQTXepjD5FUFJ7G+HDZEO +DvqhXXuO3uNWNvTiGEptIEqKxbhQcXSu/LOhbMnNXFqrF9wVxhPu4T63RycIeL8w +Zf72BQjwPPjoiikIDmR8qYD5ecX/86/zCiQg3haTSWqQDJYMKD87kC/of/Nuv9Tc +IIjl4jlxFEjBIwPFs9e/1v52C/GEwkYyS7pQwyFJ+T1pzoUJI2CFgHvDcL3MgaID ++04fKy6PAbOnNEoPNPk/+o2ouQs7G+ylSHfYEP+OCikCGg2L2kObrSItAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBALHdGWQ4YqucGJSP1n1ANrLILy+sXqEP7hMdG5HH +GDZ/ygUhjTZ/k5Cj0+auC4Aw490l8Tj8gmzt68KJmgSH+z1erY67+fhWtAewDzU5 +zIMqKHja1hSb5JIdWaD7ZFBQzor2beBO0u+VzegWqe20kw2mkFAcdQTsV28hvr1v +rcgpVkegQcmHpr6FBpYFmtnizpPnX5Zm+JJAlvSGvoYMI5i9Vc7/gdx790NeaXmy +yD1ueFMfsPtAcZq8cSbGSCS5/pcuhIx+5O9+V8iwN9lKdYksTCLAn4SREHzlgi68 +SGY0OUMlXeD82K0+mDv+hzSmq4sk7CDGbSxVV5TwzFXDgNc= -----END CERTIFICATE----- diff --git a/tests/auto/network/access/qnetworkreply/data/gzip.rcc.cpp b/tests/auto/network/access/qnetworkreply/data/gzip.rcc.cpp new file mode 100644 index 0000000000..4716ebbc02 --- /dev/null +++ b/tests/auto/network/access/qnetworkreply/data/gzip.rcc.cpp @@ -0,0 +1,1235 @@ +/**************************************************************************** +** Resource object code +** +** Created by: The Resource Compiler for Qt version 6.0.0 +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +static const unsigned char qt_resource_data[] = { + // D:/projects/qt/dev/src/qtbase/tests/auto/network/access/decompresshelper/4G.gz + 0x0,0x0,0x47,0x6b, + 0x0, + 0x47,0x80,0x16,0x78,0xda,0xec,0xce,0xb1,0xd,0x82,0x50,0x14,0x40,0xd1,0x27,0x36, + 0x34,0xce,0x40,0x18,0x81,0x41,0xc,0x33,0x58,0xd8,0x62,0x42,0x49,0x45,0x4f,0x6f, + 0xfd,0x37,0x70,0x3,0x92,0xef,0x3c,0xdf,0xd2,0x5a,0x61,0xa,0x9a,0x73,0xbb,0xdb, + 0x9d,0x66,0xa9,0xeb,0xd8,0xaa,0xce,0xd3,0x7d,0x7c,0x44,0xc9,0xdd,0x69,0xbb,0x77, + 0xfa,0xf6,0x9f,0xdb,0x25,0xc5,0xb1,0xd,0x25,0xb7,0x3b,0x67,0x4e,0xbf,0xd7,0x75, + 0x3d,0x18,0x13,0xcf,0xc0,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1, + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xf9,0xb7,0x3b,0xc7,0x34,0x0,0xc0,0x20, + 0x0,0xc0,0x92,0x89,0xc0,0xf5,0xcc,0xcd,0xc7,0x82,0x5,0x82,0xa,0x38,0xfa,0xf5, + 0xac,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, + 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0xce,0x40,0xe7,0x6f, + 0xea,0xc4,0xcd,0xf3,0x1a,0x5,0xc6,0xd3,0x33,0xd2, + +}; + +static const unsigned char qt_resource_name[] = { + // 4G.gz + 0x0,0x5, + 0x0,0x38,0xa4,0xea, + 0x0,0x34, + 0x0,0x47,0x0,0x2e,0x0,0x67,0x0,0x7a, + +}; + +static const unsigned char qt_resource_struct[] = { + // : + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + // :/4G.gz + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0, +0x0,0x0,0x1,0x72,0x1c,0xb0,0xf0,0xe2, + +}; + +#ifdef QT_NAMESPACE +# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name +# define QT_RCC_MANGLE_NAMESPACE0(x) x +# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b +# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b) +# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \ + QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE)) +#else +# define QT_RCC_PREPEND_NAMESPACE(name) name +# define QT_RCC_MANGLE_NAMESPACE(name) name +#endif + +#ifdef QT_NAMESPACE +namespace QT_NAMESPACE { +#endif + +bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); +bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); + +#if defined(__ELF__) || defined(__APPLE__) +static inline unsigned char qResourceFeatureZlib() +{ + extern const unsigned char qt_resourceFeatureZlib; + return qt_resourceFeatureZlib; +} +#else +unsigned char qResourceFeatureZlib(); +#endif + +#ifdef QT_NAMESPACE +} +#endif + +int QT_RCC_MANGLE_NAMESPACE(qInitResources_gzip)(); +int QT_RCC_MANGLE_NAMESPACE(qInitResources_gzip)() +{ + int version = 3; + QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_gzip)(); +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_gzip)() +{ + int version = 3; + version += QT_RCC_PREPEND_NAMESPACE(qResourceFeatureZlib()); + QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +namespace { + struct initializer { + initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources_gzip)(); } + ~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_gzip)(); } + } dummy; +} diff --git a/tests/auto/network/access/qnetworkreply/data/zstandard.rcc.cpp b/tests/auto/network/access/qnetworkreply/data/zstandard.rcc.cpp new file mode 100644 index 0000000000..c8a435192f --- /dev/null +++ b/tests/auto/network/access/qnetworkreply/data/zstandard.rcc.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** Resource object code +** +** Created by: The Resource Compiler for Qt version 6.0.0 +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +static const unsigned char qt_resource_data[] = { + // D:/projects/qt/dev/src/qtbase/tests/auto/network/access/decompresshelper/4G.zst + 0x0,0x0,0x1,0x75, + 0x0, + 0x2,0x3,0x93,0x78,0xda,0xed,0xd4,0x21,0xe,0x83,0x40,0x10,0x86,0xd1,0x29,0x98, + 0x26,0x98,0x3d,0x46,0x1d,0x1a,0x8f,0xec,0x29,0x50,0xdc,0x84,0x13,0xe1,0x2b,0x90, + 0x1c,0x89,0xcd,0x32,0xe9,0x25,0x2a,0xfa,0x26,0x79,0xc9,0xe8,0x5f,0x7c,0xaf,0x7d, + 0xac,0xc7,0x1a,0x79,0x8f,0xf4,0x8e,0x78,0xe6,0x73,0xb5,0xa9,0x74,0x5d,0x94,0x0, + 0xfe,0xcf,0xfc,0xed,0x41,0x6d,0xe7,0x50,0xcc,0x1,0x32,0x60,0xe,0x90,0x1,0x73, + 0x80,0xc,0x98,0x4,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90, + 0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64, + 0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19, + 0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6, + 0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1, + 0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0, + 0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0, + 0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0, + 0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40, + 0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90, + 0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64, + 0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19, + 0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6, + 0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1, + 0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0, + 0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0, + 0x64,0x0,0x90,0x1,0x40,0x6,0x0,0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x0, + 0x19,0x0,0x64,0x0,0x90,0x1,0x40,0x6,0x80,0x5f,0xe8,0xd3,0xf2,0x69,0xdb,0xd, + 0xcd,0x15,0x90,0xe9, + +}; + +static const unsigned char qt_resource_name[] = { + // 4G.zst + 0x0,0x6, + 0x3,0x8a,0x61,0xa4, + 0x0,0x34, + 0x0,0x47,0x0,0x2e,0x0,0x7a,0x0,0x73,0x0,0x74, + +}; + +static const unsigned char qt_resource_struct[] = { + // : + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + // :/4G.zst + 0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0, +0x0,0x0,0x1,0x72,0x1c,0x8d,0x7,0xac, + +}; + +#ifdef QT_NAMESPACE +# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name +# define QT_RCC_MANGLE_NAMESPACE0(x) x +# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b +# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b) +# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \ + QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE)) +#else +# define QT_RCC_PREPEND_NAMESPACE(name) name +# define QT_RCC_MANGLE_NAMESPACE(name) name +#endif + +#ifdef QT_NAMESPACE +namespace QT_NAMESPACE { +#endif + +bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); +bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); + +#if defined(__ELF__) || defined(__APPLE__) +static inline unsigned char qResourceFeatureZlib() +{ + extern const unsigned char qt_resourceFeatureZlib; + return qt_resourceFeatureZlib; +} +#else +unsigned char qResourceFeatureZlib(); +#endif + +#ifdef QT_NAMESPACE +} +#endif + +int QT_RCC_MANGLE_NAMESPACE(qInitResources_zstandard)(); +int QT_RCC_MANGLE_NAMESPACE(qInitResources_zstandard)() +{ + int version = 3; + QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_zstandard)(); +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_zstandard)() +{ + int version = 3; + version += QT_RCC_PREPEND_NAMESPACE(qResourceFeatureZlib()); + QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +namespace { + struct initializer { + initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources_zstandard)(); } + ~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_zstandard)(); } + } dummy; +} diff --git a/tests/auto/network/access/qnetworkreply/echo/.prev_CMakeLists.txt b/tests/auto/network/access/qnetworkreply/echo/.prev_CMakeLists.txt deleted file mode 100644 index 60fe797d50..0000000000 --- a/tests/auto/network/access/qnetworkreply/echo/.prev_CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Generated from echo.pro. - -##################################################################### -## echo Binary: -##################################################################### - -qt_internal_add_executable(echo - SOURCES - main.cpp -) diff --git a/tests/auto/network/access/qnetworkreply/echo/CMakeLists.txt b/tests/auto/network/access/qnetworkreply/echo/CMakeLists.txt index 664b2f2fab..137b29110d 100644 --- a/tests/auto/network/access/qnetworkreply/echo/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkreply/echo/CMakeLists.txt @@ -1,11 +1,12 @@ -# Generated from echo.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## echo Binary: ##################################################################### qt_internal_add_executable(echo - OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} # special case + OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} SOURCES main.cpp ) diff --git a/tests/auto/network/access/qnetworkreply/echo/echo.pro b/tests/auto/network/access/qnetworkreply/echo/echo.pro deleted file mode 100644 index 3e304f4105..0000000000 --- a/tests/auto/network/access/qnetworkreply/echo/echo.pro +++ /dev/null @@ -1,4 +0,0 @@ -SOURCES += main.cpp -QT = core -CONFIG -= debug_and_release_target -CONFIG += cmdline diff --git a/tests/auto/network/access/qnetworkreply/echo/main.cpp b/tests/auto/network/access/qnetworkreply/echo/main.cpp index 9085de7898..b10eaa745c 100644 --- a/tests/auto/network/access/qnetworkreply/echo/main.cpp +++ b/tests/auto/network/access/qnetworkreply/echo/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtCore/QFile> @@ -37,11 +12,13 @@ int main(int argc, char **) } QFile file; - file.open(stdin, QFile::ReadWrite); + if (!file.open(stdin, QFile::ReadWrite)) + return 1; QByteArray data = file.readAll(); file.close(); - file.open(stdout, QFile::WriteOnly); + if (!file.open(stdout, QFile::WriteOnly)) + return 1; file.write(data); file.close(); return 0; diff --git a/tests/auto/network/access/qnetworkreply/qnetworkreply.pro b/tests/auto/network/access/qnetworkreply/qnetworkreply.pro deleted file mode 100644 index ec6f35a8b1..0000000000 --- a/tests/auto/network/access/qnetworkreply/qnetworkreply.pro +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS += echo -test.depends += $$SUBDIRS -SUBDIRS += test diff --git a/tests/auto/network/access/qnetworkreply/qnetworkreply.qrc b/tests/auto/network/access/qnetworkreply/qnetworkreply.qrc deleted file mode 100644 index 85ca6312af..0000000000 --- a/tests/auto/network/access/qnetworkreply/qnetworkreply.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resource</file> -</qresource> -</RCC> diff --git a/tests/auto/network/access/qnetworkreply/test/.prev_CMakeLists.txt b/tests/auto/network/access/qnetworkreply/test/.prev_CMakeLists.txt deleted file mode 100644 index 337a505a54..0000000000 --- a/tests/auto/network/access/qnetworkreply/test/.prev_CMakeLists.txt +++ /dev/null @@ -1,59 +0,0 @@ -# Generated from test.pro. - -##################################################################### -## tst_qnetworkreply Test: -##################################################################### - -# Collect test data -list(APPEND test_data "../empty") -list(APPEND test_data "../rfc3252.txt") -list(APPEND test_data "../resource") -list(APPEND test_data "../bigfile") -file(GLOB_RECURSE test_data_glob - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - ../*.jpg) -list(APPEND test_data ${test_data_glob}) -list(APPEND test_data "../certs") -list(APPEND test_data "../index.html") -list(APPEND test_data "../smb-file.txt") - -qt_internal_add_test(tst_qnetworkreply - SOURCES - ../../../../../shared/emulationdetector.h - ../tst_qnetworkreply.cpp - INCLUDE_DIRECTORIES - ../../../../../shared - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::NetworkPrivate - TESTDATA ${test_data} -) - -# Resources: -set_source_files_properties("../resource" - PROPERTIES QT_RESOURCE_ALIAS "resource" -) -set(qnetworkreply_resource_files - "resource" -) - -qt_internal_add_resource(tst_qnetworkreply "qnetworkreply" - PREFIX - "/" - BASE - ".." - FILES - ${qnetworkreply_resource_files} -) - - -#### Keys ignored in scope 1:.:.:test.pro:<TRUE>: -# QT_FOR_CONFIG = "gui-private" -# QT_TEST_SERVER_LIST = "vsftpd" "apache2" "ftp-proxy" "danted" "squid" -# testcase.timeout = "600" - -## Scopes: -##################################################################### - -#### Keys ignored in scope 2:.:.:test.pro:NOT ANDROID: -# TEST_HELPER_INSTALLS = "../echo/echo" diff --git a/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt b/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt index e60d254651..fa353b2769 100644 --- a/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkreply/test/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from test.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkreply Test: @@ -18,25 +19,23 @@ list(APPEND test_data "../index.html") list(APPEND test_data "../smb-file.txt") qt_internal_add_test(tst_qnetworkreply - OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../" # special case + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../" SOURCES - ../../../../../shared/emulationdetector.h ../tst_qnetworkreply.cpp - INCLUDE_DIRECTORIES - ../../../../../shared - PUBLIC_LIBRARIES + ../data/gzip.rcc.cpp + ../data/zstandard.rcc.cpp + LIBRARIES Qt::CorePrivate Qt::NetworkPrivate TESTDATA ${test_data} - QT_TEST_SERVER_LIST "vsftpd" "apache2" "ftp-proxy" "danted" "squid" # special case + QT_TEST_SERVER_LIST "vsftpd" "apache2" "ftp-proxy" "danted" "squid" + BUNDLE_ANDROID_OPENSSL_LIBS ) +add_dependencies(tst_qnetworkreply echo) # Resources: -set_source_files_properties("../resource" - PROPERTIES QT_RESOURCE_ALIAS "resource" -) set(qnetworkreply_resource_files - "resource" + "../resource" ) qt_internal_add_resource(tst_qnetworkreply "qnetworkreply" @@ -47,15 +46,3 @@ qt_internal_add_resource(tst_qnetworkreply "qnetworkreply" FILES ${qnetworkreply_resource_files} ) - - -#### Keys ignored in scope 1:.:.:test.pro:<TRUE>: -# QT_FOR_CONFIG = "gui-private" -# QT_TEST_SERVER_LIST = "vsftpd" "apache2" "ftp-proxy" "danted" "squid" -# testcase.timeout = "600" - -## Scopes: -##################################################################### - -#### Keys ignored in scope 2:.:.:test.pro:NOT ANDROID: -# TEST_HELPER_INSTALLS = "../echo/echo" diff --git a/tests/auto/network/access/qnetworkreply/test/test.pro b/tests/auto/network/access/qnetworkreply/test/test.pro deleted file mode 100644 index bf09a99d27..0000000000 --- a/tests/auto/network/access/qnetworkreply/test/test.pro +++ /dev/null @@ -1,19 +0,0 @@ -CONFIG += testcase -testcase.timeout = 600 # this test is slow -CONFIG -= debug_and_release_target -INCLUDEPATH += ../../../../../shared/ -HEADERS += ../../../../../shared/emulationdetector.h -SOURCES += ../tst_qnetworkreply.cpp -TARGET = tst_qnetworkreply - -QT = core-private network-private testlib -QT_FOR_CONFIG += gui-private -RESOURCES += ../qnetworkreply.qrc - -TESTDATA += ../empty ../rfc3252.txt ../resource ../bigfile ../*.jpg ../certs \ - ../index.html ../smb-file.txt - -!android: TEST_HELPER_INSTALLS = ../echo/echo - -CONFIG += unsupported/testserver -QT_TEST_SERVER_LIST = vsftpd apache2 ftp-proxy danted squid diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index fb64eedc58..64e7716e0c 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -1,49 +1,44 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtNetwork/qtnetworkglobal.h> + +#include <QTest> +#include <QSemaphore> +#include <QTestEventLoop> +#include <QSignalSpy> +#if QT_CONFIG(process) +#include <QProcess> +#endif +#include <QTimer> +#include <QWaitCondition> +#include <QScopeGuard> +#include <QBuffer> +#include <QMap> + #include <QtCore/QCryptographicHash> #include <QtCore/QDataStream> -#include <QtCore/QUrl> +#include <QtCore/QDateTime> #include <QtCore/QEventLoop> #include <QtCore/QElapsedTimer> #include <QtCore/QFile> +#include <QtCore/QList> #include <QtCore/QRandomGenerator> #include <QtCore/QRegularExpression> #include <QtCore/QRegularExpressionMatch> +#include <QtCore/QSet> #include <QtCore/QSharedPointer> #include <QtCore/QScopedPointer> #include <QtCore/QTemporaryFile> +#include <QtCore/QTimeZone> +#include <QtCore/QUrl> + #include <QtNetwork/QTcpServer> #include <QtNetwork/QTcpSocket> +#if QT_CONFIG(localserver) #include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalServer> +#endif #include <QtNetwork/QHostInfo> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkRequest> @@ -51,17 +46,23 @@ #include <QtNetwork/QAbstractNetworkCache> #include <QtNetwork/qauthenticator.h> #include <QtNetwork/qnetworkaccessmanager.h> +#if QT_CONFIG(networkdiskcache) #include <QtNetwork/qnetworkdiskcache.h> +#endif #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> #include <QtNetwork/qnetworkcookie.h> #include <QtNetwork/QNetworkCookieJar> +#if QT_CONFIG(http) #include <QtNetwork/QHttpPart> #include <QtNetwork/QHttpMultiPart> +#include <QtNetwork/QHttp1Configuration> +#endif #include <QtNetwork/QNetworkProxyQuery> -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) #include <QtNetwork/qsslerror.h> #include <QtNetwork/qsslconfiguration.h> +#include <QtNetwork/qsslsocket.h> #ifdef QT_BUILD_INTERNAL #include <QtNetwork/private/qsslconfiguration_p.h> #endif @@ -73,6 +74,9 @@ Q_DECLARE_METATYPE(QSharedPointer<char>) #endif +#include <memory> +#include <optional> + #ifdef Q_OS_UNIX # include <sys/types.h> # include <unistd.h> // for getuid() @@ -81,25 +85,23 @@ Q_DECLARE_METATYPE(QSharedPointer<char>) #include "../../../network-settings.h" -// Non-OpenSSL backends are not able to report a specific error code -// for self-signed certificates. -#ifndef QT_NO_OPENSSL -#define FLUKE_CERTIFICATE_ERROR QSslError::SelfSignedCertificate -#else -#define FLUKE_CERTIFICATE_ERROR QSslError::CertificateUntrusted +#ifdef Q_OS_INTEGRITY +#include "qplatformdefs.h" #endif Q_DECLARE_METATYPE(QAuthenticator*) -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) Q_DECLARE_METATYPE(QNetworkProxyQuery) #endif -#include "emulationdetector.h" - typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; -#ifndef QT_NO_OPENSSL +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +#if QT_CONFIG(ssl) QT_BEGIN_NAMESPACE +// Technically, a workaround, and only needed for OpenSSL: void qt_ForceTlsSecurityLevel(); QT_END_NAMESPACE #endif @@ -109,7 +111,7 @@ class tst_QNetworkReply: public QObject { Q_OBJECT -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) struct ProxyData { ProxyData(const QNetworkProxy &p, const QByteArray &t, bool auth) @@ -118,7 +120,7 @@ class tst_QNetworkReply: public QObject QNetworkProxy proxy; bool requiresAuthentication; }; -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) static bool seedCreated; static QString createUniqueExtension() @@ -133,7 +135,30 @@ class tst_QNetworkReply: public QObject static QString tempRedirectReplyStr() { QString s = "HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } + static QString movedReplyStr() { + QString s = "HTTP/1.1 301 Moved Permanently\r\n" + "content-type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } + + static QString foundReplyStr() { + QString s = "HTTP/1.1 302 Found\r\n" + "content-type: text/plain\r\n" + "location: %1\r\n" + "\r\n"; + return s; + } + + static QString permRedirectReplyStr() { + QString s = "HTTP/1.1 308 Permanent Redirect\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "\r\n"; return s; @@ -151,16 +176,18 @@ class tst_QNetworkReply: public QObject QString wronlyFileName; #endif QString uniqueExtension; -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) QList<ProxyData> proxies; #endif QNetworkAccessManager manager; MyCookieJar *cookieJar; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QSslConfiguration storedSslConfiguration; QList<QSslError> storedExpectedSslErrors; static const QString certsFilePath; -#endif + bool isSecureTransport = false; + bool isSchannel = false; +#endif // QT_CONFIG(ssl) using QObject::connect; static bool connect(const QNetworkReplyPtr &ptr, const char *signal, const QObject *receiver, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection) @@ -173,8 +200,10 @@ public: ~tst_QNetworkReply(); QString runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReplyPtr &reply, const QByteArray &data = QByteArray()); +#if QT_CONFIG(http) QString runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, QHttpMultiPart *multiPart, const QByteArray &verb); +#endif QString runCustomRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, const QByteArray &verb, QIODevice *data); @@ -188,7 +217,7 @@ public Q_SLOTS: void pipeliningHelperSlot(); void emitErrorForAllRepliesSlot(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void sslErrors(QNetworkReply*,const QList<QSslError> &); void storeSslConfiguration(); void ignoreSslErrorListSlot(QNetworkReply *reply, const QList<QSslError> &); @@ -219,12 +248,18 @@ private Q_SLOTS: void getFromFtpAfterError(); // QTBUG-40797 void getFromHttp_data(); void getFromHttp(); + void getWithBodyFromHttp_data(); + void getWithBodyFromHttp(); + void getWithAndWithoutBodyFromHttp_data(); + void getWithAndWithoutBodyFromHttp(); + void getWithBodyRedirected_data(); + void getWithBodyRedirected(); void getErrors_data(); void getErrors(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void headFromHttp_data(); void headFromHttp(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void putToFile_data(); void putToFile(); void putToFtp_data(); @@ -234,16 +269,24 @@ private Q_SLOTS: void putToHttp(); void putToHttpSynchronous_data(); void putToHttpSynchronous(); +#if QT_CONFIG(http) void putToHttpMultipart_data(); void putToHttpMultipart(); +#endif + void putWithoutBody(); + void putWithoutBody_data(); void postToHttp_data(); void postToHttp(); void postToHttpSynchronous_data(); void postToHttpSynchronous(); +#if QT_CONFIG(http) void postToHttpMultipart_data(); void postToHttpMultipart(); void multipartSkipIndices(); // QTBUG-32534 -#ifndef QT_NO_SSL +#endif + void postWithoutBody_data(); + void postWithoutBody(); +#if QT_CONFIG(ssl) void putToHttps_data(); void putToHttps(); void putToHttpsSynchronous_data(); @@ -270,6 +313,7 @@ private Q_SLOTS: void ioGetFromFileSpecial(); void ioGetFromFile_data(); void ioGetFromFile(); + void ioGetFromFileUrl(); void ioGetFromFtp_data(); void ioGetFromFtp(); void ioGetFromFtpWithReuse(); @@ -282,36 +326,38 @@ private Q_SLOTS: void ioGetFromHttpWithAuth_data(); void ioGetFromHttpWithAuth(); void ioGetFromHttpWithAuthSynchronous(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioGetFromHttpWithProxyAuth(); void ioGetFromHttpWithProxyAuthSynchronous(); void ioGetFromHttpWithSocksProxy(); -#endif // !QT_NO_NETWORKPROXY -#ifndef QT_NO_SSL +#endif // QT_CONFIG(networkproxy) +#if QT_CONFIG(ssl) void ioGetFromHttpsWithSslErrors(); void ioGetFromHttpsWithIgnoreSslErrors(); void ioGetFromHttpsWithSslHandshakeError(); #endif void ioGetFromHttpBrokenServer_data(); void ioGetFromHttpBrokenServer(); - void ioGetFromHttpStatus100_data(); - void ioGetFromHttpStatus100(); + void ioGetFromHttpStatusInformational_data(); + void ioGetFromHttpStatusInformational(); void ioGetFromHttpNoHeaders_data(); void ioGetFromHttpNoHeaders(); void ioGetFromHttpWithCache_data(); void ioGetFromHttpWithCache(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioGetWithManyProxies_data(); void ioGetWithManyProxies(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void ioPutToFileFromFile_data(); void ioPutToFileFromFile(); void ioPutToFileFromSocket_data(); void ioPutToFileFromSocket(); +#if QT_CONFIG(localserver) void ioPutToFileFromLocalSocket_data(); void ioPutToFileFromLocalSocket(); +#endif void ioPutToFileFromProcess_data(); void ioPutToFileFromProcess(); void ioPutToFtpFromFile_data(); @@ -320,12 +366,12 @@ private Q_SLOTS: void ioPutToHttpFromFile(); void ioPostToHttpFromFile_data(); void ioPostToHttpFromFile(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void ioPostToHttpFromSocket_data(); void ioPostToHttpFromSocket(); void ioPostToHttpFromSocketSynchronous(); void ioPostToHttpFromSocketSynchronous_data(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void ioPostToHttpFromMiddleOfFileToEnd(); void ioPostToHttpFromMiddleOfFileFiveBytes(); void ioPostToHttpFromMiddleOfQBufferFiveBytes(); @@ -365,13 +411,13 @@ private Q_SLOTS: void nestedEventLoops(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void httpProxyCommands_data(); void httpProxyCommands(); void httpProxyCommandsSynchronous_data(); void httpProxyCommandsSynchronous(); void proxyChange(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void authorizationError_data(); void authorizationError(); @@ -385,7 +431,7 @@ private Q_SLOTS: void httpRecursiveCreation(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void ioPostToHttpsUploadProgress(); void ignoreSslErrorsList_data(); void ignoreSslErrorsList(); @@ -417,7 +463,9 @@ private Q_SLOTS: void ioGetFromHttpWithoutContentLength(); void ioGetFromHttpBrokenChunkedEncoding(); +#if QT_CONFIG(http) void qtbug12908compressedHttpReply(); +#endif void compressedHttpReplyBrokenGzip(); void getFromUnreachableIp(); @@ -436,26 +484,37 @@ private Q_SLOTS: void qtbug27161httpHeaderMayBeDamaged_data(); void qtbug27161httpHeaderMayBeDamaged(); +#if QT_CONFIG(networkdiskcache) void qtbug28035browserDoesNotLoadQtProjectOrgCorrectly(); +#endif void qtbug45581WrongReplyStatusCode(); void synchronousRequest_data(); void synchronousRequest(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void synchronousRequestSslFailure(); #endif void httpAbort(); + void closeClientSideConnectionEagerlyQtbug20726(); + void varyingCacheExpiry_data(); + void varyingCacheExpiry(); + +#if QT_CONFIG(http) + void amountOfHttp1ConnectionsQtbug25280_data(); + void amountOfHttp1ConnectionsQtbug25280(); +#endif + void dontInsertPartialContentIntoTheCache(); void httpUserAgent(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void authenticationCacheAfterCancel_data(); void authenticationCacheAfterCancel(); void authenticationWithDifferentRealm(); -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void synchronousAuthenticationCache(); void pipelining(); @@ -482,16 +541,21 @@ private Q_SLOTS: void ioHttpCookiesDuringRedirect(); void ioHttpRedirect_data(); void ioHttpRedirect(); +#if QT_CONFIG(networkdiskcache) + void ioHttpRedirectWithCache(); +#endif void ioHttpRedirectFromLocalToRemote(); void ioHttpRedirectPostPut_data(); void ioHttpRedirectPostPut(); +#if QT_CONFIG(http) void ioHttpRedirectMultipartPost_data(); void ioHttpRedirectMultipartPost(); +#endif void ioHttpRedirectDelete(); void ioHttpRedirectCustom(); void ioHttpRedirectWithUploadDevice_data(); void ioHttpRedirectWithUploadDevice(); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void putWithServerClosingConnectionImmediately(); #endif @@ -500,11 +564,39 @@ private Q_SLOTS: void autoDeleteReplies_data(); void autoDeleteReplies(); - void getWithTimeout(); - void postWithTimeout(); +#if QT_CONFIG(http) || defined (Q_OS_WASM) + void requestWithTimeout_data(); + void requestWithTimeout(); +#endif + + void moreActivitySignals_data(); + void moreActivitySignals(); void contentEncoding_data(); void contentEncoding(); +#if QT_CONFIG(http) + void contentEncodingBigPayload_data(); + void contentEncodingBigPayload(); +#endif + void cacheWithContentEncoding_data(); + void cacheWithContentEncoding(); + void downloadProgressWithContentEncoding_data(); + void downloadProgressWithContentEncoding(); + void contentEncodingError_data(); + void contentEncodingError(); + void compressedReadyRead(); + void notFoundWithCompression_data(); + void notFoundWithCompression(); + +#if QT_CONFIG(http) + void qhttpPartDebug_data(); + void qhttpPartDebug(); + + void qtbug68821proxyError_data(); + void qtbug68821proxyError(); +#endif + + void abortAndError(); // NOTE: This test must be last! void parentingRepliesToTheApp(); @@ -515,6 +607,9 @@ private: bool notEnoughDataForFastSender; bool ftpSupported = false; +#if QT_CONFIG(ssl) + QSslError::SslError flukeCertTlsError = QSslError::CertificateUntrusted; +#endif }; const QByteArray tst_QNetworkReply::httpEmpty200Response = @@ -541,7 +636,7 @@ static bool validateRedirectedResponseHeaders(QNetworkReplyPtr reply) && !reply->header(QNetworkRequest::LocationHeader).isValid(); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) static void setupSslServer(QSslSocket* serverSocket) { QString testDataDir = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absolutePath(); @@ -567,7 +662,7 @@ const QString tst_QNetworkReply::certsFilePath = "/certs/qt-test-server-host-net const QString tst_QNetworkReply::certsFilePath = "/certs/qt-test-server-cacert.pem"; #endif -#endif // !QT_NO_SSL +#endif // QT_CONFIG(ssl) // NOTE: MiniHttpServer has a very limited support of PUT/POST requests! Make // sure you understand the server's code before PUTting/POSTing data (and @@ -587,7 +682,8 @@ public: int totalConnections; bool stopTransfer = false; - bool hasContent = false; + bool checkedContentLength = false; + bool foundContentLength = false; int contentRead = 0; int contentLength = 0; @@ -619,13 +715,14 @@ public: { contentLength = 0; receivedData.clear(); + foundContentLength = false; } protected: void incomingConnection(qintptr socketDescriptor) override { //qDebug() << "incomingConnection" << socketDescriptor << "doSsl:" << doSsl << "ipv6:" << ipv6; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) if (doSsl) { QSslSocket *serverSocket = new QSslSocket(this); if (!serverSocket->setSocketDescriptor(socketDescriptor)) { @@ -675,8 +772,13 @@ private: void parseContentLength() { - int index = receivedData.indexOf("Content-Length:"); - index += sizeof("Content-Length:") - 1; + int index = receivedData.indexOf("content-length:"); + if (index == -1) + return; + + foundContentLength = true; + + index += sizeof("content-length:") - 1; const auto end = std::find(receivedData.cbegin() + index, receivedData.cend(), '\r'); auto num = receivedData.mid(index, std::distance(receivedData.cbegin() + index, end)); bool ok; @@ -686,7 +788,7 @@ private: } private slots: -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void slotSslErrors(const QList<QSslError>& errors) { QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender()); @@ -716,12 +818,14 @@ public slots: if (doubleEndlPos != -1) { const int endOfHeader = doubleEndlPos + 4; - hasContent = receivedData.startsWith("POST") || receivedData.startsWith("PUT") - || receivedData.startsWith("CUSTOM_WITH_PAYLOAD"); - if (hasContent && contentLength == 0) + contentRead = receivedData.size() - endOfHeader; + + if (!checkedContentLength) { parseContentLength(); - contentRead = receivedData.length() - endOfHeader; - if (hasContent && contentRead < contentLength) + checkedContentLength = true; + } + + if (contentRead < contentLength) return; // multiple requests incoming. remove the bytes of the current one @@ -757,7 +861,7 @@ public: { QNetworkCookieJar::setAllCookies(cookieList); } }; -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) class MyProxyFactory: public QNetworkProxyFactory { public: @@ -780,7 +884,7 @@ public: return toReturn; } }; -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) class MyMemoryCache: public QAbstractNetworkCache { @@ -822,7 +926,7 @@ public: qint64 cacheSize() const override { qint64 total = 0; - foreach (const CachedContent &entry, cache) + for (const auto &[_, entry] : cache.asKeyValueRange()) total += entry.second.size(); return total; } @@ -864,9 +968,15 @@ public: { } - QIODevice *data(const QUrl &) override + QIODevice *data(const QUrl &url) override { - return 0; + QIODevice *device = nullptr; + auto it = m_buffers.constFind(url); + if (it != m_buffers.cend()) { + device = *it; + device->seek(0); + } + return device; } bool remove(const QUrl &url) override @@ -893,7 +1003,6 @@ public: { QUrl url = buffer->property("url").toUrl(); m_insertedUrls << url; - delete m_buffers.take(url); } void clear() override { m_insertedUrls.clear(); } @@ -993,7 +1102,7 @@ public: } virtual void incomingConnection(qintptr socketDescriptor) override { -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) if (doSsl) { QSslSocket *serverSocket = new QSslSocket; serverSocket->setParent(this); @@ -1009,7 +1118,7 @@ public: } private slots: -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void slotSslErrors(const QList<QSslError>& errors) { qDebug() << "slotSslErrors" << sslSocket->errorString() << errors; @@ -1279,12 +1388,17 @@ tst_QNetworkReply::tst_QNetworkReply() { qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy qRegisterMetaType<QAuthenticator *>(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) qRegisterMetaType<QNetworkProxy>(); #endif -#ifndef QT_NO_SSL + +#if QT_CONFIG(ssl) qRegisterMetaType<QList<QSslError> >(); + isSecureTransport = QSslSocket::activeBackend() == QStringLiteral("securetransport"); + if (!isSecureTransport) + isSchannel = QSslSocket::activeBackend() == QStringLiteral("schannel"); #endif + qRegisterMetaType<QNetworkReply::NetworkError>(); uniqueExtension = createUniqueExtension(); @@ -1292,7 +1406,7 @@ tst_QNetworkReply::tst_QNetworkReply() cookieJar = new MyCookieJar; manager.setCookieJar(cookieJar); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::httpProxyServerName()); proxies << ProxyData(QNetworkProxy::NoProxy, "", false); @@ -1311,13 +1425,13 @@ tst_QNetworkReply::tst_QNetworkReply() << ProxyData(QNetworkProxy(QNetworkProxy::Socks5Proxy, socksProxy, 1081), "+socksauth", true); } else { -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) fprintf(stderr, "==================================================================\n"); fprintf(stderr, "Proxy could not be looked up. No proxy will be used while testing!\n"); fprintf(stderr, "==================================================================\n"); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) ftpSupported = manager.supportedSchemes().contains("ftp"); } @@ -1339,7 +1453,7 @@ void tst_QNetworkReply::proxyAuthenticationRequired(const QNetworkProxy &, QAuth auth->setPassword("password"); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors) { reply->ignoreSslErrors(); @@ -1356,6 +1470,7 @@ void tst_QNetworkReply::storeSslConfiguration() } #endif +#if QT_CONFIG(http) QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, QHttpMultiPart *multiPart, @@ -1387,6 +1502,7 @@ QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request, } return QString(); } +#endif QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, @@ -1437,11 +1553,11 @@ QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, while (!reply->isFinished()) { QTimer::singleShot(20000, loop, SLOT(quit())); code = loop->exec(); - if (count == spy.count() && !reply->isFinished()) { + if (count == spy.size() && !reply->isFinished()) { code = Timeout; break; } - count = spy.count(); + count = spy.size(); } delete loop; loop = 0; @@ -1507,11 +1623,11 @@ int tst_QNetworkReply::waitForFinish(QNetworkReplyPtr &reply) QSignalSpy spy(reply.data(), SIGNAL(downloadProgress(qint64,qint64))); while (!reply->isFinished()) { QTimer::singleShot(5000, loop, SLOT(quit())); - if (loop->exec() == Timeout && count == spy.count() && !reply->isFinished()) { + if (loop->exec() == Timeout && count == spy.size() && !reply->isFinished()) { returnCode = Timeout; break; } - count = spy.count(); + count = spy.size(); } delete loop; loop = 0; @@ -1539,8 +1655,10 @@ void tst_QNetworkReply::initTestCase() testDataDir = QCoreApplication::applicationDirPath(); #if defined(QT_TEST_SERVER) - QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpServerName(), 21)); - QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121)); + if (ftpSupported) { + QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpServerName(), 21)); + QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121)); + } QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 80)); QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 443)); QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3128)); @@ -1561,7 +1679,7 @@ void tst_QNetworkReply::initTestCase() #endif QDir::setSearchPaths("testdata", QStringList() << testDataDir); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QSslConfiguration::defaultConfiguration().caCertificates(); //preload certificates #endif @@ -1570,10 +1688,18 @@ void tst_QNetworkReply::initTestCase() QString::fromLatin1("Couldn't find echo dir starting from %1.").arg(QDir::currentPath()))); cleanupTestData(); -#ifndef QT_NO_OPENSSL +#if QT_CONFIG(ssl) QT_PREPEND_NAMESPACE(qt_ForceTlsSecurityLevel)(); -#endif // QT_NO_OPENSSL + if (QSslSocket::activeBackend() == QStringLiteral("openssl")) + flukeCertTlsError = QSslError::SelfSignedCertificate; +#endif + + // For content encoding tests + Q_INIT_RESOURCE(gzip); +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + Q_INIT_RESOURCE(zstandard); +#endif } void tst_QNetworkReply::cleanupTestCase() @@ -1582,6 +1708,12 @@ void tst_QNetworkReply::cleanupTestCase() if (!wronlyFileName.isNull()) QFile::remove(wronlyFileName); #endif + + // For content encoding tests +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + Q_CLEANUP_RESOURCE(zstandard); +#endif + Q_CLEANUP_RESOURCE(gzip); } void tst_QNetworkReply::cleanupTestData() @@ -1591,7 +1723,7 @@ void tst_QNetworkReply::cleanupTestData() // clear the internal cache manager.clearAccessCache(); -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) manager.setProxy(QNetworkProxy()); #endif manager.setCache(0); @@ -1600,7 +1732,7 @@ void tst_QNetworkReply::cleanupTestData() cookieJar->setAllCookies(QList<QNetworkCookie>()); // disconnect manager signals -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); #endif manager.disconnect(SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); @@ -1939,7 +2071,254 @@ void tst_QNetworkReply::getFromHttp() QCOMPARE(reply->readAll(), reference.readAll()); } -#ifndef QT_NO_NETWORKPROXY +void tst_QNetworkReply::getWithBodyFromHttp_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("useDevice"); + QTest::newRow("with-bytearray") << QByteArray("Body 1") << false; + QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false; + QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false; + QTest::newRow("with-device") << QByteArray("Body 1") << true; + QTest::newRow("with-device2") << QByteArray("Body 2") << true; + QTest::newRow("with-device3") << QByteArray("Body 3") << true; +} + +void tst_QNetworkReply::getWithBodyFromHttp() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, useDevice); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QByteArray dataFromServerToClient = QByteArray("Long first line\r\nLong second line"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply; + + if (useDevice) + reply.reset(manager.get(request, &buff)); + else + reply.reset(manager.get(request, dataFromClientToServer)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.contentLength, dataFromClientToServer.size()); + QCOMPARE(server.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer); + QByteArray content = reply->readAll(); + QCOMPARE(content, dataFromServerToClient); +} + +void tst_QNetworkReply::getWithAndWithoutBodyFromHttp_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("alwaysCache"); + QTest::addColumn<tst_QNetworkReply::RunSimpleRequestReturn>("requestReturn"); + QTest::addColumn<bool>("useDevice"); + QTest::newRow("with-bytearray") << QByteArray("Body 1") << false << Success << false; + QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false << Success << false; + QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false << Success << false; + QTest::newRow("with-bytearray-cache") << QByteArray("Body 1") << true << Failure << false; + QTest::newRow("with-bytearray-cache2") << QByteArray("Body 2") << true << Failure << false; + QTest::newRow("with-bytearray-cache3") << QByteArray("Body 3") << true << Failure << false; + QTest::newRow("with-device") << QByteArray("Body 1") << false << Success << true; + QTest::newRow("with-device2") << QByteArray("Body 2") << false << Success << true; + QTest::newRow("with-device3") << QByteArray("Body 3") << false << Success << true; + QTest::newRow("with-device-cache") << QByteArray("Body 1") << true << Failure << true; + QTest::newRow("with-device-cache2") << QByteArray("Body 2") << true << Failure << true; + QTest::newRow("with-device-cache3") << QByteArray("Body 3") << true << Failure << true; +} + +void tst_QNetworkReply::getWithAndWithoutBodyFromHttp() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, alwaysCache); + QFETCH(tst_QNetworkReply::RunSimpleRequestReturn, requestReturn); + QFETCH(bool, useDevice); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QNetworkAccessManager qnam; + MyMemoryCache *memoryCache = new MyMemoryCache(&qnam); + qnam.setCache(memoryCache); + + const int sizeOfDataFromServerToClient =3; + QByteArray dataFromServerToClient1 = QByteArray("aaa"); + QByteArray dataFromServerToClient2 = QByteArray("bbb"); + QByteArray dataFromServerToClient3 = QByteArray("ccc"); + + QByteArray baseHttpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + baseHttpResponse += QByteArray::number(sizeOfDataFromServerToClient); + baseHttpResponse += "\r\n\r\n"; + + MiniHttpServer server(baseHttpResponse + dataFromServerToClient1); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + + // Send request without body + QNetworkReplyPtr reply(manager.get(request)); + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QByteArray content = reply->readAll(); + QCOMPARE(content, dataFromServerToClient1); + + if (alwaysCache) { + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::AlwaysCache); + } + + server.dataToTransmit = baseHttpResponse + dataFromServerToClient2; + + // Send request with body + QNetworkReplyPtr reply2; + if (useDevice) + reply2.reset(manager.get(request, &buff)); + else + reply2.reset(manager.get(request, dataFromClientToServer)); + + QVERIFY2(waitForFinish(reply2) == requestReturn, msgWaitForFinished(reply2)); + content = reply2->readAll(); + + if (alwaysCache) + QVERIFY(content.isEmpty()); + else + QCOMPARE(content, dataFromServerToClient2); + + QCOMPARE(reply2->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false); + + if (alwaysCache) { + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::PreferNetwork); + } + + server.dataToTransmit = baseHttpResponse + dataFromServerToClient3; + + // Send another request without a body + QNetworkReplyPtr reply3(manager.get(request)); + QVERIFY2(waitForFinish(reply3) == Success, msgWaitForFinished(reply3)); + content = reply3->readAll(); + QCOMPARE(content, dataFromServerToClient3); + QCOMPARE(reply3->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false); +} + +void tst_QNetworkReply::getWithBodyRedirected_data() +{ + QTest::addColumn<QByteArray>("dataFromClientToServer"); + QTest::addColumn<bool>("useDevice"); + QTest::addColumn<int>("status"); + QTest::newRow("with-bytearray - 301") << QByteArray("Body 1") << false << 301; + QTest::newRow("with-bytearray2 - 301") << QByteArray("Body 2") << false << 301; + QTest::newRow("with-bytearray3 - 301") << QByteArray("Body 3") << false << 301; + QTest::newRow("with-device - 301") << QByteArray("Body 1") << true << 301; + QTest::newRow("with-device2 - 301") << QByteArray("Body 2") << true << 301; + QTest::newRow("with-device3 - 301") << QByteArray("Body 3") << true << 301; + QTest::newRow("with-bytearray - 302") << QByteArray("Body 1") << false << 302; + QTest::newRow("with-bytearray2 - 302") << QByteArray("Body 2") << false << 302; + QTest::newRow("with-bytearray3 - 302") << QByteArray("Body 3") << false << 302; + QTest::newRow("with-device - 302") << QByteArray("Body 1") << true << 302; + QTest::newRow("with-device2 - 302") << QByteArray("Body 2") << true << 302; + QTest::newRow("with-device3 - 302") << QByteArray("Body 3") << true << 302; + QTest::newRow("with-bytearray - 307") << QByteArray("Body 1") << false << 307; + QTest::newRow("with-bytearray2 - 307") << QByteArray("Body 2") << false << 307; + QTest::newRow("with-bytearray3 - 307") << QByteArray("Body 3") << false << 307; + QTest::newRow("with-device - 307") << QByteArray("Body 1") << true << 307; + QTest::newRow("with-device2 - 307") << QByteArray("Body 2") << true << 307; + QTest::newRow("with-device3 - 307") << QByteArray("Body 3") << true << 307; + QTest::newRow("with-bytearray - 308") << QByteArray("Body 1") << false << 308; + QTest::newRow("with-bytearray2 - 308") << QByteArray("Body 2") << false << 308; + QTest::newRow("with-bytearray3 - 308") << QByteArray("Body 3") << false << 308; + QTest::newRow("with-device - 308") << QByteArray("Body 1") << true << 308; + QTest::newRow("with-device2 - 308") << QByteArray("Body 2") << true << 308; + QTest::newRow("with-device3 - 308") << QByteArray("Body 3") << true << 308; +} + +void tst_QNetworkReply::getWithBodyRedirected() +{ + QFETCH(QByteArray, dataFromClientToServer); + QFETCH(bool, useDevice); + QFETCH(int, status); + + QBuffer buff; + buff.setData(dataFromClientToServer); + buff.open(QIODevice::ReadOnly); + + QUrl localhost = QUrl("http://localhost"); + + // Setup server to which the second server will redirect to + MiniHttpServer server2(httpEmpty200Response); + + QUrl redirectUrl = QUrl(localhost); + redirectUrl.setPort(server2.serverPort()); + + QByteArray redirectReply; + switch (status) { + case 301: redirectReply = + foundReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 302: redirectReply = + movedReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 307: redirectReply = + tempRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + case 308: redirectReply = + permRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break; + default: QFAIL("Unexpected status code"); break; + } + + // Setup redirect server + MiniHttpServer server(redirectReply); + + localhost.setPort(server.serverPort()); + QNetworkRequest request(localhost); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, + QNetworkRequest::NoLessSafeRedirectPolicy); + + QNetworkReplyPtr reply; + if (useDevice) + reply.reset(manager.get(request, &buff)); + else + reply.reset(manager.get(request, dataFromClientToServer)); + + QSignalSpy redSpy(reply.data(), SIGNAL(redirected(QUrl))); + QSignalSpy finSpy(reply.data(), SIGNAL(finished())); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + + // Redirected and finished should be emitted exactly once + QCOMPARE(redSpy.size(), 1); + QCOMPARE(finSpy.size(), 1); + + // Original URL should not be changed after redirect + QCOMPARE(request.url(), localhost); + + // Verify Redirect url + QList<QVariant> args = redSpy.takeFirst(); + QCOMPARE(args.at(0).toUrl(), redirectUrl); + + // Reply url is set to the redirect url + QCOMPARE(reply->url(), redirectUrl); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(validateRedirectedResponseHeaders(reply)); + + // Verify that the message body has arrived to the server + if (status > 302) { + QVERIFY(server2.contentLength != 0); + QCOMPARE(server2.contentLength, dataFromClientToServer.size()); + QCOMPARE(server2.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer); + } else { + // In these cases the message body should not reach the server + QVERIFY(server2.contentLength == 0); + } +} + +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::headFromHttp_data() { QTest::addColumn<qint64>("referenceSize"); @@ -1958,7 +2337,7 @@ void tst_QNetworkReply::headFromHttp_data() QString httpServer = QtNetworkSettings::httpServerName(); //testing proxies, mainly for the 407 response from http proxy - for (int i = 0; i < proxies.count(); ++i) { + for (int i = 0; i < proxies.size(); ++i) { QTest::newRow("rfc" + proxies.at(i).tag) << rfcsize << QUrl("http://" + httpServer + "/qtest/rfc3252.txt") @@ -2028,7 +2407,7 @@ void tst_QNetworkReply::headFromHttp() if (reply->header(QNetworkRequest::ContentTypeHeader).isValid()) QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), contentType); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::getErrors_data() { @@ -2118,14 +2497,6 @@ void tst_QNetworkReply::getErrors() QSKIP("Running this test as root doesn't make sense"); } - - if (EmulationDetector::isRunningArmOnX86() - && qstrcmp(QTest::currentDataTag(), "file-permissions") == 0) { - QFileInfo filePermissionFile = QFileInfo(filePermissionFileName.toLatin1()); - if (filePermissionFile.ownerId() == ::geteuid()) { - QSKIP("Sysroot directories are owned by the current user"); - } - } #endif QNetworkReplyPtr reply(manager.get(request)); @@ -2248,9 +2619,9 @@ void tst_QNetworkReply::putToFtp() QSignalSpy spy(r, SIGNAL(downloadProgress(qint64,qint64))); while (!r->isFinished()) { QTestEventLoop::instance().enterLoop(10); - if (count == spy.count() && !r->isFinished()) + if (count == spy.size() && !r->isFinished()) break; - count = spy.count(); + count = spy.size(); } QObject::disconnect(r, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -2365,6 +2736,48 @@ void tst_QNetworkReply::putToHttpSynchronous() QCOMPARE(uploadedData, data); } +void tst_QNetworkReply::putWithoutBody_data() +{ + QTest::addColumn<bool>("client_data"); + + QTest::newRow("client_has_data") << true; + QTest::newRow("client_does_not_have_data") << false; +} + +void tst_QNetworkReply::putWithoutBody() +{ + QFETCH(bool, client_data); + + QBuffer buff; + + if (client_data) { + buff.setData("Dummy data from client to server"); + buff.open(QIODevice::ReadOnly); + } + + QByteArray dataFromServerToClient = QByteArray("Some ridiculous dummy data"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + + QNetworkReplyPtr reply; + if (client_data) + reply.reset(manager.put(request, &buff)); + else + reply.reset(manager.put(request, nullptr)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.foundContentLength, client_data); +} + + void tst_QNetworkReply::postToHttp_data() { putToFile_data(); @@ -2375,7 +2788,7 @@ void tst_QNetworkReply::postToHttp() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -2402,7 +2815,7 @@ void tst_QNetworkReply::postToHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -2424,6 +2837,7 @@ void tst_QNetworkReply::postToHttpSynchronous() QCOMPARE(uploadedData, md5sum.toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::postToHttpMultipart_data() { QTest::addColumn<QUrl>("url"); @@ -2463,8 +2877,8 @@ void tst_QNetworkReply::postToHttpMultipart_data() QHttpMultiPart *customMultiPart = new QHttpMultiPart; customMultiPart->append(textPart); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n"; QTest::newRow("text-custom") << url << customMultiPart << expectedData << QByteArray("custom"); @@ -2500,18 +2914,18 @@ void tst_QNetworkReply::postToHttpMultipart_data() multiPart3->append(textPart); multiPart3->append(textPart2); multiPart3->append(textPart3); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: myRawHeader, value: 'myValue'\n" - "header: Content-Disposition, value: 'form-data; name=\"text2\"'\n" + "header: content-type, value: 'text/plain'\n" + "header: myrawheader, value: 'myValue'\n" + "header: content-disposition, value: 'form-data; name=\"text2\"'\n" "content: some more bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text3\"'\n" - "header: Content-Location, value: 'http://my.test.location.tld'\n" + "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text3\"'\n" + "header: content-location, value: 'http://my.test.location.tld'\n" "content: even more bytes\n\n"; QTest::newRow("text-text-text") << url << multiPart3 << expectedData << QByteArray("alternative"); @@ -2682,8 +3096,8 @@ void tst_QNetworkReply::postToHttpMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); @@ -2733,7 +3147,50 @@ void tst_QNetworkReply::multipartSkipIndices() // QTBUG-32534 } multiPart->deleteLater(); } +#endif + +void tst_QNetworkReply::postWithoutBody_data() +{ + QTest::addColumn<bool>("client_data"); + + QTest::newRow("client_has_data") << true; + QTest::newRow("client_does_not_have_data") << false; +} + +void tst_QNetworkReply::postWithoutBody() +{ + QFETCH(bool, client_data); + + QBuffer buff; + + if (client_data) { + buff.setData("Dummy data from client to server"); + buff.open(QIODevice::ReadOnly); + } + + QByteArray dataFromServerToClient = QByteArray("Some ridiculous dummy data"); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(dataFromServerToClient.size()); + httpResponse += "\r\n\r\n"; + httpResponse += dataFromServerToClient; + + MiniHttpServer server(httpResponse); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + + QNetworkReplyPtr reply; + if (client_data) + reply.reset(manager.post(request, &buff)); + else + reply.reset(manager.post(request, nullptr)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(server.foundContentLength, client_data); +} +#if QT_CONFIG(http) void tst_QNetworkReply::putToHttpMultipart_data() { postToHttpMultipart_data(); @@ -2770,21 +3227,22 @@ void tst_QNetworkReply::putToHttpMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); // QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above QCOMPARE(replyData, expectedReplyData); } +#endif -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::putToHttps_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + uniqueExtension = createUniqueExtension(); putToFile_data(); } @@ -2826,9 +3284,9 @@ void tst_QNetworkReply::putToHttps() void tst_QNetworkReply::putToHttpsSynchronous_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvalueate() retruns recoverable error, update the server's certificate"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvalueate() retruns recoverable error, update the server's certificate"); + uniqueExtension = createUniqueExtension(); putToFile_data(); } @@ -2874,9 +3332,9 @@ void tst_QNetworkReply::putToHttpsSynchronous() void tst_QNetworkReply::postToHttps_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + putToFile_data(); } @@ -2889,7 +3347,7 @@ void tst_QNetworkReply::postToHttps() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -2908,9 +3366,9 @@ void tst_QNetworkReply::postToHttps() void tst_QNetworkReply::postToHttpsSynchronous_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + putToFile_data(); } @@ -2923,7 +3381,7 @@ void tst_QNetworkReply::postToHttpsSynchronous() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -2945,11 +3403,12 @@ void tst_QNetworkReply::postToHttpsSynchronous() QCOMPARE(uploadedData, md5sum.toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::postToHttpsMultipart_data() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvaluate() returns recoverable error, update the certificate on server"); + postToHttpMultipart_data(); } @@ -2988,15 +3447,15 @@ void tst_QNetworkReply::postToHttpsMultipart() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); QCOMPARE(replyData, expectedReplyData); } - -#endif // QT_NO_SSL +#endif +#endif // QT_CONFIG(ssl) void tst_QNetworkReply::deleteFromHttp_data() { @@ -3125,7 +3584,7 @@ void tst_QNetworkReply::connectToIPv6Address() if (!QtNetworkSettings::hasIPv6()) QSKIP("system doesn't support ipv6!"); - QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\ncontent-length: "); httpResponse += QByteArray::number(dataToSend.size()); httpResponse += "\r\n\r\n"; httpResponse += dataToSend; @@ -3140,7 +3599,7 @@ void tst_QNetworkReply::connectToIPv6Address() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QByteArray content = reply->readAll(); //qDebug() << server.receivedData; - QByteArray hostinfo = "\r\nHost: " + hostfield + ':' + QByteArray::number(server.serverPort()) + "\r\n"; + QByteArray hostinfo = "\r\nhost: " + hostfield + ':' + QByteArray::number(server.serverPort()) + "\r\n"; QVERIFY(server.receivedData.contains(hostinfo)); QCOMPARE(content, dataToSend); QCOMPARE(reply->url(), request.url()); @@ -3302,6 +3761,18 @@ void tst_QNetworkReply::ioGetFromFile() QCOMPARE(reader.data, data); } +void tst_QNetworkReply::ioGetFromFileUrl() +{ + // This immediately fails on non-windows platforms: + QNetworkRequest request(QUrl("file://unc-server/some/path")); + QNetworkReplyPtr reply(manager.get(request)); + QSignalSpy finishedSpy(reply.get(), &QNetworkReply::finished); + // QTBUG-105618: This would, on non-Windows platforms, never happen because the signal + // was emitted before the constructor finished, leaving no chance at all to connect to the + // signal + QVERIFY(finishedSpy.wait()); +} + void tst_QNetworkReply::ioGetFromFtp_data() { if (!ftpSupported) @@ -3319,7 +3790,9 @@ void tst_QNetworkReply::ioGetFromFtp() { QFETCH(QString, fileName); QFile reference(fileName); - reference.open(QIODevice::ReadOnly); // will fail for bigfile + const bool ok = reference.open(QIODevice::ReadOnly); // will fail for bigfile + if (fileName != "bigfile") + QVERIFY(ok); QNetworkRequest request("ftp://" + QtNetworkSettings::ftpServerName() + "/qtest/" + fileName); QNetworkReplyPtr reply(manager.get(request)); @@ -3344,7 +3817,7 @@ void tst_QNetworkReply::ioGetFromFtpWithReuse() QSKIP("FTP is not supported"); QString fileName = testDataDir + "/rfc3252.txt"; QFile reference(fileName); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QNetworkRequest request(QUrl("ftp://" + QtNetworkSettings::ftpServerName() + "/qtest/rfc3252.txt")); @@ -3474,7 +3947,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth_data() QTest::addColumn<int>("expectedAuth"); QFile reference(testDataDir + "/rfc3252.txt"); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QByteArray referenceData = reference.readAll(); QString httpServer = QtNetworkSettings::httpServerName(); QTest::newRow("basic") @@ -3548,7 +4021,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() QCOMPARE(reader1.data, expectedData); QCOMPARE(reader2.data, expectedData); - QCOMPARE(authspy.count(), (expectedAuth ? 1 : 0)); + QCOMPARE(authspy.size(), (expectedAuth ? 1 : 0)); expectedAuth = qMax(0, expectedAuth - 1); } @@ -3569,7 +4042,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, expectedData); - QCOMPARE(authspy.count(), (expectedAuth ? 1 : 0)); + QCOMPARE(authspy.size(), (expectedAuth ? 1 : 0)); expectedAuth = qMax(0, expectedAuth - 1); } @@ -3586,7 +4059,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() // bad credentials in a synchronous request should just fail QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); } else { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3612,7 +4085,7 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() // bad credentials in a synchronous request should just fail QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); } else { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3638,11 +4111,11 @@ void tst_QNetworkReply::ioGetFromHttpWithAuthSynchronous() QNetworkReplyPtr replySync(manager.get(request)); QVERIFY(replySync->isFinished()); // synchronous QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 401); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() { // This test sends three requests @@ -3679,7 +4152,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QCOMPARE(reader1.data, referenceData); QCOMPARE(reader2.data, referenceData); - QCOMPARE(authspy.count(), 1); + QCOMPARE(authspy.size(), 1); } reference.seek(0); @@ -3702,7 +4175,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } // now check with synchronous calls: @@ -3715,7 +4188,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QNetworkReplyPtr replySync(manager.get(request)); QVERIFY(replySync->isFinished()); // synchronous - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); // we cannot use a data reader here, since that connects to the readyRead signal, // just use readAll() @@ -3743,7 +4216,7 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuthSynchronous() manager.setProxy(QNetworkProxy()); // reset QVERIFY(replySync->isFinished()); // synchronous QCOMPARE(replySync->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 407); } @@ -3775,7 +4248,7 @@ void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } // set an invalid proxy just to make sure that we can't load @@ -3799,15 +4272,14 @@ void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() QVERIFY(reader.data.isEmpty()); QVERIFY(int(reply->error()) > 0); - QEXPECT_FAIL("", "QTcpSocket doesn't return enough information yet", Continue); QCOMPARE(int(reply->error()), int(QNetworkReply::ProxyConnectionRefusedError)); - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::ioGetFromHttpsWithSslErrors() { QFile reference(testDataDir + "/rfc3252.txt"); @@ -3830,7 +4302,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithSslErrors() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(sslspy.count(), 1); + QCOMPARE(sslspy.size(), 1); QVERIFY(!storedSslConfiguration.isNull()); QVERIFY(!reply->sslConfiguration().isNull()); @@ -3858,7 +4330,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithIgnoreSslErrors() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QCOMPARE(reader.data, reference.readAll()); - QCOMPARE(sslspy.count(), 1); + QCOMPARE(sslspy.size(), 1); QVERIFY(!storedSslConfiguration.isNull()); QVERIFY(!reply->sslConfiguration().isNull()); @@ -3881,7 +4353,7 @@ void tst_QNetworkReply::ioGetFromHttpsWithSslHandshakeError() QCOMPARE(waitForFinish(reply), int(Failure)); QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); - QCOMPARE(sslspy.count(), 0); + QCOMPARE(sslspy.size(), 0); } #endif @@ -3939,11 +4411,11 @@ void tst_QNetworkReply::ioGetFromHttpBrokenServer() QCOMPARE(waitForFinish(reply), int(Failure)); QCOMPARE(reply->url(), request.url()); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QVERIFY(reply->error() != QNetworkReply::NoError); } -void tst_QNetworkReply::ioGetFromHttpStatus100_data() +void tst_QNetworkReply::ioGetFromHttpStatusInformational_data() { QTest::addColumn<QByteArray>("dataToSend"); QTest::addColumn<int>("statusCode"); @@ -3954,9 +4426,25 @@ void tst_QNetworkReply::ioGetFromHttpStatus100_data() QTest::newRow("minimal+404") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; QTest::newRow("with_headers") << QByteArray("HTTP/1.1 100 Continue\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; QTest::newRow("with_headers2") << QByteArray("HTTP/1.1 100 Continue\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + + QTest::newRow("normal-custom") << QByteArray("HTTP/1.1 133 Custom\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal2-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 200 OK\r\n\r\n") << 200; + QTest::newRow("minimal3-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 200 OK\n\n") << 200; + QTest::newRow("minimal+404-custom") << QByteArray("HTTP/1.1 133 Custom\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; + QTest::newRow("with_headers-custom") << QByteArray("HTTP/1.1 133 Custom\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("with_headers2-custom") << QByteArray("HTTP/1.1 133 Custom\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + + QTest::newRow("normal-custom2") << QByteArray("HTTP/1.1 179 Custom2\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal2-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 200 OK\r\n\r\n") << 200; + QTest::newRow("minimal3-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 200 OK\n\n") << 200; + QTest::newRow("minimal+404-custom2") << QByteArray("HTTP/1.1 179 Custom2\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; + QTest::newRow("with_headers-custom2") << QByteArray("HTTP/1.1 179 Custom2\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("with_headers2-custom2") << QByteArray("HTTP/1.1 179 Custom2\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; } -void tst_QNetworkReply::ioGetFromHttpStatus100() +void tst_QNetworkReply::ioGetFromHttpStatusInformational() { QFETCH(QByteArray, dataToSend); QFETCH(int, statusCode); @@ -4010,7 +4498,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply200 = "HTTP/1.0 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-store\r\n" "Content-length: 8\r\n" "\r\n" @@ -4145,7 +4633,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -4194,7 +4682,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache() QCOMPARE(reply->readAll().constData(), qPrintable(body)); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioGetWithManyProxies_data() { QTest::addColumn<QList<QNetworkProxy> >("proxyList"); @@ -4255,7 +4743,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS with HTTP transparent proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); @@ -4275,15 +4763,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() // Tests that fail: - // HTTP request with FTP caching proxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); - QTest::newRow("http-on-ftp") - << proxyList << QNetworkProxy() - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::ProxyNotFoundError; - if (ftpSupported) { + // HTTP request with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); + QTest::newRow("http-on-ftp") + << proxyList << QNetworkProxy() + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + // FTP request with HTTP caching proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, @@ -4305,7 +4793,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::ProxyNotFoundError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS with HTTP caching proxy proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129); @@ -4314,13 +4802,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::ProxyNotFoundError; - // HTTPS with FTP caching proxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); - QTest::newRow("https-on-ftp") - << proxyList << QNetworkProxy() - << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::ProxyNotFoundError; + if (ftpSupported) { + // HTTPS with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121); + QTest::newRow("https-on-ftp") + << proxyList << QNetworkProxy() + << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + } #endif // Complex requests: @@ -4343,15 +4833,17 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::NoError; - // HTTP request with FTP + HTTP + SOCKS - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) - << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1081); - QTest::newRow("http-on-ftp+http+socks") - << proxyList << proxyList.at(1) // second proxy should be used - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; + if (ftpSupported) { + // HTTP request with FTP + HTTP + SOCKS + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) + << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1081); + QTest::newRow("http-on-ftp+http+socks") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + } // HTTP request with NoProxy + HTTP proxyList.clear(); @@ -4363,15 +4855,15 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; // HTTP request with FTP + NoProxy - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::NoProxy); - QTest::newRow("http-on-ftp+noproxy") - << proxyList << proxyList.at(1) // second proxy should be used - << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; - if (ftpSupported) { + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::NoProxy); + QTest::newRow("http-on-ftp+noproxy") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + // FTP request with HTTP Caching + FTP proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, @@ -4384,7 +4876,7 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << QNetworkReply::NoError; } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // HTTPS request with HTTP Caching + HTTP transparent proxyList.clear(); proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) @@ -4394,15 +4886,17 @@ void tst_QNetworkReply::ioGetWithManyProxies_data() << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" << QNetworkReply::NoError; - // HTTPS request with FTP + HTTP C + HTTP T - proxyList.clear(); - proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) - << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) - << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); - QTest::newRow("https-on-ftp+httpcaching+http") - << proxyList << proxyList.at(2) // skip the first two - << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" - << QNetworkReply::NoError; + if (ftpSupported) { + // HTTPS request with FTP + HTTP C + HTTP T + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3129); + QTest::newRow("https-on-ftp+httpcaching+http") + << proxyList << proxyList.at(2) // skip the first two + << "https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + } #endif } @@ -4428,7 +4922,7 @@ void tst_QNetworkReply::ioGetWithManyProxies() QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -4437,7 +4931,7 @@ void tst_QNetworkReply::ioGetWithManyProxies() manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -4457,19 +4951,19 @@ void tst_QNetworkReply::ioGetWithManyProxies() // now verify that the proxies worked: QFETCH(QNetworkProxy, proxyUsed); if (proxyUsed.type() == QNetworkProxy::NoProxy) { - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } else { if (QByteArray(QTest::currentDataTag()).startsWith("ftp-")) return; // No authentication with current FTP or with FTP proxies - QCOMPARE(authspy.count(), 1); + QCOMPARE(authspy.size(), 1); QCOMPARE(qvariant_cast<QNetworkProxy>(authspy.at(0).at(0)), proxyUsed); } } else { // request failed - QCOMPARE(authspy.count(), 0); + QCOMPARE(authspy.size(), 0); } } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::ioPutToFileFromFile_data() { @@ -4542,6 +5036,7 @@ void tst_QNetworkReply::ioPutToFileFromSocket() QCOMPARE(contents, data); } +#if QT_CONFIG(localserver) void tst_QNetworkReply::ioPutToFileFromLocalSocket_data() { putToFile_data(); @@ -4572,10 +5067,6 @@ void tst_QNetworkReply::ioPutToFileFromLocalSocket() QNetworkReplyPtr reply(manager.put(QNetworkRequest(url), passive)); passive->setParent(reply.data()); -#ifdef Q_OS_WIN - if (!data.isEmpty()) - QEXPECT_FAIL("", "QTBUG-18385", Abort); -#endif QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QCOMPARE(reply->error(), QNetworkReply::NoError); @@ -4589,6 +5080,7 @@ void tst_QNetworkReply::ioPutToFileFromLocalSocket() QByteArray contents = file.readAll(); QCOMPARE(contents, data); } +#endif // Currently no stdin/out supported for Windows CE. void tst_QNetworkReply::ioPutToFileFromProcess_data() @@ -4638,6 +5130,10 @@ void tst_QNetworkReply::ioPutToFileFromProcess() QByteArray contents = file.readAll(); QCOMPARE(contents, data); + if (process.state() == QProcess::Running) + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitCode(), 0); + #endif // QT_CONFIG(process) } @@ -4748,7 +5244,7 @@ void tst_QNetworkReply::ioPostToHttpFromFile() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); @@ -4766,7 +5262,7 @@ void tst_QNetworkReply::ioPostToHttpFromFile() QCOMPARE(reply->readAll().trimmed(), md5sum(sourceFile.readAll()).toHex()); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::ioPostToHttpFromSocket_data() { QTest::addColumn<QByteArray>("data"); @@ -4776,7 +5272,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocket_data() QTest::addColumn<int>("authenticationRequiredCount"); QTest::addColumn<int>("proxyAuthenticationRequiredCount"); - for (int i = 0; i < proxies.count(); ++i) + for (int i = 0; i < proxies.size(); ++i) for (int auth = 0; auth < 2; ++auth) { QUrl url; if (auth) @@ -4825,7 +5321,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() socketpair.endPoints[0]->write(data); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); manager.setProxy(proxy); QNetworkReplyPtr reply(manager.post(request, socketpair.endPoints[1])); @@ -4854,8 +5350,8 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); - QTEST(int(authenticationRequiredSpy.count()), "authenticationRequiredCount"); - QTEST(int(proxyAuthenticationRequiredSpy.count()), "proxyAuthenticationRequiredCount"); + QTEST(int(authenticationRequiredSpy.size()), "authenticationRequiredCount"); + QTEST(int(proxyAuthenticationRequiredSpy.size()), "proxyAuthenticationRequiredCount"); } void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous_data() @@ -4899,7 +5395,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -4917,7 +5413,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) // this tests checks if rewinding the POST-data to some place in the middle // worked. @@ -4930,7 +5426,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileToEnd() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -4956,7 +5452,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // only send 5 bytes request.setHeader(QNetworkRequest::ContentLengthHeader, 5); QVERIFY(request.header(QNetworkRequest::ContentLengthHeader).isValid()); @@ -4987,7 +5483,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfQBufferFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &uploadBuffer)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -5015,7 +5511,7 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // disallow buffering request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); @@ -5034,7 +5530,7 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() QCOMPARE(reply->error(), QNetworkReply::ContentReSendError); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) class SslServer : public QTcpServer { Q_OBJECT @@ -5100,7 +5596,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress() QUrl url = QUrl(QLatin1String("https://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); @@ -5150,7 +5646,7 @@ void tst_QNetworkReply::ioGetFromBuiltinHttp_data() QTest::addColumn<int>("bufferSize"); QTest::newRow("http+unlimited") << false << 0; QTest::newRow("http+limited") << false << 4096; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https+unlimited") << true << 0; QTest::newRow("https+limited") << true << 4096; #endif @@ -5254,7 +5750,7 @@ void tst_QNetworkReply::ioPostToHttpUploadProgress() // create the request QUrl url = QUrl(QString("http://127.0.0.1:%1/").arg(server.serverPort())); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5322,18 +5818,14 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest normalRequest(url); - normalRequest.setRawHeader("Content-Type", "application/octet-stream"); + normalRequest.setRawHeader("content-type", "application/octet-stream"); QNetworkRequest catchAllSignalsRequest(normalRequest); catchAllSignalsRequest.setAttribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute, true); - QList<QNetworkRequest> requests; - requests << normalRequest << catchAllSignalsRequest; - QList<int> signalCount; - foreach (const QNetworkRequest &request, requests) { - + for (const QNetworkRequest &request : {normalRequest, catchAllSignalsRequest}) { sourceFile.seek(0); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); @@ -5354,7 +5846,7 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QVERIFY(!QTestEventLoop::instance().timeout()); incomingSocket->close(); - signalCount.append(spy.count()); + signalCount.append(spy.size()); reply->deleteLater(); } server.close(); @@ -5377,7 +5869,7 @@ void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() // create the request QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &buffer)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5400,7 +5892,7 @@ void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() QVERIFY(!QTestEventLoop::instance().timeout()); // final check: only 1 uploadProgress has been emitted - QCOMPARE(spy.length(), 1); + QCOMPARE(spy.size(), 1); QList<QVariant> args = spy.last(); QVERIFY(!args.isEmpty()); QCOMPARE(args.at(0).toLongLong(), buffer.size()); @@ -5423,7 +5915,11 @@ void tst_QNetworkReply::lastModifiedHeaderForFile() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); - QCOMPARE(header, fileInfo.lastModified()); + QDateTime expected = fileInfo.lastModified(); + // remove msecs, HTTP dates don't support it + expected = expected.addMSecs(-expected.time().msec()); + + QCOMPARE(header.toUTC(), expected.toUTC()); } void tst_QNetworkReply::lastModifiedHeaderForHttp() @@ -5438,7 +5934,7 @@ void tst_QNetworkReply::lastModifiedHeaderForHttp() QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); QDateTime realDate = QDateTime::fromString("2007-05-22T12:04:57", Qt::ISODate); - realDate.setTimeSpec(Qt::UTC); + realDate.setTimeZone(QTimeZone::UTC); QCOMPARE(header, realDate); } @@ -5571,7 +6067,7 @@ void tst_QNetworkReply::downloadProgress() QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(reply->isFinished()); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); //final progress should have equal current & total QList<QVariant> args = spy.takeLast(); @@ -5617,14 +6113,14 @@ void tst_QNetworkReply::uploadProgress() QVERIFY(server.hasPendingConnections()); QTcpSocket *receiver = server.nextPendingConnection(); - if (finished.count() == 0) { + if (finished.size() == 0) { // it's not finished yet, so wait for it to be QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); } delete receiver; - QVERIFY(finished.count() > 0); - QVERIFY(spy.count() > 0); + QVERIFY(finished.size() > 0); + QVERIFY(spy.size() > 0); QList<QVariant> args = spy.last(); QCOMPARE(args.at(0).toInt(), data.size()); @@ -5729,7 +6225,7 @@ void tst_QNetworkReply::receiveCookiesFromHttp() QByteArray data = cookieString.toLatin1() + '\n'; QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PostOperation, request, reply, data)); @@ -5757,7 +6253,7 @@ void tst_QNetworkReply::receiveCookiesFromHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -5906,11 +6402,11 @@ void tst_QNetworkReply::nestedEventLoops() QTestEventLoop::instance().enterLoop(20); QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); - QCOMPARE(finishedspy.count(), 1); - QCOMPARE(errorspy.count(), 0); + QCOMPARE(finishedspy.size(), 1); + QCOMPARE(errorspy.size(), 0); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommands_data() { QTest::addColumn<QUrl>("url"); @@ -5921,7 +6417,7 @@ void tst_QNetworkReply::httpProxyCommands_data() << QUrl("http://0.0.0.0:4443/http-request") << QByteArray("HTTP/1.0 200 OK\r\nProxy-Connection: close\r\nContent-Length: 1\r\n\r\n1") << "GET http://0.0.0.0:4443/http-request HTTP/1."; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https") << QUrl("https://0.0.0.0:4443/https-request") << QByteArray("HTTP/1.0 200 Connection Established\r\n\r\n") @@ -5940,7 +6436,7 @@ void tst_QNetworkReply::httpProxyCommands() manager.setProxy(proxy); QNetworkRequest request(url); - request.setRawHeader("User-Agent", "QNetworkReplyAutoTest/1.0"); + request.setRawHeader("user-agent", "QNetworkReplyAutoTest/1.0"); QNetworkReplyPtr reply(manager.get(request)); // wait for the finished signal @@ -5953,14 +6449,15 @@ void tst_QNetworkReply::httpProxyCommands() // especially since it won't succeed in the HTTPS case // so just check that the command was correct - QString receivedHeader = proxyServer.receivedData.left(expectedCommand.length()); + QString receivedHeader = proxyServer.receivedData.left(expectedCommand.size()); QCOMPARE(receivedHeader, expectedCommand); //QTBUG-17223 - make sure the user agent from the request is sent to proxy server even for CONNECT - int uapos = proxyServer.receivedData.indexOf("User-Agent"); + const QByteArray cUserAgent = "user-agent: "; + int uapos = proxyServer.receivedData.toLower().indexOf(cUserAgent) + cUserAgent.size(); int uaend = proxyServer.receivedData.indexOf("\r\n", uapos); QByteArray uaheader = proxyServer.receivedData.mid(uapos, uaend - uapos); - QCOMPARE(uaheader, QByteArray("User-Agent: QNetworkReplyAutoTest/1.0")); + QCOMPARE(uaheader, QByteArray("QNetworkReplyAutoTest/1.0")); } class ProxyChangeHelper : public QObject @@ -5983,7 +6480,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous_data() { httpProxyCommands_data(); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) struct QThreadCleanup { @@ -5997,15 +6494,7 @@ struct QThreadCleanup } }; -struct QDeleteLaterCleanup -{ - static inline void cleanup(QObject *o) - { - o->deleteLater(); - } -}; - -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::httpProxyCommandsSynchronous() { QFETCH(QUrl, url); @@ -6016,7 +6505,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous() // the server thread, because the client is never returning to the // event loop QScopedPointer<QThread, QThreadCleanup> serverThread(new QThread); - QScopedPointer<MiniHttpServer, QDeleteLaterCleanup> proxyServer(new MiniHttpServer(responseToSend, false, serverThread.data())); + QScopedPointer<MiniHttpServer, QScopedPointerDeleteLater> proxyServer(new MiniHttpServer(responseToSend, false, serverThread.data())); QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer->serverPort()); manager.setProxy(proxy); @@ -6037,7 +6526,7 @@ void tst_QNetworkReply::httpProxyCommandsSynchronous() // especially since it won't succeed in the HTTPS case // so just check that the command was correct - QString receivedHeader = proxyServer->receivedData.left(expectedCommand.length()); + QString receivedHeader = proxyServer->receivedData.left(expectedCommand.size()); QCOMPARE(receivedHeader, expectedCommand); } @@ -6095,7 +6584,7 @@ void tst_QNetworkReply::proxyChange() QVERIFY(int(reply3->error()) > 0); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) void tst_QNetworkReply::authorizationError_data() { @@ -6134,9 +6623,9 @@ void tst_QNetworkReply::authorizationError() QCOMPARE(waitForFinish(reply), int(Failure)); QFETCH(int, errorSignalCount); - QCOMPARE(errorSpy.count(), errorSignalCount); + QCOMPARE(errorSpy.size(), errorSignalCount); QFETCH(int, finishedSignalCount); - QCOMPARE(finishedSpy.count(), finishedSignalCount); + QCOMPARE(finishedSpy.size(), finishedSignalCount); QFETCH(int, error); QCOMPARE(reply->error(), QNetworkReply::NetworkError(error)); @@ -6174,7 +6663,6 @@ void tst_QNetworkReply::httpConnectionCount() } QVERIFY(server->listen()); - QCoreApplication::instance()->processEvents(); QUrl url("http://127.0.0.1:" + QString::number(server->serverPort()) + QLatin1Char('/')); if (encrypted) @@ -6185,7 +6673,7 @@ void tst_QNetworkReply::httpConnectionCount() QUrl urlCopy = url; urlCopy.setPath(u'/' + QString::number(i)); // Differentiate the requests a bit QNetworkRequest request(urlCopy); - request.setAttribute(QNetworkRequest::Http2AllowedAttribute, http2Enabled); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, http2Enabled); QNetworkReply* reply = manager.get(request); reply->setParent(server.data()); if (encrypted) @@ -6193,26 +6681,39 @@ void tst_QNetworkReply::httpConnectionCount() } int pendingConnectionCount = 0; - QElapsedTimer timer; - timer.start(); - while(pendingConnectionCount <= 20) { - QTestEventLoop::instance().enterLoop(1); + 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 + // to see if we should upgrade: + const int rampDown = http2Enabled ? 2 : 1; + while (pendingConnectionCount <= 6) { + if (!QTest::qWaitFor(newPendingConnection, pendingConnectionCount >= rampDown ? 3s : 7s)) + break; QTcpSocket *socket = server->nextPendingConnection(); - while (socket != 0) { - if (pendingConnectionCount == 0) { - // respond to the first connection so we know to transition to HTTP/1.1 when using - // HTTP/2 - socket->write(httpEmpty200Response); + while (socket) { + if (pendingConnectionCount == 0 && http2Enabled) { + // Respond to the first connection so we know to transition to HTTP/1.1 when using + // HTTP/2. + // Because of some internal state machinery we need to wait until the request has + // actually been written to the server before we can reply. + auto connection = std::make_shared<QMetaObject::Connection>(); + auto replyOnRequest = [=, buffer = QByteArray()]() mutable { + buffer += socket->readAll(); + if (!buffer.contains("\r\n\r\n")) + return; + socket->write(httpEmpty200Response); + QObject::disconnect(*connection); + }; + *connection = QObject::connect(socket, &QTcpSocket::readyRead, socket, + std::move(replyOnRequest)); + if (socket->bytesAvailable()) // If we already have data, check it now + emit socket->readyRead(); } pendingConnectionCount++; socket->setParent(server.data()); socket = server->nextPendingConnection(); } - - // at max. wait 10 sec - if (timer.elapsed() > 10000) - break; } QCOMPARE(pendingConnectionCount, 6); @@ -6392,7 +6893,7 @@ void tst_QNetworkReply::httpRecursiveCreation() QVERIFY(!QTestEventLoop::instance().timeout()); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::ignoreSslErrorsList_data() { QTest::addColumn<QList<QSslError> >("expectedSslErrors"); @@ -6400,8 +6901,8 @@ void tst_QNetworkReply::ignoreSslErrorsList_data() QList<QSslError> expectedSslErrors; QList<QSslCertificate> certs = QSslCertificate::fromPath(testDataDir + certsFilePath); - QSslError rightError(FLUKE_CERTIFICATE_ERROR, certs.at(0)); - QSslError wrongError(FLUKE_CERTIFICATE_ERROR); + QSslError rightError(flukeCertTlsError, certs.at(0)); + QSslError wrongError(flukeCertTlsError); QTest::newRow("SSL-failure-empty-list") << expectedSslErrors << QNetworkReply::SslHandshakeFailedError; expectedSslErrors.append(wrongError); @@ -6468,23 +6969,23 @@ void tst_QNetworkReply::sslConfiguration_data() QTest::newRow("empty") << QSslConfiguration() << false; QSslConfiguration conf = QSslConfiguration::defaultConfiguration(); QTest::newRow("default") << conf << false; // does not contain test server cert -#if QT_CONFIG(securetransport) - qWarning("SecTrustEvaluate() will fail, update the certificate on server"); -#else - QList<QSslCertificate> testServerCert = QSslCertificate::fromPath(testDataDir + certsFilePath); - conf.setCaCertificates(testServerCert); + if (isSecureTransport) { + qWarning("SecTrustEvaluate() will fail, update the certificate on server"); + } else { + QList<QSslCertificate> testServerCert = QSslCertificate::fromPath(testDataDir + certsFilePath); + conf.setCaCertificates(testServerCert); - QTest::newRow("set-root-cert") << conf << true; - conf.setProtocol(QSsl::SecureProtocols); - QTest::newRow("secure") << conf << true; -#endif + QTest::newRow("set-root-cert") << conf << true; + conf.setProtocol(QSsl::SecureProtocols); + QTest::newRow("secure") << conf << true; + } } void tst_QNetworkReply::encrypted() { -#if QT_CONFIG(securetransport) - QSKIP("SecTrustEvalute() fails with old server certificate"); -#endif + if (isSecureTransport) + QSKIP("SecTrustEvalute() fails with old server certificate"); + QUrl url("https://" + QtNetworkSettings::httpServerName()); QNetworkRequest request(url); QNetworkReply *reply = manager.get(request); @@ -6495,7 +6996,7 @@ void tst_QNetworkReply::encrypted() QTestEventLoop::instance().enterLoop(20); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); reply->deleteLater(); } @@ -6510,8 +7011,7 @@ void tst_QNetworkReply::abortOnEncrypted() server.connect(&server, &SslServer::newEncryptedConnection, [&server]() { // MSVC 201X C4573-misunderstands connect() or QObject::connect(), so use server.connect(): server.connect(server.socket, &QTcpSocket::readyRead, server.socket, []() { - // This slot must not be invoked! - QVERIFY(false); + QFAIL("This slot must not be invoked!"); }); }); @@ -6525,7 +7025,7 @@ void tst_QNetworkReply::abortOnEncrypted() }); QSignalSpy spyEncrypted(reply, &QNetworkReply::encrypted); - QTRY_COMPARE(spyEncrypted.count(), 1); + QTRY_COMPARE(spyEncrypted.size(), 1); // Wait for the socket to be closed again in order to be sure QTcpSocket::readyRead would have been emitted. QTRY_VERIFY(server.socket != nullptr); @@ -6557,9 +7057,8 @@ void tst_QNetworkReply::sslSessionSharing_data() void tst_QNetworkReply::sslSessionSharing() { -#if QT_CONFIG(schannel) || defined(QT_SECURETRANSPORT) - QSKIP("Not implemented with SecureTransport/Schannel"); -#endif + if (isSchannel || isSecureTransport) + QSKIP("Not implemented with SecureTransport/Schannel"); QString urlString("https://" + QtNetworkSettings::httpServerName()); QList<QNetworkReplyPtr> replies; @@ -6628,9 +7127,8 @@ void tst_QNetworkReply::sslSessionSharingFromPersistentSession_data() void tst_QNetworkReply::sslSessionSharingFromPersistentSession() { -#if QT_CONFIG(schannel) || defined(QT_SECURETRANSPORT) - QSKIP("Not implemented with SecureTransport/Schannel"); -#endif + if (isSchannel || isSecureTransport) + QSKIP("Not implemented with SecureTransport/Schannel"); QString urlString("https://" + QtNetworkSettings::httpServerName()); @@ -6683,7 +7181,7 @@ void tst_QNetworkReply::sslSessionSharingFromPersistentSession() } #endif // QT_BUILD_INTERNAL -#endif // QT_NO_SSL +#endif // QT_CONFIG(ssl) void tst_QNetworkReply::getAndThenDeleteObject_data() { @@ -6721,7 +7219,20 @@ void tst_QNetworkReply::getAndThenDeleteObject() // see https://bugs.webkit.org/show_bug.cgi?id=38935 void tst_QNetworkReply::symbianOpenCDataUrlCrash() { - QString requestUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAAB3RJTUUH2AUSEgolrgBvVQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAAHlSURBVHja5VbNShxBEK6ZaXtnHTebQPA1gngNmfaeq+QNPIlIXkC9iQdJxJNvEHLN3VkxhxxE8gTmEhAVddXZ6Z3f9Ndriz89/sHmkBQUVVT1fB9d9c3uOERUKTunIdn3HzstxGpYBDS4wZk7TAJj/wlJ90J+jnuygqs8svSj+/rGHBos3rE18XBvfU3no7NzlJfUaY/5whAwl8Lr/WDUv4ODxTMb+P5xLExe5LmO559WqTX/MQR4WZYEAtSePS4pE0qSnuhnRUcBU5Gm2k9XljU4Z26I3NRxBrd80rj2fh+KNE0FY4xevRgTjREvPFpasAK8Xli6MUbbuKw3afAGgSBXozo5u4hkmncAlkl5wx8iMGbdyQjnCFEiEwGiosj1UQA/x2rVddiVoi+l4IxE0PTDnx+mrQBvvnx9cFz3krhVvuhzFn579/aq/n5rW8fbtTqiWhIQZEo17YBvbkxOXNVndnYpTvod7AtiuN2re0+siwcB9oH8VxxrNwQQAhzyRs30n7wTI2HIN2g2QtQwjjhJIQatOq7E8bIVCLwzpl83Lvtvl+NohWWlE8UZTWEMAGCcR77fHKhPnZF5tYie6dfdxCphACmLPM+j8bYfmTryg64kV9Vh3mV8jP0b/4wO/YUPiT/8i0MLf55lSQAAAABJRU5ErkJggg=="); + QString requestUrl("data:image/" + "png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAAB3RJTUUH2AUSEgolrgBvVQAAAAl" + "wSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAAHlSURBVHja5VbNShxBEK6ZaXtnHT" + "ebQPA1gngNmfaeq+QNPIlIXkC9iQdJxJNvEHLN3VkxhxxE8gTmEhAVddXZ6Z3f9Ndriz89/" + "sHmkBQUVVT1fB9d9c3uOERUKTunIdn3HzstxGpYBDS4wZk7TAJj/wlJ90J+jnuygqs8svSj+/" + "rGHBos3rE18XBvfU3no7NzlJfUaY/5whAwl8Lr/WDUv4ODxTMb+P5xLExe5LmO559WqTX/" + "MQR4WZYEAtSePS4pE0qSnuhnRUcBU5Gm2k9XljU4Z26I3NRxBrd80rj2fh+" + "KNE0FY4xevRgTjREvPFpasAK8Xli6MUbbuKw3afAGgSBXozo5u4hkmncAlkl5wx8iMGbdyQjnCF" + "EiEwGiosj1UQA/x2rVddiVoi+l4IxE0PTDnx+mrQBvvnx9cFz3krhVvuhzFn579/aq/" + "n5rW8fbtTqiWhIQZEo17YBvbkxOXNVndnYpTvod7AtiuN2re0+" + "siwcB9oH8VxxrNwQQAhzyRs30n7wTI2HIN2g2QtQwjjhJIQatOq7E8bIVCLwzpl83Lvtvl+" + "NohWWlE8UZTWEMAGCcR77fHKhPnZF5tYie6dfdxCphACmLPM+j8bYfmTryg64kV9Vh3mV8jP0b/" + "4wO/YUPiT/8i0MLf55lSQAAAABJRU5ErkJggg=="); QUrl url = QUrl::fromEncoded(requestUrl.toLatin1()); QNetworkRequest req(url); QNetworkReplyPtr reply; @@ -6897,13 +7408,8 @@ public: // bytesAvailable must never be 0 QVERIFY(bytesAvailable != 0); - if (bytesAvailableList.length() < 5) { - // We assume that the first few times the bytes available must be less than the complete size, e.g. - // the bytesAvailable() function works correctly in case of a downloadBuffer. - QVERIFY(bytesAvailable < uploadSize); - } if (!bytesAvailableList.isEmpty()) { - // Also check that the same bytesAvailable is not coming twice in a row + // Check that the same bytesAvailable is not coming twice in a row QVERIFY(bytesAvailableList.last() != bytesAvailable); } @@ -6915,6 +7421,12 @@ public: { // We should have already received all readyRead QVERIFY(!bytesAvailableList.isEmpty()); + for (int i = 0; i < std::min(int(bytesAvailableList.size() - 1), 5); ++i) { + // We assume that, at least, the first time the bytes available must be less than the + // complete size, e.g. the bytesAvailable() function works correctly in case of a + // downloadBuffer. + QVERIFY(bytesAvailableList.at(i) < uploadSize); + } QCOMPARE(bytesAvailableList.last(), uploadSize); } }; @@ -7018,6 +7530,7 @@ void tst_QNetworkReply::ioGetFromHttpBrokenChunkedEncoding() QCOMPARE(reply->error(), QNetworkReply::NoError); } +#if QT_CONFIG(http) // TODO: // Prepare a gzip that has one chunk that expands to the size mentioned in the bugreport. // Then have a custom HTTP server that waits after this chunk so the returning gets @@ -7037,8 +7550,7 @@ void tst_QNetworkReply::qtbug12908compressedHttpReply() QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); // QDecompressHelper will abort the download if the compressed to decompressed size ratio // differs too much, so we override it - request.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User - 1), - QByteArray("__qdecompresshelper_ignore_download_ratio")); + request.setDecompressedSafetyCheckThreshold(-1); QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -7047,6 +7559,7 @@ void tst_QNetworkReply::qtbug12908compressedHttpReply() QCOMPARE(reply->size(), qint64(16384)); QCOMPARE(reply->readAll(), QByteArray(16384, '\0')); } +#endif void tst_QNetworkReply::compressedHttpReplyBrokenGzip() { @@ -7066,7 +7579,7 @@ void tst_QNetworkReply::compressedHttpReplyBrokenGzip() QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(reply->error(), QNetworkReply::ProtocolFailure); + QCOMPARE(reply->error(), QNetworkReply::UnknownContentError); } // TODO add similar test for FTP @@ -7110,25 +7623,25 @@ void tst_QNetworkReply::qtbug4121unknownAuthentication() QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(errorSpy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); } -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) void tst_QNetworkReply::authenticationCacheAfterCancel_data() { QTest::addColumn<QNetworkProxy>("proxy"); QTest::addColumn<bool>("proxyAuth"); QTest::addColumn<QUrl>("url"); - for (int i = 0; i < proxies.count(); ++i) { + for (int i = 0; i < proxies.size(); ++i) { QTest::newRow("http" + proxies.at(i).tag) << proxies.at(i).proxy << proxies.at(i).requiresAuthentication << QUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/rfcs-auth/rfc3252.txt"); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) QTest::newRow("https" + proxies.at(i).tag) << proxies.at(i).proxy << proxies.at(i).requiresAuthentication @@ -7184,7 +7697,7 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QFETCH(bool, proxyAuth); QFETCH(QUrl, url); QNetworkAccessManager manager; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -7206,8 +7719,8 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authSpy.count(), 0); - QCOMPARE(proxyAuthSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(proxyAuthSpy.size(), 1); proxyAuthSpy.clear(); //should fail due to bad credentials @@ -7221,8 +7734,8 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() // Work round known quirk in the old test server (danted -v < v1.1.19): if (reply->error() != QNetworkReply::HostNotFoundError) QCOMPARE(reply->error(), QNetworkReply::ProxyAuthenticationRequiredError); - QCOMPARE(authSpy.count(), 0); - QVERIFY(proxyAuthSpy.count() > 0); + QCOMPARE(authSpy.size(), 0); + QVERIFY(proxyAuthSpy.size() > 0); proxyAuthSpy.clear(); // QTBUG-23136 workaround (needed even with danted v1.1.19): @@ -7247,10 +7760,10 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { - QVERIFY(proxyAuthSpy.count() > 0); + QVERIFY(proxyAuthSpy.size() > 0); proxyAuthSpy.clear(); } @@ -7263,11 +7776,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7281,11 +7794,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::NoError); - QVERIFY(authSpy.count() > 0); + QVERIFY(authSpy.size() > 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7297,11 +7810,11 @@ void tst_QNetworkReply::authenticationCacheAfterCancel() QCOMPARE(reply->error(), QNetworkReply::NoError); //should be supplied from cache - QCOMPARE(authSpy.count(), 0); + QCOMPARE(authSpy.size(), 0); authSpy.clear(); if (proxyAuth) { //should be supplied from cache - QCOMPARE(proxyAuthSpy.count(), 0); + QCOMPARE(proxyAuthSpy.size(), 0); proxyAuthSpy.clear(); } @@ -7311,7 +7824,7 @@ void tst_QNetworkReply::authenticationWithDifferentRealm() { AuthenticationCacheHelper helper; QNetworkAccessManager manager; -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); #endif @@ -7338,7 +7851,7 @@ void tst_QNetworkReply::authenticationWithDifferentRealm() QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(reply->error(), QNetworkReply::NoError); } -#endif // !QT_NO_NETWORKPROXY +#endif // QT_CONFIG(networkproxy) class QtBug13431Helper : public QObject { @@ -7403,8 +7916,8 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // credentials in URL, so don't expect authentication signal - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); finishedSpy.clear(); } @@ -7414,8 +7927,8 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // credentials in cache, so don't expect authentication signal - QCOMPARE(authSpy.count(), 0); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(authSpy.size(), 0); + QCOMPARE(finishedSpy.size(), 1); finishedSpy.clear(); } @@ -7432,9 +7945,9 @@ void tst_QNetworkReply::httpWithNoCredentialUsage() QVERIFY(!QTestEventLoop::instance().timeout()); // We check if authenticationRequired was emitted, however we do not anything in it so it should be 401 - QCOMPARE(authSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(authSpy.size(), 1); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(errorSpy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); } @@ -7573,11 +8086,12 @@ void tst_QNetworkReply::qtbug27161httpHeaderMayBeDamaged(){ QCOMPARE(reply->readAll(), QByteArray("ABC")); } +#if QT_CONFIG(networkdiskcache) void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray getReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n" "GET"; @@ -7585,7 +8099,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray postReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "Content-length: 4\r\n" "\r\n" @@ -7594,7 +8108,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray putReply = "HTTP/1.1 201\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n"; @@ -7632,7 +8146,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { server.clearHeaderParserState(); server.setDataToTransmit(postReply); - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); reply.reset(manager.post(request, postData)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -7697,6 +8211,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QCOMPARE(reply->readAll(), QByteArray("GET")); QCOMPARE(reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), true); } +#endif void tst_QNetworkReply::qtbug45581WrongReplyStatusCode() { @@ -7719,8 +8234,8 @@ void tst_QNetworkReply::qtbug45581WrongReplyStatusCode() QCOMPARE(reply->readAll(), expectedContent); - QCOMPARE(finishedSpy.count(), 0); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(finishedSpy.size(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), expectedContent.size()); @@ -7752,17 +8267,17 @@ void tst_QNetworkReply::synchronousRequest_data() // ### we would need to enflate (un-deflate) the file content and compare the sizes << QString("text/plain"); -#ifndef QT_NO_SSL -#if QT_CONFIG(securetransport) - qWarning("Skipping https scheme, SecTrustEvalue() fails, update the certificate on server"); -#else - QTest::newRow("https") - << QUrl("https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt") - << QString("file:" + testDataDir + "/rfc3252.txt") - << true - << QString("text/plain"); -#endif -#endif +#if QT_CONFIG(ssl) + if (isSecureTransport) { + qWarning("Skipping https scheme, SecTrustEvalue() fails, update the certificate on server"); + } else { + QTest::newRow("https") + << QUrl("https://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt") + << QString("file:" + testDataDir + "/rfc3252.txt") + << true + << QString("text/plain"); + } +#endif // QT_CONFIG(ssl) QTest::newRow("data") << QUrl(QString::fromLatin1("data:text/plain,hello world")) @@ -7787,7 +8302,7 @@ void tst_QNetworkReply::synchronousRequest() QNetworkRequest request(url); -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) // workaround for HTTPS requests: add self-signed server cert to list of CA certs, // since we cannot react to the sslErrors() signal // to fix this properly we would need to have an ignoreSslErrors() method in the @@ -7809,8 +8324,8 @@ void tst_QNetworkReply::synchronousRequest() QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0)); QVERIFY(reply->isFinished()); - QCOMPARE(finishedSpy.count(), 0); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(finishedSpy.size(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), mimeType); @@ -7819,7 +8334,7 @@ void tst_QNetworkReply::synchronousRequest() if (expected.startsWith("file:")) { QString path = expected.mid(5); QFile file(path); - file.open(QIODevice::ReadOnly); + QVERIFY(file.open(QIODevice::ReadOnly)); expectedContent = file.readAll(); } else if (expected.startsWith("data:")) { expectedContent = expected.mid(5).toUtf8(); @@ -7832,7 +8347,7 @@ void tst_QNetworkReply::synchronousRequest() reply->deleteLater(); } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) void tst_QNetworkReply::synchronousRequestSslFailure() { // test that SSL won't be accepted with self-signed certificate, @@ -7849,7 +8364,7 @@ void tst_QNetworkReply::synchronousRequestSslFailure() runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0); QVERIFY(reply->isFinished()); QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); - QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.size(), 0); } #endif @@ -7911,12 +8426,226 @@ void tst_QNetworkReply::httpAbort() QCOMPARE(reply3->error(), QNetworkReply::NoError); } +void tst_QNetworkReply::closeClientSideConnectionEagerlyQtbug20726() +{ + QNetworkAccessManager manager; // function local instance + // Setup HTTP servers + MiniHttpServer server("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + server.doClose = false; // server should not disconnect. + + MiniHttpServer serverNotEagerClientClose("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + serverNotEagerClientClose.doClose = false; // server should not disconnect. + QUrl urlNotEager(QLatin1String("http://localhost")); + urlNotEager.setPort(serverNotEagerClientClose.serverPort()); + QNetworkRequest requestNotEager(urlNotEager); + QNetworkReplyPtr replyNotEager(manager.get(requestNotEager)); + QCOMPARE(waitForFinish(replyNotEager), Success); + // The reply was finished, the connection should be hanging and waiting to be expired + + // Another server not eager to close, the connection should be hanging and waiting to be expired + MiniHttpServer serverNotEagerClientClose2("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n", false); + serverNotEagerClientClose2.doClose = false; // server should not disconnect. + QUrl urlNotEager2(QLatin1String("http://localhost")); + urlNotEager2.setPort(serverNotEagerClientClose2.serverPort()); + QNetworkRequest requestNotEager2(urlNotEager2); + QNetworkReplyPtr replyNotEager2(manager.get(requestNotEager2)); + QCOMPARE(waitForFinish(replyNotEager2), Success); + + // However for this one we want to eagerly close. + QUrl url(QLatin1String("http://localhost")); + url.setPort(server.serverPort()); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0); + QNetworkReplyPtr reply(manager.get(request)); + // The server just uses a normal TCP socket and prints out this error when the client disconnects: + QTest::ignoreMessage(QtDebugMsg, + "slotError QAbstractSocket::RemoteHostClosedError " + "\"The remote host closed the connection\""); + QCOMPARE(waitForFinish(reply), Success); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + // Socket from server to QNAM still connected? + QVERIFY (!server.client.isNull()); + QVERIFY (server.client->state() == QTcpSocket::ConnectedState); + // Wait a bit + QTest::qWait(1*1000); + // The QNAM should have disconnected the socket, so on our server it's disconnected now. + QVERIFY (!server.client.isNull()); + QVERIFY (server.client->state() != QTcpSocket::ConnectedState); + + // Now we check the not eager reply, it should still be connected. + QCOMPARE(replyNotEager->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(serverNotEagerClientClose.client->state(), QTcpSocket::ConnectedState); + QCOMPARE(replyNotEager2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(serverNotEagerClientClose2.client->state(), QTcpSocket::ConnectedState); +} + +void tst_QNetworkReply::varyingCacheExpiry_data() +{ + QTest::addColumn<int>("firstExpiry"); + QTest::addColumn<int>("secondExpiry"); + QTest::addColumn<int>("thirdExpiry"); + QTest::addColumn<int>("fourthExpiry"); + + // The datatags signify the Keep-Alive time-outs of the successive requests: + QTest::newRow("1-2-3-4") << 1 << 2 << 3 << 4; + QTest::newRow("4-1-2-3") << 4 << 1 << 2 << 3; + QTest::newRow("3-4-1-2") << 3 << 4 << 1 << 2; + QTest::newRow("2-3-4-1") << 2 << 3 << 4 << 1; + QTest::newRow("1-2-2-1") << 1 << 2 << 2 << 1; +} + +// Test creating a few requests with various expiry timeouts. +// We do this because the internal QNetworkAccessCache inserts them in sorted +// order, so make sure it gets it right. +void tst_QNetworkReply::varyingCacheExpiry() +{ + // Local QNAM instance because there may be leftover entries from other + // tests. Which wouldn't be a big deal, it would just get in the way of our + // pattern + QNetworkAccessManager qnam; + QFETCH(int, firstExpiry); + QFETCH(int, secondExpiry); + QFETCH(int, thirdExpiry); + QFETCH(int, fourthExpiry); + + int expiryTimes[4] = { + firstExpiry, + secondExpiry, + thirdExpiry, + fourthExpiry, + }; + + // We need multiple servers because we want to have multiple connections + // in the QNetworkAccessCache, not to just reuse one connection from the + // cache. + MiniHttpServer servers[4] = { + { httpEmpty200Response }, + { httpEmpty200Response }, + { httpEmpty200Response }, + { httpEmpty200Response }, + }; + for (MiniHttpServer &server : servers) + server.doClose = false; + + QUrl urls[4] = { + u"http://localhost"_s, + u"http://localhost"_s, + u"http://localhost"_s, + u"http://localhost"_s, + }; + for (size_t i = 0; i < std::size(urls); ++i) + urls[i].setPort(servers[i].serverPort()); + + // After the initial request is completed the connection is kept alive + // (Keep-Alive). Internally they are added to a sorted linked-list based on + // expiry. So, set the requests to be torn down at varying timeouts. + + QNetworkRequest requests[4] = { + QNetworkRequest(urls[0]), + QNetworkRequest(urls[1]), + QNetworkRequest(urls[2]), + QNetworkRequest(urls[3]), + }; + + for (int i = 0; i < 4; ++i) { + requests[i].setAttribute(QNetworkRequest::Http2AllowedAttribute, + QVariant::fromValue(false)); + requests[i].setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, + expiryTimes[i]); + } + + // The server just uses a normal TCP socket and prints out this error when the client + // disconnects: + for (int i = 0; i < 4; ++i) { + QTest::ignoreMessage(QtDebugMsg, + "slotError QAbstractSocket::RemoteHostClosedError " + "\"The remote host closed the connection\""); + } + + // Start each request and wait for it to finish before starting the next + // one, we must do this because the connections are only added to the expiry + // list once finished + for (const auto &request : requests) { + QNetworkReplyPtr reply(qnam.get(request)); + QCOMPARE(waitForFinish(reply), Success); + } + + int lastExpiry = *std::max_element(std::begin(expiryTimes), std::end(expiryTimes)); + auto allServersDisconnected = [&servers]() { + auto socketDisconnected = [](const MiniHttpServer &s) { + return s.client->state() == QAbstractSocket::UnconnectedState; + }; + return std::all_of(std::begin(servers), std::end(servers), socketDisconnected); + }; + // At least +5 seconds due to CI. Completes much faster locally + bool success = QTest::qWaitFor(allServersDisconnected, lastExpiry * 1000 + 5000); + + QVERIFY(success); +} + +class Qtbug25280Server : public MiniHttpServer +{ +public: + Qtbug25280Server(QByteArray qba) : MiniHttpServer(qba, false) {} + QSet<QTcpSocket*> receivedSockets; + void reply() override + { + // Save sockets in a list + receivedSockets.insert((QTcpSocket*)sender()); + qobject_cast<QTcpSocket*>(sender())->write(dataToTransmit); + //qDebug() << "count=" << receivedSockets.count(); + } +}; + +#if QT_CONFIG(http) +void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280_data() +{ + QTest::addColumn<int>("amount"); + QTest::addRow("default") << 6; + QTest::addRow("minimize") << 1; + QTest::addRow("increase") << 12; +} + +// Also kind of QTBUG-8468 +void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280() +{ + QFETCH(const int, amount); + QNetworkAccessManager manager; // function local instance + Qtbug25280Server server(tst_QNetworkReply::httpEmpty200Response); + server.doClose = false; + server.multiple = true; + QUrl url(QLatin1String("http://127.0.0.1")); // not "localhost" to prevent "Happy Eyeballs" + // from skewing the counting + url.setPort(server.serverPort()); + std::optional<QHttp1Configuration> http1Configuration; + if (amount != 6) // don't set if it's the default + http1Configuration.emplace().setNumberOfConnectionsPerHost(amount); + constexpr int NumRequests = 200; // send a lot more than we have sockets + int finished = 0; + std::array<std::unique_ptr<QNetworkReply>, NumRequests> replies; + for (auto &reply : replies) { + QNetworkRequest request(url); + if (http1Configuration) + request.setHttp1Configuration(*http1Configuration); + reply.reset(manager.get(request)); + QObject::connect(reply.get(), &QNetworkReply::finished, + [&finished] { ++finished; }); + } + QTRY_COMPARE_WITH_TIMEOUT(finished, NumRequests, 60'000); + for (const auto &reply : replies) { + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + } + QCOMPARE(server.receivedSockets.size(), amount); +} +#endif + void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() { QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -7939,7 +8668,7 @@ void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() QVERIFY(server.totalConnections > 0); QCOMPARE(reply->readAll().constData(), "load"); - QCOMPARE(memoryCache->m_insertedUrls.count(), 0); + QCOMPARE(memoryCache->m_insertedUrls.size(), 0); } void tst_QNetworkReply::httpUserAgent() @@ -7956,7 +8685,7 @@ void tst_QNetworkReply::httpUserAgent() QVERIFY(reply->isFinished()); QCOMPARE(reply->error(), QNetworkReply::NoError); - QVERIFY(server.receivedData.contains("\r\nUser-Agent: abcDEFghi\r\n")); + QVERIFY(server.receivedData.contains("\r\nuser-agent: abcDEFghi\r\n")); } void tst_QNetworkReply::synchronousAuthenticationCache() @@ -7973,16 +8702,16 @@ void tst_QNetworkReply::synchronousAuthenticationCache() "WWW-Authenticate: Basic realm=\"QNetworkAccessManager Test Realm\"\r\n" "Content-Length: 4\r\n" "Connection: close\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "\r\n" "auth"; - QRegularExpression rx("Authorization: Basic ([^\r\n]*)\r\n"); + QRegularExpression rx("authorization: Basic ([^\r\n]*)\r\n"); QRegularExpressionMatch match = rx.match(receivedData); if (match.hasMatch()) { if (QByteArray::fromBase64(match.captured(1).toLatin1()) == "login:password") { dataToTransmit = "HTTP/1.0 200 OK\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Content-Length: 2\r\n" "\r\n" "OK"; @@ -7997,7 +8726,7 @@ void tst_QNetworkReply::synchronousAuthenticationCache() // the server thread, because the client is never returning to the // event loop QScopedPointer<QThread, QThreadCleanup> serverThread(new QThread); - QScopedPointer<MiniHttpServer, QDeleteLaterCleanup> server(new MiniAuthServer(serverThread.data())); + QScopedPointer<MiniHttpServer, QScopedPointerDeleteLater> server(new MiniAuthServer(serverThread.data())); server->doClose = true; //1) URL without credentials, we are not authenticated @@ -8136,31 +8865,33 @@ void tst_QNetworkReply::ftpAuthentication() void tst_QNetworkReply::emitErrorForAllReplies() // QTBUG-36890 { // port 100 is not well-known and should be closed - QList<QUrl> urls = QList<QUrl>() << QUrl("http://localhost:100/request1") - << QUrl("http://localhost:100/request2") - << QUrl("http://localhost:100/request3"); - QList<QNetworkReply *> replies; - QList<QSignalSpy *> errorSpies; - QList<QSignalSpy *> finishedSpies; - for (int a = 0; a < urls.count(); ++a) { - QNetworkRequest request(urls.at(a)); + const QUrl urls[] = { + QUrl("http://localhost:100/request1"), + QUrl("http://localhost:100/request2"), + QUrl("http://localhost:100/request3"), + }; + constexpr auto NUrls = std::size(urls); + + std::unique_ptr<QNetworkReply, QScopedPointerDeleteLater> replies[NUrls]; + std::optional<QSignalSpy> errorSpies[NUrls]; + std::optional<QSignalSpy> finishedSpies[NUrls]; + + for (size_t i = 0; i < NUrls; ++i) { + QNetworkRequest request(urls[i]); QNetworkReply *reply = manager.get(request); - replies.append(reply); - QSignalSpy *errorSpy = new QSignalSpy(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - errorSpies.append(errorSpy); - QSignalSpy *finishedSpy = new QSignalSpy(reply, SIGNAL(finished())); - finishedSpies.append(finishedSpy); + replies[i].reset(reply); + errorSpies[i].emplace(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + finishedSpies[i].emplace(reply, SIGNAL(finished())); QObject::connect(reply, SIGNAL(finished()), SLOT(emitErrorForAllRepliesSlot())); } + QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); - for (int a = 0; a < urls.count(); ++a) { - QVERIFY(replies.at(a)->isFinished()); - QCOMPARE(errorSpies.at(a)->count(), 1); - errorSpies.at(a)->deleteLater(); - QCOMPARE(finishedSpies.at(a)->count(), 1); - finishedSpies.at(a)->deleteLater(); - replies.at(a)->deleteLater(); + + for (size_t i = 0; i < NUrls; ++i) { + QVERIFY(replies[i]->isFinished()); + QCOMPARE(errorSpies[i]->size(), 1); + QCOMPARE(finishedSpies[i]->size(), 1); } } @@ -8208,7 +8939,7 @@ public: return ret; } virtual bool atEnd() const override { return buffer.atEnd(); } - virtual qint64 size() const override { return data.length(); } + virtual qint64 size() const override { return data.size(); } qint64 bytesAvailable() const override { return buffer.bytesAvailable() + QIODevice::bytesAvailable(); @@ -8229,9 +8960,9 @@ protected slots: void tst_QNetworkReply::putWithRateLimiting() { QFile reference(testDataDir + "/rfc3252.txt"); - reference.open(QIODevice::ReadOnly); + QVERIFY(reference.open(QIODevice::ReadOnly)); QByteArray data = reference.readAll(); - QVERIFY(data.length() > 0); + QVERIFY(data.size() > 0); QUrl url = QUrl::fromUserInput("http://" + QtNetworkSettings::httpServerName()+ "/qtest/cgi-bin/echo.cgi?"); @@ -8246,7 +8977,7 @@ void tst_QNetworkReply::putWithRateLimiting() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QByteArray uploadedData = reply->readAll(); - QCOMPARE(uploadedData.length(), data.length()); + QCOMPARE(uploadedData.size(), data.size()); QCOMPARE(uploadedData, data); } @@ -8278,8 +9009,8 @@ void tst_QNetworkReply::ioHttpSingleRedirect() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); // Redirected and finished should be emitted exactly once - QCOMPARE(redSpy.count(), 1); - QCOMPARE(finSpy.count(), 1); + QCOMPARE(redSpy.size(), 1); + QCOMPARE(finSpy.size(), 1); // Original URL should not be changed after redirect QCOMPARE(request.url(), localhost); @@ -8325,8 +9056,8 @@ void tst_QNetworkReply::ioHttpChangeMaxRedirects() QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(redSpy.count(), request.maximumRedirectsAllowed()); - QCOMPARE(spy.count(), 1); + QCOMPARE(redSpy.size(), request.maximumRedirectsAllowed()); + QCOMPARE(spy.size(), 1); QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError); // Increase max redirects to allow successful completion @@ -8337,7 +9068,7 @@ void tst_QNetworkReply::ioHttpChangeMaxRedirects() QVERIFY2(waitForFinish(reply2) == Success, msgWaitForFinished(reply2)); - QCOMPARE(redSpy2.count(), 2); + QCOMPARE(redSpy2.size(), 2); QCOMPARE(reply2->url(), server3Url); QCOMPARE(reply2->error(), QNetworkReply::NoError); QVERIFY(validateRedirectedResponseHeaders(reply2)); @@ -8350,7 +9081,7 @@ void tst_QNetworkReply::ioHttpRedirectErrors_data() QTest::addColumn<QNetworkReply::NetworkError>("error"); QString tempRedirectReply = QString("HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: http://localhost:%1\r\n\r\n"); QTest::newRow("too-many-redirects") << "http://localhost" << tempRedirectReply << QNetworkReply::TooManyRedirectsError; @@ -8470,8 +9201,8 @@ void tst_QNetworkReply::ioHttpRedirectPolicy() QSignalSpy redirectSpy(reply.data(), SIGNAL(redirected(QUrl))); QSignalSpy finishedSpy(reply.data(), SIGNAL(finished())); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(redirectSpy.count(), redirectCount); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(redirectSpy.size(), redirectCount); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); QVERIFY(validateRedirectedResponseHeaders(reply) || statusCode != 200); } @@ -8554,7 +9285,7 @@ void tst_QNetworkReply::ioHttpRedirectPolicyErrors() QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); QCOMPARE(waitForFinish(reply), int(Failure)); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(reply->error(), expectedError); } @@ -8604,7 +9335,7 @@ void tst_QNetworkReply::ioHttpUserVerifiedRedirect() QSignalSpy finishedSpy(reply.data(), SIGNAL(finished())); waitForFinish(reply); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(finishedSpy.size(), 1); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); QVERIFY(validateRedirectedResponseHeaders(reply) || statusCode != 200); } @@ -8616,7 +9347,7 @@ void tst_QNetworkReply::ioHttpCookiesDuringRedirect() const QString cookieHeader = QStringLiteral("Set-Cookie: hello=world; Path=/;\r\n"); QString redirect = tempRedirectReplyStr(); // Insert 'cookieHeader' before the final \r\n - redirect.insert(redirect.length() - 2, cookieHeader); + redirect.insert(redirect.size() - 2, cookieHeader); QUrl url("http://localhost/"); url.setPort(target.serverPort()); @@ -8633,7 +9364,7 @@ void tst_QNetworkReply::ioHttpCookiesDuringRedirect() manager.setRedirectPolicy(oldRedirectPolicy); QVERIFY(waitForFinish(reply) == Success); - QVERIFY(target.receivedData.contains("\r\nCookie: hello=world\r\n")); + QVERIFY(target.receivedData.contains("\r\ncookie: hello=world\r\n")); QVERIFY(validateRedirectedResponseHeaders(reply)); } @@ -8658,7 +9389,7 @@ void tst_QNetworkReply::ioHttpRedirect() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1(), false); @@ -8676,6 +9407,66 @@ void tst_QNetworkReply::ioHttpRedirect() QVERIFY(validateRedirectedResponseHeaders(reply)); } +#if QT_CONFIG(networkdiskcache) +/* + Test that, if we load a redirect from cache, we don't treat the request to + the destination of the redirect as a redirect. + + If it was treated as a redirect the finished() signal was never emitted! +*/ +void tst_QNetworkReply::ioHttpRedirectWithCache() +{ + // Disallow caching the result so that the second request must also send the request + QByteArray http200ResponseNoCache = "HTTP/1.1 200 OK\r\n" + "content-type: text/plain\r\n" + "Cache-Control: no-cache\r\n" + "\r\nHello"; + + MiniHttpServer target(http200ResponseNoCache, false); + QUrl targetUrl("http://localhost/"); + targetUrl.setPort(target.serverPort()); + + // A cache-able redirect reply + QString redirectReply = QStringLiteral("HTTP/1.1 308\r\n" + "content-type: text/plain\r\n" + "location: %1\r\n" + "Cache-Control: max-age=3600\r\n" + "\r\nYou're being redirected").arg(targetUrl.toString()); + MiniHttpServer redirectServer(redirectReply.toLatin1(), false); + QUrl url("http://localhost/"); + url.setPort(redirectServer.serverPort()); + + QTemporaryDir tempDir(QDir::tempPath() + "/tmp_cache_28035"); + QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); + tempDir.setAutoRemove(true); + + QNetworkDiskCache *diskCache = new QNetworkDiskCache(); + diskCache->setCacheDirectory(tempDir.path()); + // Manager takes ownership of the cache: + manager.setCache(diskCache); + QCOMPARE(diskCache->cacheSize(), 0); + + // Send the first request, we end up caching the redirect reply + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + + QCOMPARE(waitForFinish(reply), int(Success)); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(validateRedirectedResponseHeaders(reply)); + + QVERIFY(diskCache->cacheSize() != 0); + + // Now for the second request, we will use the cache, and we test that the finished() + // signal is still emitted. + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + reply.reset(manager.get(request)); + + QCOMPARE(waitForFinish(reply), int(Success)); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(validateRedirectedResponseHeaders(reply)); +} +#endif + void tst_QNetworkReply::ioHttpRedirectFromLocalToRemote() { QUrl targetUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/rfc3252.txt"); @@ -8750,7 +9541,7 @@ void tst_QNetworkReply::ioHttpRedirectPostPut() QUrl targetUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -8769,6 +9560,7 @@ void tst_QNetworkReply::ioHttpRedirectPostPut() QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); } +#if QT_CONFIG(http) void tst_QNetworkReply::ioHttpRedirectMultipartPost_data() { postToHttpMultipart_data(); @@ -8821,14 +9613,15 @@ void tst_QNetworkReply::ioHttpRedirectMultipartPost() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 OK - QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string - QVERIFY(multiPart->boundary().count() < 70); + QVERIFY(multiPart->boundary().size() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().size() < 70); QByteArray replyData = reply->readAll(); expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); // QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above QCOMPARE(replyData, expectedReplyData); } +#endif void tst_QNetworkReply::ioHttpRedirectDelete() { @@ -8904,7 +9697,7 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -8938,12 +9731,12 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() // we shouldn't send Content-Length with not content (esp. for GET) QVERIFY2(!target.receivedData.contains("Content-Length"), "Target server should not have received a Content-Length header"); - QVERIFY2(!target.receivedData.contains("Content-Type"), - "Target server should not have received a Content-Type header"); + QVERIFY2(!target.receivedData.contains("content-type"), + "Target server should not have received a content-type header"); } } -#ifndef QT_NO_SSL +#if QT_CONFIG(ssl) class PutWithServerClosingConnectionImmediatelyHandler: public QObject { @@ -8985,9 +9778,9 @@ public slots: //qDebug() << m_receivedData.left(m_receivedData.indexOf("\r\n\r\n")); m_receivedData = m_receivedData.mid(m_receivedData.indexOf("\r\n\r\n")+4); // check only actual data } - if (m_receivedData.length() > 0 && !m_expectedData.startsWith(m_receivedData)) { + if (m_receivedData.size() > 0 && !m_expectedData.startsWith(m_receivedData)) { // We had received some data but it is corrupt! - qDebug() << "CORRUPT" << m_receivedData.count(); + qDebug() << "CORRUPT" << m_receivedData.size(); #if 0 // Use this to track down the pattern of the corruption and conclude the source QFile a("/tmp/corrupt"); @@ -9076,6 +9869,9 @@ void tst_QNetworkReply::putWithServerClosingConnectionImmediately() for (int i = 0; i < numUploads; i++) { // create the request QNetworkRequest request(QUrl(urlPrefix + QString::number(i))); + // Disable http2 so we get the 6 simultaneous channels as when + // the test was originally written + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); QNetworkReply *reply = manager.put(request, sourceFile); connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors())); connect(reply, SIGNAL(finished()), &server, SLOT(replyFinished())); @@ -9106,8 +9902,12 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute_data() { QTest::addColumn<QUrl>("destination"); - QTest::newRow("http") << QUrl("http://QInvalidDomain.qt/test"); - QTest::newRow("https") << QUrl("https://QInvalidDomain.qt/test"); + QUrl webServerUrl = QtNetworkSettings::httpServerIp().toString(); + webServerUrl.setPath("/notfound"); + webServerUrl.setScheme("http"); + QTest::newRow("http") << webServerUrl; + webServerUrl.setScheme("https"); + QTest::newRow("https") << webServerUrl; if (ftpSupported) QTest::newRow("ftp") << QUrl("ftp://QInvalidDomain.qt/test"); QTest::newRow("file") << QUrl("file:///thisfolderdoesn'texist/probably.txt"); @@ -9130,7 +9930,7 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } { @@ -9141,7 +9941,7 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } // Now repeated, but without the attribute to make sure it does not get deleted automatically. @@ -9155,10 +9955,10 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9167,10 +9967,10 @@ void tst_QNetworkReply::autoDeleteRepliesAttribute() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } } @@ -9191,7 +9991,7 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } { @@ -9201,7 +10001,7 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QSignalSpy destroyedSpy(reply, &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QVERIFY(destroyedSpy.wait()); } // Here we repeat the test, but override the auto-deletion in the QNetworkRequest @@ -9216,10 +10016,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9229,10 +10029,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } // Now we repeat the test with autoDeleteReplies set to false cleanup.dismiss(); @@ -9244,10 +10044,10 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } { // Post @@ -9256,89 +10056,140 @@ void tst_QNetworkReply::autoDeleteReplies() QSignalSpy finishedSpy(reply.data(), &QNetworkReply::finished); QSignalSpy destroyedSpy(reply.data(), &QObject::destroyed); QVERIFY(finishedSpy.wait()); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); QCoreApplication::processEvents(); QCoreApplication::processEvents(); - QCOMPARE(destroyedSpy.count(), 0); + QCOMPARE(destroyedSpy.size(), 0); } } -void tst_QNetworkReply::getWithTimeout() +#if QT_CONFIG(http) || defined (Q_OS_WASM) +void tst_QNetworkReply::requestWithTimeout_data() { - MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); + using Operation = QNetworkAccessManager::Operation; + QTest::addColumn<Operation>("method"); + QTest::addColumn<int>("reqInt"); + QTest::addColumn<std::chrono::milliseconds>("reqChrono"); + QTest::addColumn<int>("mgrInt"); + QTest::addColumn<std::chrono::milliseconds>("mgrChrono"); - QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); - QNetworkReplyPtr reply(manager.get(request)); - QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); + QTest::addRow("get_req_int") << Operation::GetOperation << 500 << 0ms << 0 << 0ms; + QTest::addRow("get_req_chrono") << Operation::GetOperation << 0 << 500ms << 0 << 0ms; + QTest::addRow("get_mgr_int") << Operation::GetOperation << 0 << 0ms << 500 << 0ms; + QTest::addRow("get_mgr_chrono") << Operation::GetOperation << 0 << 0ms << 0 << 500ms; - QCOMPARE(waitForFinish(reply), int(Success)); + QTest::addRow("post_req_int") << Operation::PostOperation << 500 << 0ms << 0 << 0ms; + QTest::addRow("post_req_chrono") << Operation::PostOperation << 0 << 500ms << 0 << 0ms; + QTest::addRow("post_mgr_int") << Operation::PostOperation << 0 << 0ms << 500 << 0ms; + QTest::addRow("post_mgr_chrono") << Operation::PostOperation << 0 << 0ms << 0 << 500ms; +} - QCOMPARE(spy.count(), 0); - QVERIFY(reply->error() == QNetworkReply::NoError); +void tst_QNetworkReply::requestWithTimeout() +{ + QFETCH(QNetworkAccessManager::Operation, method); + QFETCH(int, reqInt); + QFETCH(int, mgrInt); + QFETCH(std::chrono::milliseconds, reqChrono); + QFETCH(std::chrono::milliseconds, mgrChrono); + const auto data = "some data"_ba; + // Manager instance remains between case runs => always reset it's transferTimeout to + // ensure setting its transferTimeout in this case has effect + manager.setTransferTimeout(0ms); + auto cleanup = qScopeGuard([this] { manager.setTransferTimeout(0ms); }); - request.setTransferTimeout(1000); + MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); server.stopTransfer = true; - QNetworkReplyPtr reply2(manager.get(request)); - QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.count(), 1); - QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError); - - request.setTransferTimeout(0); - manager.setTransferTimeout(1000); - - QNetworkReplyPtr reply3(manager.get(request)); - QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply3), int(Failure)); + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + request.setRawHeader("content-type", "application/octet-stream"); + if (reqInt > 0) + request.setTransferTimeout(reqInt); + if (reqChrono > 0ms) + request.setTransferTimeout(reqChrono); + if (mgrInt > 0) + manager.setTransferTimeout(mgrInt); + if (mgrChrono > 0ms) + manager.setTransferTimeout(mgrChrono); - QCOMPARE(spy3.count(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); + QNetworkReplyPtr reply; + if (method == QNetworkAccessManager::GetOperation) + reply.reset(manager.get(request)); + else if (method == QNetworkAccessManager::PostOperation) + reply.reset(manager.post(request, data)); + QVERIFY(reply); - manager.setTransferTimeout(0); + QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred); + QCOMPARE(waitForFinish(reply), int(Failure)); + QCOMPARE(spy.size(), 1); + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); } +#endif -void tst_QNetworkReply::postWithTimeout() +void tst_QNetworkReply::moreActivitySignals_data() { - MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false); - - QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); - request.setRawHeader("Content-Type", "application/octet-stream"); - QByteArray postData("Just some nonsense"); - QNetworkReplyPtr reply(manager.post(request, postData)); - QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply), int(Success)); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<bool>("useipv6"); + QTest::addColumn<bool>("postWithData"); + QTest::addRow("local4") << QUrl("http://127.0.0.1") << false << false; + QTest::addRow("local6") << QUrl("http://[::1]") << true << false; + if (qEnvironmentVariable("QTEST_ENVIRONMENT").split(' ').contains("ci")) { + // On CI server + QTest::addRow("localDns") << QUrl("http://localhost") << false << false; // will find v6 + } else { + // For manual testing + QTest::addRow("localDns4") << QUrl("http://localhost") << true << false; // will find both v4 and v6 + QTest::addRow("localDns6") << QUrl("http://localhost") << false << false; // will find both v4 and v6 + } + QTest::addRow("post-with-data") << QUrl("http://[::1]") << true << true; +} - QCOMPARE(spy.count(), 0); +void tst_QNetworkReply::moreActivitySignals() +{ + QFETCH(QUrl, url); + QFETCH(bool, useipv6); + QFETCH(bool, postWithData); + MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false, nullptr/*thread*/, useipv6); + server.doClose = false; + url.setPort(server.serverPort()); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + if (postWithData) { + request.setRawHeader("content-type", "text/plain"); + reply.reset(manager.post(request, "Hello, world!")); + } else { + reply.reset(manager.get(request)); + } + QSignalSpy spy1(reply.data(), SIGNAL(socketStartedConnecting())); + QSignalSpy spy2(reply.data(), SIGNAL(requestSent())); + QSignalSpy spy3(reply.data(), SIGNAL(metaDataChanged())); + QSignalSpy spy4(reply.data(), SIGNAL(finished())); + spy1.wait(); + QCOMPARE(spy1.size(), 1); + spy2.wait(); + QCOMPARE(spy2.size(), 1); + spy3.wait(); + QCOMPARE(spy3.size(), 1); + spy4.wait(); + QCOMPARE(spy4.size(), 1); QVERIFY(reply->error() == QNetworkReply::NoError); - - request.setTransferTimeout(1000); - server.stopTransfer = true; - - QNetworkReplyPtr reply2(manager.post(request, postData)); - QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply2), int(Failure)); - - QCOMPARE(spy2.count(), 1); - QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError); - - request.setTransferTimeout(0); - manager.setTransferTimeout(1000); - - QNetworkReplyPtr reply3(manager.post(request, postData)); - QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError))); - - QCOMPARE(waitForFinish(reply3), int(Failure)); - - QCOMPARE(spy3.count(), 1); - QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError); - - manager.setTransferTimeout(0); + // Second request will not send socketStartedConnecting because of keep-alive, so don't check it. + QNetworkReplyPtr secondreply; + if (postWithData) { + request.setRawHeader("content-type", "text/plain"); + secondreply.reset(manager.post(request, "Hello, world!")); + } else { + secondreply.reset(manager.get(request)); + } + QSignalSpy secondspy2(secondreply.data(), SIGNAL(requestSent())); + QSignalSpy secondspy3(secondreply.data(), SIGNAL(metaDataChanged())); + QSignalSpy secondspy4(secondreply.data(), SIGNAL(finished())); + secondspy2.wait(); + QCOMPARE(secondspy2.size(), 1); + secondspy3.wait(); + QCOMPARE(secondspy3.size(), 1); + secondspy4.wait(); + QCOMPARE(secondspy4.size(), 1); + QVERIFY(secondreply->error() == QNetworkReply::NoError); } void tst_QNetworkReply::contentEncoding_data() @@ -9346,25 +10197,53 @@ void tst_QNetworkReply::contentEncoding_data() QTest::addColumn<QByteArray>("encoding"); QTest::addColumn<QByteArray>("body"); QTest::addColumn<QByteArray>("expected"); + QTest::addColumn<bool>("decompress"); + const QByteArray helloWorld = "hello world"; + + const QByteArray gzipBody = QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA=="); QTest::newRow("gzip-hello-world") << QByteArray("gzip") - << QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA==") - << QByteArray("hello world"); + << gzipBody + << helloWorld + << true; + QTest::newRow("gzip-hello-world-no-decompress") + << QByteArray("gzip") + << gzipBody + << helloWorld + << false; + const QByteArray deflateBody = QByteArray::fromBase64("eJzLSM3JyVcozy/KSQEAGgsEXQ=="); QTest::newRow("deflate-hello-world") - << QByteArray("deflate") << QByteArray::fromBase64("eJzLSM3JyVcozy/KSQEAGgsEXQ==") - << QByteArray("hello world"); + << QByteArray("deflate") << deflateBody + << helloWorld + << true; + QTest::newRow("deflate-hello-world-no-decompress") + << QByteArray("deflate") << deflateBody + << helloWorld + << false; #if QT_CONFIG(brotli) + const QByteArray brotliBody = QByteArray::fromBase64("DwWAaGVsbG8gd29ybGQD"); QTest::newRow("brotli-hello-world") - << QByteArray("br") << QByteArray::fromBase64("DwWAaGVsbG8gd29ybGQD") - << QByteArray("hello world"); + << QByteArray("br") << brotliBody + << helloWorld + << true; + QTest::newRow("brotli-hello-world-no-decompress") + << QByteArray("br") << brotliBody + << helloWorld + << false; #endif #if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + const QByteArray zstdBody = QByteArray::fromBase64("KLUv/QRYWQAAaGVsbG8gd29ybGRoaR6y"); QTest::newRow("zstandard-hello-world") - << QByteArray("zstd") << QByteArray::fromBase64("KLUv/QRYWQAAaGVsbG8gd29ybGRoaR6y") - << QByteArray("hello world"); + << QByteArray("zstd") << zstdBody + << helloWorld + << true; + QTest::newRow("zstandard-hello-world-no-decompress") + << QByteArray("zstd") << zstdBody + << helloWorld + << false; #else qDebug("Note: ZStandard testdata is only available for developer builds."); #endif @@ -9374,12 +10253,19 @@ void tst_QNetworkReply::contentEncoding() { QFETCH(QByteArray, encoding); QFETCH(QByteArray, body); + QFETCH(bool, decompress); QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); header = header.arg(encoding, QString::number(body.size())); MiniHttpServer server(header.toLatin1() + body); QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + if (!decompress) { + // This disables decompression of the received content: + request.setRawHeader("accept-encoding", QLatin1String("%1").arg(encoding).toLatin1()); + // This disables the zerocopy optimization + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 0); + } QNetworkReplyPtr reply(manager.get(request)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -9388,7 +10274,7 @@ void tst_QNetworkReply::contentEncoding() { // Check that we included the content encoding method in our Accept-Encoding header const QByteArray &receivedData = server.receivedData; - int start = receivedData.indexOf("Accept-Encoding"); + int start = receivedData.indexOf("accept-encoding"); QVERIFY(start != -1); int end = receivedData.indexOf("\r\n", start); QVERIFY(end != -1); @@ -9400,12 +10286,395 @@ void tst_QNetworkReply::contentEncoding() QVERIFY2(list.contains(encoding), acceptedEncoding.data()); } + if (decompress) { + QFETCH(QByteArray, expected); + QCOMPARE(reply->bytesAvailable(), expected.size()); + QCOMPARE(reply->readAll(), expected); + } else { + QCOMPARE(reply->bytesAvailable(), body.size()); + QCOMPARE(reply->readAll(), body); + } +} + +#if QT_CONFIG(http) +void tst_QNetworkReply::contentEncodingBigPayload_data() +{ + QTest::addColumn<QByteArray>("encoding"); + QTest::addColumn<QString>("path"); + QTest::addColumn<qint64>("expectedSize"); + + qint64 fourGiB = 4ll * 1024ll * 1024ll * 1024ll; + + QTest::addRow("gzip-4GB") << QByteArray("gzip") << (":/4G.gz") << fourGiB; + +#if QT_CONFIG(brotli) + QTest::addRow("brotli-4GB") << QByteArray("br") << (testDataDir + "/4G.br") << fourGiB; +#endif +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(zstd) + QTest::addRow("zstd-4GB") << QByteArray("zstd") << (":/4G.zst") << fourGiB; +#else + qDebug("Note: ZStandard testdata is only available for developer builds."); +#endif +} + +void tst_QNetworkReply::contentEncodingBigPayload() +{ + QFETCH(QString, path); + QFile compressedFile(path); + QVERIFY(compressedFile.open(QIODevice::ReadOnly)); + QByteArray body = compressedFile.readAll(); + + QFETCH(QByteArray, encoding); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + // QDecompressHelper will abort the download if the compressed to decompressed size ratio + // differs too much, so we override it + request.setDecompressedSafetyCheckThreshold(-1); + QNetworkReplyPtr reply(manager.get(request)); + + QTRY_VERIFY2_WITH_TIMEOUT(reply->isFinished(), qPrintable(reply->errorString()), 15000); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QFETCH(qint64, expectedSize); + + QCOMPARE(reply->bytesAvailable(), expectedSize); + QByteArray output(512 * 1024 * 1024, Qt::Uninitialized); + qint64 total = 0; + while (reply->bytesAvailable()) { + qint64 read = reply->read(output.data(), output.size()); + QVERIFY(read != -1); + total += read; + + static const auto isZero = [](char c) { return c == '\0'; }; + bool allZero = std::all_of(output.cbegin(), output.cbegin() + read, isZero); + QVERIFY(allZero); + } + QCOMPARE(total, expectedSize); +} +#endif + +void tst_QNetworkReply::cacheWithContentEncoding_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::cacheWithContentEncoding() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); + QFETCH(QByteArray, expected); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + MySpyMemoryCache *cache = new MySpyMemoryCache(this); + manager.setCache(cache); + auto unsetCache = qScopeGuard([this](){ manager.setCache(nullptr); }); + + QUrl url("http://localhost:" + QString::number(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QByteArray output = reply->readAll(); + QIODevice *device = cache->data(url); + QVERIFY(device); + QByteArray fromCache = device->readAll(); + QCOMPARE(output, expected); + QCOMPARE(fromCache, expected); +} + +void tst_QNetworkReply::downloadProgressWithContentEncoding_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::downloadProgressWithContentEncoding() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); QFETCH(QByteArray, expected); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QUrl url("http://localhost:" + QString::number(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply(manager.get(request)); + // Limit the amount of bytes we read so we get more than one downloadProgress emission + reply->setReadBufferSize(5); + + qint64 bytesReceived = -1; + connect(reply.data(), &QNetworkReply::downloadProgress, this, + [reply = reply.data(), &expected, &bytesReceived](qint64 recv, qint64 /*total*/) { + qint64 previous = bytesReceived; + bytesReceived = recv; + if (bytesReceived > expected.size()) { + qWarning("bytesReceived greater than expected size!"); + reply->abort(); + } + if (bytesReceived < previous) { + qWarning("bytesReceived shrank!"); + reply->abort(); + } + }); - QCOMPARE(reply->bytesAvailable(), expected.size()); + SlowReader reader(reply.data()); + + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(bytesReceived, expected.size()); +} + +void tst_QNetworkReply::contentEncodingError_data() +{ + QTest::addColumn<QByteArray>("encoding"); + QTest::addColumn<QString>("path"); + QTest::addColumn<QNetworkReply::NetworkError>("expectedError"); + + QTest::addRow("archive-bomb") << QByteArray("gzip") << (":/4G.gz") + << QNetworkReply::UnknownContentError; +} + +void tst_QNetworkReply::contentEncodingError() +{ + QFETCH(QString, path); + QFile compressedFile(path); + QVERIFY(compressedFile.open(QIODevice::ReadOnly)); + QByteArray body = compressedFile.readAll(); + + QFETCH(QByteArray, encoding); + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request( + 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); + QTEST(reply->error(), "expectedError"); +} + +// When this test is failing it will appear flaky because it relies on the +// timing of delivery from one socket to another in the OS. +// + we have to send all the data at once, so the readyRead emissions are +// compressed into a single emission, so we cannot artificially time it with +// waits and sleeps. +void tst_QNetworkReply::compressedReadyRead() +{ + // There were historically an issue where a mix of signal compression and + // data decompression made it so we accidentally didn't emit the final + // readyRead signal before emitting finished(). Test this here to make sure + // it happens: + const QByteArray gzipPayload = + QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA=="); + const QByteArray expected = "hello world"; + + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: %1\r\n\r\n"); + header = header.arg(gzipPayload.size()); + MiniHttpServer server(header.toLatin1()); // only send header automatically + server.doClose = false; // don't close and delete client socket right away + + QNetworkRequest request( + QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); + QNetworkReplyPtr reply(manager.get(request)); + + QObject::connect(reply.get(), &QNetworkReply::metaDataChanged, reply.get(), + [&server, &gzipPayload]() { + // Client received headers, now send data: + // We do this awkward write,flush,write dance to try to + // make sure the data does not all arrive at the same + // time. By design we send the final "=" byte by itself + qsizetype boundary = gzipPayload.size() - 1; + server.client->write(gzipPayload.sliced(0, boundary)); + server.client->flush(); + // Let the server take care of deleting the client once + // the rest of the data is written: + server.doClose = true; + server.client->write(gzipPayload.sliced(boundary)); + }); + + QByteArray received; + QObject::connect(reply.get(), &QNetworkReply::readyRead, reply.get(), + [reply = reply.get(), &received]() { + received += reply->readAll(); + }); + QTRY_VERIFY(reply->isFinished()); + QCOMPARE(received, expected); +} + +void tst_QNetworkReply::notFoundWithCompression_data() +{ + contentEncoding_data(); +} + +void tst_QNetworkReply::notFoundWithCompression() +{ + QFETCH(QByteArray, encoding); + QFETCH(QByteArray, body); + QString header("HTTP/1.0 404 OK\r\nContent-Encoding: %1\r\nContent-Length: %2\r\n\r\n"); + header = header.arg(encoding, QString::number(body.size())); + + MiniHttpServer server(header.toLatin1() + body); + + QNetworkRequest request( + 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); + QCOMPARE(reply->error(), QNetworkReply::ContentNotFoundError); + + QFETCH(QByteArray, expected); QCOMPARE(reply->readAll(), expected); } +#if QT_CONFIG(http) +void tst_QNetworkReply::qhttpPartDebug_data() +{ + QTest::addColumn<QByteArray>("header_data"); + QTest::addColumn<QByteArray>("raw_header_data"); + QTest::addColumn<QList<QByteArray>>("expected_header_values"); + QTest::addColumn<bool>("overwrite"); + + QTest::newRow("header-data-set") << "form-data; name=\"prompt\""_ba << ""_ba + << (QList<QByteArray>() << "form-data; name=\"prompt\""_ba) << false; + QTest::newRow("raw-header-data-set") << ""_ba << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() << "thisismykeyherebutnotreally"_ba) << false; + QTest::newRow("both-set") << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() + << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba) << false; + QTest::newRow("overwrite") << "form-data; name=\"prompt\""_ba + << "thisismykeyherebutnotreally"_ba + << (QList<QByteArray>() + << "thisismykeyherebutnotreally"_ba + << "thisismykeyherebutnotreally"_ba) << true; +} + +void tst_QNetworkReply::qhttpPartDebug() +{ + QFETCH(const QByteArray, header_data); + QFETCH(const QByteArray, raw_header_data); + QFETCH(const QList<QByteArray>, expected_header_values); + QFETCH(bool, overwrite); + + QHttpPart httpPart; + + if (!header_data.isEmpty()) + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, header_data); + + if (!raw_header_data.isEmpty()) + httpPart.setRawHeader("Authorization", raw_header_data); + + if (overwrite) + httpPart.setRawHeader("Content-Disposition", raw_header_data); + + QByteArray msg; + { + QBuffer buf(&msg); + QVERIFY(buf.open(QIODevice::WriteOnly)); + QDebug debug(&buf); + debug << httpPart; + } + + for (const auto &value : expected_header_values) + QVERIFY2(msg.contains(value), "Missing header value: " + value); +} + +void tst_QNetworkReply::qtbug68821proxyError_data() +{ + QTest::addColumn<QString>("proxyHost"); + QTest::addColumn<QString>("scheme"); + QTest::addColumn<QNetworkReply::NetworkError>("error"); + + QTest::newRow("invalidhost+http") << "this-host-will-never-exist.qt-project.org" + << "http" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+http") << "localhost" + << "http" << QNetworkReply::ProxyConnectionRefusedError; +#ifndef QT_NO_SSL + QTest::newRow("invalidhost+https") << "this-host-will-never-exist.qt-project.org" + << "https" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+https") << "localhost" + << "https" << QNetworkReply::ProxyConnectionRefusedError; +#endif +} + +void tst_QNetworkReply::qtbug68821proxyError() +{ + auto getUnusedPort = []() -> std::optional<quint16> { + QTcpServer probeServer; + if (!probeServer.listen()) + return std::nullopt; + // If we can listen on it, it was unused, and hopefully is also + // still unused after we stop listening. + return probeServer.serverPort(); + }; + + auto proxyPort = getUnusedPort(); + QVERIFY(proxyPort); + + QFETCH(QString, proxyHost); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, proxyHost, proxyPort.value()); + + manager.setProxy(proxy); + + QFETCH(QString, scheme); + QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(scheme + "://example.com"))); + QSignalSpy spy(reply, &QNetworkReply::errorOccurred); + QVERIFY(spy.isValid()); + + QVERIFY(spy.wait(15000)); + + QFETCH(QNetworkReply::NetworkError, error); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0), error); +} +#endif + +void tst_QNetworkReply::abortAndError() +{ + const QByteArray response = + R"(HTTP/1.0 500 Internal Server Error +Content-Length: 12 +Content-Type: text/plain + +Hello World!)"_ba; + + MiniHttpServer server(response); + + QNetworkAccessManager manager; + QNetworkRequest req(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()))); + std::unique_ptr<QNetworkReply> reply(manager.post(req, "my data goes here"_ba)); + QSignalSpy errorSignal(reply.get(), &QNetworkReply::errorOccurred); + QSignalSpy finishedSignal(reply.get(), &QNetworkReply::finished); + + reply->abort(); + + // We don't want to print this warning in this case because it is impossible + // for users to avoid it. + QTest::failOnWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only " + "be called once."); + // Process any signals from the http thread: + QTest::qWait(1s); + if (QTest::currentTestFailed()) + return; + + QCOMPARE(finishedSignal.count(), 1); + QCOMPARE(errorSignal.count(), 1); + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); +} + // NOTE: This test must be last testcase in tst_qnetworkreply! void tst_QNetworkReply::parentingRepliesToTheApp() { 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/CMakeLists.txt b/tests/auto/network/access/qnetworkrequest/CMakeLists.txt index 1a98449549..2c4a7dd7ca 100644 --- a/tests/auto/network/access/qnetworkrequest/CMakeLists.txt +++ b/tests/auto/network/access/qnetworkrequest/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qnetworkrequest.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qnetworkrequest Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnetworkrequest LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qnetworkrequest SOURCES tst_qnetworkrequest.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Network ) diff --git a/tests/auto/network/access/qnetworkrequest/qnetworkrequest.pro b/tests/auto/network/access/qnetworkrequest/qnetworkrequest.pro deleted file mode 100644 index c00c2c30a2..0000000000 --- a/tests/auto/network/access/qnetworkrequest/qnetworkrequest.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qnetworkrequest -SOURCES += tst_qnetworkrequest.cpp - -QT = core network testlib diff --git a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp index b2d8136ea3..f0b02ae91d 100644 --- a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp +++ b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp @@ -1,37 +1,19 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> -#include <QtCore/QUrl> +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> + +#if QT_CONFIG(http) +#include <QtNetwork/QHttp1Configuration> +#include <QtNetwork/QHttp2Configuration> +#endif #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkCookie> +#include <QtCore/QDateTime> +#include <QtCore/QTimeZone> +#include <QtCore/QUrl> + Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders) class tst_QNetworkRequest: public QObject @@ -53,6 +35,10 @@ private slots: void rawHeaderParsing_data(); void rawHeaderParsing(); void originatingObject(); + void setHeaders_data(); + void setHeaders(); + void operatorEqual_data(); + void operatorEqual(); void removeHeader(); }; @@ -188,11 +174,11 @@ void tst_QNetworkRequest::rawHeaderList_data() void tst_QNetworkRequest::rawHeaderList() { - QFETCH(QList<QByteArray>, set); + QFETCH(const QList<QByteArray>, set); QFETCH(QList<QByteArray>, expected); QNetworkRequest request; - foreach (QByteArray header, set) + for (const QByteArray &header : set) request.setRawHeader(header, "a value"); QList<QByteArray> got = request.rawHeaderList(); @@ -236,12 +222,30 @@ void tst_QNetworkRequest::setHeader_data() << QVariant(QDate(2007, 11, 01)) << true << "Last-Modified" << "Thu, 01 Nov 2007 00:00:00 GMT"; - QTest::newRow("Last-Modified-DateTime") << QNetworkRequest::LastModifiedHeader - << QVariant(QDateTime(QDate(2007, 11, 01), - QTime(18, 8, 30), - Qt::UTC)) - << true << "Last-Modified" - << "Thu, 01 Nov 2007 18:08:30 GMT"; + QTest::newRow("Last-Modified-DateTime-UTC") + << QNetworkRequest::LastModifiedHeader + << QVariant(QDateTime(QDate(2007, 11, 1), QTime(18, 8, 30), QTimeZone::UTC)) + << true << "Last-Modified" << "Thu, 01 Nov 2007 18:08:30 GMT"; + // QTBUG-80666: format dates correctly (as GMT) even if the date passed in isn't in UTC: + QTest::newRow("Last-Modified-DateTime-Local") + << QNetworkRequest::LastModifiedHeader + << QVariant(QDateTime(QDate(2007, 11, 1), QTime(18, 8, 30), QTimeZone::UTC).toLocalTime()) + << true << "Last-Modified" << "Thu, 01 Nov 2007 18:08:30 GMT"; + QTest::newRow("Last-Modified-DateTime-Offset") + << QNetworkRequest::LastModifiedHeader + << QVariant(QDateTime(QDate(2007, 11, 1), QTime(18, 8, 30), + QTimeZone::UTC).toOffsetFromUtc(3600)) + << true << "Last-Modified" << "Thu, 01 Nov 2007 18:08:30 GMT"; +#if QT_CONFIG(timezone) + QTimeZone cet("Europe/Oslo"); + if (cet.isValid()) { + QTest::newRow("Last-Modified-DateTime-CET") + << QNetworkRequest::LastModifiedHeader + << QVariant(QDateTime(QDate(2007, 11, 1), QTime(18, 8, 30), + QTimeZone::UTC).toTimeZone(cet)) + << true << "Last-Modified" << "Thu, 01 Nov 2007 18:08:30 GMT"; + } +#endif QTest::newRow("If-Modified-Since-Date") << QNetworkRequest::IfModifiedSinceHeader << QVariant(QDate(2017, 7, 01)) @@ -250,7 +254,7 @@ void tst_QNetworkRequest::setHeader_data() QTest::newRow("If-Modified-Since-DateTime") << QNetworkRequest::IfModifiedSinceHeader << QVariant(QDateTime(QDate(2017, 7, 01), QTime(3, 14, 15), - Qt::UTC)) + QTimeZone::UTC)) << true << "If-Modified-Since" << "Sat, 01 Jul 2017 03:14:15 GMT"; @@ -351,45 +355,45 @@ void tst_QNetworkRequest::rawHeaderParsing_data() << true << "Content-Type" << "text/html"; QTest::newRow("Content-Length") << QNetworkRequest::ContentLengthHeader << QVariant(qint64(1)) - << true << "Content-Length" << " 1 "; + << true << "Content-Length" << "1"; QTest::newRow("Location") << QNetworkRequest::LocationHeader << QVariant(QUrl("http://foo/with space")) << true << "Location" << "http://foo/with%20space"; QTest::newRow("Last-Modified-RFC1123") << QNetworkRequest::LastModifiedHeader << QVariant(QDateTime(QDate(1994, 11, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "Last-Modified" << "Sun, 06 Nov 1994 08:49:37 GMT"; QTest::newRow("Last-Modified-RFC850") << QNetworkRequest::LastModifiedHeader << QVariant(QDateTime(QDate(1994, 11, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "Last-Modified" << "Sunday, 06-Nov-94 08:49:37 GMT"; QTest::newRow("Last-Modified-asctime") << QNetworkRequest::LastModifiedHeader << QVariant(QDateTime(QDate(1994, 11, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "Last-Modified" << "Sun Nov 6 08:49:37 1994"; QTest::newRow("If-Modified-Since-RFC1123") << QNetworkRequest::IfModifiedSinceHeader << QVariant(QDateTime(QDate(1994, 8, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "If-Modified-Since" << "Sun, 06 Aug 1994 08:49:37 GMT"; QTest::newRow("If-Modified-Since-RFC850") << QNetworkRequest::IfModifiedSinceHeader << QVariant(QDateTime(QDate(1994, 8, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "If-Modified-Since" << "Sunday, 06-Aug-94 08:49:37 GMT"; QTest::newRow("If-Modified-Since-asctime") << QNetworkRequest::IfModifiedSinceHeader << QVariant(QDateTime(QDate(1994, 8, 06), QTime(8, 49, 37), - Qt::UTC)) + QTimeZone::UTC)) << true << "If-Modified-Since" << "Sun Aug 6 08:49:37 1994"; @@ -556,5 +560,155 @@ void tst_QNetworkRequest::originatingObject() QVERIFY(!request.originatingObject()); } +void tst_QNetworkRequest::setHeaders_data() +{ + QTest::addColumn<QHttpHeaders>("h"); + QTest::newRow("null") << QHttpHeaders(); + QHttpHeaders headers; + headers.append("name1", "value1"); + QTest::newRow("valid") << headers; +} + +void tst_QNetworkRequest::setHeaders() +{ + QFETCH(QHttpHeaders, h); + + QNetworkRequest r1; + + auto result = r1.headers(); + QVERIFY(result.isEmpty()); + + r1.setHeaders(h); + QCOMPARE(r1.headers().toListOfPairs(), h.toListOfPairs()); + + QNetworkRequest r2; + auto tmp = h; + r2.setHeaders(std::move(tmp)); + QCOMPARE(r2.headers().toListOfPairs(), h.toListOfPairs()); +} + +void tst_QNetworkRequest::operatorEqual_data() +{ + QTest::addColumn<QNetworkRequest>("a"); + QTest::addColumn<QNetworkRequest>("b"); + QTest::addColumn<bool>("expectedToMatch"); + QTest::newRow("null") << QNetworkRequest() << QNetworkRequest() << true; + + QNetworkRequest data1; + data1.setUrl(QUrl("http://qt-project.org")); + QTest::newRow("url-1-1") << data1 << QNetworkRequest() << false; + QTest::newRow("url-1-2") << data1 << data1 << true; + + QNetworkRequest data2; + QHttpHeaders headers; + headers.append("name1", "value1"); + data2.setHeaders(headers); + QTest::newRow("headers-2-1") << data2 << QNetworkRequest() << false; + QTest::newRow("headers-2-2") << data2 << data2 << true; + QTest::newRow("headers-2-3") << data2 << data1 << false; + + QNetworkRequest data3; + data3.setPeerVerifyName("peerName"); + QTest::newRow("peerName-3-1") << data3 << QNetworkRequest() << false; + QTest::newRow("peerName-3-2") << data3 << data3 << true; + QTest::newRow("peerName-3-3") << data3 << data1 << false; + QTest::newRow("peerName-3-4") << data3 << data2 << false; + + QNetworkRequest data4; + data4.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); + QTest::newRow("attribute-4-1") << data4 << QNetworkRequest() << false; + QTest::newRow("attribute-4-2") << data4 << data4 << true; + QTest::newRow("attribute-4-3") << data4 << data1 << false; + QTest::newRow("attribute-4-4") << data4 << data2 << false; + QTest::newRow("attribute-4-5") << data4 << data3 << false; + + QNetworkRequest data5; + data5.setPriority(QNetworkRequest::Priority::HighPriority); + QTest::newRow("priority-5-1") << data5 << QNetworkRequest() << false; + QTest::newRow("priority-5-2") << data5 << data5 << true; + QTest::newRow("priority-5-3") << data5 << data1 << false; + QTest::newRow("priority-5-4") << data5 << data2 << false; + QTest::newRow("priority-5-5") << data5 << data3 << false; + QTest::newRow("priority-5-6") << data5 << data4 << false; + + QNetworkRequest data6; + data6.setMaximumRedirectsAllowed(3); + QTest::newRow("maxRedirects-6-1") << data6 << QNetworkRequest() << false; + QTest::newRow("maxRedirects-6-2") << data6 << data6 << true; + QTest::newRow("maxRedirects-6-3") << data6 << data1 << false; + QTest::newRow("maxRedirects-6-4") << data6 << data2 << false; + QTest::newRow("maxRedirects-6-5") << data6 << data3 << false; + QTest::newRow("maxRedirects-6-6") << data6 << data4 << false; + QTest::newRow("maxRedirects-6-7") << data6 << data5 << false; + +#if QT_CONFIG(http) + QNetworkRequest data7; + QHttp1Configuration http1Configuration; + http1Configuration.setNumberOfConnectionsPerHost(5); + data7.setHttp1Configuration(http1Configuration); + QTest::newRow("http1Config-7-1") << data7 << QNetworkRequest() << false; + QTest::newRow("http1Config-7-2") << data7 << data7 << true; + QTest::newRow("http1Config-7-3") << data7 << data1 << false; + QTest::newRow("http1Config-7-4") << data7 << data2 << false; + QTest::newRow("http1Config-7-5") << data7 << data3 << false; + QTest::newRow("http1Config-7-6") << data7 << data4 << false; + QTest::newRow("http1Config-7-7") << data7 << data5 << false; + QTest::newRow("http1Config-7-8") << data7 << data6 << false; + + QNetworkRequest data8; + QHttp2Configuration http2Configuration; + http2Configuration.setMaxFrameSize(16386); + data8.setHttp2Configuration(http2Configuration); + QTest::newRow("http2Config-8-1") << data8 << QNetworkRequest() << false; + QTest::newRow("http2Config-8-2") << data8 << data8 << true; + QTest::newRow("http2Config-8-3") << data8 << data1 << false; + QTest::newRow("http2Config-8-4") << data8 << data2 << false; + QTest::newRow("http2Config-8-5") << data8 << data3 << false; + QTest::newRow("http2Config-8-6") << data8 << data4 << false; + QTest::newRow("http2Config-8-7") << data8 << data5 << false; + QTest::newRow("http2Config-8-8") << data8 << data6 << false; + QTest::newRow("http2Config-8-9") << data8 << data7 << false; + + QNetworkRequest data9; + data9.setDecompressedSafetyCheckThreshold(-1); + QTest::newRow("threshold-9-1") << data9 << QNetworkRequest() << false; + QTest::newRow("threshold-9-2") << data9 << data9 << true; + QTest::newRow("threshold-9-3") << data9 << data1 << false; + QTest::newRow("threshold-9-4") << data9 << data2 << false; + QTest::newRow("threshold-9-5") << data9 << data3 << false; + QTest::newRow("threshold-9-6") << data9 << data4 << false; + QTest::newRow("threshold-9-7") << data9 << data5 << false; + QTest::newRow("threshold-9-8") << data9 << data6 << false; + QTest::newRow("threshold-9-9") << data9 << data7 << false; + QTest::newRow("threshold-9-10") << data9 << data8 << false; +#endif + +#if QT_CONFIG(http) || defined (Q_OS_WASM) + QNetworkRequest data10; + data10.setTransferTimeout(50000); + QTest::newRow("timeout-10-1") << data10 << QNetworkRequest() << false; + QTest::newRow("timeout-10-2") << data10 << data10 << true; + QTest::newRow("timeout-10-3") << data10 << data1 << false; + QTest::newRow("timeout-10-4") << data10 << data2 << false; + QTest::newRow("timeout-10-5") << data10 << data3 << false; + QTest::newRow("timeout-10-6") << data10 << data4 << false; + QTest::newRow("timeout-10-7") << data10 << data5 << false; + QTest::newRow("timeout-10-8") << data10 << data6 << false; + QTest::newRow("timeout-10-9") << data10 << data7 << false; + QTest::newRow("timeout-10-10") << data10 << data8 << false; + QTest::newRow("timeout-10-11") << data10 << data9 << false; +#endif +} + +// public bool operator==(const QNetworkRequest &other) const +void tst_QNetworkRequest::operatorEqual() +{ + QFETCH(QNetworkRequest, a); + QFETCH(QNetworkRequest, b); + QFETCH(bool, expectedToMatch); + + QCOMPARE(a == b, expectedToMatch); +} + QTEST_MAIN(tst_QNetworkRequest) #include "tst_qnetworkrequest.moc" diff --git a/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt b/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt new file mode 100644 index 0000000000..d2112de58f --- /dev/null +++ b/tests/auto/network/access/qnetworkrequestfactory/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2023 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_qnetworkrequestfactory LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qnetworkrequestfactory + SOURCES + tst_qnetworkrequestfactory.cpp + LIBRARIES + Qt::Core + Qt::Test + Qt::Network +) diff --git a/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp b/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp new file mode 100644 index 0000000000..d04a7ff3ec --- /dev/null +++ b/tests/auto/network/access/qnetworkrequestfactory/tst_qnetworkrequestfactory.cpp @@ -0,0 +1,423 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/qtest.h> +#include <QtNetwork/qnetworkrequestfactory.h> +#ifndef QT_NO_SSL +#include <QtNetwork/qsslconfiguration.h> +#endif +#include <QtCore/qurlquery.h> +#include <QtCore/qurl.h> + +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +class tst_QNetworkRequestFactory : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void urlAndPath_data(); + void urlAndPath(); + void queryParameters(); + void sslConfiguration(); + void headers(); + void bearerToken(); + void operators(); + void timeout(); + void userInfo(); + void priority(); + void attributes(); + +private: + const QUrl url1{u"http://foo.io"_s}; + const QUrl url2{u"http://bar.io"_s}; + const QByteArray bearerToken1{"bearertoken1"}; + const QByteArray bearerToken2{"bearertoken2"}; +}; + +void tst_QNetworkRequestFactory::urlAndPath_data() +{ + QTest::addColumn<QUrl>("baseUrl"); + QTest::addColumn<QString>("requestPath"); + QTest::addColumn<QUrl>("expectedRequestUrl"); + + QUrl base{"http://xyz.io"}; + QUrl result{"http://xyz.io/path/to"}; + QTest::newRow("baseUrl_nopath_noslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_nopath_noslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_nopath_noslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/"); + result.setUrl("http://xyz.io/path/to"); + QTest::newRow("baseUrl_nopath_withslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_nopath_withslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_nopath_withslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/v1"); + result.setUrl("http://xyz.io/v1/path/to"); + QTest::newRow("baseUrl_withpath_noslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_noslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_noslash_3") << base << u"path/to"_s << result; + + base.setUrl("http://xyz.io/v1/"); + QTest::newRow("baseUrl_withpath_withslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_withslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_withslash_3") << base << u"path/to"_s << result; + + // Currently we keep any double '//', but not sure if there is a use case for it, or could + // it be corrected to a single '/' + base.setUrl("http://xyz.io/v1//"); + result.setUrl("http://xyz.io/v1//path/to"); + QTest::newRow("baseUrl_withpath_doubleslash_1") << base << u""_s << base; + QTest::newRow("baseUrl_withpath_doubleslash_2") << base << u"/path/to"_s << result; + QTest::newRow("baseUrl_withpath_doubleslash_3") << base << u"path/to"_s << result; +} + +void tst_QNetworkRequestFactory::urlAndPath() +{ + QFETCH(QUrl, baseUrl); + QFETCH(QString, requestPath); + QFETCH(QUrl, expectedRequestUrl); + + // Set with constructor + QNetworkRequestFactory factory1{baseUrl}; + QCOMPARE(factory1.baseUrl(), baseUrl); + + // Set with setter calls + QNetworkRequestFactory factory2{}; + factory2.setBaseUrl(baseUrl); + QCOMPARE(factory2.baseUrl(), baseUrl); + + // Request path + QNetworkRequest request = factory1.createRequest(); + QCOMPARE(request.url(), baseUrl); // No path was provided for createRequest(), expect baseUrl + request = factory1.createRequest(requestPath); + QCOMPARE(request.url(), expectedRequestUrl); + + // Check the request path didn't change base url + QCOMPARE(factory1.baseUrl(), baseUrl); +} + +void tst_QNetworkRequestFactory::queryParameters() +{ + QNetworkRequestFactory factory({"http://example.com"}); + const QUrlQuery query1{{"q1k", "q1v"}}; + const QUrlQuery query2{{"q2k", "q2v"}}; + + // Set query parameters in createRequest() call + QCOMPARE(factory.createRequest(query1).url(), QUrl{"http://example.com?q1k=q1v"}); + QCOMPARE(factory.createRequest(query2).url(), QUrl{"http://example.com?q2k=q2v"}); + + // Set query parameters into the factory + factory.setQueryParameters(query1); + QUrlQuery resultQuery = factory.queryParameters(); + for (const auto &item: query1.queryItems()) { + QVERIFY(resultQuery.hasQueryItem(item.first)); + QCOMPARE(resultQuery.queryItemValue(item.first), item.second); + } + QCOMPARE(factory.createRequest().url(), QUrl{"http://example.com?q1k=q1v"}); + + // Set query parameters into both createRequest() and factory + QCOMPARE(factory.createRequest(query2).url(), QUrl{"http://example.com?q2k=q2v&q1k=q1v"}); + + // Clear query parameters + factory.clearQueryParameters(); + QVERIFY(factory.queryParameters().isEmpty()); + QCOMPARE(factory.createRequest().url(), QUrl{"http://example.com"}); + + const QString pathWithQuery{"content?raw=1"}; + // Set query parameters in per-request path + QCOMPARE(factory.createRequest(pathWithQuery).url(), + QUrl{"http://example.com/content?raw=1"}); + // Set query parameters in per-request path and the query parameter + QCOMPARE(factory.createRequest(pathWithQuery, query1).url(), + QUrl{"http://example.com/content?q1k=q1v&raw=1"}); + // Set query parameter in per-request path and into the factory + factory.setQueryParameters(query2); + QCOMPARE(factory.createRequest(pathWithQuery).url(), + QUrl{"http://example.com/content?raw=1&q2k=q2v"}); + // Set query parameters in per-request, as additional parameters, and into the factory + QCOMPARE(factory.createRequest(pathWithQuery, query1).url(), + QUrl{"http://example.com/content?q1k=q1v&raw=1&q2k=q2v"}); + + // Test that other than path and query items as part of path are ignored + factory.setQueryParameters(query1); + QRegularExpression re("The provided path*"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QCOMPARE(factory.createRequest("https://example2.com").url(), QUrl{"http://example.com?q1k=q1v"}); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); + QCOMPARE(factory.createRequest("https://example2.com?q3k=q3v").url(), + QUrl{"http://example.com?q3k=q3v&q1k=q1v"}); +} + +void tst_QNetworkRequestFactory::sslConfiguration() +{ +#ifdef QT_NO_SSL + QSKIP("Skipping SSL tests, not supported by build"); +#else + // Two initially equal factories + QNetworkRequestFactory factory1{url1}; + QNetworkRequestFactory factory2{url1}; + + // Make two differing SSL configurations (for this test it's irrelevant how they differ) + QSslConfiguration config1; + config1.setProtocol(QSsl::TlsV1_2); + QSslConfiguration config2; + config2.setProtocol(QSsl::DtlsV1_2); + + // Set configuration and verify that the same config is returned + factory1.setSslConfiguration(config1); + QCOMPARE(factory1.sslConfiguration(), config1); + factory2.setSslConfiguration(config2); + QCOMPARE(factory2.sslConfiguration(), config2); + + // Verify requests are set with appropriate SSL configs + QNetworkRequest request1 = factory1.createRequest(); + QCOMPARE(request1.sslConfiguration(), config1); + QNetworkRequest request2 = factory2.createRequest(); + QCOMPARE(request2.sslConfiguration(), config2); +#endif +} + +void tst_QNetworkRequestFactory::headers() +{ + const QByteArray name1{"headername1"}; + const QByteArray name2{"headername2"}; + const QByteArray value1{"headervalue1"}; + const QByteArray value2{"headervalue2"}; + const QByteArray value3{"headervalue3"}; + + QNetworkRequestFactory factory{url1}; + // Initial state when no headers are set + QVERIFY(factory.commonHeaders().isEmpty()); + QVERIFY(factory.commonHeaders().values(name1).isEmpty()); + QVERIFY(!factory.commonHeaders().contains(name1)); + + // Set headers + QHttpHeaders h1; + h1.append(name1, value1); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1); + QCOMPARE(factory.commonHeaders().size(), 1); + QVERIFY(factory.commonHeaders().values("nonexistent").isEmpty()); + QNetworkRequest request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QCOMPARE(request.rawHeader(name1), value1); + + // Check that empty header does not match + QVERIFY(!factory.commonHeaders().contains(""_ba)); + QVERIFY(factory.commonHeaders().values(""_ba).isEmpty()); + + // Clear headers + factory.clearCommonHeaders(); + QVERIFY(factory.commonHeaders().isEmpty()); + request = factory.createRequest(); + QVERIFY(!request.hasRawHeader(name1)); + + // Set headers with more entries + h1.clear(); + h1.append(name1, value1); + h1.append(name2, value2); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QVERIFY(factory.commonHeaders().contains(name2)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1); + QCOMPARE(factory.commonHeaders().combinedValue(name2), value2); + QCOMPARE(factory.commonHeaders().size(), 2); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QVERIFY(request.hasRawHeader(name2)); + QCOMPARE(request.rawHeader(name1), value1); + QCOMPARE(request.rawHeader(name2), value2); + // Append more values to pre-existing header name2 + h1.clear(); + h1.append(name1, value1); + h1.append(name1, value2); + h1.append(name1, value3); + factory.setCommonHeaders(h1); + QVERIFY(factory.commonHeaders().contains(name1)); + QCOMPARE(factory.commonHeaders().combinedValue(name1), value1 + ", " + value2 + ", " + value3); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(name1)); + QCOMPARE(request.rawHeader(name1), value1 + ", " + value2 + ", " + value3); +} + +void tst_QNetworkRequestFactory::bearerToken() +{ + const auto authHeader = "Authorization"_ba; + QNetworkRequestFactory factory{url1}; + QVERIFY(factory.bearerToken().isEmpty()); + + factory.setBearerToken(bearerToken1); + QCOMPARE(factory.bearerToken(), bearerToken1); + QNetworkRequest request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken1); + + // Verify that bearerToken is not in debug output + QString debugOutput; + QDebug debug(&debugOutput); + debug << factory; + QVERIFY(debugOutput.contains("bearerToken = (is set)")); + QVERIFY(!debugOutput.contains(bearerToken1)); + + factory.setBearerToken(bearerToken2); + QCOMPARE(factory.bearerToken(), bearerToken2); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken2); + + // Set authorization header manually + const auto value = "headervalue"_ba; + QHttpHeaders h1; + h1.append(authHeader, value); + factory.setCommonHeaders(h1); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + // bearerToken has precedence over manually set header + QCOMPARE(request.rawHeader(authHeader), "Bearer "_ba + bearerToken2); + // clear bearer token, the manually set header is now used + factory.clearBearerToken(); + request = factory.createRequest(); + QVERIFY(request.hasRawHeader(authHeader)); + QCOMPARE(request.rawHeader(authHeader), value); +} + +void tst_QNetworkRequestFactory::operators() +{ + QNetworkRequestFactory factory1(url1); + + // Copy ctor + QNetworkRequestFactory factory2(factory1); + QCOMPARE(factory2.baseUrl(), factory1.baseUrl()); + + // Copy assignment + QNetworkRequestFactory factory3; + factory3 = factory2; + QCOMPARE(factory3.baseUrl(), factory2.baseUrl()); + + // Move assignment + QNetworkRequestFactory factory4; + factory4 = std::move(factory3); + QCOMPARE(factory4.baseUrl(), factory2.baseUrl()); + + // Verify implicit sharing + factory1.setBaseUrl(url2); + QCOMPARE(factory1.baseUrl(), url2); // changed + QCOMPARE(factory2.baseUrl(), url1); // remains + + // Move ctor + QNetworkRequestFactory factory5{std::move(factory4)}; + QCOMPARE(factory5.baseUrl(), factory2.baseUrl()); // the moved factory4 originates from factory2 + QCOMPARE(factory5.baseUrl(), url1); +} + +void tst_QNetworkRequestFactory::timeout() +{ + constexpr auto defaultTimeout = 0ms; + constexpr auto timeout = 150ms; + + QNetworkRequestFactory factory; + QNetworkRequest request = factory.createRequest(); + QCOMPARE(factory.transferTimeout(), defaultTimeout); + QCOMPARE(request.transferTimeoutAsDuration(), defaultTimeout); + + factory.setTransferTimeout(timeout); + request = factory.createRequest(); + QCOMPARE(factory.transferTimeout(), timeout); + QCOMPARE(request.transferTimeoutAsDuration(), timeout); +} + +void tst_QNetworkRequestFactory::userInfo() +{ + QNetworkRequestFactory factory; + QVERIFY(factory.userName().isEmpty()); + QVERIFY(factory.password().isEmpty()); + + const auto uname = u"a_username"_s; + const auto password = u"a_password"_s; + factory.setUserName(uname); + QCOMPARE(factory.userName(), uname); + factory.setPassword(password); + QCOMPARE(factory.password(), password); + + // Verify that debug output does not contain password + QString debugOutput; + QDebug debug(&debugOutput); + debug << factory; + QVERIFY(debugOutput.contains("password = (is set)")); + QVERIFY(!debugOutput.contains(password)); + + factory.clearUserName(); + factory.clearPassword(); + QVERIFY(factory.userName().isEmpty()); + QVERIFY(factory.password().isEmpty()); +} + +void tst_QNetworkRequestFactory::priority() +{ + QNetworkRequestFactory factory(u"http://example.com"_s); + QCOMPARE(factory.priority(), QNetworkRequest::NormalPriority); + auto request = factory.createRequest("/index.html"); + QCOMPARE(request.priority(), QNetworkRequest::NormalPriority); + + factory.setPriority(QNetworkRequest::HighPriority); + QCOMPARE(factory.priority(), QNetworkRequest::HighPriority); + request = factory.createRequest("/index.html"); + QCOMPARE(request.priority(), QNetworkRequest::HighPriority); +} + +void tst_QNetworkRequestFactory::attributes() +{ + const auto attribute1 = QNetworkRequest::Attribute::BackgroundRequestAttribute; + const auto attribute2 = QNetworkRequest::User; + QNetworkRequestFactory factory; + QNetworkRequest request; + + // Empty factory + QVERIFY(!factory.attribute(attribute1).isValid()); + request = factory.createRequest(); + QVERIFY(!request.attribute(attribute1).isValid()); + + // (Re-)set and clear individual attribute + factory.setAttribute(attribute1, true); + QVERIFY(factory.attribute(attribute1).isValid()); + QCOMPARE(factory.attribute(attribute1).toBool(), true); + request = factory.createRequest(); + QVERIFY(request.attribute(attribute1).isValid()); + QCOMPARE(request.attribute(attribute1).toBool(), true); + // Replace previous value + factory.setAttribute(attribute1, false); + QVERIFY(factory.attribute(attribute1).isValid()); + QCOMPARE(factory.attribute(attribute1).toBool(), false); + request = factory.createRequest(); + QVERIFY(request.attribute(attribute1).isValid()); + QCOMPARE(request.attribute(attribute1).toBool(), false); + // Clear individual attribute + factory.clearAttribute(attribute1); + QVERIFY(!factory.attribute(attribute1).isValid()); + + // Getter default value + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 111); // default value returned + factory.setAttribute(attribute2, 222); + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 222); // actual value returned + factory.clearAttribute(attribute2); + QCOMPARE(factory.attribute(attribute2, 111).toInt(), 111); // default value returned + + // Clear attributes + factory.setAttribute(attribute1, true); + factory.setAttribute(attribute2, 333); + QVERIFY(factory.attribute(attribute1).isValid()); + QVERIFY(factory.attribute(attribute2).isValid()); + factory.clearAttributes(); + QVERIFY(!factory.attribute(attribute1).isValid()); + QVERIFY(!factory.attribute(attribute2).isValid()); + request = factory.createRequest(); + QVERIFY(!request.attribute(attribute1).isValid()); + QVERIFY(!request.attribute(attribute2).isValid()); +} + +QTEST_MAIN(tst_QNetworkRequestFactory) +#include "tst_qnetworkrequestfactory.moc" diff --git a/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt b/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt new file mode 100644 index 0000000000..614248be28 --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2023 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_qrestaccessmanager LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qrestaccessmanager + SOURCES + tst_qrestaccessmanager.cpp + httptestserver.cpp httptestserver_p.h + LIBRARIES + Qt::Network + Qt::CorePrivate +) diff --git a/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp b/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp new file mode 100644 index 0000000000..25869eb46b --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/httptestserver.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "httptestserver_p.h" + +#include <QtNetwork/qtcpsocket.h> + +#include <QtCore/qcoreapplication.h> + +#include <private/qlocale_p.h> + +using namespace Qt::StringLiterals; + +static constexpr char CRLF[] = "\r\n"; + +HttpTestServer::HttpTestServer(QObject *parent) : QTcpServer(parent) +{ + QObject::connect(this, &QTcpServer::newConnection, this, &HttpTestServer::handleConnected); + const auto ok = listen(QHostAddress::LocalHost); + Q_ASSERT(ok); +}; + +HttpTestServer::~HttpTestServer() +{ + if (isListening()) + close(); +} + +QUrl HttpTestServer::url() +{ + return QUrl(u"http://127.0.0.1:%1"_s.arg(serverPort())); +} + +void HttpTestServer::handleConnected() +{ + Q_ASSERT(!m_socket); // No socket must exist previously, this is a single-connection server + m_socket = nextPendingConnection(); + Q_ASSERT(m_socket); + QObject::connect(m_socket, &QTcpSocket::readyRead, + this, &HttpTestServer::handleDataAvailable); +} + +void HttpTestServer::handleDataAvailable() +{ + Q_ASSERT(m_socket); + bool ok = true; + + // Parse the incoming request data into the HttpData object + while (m_socket->bytesAvailable()) { + if (state == State::ReadingMethod && !(ok = readMethod(m_socket))) + qWarning("Invalid Method"); + if (ok && state == State::ReadingUrl && !(ok = readUrl(m_socket))) + qWarning("Invalid URL"); + if (ok && state == State::ReadingStatus && !(ok = readStatus(m_socket))) + qWarning("Invalid Status"); + if (ok && state == State::ReadingHeader && !(ok = readHeaders(m_socket))) + qWarning("Invalid Header"); + if (ok && state == State::ReadingBody && !(ok = readBody(m_socket))) + qWarning("Invalid Body"); + } // while bytes available + + Q_ASSERT(ok); + Q_ASSERT(m_handler); + Q_ASSERT(state == State::AllDone); + + if (auto values = m_request.headers.values( + QHttpHeaders::WellKnownHeader::Host); !values.empty()) { + const auto parts = values.first().split(':'); + m_request.url.setHost(parts.at(0)); + if (parts.size() == 2) + m_request.url.setPort(parts.at(1).toUInt()); + } + HttpData response; + ResponseControl control; + // Inform the testcase about request and ask for response data + m_handler(m_request, response, control); + + QByteArray responseMessage; + responseMessage += "HTTP/1.1 "; + responseMessage += QByteArray::number(response.status); + responseMessage += CRLF; + // Insert headers if any + for (const auto &[name,value] : response.headers.toListOfPairs()) { + responseMessage += name; + responseMessage += ": "; + responseMessage += value; + responseMessage += CRLF; + } + responseMessage += CRLF; + /* + qDebug() << "HTTPTestServer received request" + << "\nMethod:" << m_request.method + << "\nHeaders:" << m_request.headers + << "\nBody:" << m_request.body; + */ + if (control.respond) { + if (control.responseChunkSize <= 0) { + responseMessage += response.body; + // qDebug() << "HTTPTestServer response:" << responseMessage; + m_socket->write(responseMessage); + } else { + // Respond in chunks, first write the headers + // qDebug() << "HTTPTestServer response:" << responseMessage; + m_socket->write(responseMessage); + // Then write bodydata in chunks, while allowing the testcase to process as well + QByteArray chunk; + while (!response.body.isEmpty()) { + chunk = response.body.left(control.responseChunkSize); + response.body.remove(0, control.responseChunkSize); + // qDebug() << "SERVER writing chunk" << chunk; + m_socket->write(chunk); + m_socket->flush(); + m_socket->waitForBytesWritten(); + // Process events until testcase indicates it's ready for next chunk. + // This way we can control the bytes the testcase gets in each chunk + control.readyForNextChunk = false; + while (!control.readyForNextChunk) + QCoreApplication::processEvents(); + } + } + } + m_socket->disconnectFromHost(); + m_request = {}; + m_socket = nullptr; // deleted by QTcpServer during destruction + state = State::ReadingMethod; + fragment.clear(); +} + +bool HttpTestServer::readMethod(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + const auto c = socket->read(1).at(0); + if (ascii_isspace(c)) + finished = true; + else if (std::isupper(c) && fragment.size() < 8) + fragment += c; + else + return false; + } + if (finished) { + if (fragment == "HEAD") + method = Method::Head; + else if (fragment == "GET") + method = Method::Get; + else if (fragment == "PUT") + method = Method::Put; + else if (fragment == "PATCH") + method = Method::Patch; + else if (fragment == "POST") + method = Method::Post; + else if (fragment == "DELETE") + method = Method::Delete; + else if (fragment == "FOOBAR") // used by custom verb/method tests + method = Method::Custom; + else + qWarning("Invalid operation %s", fragment.data()); + + state = State::ReadingUrl; + m_request.method = fragment; + fragment.clear(); + + return method != Method::Unknown; + } + return true; +} + +bool HttpTestServer::readUrl(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + const auto c = socket->read(1).at(0); + if (std::isspace(c)) + finished = true; + else + fragment += c; + } + if (finished) { + if (!fragment.startsWith('/')) { + qWarning("Invalid URL path %s", fragment.constData()); + return false; + } + m_request.url = QStringLiteral("http://127.0.0.1:") + QString::number(m_request.port) + + QString::fromUtf8(fragment); + state = State::ReadingStatus; + if (!m_request.url.isValid()) { + qWarning("Invalid URL %s", fragment.constData()); + return false; + } + fragment.clear(); + } + return true; +} + +bool HttpTestServer::readStatus(QTcpSocket *socket) +{ + bool finished = false; + while (socket->bytesAvailable() && !finished) { + fragment += socket->read(1); + if (fragment.endsWith(CRLF)) { + finished = true; + fragment.resize(fragment.size() - 2); + } + } + if (finished) { + if (!std::isdigit(fragment.at(fragment.size() - 3)) || + fragment.at(fragment.size() - 2) != '.' || + !std::isdigit(fragment.at(fragment.size() - 1))) { + qWarning("Invalid version"); + return false; + } + m_request.version = std::pair(fragment.at(fragment.size() - 3) - '0', + fragment.at(fragment.size() - 1) - '0'); + state = State::ReadingHeader; + fragment.clear(); + } + return true; +} + +bool HttpTestServer::readHeaders(QTcpSocket *socket) +{ + while (socket->bytesAvailable()) { + fragment += socket->read(1); + if (fragment.endsWith(CRLF)) { + if (fragment == CRLF) { + state = State::ReadingBody; + fragment.clear(); + return true; + } else { + fragment.chop(2); + const int index = fragment.indexOf(':'); + if (index == -1) + return false; + + QByteArray key = fragment.sliced(0, index).trimmed(); + QByteArray value = fragment.sliced(index + 1).trimmed(); + m_request.headers.append(key, value); + fragment.clear(); + } + } + } + return true; +} + +bool HttpTestServer::readBody(QTcpSocket *socket) +{ + qint64 bytesLeft = 0; + if (auto values = m_request.headers.values( + QHttpHeaders::WellKnownHeader::ContentLength); !values.empty()) { + bool conversionResult; + bytesLeft = values.first().toInt(&conversionResult); + if (!conversionResult) + return false; + fragment.resize(bytesLeft); + } + while (bytesLeft) { + qint64 got = socket->read(&fragment.data()[fragment.size() - bytesLeft], bytesLeft); + if (got < 0) + return false; // error + bytesLeft -= got; + if (bytesLeft) + qApp->processEvents(); + } + fragment.swap(m_request.body); + state = State::AllDone; + return true; +} + diff --git a/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h b/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h new file mode 100644 index 0000000000..0a94b2c8a6 --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/httptestserver_p.h @@ -0,0 +1,90 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H +#define QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H + +#include <QtNetwork/qtcpserver.h> +#include <QtNetwork/qhttpheaders.h> + +#include <QtCore/qmap.h> +#include <QtCore/qurl.h> + +#include <functional> + +// This struct is used for parsing the incoming network request data into, as well +// as getting the response data from the testcase +struct HttpData { + QUrl url; + int status = 0; + QByteArray body; + QByteArray method; + quint16 port = 0; + QPair<quint8, quint8> version; + QHttpHeaders headers; +}; + +struct ResponseControl +{ + bool respond = true; + qsizetype responseChunkSize = -1; + bool readyForNextChunk = true; +}; + +// Simple HTTP server. Currently supports only one concurrent connection +class HttpTestServer : public QTcpServer +{ + Q_OBJECT + +public: + explicit HttpTestServer(QObject *parent = nullptr); + ~HttpTestServer() override; + + // Returns this server's URL for the testcase to send requests to + QUrl url(); + + enum class State { + ReadingMethod, + ReadingUrl, + ReadingStatus, + ReadingHeader, + ReadingBody, + AllDone + } state = State::ReadingMethod; + + enum class Method { + Unknown, + Head, + Get, + Put, + Patch, + Post, + Delete, + Custom, + } method = Method::Unknown; + + // Parsing helpers for incoming data => HttpData + bool readMethod(QTcpSocket *socket); + bool readUrl(QTcpSocket *socket); + bool readStatus(QTcpSocket *socket); + bool readHeaders(QTcpSocket *socket); + bool readBody(QTcpSocket *socket); + // Parsing-time buffer in case data is received a small chunk at a time (readyRead()) + QByteArray fragment; + + // Settable callback for testcase. Gives the received request data, and takes in response data + using Handler = std::function<void(const HttpData &request, HttpData &response, + ResponseControl &control)>; + void setHandler(Handler handler) { m_handler = std::move(handler); } + +private slots: + void handleConnected(); + void handleDataAvailable(); + +private: + QTcpSocket *m_socket = nullptr; + HttpData m_request; + Handler m_handler = nullptr; +}; + +#endif // QRESTACCESSSMANAGER_HTTPTESTSERVER_P_H diff --git a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp new file mode 100644 index 0000000000..d6bdda76ca --- /dev/null +++ b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp @@ -0,0 +1,876 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "httptestserver_p.h" + +#if QT_CONFIG(http) +#include <QtNetwork/qhttpmultipart.h> +#endif +#include <QtNetwork/qrestaccessmanager.h> +#include <QtNetwork/qauthenticator.h> +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qnetworkrequestfactory.h> +#include <QtNetwork/qrestreply.h> + +#include <QTest> +#include <QtTest/qsignalspy.h> + +#include <QtCore/private/qglobal_p.h> // for access to Qt's feature system +#include <QtCore/qbuffer.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qstringconverter.h> + +using namespace Qt::StringLiterals; +using namespace std::chrono_literals; + +using Header = QHttpHeaders::WellKnownHeader; + +class tst_QRestAccessManager : public QObject +{ + Q_OBJECT + +private slots: + void initialization(); + void destruction(); + void callbacks(); +#if QT_CONFIG(http) + void requests(); +#endif + void reply(); + void errors(); + void body(); + void json(); + void text(); + void textStreaming(); + +private: + void memberHandler(QRestReply &reply); + + friend class Transient; + QList<QNetworkReply*> m_expectedReplies; + QList<QNetworkReply*> m_actualReplies; +}; + +void tst_QRestAccessManager::initialization() +{ + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestAccessManager: QNetworkAccesManager is nullptr"); + QRestAccessManager manager1(nullptr); + QVERIFY(!manager1.networkAccessManager()); + + QNetworkAccessManager qnam; + QRestAccessManager manager2(&qnam); + QVERIFY(manager2.networkAccessManager()); +} + +void tst_QRestAccessManager::reply() +{ + QNetworkAccessManager qnam; + + QNetworkReply *nr = qnam.get(QNetworkRequest(QUrl{"someurl"})); + QRestReply rr1(nr); + QCOMPARE(rr1.networkReply(), nr); + + // Move-construct + QRestReply rr2(std::move(rr1)); + QCOMPARE(rr2.networkReply(), nr); + + // Move-assign + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestReply: QNetworkReply is nullptr"); + QRestReply rr3(nullptr); + rr3 = std::move(rr2); + QCOMPARE(rr3.networkReply(), nr); +} + +#define VERIFY_REPLY_OK(METHOD) \ +{ \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + QCOMPARE(serverSideRequest.method, METHOD); \ + QVERIFY(restReply.isSuccess()); \ + QVERIFY(!restReply.hasError()); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} + +#if QT_CONFIG(http) +void tst_QRestAccessManager::requests() +{ + // A basic test for each HTTP method against the local testserver. + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn + QNetworkReply *networkReply = nullptr; + std::unique_ptr<QHttpMultiPart> multiPart; + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\"")); + part.setBody("multipart_text"); + QByteArray ioDeviceData{"io_device_data"_ba}; + QBuffer bufferIoDevice(&ioDeviceData); + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + + }); + auto callback = [&](QRestReply &reply) { networkReply = reply.networkReply(); }; + const QByteArray byteArrayData{"some_data"_ba}; + const QJsonObject jsonObjectData{{"key1", "value1"}, {"key2", "value2"}}; + const QJsonArray jsonArrayData{{"arrvalue1", "arrvalue2", QJsonObject{{"key1", "value1"}}}}; + const QVariantMap variantMapData{{"key1", "value1"}, {"key2", "value2"}}; + const QByteArray methodDELETE{"DELETE"_ba}; + const QByteArray methodHEAD{"HEAD"_ba}; + const QByteArray methodPOST{"POST"_ba}; + const QByteArray methodGET{"GET"_ba}; + const QByteArray methodPUT{"PUT"_ba}; + const QByteArray methodPATCH{"PATCH"_ba}; + const QByteArray methodCUSTOM{"FOOBAR"_ba}; + + // DELETE + manager.deleteResource(request, this, callback); + VERIFY_REPLY_OK(methodDELETE); + QCOMPARE(serverSideRequest.body, ""_ba); + + // HEAD + manager.head(request, this, callback); + VERIFY_REPLY_OK(methodHEAD); + QCOMPARE(serverSideRequest.body, ""_ba); + + // GET + manager.get(request, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, ""_ba); + + manager.get(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.get(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.get(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodGET); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // CUSTOM + manager.sendCustomRequest(request, methodCUSTOM, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.sendCustomRequest(request, methodCUSTOM, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + multiPart.reset(new QHttpMultiPart(QHttpMultiPart::FormDataType)); + multiPart->append(part); + manager.sendCustomRequest(request, methodCUSTOM, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodCUSTOM); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + // POST + manager.post(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.post(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.post(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.post(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType); + multiPart->append(part); + manager.post(request, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodPOST); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + manager.post(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPOST); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // PUT + manager.put(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.put(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.put(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.put(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType); + multiPart->append(part); + manager.put(request, multiPart.get(), this, callback); + VERIFY_REPLY_OK(methodPUT); + QVERIFY(serverSideRequest.body.contains("--boundary"_ba)); + QVERIFY(serverSideRequest.body.contains("multipart_text"_ba)); + + manager.put(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPUT); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + // PATCH + manager.patch(request, byteArrayData, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(serverSideRequest.body, byteArrayData); + + manager.patch(request, QJsonDocument{jsonObjectData}, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.patch(request, QJsonDocument{jsonArrayData}, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).array(), jsonArrayData); + + manager.patch(request, variantMapData, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(QJsonDocument::fromJson(serverSideRequest.body).object(), jsonObjectData); + + manager.patch(request, &bufferIoDevice, this, callback); + VERIFY_REPLY_OK(methodPATCH); + QCOMPARE(serverSideRequest.body, ioDeviceData); + + //These must NOT compile + //manager.get(request, [](){}); // callback without context object + //manager.get(request, ""_ba, [](){}); // callback without context object + //manager.get(request, QString()); // wrong datatype + //manager.get(request, 123); // wrong datatype + //manager.post(request, QString()); // wrong datatype + //manager.put(request, 123); // wrong datatype + //manager.post(request); // data is required + //manager.put(request, QString()); // wrong datatype + //manager.put(request); // data is required + //manager.patch(request, 123); // wrong datatype + //manager.patch(request, QString()); // wrong datatype + //manager.patch(request); // data is required + //manager.deleteResource(request, "f"_ba); // data not allowed + //manager.head(request, "f"_ba); // data not allowed + //manager.post(request, ""_ba, this, [](int param){}); // Wrong callback signature + //manager.get(request, this, [](int param){}); // Wrong callback signature + //manager.sendCustomRequest(request, this, [](){}); // No verb && no data + //manager.sendCustomRequest(request, "FOOBAR", this, [](){}); // No verb || no data +} +#endif + +void tst_QRestAccessManager::memberHandler(QRestReply &reply) +{ + m_actualReplies.append(reply.networkReply()); +} + +// Class that is destroyed during an active request. +// Used to test that the callbacks won't be called in these cases +class Transient : public QObject +{ + Q_OBJECT +public: + explicit Transient(tst_QRestAccessManager *test) : QObject(test), m_test(test) {} + + void memberHandler(QRestReply &reply) + { + m_test->m_actualReplies.append(reply.networkReply()); + } + +private: + tst_QRestAccessManager *m_test = nullptr; +}; + +template <typename Functor, std::enable_if_t< + QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true> +inline constexpr bool isCompatibleCallback(Functor &&) { return true; } + +template <typename Functor, std::enable_if_t< + !QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true, + typename = void> +inline constexpr bool isCompatibleCallback(Functor &&) { return false; } + +void tst_QRestAccessManager::callbacks() +{ + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + + QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error + + auto lambdaHandler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; + Transient *transient = nullptr; + QByteArray data{"some_data"}; + + // Compile-time tests for callback signatures + static_assert(isCompatibleCallback([](QRestReply&){})); // Correct signature + static_assert(isCompatibleCallback(lambdaHandler)); + static_assert(isCompatibleCallback(&Transient::memberHandler)); + static_assert(isCompatibleCallback([](){})); // Less parameters are allowed + + static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply*){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply&, QString){})); // Too many parameters + + // -- Test without data + // Without callback using signals and slot + QNetworkReply* reply = manager.get(request); + m_expectedReplies.append(reply); + QObject::connect(reply, &QNetworkReply::finished, this, + [this, reply](){m_actualReplies.append(reply);}); + + // With lambda callback, without context object + m_expectedReplies.append(manager.get(request, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.get(request, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.get(request, this, lambdaHandler)); + m_expectedReplies.append(manager.get(request, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.get(request, this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash. + transient = new Transient(this); + manager.get(request, transient, &Transient::memberHandler); // Reply not added to expecteds + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); + + // -- Test with data + // With lambda callback, without context object + m_expectedReplies.append(manager.post(request, data, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.post(request, data, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.post(request, data, this, lambdaHandler)); + m_expectedReplies.append(manager.post(request, data, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.post(request, data, + this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash + transient = new Transient(this); + manager.post(request, data, transient, &Transient::memberHandler); // Note: reply not expected + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + reply->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); + + // -- Test GET with data separately, as GET provides methods that are usable with and + // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong. + // With lambda callback, without context object + m_expectedReplies.append(manager.get(request, data, nullptr, lambdaHandler)); + m_expectedReplies.append(manager.get(request, data, nullptr, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With lambda callback and context object + m_expectedReplies.append(manager.get(request, data, this, lambdaHandler)); + m_expectedReplies.append(manager.get(request, data, this, + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); + // With member callback and context object + m_expectedReplies.append(manager.get(request, data, + this, &tst_QRestAccessManager::memberHandler)); + // With context object that is destroyed, there should be no callback or eg. crash + transient = new Transient(this); + manager.get(request, data, transient, &Transient::memberHandler); // Reply not added + delete transient; + + // Let requests finish + QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); + for (auto reply: m_actualReplies) { + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); + } + m_actualReplies.clear(); + m_expectedReplies.clear(); +} + +void tst_QRestAccessManager::destruction() +{ + std::unique_ptr<QNetworkAccessManager> qnam = std::make_unique<QNetworkAccessManager>(); + std::unique_ptr<QRestAccessManager> manager = std::make_unique<QRestAccessManager>(qnam.get()); + QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error + m_expectedReplies.clear(); + m_actualReplies.clear(); + auto handler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; + + // Delete reply immediately, make sure nothing bad happens and that there is no callback + QNetworkReply *networkReply = manager->get(request, this, handler); + delete networkReply; + QTest::qWait(20); // allow some time for the callback to arrive (it shouldn't) + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 + + // Delete access manager immediately after request, make sure nothing bad happens + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + QTest::ignoreMessage(QtWarningMsg, "Access manager destroyed while 2 requests were still" + " in progress"); + manager.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 + + // Destroy the underlying QNAM while requests in progress + manager = std::make_unique<QRestAccessManager>(qnam.get()); + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + qnam.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 +} + +#define VERIFY_HTTP_ERROR_STATUS(STATUS) \ +{ \ + serverSideResponse.status = STATUS; \ + QRestReply restReply(manager.get(request)); \ + QTRY_VERIFY(restReply.networkReply()->isFinished()); \ + QVERIFY(!restReply.hasError()); \ + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); \ + QCOMPARE(restReply.error(), QNetworkReply::NetworkError::NoError); \ + QVERIFY(!restReply.isSuccess()); \ + restReply.networkReply()->deleteLater(); \ +} \ + +void tst_QRestAccessManager::errors() +{ + // Tests the distinction between HTTP and other (network/protocol) errors + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + + HttpData serverSideResponse; // The response data the server responds with + server.setHandler([&](const HttpData &, HttpData &response, ResponseControl &) { + response = serverSideResponse; + }); + + // Test few HTTP statuses in different categories + VERIFY_HTTP_ERROR_STATUS(301) // QNetworkReply::ProtocolUnknownError + VERIFY_HTTP_ERROR_STATUS(302) // QNetworkReply::ProtocolUnknownError + VERIFY_HTTP_ERROR_STATUS(400) // QNetworkReply::ProtocolInvalidOperationError + VERIFY_HTTP_ERROR_STATUS(401) // QNetworkReply::AuthenticationRequiredEror + VERIFY_HTTP_ERROR_STATUS(402) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(403) // QNetworkReply::ContentAccessDenied + VERIFY_HTTP_ERROR_STATUS(404) // QNetworkReply::ContentNotFoundError + VERIFY_HTTP_ERROR_STATUS(405) // QNetworkReply::ContentOperationNotPermittedError + VERIFY_HTTP_ERROR_STATUS(406) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(407) // QNetworkReply::ProxyAuthenticationRequiredError + VERIFY_HTTP_ERROR_STATUS(408) // QNetworkReply::UnknownContentError + VERIFY_HTTP_ERROR_STATUS(409) // QNetworkReply::ContentConflictError + VERIFY_HTTP_ERROR_STATUS(410) // QNetworkReply::ContentGoneError + VERIFY_HTTP_ERROR_STATUS(500) // QNetworkReply::InternalServerError + VERIFY_HTTP_ERROR_STATUS(501) // QNetworkReply::OperationNotImplementedError + VERIFY_HTTP_ERROR_STATUS(502) // QNetworkReply::UnknownServerError + VERIFY_HTTP_ERROR_STATUS(503) // QNetworkReply::ServiceUnavailableError + VERIFY_HTTP_ERROR_STATUS(504) // QNetworkReply::UnknownServerError + VERIFY_HTTP_ERROR_STATUS(505) // QNetworkReply::UnknownServerError + + { + // Test that actual network/protocol errors come through + QRestReply restReply(manager.get({})); // Empty url + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}})); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::HostNotFoundError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(request)); + restReply.networkReply()->abort(); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::OperationCanceledError); + restReply.networkReply()->deleteLater(); + } +} + +void tst_QRestAccessManager::body() +{ + // Test using QRestReply::body() data accessor + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + { + serverSideResponse.status = 200; + serverSideResponse.body = "some_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 200; + serverSideResponse.body = ""_ba; // Empty + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 500; + serverSideResponse.body = "some_other_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.readBody(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } +} + +void tst_QRestAccessManager::json() +{ + // Tests using QRestReply::readJson() + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + QJsonDocument responseJsonDocument; + std::optional<QJsonDocument> json; + QJsonParseError parseError; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + { + // Test receiving valid json object + serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; + networkReply = manager.get(request); + // Read unfinished reply + QVERIFY(!networkReply->isFinished()); + QTest::ignoreMessage(QtWarningMsg, "readJson() called on an unfinished reply, ignoring"); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value + QRestReply restReply(networkReply); + QVERIFY(!restReply.readJson(&parseError)); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + // Read finished reply + QTRY_VERIFY(networkReply->isFinished()); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.readJson(&parseError); + QVERIFY(json); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isObject()); + QCOMPARE(responseJsonDocument["key1"], "value1"); + QCOMPARE(responseJsonDocument["key2"], "value2"); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + // Test receiving an invalid json object + serverSideResponse.body = "foobar"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + const auto json = restReply.readJson(&parseError); + networkReply->deleteLater(); + networkReply = nullptr; + QCOMPARE_EQ(json, std::nullopt); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); + QCOMPARE_GT(parseError.offset, 0); + } + + { + // Test receiving valid json array + serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.readJson(&parseError); + networkReply->deleteLater(); + networkReply = nullptr; + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + QVERIFY(json); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isArray()); + QCOMPARE(responseJsonDocument.array().size(), 2); + QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); + QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); + } +} + +#define VERIFY_TEXT_REPLY_OK \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + responseString = restReply.readText(); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ + QCOMPARE(responseString, sourceString); \ +} + +#define VERIFY_TEXT_REPLY_ERROR(WARNING_MESSAGE) \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QTest::ignoreMessage(QtWarningMsg, WARNING_MESSAGE); \ + QRestReply restReply(networkReply); \ + responseString = restReply.readText(); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ + QVERIFY(responseString.isEmpty()); \ +} + +void tst_QRestAccessManager::text() +{ + // Test using QRestReply::text() data accessor with various text encodings + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + QNetworkReply *networkReply = nullptr; + QJsonObject responseJsonObject; + + QStringEncoder encUTF8("UTF-8"); + QStringEncoder encUTF16("UTF-16"); + QStringEncoder encUTF32("UTF-32"); + QString responseString; + + HttpData serverSideRequest; // The request data the server received + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.status = 200; + server.setHandler([&](const HttpData &request, HttpData &response, ResponseControl&) { + serverSideRequest = request; + response = serverSideResponse; + }); + + const QString sourceString("this is a string"_L1); + + // Charset parameter of Content-Type header may specify non-UTF-8 character encoding. + // + // QString is UTF-16, and in the tests below we encode the response data to various + // charset encodings (into byte arrays). When we get the response data, the text() + // should consider the indicated charset and convert it to an UTF-16 QString => the returned + // QString from text() should match with the original (UTF-16) QString. + + // Successful UTF-8 (explicit) + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (obfuscated) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=\"UT\\F-8\""_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (empty charset) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=\"\""_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-8 (implicit) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-16 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-16"_ba); + serverSideResponse.body = encUTF16(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-16, parameter case insensitivity + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; chARset=uTf-16"_ba); + serverSideResponse.body = encUTF16(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 with spec-wise allowed extra trailing content in the Content-Type header value + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, + "text(this is a \\)comment)/ (this (too)) plain; charset = \"UTF-32\";extraparameter=bar"_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + // Successful UTF-32 with spec-wise allowed extra leading content in the Content-Type header value + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, + "text/plain; extraparameter=bar;charset = \"UT\\F-32\""_ba); + serverSideResponse.body = encUTF32(sourceString); + VERIFY_TEXT_REPLY_OK; + + { + // Unsuccessful UTF-32, wrong encoding indicated (indicated UTF-32 but data is UTF-8) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF8(sourceString); + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + responseString = restReply.readText(); + QCOMPARE_NE(responseString, sourceString); + networkReply->deleteLater(); + networkReply = nullptr; + } + + // Unsupported encoding + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=foo"_ba); + serverSideResponse.body = encUTF8(sourceString); + VERIFY_TEXT_REPLY_ERROR("readText(): Charset \"foo\" is not supported") + + // Broken UTF-8 + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0"; // invalid characters + VERIFY_TEXT_REPLY_ERROR("readText(): Decoding error occurred"); +} + +void tst_QRestAccessManager::textStreaming() +{ + // Tests textual data received in chunks + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); + HttpTestServer server; + QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); + + // Create long text data + const QString expectedData = u"사랑abcd€fghiklmnΩpqrstuvwx愛사랑A사랑BCD€FGHIJKLMNΩPQRsTUVWXYZ愛"_s; + QString cumulativeReceivedText; + QStringEncoder encUTF8("UTF-8"); + ResponseControl *responseControl = nullptr; + + HttpData serverSideResponse; // The response data the server responds with + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-8"_ba); + serverSideResponse.body = encUTF8(expectedData); + serverSideResponse.status = 200; + + server.setHandler([&](const HttpData &, HttpData &response, ResponseControl &control) { + response = serverSideResponse; + responseControl = &control; // store for later + control.responseChunkSize = 5; // tell testserver to send data in chunks of this size + }); + + { + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + cumulativeReceivedText += restReply.readText(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, expectedData); + restReply.networkReply()->deleteLater(); + } + + { + cumulativeReceivedText.clear(); + // Broken UTF-8 characters after first five ok characters + serverSideResponse.body = + "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + static bool firstTime = true; + if (!firstTime) // First text part is without warnings + QTest::ignoreMessage(QtWarningMsg, "readText(): Decoding error occurred"); + firstTime = false; + cumulativeReceivedText += restReply.readText(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, "12345"_ba); + restReply.networkReply()->deleteLater(); + } +} + +QTEST_MAIN(tst_QRestAccessManager) +#include "tst_qrestaccessmanager.moc" |