qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# SSL library include path is not propagated with private tests which results in
# test not being able to find the ssl header when they are not in the standard
# include paths
if (QT_FEATURE_private_tests)
if (QT_FEATURE_openssl AND QT_FEATURE_openssl_linked AND QT_FEATURE_ssl)
include_directories($<TARGET_PROPERTY:OpenSSL::SSL,INTERFACE_INCLUDE_DIRECTORIES>)
endif()
if (QT_FEATURE_openssl AND QT_FEATURE_ssl AND NOT QT_FEATURE_openssl_linked)
include_directories($<TARGET_PROPERTY:WrapOpenSSLHeaders::WrapOpenSSLHeaders,INTERFACE_INCLUDE_DIRECTORIES>)
endif()
endif()
# add_subdirectory(selftest) # special case not ported
add_subdirectory(access)
add_subdirectory(kernel)
add_subdirectory(ssl)
add_subdirectory(socket)

View File

@ -0,0 +1,20 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qnetworkdiskcache)
add_subdirectory(qnetworkcookiejar)
add_subdirectory(qnetworkaccessmanager)
add_subdirectory(qnetworkcookie)
add_subdirectory(qnetworkrequest)
add_subdirectory(qnetworkreply)
add_subdirectory(qnetworkcachemetadata)
add_subdirectory(qabstractnetworkcache)
if(QT_FEATURE_private_tests)
add_subdirectory(qhttpheaderparser)
add_subdirectory(qhttpnetworkconnection)
add_subdirectory(qhttpnetworkreply)
add_subdirectory(hpack)
add_subdirectory(http2)
add_subdirectory(hsts)
add_subdirectory(qdecompresshelper)
endif()

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_hpack Test:
#####################################################################
qt_internal_add_test(tst_hpack
SOURCES
tst_hpack.cpp
LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
)

View File

@ -0,0 +1,805 @@
// 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 WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QRandomGenerator>
#include <QtNetwork/private/bitstreams_p.h>
#include <QtNetwork/private/hpack_p.h>
#include <QtCore/qbytearray.h>
#include <cstdlib>
#include <vector>
#include <string>
QT_USE_NAMESPACE
using namespace HPack;
class tst_Hpack: public QObject
{
Q_OBJECT
public:
tst_Hpack();
private Q_SLOTS:
void bitstreamConstruction();
void bitstreamWrite();
void bitstreamReadWrite();
void bitstreamCompression();
void bitstreamErrors();
void lookupTableConstructor();
void lookupTableStatic();
void lookupTableDynamic();
void hpackEncodeRequest_data();
void hpackEncodeRequest();
void hpackDecodeRequest_data();
void hpackDecodeRequest();
void hpackEncodeResponse_data();
void hpackEncodeResponse();
void hpackDecodeResponse_data();
void hpackDecodeResponse();
// TODO: more-more-more tests needed!
private:
void hpackEncodeRequest(bool withHuffman);
void hpackEncodeResponse(bool withHuffman);
HttpHeader header1;
std::vector<uchar> buffer1;
BitOStream request1;
HttpHeader header2;
std::vector<uchar> buffer2;
BitOStream request2;
HttpHeader header3;
std::vector<uchar> buffer3;
BitOStream request3;
};
using StreamError = BitIStream::Error;
tst_Hpack::tst_Hpack()
: request1(buffer1),
request2(buffer2),
request3(buffer3)
{
}
void tst_Hpack::bitstreamConstruction()
{
const uchar bytes[] = {0xDE, 0xAD, 0xBE, 0xEF};
const int size = int(sizeof bytes);
// Default ctors:
std::vector<uchar> buffer;
{
const BitOStream out(buffer);
QVERIFY(out.bitLength() == 0);
QVERIFY(out.byteLength() == 0);
const BitIStream in;
QVERIFY(in.bitLength() == 0);
QVERIFY(in.streamOffset() == 0);
QVERIFY(in.error() == StreamError::NoError);
}
// Create istream with some data:
{
BitIStream in(bytes, bytes + size);
QVERIFY(in.bitLength() == size * 8);
QVERIFY(in.streamOffset() == 0);
QVERIFY(in.error() == StreamError::NoError);
// 'Read' some data back:
for (int i = 0; i < size; ++i) {
uchar bitPattern = 0;
const auto bitsRead = in.peekBits(quint64(i * 8), 8, &bitPattern);
QVERIFY(bitsRead == 8);
QVERIFY(bitPattern == bytes[i]);
}
}
// Copy ctors:
{
// Ostreams - copy is disabled.
// Istreams:
const BitIStream in1;
const BitIStream in2(in1);
QVERIFY(in2.bitLength() == in1.bitLength());
QVERIFY(in2.streamOffset() == in1.streamOffset());
QVERIFY(in2.error() == StreamError::NoError);
const BitIStream in3(bytes, bytes + size);
const BitIStream in4(in3);
QVERIFY(in4.bitLength() == in3.bitLength());
QVERIFY(in4.streamOffset() == in3.streamOffset());
QVERIFY(in4.error() == StreamError::NoError);
}
}
void tst_Hpack::bitstreamWrite()
{
// Known representations,
// https://http2.github.io/http2-spec/compression.html.
// 5.1 Integer Representation
// Test bit/byte lengths of the
// resulting data:
std::vector<uchar> buffer;
BitOStream out(buffer);
out.write(3);
// 11, fits into 8-bit prefix:
QVERIFY(out.bitLength() == 8);
QVERIFY(out.byteLength() == 1);
QVERIFY(out.begin()[0] == 3);
out.clear();
QVERIFY(out.bitLength() == 0);
QVERIFY(out.byteLength() == 0);
// This number does not fit into 8-bit
// prefix we'll need 2 bytes:
out.write(256);
QVERIFY(out.byteLength() == 2);
QVERIFY(out.bitLength() == 16);
QVERIFY(out.begin()[0] == 0xff);
QVERIFY(out.begin()[1] == 1);
out.clear();
// See 5.2 String Literal Representation.
// We use Huffman code,
// char 'a' has a prefix code 00011 (5 bits)
out.write(QByteArray("aaa", 3), true);
QVERIFY(out.byteLength() == 3);
QVERIFY(out.bitLength() == 24);
// Now we must have in our stream:
// 10000010 | 00011000| 11000111
const uchar *encoded = out.begin();
QVERIFY(encoded[0] == 0x82);
QVERIFY(encoded[1] == 0x18);
QVERIFY(encoded[2] == 0xC7);
// TODO: add more tests ...
}
void tst_Hpack::bitstreamReadWrite()
{
// We can write into the bit stream:
// 1) bit patterns
// 2) integers (see HPACK, 5.1)
// 3) string (see HPACK, 5.2)
std::vector<uchar> buffer;
BitOStream out(buffer);
out.writeBits(0xf, 3);
QVERIFY(out.byteLength() == 1);
QVERIFY(out.bitLength() == 3);
// Now, read it back:
{
BitIStream in(out.begin(), out.end());
uchar bitPattern = 0;
const auto bitsRead = in.peekBits(0, 3, &bitPattern);
// peekBits pack into the most significant byte/bit:
QVERIFY(bitsRead == 3);
QVERIFY((bitPattern >> 5) == 7);
}
const quint32 testInt = 133;
out.write(testInt);
// This integer does not fit into the current 5-bit prefix,
// so byteLength == 2.
QVERIFY(out.byteLength() == 2);
const auto bitLength = out.bitLength();
QVERIFY(bitLength > 3);
// Now, read it back:
{
BitIStream in(out.begin(), out.end());
in.skipBits(3); // Bit pattern
quint32 value = 0;
QVERIFY(in.read(&value));
QVERIFY(in.error() == StreamError::NoError);
QCOMPARE(value, testInt);
}
const QByteArray testString("ABCDE", 5);
out.write(testString, true); // Compressed
out.write(testString, false); // Non-compressed
QVERIFY(out.byteLength() > 2);
QVERIFY(out.bitLength() > bitLength);
// Now, read it back:
{
BitIStream in(out.begin(), out.end());
in.skipBits(bitLength); // Bit pattern and integer
QByteArray value;
// Read compressed string first ...
QVERIFY(in.read(&value));
QCOMPARE(value, testString);
QCOMPARE(in.error(), StreamError::NoError);
// Now non-compressed ...
QVERIFY(in.read(&value));
QCOMPARE(value, testString);
QCOMPARE(in.error(), StreamError::NoError);
}
}
void tst_Hpack::bitstreamCompression()
{
// Similar to bitstreamReadWrite but
// writes/reads a lot of mixed strings/integers.
std::vector<std::string> strings;
std::vector<quint32> integers;
std::vector<bool> isA; // integer or string.
const std::string bytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()[]/*");
const unsigned nValues = 100000;
quint64 totalStringBytes = 0;
std::vector<uchar> buffer;
BitOStream out(buffer);
for (unsigned i = 0; i < nValues; ++i) {
const bool isString = QRandomGenerator::global()->bounded(1000) > 500;
isA.push_back(isString);
if (!isString) {
integers.push_back(QRandomGenerator::global()->bounded(1000u));
out.write(integers.back());
} else {
const auto start = QRandomGenerator::global()->bounded(uint(bytes.length()) / 2);
auto end = start * 2;
if (!end)
end = unsigned(bytes.length() / 2);
strings.push_back(bytes.substr(start, end - start));
const auto &s = strings.back();
totalStringBytes += s.size();
QByteArray data(s.c_str(), int(s.size()));
const bool compressed(QRandomGenerator::global()->bounded(1000) > 500);
out.write(data, compressed);
}
}
qDebug() << "Compressed(?) byte length:" << out.byteLength()
<< "total string bytes:" << totalStringBytes;
qDebug() << "total integer bytes (for quint32):" << integers.size() * sizeof(quint32);
QVERIFY(out.byteLength() > 0);
QVERIFY(out.bitLength() > 0);
BitIStream in(out.begin(), out.end());
for (unsigned i = 0, iS = 0, iI = 0; i < nValues; ++i) {
if (isA[i]) {
QByteArray data;
QVERIFY(in.read(&data));
QCOMPARE(in.error(), StreamError::NoError);
QCOMPARE(data.toStdString(), strings[iS]);
++iS;
} else {
quint32 value = 0;
QVERIFY(in.read(&value));
QCOMPARE(in.error(), StreamError::NoError);
QCOMPARE(value, integers[iI]);
++iI;
}
}
}
void tst_Hpack::bitstreamErrors()
{
{
BitIStream in;
quint32 val = 0;
QVERIFY(!in.read(&val));
QCOMPARE(in.error(), StreamError::NotEnoughData);
}
{
// Integer in a stream, that does not fit into quint32.
const uchar bytes[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
BitIStream in(bytes, bytes + sizeof bytes);
quint32 val = 0;
QVERIFY(!in.read(&val));
QCOMPARE(in.error(), StreamError::InvalidInteger);
}
{
const uchar byte = 0x82; // 1 - Huffman compressed, 2 - the (fake) byte length.
BitIStream in(&byte, &byte + 1);
QByteArray val;
QVERIFY(!in.read(&val));
QCOMPARE(in.error(), StreamError::NotEnoughData);
}
}
void tst_Hpack::lookupTableConstructor()
{
{
FieldLookupTable nonIndexed(4096, false);
QVERIFY(nonIndexed.dynamicDataSize() == 0);
QVERIFY(nonIndexed.numberOfDynamicEntries() == 0);
QVERIFY(nonIndexed.numberOfStaticEntries() != 0);
QVERIFY(nonIndexed.numberOfStaticEntries() == nonIndexed.numberOfEntries());
// Now we add some fake field and verify what 'non-indexed' means ... no search
// by name.
QVERIFY(nonIndexed.prependField("custom-key", "custom-value"));
// 54: 10 + 12 in name/value pair above + 32 required by HPACK specs ...
QVERIFY(nonIndexed.dynamicDataSize() == 54);
QVERIFY(nonIndexed.numberOfDynamicEntries() == 1);
QCOMPARE(nonIndexed.numberOfEntries(), nonIndexed.numberOfStaticEntries() + 1);
// Should fail to find it (invalid index 0) - search is disabled.
QVERIFY(nonIndexed.indexOf("custom-key", "custom-value") == 0);
}
{
// "key" + "value" == 8 bytes, + 32 (HPACK's requirement) == 40.
// Let's ask for a max-size 32 so that entry does not fit:
FieldLookupTable nonIndexed(32, false);
QVERIFY(nonIndexed.prependField("key", "value"));
QVERIFY(nonIndexed.numberOfEntries() == nonIndexed.numberOfStaticEntries());
QVERIFY(nonIndexed.indexOf("key", "value") == 0);
}
{
FieldLookupTable indexed(4096, true);
QVERIFY(indexed.dynamicDataSize() == 0);
QVERIFY(indexed.numberOfDynamicEntries() == 0);
QVERIFY(indexed.numberOfStaticEntries() != 0);
QVERIFY(indexed.numberOfStaticEntries() == indexed.numberOfEntries());
QVERIFY(indexed.prependField("custom-key", "custom-value"));
QVERIFY(indexed.dynamicDataSize() == 54);
QVERIFY(indexed.numberOfDynamicEntries() == 1);
QVERIFY(indexed.numberOfEntries() == indexed.numberOfStaticEntries() + 1);
QVERIFY(indexed.indexOf("custom-key") == indexed.numberOfStaticEntries() + 1);
QVERIFY(indexed.indexOf("custom-key", "custom-value") == indexed.numberOfStaticEntries() + 1);
}
}
void tst_Hpack::lookupTableStatic()
{
const FieldLookupTable table(0, false /*all static, no need in 'search index'*/);
const auto &staticTable = FieldLookupTable::staticPart();
QByteArray name, value;
quint32 currentIndex = 1; // HPACK is indexing starting from 1.
for (const HeaderField &field : staticTable) {
const quint32 index = table.indexOf(field.name, field.value);
QVERIFY(index != 0);
QCOMPARE(index, currentIndex);
QVERIFY(table.field(index, &name, &value));
QCOMPARE(name, field.name);
QCOMPARE(value, field.value);
++currentIndex;
}
}
void tst_Hpack::lookupTableDynamic()
{
// HPACK's table size:
// for every field -> size += field.name.length() + field.value.length() + 32.
// Let's set some size limit and try to fill table with enough entries to have several
// items evicted.
const quint32 tableSize = 8192;
const char stringData[] = "abcdefghijklmnopABCDEFGHIJKLMNOP0123456789()[]:";
const quint32 dataSize = sizeof stringData - 1;
FieldLookupTable table(tableSize, true);
std::vector<QByteArray> fieldsToFind;
quint32 evicted = 0;
while (true) {
// Strings are repeating way too often, I want to
// have at least some items really evicted and not found,
// therefore these weird dances with start/len.
const quint32 start = QRandomGenerator::global()->bounded(dataSize - 10);
quint32 len = QRandomGenerator::global()->bounded(dataSize - start);
if (!len)
len = 1;
const QByteArray val(stringData + start, len);
fieldsToFind.push_back(val);
const quint32 entriesBefore = table.numberOfDynamicEntries();
QVERIFY(table.prependField(val, val));
QVERIFY(table.indexOf(val));
QVERIFY(table.indexOf(val) == table.indexOf(val, val));
QByteArray fieldName, fieldValue;
table.field(table.indexOf(val), &fieldName, &fieldValue);
QVERIFY(val == fieldName);
QVERIFY(val == fieldValue);
if (table.numberOfDynamicEntries() <= entriesBefore) {
// We had to evict several items ...
evicted += entriesBefore - table.numberOfDynamicEntries() + 1;
if (evicted >= 200)
break;
}
}
QVERIFY(table.dynamicDataSize() <= tableSize);
QVERIFY(table.numberOfDynamicEntries() > 0);
QVERIFY(table.indexOf(fieldsToFind.back())); // We MUST have it in a table!
using size_type = std::vector<QByteArray>::size_type;
for (size_type i = 0, e = fieldsToFind.size(); i < e; ++i) {
const auto &val = fieldsToFind[i];
const quint32 index = table.indexOf(val);
if (!index) {
QVERIFY(i < size_type(evicted));
} else {
QVERIFY(index == table.indexOf(val, val));
QByteArray fieldName, fieldValue;
QVERIFY(table.field(index, &fieldName, &fieldValue));
QVERIFY(val == fieldName);
QVERIFY(val == fieldValue);
}
}
table.clearDynamicTable();
QVERIFY(table.numberOfDynamicEntries() == 0);
QVERIFY(table.dynamicDataSize() == 0);
QVERIFY(table.indexOf(fieldsToFind.back()) == 0);
QVERIFY(table.prependField("name1", "value1"));
QVERIFY(table.prependField("name2", "value2"));
QVERIFY(table.indexOf("name1") == table.numberOfStaticEntries() + 2);
QVERIFY(table.indexOf("name2", "value2") == table.numberOfStaticEntries() + 1);
QVERIFY(table.indexOf("name1", "value2") == 0);
QVERIFY(table.indexOf("name2", "value1") == 0);
QVERIFY(table.indexOf("name3") == 0);
QVERIFY(!table.indexIsValid(table.numberOfEntries() + 1));
QVERIFY(table.prependField("name1", "value1"));
QVERIFY(table.numberOfDynamicEntries() == 3);
table.evictEntry();
QVERIFY(table.indexOf("name1") != 0);
table.evictEntry();
QVERIFY(table.indexOf("name2") == 0);
QVERIFY(table.indexOf("name1") != 0);
table.evictEntry();
QVERIFY(table.dynamicDataSize() == 0);
QVERIFY(table.numberOfDynamicEntries() == 0);
QVERIFY(table.indexOf("name1") == 0);
}
void tst_Hpack::hpackEncodeRequest_data()
{
QTest::addColumn<bool>("compression");
QTest::newRow("no-string-compression") << false;
QTest::newRow("with-string-compression") << true;
}
void tst_Hpack::hpackEncodeRequest(bool withHuffman)
{
// This function uses examples from HPACK specs
// (see appendix).
Encoder encoder(4096, withHuffman);
// HPACK, C.3.1 First Request
/*
:method: GET
:scheme: http
:path: /
:authority: www.example.com
Hex dump of encoded data (without Huffman):
8286 8441 0f77 7777 2e65 7861 6d70 6c65 | ...A.www.example
2e63 6f6d
Hex dump of encoded data (with Huffman):
8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff
*/
request1.clear();
header1 = {{":method", "GET"},
{":scheme", "http"},
{":path", "/"},
{":authority", "www.example.com"}};
QVERIFY(encoder.encodeRequest(request1, header1));
QVERIFY(encoder.dynamicTableSize() == 57);
// HPACK, C.3.2 Second Request
/*
Header list to encode:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
cache-control: no-cache
Hex dump of encoded data (without Huffman):
8286 84be 5808 6e6f 2d63 6163 6865
Hex dump of encoded data (with Huffman):
8286 84be 5886 a8eb 1064 9cbf
*/
request2.clear();
header2 = {{":method", "GET"},
{":scheme", "http"},
{":path", "/"},
{":authority", "www.example.com"},
{"cache-control", "no-cache"}};
encoder.encodeRequest(request2, header2);
QVERIFY(encoder.dynamicTableSize() == 110);
// HPACK, C.3.3 Third Request
/*
Header list to encode:
:method: GET
:scheme: https
:path: /index.html
:authority: www.example.com
custom-key: custom-value
Hex dump of encoded data (without Huffman):
8287 85bf 400a 6375 7374 6f6d 2d6b 6579
0c63 7573 746f 6d2d 7661 6c75 65
Hex dump of encoded data (with Huffman):
8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
a849 e95b b8e8 b4bf
*/
request3.clear();
header3 = {{":method", "GET"},
{":scheme", "https"},
{":path", "/index.html"},
{":authority", "www.example.com"},
{"custom-key", "custom-value"}};
encoder.encodeRequest(request3, header3);
QVERIFY(encoder.dynamicTableSize() == 164);
}
void tst_Hpack::hpackEncodeRequest()
{
QFETCH(bool, compression);
hpackEncodeRequest(compression);
// See comments above about these hex dumps ...
const uchar bytes1NH[] = {0x82, 0x86, 0x84, 0x41,
0x0f, 0x77, 0x77, 0x77,
0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65,
0x2e, 0x63, 0x6f, 0x6d};
const uchar bytes1WH[] = {0x82, 0x86, 0x84, 0x41,
0x8c, 0xf1, 0xe3, 0xc2,
0xe5, 0xf2, 0x3a, 0x6b,
0xa0, 0xab, 0x90, 0xf4,
0xff};
const uchar *hexDump1 = compression ? bytes1WH : bytes1NH;
const quint64 byteLength1 = compression ? sizeof bytes1WH : sizeof bytes1NH;
QCOMPARE(request1.byteLength(), byteLength1);
QCOMPARE(request1.bitLength(), byteLength1 * 8);
for (quint32 i = 0, e = request1.byteLength(); i < e; ++i)
QCOMPARE(hexDump1[i], request1.begin()[i]);
const uchar bytes2NH[] = {0x82, 0x86, 0x84, 0xbe,
0x58, 0x08, 0x6e, 0x6f,
0x2d, 0x63, 0x61, 0x63,
0x68, 0x65};
const uchar bytes2WH[] = {0x82, 0x86, 0x84, 0xbe,
0x58, 0x86, 0xa8, 0xeb,
0x10, 0x64, 0x9c, 0xbf};
const uchar *hexDump2 = compression ? bytes2WH : bytes2NH;
const auto byteLength2 = compression ? sizeof bytes2WH : sizeof bytes2NH;
QVERIFY(request2.byteLength() == byteLength2);
QVERIFY(request2.bitLength() == byteLength2 * 8);
for (quint32 i = 0, e = request2.byteLength(); i < e; ++i)
QCOMPARE(hexDump2[i], request2.begin()[i]);
const uchar bytes3NH[] = {0x82, 0x87, 0x85, 0xbf,
0x40, 0x0a, 0x63, 0x75,
0x73, 0x74, 0x6f, 0x6d,
0x2d, 0x6b, 0x65, 0x79,
0x0c, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x2d,
0x76, 0x61, 0x6c, 0x75,
0x65};
const uchar bytes3WH[] = {0x82, 0x87, 0x85, 0xbf,
0x40, 0x88, 0x25, 0xa8,
0x49, 0xe9, 0x5b, 0xa9,
0x7d, 0x7f, 0x89, 0x25,
0xa8, 0x49, 0xe9, 0x5b,
0xb8, 0xe8, 0xb4, 0xbf};
const uchar *hexDump3 = compression ? bytes3WH : bytes3NH;
const quint64 byteLength3 = compression ? sizeof bytes3WH : sizeof bytes3NH;
QCOMPARE(request3.byteLength(), byteLength3);
QCOMPARE(request3.bitLength(), byteLength3 * 8);
for (quint32 i = 0, e = request3.byteLength(); i < e; ++i)
QCOMPARE(hexDump3[i], request3.begin()[i]);
}
void tst_Hpack::hpackDecodeRequest_data()
{
QTest::addColumn<bool>("compression");
QTest::newRow("no-string-compression") << false;
QTest::newRow("with-string-compression") << true;
}
void tst_Hpack::hpackDecodeRequest()
{
QFETCH(bool, compression);
hpackEncodeRequest(compression);
QVERIFY(request1.byteLength());
QVERIFY(request2.byteLength());
QVERIFY(request3.byteLength());
Decoder decoder(4096);
BitIStream inputStream1(request1.begin(), request1.end());
QVERIFY(decoder.decodeHeaderFields(inputStream1));
QCOMPARE(decoder.dynamicTableSize(), quint32(57));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header1);
}
BitIStream inputStream2{request2.begin(), request2.end()};
QVERIFY(decoder.decodeHeaderFields(inputStream2));
QCOMPARE(decoder.dynamicTableSize(), quint32(110));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header2);
}
BitIStream inputStream3(request3.begin(), request3.end());
QVERIFY(decoder.decodeHeaderFields(inputStream3));
QCOMPARE(decoder.dynamicTableSize(), quint32(164));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header3);
}
}
void tst_Hpack::hpackEncodeResponse_data()
{
hpackEncodeRequest_data();
}
void tst_Hpack::hpackEncodeResponse()
{
QFETCH(bool, compression);
hpackEncodeResponse(compression);
// TODO: we can also test bytes - using hex dumps from HPACK's specs,
// for now only test a table behavior/expected sizes.
}
void tst_Hpack::hpackEncodeResponse(bool withCompression)
{
Encoder encoder(256, withCompression); // 256 - this will result in entries evicted.
// HPACK, C.5.1 First Response
/*
Header list to encode:
:status: 302
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
*/
request1.clear();
header1 = {{":status", "302"},
{"cache-control", "private"},
{"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
{"location", "https://www.example.com"}};
QVERIFY(encoder.encodeResponse(request1, header1));
QCOMPARE(encoder.dynamicTableSize(), quint32(222));
// HPACK, C.5.2 Second Response
/*
The (":status", "302") header field is evicted from the dynamic
table to free space to allow adding the (":status", "307") header field.
Header list to encode:
:status: 307
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
*/
request2.clear();
header2 = {{":status", "307"},
{"cache-control", "private"},
{"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
{"location", "https://www.example.com"}};
QVERIFY(encoder.encodeResponse(request2, header2));
QCOMPARE(encoder.dynamicTableSize(), quint32(222));
// HPACK, C.5.3 Third Response
/*
Several header fields are evicted from the dynamic table
during the processing of this header list.
Header list to encode:
:status: 200
cache-control: private
date: Mon, 21 Oct 2013 20:13:22 GMT
location: https://www.example.com
content-encoding: gzip
set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
*/
request3.clear();
header3 = {{":status", "200"},
{"cache-control", "private"},
{"date", "Mon, 21 Oct 2013 20:13:22 GMT"},
{"location", "https://www.example.com"},
{"content-encoding", "gzip"},
{"set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}};
QVERIFY(encoder.encodeResponse(request3, header3));
QCOMPARE(encoder.dynamicTableSize(), quint32(215));
}
void tst_Hpack::hpackDecodeResponse_data()
{
hpackEncodeRequest_data();
}
void tst_Hpack::hpackDecodeResponse()
{
QFETCH(bool, compression);
hpackEncodeResponse(compression);
QVERIFY(request1.byteLength());
Decoder decoder(256); // This size will result in entries evicted.
BitIStream inputStream1(request1.begin(), request1.end());
QVERIFY(decoder.decodeHeaderFields(inputStream1));
QCOMPARE(decoder.dynamicTableSize(), quint32(222));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header1);
}
QVERIFY(request2.byteLength());
BitIStream inputStream2(request2.begin(), request2.end());
QVERIFY(decoder.decodeHeaderFields(inputStream2));
QCOMPARE(decoder.dynamicTableSize(), quint32(222));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header2);
}
QVERIFY(request3.byteLength());
BitIStream inputStream3(request3.begin(), request3.end());
QVERIFY(decoder.decodeHeaderFields(inputStream3));
QCOMPARE(decoder.dynamicTableSize(), quint32(215));
{
const auto &decoded = decoder.decodedHeader();
QVERIFY(decoded == header3);
}
}
QTEST_MAIN(tst_Hpack)
#include "tst_hpack.moc"

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qhsts Test:
#####################################################################
qt_internal_add_test(tst_qhsts
SOURCES
tst_qhsts.cpp
LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
)
# TEMPLATE = "app"

View File

@ -0,0 +1,371 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtCore/qdatetime.h>
#include <QtCore/qdir.h>
#include <QtCore/qlist.h>
#include <QtCore/qpair.h>
#include <QtCore/qurl.h>
#include <QtNetwork/private/qhstsstore_p.h>
#include <QtNetwork/private/qhsts_p.h>
QT_USE_NAMESPACE
class tst_QHsts : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testSingleKnownHost_data();
void testSingleKnownHost();
void testMultilpeKnownHosts();
void testPolicyExpiration();
void testSTSHeaderParser();
void testStore();
};
void tst_QHsts::testSingleKnownHost_data()
{
QTest::addColumn<QUrl>("knownHost");
QTest::addColumn<QDateTime>("policyExpires");
QTest::addColumn<bool>("includeSubDomains");
QTest::addColumn<QUrl>("hostToTest");
QTest::addColumn<bool>("isKnown");
const QDateTime currentUTC = QDateTime::currentDateTimeUtc();
const QUrl knownHost(QLatin1String("http://example.com"));
const QUrl validSubdomain(QLatin1String("https://sub.example.com/ohoho"));
const QUrl unknownDomain(QLatin1String("http://example.org"));
const QUrl subSubdomain(QLatin1String("https://level3.level2.example.com"));
const QDateTime validDate(currentUTC.addSecs(1000));
QTest::newRow("same-known") << knownHost << validDate << false << knownHost << true;
QTest::newRow("subexcluded") << knownHost << validDate << false << validSubdomain << false;
QTest::newRow("subincluded") << knownHost << validDate << true << validSubdomain << true;
QTest::newRow("unknown-subexcluded") << knownHost << validDate << false << unknownDomain << false;
QTest::newRow("unknown-subincluded") << knownHost << validDate << true << unknownDomain << false;
QTest::newRow("sub-subdomain-subincluded") << knownHost << validDate << true << subSubdomain << true;
QTest::newRow("sub-subdomain-subexcluded") << knownHost << validDate << false << subSubdomain << false;
const QDateTime invalidDate;
QTest::newRow("invalid-time") << knownHost << invalidDate << false << knownHost << false;
QTest::newRow("invalid-time-subexcluded") << knownHost << invalidDate << false
<< validSubdomain << false;
QTest::newRow("invalid-time-subincluded") << knownHost << invalidDate << true
<< validSubdomain << false;
const QDateTime expiredDate(currentUTC.addSecs(-1000));
QTest::newRow("expired-time") << knownHost << expiredDate << false << knownHost << false;
QTest::newRow("expired-time-subexcluded") << knownHost << expiredDate << false
<< validSubdomain << false;
QTest::newRow("expired-time-subincluded") << knownHost << expiredDate << true
<< validSubdomain << false;
const QUrl ipAsHost(QLatin1String("http://127.0.0.1"));
QTest::newRow("ip-address-in-hostname") << ipAsHost << validDate << false
<< ipAsHost << false;
const QUrl anyIPv4AsHost(QLatin1String("http://0.0.0.0"));
QTest::newRow("anyip4-address-in-hostname") << anyIPv4AsHost << validDate
<< false << anyIPv4AsHost << false;
const QUrl anyIPv6AsHost(QLatin1String("http://[::]"));
QTest::newRow("anyip6-address-in-hostname") << anyIPv6AsHost << validDate
<< false << anyIPv6AsHost << false;
}
void tst_QHsts::testSingleKnownHost()
{
QFETCH(const QUrl, knownHost);
QFETCH(const QDateTime, policyExpires);
QFETCH(const bool, includeSubDomains);
QFETCH(const QUrl, hostToTest);
QFETCH(const bool, isKnown);
QHstsCache cache;
cache.updateKnownHost(knownHost, policyExpires, includeSubDomains);
QCOMPARE(cache.isKnownHost(hostToTest), isKnown);
}
void tst_QHsts::testMultilpeKnownHosts()
{
const QDateTime currentUTC = QDateTime::currentDateTimeUtc();
const QDateTime validDate(currentUTC.addSecs(10000));
const QDateTime expiredDate(currentUTC.addSecs(-10000));
const QUrl exampleCom(QLatin1String("https://example.com"));
const QUrl subExampleCom(QLatin1String("https://sub.example.com"));
QHstsCache cache;
// example.com is HSTS and includes subdomains:
cache.updateKnownHost(exampleCom, validDate, true);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subExampleCom));
// example.com can set its policy not to include subdomains:
cache.updateKnownHost(exampleCom, validDate, false);
QVERIFY(!cache.isKnownHost(subExampleCom));
// but sub.example.com can set its own policy:
cache.updateKnownHost(subExampleCom, validDate, false);
QVERIFY(cache.isKnownHost(subExampleCom));
// let's say example.com's policy has expired:
cache.updateKnownHost(exampleCom, expiredDate, false);
QVERIFY(!cache.isKnownHost(exampleCom));
// it should not affect sub.example.com's policy:
QVERIFY(cache.isKnownHost(subExampleCom));
// clear cache and invalidate all policies:
cache.clear();
QVERIFY(!cache.isKnownHost(exampleCom));
QVERIFY(!cache.isKnownHost(subExampleCom));
// siblings:
const QUrl anotherSub(QLatin1String("https://sub2.example.com"));
cache.updateKnownHost(subExampleCom, validDate, true);
cache.updateKnownHost(anotherSub, validDate, true);
QVERIFY(cache.isKnownHost(subExampleCom));
QVERIFY(cache.isKnownHost(anotherSub));
// they cannot set superdomain's policy:
QVERIFY(!cache.isKnownHost(exampleCom));
// a sibling cannot set another sibling's policy:
cache.updateKnownHost(anotherSub, expiredDate, false);
QVERIFY(cache.isKnownHost(subExampleCom));
QVERIFY(!cache.isKnownHost(anotherSub));
QVERIFY(!cache.isKnownHost(exampleCom));
// let's make example.com known again:
cache.updateKnownHost(exampleCom, validDate, true);
// a subdomain cannot affect its superdomain's policy:
cache.updateKnownHost(subExampleCom, expiredDate, true);
QVERIFY(cache.isKnownHost(exampleCom));
// and this superdomain includes subdomains in its HSTS policy:
QVERIFY(cache.isKnownHost(subExampleCom));
QVERIFY(cache.isKnownHost(anotherSub));
// a subdomain (with its subdomains) cannot affect its superdomain's policy:
cache.updateKnownHost(exampleCom, expiredDate, true);
cache.updateKnownHost(subExampleCom, validDate, true);
QVERIFY(cache.isKnownHost(subExampleCom));
QVERIFY(!cache.isKnownHost(exampleCom));
}
void tst_QHsts::testPolicyExpiration()
{
QDateTime currentUTC = QDateTime::currentDateTimeUtc();
const QUrl exampleCom(QLatin1String("http://example.com"));
const QUrl subdomain(QLatin1String("http://subdomain.example.com"));
const qint64 lifeTimeMS = 50;
QHstsCache cache;
// start with 'includeSubDomains' and 5 s. lifetime:
cache.updateKnownHost(exampleCom, currentUTC.addMSecs(lifeTimeMS), true);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subdomain));
// wait for approx. a half of lifetime:
QTest::qWait(lifeTimeMS / 2);
if (QDateTime::currentDateTimeUtc() < currentUTC.addMSecs(lifeTimeMS)) {
// Should still be valid:
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subdomain));
}
QTest::qWait(lifeTimeMS);
// expired:
QVERIFY(!cache.isKnownHost(exampleCom));
QVERIFY(!cache.isKnownHost(subdomain));
// now check that superdomain's policy expires, but not subdomain's policy:
currentUTC = QDateTime::currentDateTimeUtc();
cache.updateKnownHost(exampleCom, currentUTC.addMSecs(lifeTimeMS / 5), true);
cache.updateKnownHost(subdomain, currentUTC.addMSecs(lifeTimeMS), true);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subdomain));
QTest::qWait(lifeTimeMS / 2);
if (QDateTime::currentDateTimeUtc() < currentUTC.addMSecs(lifeTimeMS)) {
QVERIFY(!cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subdomain));
}
}
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));
QVERIFY(!parser.includeSubDomains());
QVERIFY(!parser.expirationDate().isValid());
list << Header("Strict-Transport-security", "200");
QVERIFY(!parser.parse(list));
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));
QVERIFY(!parser.includeSubDomains());
QVERIFY(!parser.expirationDate().isValid());
list.pop_back();
list << Header("Strict-Transport-Security", "includeSubDomains;max-age=1000");
QVERIFY(parser.parse(list));
QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc());
QVERIFY(parser.includeSubDomains());
list.pop_back();
list << Header("strict-transport-security", "includeSubDomains;max-age=1000");
QVERIFY(parser.parse(list));
QVERIFY(parser.expirationDate() > QDateTime::currentDateTimeUtc());
QVERIFY(parser.includeSubDomains());
list.pop_back();
// Invalid (includeSubDomains twice):
list << Header("Strict-Transport-Security", "max-age = 1000 ; includeSubDomains;includeSubDomains");
QVERIFY(!parser.parse(list));
QVERIFY(!parser.includeSubDomains());
QVERIFY(!parser.expirationDate().isValid());
list.pop_back();
// Invalid (weird number of seconds):
list << Header("Strict-Transport-Security", "max-age=-1000 ; includeSubDomains");
QVERIFY(!parser.parse(list));
QVERIFY(!parser.includeSubDomains());
QVERIFY(!parser.expirationDate().isValid());
list.pop_back();
// Note, directives are case-insensitive + we should ignore unknown directive.
list << Header("Strict-Transport-Security", ";max-age=1000 ;includesubdomains;;"
"nowsomeunknownheader=\"somevaluewithescapes\\;\"");
QVERIFY(parser.parse(list));
QVERIFY(parser.includeSubDomains());
QVERIFY(parser.expirationDate().isValid());
list.pop_back();
// Check that we know how to unescape max-age:
list << Header("Strict-Transport-Security", "max-age=\"1000\"");
QVERIFY(parser.parse(list));
QVERIFY(!parser.includeSubDomains());
QVERIFY(parser.expirationDate().isValid());
list.pop_back();
// 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));
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));
QVERIFY(!parser.includeSubDomains());
QVERIFY(parser.expirationDate().isValid());
list.clear();
list << Header("Strict-Transport-Security", "max-age=0");
QVERIFY(parser.parse(list));
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));
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; ; ; ; ;;; ;;"
";;; ; includeSubdomains ;;thisIsUnknownDirective;;;;");
QVERIFY(parser.parse(list));
QVERIFY(parser.includeSubDomains());
QVERIFY(parser.expirationDate().isValid());
list.pop_back();
list << Header("Strict-Transport-Security", "max-age=1000; includeSubDomains bogon");
QVERIFY(!parser.parse(list));
QVERIFY(!parser.includeSubDomains());
QVERIFY(!parser.expirationDate().isValid());
}
const QLatin1String storeDir(".");
struct TestStoreDeleter
{
~TestStoreDeleter()
{
QDir cwd;
if (!cwd.remove(QHstsStore::absoluteFilePath(storeDir)))
qWarning() << "tst_QHsts::testStore: failed to remove the hsts store file";
}
};
void tst_QHsts::testStore()
{
// Delete the store's file after we finish the test.
TestStoreDeleter cleaner;
const QUrl exampleCom(QStringLiteral("http://example.com"));
const QUrl subDomain(QStringLiteral("http://subdomain.example.com"));
const QDateTime validDate(QDateTime::currentDateTimeUtc().addDays(1));
{
// We start from an empty cache and empty store:
QHstsCache cache;
QHstsStore store(storeDir);
cache.setStore(&store);
QVERIFY(!cache.isKnownHost(exampleCom));
QVERIFY(!cache.isKnownHost(subDomain));
// (1) This will also store the policy:
cache.updateKnownHost(exampleCom, validDate, true);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subDomain));
}
{
// Test the policy stored at (1):
QHstsCache cache;
QHstsStore store(storeDir);
cache.setStore(&store);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(cache.isKnownHost(subDomain));
// (2) Remove subdomains:
cache.updateKnownHost(exampleCom, validDate, false);
QVERIFY(!cache.isKnownHost(subDomain));
}
{
// Test the previous update (2):
QHstsCache cache;
QHstsStore store(storeDir);
cache.setStore(&store);
QVERIFY(cache.isKnownHost(exampleCom));
QVERIFY(!cache.isKnownHost(subDomain));
}
{
QHstsCache cache;
cache.updateKnownHost(subDomain, validDate, false);
QVERIFY(cache.isKnownHost(subDomain));
QHstsStore store(storeDir);
// (3) This should store policy from cache, over old policy from store:
cache.setStore(&store);
}
{
// Test that (3) was stored:
QHstsCache cache;
QHstsStore store(storeDir);
cache.setStore(&store);
QVERIFY(cache.isKnownHost(subDomain));
}
}
QTEST_MAIN(tst_QHsts)
#include "tst_qhsts.moc"

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_http2 Test:
#####################################################################
qt_internal_add_test(tst_http2
SOURCES
http2srv.cpp http2srv.h
tst_http2.cpp
LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
Qt::TestPrivate
)

View File

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIUfo9amJtJGWqWE6f+SkAO85zkGr4wDQYJKoZIhvcNAQEL
BQAwgYMxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARPc2xvMQ0wCwYDVQQHDARPc2xv
MRcwFQYDVQQKDA5UaGUgUXQgQ29tcGFueTEMMAoGA1UECwwDUiZEMRIwEAYDVQQD
DAlIMiBUZXN0ZXIxGzAZBgkqhkiG9w0BCQEWDG1pbmltaUBxdC5pbzAgFw0yMDEw
MjYxMjAxMzFaGA8yMTIwMTAwMjEyMDEzMVowgYMxCzAJBgNVBAYTAk5PMQ0wCwYD
VQQIDARPc2xvMQ0wCwYDVQQHDARPc2xvMRcwFQYDVQQKDA5UaGUgUXQgQ29tcGFu
eTEMMAoGA1UECwwDUiZEMRIwEAYDVQQDDAlIMiBUZXN0ZXIxGzAZBgkqhkiG9w0B
CQEWDG1pbmltaUBxdC5pbzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AOiUp5+E4blouKH7q+rVNR8NoYX2XkBW+q+rpy1zu5ssRSzbqxAjDx9dkht7Qlnf
VlDT00JvpOWdeuPon5915edQRsY4Unl6mKH29ra3OtUa1/yCJXsGVJTKCj7k4Bxb
5mZzb/fTlZntMLdTIBMfUbw62FKir1WjKIcJ9fCoG8JaGeKVO4Rh5p0ezd4UUUId
r1BXl5Nqdqy2vTMsEDnjOsD3egkv8I2SKN4O6n/C3wWYpMOWYZkGoZiKz7rJs/i/
ez7bsV7JlwdzTlhpJzkcOSVFBP6JlEOxTNNxZ1wtKy7PtZGmsSSATq2e6+bw38Ae
Op0XnzzqcGjtDDofBmT7OFzZWjS9VZS6+DOOe2QHWle1nCHcHyH4ku6IRlsr9xkR
NAIlOfnvHHxqJUenoeaZ4oQDjCBKS1KXygJO/tL7BLTQVn/xK1EmPvKNnjzWk4tR
PnibUhhs5635qpOU/YPqFBh1JjVruZbsWcDAhRcew0uxONXOa9E+4lttQ9ySYa1A
LvWqJuAX7gu2BsBMLyqfm811YnA7CIFMyO+HlqmkLFfv5L/xIRAXR7l26YGO0VwX
CGjMfz4NVPMMke4nB7qa9NkpXQBQKMms3Qzd5JW0Hy9Ruj5O8GPcFZmV0twjd1uJ
PD/cAjkWLaXjdNsJ16QWc2nghQRS6HYqKRX6j+CXOxupAgMBAAGjUzBRMB0GA1Ud
DgQWBBRSCOU58j9NJZkMamt623qyCrhN3TAfBgNVHSMEGDAWgBRSCOU58j9NJZkM
amt623qyCrhN3TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCq
q4jxsWeNDv5Nq14hJtF9HB+ZL64zcZtRjJP1YgNs0QppKICmjPOL2nIMGmI/jKrs
0eGAL/9XXNVHPxm1OPOncvimMMmU6emZfpMdEtTfKP43+Pg9HgKRjLoQp406vGeQ
8ki/mbBhrItVPgEm3tu2AFA02XTYi+YxCI9kRZLGkM3FbgtOuTLPl0Z9y+kiPc9F
uCSC03anBEqv+vDSI8+wODymQ/IJ3Jyz1lxIRDfp4qAekmy0jU2c91VOHHEmOmqq
kqygGFRdwbe99m9yP63r6q0b5K3X2UnJ6bns0hmTwThYwpVPXLU8jdaTddbMukN2
/Ef96Tsw8nWOEOPMySHOTIPgwyZRp26b0kA9EmhLwOP401SxXVQCmSRmtwNagmtg
jJKmZoYBN+//D45ibK8z6Q0oOm9P+Whf/uUXehcRxBxyV3xz7k0wKGQbHj/ddwcy
IUoIN4lrAlib+lK170kTKN352PDmrpo2gmIzPEsfurKAIMSelDl6H+kih16BtZ8y
Nz6fh9Soqrg3OSAware8pxV7k51crBMoPLN78KoRV8MFCK4K7Fddq4rRISq6hiXq
r1nsjoEPuKM9huprmZVZe9t5YcDa2I+wb3IiE3uwpZbAdaLDyQ5n6F/qpsiIkZXn
gtcF7oqpG5oYrwCcZ53y/ezUgUg7PlSz2XwAGvQtgg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDolKefhOG5aLih
+6vq1TUfDaGF9l5AVvqvq6ctc7ubLEUs26sQIw8fXZIbe0JZ31ZQ09NCb6TlnXrj
6J+fdeXnUEbGOFJ5epih9va2tzrVGtf8giV7BlSUygo+5OAcW+Zmc2/305WZ7TC3
UyATH1G8OthSoq9VoyiHCfXwqBvCWhnilTuEYeadHs3eFFFCHa9QV5eTanastr0z
LBA54zrA93oJL/CNkijeDup/wt8FmKTDlmGZBqGYis+6ybP4v3s+27FeyZcHc05Y
aSc5HDklRQT+iZRDsUzTcWdcLSsuz7WRprEkgE6tnuvm8N/AHjqdF5886nBo7Qw6
HwZk+zhc2Vo0vVWUuvgzjntkB1pXtZwh3B8h+JLuiEZbK/cZETQCJTn57xx8aiVH
p6HmmeKEA4wgSktSl8oCTv7S+wS00FZ/8StRJj7yjZ481pOLUT54m1IYbOet+aqT
lP2D6hQYdSY1a7mW7FnAwIUXHsNLsTjVzmvRPuJbbUPckmGtQC71qibgF+4LtgbA
TC8qn5vNdWJwOwiBTMjvh5appCxX7+S/8SEQF0e5dumBjtFcFwhozH8+DVTzDJHu
Jwe6mvTZKV0AUCjJrN0M3eSVtB8vUbo+TvBj3BWZldLcI3dbiTw/3AI5Fi2l43Tb
CdekFnNp4IUEUuh2KikV+o/glzsbqQIDAQABAoICAFw1q6tr5I48vY7DF+rXsuLn
5ZUWE1IQ6fzB4lr72nJv/9EEGnMgYzt9PpMUsD6vdCpBgS2C0+6RHArFzJtNA+RM
iHLIG7K7702veyr/xBx/MwiSlMeMv/XpkFxVI6E6skMGG2s3AMXxKvJTy5CpRx+I
eQFyLG+Ya1X2lgJes/q+/CpAHkOjCOpcLySQC5NZ74q734V7nSdmn+Zs3tYEh+O/
eiuwTP/j5b38Te5vVTqDxTciJPmljmXLCwa0N100lWlbcpvw8qbqiTI2Jm3XCbUE
AzHjW9vmrF3cRS1fXxKFGShw3SRqlkbxjfeWoi8qDPUBS4m8LOr8qG9Wo5Nfon0z
zLP4bci3zHDvVcaaZrrsUBs/yZbg+Dgka1DmX7ekmeccr2yTdKDFgPupYUyxVbTl
a9ZLJysjFD7rgBv1ZclHonLp6Vbm+ZoTqvteo4ikAy6L9RtBWJ23XEK34PkP/+c5
2vWZaOrnjSeBHbFce8cdJSxqWpP+eSCI5I9XbDrYFIsQ/gqKgtzDKy2ihJ2Y8STL
yO4hyFPFjxc+Gg4/P2PpmT5CY2ty44M0BWs+JGW96CJPrrplf2lmQUQJj5LZY66X
Z/4C9L7ZYtKZ+bs5SvU46yWugAvQZX22Xm9xLXWyVXRdx3bj+3M3fDnF9di/zdbh
CgLx7oWPNrXc7FCajnn9AoIBAQD5FMYwRpw9NWT9WDxQwx+cSI4Icbd88ByTW63S
LzeRwZA0J9/SfwO+aBRupzc9GkGXCiZcGMw3AGsCtig8yFlw8E5KnzN7KlftDMnM
9NUxxzlR8VwKyLnZfG7sDTl057ZlUujnqhmt/F8F7dIy7FVO1dE/8nngA+FYTCOG
UZdGjwyBDlDM0JJdUWGY3xslutcpCDN5mzSTKjy9drMvImAshRawxRF6WBpn7vr2
nC6vciqfx1Mzx1vyk0Jm0ilaydDdLMADjt/iL4Nkr0BEs4k+UzQiKDwp8gu7abQ1
eBfxd9Iar4htQa2I1Ewl6P01G/q+ZYwgHhJ9RVn4AxQXefILAoIBAQDvCouORdQX
C8wsyp7MwXlF/3NQeNN5/+B2mhbxrBOf7PmMCXLnkRWcjwJtzypWFqJ0sqai/2+0
bqbMcjX5maT8stT2shl3zXe/Ejt2e3TBYpc1tyuses8Kb5BMU8hu6tTd3G2CMXpD
dT6DVemJZCTtwj9aBNIxSizvlgMolJnCpzhPnlfHSI6E+g3m/LTTo3HwbjMSw/Uq
irgjOpI2wSBB6LZPSgjvfcYPRyWUk16L4A5uSX0cADnovDFLa5/h0wJvN/OoCSQg
rLCXG5E18EyL5Wc58BCY1ZvxmjG3lQtgPxYu2Jwc36R/y/JKlxW5suER5ZNpbbD4
uOyTt2VxMQ2bAoIBAQC5+MzRFqdo/AjfL5Y5JrbfVTzXCTDa09xCGd16ZU60QTWN
+4ed/r+o1sUKqUcRFB2MzEM/2DQBjQpZB/CbEWvWa1XJWXxypXbowveZU+QqOnmN
uQvj8WLyA3o+PNF9e9QvauwCrHpn8VpxbtPWuaYoKnUFreFZZQxHhPGxRBIS2JOZ
eDrT8ZaWnkCkh1AZp5smQ71LOprSlmKrg4jd1GjCVMxQR5N5KXbtyv0OTCZ/UFqK
2aRBsMPyJgkaBChkZPLRcKwc+/wlQRx1fHQb14DNTApMxoXFO7eOwqmOkpAt9iyl
SBIwoS0UUI5ab88+bBmXNvKcuFdNuQ4nowTJUn9pAoIBADMNkILBXSvS5DeIyuO2
Sp1tkoZUV+5NfPY3sMDK3KIibaW/+t+EOBZo4L7tKQCb8vRzl21mmsfxfgRaPDbj
3r3tv9g0b4YLxxBy52pFscj/soXRai17SS7UZwA2QK+XzgDYbDcLNC6mIsTQG4Gx
dsWk3/zs3KuUSQaehmwrWK+fIUK38c1pLK8v7LoxrLkqxlHwZ04RthHw8KTthH7X
Pnl1J0LF8CSeOyfWLSuPUfkT0GEzptnNHpEbaHfQM6R6eaGhVJPF6AZme4y6YYgg
m2ihhSt1n0XVEWpHYWjxFy3mK2mz75unFC4LM+NEY2p2zuUQoCw7NjnY3QYrfCnx
rRMCggEAXeXsMSLFjjyuoL7iKbAxo52HD/P0fBoy58LyRcwfNVr0lvYan4pYEx+o
KijIh9K16PqXZXKMA9v003B+ulmF8bJ7SddCZ5NGvnFhUTDe4DdTKgp2RuwQ3Bsc
3skPIDbhVETyOLCtys34USHrq8U/0DlGY3eLRfxw9GnbKxSBGa/KEu/qQLPNUo50
7xHZDg7GKeC3kqNJeqKM9rkp0VzIGkEnaD9127LeNDmERDfftxJzFoC/THvUBLfU
6Sus2ZYwRE8VFvKC30Q45t/c54X3IuhYvAuiCuTmyfE4ruyzyOwKzhUkeeLq1APX
g0veFbyfzlJ0q8qzD/iffqqIa2ZSmQ==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,985 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtNetwork/private/http2protocol_p.h>
#include <QtNetwork/private/bitstreams_p.h>
#include "http2srv.h"
#ifndef QT_NO_SSL
#include <QtNetwork/qsslconfiguration.h>
#include <QtNetwork/qsslsocket.h>
#include <QtNetwork/qsslkey.h>
#endif
#include <QtNetwork/qtcpsocket.h>
#include <QtCore/qtimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
#include <QtCore/qfile.h>
#include <cstdlib>
#include <cstring>
#include <limits>
QT_BEGIN_NAMESPACE
using namespace Http2;
using namespace HPack;
namespace
{
inline bool is_valid_client_stream(quint32 streamID)
{
// A valid client stream ID is an odd integer number in the range [1, INT_MAX].
return (streamID & 0x1) && streamID <= quint32(std::numeric_limits<qint32>::max());
}
void fill_push_header(const HttpHeader &originalRequest, HttpHeader &promisedRequest)
{
for (const auto &field : originalRequest) {
if (field.name == QByteArray(":authority") ||
field.name == QByteArray(":scheme")) {
promisedRequest.push_back(field);
}
}
}
}
Http2Server::Http2Server(H2Type type, const RawSettings &ss, const RawSettings &cs)
: connectionType(type),
serverSettings(ss),
expectedClientSettings(cs)
{
#if !QT_CONFIG(ssl)
Q_ASSERT(type != H2Type::h2Alpn && type != H2Type::h2Direct);
#endif
responseBody = "<html>\n"
"<head>\n"
"<title>Sample \"Hello, World\" Application</title>\n"
"</head>\n"
"<body bgcolor=white>\n"
"<table border=\"0\" cellpadding=\"10\">\n"
"<tr>\n"
"<td>\n"
"<img src=\"images/springsource.png\">\n"
"</td>\n"
"<td>\n"
"<h1>Sample \"Hello, World\" Application</h1>\n"
"</td>\n"
"</tr>\n"
"</table>\n"
"<p>This is the home page for the HelloWorld Web application. </p>\n"
"</body>\n"
"</html>";
}
Http2Server::~Http2Server()
{
}
void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path)
{
pushPromiseEnabled = pushEnabled;
pushPath = path;
}
void Http2Server::setResponseBody(const QByteArray &body)
{
responseBody = body;
}
void Http2Server::setContentEncoding(const QByteArray &encoding)
{
contentEncoding = encoding;
}
void Http2Server::setAuthenticationHeader(const QByteArray &authentication)
{
authenticationHeader = authentication;
}
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);
testingGOAWAY = true;
goawayTimeout = timeout;
}
void Http2Server::redirectOpenStream(quint16 port)
{
redirectWhileReading = true;
targetPort = port;
}
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()) {
if (isClearText())
authority = QStringLiteral("127.0.0.1:%1").arg(serverPort()).toLatin1();
emit serverStarted(serverPort());
}
}
bool Http2Server::sendProtocolSwitchReply()
{
Q_ASSERT(socket);
Q_ASSERT(connectionType == H2Type::h2c);
// The first and the last HTTP/1.1 response we send:
const char response[] = "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: h2c\r\n\r\n";
const qint64 size = sizeof response - 1;
return socket->write(response, size) == size;
}
void Http2Server::sendServerSettings()
{
Q_ASSERT(socket);
if (!serverSettings.size())
return;
writer.start(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
for (auto it = serverSettings.cbegin(); it != serverSettings.cend(); ++it) {
writer.append(it.key());
writer.append(it.value());
if (it.key() == Settings::INITIAL_WINDOW_SIZE_ID)
streamRecvWindowSize = it.value();
}
writer.write(*socket);
// Now, let's update our peer on a session recv window size:
const quint32 updatedSize = 10 * streamRecvWindowSize;
if (sessionRecvWindowSize < updatedSize) {
const quint32 delta = updatedSize - sessionRecvWindowSize;
sessionRecvWindowSize = updatedSize;
sessionCurrRecvWindow = updatedSize;
sendWINDOW_UPDATE(connectionStreamID, delta);
}
waitingClientAck = true;
settingsSent = true;
}
void Http2Server::sendGOAWAY(quint32 streamID, quint32 error, quint32 lastStreamID)
{
Q_ASSERT(socket);
writer.start(FrameType::GOAWAY, FrameFlag::EMPTY, streamID);
writer.append(lastStreamID);
writer.append(error);
writer.write(*socket);
}
void Http2Server::sendRST_STREAM(quint32 streamID, quint32 error)
{
Q_ASSERT(socket);
writer.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
writer.append(error);
writer.write(*socket);
}
void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
{
Q_ASSERT(socket);
const auto it = suspendedStreams.find(streamID);
Q_ASSERT(it != suspendedStreams.end());
const quint32 offset = it->second;
Q_ASSERT(offset < quint32(responseBody.size()));
quint32 bytesToSend = std::min<quint32>(windowSize, responseBody.size() - offset);
quint32 bytesSent = 0;
const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::minPayloadLimit));
const uchar *src = reinterpret_cast<const uchar *>(responseBody.constData() + offset);
const bool last = offset + bytesToSend == quint32(responseBody.size());
// The payload can significantly exceed frameSizeLimit. Internally, writer
// will do needed fragmentation, but if some test failed, there is no need
// to wait for writer to send all DATA frames, we check 'interrupted' and
// stop early instead.
const quint32 framesInChunk = 10;
while (bytesToSend) {
if (interrupted.loadAcquire())
return;
const quint32 chunkSize = std::min<quint32>(framesInChunk * frameSizeLimit, bytesToSend);
writer.start(FrameType::DATA, FrameFlag::EMPTY, streamID);
writer.writeDATA(*socket, frameSizeLimit, src, chunkSize);
src += chunkSize;
bytesToSend -= chunkSize;
bytesSent += chunkSize;
if (frameSizeLimit != Http2::minPayloadLimit) {
// Our test is probably interested in how many DATA frames were sent.
emit sendingData();
}
}
if (interrupted.loadAcquire())
return;
if (last) {
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);
Q_ASSERT(closedStreams.find(streamID) == closedStreams.end());
closedStreams.insert(streamID);
} else {
it->second += bytesSent;
}
}
void Http2Server::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
{
Q_ASSERT(socket);
writer.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
writer.append(delta);
writer.write(*socket);
}
void Http2Server::incomingConnection(qintptr socketDescriptor)
{
if (isClearText()) {
socket.reset(new QTcpSocket);
const bool set = socket->setSocketDescriptor(socketDescriptor);
Q_ASSERT(set);
// Stop listening:
close();
upgradeProtocol = connectionType == H2Type::h2c;
QMetaObject::invokeMethod(this, "connectionEstablished",
Qt::QueuedConnection);
} else {
#if QT_CONFIG(ssl)
socket.reset(new QSslSocket);
QSslSocket *sslSocket = static_cast<QSslSocket *>(socket.data());
if (connectionType == H2Type::h2Alpn) {
// Add HTTP2 as supported protocol:
auto conf = QSslConfiguration::defaultConfiguration();
auto protos = conf.allowedNextProtocols();
protos.prepend(QSslConfiguration::ALPNProtocolHTTP2);
conf.setAllowedNextProtocols(protos);
sslSocket->setSslConfiguration(conf);
}
// SSL-related setup ...
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
sslSocket->setProtocol(QSsl::TlsV1_2OrLater);
connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(ignoreErrorSlot()));
QFile file(QT_TESTCASE_SOURCEDIR "/certs/fluke.key");
file.open(QIODevice::ReadOnly);
QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
sslSocket->setPrivateKey(key);
auto localCert = QSslCertificate::fromPath(QT_TESTCASE_SOURCEDIR "/certs/fluke.cert");
sslSocket->setLocalCertificateChain(localCert);
sslSocket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState);
// Stop listening.
close();
// Start SSL handshake and ALPN:
connect(sslSocket, SIGNAL(encrypted()), this, SLOT(connectionEstablished()));
sslSocket->startServerEncryption();
#else
Q_ASSERT(0);
#endif
}
}
quint32 Http2Server::clientSetting(Http2::Settings identifier, quint32 defaultValue)
{
const auto it = expectedClientSettings.find(identifier);
if (it != expectedClientSettings.end())
return it.value();
return defaultValue;
}
bool Http2Server::readMethodLine()
{
// We know for sure that Qt did the right thing sending us the correct
// Request-line with CRLF at the end ...
// We're overly simplistic here but all we need to know - the method.
while (socket->bytesAvailable()) {
char c = 0;
if (socket->read(&c, 1) != 1)
return false;
if (c == '\n' && requestLine.endsWith('\r')) {
if (requestLine.startsWith("GET"))
requestType = QHttpNetworkRequest::Get;
else if (requestLine.startsWith("POST"))
requestType = QHttpNetworkRequest::Post;
else
requestType = QHttpNetworkRequest::Custom; // 'invalid'.
requestLine.clear();
return true;
} else {
requestLine.append(c);
}
}
return false;
}
bool Http2Server::verifyProtocolUpgradeRequest()
{
Q_ASSERT(protocolUpgradeHandler.data());
bool connectionOk = false;
bool upgradeOk = false;
bool settingsOk = false;
QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
// That's how we append them, that's what I expect to find:
for (const auto &header : firstRequestReader->headers()) {
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;
}
return connectionOk && upgradeOk && settingsOk;
}
void Http2Server::triggerGOAWAYEmulation()
{
Q_ASSERT(testingGOAWAY);
auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [this]() {
sendGOAWAY(quint32(connectionStreamID), quint32(INTERNAL_ERROR), 0);
});
timer->start(goawayTimeout);
}
void Http2Server::connectionEstablished()
{
using namespace Http2;
if (testingGOAWAY && !isClearText())
return triggerGOAWAYEmulation();
// For clearTextHTTP2 we first have to respond with 'protocol switch'
// and then continue with whatever logic we have (testingGOAWAY or not),
// otherwise our 'peer' cannot process HTTP/2 frames yet.
connect(socket.data(), SIGNAL(readyRead()),
this, SLOT(readReady()));
waitingClientPreface = true;
waitingClientAck = false;
waitingClientSettings = false;
settingsSent = false;
if (connectionType == H2Type::h2c) {
requestLine.clear();
// Now we have to handle HTTP/1.1 request. We use Get/Post in our test,
// so set requestType to something unsupported:
requestType = QHttpNetworkRequest::Options;
} else {
// We immediately send our settings so that our client
// can use flow control correctly.
sendServerSettings();
}
if (socket->bytesAvailable())
readReady();
}
void Http2Server::ignoreErrorSlot()
{
#ifndef QT_NO_SSL
static_cast<QSslSocket *>(socket.data())->ignoreSslErrors();
#endif
}
// Now HTTP2 "server" part:
/*
This code is overly simplified but it tests the basic HTTP2 expected behavior:
1. CONNECTION PREFACE
2. SETTINGS
3. sends our own settings (to modify the flow control)
4. collects and reports requests
5. if asked - sends responds to those requests
6. does some very basic error handling
7. tests frames validity/stream logic at the very basic level.
*/
void Http2Server::readReady()
{
if (connectionError)
return;
if (redirectSent) {
// We are a "single shot" server, working in 'h2' mode,
// responding with a redirect code. Don't bother to handle
// anything else now.
return;
}
if (upgradeProtocol) {
handleProtocolUpgrade();
} else if (waitingClientPreface) {
handleConnectionPreface();
} else {
const auto status = reader.read(*socket);
switch (status) {
case FrameStatus::incompleteFrame:
break;
case FrameStatus::goodFrame:
handleIncomingFrame();
break;
default:
connectionError = true;
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
}
}
if (socket->bytesAvailable())
QMetaObject::invokeMethod(this, "readReady", Qt::QueuedConnection);
}
void Http2Server::handleProtocolUpgrade()
{
using ReplyPrivate = QHttpNetworkReplyPrivate;
Q_ASSERT(upgradeProtocol);
if (!protocolUpgradeHandler.data())
protocolUpgradeHandler.reset(new Http11Reply);
QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
// QHttpNetworkReplyPrivate parses ... reply. It will, unfortunately, fail
// on the first line ... which is a part of request. So we read this line
// and extract the method first.
if (firstRequestReader->state == ReplyPrivate::NothingDoneState) {
if (!readMethodLine())
return;
if (requestType != QHttpNetworkRequest::Get && requestType != QHttpNetworkRequest::Post) {
emit invalidRequest(1);
return;
}
firstRequestReader->state = ReplyPrivate::ReadingHeaderState;
}
if (!socket->bytesAvailable())
return;
if (firstRequestReader->state == ReplyPrivate::ReadingHeaderState)
firstRequestReader->readHeader(socket.data());
else if (firstRequestReader->state == ReplyPrivate::ReadingDataState)
firstRequestReader->readBodyFast(socket.data(), &firstRequestReader->responseData);
switch (firstRequestReader->state) {
case ReplyPrivate::ReadingHeaderState:
return;
case ReplyPrivate::ReadingDataState:
if (requestType == QHttpNetworkRequest::Post)
return;
break;
case ReplyPrivate::AllDoneState:
break;
default:
socket->close();
return;
}
if (!verifyProtocolUpgradeRequest() || !sendProtocolSwitchReply()) {
socket->close();
return;
}
upgradeProtocol = false;
protocolUpgradeHandler.reset(nullptr);
if (testingGOAWAY)
return triggerGOAWAYEmulation();
// HTTP/1.1 'fields' we have in firstRequestRead are useless (they are not
// even allowed in HTTP/2 header). Let's pretend we have received
// valid HTTP/2 headers and can extract fields we need:
HttpHeader h2header;
h2header.push_back(HeaderField(":scheme", "http")); // we are in clearTextHTTP2 mode.
h2header.push_back(HeaderField(":authority", authority));
activeRequests[1] = std::move(h2header);
// After protocol switch we immediately send our SETTINGS.
sendServerSettings();
if (requestType == QHttpNetworkRequest::Get)
emit receivedRequest(1);
else
emit receivedData(1);
}
void Http2Server::handleConnectionPreface()
{
Q_ASSERT(waitingClientPreface);
if (socket->bytesAvailable() < clientPrefaceLength)
return; // Wait for more data ...
char buf[clientPrefaceLength] = {};
socket->read(buf, clientPrefaceLength);
if (std::memcmp(buf, Http2clientPreface, clientPrefaceLength)) {
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
emit clientPrefaceError();
connectionError = true;
return;
}
waitingClientPreface = false;
waitingClientSettings = true;
}
void Http2Server::handleIncomingFrame()
{
// Frames that our implementation can send include:
// 1. SETTINGS (happens only during connection preface,
// handled already by this point)
// 2. SETTIGNS with ACK should be sent only as a response
// to a server's SETTINGS
// 3. HEADERS
// 4. CONTINUATION
// 5. DATA
// 6. PING
// 7. RST_STREAM
// 8. GOAWAY
if (testingGOAWAY) {
// GOAWAY test is simplistic for now: after HTTP/2 was
// negotiated (via ALPN/NPN or a protocol switch), send
// a GOAWAY frame after some (probably non-zero) timeout.
// We do not handle any frames, but timeout gives QNAM
// more time to initiate more streams and thus make the
// test more interesting/complex (on a client side).
return;
}
inboundFrame = std::move(reader.inboundFrame());
if (continuedRequest.size()) {
if (inboundFrame.type() != FrameType::CONTINUATION ||
inboundFrame.streamID() != continuedRequest.front().streamID()) {
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
emit invalidFrame();
connectionError = true;
return;
}
}
switch (inboundFrame.type()) {
case FrameType::SETTINGS:
handleSETTINGS();
break;
case FrameType::HEADERS:
case FrameType::CONTINUATION:
continuedRequest.push_back(std::move(inboundFrame));
processRequest();
break;
case FrameType::DATA:
handleDATA();
break;
case FrameType::RST_STREAM:
// TODO: this is not tested for now.
break;
case FrameType::PING:
// TODO: this is not tested for now.
break;
case FrameType::GOAWAY:
// TODO: this is not tested for now.
break;
case FrameType::WINDOW_UPDATE:
handleWINDOW_UPDATE();
break;
default:;
}
}
void Http2Server::handleSETTINGS()
{
// SETTINGS is either a part of the connection preface,
// or a SETTINGS ACK.
Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
if (!waitingClientAck || inboundFrame.dataSize()) {
emit invalidFrame();
connectionError = true;
waitingClientAck = false;
return;
}
waitingClientAck = false;
emit serverSettingsAcked();
return;
}
// QHttp2ProtocolHandler always sends some settings,
// and the size is a multiple of 6.
if (!inboundFrame.dataSize() || inboundFrame.dataSize() % 6) {
sendGOAWAY(connectionStreamID, FRAME_SIZE_ERROR, connectionStreamID);
emit clientPrefaceError();
connectionError = true;
return;
}
const uchar *src = inboundFrame.dataBegin();
const uchar *end = src + inboundFrame.dataSize();
const auto notFound = expectedClientSettings.end();
while (src != end) {
const auto id = Http2::Settings(qFromBigEndian<quint16>(src));
const auto value = qFromBigEndian<quint32>(src + 2);
if (expectedClientSettings.find(id) == notFound ||
expectedClientSettings[id] != value) {
emit clientPrefaceError();
connectionError = true;
return;
}
src += 6;
}
// Send SETTINGS ACK:
writer.start(FrameType::SETTINGS, FrameFlag::ACK, connectionStreamID);
writer.write(*socket);
waitingClientSettings = false;
emit clientPrefaceOK();
}
void Http2Server::handleDATA()
{
Q_ASSERT(inboundFrame.type() == FrameType::DATA);
const auto streamID = inboundFrame.streamID();
if (!is_valid_client_stream(streamID) ||
closedStreams.find(streamID) != closedStreams.end()) {
emit invalidFrame();
connectionError = true;
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
return;
}
const auto payloadSize = inboundFrame.payloadSize();
if (sessionCurrRecvWindow < payloadSize) {
// Client does not respect our session window size!
emit invalidRequest(streamID);
connectionError = true;
sendGOAWAY(connectionStreamID, FLOW_CONTROL_ERROR, connectionStreamID);
return;
}
auto it = streamWindows.find(streamID);
if (it == streamWindows.end())
it = streamWindows.insert(std::make_pair(streamID, streamRecvWindowSize)).first;
if (it->second < payloadSize) {
emit invalidRequest(streamID);
connectionError = true;
sendGOAWAY(connectionStreamID, FLOW_CONTROL_ERROR, connectionStreamID);
return;
}
it->second -= payloadSize;
if (it->second < streamRecvWindowSize / 2) {
sendWINDOW_UPDATE(streamID, streamRecvWindowSize / 2);
it->second += streamRecvWindowSize / 2;
}
sessionCurrRecvWindow -= payloadSize;
if (sessionCurrRecvWindow < sessionRecvWindowSize / 2) {
// This is some quite naive and trivial logic on when to update.
sendWINDOW_UPDATE(connectionStreamID, sessionRecvWindowSize / 2);
sessionCurrRecvWindow += sessionRecvWindowSize / 2;
}
if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
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()
{
const auto streamID = inboundFrame.streamID();
if (!streamID) // We ignore this for now to keep things simple.
return;
if (streamID && suspendedStreams.find(streamID) == suspendedStreams.end()) {
if (closedStreams.find(streamID) == closedStreams.end()) {
sendRST_STREAM(streamID, PROTOCOL_ERROR);
emit invalidFrame();
connectionError = true;
}
return;
}
const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
if (!delta || delta > quint32(std::numeric_limits<qint32>::max())) {
sendRST_STREAM(streamID, PROTOCOL_ERROR);
emit invalidFrame();
connectionError = true;
return;
}
emit windowUpdate(streamID);
sendDATA(streamID, delta);
}
void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
{
Q_ASSERT(activeRequests.find(streamID) != activeRequests.end());
const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID,
Http2::maxPayloadSize));
if (pushPromiseEnabled) {
// A real server supporting PUSH_PROMISE will probably first send
// PUSH_PROMISE and then a normal response (to a real request),
// so that a client parsing this response and discovering another
// resource it needs, will _already_ have this additional resource
// in PUSH_PROMISE.
lastPromisedStream += 2;
writer.start(FrameType::PUSH_PROMISE, FrameFlag::END_HEADERS, streamID);
writer.append(lastPromisedStream);
HttpHeader pushHeader;
fill_push_header(activeRequests[streamID], pushHeader);
pushHeader.push_back(HeaderField(":method", "GET"));
pushHeader.push_back(HeaderField(":path", pushPath));
// Now interesting part, let's make it into 'stream':
activeRequests[lastPromisedStream] = pushHeader;
HPack::BitOStream ostream(writer.outboundFrame().buffer);
const bool result = encoder.encodeRequest(ostream, pushHeader);
Q_ASSERT(result);
// Well, it's not HEADERS, it's PUSH_PROMISE with ... HEADERS block.
// Should work.
writer.writeHEADERS(*socket, maxFrameSize);
qDebug() << "server sent a PUSH_PROMISE on" << lastPromisedStream;
if (responseBody.isEmpty())
responseBody = QByteArray("I PROMISE (AND PUSH) YOU ...");
// Now we send this promised data as a normal response on our reserved
// stream (disabling PUSH_PROMISE for the moment to avoid recursion):
pushPromiseEnabled = false;
sendResponse(lastPromisedStream, false);
pushPromiseEnabled = true;
// Now we'll continue with _normal_ response.
}
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) {
// This is a "single-shot" server responding with a redirect code.
return;
}
redirectSent = true;
qDebug("server received HEADERS frame (followed by DATA frames), redirecting ...");
Q_ASSERT(targetPort);
header.push_back({":status", "308"});
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));
authenticationHeader.clear();
} else {
header.push_back({":status", "200"});
}
if (!emptyBody) {
header.push_back(HPack::HeaderField("content-length",
QString("%1").arg(responseBody.size()).toLatin1()));
}
if (!contentEncoding.isEmpty())
header.push_back(HPack::HeaderField("content-encoding", contentEncoding));
HPack::BitOStream ostream(writer.outboundFrame().buffer);
const bool result = encoder.encodeResponse(ostream, header);
Q_ASSERT(result);
writer.writeHEADERS(*socket, maxFrameSize);
if (!emptyBody) {
Q_ASSERT(suspendedStreams.find(streamID) == suspendedStreams.end());
const quint32 windowSize = clientSetting(Settings::INITIAL_WINDOW_SIZE_ID,
Http2::defaultSessionWindowSize);
// Suspend to immediately resume it.
suspendedStreams[streamID] = 0; // start sending from offset 0
sendDATA(streamID, windowSize);
} else {
activeRequests.erase(streamID);
closedStreams.insert(streamID);
}
}
void Http2Server::stopSendingDATAFrames()
{
interrupted.storeRelease(1);
}
void Http2Server::processRequest()
{
Q_ASSERT(continuedRequest.size());
if (!continuedRequest.back().flags().testFlag(FrameFlag::END_HEADERS))
return;
// We test here:
// 1. stream is 'idle'.
// 2. has priority set and dependency (it's 0x0 at the moment).
// 3. header can be decompressed.
const auto &headersFrame = continuedRequest.front();
const auto streamID = headersFrame.streamID();
if (!is_valid_client_stream(streamID)) {
emit invalidRequest(streamID);
connectionError = true;
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
return;
}
if (closedStreams.find(streamID) != closedStreams.end()) {
emit invalidFrame();
connectionError = true;
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
return;
}
quint32 dep = 0;
uchar w = 0;
if (!headersFrame.priority(&dep, &w)) {
emit invalidFrame();
sendRST_STREAM(streamID, PROTOCOL_ERROR);
return;
}
// Assemble headers ...
quint32 totalSize = 0;
for (const auto &frame : continuedRequest) {
if (std::numeric_limits<quint32>::max() - frame.dataSize() < totalSize) {
// Resulted in overflow ...
emit invalidFrame();
connectionError = true;
sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID);
return;
}
totalSize += frame.dataSize();
}
std::vector<uchar> hpackBlock(totalSize);
auto dst = hpackBlock.begin();
for (const auto &frame : continuedRequest) {
if (!frame.dataSize())
continue;
std::copy(frame.dataBegin(), frame.dataBegin() + frame.dataSize(), dst);
dst += frame.dataSize();
}
HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
if (!decoder.decodeHeaderFields(inputStream)) {
emit decompressionFailed(streamID);
sendRST_STREAM(streamID, COMPRESSION_ERROR);
closedStreams.insert(streamID);
return;
}
// Actually, if needed, we can do a comparison here.
activeRequests[streamID] = decoder.decodedHeader();
if (headersFrame.flags().testFlag(FrameFlag::END_STREAM))
emit receivedRequest(streamID);
if (redirectWhileReading) {
sendResponse(streamID, true);
// Don't try to read any DATA frames ...
socket->disconnect();
} // else - we're waiting for incoming DATA frames ...
continuedRequest.clear();
}
QT_END_NAMESPACE

View File

@ -0,0 +1,217 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef HTTP2SRV_H
#define HTTP2SRV_H
#include <QtNetwork/private/qhttpnetworkrequest_p.h>
#include <QtNetwork/private/qhttpnetworkreply_p.h>
#include <QtNetwork/private/http2protocol_p.h>
#include <QtNetwork/private/http2frames_p.h>
#include <QtNetwork/private/hpack_p.h>
#include <QtNetwork/qabstractsocket.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qscopedpointer.h>
#include <QtNetwork/qtcpserver.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qatomic.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmap.h>
#include <vector>
#include <map>
#include <set>
QT_BEGIN_NAMESPACE
// At the moment we do not have any public API parsing HTTP headers. Even worse -
// the code that can do this exists only in QHttpNetworkReplyPrivate class.
// To be able to access reply's d_func() we have these classes:
class Http11ReplyPrivate : public QHttpNetworkReplyPrivate
{
};
class Http11Reply : public QHttpNetworkReply
{
public:
Q_DECLARE_PRIVATE(Http11Reply)
};
enum class H2Type {
h2Alpn, // Secure connection, ALPN to negotiate h2.
h2c, // Clear text with protocol upgrade.
h2Direct, // Secure connection, ALPN not supported.
h2cDirect, // Clear text direct
};
using RawSettings = QMap<Http2::Settings, quint32>;
class Http2Server : public QTcpServer
{
Q_OBJECT
public:
Http2Server(H2Type type, const RawSettings &serverSettings,
const RawSettings &clientSettings);
~Http2Server();
// 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);
// 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();
bool sendProtocolSwitchReply();
Q_INVOKABLE void sendServerSettings();
Q_INVOKABLE void sendGOAWAY(quint32 streamID, quint32 error,
quint32 lastStreamID);
Q_INVOKABLE void sendRST_STREAM(quint32 streamID, quint32 error);
Q_INVOKABLE void sendDATA(quint32 streamID, quint32 windowSize);
Q_INVOKABLE void sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
Q_INVOKABLE void handleProtocolUpgrade();
Q_INVOKABLE void handleConnectionPreface();
Q_INVOKABLE void handleIncomingFrame();
Q_INVOKABLE void handleSETTINGS();
Q_INVOKABLE void handleDATA();
Q_INVOKABLE void handleWINDOW_UPDATE();
Q_INVOKABLE void sendResponse(quint32 streamID, bool emptyBody);
void stopSendingDATAFrames();
private:
void processRequest();
Q_SIGNALS:
void serverStarted(quint16 port);
// Error/success notifications:
void clientPrefaceOK();
void clientPrefaceError();
void serverSettingsAcked();
void invalidFrame();
void invalidRequest(quint32 streamID);
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();
private slots:
void connectionEstablished();
void readReady();
private:
void incomingConnection(qintptr socketDescriptor) override;
quint32 clientSetting(Http2::Settings identifier, quint32 defaultValue);
bool readMethodLine();
bool verifyProtocolUpgradeRequest();
void triggerGOAWAYEmulation();
QScopedPointer<QAbstractSocket> socket;
H2Type connectionType = H2Type::h2Alpn;
// Connection preface:
bool waitingClientPreface = false;
bool waitingClientSettings = false;
bool settingsSent = false;
bool waitingClientAck = false;
RawSettings serverSettings;
RawSettings expectedClientSettings;
bool connectionError = false;
Http2::FrameReader reader;
Http2::Frame inboundFrame;
Http2::FrameWriter writer;
using FrameSequence = std::vector<Http2::Frame>;
FrameSequence continuedRequest;
std::map<quint32, quint32> streamWindows;
HPack::Decoder decoder{HPack::FieldLookupTable::DefaultSize};
HPack::Encoder encoder{HPack::FieldLookupTable::DefaultSize, true};
using Http2Requests = std::map<quint32, HPack::HttpHeader>;
Http2Requests activeRequests;
// 'remote half-closed' streams to keep
// track of streams with END_STREAM set:
std::set<quint32> closedStreams;
// streamID + offset in response body to send.
std::map<quint32, quint32> suspendedStreams;
// We potentially reset this once (see sendServerSettings)
// and do not change later:
quint32 sessionRecvWindowSize = Http2::defaultSessionWindowSize;
// This changes in the range [0, sessionRecvWindowSize]
// while handling DATA frames:
quint32 sessionCurrRecvWindow = sessionRecvWindowSize;
// This we potentially update only once (sendServerSettings).
quint32 streamRecvWindowSize = Http2::defaultSessionWindowSize;
QByteArray responseBody;
bool pushPromiseEnabled = false;
quint32 lastPromisedStream = 0;
QByteArray pushPath;
bool testingGOAWAY = false;
int goawayTimeout = 0;
// Clear text HTTP/2, we have to deal with the protocol upgrade request
// from the initial HTTP/1.1 request.
bool upgradeProtocol = false;
QByteArray requestLine;
QHttpNetworkRequest::Operation requestType;
// We need QHttpNetworkReply (actually its private d-object) to handle the
// first HTTP/1.1 request. QHttpNetworkReplyPrivate does parsing + in case
// of POST it is also reading the body for us.
QScopedPointer<Http11Reply> protocolUpgradeHandler;
// We need it for PUSH_PROMISE, with the correct port number appended,
// when replying to essentially 1.1 request.
QByteArray authority;
// Redirect, with status code 308, as soon as we've seen headers, while client
// may still be sending DATA frames. See tst_Http2::earlyResponse().
bool redirectWhileReading = false;
bool redirectSent = false;
quint16 targetPort = 0;
QAtomicInt interrupted;
QByteArray contentEncoding;
QByteArray authenticationHeader;
QByteArray redirectUrl;
int redirectCount = 0;
bool sendTrailingHEADERS = false;
protected slots:
void ignoreErrorSlot();
};
QT_END_NAMESPACE
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## 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
LIBRARIES
Qt::Network
TESTDATA ${test_data}
QT_TEST_SERVER_LIST "apache2"
)
# QT_TEST_SERVER_LIST = "apache2"

View File

@ -0,0 +1,7 @@
#!/bin/bash
# cache control takes precedence over expires
echo "Cache-Control: max-age=-1"
echo "Expires: Mon, 30 Oct 2028 14:19:41 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,13 @@
#!/bin/bash
if [ ! -z ${HTTP_IF_MODIFIED_SINCE} ] ; then
echo "Status: 304"
echo ""
exit;
fi
cc=`echo "${QUERY_STRING}" | sed -e s/%20/\ /g`
echo "Cache-Control: $cc"
echo "Last-Modified: Sat, 31 Oct 1981 06:00:00 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,9 @@
#!/bin/bash
cc=`echo "${QUERY_STRING}" | sed -e s/%20/\ /g`
echo "Status: 200"
echo "Cache-Control: $cc"
echo "Last-Modified: Sat, 31 Oct 1981 06:00:00 GMT"
echo "Content-type: text/html";
echo "X-Script: $0"
echo ""
echo "Hello World!"

View File

@ -0,0 +1,5 @@
#!/bin/bash
echo "ETag: foo"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,11 @@
#!/bin/bash
if [ ! -z ${HTTP_IF_NONE_MATCH} ] ; then
echo "Status: 304"
echo ""
exit;
fi
echo "ETag: foo"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,5 @@
#!/bin/bash
echo "Expires: Sat, 31 Oct 1981 6:00:00 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,11 @@
#!/bin/bash
if [ ${HTTP_IF_MODIFIED_SINCE} == "Mon, 30 Oct 2028 14:19:41 GMT" ] ; then
echo "Status: 304"
echo ""
exit;
fi
echo "Expires: Mon, 30 Oct 2028 14:19:41 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,11 @@
#!/bin/bash
if [ ! -z ${HTTP_IF_MODIFIED_SINCE} ] ; then
echo "Status: 500"
echo ""
exit;
fi
echo "Expires: Mon, 30 Oct 2028 14:19:41 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,5 @@
#!/bin/bash
echo "Last-Modified: Sat, 31 Oct 1981 6:00:00 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,11 @@
#!/bin/bash
if [ ${HTTP_IF_MODIFIED_SINCE} == "Sat, 31 Oct 1981 06:00:00 GMT" ] ; then
echo "Status: 304"
echo ""
exit;
fi
echo "Last-Modified: Sat, 31 Oct 1981 06:00:00 GMT"
echo "Content-type: text/html";
echo ""
echo "Hello World!"

View File

@ -0,0 +1,372 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTemporaryDir>
#include <QTest>
#include <QtNetwork/QtNetwork>
#include <QSignalSpy>
#include "../../../network-settings.h"
#include <algorithm>
#define TESTFILE QLatin1String("http://") + QtNetworkSettings::httpServerName() + QLatin1String("/qtest/cgi-bin/")
class tst_QAbstractNetworkCache : public QObject
{
Q_OBJECT
public:
tst_QAbstractNetworkCache();
virtual ~tst_QAbstractNetworkCache();
private slots:
void initTestCase();
void expires_data();
void expires();
void expiresSynchronous_data();
void expiresSynchronous();
void lastModified_data();
void lastModified();
void lastModifiedSynchronous_data();
void lastModifiedSynchronous();
void etag_data();
void etag();
void etagSynchronous_data();
void etagSynchronous();
void cacheControl_data();
void cacheControl();
void cacheControlSynchronous_data();
void cacheControlSynchronous();
void deleteCache();
private:
void runTest();
void checkSynchronous();
};
class NetworkDiskCache : public QNetworkDiskCache
{
Q_OBJECT
public:
NetworkDiskCache(QObject *parent = nullptr)
: QNetworkDiskCache(parent)
, tempDir(QDir::tempPath() + QLatin1String("/tst_qabstractnetworkcache.XXXXXX"))
, gotData(false)
{
setCacheDirectory(tempDir.path());
clear();
}
QIODevice *data(const QUrl &url) override
{
gotData = true;
return QNetworkDiskCache::data(url);
}
QTemporaryDir tempDir;
bool gotData;
};
tst_QAbstractNetworkCache::tst_QAbstractNetworkCache()
{
QCoreApplication::setOrganizationName(QLatin1String("QtProject"));
QCoreApplication::setApplicationName(QLatin1String("autotest_qabstractnetworkcache"));
QCoreApplication::setApplicationVersion(QLatin1String("1.0"));
}
tst_QAbstractNetworkCache::~tst_QAbstractNetworkCache()
{
}
static bool AlwaysTrue = true;
static bool AlwaysFalse = false;
Q_DECLARE_METATYPE(QNetworkRequest::CacheLoadControl)
void tst_QAbstractNetworkCache::initTestCase()
{
#if defined(QT_TEST_SERVER)
QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 80));
#else
if (!QtNetworkSettings::verifyTestNetworkSettings())
QSKIP("No network test server available");
#endif
}
void tst_QAbstractNetworkCache::expires_data()
{
QTest::addColumn<QNetworkRequest::CacheLoadControl>("cacheLoadControl");
QTest::addColumn<QString>("url");
QTest::addColumn<bool>("fetchFromCache");
QTest::newRow("304-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_expires304.cgi" << AlwaysFalse;
QTest::newRow("304-1") << QNetworkRequest::PreferNetwork << "httpcachetest_expires304.cgi" << true;
QTest::newRow("304-2") << QNetworkRequest::AlwaysCache << "httpcachetest_expires304.cgi" << AlwaysTrue;
QTest::newRow("304-3") << QNetworkRequest::PreferCache << "httpcachetest_expires304.cgi" << true;
QTest::newRow("500-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_expires500.cgi" << AlwaysFalse;
QTest::newRow("500-1") << QNetworkRequest::PreferNetwork << "httpcachetest_expires500.cgi" << true;
QTest::newRow("500-2") << QNetworkRequest::AlwaysCache << "httpcachetest_expires500.cgi" << AlwaysTrue;
QTest::newRow("500-3") << QNetworkRequest::PreferCache << "httpcachetest_expires500.cgi" << true;
QTest::newRow("200-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_expires200.cgi" << AlwaysFalse;
QTest::newRow("200-1") << QNetworkRequest::PreferNetwork << "httpcachetest_expires200.cgi" << false;
QTest::newRow("200-2") << QNetworkRequest::AlwaysCache << "httpcachetest_expires200.cgi" << AlwaysTrue;
QTest::newRow("200-3") << QNetworkRequest::PreferCache << "httpcachetest_expires200.cgi" << false;
}
void tst_QAbstractNetworkCache::expires()
{
runTest();
}
void tst_QAbstractNetworkCache::expiresSynchronous_data()
{
expires_data();
}
void tst_QAbstractNetworkCache::expiresSynchronous()
{
checkSynchronous();
}
void tst_QAbstractNetworkCache::lastModified_data()
{
QTest::addColumn<QNetworkRequest::CacheLoadControl>("cacheLoadControl");
QTest::addColumn<QString>("url");
QTest::addColumn<bool>("fetchFromCache");
QTest::newRow("304-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_lastModified304.cgi" << AlwaysFalse;
QTest::newRow("304-1") << QNetworkRequest::PreferNetwork << "httpcachetest_lastModified304.cgi" << true;
QTest::newRow("304-2") << QNetworkRequest::AlwaysCache << "httpcachetest_lastModified304.cgi" << AlwaysTrue;
QTest::newRow("304-3") << QNetworkRequest::PreferCache << "httpcachetest_lastModified304.cgi" << true;
QTest::newRow("200-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_lastModified200.cgi" << AlwaysFalse;
QTest::newRow("200-1") << QNetworkRequest::PreferNetwork << "httpcachetest_lastModified200.cgi" << true;
QTest::newRow("200-2") << QNetworkRequest::AlwaysCache << "httpcachetest_lastModified200.cgi" << AlwaysTrue;
QTest::newRow("200-3") << QNetworkRequest::PreferCache << "httpcachetest_lastModified200.cgi" << true;
}
void tst_QAbstractNetworkCache::lastModified()
{
runTest();
}
void tst_QAbstractNetworkCache::lastModifiedSynchronous_data()
{
tst_QAbstractNetworkCache::lastModified_data();
}
void tst_QAbstractNetworkCache::lastModifiedSynchronous()
{
checkSynchronous();
}
void tst_QAbstractNetworkCache::etag_data()
{
QTest::addColumn<QNetworkRequest::CacheLoadControl>("cacheLoadControl");
QTest::addColumn<QString>("url");
QTest::addColumn<bool>("fetchFromCache");
QTest::newRow("304-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_etag304.cgi" << AlwaysFalse;
QTest::newRow("304-1") << QNetworkRequest::PreferNetwork << "httpcachetest_etag304.cgi" << true;
QTest::newRow("304-2") << QNetworkRequest::AlwaysCache << "httpcachetest_etag304.cgi" << AlwaysTrue;
QTest::newRow("304-3") << QNetworkRequest::PreferCache << "httpcachetest_etag304.cgi" << true;
QTest::newRow("200-0") << QNetworkRequest::AlwaysNetwork << "httpcachetest_etag200.cgi" << AlwaysFalse;
QTest::newRow("200-1") << QNetworkRequest::PreferNetwork << "httpcachetest_etag200.cgi" << false;
QTest::newRow("200-2") << QNetworkRequest::AlwaysCache << "httpcachetest_etag200.cgi" << AlwaysTrue;
QTest::newRow("200-3") << QNetworkRequest::PreferCache << "httpcachetest_etag200.cgi" << false;
}
void tst_QAbstractNetworkCache::etag()
{
runTest();
}
void tst_QAbstractNetworkCache::etagSynchronous_data()
{
tst_QAbstractNetworkCache::etag_data();
}
void tst_QAbstractNetworkCache::etagSynchronous()
{
checkSynchronous();
}
void tst_QAbstractNetworkCache::cacheControl_data()
{
QTest::addColumn<QNetworkRequest::CacheLoadControl>("cacheLoadControl");
QTest::addColumn<QString>("url");
QTest::addColumn<bool>("fetchFromCache");
QTest::newRow("200-0") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol.cgi?max-age=-1" << true;
QTest::newRow("200-1") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol-expire.cgi" << false;
QTest::newRow("200-2") << QNetworkRequest::AlwaysNetwork << "httpcachetest_cachecontrol.cgi?no-cache" << AlwaysFalse;
QTest::newRow("200-3") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol.cgi?no-cache" << true;
QTest::newRow("200-4") << QNetworkRequest::AlwaysCache << "httpcachetest_cachecontrol.cgi?no-cache" << false;
QTest::newRow("200-5") << QNetworkRequest::PreferCache << "httpcachetest_cachecontrol.cgi?no-cache" << true;
QTest::newRow("200-6") << QNetworkRequest::AlwaysNetwork << "httpcachetest_cachecontrol.cgi?no-store" << AlwaysFalse;
QTest::newRow("200-7") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol.cgi?no-store" << false;
QTest::newRow("200-8") << QNetworkRequest::AlwaysCache << "httpcachetest_cachecontrol.cgi?no-store" << false;
QTest::newRow("200-9") << QNetworkRequest::PreferCache << "httpcachetest_cachecontrol.cgi?no-store" << false;
QTest::newRow("304-0") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol.cgi?max-age=1000" << true;
QTest::newRow("304-1") << QNetworkRequest::AlwaysNetwork << "httpcachetest_cachecontrol.cgi?max-age=1000, must-revalidate" << AlwaysFalse;
QTest::newRow("304-2") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol.cgi?max-age=1000, must-revalidate" << true;
QTest::newRow("304-3") << QNetworkRequest::AlwaysCache << "httpcachetest_cachecontrol.cgi?max-age=1000, must-revalidate" << false;
QTest::newRow("304-4") << QNetworkRequest::PreferCache << "httpcachetest_cachecontrol.cgi?max-age=1000, must-revalidate" << true;
QTest::newRow("304-2b") << QNetworkRequest::PreferNetwork << "httpcachetest_cachecontrol200.cgi?private, max-age=1000" << true;
QTest::newRow("304-4b") << QNetworkRequest::PreferCache << "httpcachetest_cachecontrol200.cgi?private, max-age=1000" << true;
}
void tst_QAbstractNetworkCache::cacheControl()
{
runTest();
}
void tst_QAbstractNetworkCache::cacheControlSynchronous_data()
{
tst_QAbstractNetworkCache::cacheControl_data();
}
void tst_QAbstractNetworkCache::cacheControlSynchronous()
{
checkSynchronous();
}
void tst_QAbstractNetworkCache::runTest()
{
QFETCH(QNetworkRequest::CacheLoadControl, cacheLoadControl);
QFETCH(QString, url);
QFETCH(bool, fetchFromCache);
QNetworkAccessManager manager;
NetworkDiskCache *diskCache = new NetworkDiskCache(&manager);
QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString()));
manager.setCache(diskCache);
QCOMPARE(diskCache->gotData, false);
QUrl realUrl = url.contains("://") ? url : TESTFILE + url;
QNetworkRequest request(realUrl);
// prime the cache
QNetworkReply *reply = manager.get(request);
QSignalSpy downloaded1(reply, SIGNAL(finished()));
QTRY_COMPARE(downloaded1.size(), 1);
QCOMPARE(diskCache->gotData, false);
QByteArray goodData = reply->readAll();
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl);
// should be in the cache now
QNetworkReply *reply2 = manager.get(request);
QSignalSpy downloaded2(reply2, SIGNAL(finished()));
QTRY_COMPARE(downloaded2.size(), 1);
QByteArray secondData = reply2->readAll();
if (!fetchFromCache && cacheLoadControl == QNetworkRequest::AlwaysCache) {
QCOMPARE(reply2->error(), QNetworkReply::ContentNotFoundError);
QCOMPARE(secondData, QByteArray());
} else {
QCOMPARE(reply2->error(), QNetworkReply::NoError);
QCOMPARE(QString(secondData), QString(goodData));
QCOMPARE(secondData, goodData);
QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);
}
if (fetchFromCache) {
QList<QByteArray> rawHeaderList = reply->rawHeaderList();
QList<QByteArray> rawHeaderList2 = reply2->rawHeaderList();
std::sort(rawHeaderList.begin(), rawHeaderList.end());
std::sort(rawHeaderList2.begin(), rawHeaderList2.end());
}
QCOMPARE(diskCache->gotData, fetchFromCache);
}
void tst_QAbstractNetworkCache::checkSynchronous()
{
QSKIP("not working yet, see QTBUG-15221");
QFETCH(QNetworkRequest::CacheLoadControl, cacheLoadControl);
QFETCH(QString, url);
QFETCH(bool, fetchFromCache);
QNetworkAccessManager manager;
NetworkDiskCache *diskCache = new NetworkDiskCache(&manager);
QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString()));
manager.setCache(diskCache);
QCOMPARE(diskCache->gotData, false);
QUrl realUrl = url.contains("://") ? url : TESTFILE + url;
QNetworkRequest request(realUrl);
request.setAttribute(
QNetworkRequest::SynchronousRequestAttribute,
true);
// prime the cache
QNetworkReply *reply = manager.get(request);
QVERIFY(reply->isFinished()); // synchronous
QCOMPARE(diskCache->gotData, false);
QByteArray goodData = reply->readAll();
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl);
// should be in the cache now
QNetworkReply *reply2 = manager.get(request);
QVERIFY(reply2->isFinished()); // synchronous
QByteArray secondData = reply2->readAll();
if (!fetchFromCache && cacheLoadControl == QNetworkRequest::AlwaysCache) {
QCOMPARE(reply2->error(), QNetworkReply::ContentNotFoundError);
QCOMPARE(secondData, QByteArray());
} else {
if (reply2->error() != QNetworkReply::NoError)
qDebug() << reply2->errorString();
QCOMPARE(reply2->error(), QNetworkReply::NoError);
QCOMPARE(QString(secondData), QString(goodData));
QCOMPARE(secondData, goodData);
QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);
}
if (fetchFromCache) {
QList<QByteArray> rawHeaderList = reply->rawHeaderList();
QList<QByteArray> rawHeaderList2 = reply2->rawHeaderList();
std::sort(rawHeaderList.begin(), rawHeaderList.end());
std::sort(rawHeaderList2.begin(), rawHeaderList2.end());
}
QCOMPARE(diskCache->gotData, fetchFromCache);
}
void tst_QAbstractNetworkCache::deleteCache()
{
QNetworkAccessManager manager;
NetworkDiskCache *diskCache = new NetworkDiskCache(&manager);
QVERIFY2(diskCache->tempDir.isValid(), qPrintable(diskCache->tempDir.errorString()));
manager.setCache(diskCache);
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.size(), 1);
}
QTEST_MAIN(tst_QAbstractNetworkCache)
#include "tst_qabstractnetworkcache.moc"

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
[bigZlib]
macos arm

View File

@ -0,0 +1,18 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## 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}
LIBRARIES
Qt::NetworkPrivate
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,617 @@
/****************************************************************************
** 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/qdecompresshelper/5GiB.txt.inflate
0x0,0x0,0x20,0xca,
0x0,
0x4f,0x9f,0x57,0x78,0xda,0xec,0xdc,0x3d,0x2e,0x44,0x1,0x18,0x86,0xd1,0x3b,0x31,
0x85,0x28,0xfc,0x14,0x2a,0x8d,0x65,0x68,0x24,0x34,0x6a,0x5,0xb5,0x58,0x80,0x5e,
0x67,0x12,0xd1,0x5b,0x80,0x3d,0x4c,0x65,0x1,0x3a,0x3b,0x40,0x67,0x1,0xa2,0x50,
0xa8,0xc4,0x88,0xbb,0x8c,0xfb,0x9c,0xb3,0x84,0xb7,0xfc,0xf2,0xe5,0xb9,0x7e,0xf8,
0x78,0x9a,0xcd,0x86,0x61,0x58,0xdc,0xff,0x2e,0x3f,0xd7,0x37,0x6,0x0,0x0,0x0,
0x60,0xe2,0x16,0x6f,0xb7,0x6b,0xff,0xc7,0x80,0x61,0x7f,0xf5,0x72,0x72,0x7a,0xe,
0x0,0x0,0x0,0x4c,0xdd,0xe5,0xd6,0xe6,0x7c,0x3c,0xa,0xfc,0x5c,0xec,0x1d,0x9b,
0x3,0x0,0x0,0x0,0x26,0xef,0xee,0xf0,0x60,0x7b,0xfc,0x10,0xf8,0xba,0xb9,0x9a,
0x9b,0x3,0x0,0x0,0x0,0x26,0x6f,0xf7,0xfb,0xfd,0x68,0xbc,0x5,0x3c,0xaf,0x1e,
0x77,0xce,0xec,0x1,0x0,0x0,0x0,0x93,0xf7,0x2a,0x18,0x0,0x0,0x0,0x0,0x29,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,
0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,
0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,
0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,
0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,
0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,
0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,
0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,
0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,
0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,
0x10,0x23,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,
0x0,0x0,0x0,0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0x10,0x23,0x18,0x0,0x0,0x0,
0x0,0x2d,0x82,0x1,0x0,0x0,0x0,0xd0,0x22,0x18,0x0,0x0,0x0,0x0,0x2d,0x82,
0x1,0x0,0x7f,0xed,0xdc,0x31,0x15,0x40,0x0,0x0,0x40,0x41,0xde,0x63,0x65,0xd4,
0x47,0xd,0x9,0xb4,0x53,0x41,0x2c,0x9b,0x45,0x7,0xc3,0xbf,0xb,0x72,0x0,0x0,
0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,
0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,
0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,
0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,
0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,
0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,
0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,
0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,
0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,
0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,
0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,
0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,
0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,
0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,
0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,
0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,
0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,
0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,
0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,
0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,
0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,
0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,
0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,
0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,
0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,
0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,
0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,
0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,
0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,
0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,
0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,
0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,
0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,
0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,
0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,
0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,
0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,
0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,
0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,
0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,
0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,
0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,
0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,
0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,
0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,
0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,
0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,
0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,
0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,
0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,
0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,
0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,
0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,
0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,
0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,
0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,
0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,
0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,
0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,
0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,
0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,
0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,
0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,
0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,
0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,
0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,
0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,
0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,
0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,
0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,
0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,
0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,
0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,
0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,
0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,
0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,
0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,
0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,
0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,
0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,
0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,
0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,
0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,
0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,
0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,
0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,
0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,
0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,
0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,
0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,0x0,0x0,0x0,
0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,0x0,0x31,0xc2,
0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,0x0,0x0,0x0,
0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,0x80,0x16,0x61,
0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,0x0,0x0,0x0,
0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,0x40,0x8b,0x30,
0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,0x0,0x0,0x0,
0x80,0x16,0x61,0x0,0x0,0x0,0x0,0xb4,0x8,0x3,0x0,0x0,0x0,0xa0,0x45,0x18,
0x0,0x0,0x0,0x0,0x31,0xc2,0x0,0x0,0x0,0x0,0x68,0x11,0x6,0x0,0x0,0x0,
0x40,0x8b,0x30,0x0,0x0,0x0,0x0,0x5a,0x84,0x1,0x0,0x0,0x0,0x10,0x23,0xc,
0x0,0x0,0x0,0x80,0x96,0xf3,0xb,0x3,0xa6,0xe7,0x58,0xaf,0x7d,0x1e,0xe0,0x1f,
0xdb,0xbd,0xc,0xe3,0xb,0x89,0xb2,0x9c,0x98,
};
static const unsigned char qt_resource_name[] = {
// 5GiB.txt.inflate
0x0,0x10,
0x6,0xf4,0xb1,0x65,
0x0,0x35,
0x0,0x47,0x0,0x69,0x0,0x42,0x0,0x2e,0x0,0x74,0x0,0x78,0x0,0x74,0x0,0x2e,0x0,0x69,0x0,0x6e,0x0,0x66,0x0,0x6c,0x0,0x61,0x0,0x74,0x0,0x65,
};
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,
// :/5GiB.txt.inflate
0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,
0x0,0x0,0x1,0x72,0x1c,0xae,0x5,0x93,
};
#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_inflate)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources_inflate)()
{
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_inflate)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_inflate)()
{
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_inflate)(); }
~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_inflate)(); }
} dummy;
}

View File

@ -0,0 +1,461 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtNetwork/private/qdecompresshelper_p.h>
#include <QtCore/qbytearray.h>
const QString srcDir = QStringLiteral(QT_STRINGIFY(SRC_DIR));
class tst_QDecompressHelper : public QObject
{
Q_OBJECT
private:
void sharedDecompress_data();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void encodingSupported();
void decompress_data();
void decompress();
void partialDecompress_data();
void partialDecompress();
void countAhead_data();
void countAhead();
void countAheadByteDataBuffer_data();
void countAheadByteDataBuffer();
void countAheadPartialRead_data();
void countAheadPartialRead();
void decompressBigData_data();
void decompressBigData();
void archiveBomb_data();
void archiveBomb();
void bigZlib();
};
void tst_QDecompressHelper::initTestCase()
{
Q_INIT_RESOURCE(gzip);
Q_INIT_RESOURCE(inflate);
#if QT_CONFIG(zstd)
Q_INIT_RESOURCE(zstandard);
#endif
}
void tst_QDecompressHelper::cleanupTestCase()
{
#if QT_CONFIG(zstd)
Q_CLEANUP_RESOURCE(zstandard);
#endif
Q_CLEANUP_RESOURCE(inflate);
Q_CLEANUP_RESOURCE(gzip);
}
void tst_QDecompressHelper::encodingSupported()
{
const QByteArrayList &accepted = QDecompressHelper::acceptedEncoding();
QVERIFY(QDecompressHelper::isSupportedEncoding("deflate"));
QVERIFY(accepted.contains("deflate"));
QVERIFY(QDecompressHelper::isSupportedEncoding("gzip"));
QVERIFY(accepted.contains("gzip"));
int expected = 2;
QVERIFY(accepted.indexOf("gzip") < accepted.indexOf("deflate"));
#if QT_CONFIG(brotli)
QVERIFY(QDecompressHelper::isSupportedEncoding("br"));
QVERIFY(accepted.contains("br"));
++expected;
#endif
#if QT_CONFIG(zstd)
QVERIFY(QDecompressHelper::isSupportedEncoding("zstd"));
QVERIFY(accepted.contains("zstd"));
++expected;
#endif
QCOMPARE(expected, accepted.size());
}
void tst_QDecompressHelper::sharedDecompress_data()
{
QTest::addColumn<QByteArray>("encoding");
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QByteArray>("expected");
QTest::newRow("gzip-hello-world")
<< QByteArray("gzip")
<< QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA==")
<< QByteArray("hello world");
// Has two streams. ZLib reports end of stream after the first one, but we need to decompress
// all of the streams to get the full file.
QTest::newRow("gzip-multistream-hello-world")
<< QByteArray("gzip")
<< QByteArray::fromBase64(
"H4sIAAAAAAAAA8tIzcnJBwCGphA2BQAAAB+LCAAAAAAAAANTKM8vykkBAMtCO0oGAAAA")
<< QByteArray("hello world");
QTest::newRow("deflate-hello-world")
<< QByteArray("deflate") << QByteArray::fromBase64("eJzLSM3JyVcozy/KSQEAGgsEXQ==")
<< QByteArray("hello world");
#if QT_CONFIG(brotli)
QTest::newRow("brotli-hello-world")
<< QByteArray("br") << QByteArray::fromBase64("DwWAaGVsbG8gd29ybGQD")
<< QByteArray("hello world");
#endif
#if QT_CONFIG(zstd)
QTest::newRow("zstandard-hello-world")
<< QByteArray("zstd") << QByteArray::fromBase64("KLUv/QRYWQAAaGVsbG8gd29ybGRoaR6y")
<< QByteArray("hello world");
#endif
}
void tst_QDecompressHelper::decompress_data()
{
sharedDecompress_data();
}
void tst_QDecompressHelper::decompress()
{
QDecompressHelper helper;
QFETCH(QByteArray, encoding);
QVERIFY(helper.setEncoding(encoding));
QFETCH(QByteArray, data);
helper.feed(data);
QFETCH(QByteArray, expected);
QByteArray actual(expected.size(), Qt::Uninitialized);
qsizetype read = helper.read(actual.data(), actual.size());
QCOMPARE(read, expected.size());
QCOMPARE(actual, expected);
}
void tst_QDecompressHelper::partialDecompress_data()
{
sharedDecompress_data();
}
// Test that even though we read 1 byte at a time we
// don't lose data from the decoder's internal storage
void tst_QDecompressHelper::partialDecompress()
{
QDecompressHelper helper;
QFETCH(QByteArray, encoding);
QVERIFY(helper.setEncoding(encoding));
QFETCH(QByteArray, data);
helper.feed(data);
QFETCH(QByteArray, expected);
QByteArray actual(expected.size(), Qt::Uninitialized);
qsizetype readTotal = 0;
while (helper.hasData()) {
qsizetype read = helper.read(actual.data() + readTotal, 1);
if (read != 0) // last read might return 0
QCOMPARE(read, 1); // Make sure we don't suddenly read too much
readTotal += read;
}
QCOMPARE(readTotal, expected.size());
QCOMPARE(actual, expected);
}
void tst_QDecompressHelper::countAhead_data()
{
sharedDecompress_data();
}
// Test the double-decompress / count uncompressed size feature.
// We expect that after it has been fed data it will be able to
// tell us the full size of the data when uncompressed.
void tst_QDecompressHelper::countAhead()
{
QDecompressHelper helper;
helper.setCountingBytesEnabled(true);
QFETCH(QByteArray, encoding);
QVERIFY(helper.setEncoding(encoding));
QFETCH(QByteArray, data);
QByteArray firstPart = data.left(data.size() - data.size() / 6);
QVERIFY(firstPart.size() < data.size()); // sanity check
QByteArray secondPart = data.mid(firstPart.size());
helper.feed(firstPart); // feed by copy
// it's a reasonable assumption that after feeding it the first part
// should have decompressed something
QVERIFY(helper.uncompressedSize() > 0);
helper.feed(std::move(secondPart)); // feed by move
QFETCH(QByteArray, expected);
QCOMPARE(helper.uncompressedSize(), expected.size());
QByteArray actual(helper.uncompressedSize(), Qt::Uninitialized);
qsizetype read = helper.read(actual.data(), actual.size());
QCOMPARE(read, expected.size());
QCOMPARE(actual, expected);
}
void tst_QDecompressHelper::countAheadByteDataBuffer_data()
{
sharedDecompress_data();
}
void tst_QDecompressHelper::countAheadByteDataBuffer()
{
QFETCH(QByteArray, encoding);
QFETCH(QByteArray, data);
QFETCH(QByteArray, expected);
{ // feed buffer by const-ref
QDecompressHelper helper;
helper.setCountingBytesEnabled(true);
QVERIFY(helper.setEncoding(encoding));
QByteArray firstPart = data.left(data.size() - data.size() / 6);
QVERIFY(firstPart.size() < data.size()); // sanity check
QByteArray secondPart = data.mid(firstPart.size());
QByteDataBuffer buffer;
buffer.append(firstPart);
buffer.append(secondPart);
helper.feed(buffer);
QCOMPARE(helper.uncompressedSize(), expected.size());
QByteArray actual(helper.uncompressedSize(), Qt::Uninitialized);
qsizetype read = helper.read(actual.data(), actual.size());
QCOMPARE(read, expected.size());
QCOMPARE(actual, expected);
}
{ // Feed buffer by move
QDecompressHelper helper;
helper.setCountingBytesEnabled(true);
QVERIFY(helper.setEncoding(encoding));
QByteArray firstPart = data.left(data.size() - data.size() / 6);
QVERIFY(firstPart.size() < data.size()); // sanity check
QByteArray secondPart = data.mid(firstPart.size());
QByteDataBuffer buffer;
buffer.append(firstPart);
buffer.append(secondPart);
helper.feed(std::move(buffer));
QCOMPARE(helper.uncompressedSize(), expected.size());
QByteArray actual(helper.uncompressedSize(), Qt::Uninitialized);
qsizetype read = helper.read(actual.data(), actual.size());
QCOMPARE(read, expected.size());
QCOMPARE(actual, expected);
}
}
void tst_QDecompressHelper::countAheadPartialRead_data()
{
sharedDecompress_data();
}
// Make sure that the size is adjusted as we read data
void tst_QDecompressHelper::countAheadPartialRead()
{
QDecompressHelper helper;
helper.setCountingBytesEnabled(true);
QFETCH(QByteArray, encoding);
QVERIFY(helper.setEncoding(encoding));
QFETCH(QByteArray, data);
QByteArray firstPart = data.left(data.size() - data.size() / 6);
QVERIFY(firstPart.size() < data.size()); // sanity check
QByteArray secondPart = data.mid(firstPart.size());
helper.feed(firstPart);
// it's a reasonable assumption that after feeding it half the data it
// should have decompressed something
QVERIFY(helper.uncompressedSize() > 0);
helper.feed(secondPart);
QFETCH(QByteArray, expected);
QCOMPARE(helper.uncompressedSize(), expected.size());
QByteArray actual(helper.uncompressedSize(), Qt::Uninitialized);
qsizetype read = helper.read(actual.data(), 5);
QCOMPARE(read, 5);
QCOMPARE(helper.uncompressedSize(), expected.size() - read);
read += helper.read(actual.data() + read, 1);
QCOMPARE(read, 6);
QCOMPARE(helper.uncompressedSize(), expected.size() - read);
read += helper.read(actual.data() + read, expected.size() - read);
QCOMPARE(read, expected.size());
QCOMPARE(actual, expected);
}
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;
// 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 << false;
#if QT_CONFIG(brotli)
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 << false;
QTest::newRow("zstandard-counted-4G") << QByteArray("zstd") << (":/4G.zst") << fourGiB << true;
#endif
}
void tst_QDecompressHelper::decompressBigData()
{
QFETCH(QString, path);
QFile file(path);
QVERIFY(file.open(QIODevice::ReadOnly));
const qint64 third = file.bytesAvailable() / 3;
QDecompressHelper helper;
QFETCH(bool, countAhead);
helper.setCountingBytesEnabled(countAhead);
helper.setDecompressedSafetyCheckThreshold(-1);
QFETCH(QByteArray, encoding);
helper.setEncoding(encoding);
// 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'; };
bool allZero = std::all_of(output.cbegin(), output.cbegin() + bytesRead, isZero);
QVERIFY(allZero);
}
}
QTEST(totalSize, "size");
}
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");
QVERIFY(file.open(QIODevice::ReadOnly));
QByteArray compressedData = file.readAll();
QDecompressHelper helper;
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
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)
#include "tst_qdecompresshelper.moc"

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_FEATURE_private_tests)
return()
endif()
qt_internal_add_test(tst_qhttpheaderparser
SOURCES
tst_qhttpheaderparser.cpp
LIBRARIES
Qt::NetworkPrivate
)

View File

@ -0,0 +1,94 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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"

View File

@ -0,0 +1,19 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qhttpnetworkconnection Test:
#####################################################################
qt_internal_add_test(tst_qhttpnetworkconnection
SOURCES
tst_qhttpnetworkconnection.cpp
LIBRARIES
Qt::CorePrivate
Qt::NetworkPrivate
QT_TEST_SERVER_LIST "apache2"
)

View File

@ -0,0 +1,994 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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
{
Q_OBJECT
public Q_SLOTS:
void finishedReply();
void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail);
void challenge401(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
#ifndef QT_NO_SSL
void sslErrors(const QList<QSslError> &errors);
#endif
private:
bool finishedCalled;
bool finishedWithErrorCalled;
QNetworkReply::NetworkError netErrorCode;
QString (*httpServerName)() = QtNetworkSettings::httpServerName;
private Q_SLOTS:
void initTestCase();
void options_data();
void options();
void get_data();
void get();
void head_data();
void head();
void post_data();
void post();
void put_data();
void put();
void _delete_data();
void _delete();
void trace_data();
void trace();
void _connect_data();
void _connect();
#ifndef QT_NO_COMPRESS
void compression_data();
void compression();
#endif
#ifndef QT_NO_SSL
void ignoresslerror_data();
void ignoresslerror();
#endif
#ifdef QT_NO_SSL
void nossl_data();
void nossl();
#endif
void get401_data();
void get401();
void getMultiple_data();
void getMultiple();
void getMultipleWithPipeliningAndMultiplePriorities();
void getMultipleWithPriorities();
void getEmptyWithPipelining();
void getAndEverythingShouldBePipelined();
void getAndThenDeleteObject();
void getAndThenDeleteObject_data();
void overlappingCloseAndWrite();
};
void tst_QHttpNetworkConnection::initTestCase()
{
#if defined(QT_TEST_SERVER)
QVERIFY(QtNetworkSettings::verifyConnection(httpServerName(), 80));
#else
if (!QtNetworkSettings::verifyTestNetworkSettings())
QSKIP("No network test server available");
#endif
}
void tst_QHttpNetworkConnection::options_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::options()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::head_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962;
QTest::newRow("failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1;
QTest::newRow("failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1;
}
void tst_QHttpNetworkConnection::head()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Head);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
// only check it if it is set and expected
if (reply->contentLength() != -1 && contentLength != -1)
QCOMPARE(reply->contentLength(), qint64(contentLength));
QVERIFY(reply->isFinished());
delete reply;
}
void tst_QHttpNetworkConnection::get_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962 << 25962;
QTest::newRow("failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1 << -1;
QTest::newRow("failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1 << -1;
}
void tst_QHttpNetworkConnection::get()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
// only check it if it is set and expected
if (reply->contentLength() != -1 && contentLength != -1)
QCOMPARE(reply->contentLength(), qint64(contentLength));
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
//do not require server generated error pages to be a fixed size
if (downloadSize != -1)
QCOMPARE(ba.size(), downloadSize);
//but check against content length if it was sent
if (reply->contentLength() != -1)
QCOMPARE(ba.size(), (int)reply->contentLength());
delete reply;
}
void tst_QHttpNetworkConnection::finishedReply()
{
finishedCalled = true;
}
void tst_QHttpNetworkConnection::finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail)
{
Q_UNUSED(detail);
finishedWithErrorCalled = true;
netErrorCode = errorCode;
}
void tst_QHttpNetworkConnection::put_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("succeed");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/dav/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<true;
QTest::newRow("fail-internal") << "http://" << httpServerName() << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
QTest::newRow("fail-host") << "http://" << "invalid.test.qt-project.org" << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
}
void tst_QHttpNetworkConnection::put()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QString, data);
QFETCH(bool, succeed);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Put);
QByteArray array = data.toLatin1();
QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(&array);
bd->setParent(this);
request.setUploadByteDevice(bd);
finishedCalled = false;
finishedWithErrorCalled = false;
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished() || finishedCalled || finishedWithErrorCalled, 30000);
if (reply->isFinished()) {
QByteArray ba;
while (reply->bytesAvailable())
ba += reply->readAny();
} else if(finishedWithErrorCalled) {
if(!succeed) {
delete reply;
return;
} else {
QFAIL("Error in PUT");
}
} else {
QFAIL("PUT timed out");
}
int status = reply->statusCode();
if (status != 200 && status != 201 && status != 204) {
if (succeed) {
qDebug()<<"PUT failed, Status Code:" <<status;
QFAIL("Error in PUT");
}
} else {
if (!succeed) {
qDebug()<<"PUT Should fail, Status Code:" <<status;
QFAIL("Error in PUT");
}
}
delete reply;
}
void tst_QHttpNetworkConnection::post_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QString>("data");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/cgi-bin/echo.cgi" << ushort(80) << false << "7 bytes" << 200 << "OK" << 7 << 7;
QTest::newRow("failure-internal") << "http://" << httpServerName() << "/t" << ushort(80) << false << "Hello World" << 404 << "Not Found" << -1 << -1;
}
void tst_QHttpNetworkConnection::post()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QString, data);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Post);
QByteArray array = data.toLatin1();
QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(&array);
bd->setParent(this);
request.setUploadByteDevice(bd);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
qint64 cLen = reply->contentLength();
if (contentLength != -1) {
// only check the content length if test expected it to be set
if (cLen==-1) {
// HTTP 1.1 server may respond with chunked encoding and in that
// case contentLength is not present in reply -> verify that it is the case
QByteArray transferEnc = reply->headerField("Transfer-Encoding");
QCOMPARE(transferEnc, QByteArray("chunked"));
} else {
QCOMPARE(cLen, qint64(contentLength));
}
}
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
//don't require fixed size for generated error pages
if (downloadSize != -1)
QCOMPARE(ba.size(), downloadSize);
//but do compare with content length if possible
if (cLen != -1)
QCOMPARE(ba.size(), (int)cLen);
delete reply;
}
void tst_QHttpNetworkConnection::_delete_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::_delete()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::trace_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::trace()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::_connect_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::_connect()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::challenge401(const QHttpNetworkRequest &request,
QAuthenticator *authenticator)
{
Q_UNUSED(request);
QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(sender());
if (reply) {
QHttpNetworkConnection *c = reply->connection();
QVariant val = c->property("setCredentials");
if (val.toBool()) {
QVariant user = c->property("username");
QVariant password = c->property("password");
authenticator->setUser(user.toString());
authenticator->setPassword(password.toString());
c->setProperty("setCredentials", false);
}
}
}
void tst_QHttpNetworkConnection::get401_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<bool>("setCredentials");
QTest::addColumn<QString>("username");
QTest::addColumn<QString>("password");
QTest::addColumn<int>("statusCode");
QTest::newRow("no-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << false << "" << ""<<401;
QTest::newRow("invalid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "test" << "test"<<401;
QTest::newRow("valid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
QTest::newRow("digest-authentication-invalid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "wrong" << "wrong"<<401;
QTest::newRow("digest-authentication-valid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
}
void tst_QHttpNetworkConnection::get401()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(bool, setCredentials);
QFETCH(QString, username);
QFETCH(QString, password);
QFETCH(int, statusCode);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
connection.setProperty("setCredentials", setCredentials);
connection.setProperty("username", username);
connection.setProperty("password", password);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
SLOT(challenge401(QHttpNetworkRequest,QAuthenticator*)));
finishedCalled = false;
finishedWithErrorCalled = false;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(finishedCalled || finishedWithErrorCalled, 30000);
QCOMPARE(reply->statusCode(), statusCode);
delete reply;
}
#ifndef QT_NO_COMPRESS
void tst_QHttpNetworkConnection::compression_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::addColumn<bool>("autoCompress");
QTest::addColumn<QString>("contentCoding");
QTest::newRow("success-autogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << -1 << 418321 << true << "";
QTest::newRow("success-nogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << 418321 << 418321 << false << "identity";
QTest::newRow("success-manualgzip-temp") << "http://" << httpServerName() << "/qtest/deflate/rfc2616.html" << ushort(80) << false << 200 << "OK" << 119124 << 119124 << false << "gzip";
}
void tst_QHttpNetworkConnection::compression()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QFETCH(bool, autoCompress);
QFETCH(QString, contentCoding);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path);
if (!autoCompress)
request.setHeaderField("Accept-Encoding", contentCoding.toLatin1());
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
bool isLengthOk = (reply->contentLength() == qint64(contentLength)
|| reply->contentLength() == qint64(downloadSize)
|| reply->contentLength() == -1); //apache2 does not send content-length for compressed pages
QVERIFY(isLengthOk);
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
QCOMPARE(ba.size(), downloadSize);
delete reply;
}
#endif
#ifndef QT_NO_SSL
void tst_QHttpNetworkConnection::sslErrors(const QList<QSslError> &errors)
{
Q_UNUSED(errors);
QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(sender());
if (reply) {
QHttpNetworkConnection *connection = reply->connection();
QVariant val = connection->property("ignoreFromSignal");
if (val.toBool())
connection->ignoreSslErrors();
finishedWithErrorCalled = true;
}
}
void tst_QHttpNetworkConnection::ignoresslerror_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<bool>("ignoreInit");
QTest::addColumn<bool>("ignoreFromSignal");
QTest::addColumn<int>("statusCode");
// This test will work only if the website has ssl errors.
// fluke's certificate is signed by a non-standard authority.
// Since we don't introduce that CA into the SSL verification chain,
// connecting should fail.
QTest::newRow("success-init") << "https://" << httpServerName() << "/" << ushort(443) << true << true << false << 200;
QTest::newRow("success-fromSignal") << "https://" << httpServerName() << "/" << ushort(443) << true << false << true << 200;
QTest::newRow("failure") << "https://" << httpServerName() << "/" << ushort(443) << true << false << false << 100;
}
void tst_QHttpNetworkConnection::ignoresslerror()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(bool, ignoreInit);
QFETCH(bool, ignoreFromSignal);
QFETCH(int, statusCode);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
if (ignoreInit)
connection.ignoreSslErrors();
QCOMPARE(connection.isSsl(), encrypt);
connection.setProperty("ignoreFromSignal", ignoreFromSignal);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
SLOT(sslErrors(QList<QSslError>)));
finishedWithErrorCalled = false;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable() || (statusCode == 100 && finishedWithErrorCalled), 30000);
QCOMPARE(reply->statusCode(), statusCode);
delete reply;
}
#endif
#ifdef QT_NO_SSL
void tst_QHttpNetworkConnection::nossl_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QNetworkReply::NetworkError>("networkError");
QTest::newRow("protocol-error") << "https://" << httpServerName() << "/" << ushort(443) << true <<QNetworkReply::ProtocolUnknownError;
}
void tst_QHttpNetworkConnection::nossl()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QNetworkReply::NetworkError, networkError);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
finishedWithErrorCalled = false;
netErrorCode = QNetworkReply::NoError;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(finishedWithErrorCalled, 30000);
QCOMPARE(netErrorCode, networkError);
delete reply;
}
#endif
void tst_QHttpNetworkConnection::getMultiple_data()
{
QTest::addColumn<quint16>("connectionCount");
QTest::addColumn<bool>("pipeliningAllowed");
// send 100 requests. apache will usually force-close after 100 requests in a single tcp connection
QTest::addColumn<int>("requestCount");
QTest::newRow("6 connections, no pipelining, 100 requests") << quint16(6) << false << 100;
QTest::newRow("1 connection, no pipelining, 100 requests") << quint16(1) << false << 100;
QTest::newRow("6 connections, pipelining allowed, 100 requests") << quint16(6) << true << 100;
QTest::newRow("1 connection, pipelining allowed, 100 requests") << quint16(1) << true << 100;
}
static bool allRepliesFinished(const QList<QHttpNetworkReply*> *_replies)
{
const QList<QHttpNetworkReply*> &replies = *_replies;
for (int i = 0; i < replies.size(); i++)
if (!replies.at(i)->isFinished())
return false;
return true;
}
void tst_QHttpNetworkConnection::getMultiple()
{
QFETCH(quint16, connectionCount);
QFETCH(bool, pipeliningAllowed);
QFETCH(int, requestCount);
QHttpNetworkConnection connection(connectionCount, httpServerName());
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
// depending on what you use the results will vary.
// for the "real" results, use a URL that has "internet latency" for you. Then (6 connections, pipelining) will win.
// for LAN latency, you will possibly get that (1 connection, no pipelining) is the fastest
QHttpNetworkRequest *request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt");
if (pipeliningAllowed)
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
replies.append(reply);
}
QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
qDeleteAll(requests);
qDeleteAll(replies);
}
void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities()
{
quint16 requestCount = 100;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = nullptr;
if (i % 3)
request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Get);
else
request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Head);
if (i % 2 || i % 3)
request->setPipeliningAllowed(true);
if (i % 3)
request->setPriority(QHttpNetworkRequest::HighPriority);
else if (i % 5)
request->setPriority(QHttpNetworkRequest::NormalPriority);
else if (i % 7)
request->setPriority(QHttpNetworkRequest::LowPriority);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
replies.append(reply);
}
QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
int pipelinedCount = 0;
for (int i = 0; i < replies.size(); i++) {
QVERIFY (!(replies.at(i)->request().isPipeliningAllowed() == false
&& replies.at(i)->isPipeliningUsed()));
if (replies.at(i)->isPipeliningUsed())
pipelinedCount++;
}
// We allow pipelining for every 2nd,3rd,4th,6th,8th,9th,10th etc request.
// Assume that half of the requests had been pipelined.
// (this is a very relaxed condition, when last measured 79 of 100
// requests had been pipelined)
QVERIFY(pipelinedCount >= requestCount / 2);
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetMultipleWithPrioritiesReceiver : public QObject
{
Q_OBJECT
public:
int highPrioReceived;
int lowPrioReceived;
int requestCount;
GetMultipleWithPrioritiesReceiver(int rq) : highPrioReceived(0), lowPrioReceived(0), requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
if (reply->request().priority() == QHttpNetworkRequest::HighPriority)
highPrioReceived++;
else if (reply->request().priority() == QHttpNetworkRequest::LowPriority)
lowPrioReceived++;
else
QFAIL("Wrong priority!?");
QVERIFY(highPrioReceived + 7 >= lowPrioReceived);
if (highPrioReceived + lowPrioReceived == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getMultipleWithPriorities()
{
quint16 requestCount = 100;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
GetMultipleWithPrioritiesReceiver receiver(requestCount);
QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = nullptr;
if (i % 3)
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
else
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Head);
if (i % 2)
request->setPriority(QHttpNetworkRequest::HighPriority);
else
request->setPriority(QHttpNetworkRequest::LowPriority);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(40);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetEmptyWithPipeliningReceiver : public QObject
{
Q_OBJECT
public:
int receivedCount;
int requestCount;
GetEmptyWithPipeliningReceiver(int rq) : receivedCount(0),requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
Q_UNUSED(reply);
receivedCount++;
if (receivedCount == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getEmptyWithPipelining()
{
quint16 requestCount = 50;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
GetEmptyWithPipeliningReceiver receiver(requestCount);
QUrl url("http://" + httpServerName() + "/cgi-bin/echo.cgi"); // a get on this = getting an empty file
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = nullptr;
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(20);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetAndEverythingShouldBePipelinedReceiver : public QObject
{
Q_OBJECT
public:
int receivedCount;
int requestCount;
GetAndEverythingShouldBePipelinedReceiver(int rq) : receivedCount(0),requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
Q_UNUSED(reply);
receivedCount++;
if (receivedCount == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getAndEverythingShouldBePipelined()
{
quint16 requestCount = 100;
// use 1 connection.
QHttpNetworkConnection connection(1, httpServerName());
QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
GetAndEverythingShouldBePipelinedReceiver receiver(requestCount);
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = nullptr;
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(40);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
void tst_QHttpNetworkConnection::getAndThenDeleteObject_data()
{
QTest::addColumn<bool>("replyFirst");
QTest::newRow("delete-reply-first") << true;
QTest::newRow("delete-connection-first") << false;
}
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());
QHttpNetworkRequest request("http://" + httpServerName() + "/qtest/bigfile");
QHttpNetworkReply *reply = connection->sendRequest(request);
reply->setDownstreamLimited(true);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode() ,200);
QVERIFY(!reply->isFinished()); // must not be finished
QFETCH(bool, replyFirst);
if (replyFirst) {
delete reply;
delete connection;
} else {
delete connection;
delete reply;
}
}
class TestTcpServer : public QTcpServer
{
Q_OBJECT
public:
TestTcpServer() : errorCodeReports(0)
{
connect(this, &QTcpServer::newConnection, this, &TestTcpServer::onNewConnection);
QVERIFY(listen(QHostAddress::LocalHost));
}
int errorCodeReports;
public slots:
void onNewConnection()
{
QTcpSocket *socket = nextPendingConnection();
if (!socket)
return;
// close socket instantly!
connect(socket, &QTcpSocket::readyRead, socket, &QTcpSocket::close);
}
void onReply(QNetworkReply::NetworkError code)
{
QCOMPARE(code, QNetworkReply::RemoteHostClosedError);
++errorCodeReports;
}
};
void tst_QHttpNetworkConnection::overlappingCloseAndWrite()
{
// server accepts connections, but closes the socket instantly
TestTcpServer server;
QNetworkAccessManager accessManager;
// ten requests are scheduled. All should result in an RemoteHostClosed...
QUrl url;
url.setScheme(QStringLiteral("http"));
url.setHost(server.serverAddress().toString());
url.setPort(server.serverPort());
for (int i = 0; i < 10; ++i) {
QNetworkRequest request(url);
QNetworkReply *reply = accessManager.get(request);
QObject::connect(reply, &QNetworkReply::errorOccurred,
&server, &TestTcpServer::onReply);
}
QTRY_COMPARE(server.errorCodeReports, 10);
}
QTEST_MAIN(tst_QHttpNetworkConnection)
#include "tst_qhttpnetworkconnection.moc"

View File

@ -0,0 +1,18 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qhttpnetworkreply Test:
#####################################################################
qt_internal_add_test(tst_qhttpnetworkreply
SOURCES
tst_qhttpnetworkreply.cpp
LIBRARIES
Qt::CorePrivate
Qt::NetworkPrivate
)

View File

@ -0,0 +1,236 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtCore/QBuffer>
#include <QtCore/QByteArray>
#include <QtCore/QStringBuilder>
#include "private/qhttpnetworkconnection_p.h"
class tst_QHttpNetworkReply: public QObject
{
Q_OBJECT
private Q_SLOTS:
void parseHeader_data();
void parseHeader();
void parseHeaderVerification_data();
void parseHeaderVerification();
void parseEndOfHeader_data();
void parseEndOfHeader();
};
void tst_QHttpNetworkReply::parseHeader_data()
{
QTest::addColumn<QByteArray>("headers");
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() << "");
QTest::newRow("single-field") << QByteArray("Content-Type: text/html; charset=utf-8\r\n")
<< (QStringList() << "Content-Type")
<< (QStringList() << "text/html; charset=utf-8");
QTest::newRow("single-field-continued") << QByteArray("Content-Type: text/html;\r\n"
" 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"
"Content-Encoding: gzip\r\n")
<< (QStringList() << "Content-Type" << "Content-Length" << "Content-Encoding")
<< (QStringList() << "text/html; charset=utf-8" << "1024" << "gzip");
QTest::newRow("multi-field-with-emtpy") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: 1024\r\n"
"Set-Cookie: \r\n"
"Content-Encoding: gzip\r\n")
<< (QStringList() << "Content-Type" << "Content-Length" << "Set-Cookie" << "Content-Encoding")
<< (QStringList() << "text/html; charset=utf-8" << "1024" << "" << "gzip");
QTest::newRow("lws-field") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length:\r\n 1024\r\n"
"Content-Encoding: gzip\r\n")
<< (QStringList() << "Content-Type" << "Content-Length" << "Content-Encoding")
<< (QStringList() << "text/html; charset=utf-8" << "1024" << "gzip");
QTest::newRow("duplicated-field") << QByteArray("Vary: Accept-Language\r\n"
"Vary: Cookie\r\n"
"Vary: User-Agent\r\n")
<< (QStringList() << "Vary")
<< (QStringList() << "Accept-Language, Cookie, User-Agent");
}
void tst_QHttpNetworkReply::parseHeader()
{
QFETCH(QByteArray, headers);
QFETCH(QStringList, fields);
QFETCH(QStringList, values);
QHttpNetworkReply reply;
reply.parseHeader(headers);
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:
explicit TestHeaderSocket(const QByteArray &input) : QAbstractSocket(QAbstractSocket::TcpSocket, nullptr)
{
inputBuffer.setData(input);
inputBuffer.open(QIODevice::ReadOnly | QIODevice::Unbuffered);
open(QIODevice::ReadOnly | QIODevice::Unbuffered);
}
qint64 readData(char *data, qint64 maxlen) override { return inputBuffer.read(data, maxlen); }
QBuffer inputBuffer;
};
class TestHeaderReply : public QHttpNetworkReply
{
public:
QHttpNetworkReplyPrivate *replyPrivate() { return static_cast<QHttpNetworkReplyPrivate *>(d_ptr.data()); }
};
void tst_QHttpNetworkReply::parseEndOfHeader_data()
{
QTest::addColumn<QByteArray>("headers");
QTest::addColumn<qint64>("lengths");
QTest::newRow("CRLFCRLF") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length:\r\n 1024\r\n"
"Content-Encoding: gzip\r\n\r\nHTTPBODY")
<< qint64(90);
QTest::newRow("CRLFLF") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length:\r\n 1024\r\n"
"Content-Encoding: gzip\r\n\nHTTPBODY")
<< qint64(89);
QTest::newRow("LFCRLF") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length:\r\n 1024\r\n"
"Content-Encoding: gzip\n\r\nHTTPBODY")
<< qint64(89);
QTest::newRow("LFLF") << QByteArray("Content-Type: text/html; charset=utf-8\r\n"
"Content-Length:\r\n 1024\r\n"
"Content-Encoding: gzip\n\nHTTPBODY")
<< qint64(88);
}
void tst_QHttpNetworkReply::parseEndOfHeader()
{
QFETCH(QByteArray, headers);
QFETCH(qint64, lengths);
TestHeaderSocket socket(headers);
TestHeaderReply reply;
QHttpNetworkReplyPrivate *replyPrivate = reply.replyPrivate();
qint64 headerBytes = replyPrivate->readHeader(&socket);
QCOMPARE(headerBytes, lengths);
}
QTEST_MAIN(tst_QHttpNetworkReply)
#include "tst_qhttpnetworkreply.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkaccessmanager Test:
#####################################################################
qt_internal_add_test(tst_qnetworkaccessmanager
SOURCES
tst_qnetworkaccessmanager.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,38 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtCore/QDebug>
class tst_QNetworkAccessManager : public QObject
{
Q_OBJECT
public:
tst_QNetworkAccessManager();
private slots:
void alwaysCacheRequest();
};
tst_QNetworkAccessManager::tst_QNetworkAccessManager()
{
}
void tst_QNetworkAccessManager::alwaysCacheRequest()
{
QNetworkAccessManager manager;
QNetworkRequest req;
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
QNetworkReply *reply = manager.get(req);
reply->close();
delete reply;
}
QTEST_MAIN(tst_QNetworkAccessManager)
#include "tst_qnetworkaccessmanager.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkcachemetadata Test:
#####################################################################
qt_internal_add_test(tst_qnetworkcachemetadata
SOURCES
tst_qnetworkcachemetadata.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,309 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QBuffer>
#include <qabstractnetworkcache.h>
#define EXAMPLE_URL "http://user:pass@www.example.com/#foo"
class tst_QNetworkCacheMetaData : public QObject
{
Q_OBJECT
private slots:
void qnetworkcachemetadata_data();
void qnetworkcachemetadata();
void expirationDate_data();
void expirationDate();
void isValid_data();
void isValid();
void lastModified_data();
void lastModified();
void operatorEqual_data();
void operatorEqual();
void operatorEqualEqual_data();
void operatorEqualEqual();
void rawHeaders_data();
void rawHeaders();
void saveToDisk_data();
void saveToDisk();
void url_data();
void url();
void stream();
};
// Subclass that exposes the protected functions.
class SubQNetworkCacheMetaData : public QNetworkCacheMetaData
{
public:};
void tst_QNetworkCacheMetaData::qnetworkcachemetadata_data()
{
}
void tst_QNetworkCacheMetaData::qnetworkcachemetadata()
{
QNetworkCacheMetaData data;
QCOMPARE(data.expirationDate(), QDateTime());
QCOMPARE(data.isValid(), false);
QCOMPARE(data.lastModified(), QDateTime());
QCOMPARE(data.operator!=(QNetworkCacheMetaData()), false);
QNetworkCacheMetaData metaData;
QCOMPARE(data.operator=(metaData), QNetworkCacheMetaData());
QCOMPARE(data.operator==(QNetworkCacheMetaData()), true);
QCOMPARE(data.rawHeaders(), QNetworkCacheMetaData::RawHeaderList());
QCOMPARE(data.saveToDisk(), true);
QCOMPARE(data.url(), QUrl());
data.setExpirationDate(QDateTime());
data.setLastModified(QDateTime());
data.setRawHeaders(QNetworkCacheMetaData::RawHeaderList());
data.setSaveToDisk(false);
data.setUrl(QUrl());
}
void tst_QNetworkCacheMetaData::expirationDate_data()
{
QTest::addColumn<QDateTime>("expirationDate");
QTest::newRow("null") << QDateTime();
QTest::newRow("now") << QDateTime::currentDateTime();
}
// public QDateTime expirationDate() const
void tst_QNetworkCacheMetaData::expirationDate()
{
QFETCH(QDateTime, expirationDate);
SubQNetworkCacheMetaData data;
data.setExpirationDate(expirationDate);
QCOMPARE(data.expirationDate(), expirationDate);
}
Q_DECLARE_METATYPE(QNetworkCacheMetaData)
void tst_QNetworkCacheMetaData::isValid_data()
{
QTest::addColumn<QNetworkCacheMetaData>("data");
QTest::addColumn<bool>("isValid");
QNetworkCacheMetaData metaData;
QTest::newRow("null") << metaData << false;
QNetworkCacheMetaData data1;
data1.setUrl(QUrl(EXAMPLE_URL));
QTest::newRow("valid-1") << data1 << true;
QNetworkCacheMetaData data2;
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar"));
data2.setRawHeaders(headers);
QTest::newRow("valid-2") << data2 << true;
QNetworkCacheMetaData data3;
data3.setLastModified(QDateTime::currentDateTime());
QTest::newRow("valid-3") << data3 << true;
QNetworkCacheMetaData data4;
data4.setExpirationDate(QDateTime::currentDateTime());
QTest::newRow("valid-4") << data4 << true;
QNetworkCacheMetaData data5;
data5.setSaveToDisk(false);
QTest::newRow("valid-5") << data5 << true;
}
// public bool isValid() const
void tst_QNetworkCacheMetaData::isValid()
{
QFETCH(QNetworkCacheMetaData, data);
QFETCH(bool, isValid);
QCOMPARE(data.isValid(), isValid);
}
void tst_QNetworkCacheMetaData::lastModified_data()
{
QTest::addColumn<QDateTime>("lastModified");
QTest::newRow("null") << QDateTime();
QTest::newRow("now") << QDateTime::currentDateTime();
}
// public QDateTime lastModified() const
void tst_QNetworkCacheMetaData::lastModified()
{
QFETCH(QDateTime, lastModified);
SubQNetworkCacheMetaData data;
data.setLastModified(lastModified);
QCOMPARE(data.lastModified(), lastModified);
}
void tst_QNetworkCacheMetaData::operatorEqual_data()
{
QTest::addColumn<QNetworkCacheMetaData>("other");
QTest::newRow("null") << QNetworkCacheMetaData();
QNetworkCacheMetaData data;
data.setUrl(QUrl(EXAMPLE_URL));
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar"));
data.setRawHeaders(headers);
data.setLastModified(QDateTime::currentDateTime());
data.setExpirationDate(QDateTime::currentDateTime());
data.setSaveToDisk(false);
QTest::newRow("valid") << data;
}
// public QNetworkCacheMetaData& operator=(QNetworkCacheMetaData const& other)
void tst_QNetworkCacheMetaData::operatorEqual()
{
QFETCH(QNetworkCacheMetaData, other);
QNetworkCacheMetaData data = other;
QCOMPARE(data, other);
}
void tst_QNetworkCacheMetaData::operatorEqualEqual_data()
{
QTest::addColumn<QNetworkCacheMetaData>("a");
QTest::addColumn<QNetworkCacheMetaData>("b");
QTest::addColumn<bool>("operatorEqualEqual");
QTest::newRow("null") << QNetworkCacheMetaData() << QNetworkCacheMetaData() << true;
QNetworkCacheMetaData data1;
data1.setUrl(QUrl(EXAMPLE_URL));
QTest::newRow("valid-1-1") << data1 << QNetworkCacheMetaData() << false;
QTest::newRow("valid-1-2") << data1 << data1 << true;
QNetworkCacheMetaData data2;
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar"));
data2.setRawHeaders(headers);
QTest::newRow("valid-2-1") << data2 << QNetworkCacheMetaData() << false;
QTest::newRow("valid-2-2") << data2 << data2 << true;
QTest::newRow("valid-2-3") << data2 << data1 << false;
QNetworkCacheMetaData data3;
data3.setLastModified(QDateTime::currentDateTime());
QTest::newRow("valid-3-1") << data3 << QNetworkCacheMetaData() << false;
QTest::newRow("valid-3-2") << data3 << data3 << true;
QTest::newRow("valid-3-3") << data3 << data1 << false;
QTest::newRow("valid-3-4") << data3 << data2 << false;
QNetworkCacheMetaData data4;
data4.setExpirationDate(QDateTime::currentDateTime());
QTest::newRow("valid-4-1") << data4 << QNetworkCacheMetaData() << false;
QTest::newRow("valid-4-2") << data4 << data4 << true;
QTest::newRow("valid-4-3") << data4 << data1 << false;
QTest::newRow("valid-4-4") << data4 << data2 << false;
QTest::newRow("valid-4-5") << data4 << data3 << false;
QNetworkCacheMetaData data5;
data5.setSaveToDisk(false);
QTest::newRow("valid-5-1") << data5 << QNetworkCacheMetaData() << false;
QTest::newRow("valid-5-2") << data5 << data5 << true;
QTest::newRow("valid-5-3") << data5 << data1 << false;
QTest::newRow("valid-5-4") << data5 << data2 << false;
QTest::newRow("valid-5-5") << data5 << data3 << false;
QTest::newRow("valid-5-6") << data5 << data4 << false;
}
// public bool operator==(QNetworkCacheMetaData const& other) const
void tst_QNetworkCacheMetaData::operatorEqualEqual()
{
QFETCH(QNetworkCacheMetaData, a);
QFETCH(QNetworkCacheMetaData, b);
QFETCH(bool, operatorEqualEqual);
QCOMPARE(a == b, operatorEqualEqual);
}
Q_DECLARE_METATYPE(QNetworkCacheMetaData::RawHeaderList)
void tst_QNetworkCacheMetaData::rawHeaders_data()
{
QTest::addColumn<QNetworkCacheMetaData::RawHeaderList>("rawHeaders");
QTest::newRow("null") << QNetworkCacheMetaData::RawHeaderList();
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar"));
QTest::newRow("valie") << headers;
}
// public QNetworkCacheMetaData::RawHeaderList rawHeaders() const
void tst_QNetworkCacheMetaData::rawHeaders()
{
QFETCH(QNetworkCacheMetaData::RawHeaderList, rawHeaders);
SubQNetworkCacheMetaData data;
data.setRawHeaders(rawHeaders);
QCOMPARE(data.rawHeaders(), rawHeaders);
}
void tst_QNetworkCacheMetaData::saveToDisk_data()
{
QTest::addColumn<bool>("saveToDisk");
QTest::newRow("false") << false;
QTest::newRow("true") << true;
}
// public bool saveToDisk() const
void tst_QNetworkCacheMetaData::saveToDisk()
{
QFETCH(bool, saveToDisk);
SubQNetworkCacheMetaData data;
data.setSaveToDisk(saveToDisk);
QCOMPARE(data.saveToDisk(), saveToDisk);
}
void tst_QNetworkCacheMetaData::url_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QUrl>("expected");
QTest::newRow("null") << QUrl() << QUrl();
QTest::newRow("valid") << QUrl(EXAMPLE_URL) << QUrl("http://user@www.example.com/");
}
// public QUrl url() const
void tst_QNetworkCacheMetaData::url()
{
QFETCH(QUrl, url);
QFETCH(QUrl, expected);
SubQNetworkCacheMetaData data;
data.setUrl(url);
QCOMPARE(data.url(), expected);
}
void tst_QNetworkCacheMetaData::stream()
{
QNetworkCacheMetaData data;
data.setUrl(QUrl(EXAMPLE_URL));
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("foo", "Bar"));
data.setRawHeaders(headers);
data.setLastModified(QDateTime::currentDateTime());
data.setExpirationDate(QDateTime::currentDateTime());
data.setSaveToDisk(false);
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
QDataStream stream(&buffer);
stream << data;
buffer.seek(0);
QNetworkCacheMetaData data2;
stream >> data2;
QCOMPARE(data2, data);
}
QTEST_MAIN(tst_QNetworkCacheMetaData)
#include "tst_qnetworkcachemetadata.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkcookie Test:
#####################################################################
qt_internal_add_test(tst_qnetworkcookie
SOURCES
tst_qnetworkcookie.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,683 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtNetwork/QNetworkCookie>
#include <QtCore/QDateTime>
#include <QtCore/QTimeZone>
#include <QtCore/QUrl>
class tst_QNetworkCookie: public QObject
{
Q_OBJECT
private slots:
void getterSetter();
void parseSingleCookie_data();
void parseSingleCookie();
void parseMultipleCookies_data();
void parseMultipleCookies();
void sameSite();
};
void tst_QNetworkCookie::getterSetter()
{
QNetworkCookie cookie;
QNetworkCookie otherCookie;
QCOMPARE(cookie, otherCookie);
QCOMPARE(cookie, otherCookie);
QVERIFY(!(cookie != otherCookie));
QVERIFY(!cookie.isSecure());
QVERIFY(cookie.isSessionCookie());
QVERIFY(!cookie.expirationDate().isValid());
QVERIFY(cookie.domain().isEmpty());
QVERIFY(cookie.path().isEmpty());
QVERIFY(cookie.name().isEmpty());
QVERIFY(cookie.value().isEmpty());
// change something
cookie.setName("foo");
QVERIFY(!(cookie == otherCookie));
QVERIFY(cookie != otherCookie);
// test getters and setters:
QCOMPARE(cookie.name(), QByteArray("foo"));
cookie.setName(0);
QVERIFY(cookie.name().isEmpty());
cookie.setValue("bar");
QCOMPARE(cookie.value(), QByteArray("bar"));
cookie.setValue(0);
QVERIFY(cookie.value().isEmpty());
cookie.setPath("/");
QCOMPARE(cookie.path(), QString("/"));
cookie.setPath(QString());
QVERIFY(cookie.path().isEmpty());
cookie.setDomain(".tld");
QCOMPARE(cookie.domain(), QString(".tld"));
cookie.setDomain(QString());
QVERIFY(cookie.domain().isEmpty());
QDateTime now = QDateTime::currentDateTime();
cookie.setExpirationDate(now);
QCOMPARE(cookie.expirationDate(), now);
QVERIFY(!cookie.isSessionCookie());
cookie.setExpirationDate(QDateTime());
QVERIFY(!cookie.expirationDate().isValid());
QVERIFY(cookie.isSessionCookie());
cookie.setSecure(true);
QVERIFY(cookie.isSecure());
cookie.setSecure(false);
QVERIFY(!cookie.isSecure());
QCOMPARE(cookie, otherCookie);
}
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");
QTest::newRow("basic") << "a=" << cookie;
QTest::newRow("basic2") << " a=" << cookie;
QTest::newRow("basic3") << "a= " << cookie;
QTest::newRow("basic4") << " a= " << cookie;
QTest::newRow("basic5") << " a= ;" << cookie;
QTest::newRow("basic6") << " a=; " << cookie;
QTest::newRow("basic7") << " a =" << cookie;
QTest::newRow("basic8") << " a = " << cookie;
cookie.setValue("b");
QTest::newRow("with-value") << "a=b" << cookie;
QTest::newRow("with-value2") << " a=b" << cookie;
QTest::newRow("with-value3") << "a=b " << cookie;
QTest::newRow("with-value4") << " a=b " << cookie;
QTest::newRow("with-value5") << " a=b ;" << cookie;
QTest::newRow("with-value6") << "a =b" << cookie;
QTest::newRow("with-value7") << "a= b" << cookie;
QTest::newRow("with-value8") << "a = b" << cookie;
QTest::newRow("with-value9") << "a = b " << cookie;
cookie.setValue("\",\"");
QTest::newRow("with-value-with-special1") << "a = \",\" " << cookie;
cookie.setValue("\"");
QTest::newRow("with-value-with-special2") << "a = \";\" " << cookie;
cookie.setValue("\" \"");
QTest::newRow("with-value-with-special3") << "a = \" \" " << cookie;
cookie.setValue("\"\\\"\"");
QTest::newRow("with-value-with-special4") << "a = \"\\\"\" " << cookie;
cookie.setValue("\"\\\"a, b");
QTest::newRow("with-value-with-special5") << "a = \"\\\"a, b; c\\\"\"" << cookie;
// RFC6265 states that cookie values shouldn't contain commas, but we still allow them
// since they are only reserved for future compatibility and other browsers do the same.
cookie.setValue(",");
QTest::newRow("with-value-with-special6") << "a = ," << cookie;
cookie.setValue(",b");
QTest::newRow("with-value-with-special7") << "a = ,b" << cookie;
cookie.setValue("b,");
QTest::newRow("with-value-with-special8") << "a = b," << cookie;
cookie.setValue("b c");
QTest::newRow("with-value-with-whitespace") << "a = b c" << cookie;
cookie.setValue("\"b\"");
QTest::newRow("quoted-value") << "a = \"b\"" << cookie;
cookie.setValue("\"b c\"");
QTest::newRow("quoted-value-with-whitespace") << "a = \"b c\"" << cookie;
cookie.setValue("b");
cookie.setSecure(true);
QTest::newRow("secure") << "a=b;secure" << cookie;
QTest::newRow("secure2") << "a=b;secure " << cookie;
QTest::newRow("secure3") << "a=b; secure" << cookie;
QTest::newRow("secure4") << "a=b; secure " << cookie;
QTest::newRow("secure5") << "a=b ;secure" << cookie;
QTest::newRow("secure6") << "a=b ;secure " << cookie;
QTest::newRow("secure7") << "a=b ; secure " << cookie;
QTest::newRow("secure8") << "a=b; Secure" << cookie;
cookie.setSecure(false);
cookie.setHttpOnly(true);
QTest::newRow("httponly") << "a=b;httponly" << cookie;
QTest::newRow("httponly2") << "a=b;HttpOnly " << cookie;
QTest::newRow("httponly3") << "a=b; httpOnly" << cookie;
QTest::newRow("httponly4") << "a=b; HttpOnly " << cookie;
QTest::newRow("httponly5") << "a=b ;HttpOnly" << cookie;
QTest::newRow("httponly6") << "a=b ;httponly " << cookie;
QTest::newRow("httponly7") << "a=b ; HttpOnly " << cookie;
QTest::newRow("httponly8") << "a=b; Httponly" << cookie;
cookie.setHttpOnly(false);
cookie.setPath("/");
QTest::newRow("path1") << "a=b;path=/" << cookie;
QTest::newRow("path2") << "a=b; path=/" << cookie;
QTest::newRow("path3") << "a=b;path=/ " << cookie;
QTest::newRow("path4") << "a=b;path =/ " << cookie;
QTest::newRow("path5") << "a=b;path= / " << cookie;
QTest::newRow("path6") << "a=b;path = / " << cookie;
QTest::newRow("path7") << "a=b;Path = / " << cookie;
QTest::newRow("path8") << "a=b; PATH = / " << cookie;
cookie.setPath("/foo");
QTest::newRow("path9") << "a=b;path=/foo" << cookie;
// some weird paths:
cookie.setPath("/with%20spaces");
QTest::newRow("path-with-spaces") << "a=b;path=/with%20spaces" << cookie;
QTest::newRow("path-with-spaces2") << "a=b; path=/with%20spaces " << cookie;
cookie.setPath(QString());
QTest::newRow("invalid-path-with-spaces3") << "a=b; path=\"/with spaces\"" << cookie;
QTest::newRow("invalid-path-with-spaces4") << "a=b; path = \"/with spaces\" " << cookie;
cookie.setPath("/with spaces");
QTest::newRow("path-with-spaces5") << "a=b; path=/with spaces" << cookie;
QTest::newRow("path-with-spaces6") << "a=b; path = /with spaces " << cookie;
cookie.setPath("/with\"Quotes");
QTest::newRow("path-with-quotes") << "a=b; path = /with\"Quotes" << cookie;
cookie.setPath(QString());
QTest::newRow("invalid-path-with-quotes2") << "a=b; path = \"/with\\\"Quotes\"" << cookie;
cookie.setPath(QString::fromUtf8("/R\303\251sum\303\251"));
QTest::newRow("path-with-utf8") << QString::fromUtf8("a=b;path=/R\303\251sum\303\251") << cookie;
cookie.setPath("/R%C3%A9sum%C3%A9");
QTest::newRow("path-with-utf8-2") << "a=b;path=/R%C3%A9sum%C3%A9" << cookie;
cookie.setPath(QString());
cookie.setDomain("qt-project.org");
QTest::newRow("plain-domain1") << "a=b;domain=qt-project.org" << cookie;
QTest::newRow("plain-domain2") << "a=b; domain=qt-project.org " << cookie;
QTest::newRow("plain-domain3") << "a=b;domain=QT-PROJECT.ORG" << cookie;
QTest::newRow("plain-domain4") << "a=b;DOMAIN = QT-PROJECT.ORG" << cookie;
cookie.setDomain(".qt-project.org");
QTest::newRow("dot-domain1") << "a=b;domain=.qt-project.org" << cookie;
QTest::newRow("dot-domain2") << "a=b; domain=.qt-project.org" << cookie;
QTest::newRow("dot-domain3") << "a=b; domain=.QT-PROJECT.ORG" << cookie;
QTest::newRow("dot-domain4") << "a=b; Domain = .QT-PROJECT.ORG" << cookie;
cookie.setDomain(QString::fromUtf8(".d\303\270gn\303\245pent.troll.no"));
QTest::newRow("idn-domain1") << "a=b;domain=.xn--dgnpent-gxa2o.troll.no" << cookie;
QTest::newRow("idn-domain2") << QString::fromUtf8("a=b;domain=.d\303\270gn\303\245pent.troll.no") << cookie;
QTest::newRow("idn-domain3") << "a=b;domain=.XN--DGNPENT-GXA2O.TROLL.NO" << cookie;
QTest::newRow("idn-domain4") << QString::fromUtf8("a=b;domain=.D\303\230GN\303\205PENT.troll.NO") << cookie;
cookie.setDomain(".qt-project.org");
cookie.setPath("/");
QTest::newRow("two-fields") << "a=b;domain=.qt-project.org;path=/" << cookie;
QTest::newRow("two-fields2") << "a=b; domain=.qt-project.org; path=/" << cookie;
QTest::newRow("two-fields3") << "a=b; domain=.qt-project.org ; path=/ " << cookie;
QTest::newRow("two-fields4") << "a=b;path=/; domain=.qt-project.org" << cookie;
QTest::newRow("two-fields5") << "a=b; path=/ ; domain=.qt-project.org" << cookie;
QTest::newRow("two-fields6") << "a=b; path= / ; domain =.qt-project.org" << cookie;
cookie.setSecure(true);
QTest::newRow("three-fields") << "a=b;domain=.qt-project.org;path=/;secure" << cookie;
QTest::newRow("three-fields2") << "a=b;secure;path=/;domain=.qt-project.org" << cookie;
QTest::newRow("three-fields3") << "a=b;secure;domain=.qt-project.org; path=/" << cookie;
QTest::newRow("three-fields4") << "a = b;secure;domain=.qt-project.org; path=/" << cookie;
cookie = QNetworkCookie();
cookie.setName("a");
cookie.setValue("b");
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(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(utc(1989, 4, 14, 3, 20));
QTest::newRow("time-0") << "a=b;expires=14 Apr 89 03:20" << cookie;
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(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(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(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(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(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(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(utc(1989, 1, 1, 1, 1));
QTest::newRow("months-1") << "a=b;expires=Jan 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 2, 1, 1, 1));
QTest::newRow("months-2") << "a=b;expires=Feb 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 3, 1, 1, 1));
QTest::newRow("months-3") << "a=b;expires=mar 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 4, 1, 1, 1));
QTest::newRow("months-4") << "a=b;expires=Apr 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 5, 1, 1, 1));
QTest::newRow("months-5") << "a=b;expires=May 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 6, 1, 1, 1));
QTest::newRow("months-6") << "a=b;expires=Jun 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 7, 1, 1, 1));
QTest::newRow("months-7") << "a=b;expires=Jul 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 8, 1, 1, 1));
QTest::newRow("months-8") << "a=b;expires=Aug 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 9, 1, 1, 1));
QTest::newRow("months-9") << "a=b;expires=Sep 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 10, 1, 1, 1));
QTest::newRow("months-10") << "a=b;expires=Oct 1 89 1:1" << cookie;
cookie.setExpirationDate(utc(1989, 11, 1, 1, 1));
QTest::newRow("months-11") << "a=b;expires=Nov 1 89 1:1" << cookie;
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(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(utc(2024, 2, 29, 1, 1));
QTest::newRow("months-16") << "a=b;expires=2024 29 Feb 1:1" << cookie;
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(utc(1989, 1, 1));
QTest::newRow("zoneoffset-0") << "a=b;expires=Jan 1 89 8:0 PST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-1") << "a=b;expires=Jan 1 89 8:0 PDT" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-2") << "a=b;expires=Jan 1 89 7:0 MST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-3") << "a=b;expires=Jan 1 89 7:0 MDT" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-4") << "a=b;expires=Jan 1 89 6:0 CST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-5") << "a=b;expires=Jan 1 89 6:0 CDT" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-6") << "a=b;expires=Jan 1 89 5:0 EST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-7") << "a=b;expires=Jan 1 89 5:0 EDT" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-8") << "a=b;expires=Jan 1 89 4:0 AST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-9") << "a=b;expires=Jan 1 89 3:0 NST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-10") << "a=b;expires=Jan 1 89 0:0 GMT" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-11") << "a=b;expires=Jan 1 89 0:0 BST" << cookie;
cookie.setExpirationDate(utc(1989, 1, 2));
QTest::newRow("zoneoffset-12") << "a=b;expires=Jan 1 89 23:0 MET" << cookie;
cookie.setExpirationDate(utc(1989, 1, 2));
QTest::newRow("zoneoffset-13") << "a=b;expires=Jan 1 89 22:0 EET" << cookie;
cookie.setExpirationDate(utc(1989, 1, 2));
QTest::newRow("zoneoffset-14") << "a=b;expires=Jan 1 89 15:0 JST" << cookie;
// extra offsets
cookie.setExpirationDate(utc(1989, 1, 2));
QTest::newRow("zoneoffset-15") << "a=b;expires=Jan 1 89 15:0 JST+1" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1, 1));
QTest::newRow("zoneoffset-16") << "a=b;expires=Jan 1 89 0:0 GMT+1" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-17") << "a=b;expires=Jan 1 89 1:0 GMT-1" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1, 1));
QTest::newRow("zoneoffset-18") << "a=b;expires=Jan 1 89 0:0 GMT+01" << cookie;
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(utc(1989, 1, 1));
QTest::newRow("zoneoffset-20") << "a=b;expires=Jan 1 89 0:0 GMT+015" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-21") << "a=b;expires=Jan 1 89 0:0 GM" << cookie;
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(utc(1989, 1, 1, 1));
QTest::newRow("zoneoffset-23") << "a=b;expires=Jan 1 89 0:0 +1" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1, 1));
QTest::newRow("zoneoffset-24") << "a=b;expires=Jan 1 89 0:0 +01" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1, 1, 1));
QTest::newRow("zoneoffset-25") << "a=b;expires=Jan 1 89 0:0 +0101" << cookie;
cookie.setExpirationDate(utc(1989, 1, 1));
QTest::newRow("zoneoffset-26") << "a=b;expires=Jan 1 89 1:0 -1" << cookie;
// Y2k
cookie.setExpirationDate(utc(2000, 1, 1));
QTest::newRow("year-0") << "a=b;expires=Jan 1 00 0:0" << cookie;
cookie.setExpirationDate(utc(1970, 1, 1));
QTest::newRow("year-1") << "a=b;expires=Jan 1 70 0:0" << cookie;
cookie.setExpirationDate(utc(1971, 1, 1));
QTest::newRow("year-2") << "a=b;expires=Jan 1 71 0:0" << cookie;
// Day, month, year
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;
QTest::newRow("date-3") << "a=b;expires=Jan 2 13 0:0" << cookie;
QTest::newRow("date-4") << "a=b;expires=Jan 2, 13 0:0" << cookie;
QTest::newRow("date-5") << "a=b;expires=1-2-13 0:0" << cookie;
QTest::newRow("date-6") << "a=b;expires=1/2/13 0:0" << cookie;
// Known Year, determine month and day
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(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(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(utc(2021, 03, 26));
QTest::newRow("month-0") << "a=b;expires=26/03/21 0:0" << cookie;
cookie.setExpirationDate(utc(2015, 12, 30, 16, 25));
QTest::newRow("month-1") << "a=b;expires=wed 16:25pm December 2015 30" << cookie;
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(utc(2013, 10, 2));
QTest::newRow("ambiguousd-0") << "a=b;expires=10/2/13 0:0" << cookie;
cookie.setExpirationDate(utc(2013, 2, 10));
QTest::newRow("ambiguousd-1") << "a=b;expires=2/10/13 0:0" << cookie;
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(utc(2003, 2, 10));
QTest::newRow("ambiguousd-3") << "a=b;expires=2/10/3 0:0" << cookie;
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(utc(1989, 4, 14, 3, 20));
QTest::newRow("firefox-0") << "a=b;expires=14 Apr 89 03:20" << cookie;
cookie.setExpirationDate(utc(1989, 4, 14, 3, 20));
QTest::newRow("firefox-1") << "a=b;expires=14 Apr 89 03:20 GMT" << cookie;
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(utc(1989, 3, 17, 4, 1));
QTest::newRow("firefox-3") << "a=b;expires=Fri, 17 Mar 89 4:01 GMT" << cookie;
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(utc(1989, 1, 16, 17, 42));
QTest::newRow("firefox-5") << "a=b;expires=Mon Jan 16 16:12 +0130 1989" << cookie;
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(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(utc(1993, 8, 22, 22, 59));
QTest::newRow("firefox-8") << "a=b;expires=22-AUG-1993 10:59pm" << cookie;
cookie.setExpirationDate(utc(1993, 8, 22, 12, 59));
QTest::newRow("firefox-9") << "a=b;expires=22-AUG-1993 12:59am" << cookie;
cookie.setExpirationDate(utc(1993, 8, 22, 12, 59));
QTest::newRow("firefox-10") << "a=b;expires=22-AUG-1993 12:59 PM" << cookie;
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(utc(1995, 6, 21, 16, 24, 34));
QTest::newRow("firefox-12") << "a=b;expires=06/21/95 04:24:34 PM" << cookie;
cookie.setExpirationDate(utc(1995, 6, 20, 21, 7));
QTest::newRow("firefox-13") << "a=b;expires=20/06/95 21:07" << cookie;
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(utc(2030, 2, 25, 1, 1));
QTest::newRow("fuzz-0") << "a=b; expires=30 -000002 1:1 25;" << cookie;
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(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(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(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(utc(2030, 2, 20, 1, 1));
QTest::newRow("fuzz-5") << "a=b; expires=30 02 20 1:1" << cookie;
cookie.setExpirationDate(utc(2021, 12, 22, 1, 1));
QTest::newRow("fuzz-6") << "a=b; expires=2021 12 22 1:1" << cookie;
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(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(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(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(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(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(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(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;
QTest::newRow("path+expires") << "a=b; path=/;expires=Wed, 09-Nov-1999 23:12:40 GMT " << cookie;
cookie.setDomain(".qt-project.org");
QTest::newRow("full") << "a=b; domain=.qt-project.org;expires=Wed, 09-Nov-1999 23:12:40 GMT;path=/" << cookie;
QTest::newRow("full2") << "a=b;path=/; expires=Wed, 09-Nov-1999 23:12:40 GMT ;domain=.qt-project.org" << cookie;
// cookies obtained from the network:
cookie = QNetworkCookie("__siteid", "1");
cookie.setPath("/");
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");
cookie.setPath("/");
cookie.setDomain("mail.yahoo.com");
QTest::newRow("network3") << "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; path=/; domain=mail.yahoo.com" << cookie;
cookie = QNetworkCookie("__ac", "\"c2hhdXNtYW46U2FTYW80Wm8%3D\"");
cookie.setPath("/");
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(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
cookie = QNetworkCookie("xploreCookies", "{\"desktopReportingUrl\":\"null\",\"userIds\":\"1938850\",\"contactEmail\":\"NA\",\"contactName\":\"NA\",\"enterpriseLicenseId\":\"0\",\"openUrlTxt\":\"NA\",\"customerSurvey\":\"NA\",\"standardsLicenseId\":\"0\",\"openUrl\":\"NA\",\"smallBusinessLicenseId\":\"0\", \"instImage\":\"1938850_univ skovde.gif\",\"isMember\":\"false\",\"products\":\"IEL|VDE|\",\"openUrlImgLoc\":\"NA\",\"isIp\":\"true\",\"instName\": \"University of XXXXXX\",\"oldSessionKey\":\"LmZ8hlXo5a9uZx2Fnyw1564T1ZOWMnf3Dk*oDx2FQHwbg6RYefyrhC8PL2wx3Dx3D-18x2d8723DyqXRnkILyGpmx2Fh9wgx3Dx3Dc2lAOhHqGSKT78xxGwXZxxCgx3Dx3D-XrL4FnIlW2OPkqtVJq0LkQx3Dx3D-tujOLwhFqtX7Pa7HGqmCXQx3Dx3D\", \"isChargebackUser\":\"false\",\"isInst\":\"true\"}");
cookie.setDomain(".ieee.org");
cookie.setPath("/");
QTest::newRow("network6") << "xploreCookies={\"desktopReportingUrl\":\"null\",\"userIds\":\"1938850\",\"contactEmail\":\"NA\",\"contactName\":\"NA\",\"enterpriseLicenseId\":\"0\",\"openUrlTxt\":\"NA\",\"customerSurvey\":\"NA\",\"standardsLicenseId\":\"0\",\"openUrl\":\"NA\",\"smallBusinessLicenseId\":\"0\", \"instImage\":\"1938850_univ skovde.gif\",\"isMember\":\"false\",\"products\":\"IEL|VDE|\",\"openUrlImgLoc\":\"NA\",\"isIp\":\"true\",\"instName\": \"University of XXXXXX\",\"oldSessionKey\":\"LmZ8hlXo5a9uZx2Fnyw1564T1ZOWMnf3Dk*oDx2FQHwbg6RYefyrhC8PL2wx3Dx3D-18x2d8723DyqXRnkILyGpmx2Fh9wgx3Dx3Dc2lAOhHqGSKT78xxGwXZxxCgx3Dx3D-XrL4FnIlW2OPkqtVJq0LkQx3Dx3D-tujOLwhFqtX7Pa7HGqmCXQx3Dx3D\", \"isChargebackUser\":\"false\",\"isInst\":\"true\"}; domain=.ieee.org; path=/" << cookie;
}
void tst_QNetworkCookie::parseSingleCookie()
{
QFETCH(QString, cookieString);
QFETCH(QNetworkCookie, expectedCookie);
QList<QNetworkCookie> result = QNetworkCookie::parseCookies(cookieString.toUtf8());
//QEXPECT_FAIL("network2", "QDateTime parsing problem: the date is beyond year 8000", Abort);
QCOMPARE(result.size(), 1);
QCOMPARE(result.at(0), expectedCookie);
result = QNetworkCookie::parseCookies(result.at(0).toRawForm());
QCOMPARE(result.size(), 1);
// Drop any millisecond information, if there's any
QDateTime dt = expectedCookie.expirationDate();
if (dt.isValid()) {
QTime t = dt.time();
dt.setTime(t.addMSecs(-t.msec()));
expectedCookie.setExpirationDate(dt);
}
QCOMPARE(result.at(0), expectedCookie);
}
void tst_QNetworkCookie::parseMultipleCookies_data()
{
QTest::addColumn<QString>("cookieString");
QTest::addColumn<QList<QNetworkCookie> >("expectedCookies");
QList<QNetworkCookie> list;
QTest::newRow("empty") << "" << list;
// these are technically empty cookies:
QTest::newRow("invalid-01") << ";" << list;
QTest::newRow("invalid-02") << " " << list;
QTest::newRow("invalid-03") << " ," << list;
QTest::newRow("invalid-04") << ";;,, ; ; , , ; , ;" << list;
// these are really invalid:
// reason: malformed NAME=VALUE pair
QTest::newRow("invalid-05") << "foo" << list;
QTest::newRow("invalid-06") << "=b" << list;
QTest::newRow("invalid-07") << ";path=/" << list;
// these should be accepted by RFC6265 but ignoring the expires field
// reason: malformed expiration date string
QNetworkCookie datelessCookie;
datelessCookie.setName("a");
datelessCookie.setValue("b");
list << datelessCookie;
QTest::newRow("expiration-empty") << "a=b;expires=" << list;
QTest::newRow("expiration-invalid-01") << "a=b;expires=foobar" << list;
QTest::newRow("expiration-invalid-02") << "a=b;expires=foobar, abc" << list;
QTest::newRow("expiration-invalid-03") << "a=b; expires=123" << list; // used to ASSERT
datelessCookie.setPath("/");
list.clear();
list << datelessCookie;
QTest::newRow("expiration-invalid-04") << "a=b;expires=foobar, dd-mmm-yyyy hh:mm:ss GMT; path=/" << list;
QTest::newRow("expiration-invalid-05") << "a=b;expires=foobar, 32-Caz-1999 24:01:60 GMT; path=/" << list;
// cookies obtained from the network:
QNetworkCookie cookie;
cookie = QNetworkCookie("id", "51706646077999719");
cookie.setDomain(".bluestreak.com");
cookie.setPath("/");
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|");
list << cookie;
cookie.setName("adv");
cookie.setValue(QByteArray());
list << cookie;
QTest::newRow("network1") << "id=51706646077999719 bb=\"K14144t\"_AAQ\"ototrK_A_ttot44AQ4KwoRQtoto| adv=; Domain=.bluestreak.com; expires=Tuesday 05-Dec-2017 09:11:07 GMT; path=/;" << list;
QNetworkCookie cookieA;
cookieA.setName("a");
cookieA.setValue("b");
QNetworkCookie cookieB;
cookieB.setName("c");
cookieB.setValue("d");
// NewLine
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;
QTest::newRow("real-2") << "a=b; expires=Mar 10 07:00:00 2009 GMT, Tue\nc=d; expires=Fri Mar 20 07:00:00 2009 GMT" << list;
// Match firefox's behavior
cookieA.setPath("/foo");
list = QList<QNetworkCookie>() << cookieA << cookieB;
QTest::newRow("real-3") << "a=b; expires=Mar 10 07:00:00 2009 GMT, Tue; path=/foo\nc=d; expires=Fri Mar 20 07:00:00 2009 GMT" << list;
// do not accept cookies with non-alphanumeric characters in domain field (QTBUG-11029)
cookie = QNetworkCookie("NonAlphNumDomName", "NonAlphNumDomValue");
cookie.setDomain("!@#$%^&*();:."); // the ';' is actually problematic, because it is a separator
list = QList<QNetworkCookie>();
QTest::newRow("domain-non-alpha-numeric") << "NonAlphNumDomName=NonAlphNumDomValue; domain=!@#$%^&*()" << list;
}
void tst_QNetworkCookie::parseMultipleCookies()
{
QFETCH(QString, cookieString);
QFETCH(QList<QNetworkCookie>, expectedCookies);
QList<QNetworkCookie> result = QNetworkCookie::parseCookies(cookieString.toUtf8());
QEXPECT_FAIL("network1", "Apparently multiple cookies set in one request (and an invalid date)", Abort);
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"

View File

@ -0,0 +1,19 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkcookiejar Test:
#####################################################################
# Collect test data
list(APPEND test_data "parser.json" "testdata/publicsuffix/public_suffix_list.dafsa")
qt_internal_add_test(tst_qnetworkcookiejar
SOURCES
tst_qnetworkcookiejar.cpp
LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
TESTDATA ${test_data}
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,562 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtNetwork/QNetworkCookieJar>
#include <QtNetwork/QNetworkCookie>
#include <QtNetwork/QNetworkRequest>
#if QT_CONFIG(topleveldomain)
#include "private/qtldurl_p.h"
#endif
class tst_QNetworkCookieJar: public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void getterSetter();
void setCookiesFromUrl_data();
void setCookiesFromUrl();
void cookiesForUrl_data();
void cookiesForUrl();
#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(topleveldomain)
void effectiveTLDs_data();
void effectiveTLDs();
#endif
void rfc6265_data();
void rfc6265();
private:
QSharedPointer<QTemporaryDir> m_dataDir;
};
class MyCookieJar: public QNetworkCookieJar
{
public:
~MyCookieJar() override;
using QNetworkCookieJar::allCookies;
using QNetworkCookieJar::setAllCookies;
};
MyCookieJar::~MyCookieJar() = default;
void tst_QNetworkCookieJar::getterSetter()
{
MyCookieJar jar;
QVERIFY(jar.allCookies().isEmpty());
QList<QNetworkCookie> list;
QNetworkCookie cookie;
cookie.setName("a");
list << cookie;
jar.setAllCookies(list);
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");
QTest::addColumn<QNetworkCookie>("newCookie");
QTest::addColumn<QString>("referenceUrl");
QTest::addColumn<QList<QNetworkCookie> >("expectedResult");
QTest::addColumn<bool>("setCookies");
QList<QNetworkCookie> preset;
QList<QNetworkCookie> result;
QNetworkCookie cookie;
cookie.setName("a");
cookie.setPath("/");
cookie.setDomain(".foo.tld");
result += cookie;
QTest::newRow("just-add") << preset << cookie << "http://www.foo.tld" << result << true;
preset = result;
QTest::newRow("replace-1") << preset << cookie << "http://www.foo.tld" << result << true;
cookie.setValue("bc");
result.clear();
result += cookie;
QTest::newRow("replace-2") << preset << cookie << "http://www.foo.tld" << result << true;
preset = result;
cookie.setName("d");
result += cookie;
QTest::newRow("append") << preset << cookie << "http://www.foo.tld" << result << true;
cookie = preset.at(0);
result = preset;
cookie.setPath("/something");
result += cookie;
QTest::newRow("diff-path") << preset << cookie << "http://www.foo.tld/something" << result << true;
preset.clear();
preset += cookie;
cookie.setPath("/");
QTest::newRow("diff-path-order") << preset << cookie << "http://www.foo.tld" << result << true;
preset.clear();
result.clear();
QNetworkCookie finalCookie = cookie;
cookie.setDomain("foo.tld");
finalCookie.setDomain(".foo.tld");
result += finalCookie;
QTest::newRow("should-add-dot-prefix") << preset << cookie << "http://www.foo.tld" << result << true;
result.clear();
cookie.setDomain("");
finalCookie.setDomain("www.foo.tld");
result += finalCookie;
QTest::newRow("should-set-default-domain") << preset << cookie << "http://www.foo.tld" << result << true;
// security test:
result.clear();
preset.clear();
cookie.setDomain("something.completely.different");
QTest::newRow("security-domain-1") << preset << cookie << "http://www.foo.tld" << result << false;
// we want the cookie to be accepted although the path does not match, see QTBUG-5815
cookie.setDomain(".foo.tld");
cookie.setPath("/something");
result += cookie;
QTest::newRow("security-path-1") << preset << cookie << "http://www.foo.tld" << result << true;
// check effective TLDs
// 1. co.uk is an effective TLD, should be denied
result.clear();
preset.clear();
cookie.setPath("/");
cookie.setDomain(".co.uk");
QTest::newRow("effective-tld1-denied") << preset << cookie << "http://something.co.uk" << result << false;
cookie.setDomain("co.uk");
QTest::newRow("effective-tld1-denied2") << preset << cookie << "http://something.co.uk" << result << false;
cookie.setDomain(".something.co.uk");
result += cookie;
QTest::newRow("effective-tld1-accepted") << preset << cookie << "http://something.co.uk" << result << true;
// 2. anything .ck is an effective TLD ('*.ck'), but 'www.ck' is an exception
result.clear();
preset.clear();
cookie.setDomain(".ck");
QTest::newRow("effective-tld.ck-denied") << preset << cookie << "http://foo.ck" << result << false;
result.clear();
preset.clear();
cookie.setDomain(".foo.ck");
result += cookie;
QTest::newRow("effective-tld2-accepted2") << preset << cookie << "http://foo.ck" << result << true;
result.clear();
QTest::newRow("effective-tld2-denied2") << preset << cookie << "http://www.foo.ck" << result << false;
QTest::newRow("effective-tld2-denied3") << preset << cookie << "http://www.anything.foo.ck" << result << false;
cookie.setDomain(".www.ck");
result += cookie;
QTest::newRow("effective-tld2-accepted") << preset << cookie << "http://www.www.ck" << result << true;
result.clear();
preset.clear();
cookie.setDomain("127.0.0.1");
result += cookie;
QTest::newRow("IPv4-address-as-domain") << preset << cookie << "http://127.0.0.1/" << result << true;
result.clear();
preset.clear();
cookie.setDomain("fe80::250:56ff:fec0:1");
result += cookie;
QTest::newRow("IPv6-address-as-domain") << preset << cookie << "http://[fe80::250:56ff:fec0:1]/" << result << true;
// setting the defaults:
finalCookie = cookie;
finalCookie.setPath("/something/");
finalCookie.setDomain("www.foo.tld");
cookie.setPath("");
cookie.setDomain("");
result.clear();
result += finalCookie;
QTest::newRow("defaults-1") << preset << cookie << "http://www.foo.tld/something/" << result << true;
finalCookie.setPath("/");
result.clear();
result += finalCookie;
QTest::newRow("defaults-2") << preset << cookie << "http://www.foo.tld" << result << true;
// security test: do not accept cookie domains like ".com" nor ".com." (see RFC 2109 section 4.3.2)
result.clear();
preset.clear();
cookie.setDomain(".com");
QTest::newRow("rfc2109-4.3.2-ex3") << preset << cookie << "http://x.foo.com" << result << false;
result.clear();
preset.clear();
cookie.setDomain(".com.");
QTest::newRow("rfc2109-4.3.2-ex3-2") << preset << cookie << "http://x.foo.com" << result << false;
// When using a TLD as a hostname the hostname should still get cookies (QTBUG-52040)
// ... and nothing else should get the cookies.
result.clear();
preset.clear();
cookie.setPath("/");
cookie.setDomain(".support");
result += cookie;
QTest::newRow("TLD-as-domain-accepted") << preset << cookie << "http://support" << result << true;
result.clear();
QTest::newRow("TLD-as-domain-rejected") << preset << cookie << "http://a.support" << result << false;
// Now test with no domain in the cookie, use the domain from the url (matching TLD)
cookie.setDomain("support");
result += cookie;
cookie.setDomain("");
QTest::newRow("TLD-as-domain-accepted2") << preset << cookie << "http://support" << result << true;
}
void tst_QNetworkCookieJar::setCookiesFromUrl()
{
QFETCH(QList<QNetworkCookie>, preset);
QFETCH(QNetworkCookie, newCookie);
QFETCH(QString, referenceUrl);
QFETCH(QList<QNetworkCookie>, expectedResult);
QFETCH(bool, setCookies);
QList<QNetworkCookie> cookieList;
cookieList += newCookie;
MyCookieJar jar;
jar.setAllCookies(preset);
QCOMPARE(jar.setCookiesFromUrl(cookieList, referenceUrl), setCookies);
QList<QNetworkCookie> result = jar.allCookies();
foreach (QNetworkCookie cookie, expectedResult) {
QVERIFY2(result.contains(cookie), cookie.toRawForm());
result.removeAll(cookie);
}
QVERIFY2(result.isEmpty(), QTest::toString(result));
}
void tst_QNetworkCookieJar::cookiesForUrl_data()
{
QTest::addColumn<QList<QNetworkCookie> >("allCookies");
QTest::addColumn<QString>("url");
QTest::addColumn<QList<QNetworkCookie> >("expectedResult");
QList<QNetworkCookie> allCookies;
QList<QNetworkCookie> result;
QTest::newRow("no-cookies") << allCookies << "http://foo.bar/" << result;
QNetworkCookie cookie;
cookie.setName("a");
cookie.setPath("/web");
cookie.setDomain(".qt-project.org");
allCookies += cookie;
QTest::newRow("no-match-1") << allCookies << "http://foo.bar/" << result;
QTest::newRow("no-match-2") << allCookies << "http://foo.bar/web" << result;
QTest::newRow("no-match-3") << allCookies << "http://foo.bar/web/wiki" << result;
QTest::newRow("no-match-4") << allCookies << "http://qt-project.org" << result;
QTest::newRow("no-match-5") << allCookies << "http://qt-project.org" << result;
QTest::newRow("no-match-6") << allCookies << "http://qt-project.org/webinar" << result;
QTest::newRow("no-match-7") << allCookies << "http://qt-project.org/webinar" << result;
QTest::newRow("no-match-8") << allCookies << "http://qt-project.org./web" << result;
QTest::newRow("no-match-9") << allCookies << "http://qt-project.org./web" << result;
result = allCookies;
QTest::newRow("match-1") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("match-2") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("match-3") << allCookies << "http://qt-project.org/web/content" << result;
QTest::newRow("match-4") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("match-5") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("match-6") << allCookies << "http://qt-project.org/web/content" << result;
cookie.setPath("/web/wiki");
allCookies += cookie;
// exact same results as before:
QTest::newRow("one-match-1") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("one-match-2") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("one-match-3") << allCookies << "http://qt-project.org/web/content" << result;
QTest::newRow("one-match-4") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("one-match-5") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("one-match-6") << allCookies << "http://qt-project.org/web/content" << result;
result.prepend(cookie); // longer path, it must match first
QTest::newRow("two-matches-1") << allCookies << "http://qt-project.org/web/wiki" << result;
QTest::newRow("two-matches-2") << allCookies << "http://qt-project.org/web/wiki" << result;
// invert the order;
allCookies.clear();
allCookies << result.at(1) << result.at(0);
QTest::newRow("two-matches-3") << allCookies << "http://qt-project.org/web/wiki" << result;
QTest::newRow("two-matches-4") << allCookies << "http://qt-project.org/web/wiki" << result;
// expired cookie
allCookies.clear();
cookie.setExpirationDate(QDateTime::fromString("09-Nov-1999", "dd-MMM-yyyy"));
allCookies += cookie;
result.clear();
QTest::newRow("exp-match-1") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("exp-match-2") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("exp-match-3") << allCookies << "http://qt-project.org/web/content" << result;
QTest::newRow("exp-match-4") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("exp-match-5") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("exp-match-6") << allCookies << "http://qt-project.org/web/content" << result;
// path matching
allCookies.clear();
QNetworkCookie anotherCookie;
anotherCookie.setName("a");
anotherCookie.setPath("/web");
anotherCookie.setDomain(".qt-project.org");
allCookies += anotherCookie;
result.clear();
QTest::newRow("path-unmatch-1") << allCookies << "http://qt-project.org/" << result;
QTest::newRow("path-unmatch-2") << allCookies << "http://qt-project.org/something/else" << result;
result += anotherCookie;
QTest::newRow("path-match-1") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("path-match-2") << allCookies << "http://qt-project.org/web/" << result;
QTest::newRow("path-match-3") << allCookies << "http://qt-project.org/web/content" << result;
// secure cookies
allCookies.clear();
result.clear();
QNetworkCookie secureCookie;
secureCookie.setName("a");
secureCookie.setPath("/web");
secureCookie.setDomain(".qt-project.org");
secureCookie.setSecure(true);
allCookies += secureCookie;
QTest::newRow("no-match-secure-1") << allCookies << "http://qt-project.org/web" << result;
QTest::newRow("no-match-secure-2") << allCookies << "http://qt-project.org/web" << result;
result += secureCookie;
QTest::newRow("match-secure-1") << allCookies << "https://qt-project.org/web" << result;
QTest::newRow("match-secure-2") << allCookies << "https://qt-project.org/web" << result;
// domain ending in .
allCookies.clear();
result.clear();
QNetworkCookie cookieDot;
cookieDot.setDomain(".example.com.");
cookieDot.setName("a");
allCookies += cookieDot;
QTest::newRow("no-match-domain-dot") << allCookies << "http://example.com" << result;
result += cookieDot;
QTest::newRow("match-domain-dot") << allCookies << "http://example.com." << result;
// Root path in cookie, empty url path
allCookies.clear();
QNetworkCookie rootCookie;
rootCookie.setName("a");
rootCookie.setPath("/");
rootCookie.setDomain("qt-project.org");
allCookies += rootCookie;
result.clear();
result += rootCookie;
QTest::newRow("root-path-match") << allCookies << "http://qt-project.org" << result;
// Domain in cookie happens to match a TLD
allCookies.clear();
QNetworkCookie tldCookie;
tldCookie.setDomain(".support");
tldCookie.setName("a");
tldCookie.setValue("b");
allCookies += tldCookie;
result.clear();
result += tldCookie;
QTest::newRow("tld-cookie-match") << allCookies << "http://support/" << result;
result.clear();
QTest::newRow("tld-cookie-no-match") << allCookies << "http://a.support/" << result;
}
void tst_QNetworkCookieJar::cookiesForUrl()
{
QFETCH(QList<QNetworkCookie>, allCookies);
QFETCH(QString, url);
QFETCH(QList<QNetworkCookie>, expectedResult);
MyCookieJar jar;
jar.setAllCookies(allCookies);
QList<QNetworkCookie> result = jar.cookiesForUrl(url);
QCOMPARE(result, expectedResult);
}
// This test requires private API.
#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(topleveldomain)
void tst_QNetworkCookieJar::effectiveTLDs_data()
{
QTest::addColumn<QString>("domain");
QTest::addColumn<bool>("isTLD");
QTest::newRow("yes1") << "com" << true;
QTest::newRow("yes2") << "de" << 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("yes10") << "hk.com" << true;
QTest::newRow("yes11") << "hk.org" << true;
QTest::newRow("no1") << "anything.com" << false;
QTest::newRow("no2") << "anything.de" << false;
QTest::newRow("no3") << "eselsberg.ulm.museum" << false;
QTest::newRow("no4") << "noe.krodsherad.no" << false;
QTest::newRow("no5") << "2.1.bg" << false;
QTest::newRow("no6") << "foo.com.cn" << false;
QTest::newRow("no7") << "something.org.ws" << false;
QTest::newRow("no8") << "teatime.co.uk" << false;
QTest::newRow("no9") << "bla" << false;
QTest::newRow("no10") << "bla.bla" << false;
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 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
QTest::newRow("yes-specialchars1") << QString::fromUtf16(s1) << 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("no-specialchars1") << QString::fromUtf16(s1).prepend("something") << false;
QTest::newRow("no-specialchars3") << QString::fromUtf16(s3).prepend("foo") << false;
QTest::newRow("no-specialchars4") << QString::fromUtf16(s4).prepend("bar") << 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("exception1") << "pref.iwate.jp" << false;
QTest::newRow("exception2") << "omanpost.om" << false;
QTest::newRow("exception3") << "omantel.om" << false;
QTest::newRow("exception4") << "gobiernoelectronico.ar" << false;
QTest::newRow("exception5") << "pref.ishikawa.jp" << false;
QTest::newRow("yes-wildcard1") << "*.jm" << true;
QTest::newRow("yes-wildcard1.5") << "anything.jm" << true;
QTest::newRow("yes-wildcard2") << "something.kh" << true;
QTest::newRow("no-wildcard3") << "whatever.uk" << false; // was changed at some point
QTest::newRow("yes-wildcard4") << "anything.sendai.jp" << true;
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;
}
void tst_QNetworkCookieJar::effectiveTLDs()
{
QFETCH(QString, domain);
QFETCH(bool, isTLD);
QCOMPARE(qIsEffectiveTLD(domain), isTLD);
}
#endif
void tst_QNetworkCookieJar::rfc6265_data()
{
QTest::addColumn<QStringList>("received");
QTest::addColumn<QList<QNetworkCookie> >("sent");
QTest::addColumn<QString>("sentTo");
QFile file(QFINDTESTDATA("parser.json"));
QVERIFY(file.open(QFile::ReadOnly | QFile::Text));
QJsonDocument document;
document = QJsonDocument::fromJson(file.readAll());
QVERIFY(!document.isNull());
QVERIFY(document.isArray());
for (const QJsonValue testCase : document.array()) {
QJsonObject testObject = testCase.toObject();
//"test" - the test case name
QString testCaseName = testObject.value("test").toString();
if (testCaseName.toLower().startsWith("disabled"))
continue;
//"received" - the cookies received from the server
const QJsonArray received = testObject.value("received").toArray();
QStringList receivedList;
for (const QJsonValue receivedCookie : received)
receivedList.append(receivedCookie.toString());
//"sent" - the cookies sent back to the server
const QJsonArray sent = testObject.value("sent").toArray();
QList<QNetworkCookie> sentList;
for (const QJsonValue sentCookie : sent) {
QJsonObject sentCookieObject = sentCookie.toObject();
QNetworkCookie cookie;
cookie.setName(sentCookieObject.value("name").toString().toUtf8());
cookie.setValue(sentCookieObject.value("value").toString().toUtf8());
sentList.append(cookie);
}
//"sent-to" - the relative url where cookies are sent
QTest::newRow(qPrintable(testCaseName)) << receivedList << sentList << testObject.value("sent-to").toString();
}
}
void tst_QNetworkCookieJar::rfc6265()
{
QFETCH(QStringList, received);
QFETCH(QList<QNetworkCookie>, sent);
QFETCH(QString, sentTo);
QUrl receivedUrl("http://home.example.org:8888/cookie-parser");
QUrl sentUrl("http://home.example.org:8888/cookie-parser-result");
if (!sentTo.isEmpty())
sentUrl = receivedUrl.resolved(sentTo);
QNetworkCookieJar jar;
QList<QNetworkCookie> receivedCookies;
foreach (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.size(), sent.size());
bool ok = true;
for (int i = 0; i < cookiesToSend.size(); i++) {
if (cookiesToSend.at(i).name() != sent.at(i).name()) {
ok = false;
break;
}
if (cookiesToSend.at(i).value() != sent.at(i).value()) {
ok = false;
break;
}
}
if (!ok) {
QNetworkRequest r(sentUrl);
r.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookiesToSend));
QString actual = QString::fromUtf8(r.rawHeader("Cookie"));
r.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(sent));
QString expected = QString::fromUtf8(r.rawHeader("Cookie"));
QVERIFY2(ok, qPrintable(QString("Expected: %1\nActual: %2").arg(expected).arg(actual)));
}
}
QTEST_MAIN(tst_QNetworkCookieJar)
#include "tst_qnetworkcookiejar.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkdiskcache Test:
#####################################################################
qt_internal_add_test(tst_qnetworkdiskcache
SOURCES
tst_qnetworkdiskcache.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,818 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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 15
class tst_QNetworkDiskCache : public QObject
{
Q_OBJECT
public:
tst_QNetworkDiskCache();
public slots:
void accessAfterRemoveReadyReadSlot();
void setCookieHeaderMetaDataChangedSlot();
private slots:
void initTestCase();
void cleanupTestCase();
void qnetworkdiskcache_data();
void qnetworkdiskcache();
void prepare();
void cacheSize();
void clear();
void data_data();
void data();
void metaData();
void remove();
void accessAfterRemove(); // QTBUG-17400
void setCookieHeader(); // QTBUG-41514
void setCacheDirectory_data();
void setCacheDirectory();
void updateMetaData();
void fileMetaData();
void expire();
void oldCacheVersionFile_data();
void oldCacheVersionFile();
void streamVersion_data();
void streamVersion();
void sync();
void crashWhenParentingCache();
private:
QTemporaryDir tempDir;
QUrl url; // used by accessAfterRemove(), setCookieHeader()
QNetworkDiskCache *diskCache; // used by accessAfterRemove()
QNetworkAccessManager *manager; // used by setCookieHeader()
};
// FIXME same as in tst_qnetworkreply.cpp .. could be unified
// Does not work for POST/PUT!
class MiniHttpServer: public QTcpServer
{
Q_OBJECT
public:
QTcpSocket *client; // always the last one that was received
QByteArray dataToTransmit;
QByteArray receivedData;
bool doClose;
bool multiple;
int totalConnections;
MiniHttpServer(const QByteArray &data) : client(0), dataToTransmit(data), doClose(true), multiple(false), totalConnections(0)
{
listen();
connect(this, SIGNAL(newConnection()), this, SLOT(doAccept()));
}
public slots:
void doAccept()
{
client = nextPendingConnection();
client->setParent(this);
++totalConnections;
connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
}
void readyReadSlot()
{
receivedData += client->readAll();
int doubleEndlPos = receivedData.indexOf("\r\n\r\n");
if (doubleEndlPos != -1) {
// multiple requests incoming. remove the bytes of the current one
if (multiple)
receivedData.remove(0, doubleEndlPos+4);
client->write(dataToTransmit);
if (doClose) {
client->disconnectFromHost();
disconnect(client, 0, this, 0);
client = nullptr;
}
}
}
};
// Subclass that exposes the protected functions.
class SubQNetworkDiskCache : public QNetworkDiskCache
{
public:
~SubQNetworkDiskCache()
{
if (!cacheDirectory().isEmpty() && clearOnDestruction)
clear();
}
QNetworkCacheMetaData call_fileMetaData(QString const &fileName)
{ return SubQNetworkDiskCache::fileMetaData(fileName); }
qint64 call_expire()
{ return SubQNetworkDiskCache::expire(); }
void setupWithOne(const QString &path, const QUrl &url, const QNetworkCacheMetaData &metaData = QNetworkCacheMetaData())
{
setCacheDirectory(path);
QIODevice *d = nullptr;
if (metaData.isValid()) {
d = prepare(metaData);
} else {
QNetworkCacheMetaData m;
m.setUrl(url);
QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
QNetworkCacheMetaData::RawHeaderList list;
list.append(header);
m.setRawHeaders(list);
d = prepare(m);
}
d->write("Hello World!");
insert(d);
}
void setClearCacheOnDestruction(bool value) { clearOnDestruction = value; }
private:
bool clearOnDestruction = true;
};
tst_QNetworkDiskCache::tst_QNetworkDiskCache()
: tempDir(QDir::tempPath() + "/tst_qnetworkdiskcache.XXXXXX")
{
}
// This will be called before the first test function is executed.
// It is only called once.
void tst_QNetworkDiskCache::initTestCase()
{
QVERIFY(tempDir.isValid());
SubQNetworkDiskCache cache;
cache.setCacheDirectory(tempDir.path());
}
// This will be called after the last test function is executed.
// It is only called once.
void tst_QNetworkDiskCache::cleanupTestCase()
{
QDir workingDir("foo");
if (workingDir.exists())
workingDir.removeRecursively();
}
void tst_QNetworkDiskCache::qnetworkdiskcache_data()
{
}
void tst_QNetworkDiskCache::qnetworkdiskcache()
{
QUrl url(EXAMPLE_URL);
SubQNetworkDiskCache cache;
QCOMPARE(cache.cacheDirectory(), QString());
QCOMPARE(cache.cacheSize(), qint64(0));
cache.clear();
QCOMPARE(cache.metaData(QUrl()), QNetworkCacheMetaData());
QCOMPARE(cache.remove(QUrl()), false);
QCOMPARE(cache.remove(url), false);
cache.insert((QIODevice*)0);
cache.setCacheDirectory(QString());
cache.updateMetaData(QNetworkCacheMetaData());
cache.prepare(QNetworkCacheMetaData());
QCOMPARE(cache.call_fileMetaData(QString()), QNetworkCacheMetaData());
// leave one hanging around...
QNetworkDiskCache badCache;
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
badCache.prepare(metaData);
badCache.setCacheDirectory(tempDir.path());
badCache.prepare(metaData);
}
void tst_QNetworkDiskCache::prepare()
{
SubQNetworkDiskCache cache;
cache.setCacheDirectory(tempDir.path());
QUrl url(EXAMPLE_URL);
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
cache.prepare(metaData);
cache.remove(url);
}
// public qint64 cacheSize() const
void tst_QNetworkDiskCache::cacheSize()
{
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);
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));
}
static QStringList countFiles(const QString dir)
{
QStringList list;
QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot);
QDirIterator it(dir, filter, QDirIterator::Subdirectories);
while (it.hasNext())
list.append(it.next());
return list;
}
// public void clear()
void tst_QNetworkDiskCache::clear()
{
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
QVERIFY(cache.cacheSize() > qint64(0));
QString cacheDirectory = cache.cacheDirectory();
QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
cache.clear();
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).size(), NUM_SUBDIRECTORIES + 3);
cache.clear();
QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
}
}
Q_DECLARE_METATYPE(QNetworkCacheMetaData)
void tst_QNetworkDiskCache::data_data()
{
QTest::addColumn<QNetworkCacheMetaData>("data");
QTest::newRow("null") << QNetworkCacheMetaData();
QUrl url(EXAMPLE_URL);
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
metaData.setRawHeaders(headers);
QTest::newRow("non-null") << metaData;
}
// public QIODevice* data(QUrl const& url)
void tst_QNetworkDiskCache::data()
{
QFETCH(QNetworkCacheMetaData, data);
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url, data);
for (int i = 0; i < 3; ++i) {
QIODevice *d = cache.data(url);
QVERIFY(d);
QCOMPARE(d->readAll(), QByteArray("Hello World!"));
delete d;
}
}
// public QNetworkCacheMetaData metaData(QUrl const& url)
void tst_QNetworkDiskCache::metaData()
{
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
metaData.setRawHeaders(headers);
metaData.setLastModified(QDateTime::currentDateTime());
metaData.setExpirationDate(QDateTime::currentDateTime());
metaData.setSaveToDisk(true);
cache.setupWithOne(tempDir.path(), url, metaData);
for (int i = 0; i < 3; ++i) {
QNetworkCacheMetaData cacheMetaData = cache.metaData(url);
QVERIFY(cacheMetaData.isValid());
QCOMPARE(metaData, cacheMetaData);
}
}
// public bool remove(QUrl const& url)
void tst_QNetworkDiskCache::remove()
{
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
QString cacheDirectory = cache.cacheDirectory();
QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
cache.remove(url);
QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 2);
}
void tst_QNetworkDiskCache::accessAfterRemove() // QTBUG-17400
{
QByteArray data("HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n"
"\r\n"
"a");
MiniHttpServer server(data);
QNetworkAccessManager *manager = new QNetworkAccessManager();
SubQNetworkDiskCache subCache;
subCache.setCacheDirectory(QLatin1String("cacheDir"));
diskCache = &subCache;
manager->setCache(&subCache);
url = QUrl("http://127.0.0.1:" + QString::number(server.serverPort()));
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
connect(reply, SIGNAL(readyRead()), this, SLOT(accessAfterRemoveReadyReadSlot()));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
reply->deleteLater();
manager->deleteLater();
}
void tst_QNetworkDiskCache::accessAfterRemoveReadyReadSlot()
{
diskCache->remove(url); // this used to cause a crash later on
}
void tst_QNetworkDiskCache::setCookieHeader() // QTBUG-41514
{
SubQNetworkDiskCache *cache = new SubQNetworkDiskCache();
url = QUrl("http://localhost:4/cookieTest.html"); // hopefully no one is running an HTTP server on port 4
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("Set-Cookie", "aaa=bbb"));
metaData.setRawHeaders(headers);
metaData.setSaveToDisk(true);
QDateTime expirationDate = QDateTime::currentDateTime().addSecs(500);
metaData.setExpirationDate(expirationDate);
cache->setupWithOne(tempDir.path(), url, metaData);
manager = new QNetworkAccessManager();
manager->setCache(cache);
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
QNetworkReply *reply = manager->get(request);
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(setCookieHeaderMetaDataChangedSlot()));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(reply->error(), QNetworkReply::NoError);
reply->deleteLater();
manager->deleteLater();
}
void tst_QNetworkDiskCache::setCookieHeaderMetaDataChangedSlot()
{
QList<QNetworkCookie> actualCookieJar = manager->cookieJar()->cookiesForUrl(url);
QVERIFY(!actualCookieJar.empty());
}
void tst_QNetworkDiskCache::setCacheDirectory_data()
{
QTest::addColumn<QString>("cacheDir");
QTest::newRow("null") << QString();
QDir dir("foo");
QTest::newRow("foo") << dir.absolutePath() + QString("/");
}
// public void setCacheDirectory(QString const& cacheDir)
void tst_QNetworkDiskCache::setCacheDirectory()
{
QFETCH(QString, cacheDir);
SubQNetworkDiskCache cache;
cache.setCacheDirectory(cacheDir);
QCOMPARE(cache.cacheDirectory(), cacheDir);
}
// public void updateMetaData(QNetworkCacheMetaData const& metaData)
void tst_QNetworkDiskCache::updateMetaData()
{
QUrl url(EXAMPLE_URL);
SubQNetworkDiskCache cache;
cache.setupWithOne(tempDir.path(), url);
QNetworkCacheMetaData metaData = cache.metaData(url);
metaData.setLastModified(QDateTime::currentDateTime());
cache.updateMetaData(metaData);
QNetworkCacheMetaData newMetaData = cache.metaData(url);
QCOMPARE(newMetaData, metaData);
}
// protected QNetworkCacheMetaData fileMetaData(QString const& fileName)
void tst_QNetworkDiskCache::fileMetaData()
{
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
url.setPassword(QString());
url.setFragment(QString());
QString cacheDirectory = cache.cacheDirectory();
QStringList list = countFiles(cacheDirectory);
QCOMPARE(list.size(), NUM_SUBDIRECTORIES + 3);
foreach(QString fileName, list) {
QFileInfo info(fileName);
if (info.isFile()) {
QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
QCOMPARE(metaData.url(), url);
}
}
QTemporaryFile file(cacheDirectory + "/qt_temp.XXXXXX");
if (file.open()) {
QNetworkCacheMetaData metaData = cache.call_fileMetaData(file.fileName());
QVERIFY(!metaData.isValid());
}
}
// protected qint64 expire()
void tst_QNetworkDiskCache::expire()
{
SubQNetworkDiskCache cache;
cache.setCacheDirectory(tempDir.path());
QCOMPARE(cache.call_expire(), (qint64)0);
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
QVERIFY(cache.call_expire() > (qint64)0);
qint64 limit = (1024 * 1024 / 4) * 5;
cache.setMaximumCacheSize(limit);
qint64 max = cache.maximumCacheSize();
QCOMPARE(max, limit);
for (int i = 0; i < 10; ++i) {
if (i % 3 == 0)
QTest::qWait(2000);
QNetworkCacheMetaData m;
m.setUrl(QUrl("http://localhost:4/" + QString::number(i)));
QIODevice *d = cache.prepare(m);
QString bigString;
bigString.fill(QLatin1Char('Z'), (1024 * 1024 / 4));
d->write(bigString.toLatin1().data());
cache.insert(d);
QVERIFY(cache.call_expire() < max);
}
QString cacheDirectory = cache.cacheDirectory();
QStringList list = countFiles(cacheDirectory);
QStringList cacheList;
foreach(QString fileName, list) {
QFileInfo info(fileName);
if (info.isFile()) {
QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
cacheList.append(metaData.url().toString());
}
}
std::sort(cacheList.begin(), cacheList.end());
for (int i = 0; i < cacheList.size(); ++i) {
QString fileName = cacheList[i];
QCOMPARE(fileName, QLatin1String("http://localhost:4/") + QString::number(i + 6));
}
}
void tst_QNetworkDiskCache::oldCacheVersionFile_data()
{
QTest::addColumn<int>("pass");
QTest::newRow("0") << 0;
QTest::newRow("1") << 1;
}
void tst_QNetworkDiskCache::oldCacheVersionFile()
{
QFETCH(int, pass);
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
if (pass == 0) {
QString name;
{
QTemporaryFile file(cache.cacheDirectory() + "/XXXXXX.d");
file.setAutoRemove(false);
QVERIFY2(file.open(), qPrintable(file.errorString()));
QDataStream out(&file);
out << qint32(0xe8);
out << qint32(2);
name = file.fileName();
file.close();
}
QVERIFY(QFile::exists(name));
QNetworkCacheMetaData metaData = cache.call_fileMetaData(name);
QVERIFY(!metaData.isValid());
QVERIFY(!QFile::exists(name));
} else {
QStringList files = countFiles(cache.cacheDirectory());
QCOMPARE(files.size(), NUM_SUBDIRECTORIES + 3);
// find the file
QString cacheFile;
foreach (QString file, files) {
QFileInfo info(file);
if (info.isFile())
cacheFile = file;
}
QVERIFY(QFile::exists(cacheFile));
QFile file(cacheFile);
QVERIFY(file.open(QFile::ReadWrite));
QDataStream out(&file);
out << qint32(0xe8);
out << qint32(2);
file.close();
QIODevice *device = cache.data(url);
QVERIFY(!device);
QVERIFY(!QFile::exists(cacheFile));
}
}
void tst_QNetworkDiskCache::streamVersion_data()
{
QTest::addColumn<int>("version");
QTest::newRow("Qt 5.1") << int(QDataStream::Qt_5_1);
QDataStream ds;
QTest::newRow("current") << ds.version();
QTest::newRow("higher than current") << ds.version() + 1;
}
void tst_QNetworkDiskCache::streamVersion()
{
SubQNetworkDiskCache cache;
QUrl url(EXAMPLE_URL);
cache.setupWithOne(tempDir.path(), url);
QString cacheFile;
// find the file
QStringList files = countFiles(cache.cacheDirectory());
foreach (const QString &file, files) {
QFileInfo info(file);
if (info.isFile()) {
cacheFile = file;
break;
}
}
QFile file(cacheFile);
QVERIFY(file.open(QFile::ReadWrite|QIODevice::Truncate));
QDataStream out(&file);
QFETCH(int, version);
if (version < out.version())
out.setVersion(version);
out << qint32(0xe8); // cache magic
// Following code works only for cache file version 8 and should be updated on version change
out << qint32(8);
out << qint32(version);
QNetworkCacheMetaData md;
md.setUrl(url);
QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
QNetworkCacheMetaData::RawHeaderList list;
list.append(header);
md.setRawHeaders(list);
md.setLastModified(QDateTime::currentDateTimeUtc().toOffsetFromUtc(3600));
out << md;
bool compressed = true;
out << compressed;
QByteArray data("Hello World!");
out << qCompress(data);
file.close();
QNetworkCacheMetaData cachedMetaData = cache.call_fileMetaData(cacheFile);
if (version > out.version()) {
QVERIFY(!cachedMetaData.isValid());
QVERIFY(!QFile::exists(cacheFile));
} else {
QVERIFY(cachedMetaData.isValid());
QVERIFY(QFile::exists(cacheFile));
QIODevice *dataDevice = cache.data(url);
QVERIFY(dataDevice != 0);
QByteArray cachedData = dataDevice->readAll();
QCOMPARE(cachedData, data);
}
}
class Runner : public QThread
{
public:
Runner(const QString& cachePath)
: QThread()
, other(0)
, cachePath(cachePath)
{}
void run() override
{
QByteArray longString = "Hello World, this is some long string, well not really that long";
for (int j = 0; j < 10; ++j)
longString += longString;
QByteArray longString2 = "Help, I am stuck in an autotest!";
QUrl url(EXAMPLE_URL);
QNetworkCacheMetaData metaData;
metaData.setUrl(url);
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
metaData.setRawHeaders(headers);
metaData.setLastModified(dt);
metaData.setSaveToDisk(true);
QNetworkCacheMetaData metaData2 = metaData;
metaData2.setExpirationDate(dt);
QNetworkDiskCache cache;
cache.setCacheDirectory(cachePath);
int i = 0;
for (; i < 5000; ++i) {
if (other && other->isFinished())
break;
if (write) {
QNetworkCacheMetaData m;
if (QRandomGenerator::global()->bounded(2) == 0)
m = metaData;
else
m = metaData2;
if (QRandomGenerator::global()->bounded(20) == 1) {
//qDebug() << "write update";
cache.updateMetaData(m);
continue;
}
QIODevice *device = cache.prepare(m);
if (QRandomGenerator::global()->bounded(20) == 1) {
//qDebug() << "write remove";
cache.remove(url);
continue;
}
QVERIFY(device);
if (QRandomGenerator::global()->bounded(2) == 0)
device->write(longString);
else
device->write(longString2);
//qDebug() << "write write" << device->size();
cache.insert(device);
continue;
}
QNetworkCacheMetaData gotMetaData = cache.metaData(url);
if (gotMetaData.isValid()) {
QVERIFY(gotMetaData == metaData || gotMetaData == metaData2);
QIODevice *d = cache.data(url);
if (d) {
QByteArray x = d->readAll();
if (x != longString && x != longString2) {
qDebug() << x.size() << QString(x);
gotMetaData = cache.metaData(url);
qDebug() << (gotMetaData.url().toString())
<< gotMetaData.lastModified()
<< gotMetaData.expirationDate()
<< gotMetaData.saveToDisk();
}
if (gotMetaData.isValid())
QVERIFY(x == longString || x == longString2);
delete d;
}
}
if (QRandomGenerator::global()->bounded(5) == 1)
cache.remove(url);
if (QRandomGenerator::global()->bounded(5) == 1)
cache.clear();
sleep(0);
}
}
QDateTime dt;
bool write;
Runner *other;
QString cachePath;
};
void tst_QNetworkDiskCache::crashWhenParentingCache()
{
// the trick here is to not send the complete response
// but some data. So we get a readyRead() and it gets tried
// to be saved to the cache
QByteArray data("HTTP/1.0 200 OK\r\nCache-Control: max-age=300\r\nAge: 1\r\nContent-Length: 5\r\n\r\n123");
MiniHttpServer server(data);
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkDiskCache *diskCache = new QNetworkDiskCache(manager); // parent to qnam!
// we expect the temp dir to be cleaned at some point anyway
const QString diskCachePath = QDir::tempPath() + QLatin1String("/cacheDir_")
+ QString::number(QCoreApplication::applicationPid());
diskCache->setCacheDirectory(diskCachePath);
manager->setCache(diskCache);
QUrl url("http://127.0.0.1:" + QString::number(server.serverPort()));
QNetworkRequest request(url);
// request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
QNetworkReply *reply = manager->get(request); // new reply is parented to qnam
// wait for readyRead of reply!
connect(reply, SIGNAL(readyRead()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
delete manager; // crashed before..
}
void tst_QNetworkDiskCache::sync()
{
// This tests would be a nice to have, but is currently not supported.
return;
QTime midnight(0, 0, 0);
Runner reader(tempDir.path());
reader.dt = QDateTime::currentDateTime();
reader.write = false;
Runner writer(tempDir.path());
writer.dt = reader.dt;
writer.write = true;
writer.other = &reader;
reader.other = &writer;
writer.start();
reader.start();
writer.wait();
reader.wait();
}
QTEST_MAIN(tst_QNetworkDiskCache)
#include "tst_qnetworkdiskcache.moc"

Binary file not shown.

View File

@ -0,0 +1,52 @@
# See qtbase/src/testlib/qtestblacklist.cpp for format
[getErrors:ftp-host]
linux
[ioPostToHttpFromSocket]
osx
[ioHttpRedirectMultipartPost]
b2qt
linux
[ioHttpRedirectPolicy]
opensuse-leap
b2qt
ubuntu
windows-10
[putToFtp]
windows-10
[backgroundRequest]
macos
[deleteFromHttp]
macos
[httpCanReadLine]
macos
[httpRecursiveCreation]
osx
[httpWithNoCredentialUsage]
macos
[ioGetFromBuiltinHttp]
osx
[ioPostToHttpFromFile]
macos
[ioPostToHttpFromSocketSynchronous]
osx
[ioPostToHttpUploadProgress]
osx
[ioPutToHttpFromFile]
macos
[lastModifiedHeaderForHttp]
macos
[postToHttpSynchronous]
macos
[putToHttpSynchronous]
macos
[putToHttpsSynchronous]
osx
[receiveCookiesFromHttpSynchronous]
osx
[sendCookiesSynchronous]
osx
[backgroundRequestConnectInBackground]
osx
#QTBUG-103055
[ioGetFromHttpWithProxyAuth]
qnx

View File

@ -0,0 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(echo)
add_subdirectory(test)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDnDCCAoQCCQDV3otC4hs2KTANBgkqhkiG9w0BAQUFADCBjzELMAkGA1UEBhMC
Tk8xDTALBgNVBAgTBE9zbG8xDTALBgNVBAcTBE9zbG8xDzANBgNVBAoTBlRUIEFT
QTEOMAwGA1UECxMFUVQgU1cxHDAaBgNVBAMTE2FzcGlyaW5pa3MudHJvbGwubm8x
IzAhBgkqhkiG9w0BCQEWFGFiYWJpY0B0cm9sbHRlY2guY29tMB4XDTA4MTEwMTA4
NTcyOFoXDTA5MTEwMTA4NTcyOFowgY8xCzAJBgNVBAYTAk5PMQ0wCwYDVQQIEwRP
c2xvMQ0wCwYDVQQHEwRPc2xvMQ8wDQYDVQQKEwZUVCBBU0ExDjAMBgNVBAsTBVFU
IFNXMRwwGgYDVQQDExNhc3BpcmluaWtzLnRyb2xsLm5vMSMwIQYJKoZIhvcNAQkB
FhRhYmFiaWNAdHJvbGx0ZWNoLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAMV2bMD1DN3DMgbxU3DXw2i7EWGDXcWjTDtdHvqgIb+9nHqo3MJSrzJy
qgEPoOsXqswMla9wDPZAsWv5gVAmVSqpy2lfEgfY7LaSHiGD75seF7zIy+CxREHW
DofHXpJGGJpBCZEKQt2HfHu3+yAYNPucN78tWNZAcPbUg5tfxMZeepRimAZNIxBI
93SDrl/f9Ka7hvPSzUQsnp8hfdpHlFPFznKfD6yPrjxgz2mT9efavJ4DhtyIa4m+
paiX515CidDz4A8CFxKZbYvuqq1ilibF/si2so9VhALC77ZcAJP1IMuT8T+WUCxq
skJqiSCncl0Hgr+ba8MDGF9UQYowgjMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
KcJuNUHvjB8ok3cnTmQEeF0LPPkgj28Tqb5TFB8xpVfRI+wvTYsHsmGdOKCgYJ3a
7VflIsr63ojG8/rXK8H/cx2o2f2Hr3liJdi1UnoLDDRjBqGGz7JNuMreYokPvIbm
eP01mVyK4PO2iYRwHUIAw5eeB1vMWKX2z95MupD+HRLtmGyaLALg8aQxj5N84Ewl
eU2PQfhv8A1wj7aL17kfEUxDerQ1kUzlThJMV1J8Dl0l4C9N8evQkelROJU00i46
oJikA8BW6EpgbnGyNyyj5Loy4wLPKew9nTS8MCJ5xPMQc0urbY/VzuOeUK7WQof7
xOFSsRAVyQv+yqgmcZMCtg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,75 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 0 (0x0)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=NO, ST=Oslo, L=Nydalen, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com
Validity
Not Before: Dec 4 01:10:32 2007 GMT
Not After : Apr 21 01:10:32 2035 GMT
Subject: C=NO, ST=Oslo, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:a7:c8:a0:4a:c4:19:05:1b:66:ba:32:e2:d2:f1:
1c:6f:17:82:e4:39:2e:01:51:90:db:04:34:32:11:
21:c2:0d:6f:59:d8:53:90:54:3f:83:8f:a9:d3:b3:
d5:ee:1a:9b:80:ae:c3:25:c9:5e:a5:af:4b:60:05:
aa:a0:d1:91:01:1f:ca:04:83:e3:58:1c:99:32:45:
84:70:72:58:03:98:4a:63:8b:41:f5:08:49:d2:91:
02:60:6b:e4:64:fe:dd:a0:aa:74:08:e9:34:4c:91:
5f:12:3d:37:4d:54:2c:ad:7f:5b:98:60:36:02:8c:
3b:f6:45:f3:27:6a:9b:94:9d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
21:85:04:3D:23:01:66:E5:F7:9F:1A:84:24:8A:AF:0A:79:F4:E5:AC
X509v3 Authority Key Identifier:
DirName:/C=NO/ST=Oslo/L=Nydalen/O=Nokia Corporation and/or its subsidiary(-ies)/OU=Development/CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com
serial:8E:A8:B4:E8:91:B7:54:2E
Signature Algorithm: sha1WithRSAEncryption
6d:57:5f:d1:05:43:f0:62:05:ec:2a:71:a5:dc:19:08:f2:c4:
a6:bd:bb:25:d9:ca:89:01:0e:e4:cf:1f:c1:8c:c8:24:18:35:
53:59:7b:c0:43:b4:32:e6:98:b2:a6:ef:15:05:0b:48:5f:e1:
a0:0c:97:a9:a1:77:d8:35:18:30:bc:a9:8f:d3:b7:54:c7:f1:
a9:9e:5d:e6:19:bf:f6:3c:5b:2b:d8:e4:3e:62:18:88:8b:d3:
24:e1:40:9b:0c:e6:29:16:62:ab:ea:05:24:70:36:aa:55:93:
ef:02:81:1b:23:10:a2:04:eb:56:95:75:fc:f8:94:b1:5d:42:
c5:3f:36:44:85:5d:3a:2e:90:46:8a:a2:b9:6f:87:ae:0c:15:
40:19:31:90:fc:3b:25:bb:ae:f1:66:13:0d:85:90:d9:49:34:
8f:f2:5d:f9:7a:db:4d:5d:27:f6:76:9d:35:8c:06:a6:4c:a3:
b1:b2:b6:6f:1d:d7:a3:00:fd:72:eb:9e:ea:44:a1:af:21:34:
7d:c7:42:e2:49:91:19:8b:c0:ad:ba:82:80:a8:71:70:f4:35:
31:91:63:84:20:95:e9:60:af:64:8b:cc:ff:3d:8a:76:74:3d:
c8:55:6d:e4:8e:c3:2b:1c:e8:42:18:ae:9f:e6:6b:9c:34:06:
ec:6a:f2:c3
-----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
-----END CERTIFICATE-----

View File

@ -0,0 +1,15 @@
-----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=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIIClTCCAf4CCQC2xMhNhwvATDANBgkqhkiG9w0BAQQFADCBjjELMAkGA1UEChMC
UXQxGTAXBgNVBAsTEENvcmUgQW5kIE5ldHdvcmsxGzAZBgkqhkiG9w0BCQEWDG5v
Ym9keS5xdC5pbzENMAsGA1UEBxMET3NsbzENMAsGA1UECBMET3NsbzELMAkGA1UE
BhMCTk8xHDAaBgNVBAMUEyoudGVzdC1uZXQucXQubG9jYWwwHhcNMTgwNzAxMTgz
NjI3WhcNNDgwNjIzMTgzNjI3WjCBjjELMAkGA1UEChMCUXQxGTAXBgNVBAsTEENv
cmUgQW5kIE5ldHdvcmsxGzAZBgkqhkiG9w0BCQEWDG5vYm9keS5xdC5pbzENMAsG
A1UEBxMET3NsbzENMAsGA1UECBMET3NsbzELMAkGA1UEBhMCTk8xHDAaBgNVBAMU
EyoudGVzdC1uZXQucXQubG9jYWwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
AM2q22/WNMmn8cC+5EEYGeICySLmp9W6Ay6eKHr0Xxp3X3epETuPfvAuxp7rOtkS
18EMUegkUj8jw0IMEcbyHKFC/rTCaYOt93CxGBXMIChiMPAsFeYzGa/D6xzAkfcR
aJRQ+Ek3CDLXPnXfo7xpABXezYcPXAJrgsgBfWrwHdxzAgMBAAEwDQYJKoZIhvcN
AQEEBQADgYEAZu/lQPy8PXeyyYGamOVms/FZKJ48BH1y8KC3BeBU5FYnhvgG7pz8
Wz9JKvt2t/r45wQeAkNL6HnGUBhPJsHMjPHl5KktqN+db3D+FQygBeS2V1+zmC0X
UZNRE4aWiHvt1Lq+pTx89SOMOpfqWfh4qTQKiE5jC2V4DeCNQ3u7uI8=
-----END CERTIFICATE-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
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-----

View File

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIIClzCCAgACCQDeuuUc2HkfKDANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEChMC
UXQxGTAXBgNVBAsTEENvcmUgQW5kIE5ldHdvcmsxGzAZBgkqhkiG9w0BCQEWDG5v
Ym9keS5xdC5pbzENMAsGA1UEBxMET3NsbzENMAsGA1UECBMET3NsbzELMAkGA1UE
BhMCTk8xHTAbBgNVBAMTFHF0LXRlc3Qtc2VydmVyLmxvY2FsMB4XDTE5MDEyNTE1
NDE0N1oXDTQ5MDExNzE1NDE0N1owgY8xCzAJBgNVBAoTAlF0MRkwFwYDVQQLExBD
b3JlIEFuZCBOZXR3b3JrMRswGQYJKoZIhvcNAQkBFgxub2JvZHkucXQuaW8xDTAL
BgNVBAcTBE9zbG8xDTALBgNVBAgTBE9zbG8xCzAJBgNVBAYTAk5PMR0wGwYDVQQD
ExRxdC10ZXN0LXNlcnZlci5sb2NhbDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEAzarbb9Y0yafxwL7kQRgZ4gLJIuan1boDLp4oevRfGndfd6kRO49+8C7Gnus6
2RLXwQxR6CRSPyPDQgwRxvIcoUL+tMJpg633cLEYFcwgKGIw8CwV5jMZr8PrHMCR
9xFolFD4STcIMtc+dd+jvGkAFd7Nhw9cAmuCyAF9avAd3HMCAwEAATANBgkqhkiG
9w0BAQQFAAOBgQB1dxK3Ia4sCpvSikKLaf1ZXu+9GKaNWKJe9bWex9/RmNOla9N2
FIh6/CfaPFDy/OXCkyEiGg78iyg/DgqVoa9JJGV3diI6berisHMPJpv1syyz9YEU
G3RQUClPcPV6EcedyqCdpbnIFtiSZbtJ0ZBGef4KzBN3rTmPucKb+bhMPg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
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-----

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
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-----

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -0,0 +1,12 @@
# 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}
SOURCES
main.cpp
)

View File

@ -0,0 +1,23 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QFile>
int main(int argc, char **)
{
if (argc < 2) {
printf("usage: echo\n");
printf("echos all its input to its output.\n");
return 1;
}
QFile file;
file.open(stdin, QFile::ReadWrite);
QByteArray data = file.readAll();
file.close();
file.open(stdout, QFile::WriteOnly);
file.write(data);
file.close();
return 0;
}

View File

@ -0,0 +1 @@
<root attr="value" attr2="value2"><person /><fruit /></root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

View File

@ -0,0 +1,3 @@
<h1>Welcome to qt-test-server</h1>
<img src="gif/fluke.gif" alt="fluke">
<p>This is a network test server. It serves as a cacheing ftp and http proxy, transparent http/socks5 proxy, imap, ftp and http server, plus more.</p>

View File

@ -0,0 +1,283 @@
Network Working Group L. Masinter
Request for Comments: 2397 Xerox Corporation
Category: Standards Track August 1998
The "data" URL scheme
Status of this Memo
This document specifies an Internet standards track protocol for the
Internet community, and requests discussion and suggestions for
improvements. Please refer to the current edition of the "Internet
Official Protocol Standards" (STD 1) for the standardization state
and status of this protocol. Distribution of this memo is unlimited.
Copyright Notice
Copyright (C) The Internet Society (1998). All Rights Reserved.
1. Abstract
A new URL scheme, "data", is defined. It allows inclusion of small
data items as "immediate" data, as if it had been included
externally.
2. Description
Some applications that use URLs also have a need to embed (small)
media type data directly inline. This document defines a new URL
scheme that would work like 'immediate addressing'. The URLs are of
the form:
data:[<mediatype>][;base64],<data>
The <mediatype> is an Internet media type specification (with
optional parameters.) The appearance of ";base64" means that the data
is encoded as base64. Without ";base64", the data (as a sequence of
octets) is represented using ASCII encoding for octets inside the
range of safe URL characters and using the standard %xx hex encoding
of URLs for octets outside that range. If <mediatype> is omitted, it
defaults to text/plain;charset=US-ASCII. As a shorthand,
"text/plain" can be omitted but the charset parameter supplied.
The "data:" URL scheme is only useful for short values. Note that
some applications that use URLs may impose a length limit; for
example, URLs embedded within <A> anchors in HTML have a length limit
determined by the SGML declaration for HTML [RFC1866]. The LITLEN
(1024) limits the number of characters which can appear in a single
Masinter Standards Track [Page 1]
RFC 2397 The "data" URL scheme August 1998
attribute value literal, the ATTSPLEN (2100) limits the sum of all
lengths of all attribute value specifications which appear in a tag,
and the TAGLEN (2100) limits the overall length of a tag.
The "data" URL scheme has no relative URL forms.
3. Syntax
dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype := [ type "/" subtype ] *( ";" parameter )
data := *urlchar
parameter := attribute "=" value
where "urlchar" is imported from [RFC2396], and "type", "subtype",
"attribute" and "value" are the corresponding tokens from [RFC2045],
represented using URL escaped encoding of [RFC2396] as necessary.
Attribute values in [RFC2045] are allowed to be either represented as
tokens or as quoted strings. However, within a "data" URL, the
"quoted-string" representation would be awkward, since the quote mark
is itself not a valid urlchar. For this reason, parameter values
should use the URL Escaped encoding instead of quoted string if the
parameter values contain any "tspecial".
The ";base64" extension is distinguishable from a content-type
parameter by the fact that it doesn't have a following "=" sign.
4. Examples
A data URL might be used for arbitrary types of data. The URL
data:,A%20brief%20note
encodes the text/plain string "A brief note", which might be useful
in a footnote link.
The HTML fragment:
<IMG
SRC="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw
AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz
ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp
a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl
ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis
F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH
hhx4dbgYKAAA7"
ALT="Larry">
Masinter Standards Track [Page 2]
RFC 2397 The "data" URL scheme August 1998
could be used for a small inline image in a HTML document. (The
embedded image is probably near the limit of utility. For anything
else larger, data URLs are likely to be inappropriate.)
A data URL scheme's media type specification can include other
parameters; for example, one might specify a charset parameter.
data:text/plain;charset=iso-8859-7,%be%fg%be
can be used for a short sequence of greek characters.
Some applications may use the "data" URL scheme in order to provide
setup parameters for other kinds of networking applications. For
example, one might create a media type
application/vnd-xxx-query
whose content consists of a query string and a database identifier
for the "xxx" vendor's databases. A URL of the form:
data:application/vnd-xxx-
query,select_vcount,fcol_from_fieldtable/local
could then be used in a local application to launch the "helper" for
application/vnd-xxx-query and give it the immediate data included.
5. History
This idea was originally proposed August 1995. Some versions of the
data URL scheme have been used in the definition of VRML, and a
version has appeared as part of a proposal for embedded data in HTML.
Various changes have been made, based on requests, to elide the media
type, pack the indication of the base64 encoding more tightly, and
eliminate "quoted printable" as an encoding since it would not easily
yield valid URLs without additional %xx encoding, which itself is
sufficient. The "data" URL scheme is in use in VRML, new applications
of HTML, and various commercial products. It is being used for object
parameters in Java and ActiveX applications.
6. Security
Interpretation of the data within a "data" URL has the same security
considerations as any implementation of the given media type. An
application should not interpret the contents of a data URL which is
marked with a media type that has been disallowed for processing by
the application's configuration.
Masinter Standards Track [Page 3]
RFC 2397 The "data" URL scheme August 1998
Sites which use firewall proxies to disallow the retrieval of certain
media types (such as application script languages or types with known
security problems) will find it difficult to screen against the
inclusion of such types using the "data" URL scheme. However, they
should be aware of the threat and take whatever precautions are
considered necessary within their domain.
The effect of using long "data" URLs in applications is currently
unknown; some software packages may exhibit unreasonable behavior
when confronted with data that exceeds its allocated buffer size.
7. References
[RFC2396] Berners-Lee, T., Fielding, R., and L. Masinter,
"Uniform Resource Identifiers (URI): Generic Syntax", RFC
2396, August 1998.
[RFC1866] Berners-Lee, T., and D. Connolly, "Hypertext Markup
Language - 2.0.", RFC 1866, November 1995.
[RFC2045] Freed N., and N. Borenstein., "Multipurpose Internet Mail
Extensions (MIME) Part One: Format of Internet Message
Bodies", RFC 2045, November 1996.
Author contact information:
Larry Masinter
Xerox Palo Alto Research Center
3333 Coyote Hill Road
Palo Alto, CA 94304
EMail: masinter@parc.xerox.com
Masinter Standards Track [Page 4]
RFC 2397 The "data" URL scheme August 1998
Full Copyright Statement
Copyright (C) The Internet Society (1998). All Rights Reserved.
This document and translations of it may be copied and furnished to
others, and derivative works that comment on or otherwise explain it
or assist in its implementation may be prepared, copied, published
and distributed, in whole or in part, without restriction of any
kind, provided that the above copyright notice and this paragraph are
included on all such copies and derivative works. However, this
document itself may not be modified in any way, such as by removing
the copyright notice or references to the Internet Society or other
Internet organizations, except as needed for the purpose of
developing Internet standards in which case the procedures for
copyrights defined in the Internet Standards process must be
followed, or as required to translate it into languages other than
English.
The limited permissions granted above are perpetual and will not be
revoked by the Internet Society or its successors or assigns.
This document and the information contained herein is provided on an
"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Masinter Standards Track [Page 5]

View File

@ -0,0 +1,899 @@
Network Working Group H. Kennedy
Request for Comments: 3252 Mimezine
Category: Informational 1 April 2002
Binary Lexical Octet Ad-hoc Transport
Status of this Memo
This memo provides information for the Internet community. It does
not specify an Internet standard of any kind. Distribution of this
memo is unlimited.
Copyright Notice
Copyright (C) The Internet Society (2002). All Rights Reserved.
Abstract
This document defines a reformulation of IP and two transport layer
protocols (TCP and UDP) as XML applications.
1. Introduction
1.1. Overview
This document describes the Binary Lexical Octet Ad-hoc Transport
(BLOAT): a reformulation of a widely-deployed network-layer protocol
(IP [RFC791]), and two associated transport layer protocols (TCP
[RFC793] and UDP [RFC768]) as XML [XML] applications. It also
describes methods for transporting BLOAT over Ethernet and IEEE 802
networks as well as encapsulating BLOAT in IP for gatewaying BLOAT
across the public Internet.
1.2. Motivation
The wild popularity of XML as a basis for application-level protocols
such as the Blocks Extensible Exchange Protocol [RFC3080], the Simple
Object Access Protocol [SOAP], and Jabber [JABBER] prompted
investigation into the possibility of extending the use of XML in the
protocol stack. Using XML at both the transport and network layer in
addition to the application layer would provide for an amazing amount
of power and flexibility while removing dependencies on proprietary
and hard-to-understand binary protocols. This protocol unification
would also allow applications to use a single XML parser for all
aspects of their operation, eliminating developer time spent figuring
out the intricacies of each new protocol, and moving the hard work of
Kennedy Informational [Page 1]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
parsing to the XML toolset. The use of XML also mitigates concerns
over "network vs. host" byte ordering which is at the root of many
network application bugs.
1.3. Relation to Existing Protocols
The reformulations specified in this RFC follow as closely as
possible the spirit of the RFCs on which they are based, and so MAY
contain elements or attributes that would not be needed in a pure
reworking (e.g. length attributes, which are implicit in XML.)
The layering of network and transport protocols are maintained in
this RFC despite the optimizations that could be made if the line
were somewhat blurred (i.e. merging TCP and IP into a single, larger
element in the DTD) in order to foster future use of this protocol as
a basis for reformulating other protocols (such as ICMP.)
Other than the encoding, the behavioral aspects of each of the
existing protocols remain unchanged. Routing, address spaces, TCP
congestion control, etc. behave as specified in the extant standards.
Adapting to new standards and experimental algorithm heuristics for
improving performance will become much easier once the move to BLOAT
has been completed.
1.4. Requirement Levels
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in BCP 14, RFC 2119
[RFC2119].
2. IPoXML
This protocol MUST be implemented to be compliant with this RFC.
IPoXML is the root protocol REQUIRED for effective use of TCPoXML
(section 3.) and higher-level application protocols.
The DTD for this document type can be found in section 7.1.
The routing of IPoXML can be easily implemented on hosts with an XML
parser, as the regular structure lends itself handily to parsing and
validation of the document/datagram and then processing the
destination address, TTL, and checksum before sending it on to its
next-hop.
The reformulation of IPv4 was chosen over IPv6 [RFC2460] due to the
wider deployment of IPv4 and the fact that implementing IPv6 as XML
would have exceeded the 1500 byte Ethernet MTU.
Kennedy Informational [Page 2]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
All BLOAT implementations MUST use - and specify - the UTF-8 encoding
of RFC 2279 [RFC2279]. All BLOAT document/datagrams MUST be well-
formed and include the XMLDecl.
2.1. IP Description
A number of items have changed (for the better) from the original IP
specification. Bit-masks, where present have been converted into
human-readable values. IP addresses are listed in their dotted-
decimal notation [RFC1123]. Length and checksum values are present
as decimal integers.
To calculate the length and checksum fields of the IP element, a
canonicalized form of the element MUST be used. The canonical form
SHALL have no whitespace (including newline characters) between
elements and only one space character between attributes. There
SHALL NOT be a space following the last attribute in an element.
An iterative method SHOULD be used to calculate checksums, as the
length field will vary based on the size of the checksum.
The payload element bears special attention. Due to the character
set restrictions of XML, the payload of IP datagrams (which MAY
contain arbitrary data) MUST be encoded for transport. This RFC
REQUIRES the contents of the payload to be encoded in the base-64
encoding of RFC 2045 [RFC2045], but removes the requirement that the
encoded output MUST be wrapped on 76-character lines.
Kennedy Informational [Page 3]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
2.2. Example Datagram
The following is an example IPoXML datagram with an empty payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ip PUBLIC "-//IETF//DTD BLOAT 1.0 IP//EN" "bloat.dtd">
<ip>
<header length="474">
<version value="4"/>
<tos precedence="Routine" delay="Normal" throughput="Normal"
relibility="Normal" reserved="0"/>
<total.length value="461"/>
<id value="1"/>
<flags reserved="0" df="dont" mf="last"/>
<offset value="0"/>
<ttl value="255"/>
<protocol value="6"/>
<checksum value="8707"/>
<source address="10.0.0.22"/>
<destination address="10.0.0.1"/>
<options>
<end copied="0" class="0" number="0"/>
</options>
<padding pad="0"/>
</header>
<payload>
</payload>
</ip>
3. TCPoXML
This protocol MUST be implemented to be compliant with this RFC. The
DTD for this document type can be found in section 7.2.
3.1. TCP Description
A number of items have changed from the original TCP specification.
Bit-masks, where present have been converted into human-readable
values. Length and checksum and port values are present as decimal
integers.
To calculate the length and checksum fields of the TCP element, a
canonicalized form of the element MUST be used as in section 2.1.
An iterative method SHOULD be used to calculate checksums as in
section 2.1.
The payload element MUST be encoded as in section 2.1.
Kennedy Informational [Page 4]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
The TCP offset element was expanded to a maximum of 255 from 16 to
allow for the increased size of the header in XML.
TCPoXML datagrams encapsulated by IPoXML MAY omit the <?xml?> header
as well as the <!DOCTYPE> declaration.
3.2. Example Datagram
The following is an example TCPoXML datagram with an empty payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tcp PUBLIC "-//IETF//DTD BLOAT 1.0 TCP//EN" "bloat.dtd">
<tcp>
<tcp.header>
<src port="31415"/>
<dest port="42424"/>
<sequence number="322622954"/>
<acknowledgement number="689715995"/>
<offset number=""/>
<reserved value="0"/>
<control syn="1" ack="1"/>
<window size="1"/>
<urgent pointer="0"/>
<checksum value="2988"/>
<tcp.options>
<tcp.end kind="0"/>
</tcp.options>
<padding pad="0"/>
</tcp.header>
<payload>
</payload>
</tcp>
4. UDPoXML
This protocol MUST be implemented to be compliant with this RFC. The
DTD for this document type can be found in section 7.3.
4.1. UDP Description
A number of items have changed from the original UDP specification.
Bit-masks, where present have been converted into human-readable
values. Length and checksum and port values are present as decimal
integers.
Kennedy Informational [Page 5]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
To calculate the length and checksum fields of the UDP element, a
canonicalized form of the element MUST be used as in section 2.1. An
iterative method SHOULD be used to calculate checksums as in section
2.1.
The payload element MUST be encoded as in section 2.1.
UDPoXML datagrams encapsulated by IPoXML MAY omit the <?xml?> header
as well as the <!DOCTYPE> declaration.
4.2. Example Datagram
The following is an example UDPoXML datagram with an empty payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE udp PUBLIC "-//IETF//DTD BLOAT 1.0 UDP//EN" "bloat.dtd">
<udp>
<udp.header>
<src port="31415"/>
<dest port="42424"/>
<udp.length value="143"/>
<checksum value="2988"/>
</udp.header>
<payload>
</payload>
</udp>
5. Network Transport
This document provides for the transmission of BLOAT datagrams over
two common families of physical layer transport. Future RFCs will
address additional transports as routing vendors catch up to the
specification, and we begin to see BLOAT routed across the Internet
backbone.
5.1. Ethernet
BLOAT is encapsulated in Ethernet datagrams as in [RFC894] with the
exception that the type field of the Ethernet frame MUST contain the
value 0xBEEF. The first 5 octets of the Ethernet frame payload will
be 0x3c 3f 78 6d 6c ("<?xml".)
5.2. IEEE 802
BLOAT is encapsulated in IEEE 802 Networks as in [RFC1042] except
that the protocol type code for IPoXML is 0xBEEF.
Kennedy Informational [Page 6]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
6. Gatewaying over IP
In order to facilitate the gradual introduction of BLOAT into the
public Internet, BLOAT MAY be encapsulated in IP as in [RFC2003] to
gateway between networks that run BLOAT natively on their LANs.
7. DTDs
The Transport DTDs (7.2. and 7.3.) build on the definitions in the
Network DTD (7.1.)
The DTDs are referenced by their PubidLiteral and SystemLiteral (from
[XML]) although it is understood that most IPoXML implementations
will not need to pull down the DTD, as it will normally be embedded
in the implementation, and presents something of a catch-22 if you
need to load part of your network protocol over the network.
7.1. IPoXML DTD
<!--
DTD for IP over XML.
Refer to this DTD as:
<!DOCTYPE ip PUBLIC "-//IETF//DTD BLOAT 1.0 IP//EN" "bloat.dtd">
-->
<!--
DTD data types:
Digits [0..9]+
Precedence "NetworkControl | InternetworkControl |
CRITIC | FlashOverride | Flash | Immediate |
Priority | Routine"
IP4Addr "dotted-decimal" notation of [RFC1123]
Class [0..3]
Sec "Unclassified | Confidential | EFTO | MMMM | PROG |
Restricted | Secret | Top Secret | Reserved"
Compartments [0..65535]
Handling [0..65535]
TCC [0..16777216]
-->
Kennedy Informational [Page 7]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
<!ENTITY % Digits "CDATA">
<!ENTITY % Precedence "CDATA">
<!ENTITY % IP4Addr "CDATA">
<!ENTITY % Class "CDATA">
<!ENTITY % Sec "CDATA">
<!ENTITY % Compartments "CDATA">
<!ENTITY % Handling "CDATA">
<!ENTITY % TCC "CDATA">
<!ELEMENT ip (header, payload)>
<!ELEMENT header (version, tos, total.length, id, flags, offset, ttl,
protocol, checksum, source, destination, options,
padding)>
<!-- length of header in 32-bit words -->
<!ATTLIST header
length %Digits; #REQUIRED>
<!ELEMENT version EMPTY>
<!-- ip version. SHOULD be "4" -->
<!ATTLIST version
value %Digits; #REQUIRED>
<!ELEMENT tos EMPTY>
<!ATTLIST tos
precedence %Precedence; #REQUIRED
delay (normal | low) #REQUIRED
throughput (normal | high) #REQUIRED
relibility (normal | high) #REQUIRED
reserved CDATA #FIXED "0">
<!ELEMENT total.length EMPTY>
<!--
total length of datagram (header and payload) in octets, MUST be
less than 65,535 (and SHOULD be less than 1024 for IPoXML on local
ethernets).
-->
<!ATTLIST total.length
value %Digits; #REQUIRED>
<!ELEMENT id EMPTY>
<!-- 0 <= id <= 65,535 -->
<!ATTLIST id
value %Digits; #REQUIRED>
<!ELEMENT flags EMPTY>
<!-- df = don't fragment, mf = more fragments -->
<!ATTLIST flags
Kennedy Informational [Page 8]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
reserved CDATA #FIXED "0"
df (may|dont) #REQUIRED
mf (last|more) #REQUIRED>
<!ELEMENT offset EMPTY>
<!-- 0 <= offset <= 8192 measured in 8 octet (64-bit) chunks -->
<!ATTLIST offset
value %Digits; #REQUIRED>
<!ELEMENT ttl EMPTY>
<!-- 0 <= ttl <= 255 -->
<!ATTLIST ttl
value %Digits; #REQUIRED>
<!ELEMENT protocol EMPTY>
<!-- 0 <= protocol <= 255 (per IANA) -->
<!ATTLIST protocol
value %Digits; #REQUIRED>
<!ELEMENT checksum EMPTY>
<!-- 0 <= checksum <= 65535 (over header only) -->
<!ATTLIST checksum
value %Digits; #REQUIRED>
<!ELEMENT source EMPTY>
<!ATTLIST source
address %IP4Addr; #REQUIRED>
<!ELEMENT destination EMPTY>
<!ATTLIST destination
address %IP4Addr; #REQUIRED>
<!ELEMENT options ( end | noop | security | loose | strict | record
| stream | timestamp )*>
<!ELEMENT end EMPTY>
<!ATTLIST end
copied (0|1) #REQUIRED
class CDATA #FIXED "0"
number CDATA #FIXED "0">
<!ELEMENT noop EMPTY>
<!ATTLIST noop
copied (0|1) #REQUIRED
class CDATA #FIXED "0"
number CDATA #FIXED "1">
<!ELEMENT security EMPTY>
Kennedy Informational [Page 9]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
<!ATTLIST security
copied CDATA #FIXED "1"
class CDATA #FIXED "0"
number CDATA #FIXED "2"
length CDATA #FIXED "11"
security %Sec; #REQUIRED
compartments %Compartments; #REQUIRED
handling %Handling; #REQUIRED
tcc %TCC; #REQUIRED>
<!ELEMENT loose (hop)+>
<!ATTLIST loose
copied CDATA #FIXED "1"
class CDATA #FIXED "0"
number CDATA #FIXED "3"
length %Digits; #REQUIRED
pointer %Digits; #REQUIRED>
<!ELEMENT hop EMPTY>
<!ATTLIST hop
address %IP4Addr; #REQUIRED>
<!ELEMENT strict (hop)+>
<!ATTLIST strict
copied CDATA #FIXED "1"
class CDATA #FIXED "0"
number CDATA #FIXED "9"
length %Digits; #REQUIRED
pointer %Digits; #REQUIRED>
<!ELEMENT record (hop)+>
<!ATTLIST record
copied CDATA #FIXED "0"
class CDATA #FIXED "0"
number CDATA #FIXED "7"
length %Digits; #REQUIRED
pointer %Digits; #REQUIRED>
<!ELEMENT stream EMPTY>
<!-- 0 <= id <= 65,535 -->
<!ATTLIST stream
copied CDATA #FIXED "1"
class CDATA #FIXED "0"
number CDATA #FIXED "8"
length CDATA #FIXED "4"
id %Digits; #REQUIRED>
<!ELEMENT timestamp (tstamp)+>
<!-- 0 <= oflw <=15 -->
Kennedy Informational [Page 10]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
<!ATTLIST timestamp
copied CDATA #FIXED "0"
class CDATA #FIXED "2"
number CDATA #FIXED "4"
length %Digits; #REQUIRED
pointer %Digits; #REQUIRED
oflw %Digits; #REQUIRED
flag (0 | 1 | 3) #REQUIRED>
<!ELEMENT tstamp EMPTY>
<!ATTLIST tstamp
time %Digits; #REQUIRED
address %IP4Addr; #IMPLIED>
<!--
padding to bring header to 32-bit boundary.
pad MUST be "0"*
-->
<!ELEMENT padding EMPTY>
<!ATTLIST padding
pad CDATA #REQUIRED>
<!-- payload MUST be encoded as base-64 [RFC2045], as modified
by section 2.1 of this RFC -->
<!ELEMENT payload (CDATA)>
7.2. TCPoXML DTD
<!--
DTD for TCP over XML.
Refer to this DTD as:
<!DOCTYPE tcp PUBLIC "-//IETF//DTD BLOAT 1.0 TCP//EN" "bloat.dtd">
-->
<!-- the pseudoheader is only included for checksum calculations -->
<!ELEMENT tcp (tcp.pseudoheader?, tcp.header, payload)>
<!ELEMENT tcp.header (src, dest, sequence, acknowledgement, offset,
reserved, control, window, checksum, urgent,
tcp.options, padding)>
<!ELEMENT src EMPTY>
<!-- 0 <= port <= 65,535 -->
<!ATTLIST src
port %Digits; #REQUIRED>
<!ELEMENT dest EMPTY>
<!-- 0 <= port <= 65,535 -->
Kennedy Informational [Page 11]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
<!ATTLIST dest
port %Digits; #REQUIRED>
<!ELEMENT sequence EMPTY>
<!-- 0 <= number <= 4294967295 -->
<!ATTLIST sequence
number %Digits; #REQUIRED>
<!ELEMENT acknowledgement EMPTY>
<!-- 0 <= number <= 4294967295 -->
<!ATTLIST acknowledgement
number %Digits; #REQUIRED>
<!ELEMENT offset EMPTY>
<!-- 0 <= number <= 255 -->
<!ATTLIST offset
number %Digits; #REQUIRED>
<!ELEMENT reserved EMPTY>
<!ATTLIST reserved
value CDATA #FIXED "0">
<!ELEMENT control EMPTY>
<!ATTLIST control
urg (0|1) #IMPLIED
ack (0|1) #IMPLIED
psh (0|1) #IMPLIED
rst (0|1) #IMPLIED
syn (0|1) #IMPLIED
fin (0|1) #IMPLIED>
<!ELEMENT window EMPTY>
<!-- 0 <= size <= 65,535 -->
<!ATTLIST window
size %Digits; #REQUIRED>
<!--
checksum as in ip, but with
the following pseudo-header added into the tcp element:
-->
<!ELEMENT tcp.pseudoheader (source, destination, protocol,
tcp.length)>
<!--
tcp header + data length in octets. does not include the size of
the pseudoheader.
-->
Kennedy Informational [Page 12]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
<!ELEMENT tcp.length EMPTY>
<!ATTLIST tcp.length
value %Digits; #REQUIRED>
<!ELEMENT urgent EMPTY>
<!-- 0 <= pointer <= 65,535 -->
<!ATTLIST urgent
pointer %Digits; #REQUIRED>
<!ELEMENT tcp.options (tcp.end | tcp.noop | tcp.mss)+>
<!ELEMENT tcp.end EMPTY>
<!ATTLIST tcp.end
kind CDATA #FIXED "0">
<!ELEMENT tcp.noop EMPTY>
<!ATTLIST tcp.noop
kind CDATA #FIXED "1">
<!ELEMENT tcp.mss EMPTY>
<!ATTLIST tcp.mss
kind CDATA #FIXED "2"
length CDATA #FIXED "4"
size %Digits; #REQUIRED>
7.3. UDPoXML DTD
<!--
DTD for UDP over XML.
Refer to this DTD as:
<!DOCTYPE udp PUBLIC "-//IETF//DTD BLOAT 1.0 UDP//EN" "bloat.dtd">
-->
<!ELEMENT udp (udp.pseudoheader?, udp.header, payload)>
<!ELEMENT udp.header (src, dest, udp.length, checksum)>
<!ELEMENT udp.pseudoheader (source, destination, protocol,
udp.length)>
<!--
udp header + data length in octets. does not include the size of
the pseudoheader.
-->
<!ELEMENT udp.length EMPTY>
<!ATTLIST udp.length
value %Digits; #REQUIRED>
Kennedy Informational [Page 13]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
8. Security Considerations
XML, as a subset of SGML, has the same security considerations as
specified in SGML Media Types [RFC1874]. Security considerations
that apply to IP, TCP and UDP also likely apply to BLOAT as it does
not attempt to correct for issues not related to message format.
9. References
[JABBER] Miller, J., "Jabber", draft-miller-jabber-00.txt,
February 2002. (Work in Progress)
[RFC768] Postel, J., "User Datagram Protocol", STD 6, RFC 768,
August 1980.
[RFC791] Postel, J., "Internet Protocol", STD 5, RFC 791,
September 1981.
[RFC793] Postel, J., "Transmission Control Protocol", STD 7, RFC
793, September 1981.
[RFC894] Hornig, C., "Standard for the Transmission of IP
Datagrams over Ethernet Networks.", RFC 894, April 1984.
[RFC1042] Postel, J. and J. Reynolds, "Standard for the
Transmission of IP Datagrams Over IEEE 802 Networks", STD
43, RFC 1042, February 1988.
[RFC1123] Braden, R., "Requirements for Internet Hosts -
Application and Support", RFC 1123, October 1989.
[RFC1874] Levinson, E., "SGML Media Types", RFC 1874, December
1995.
[RFC2003] Perkins, C., "IP Encapsulation within IP", RFC 2003,
October 1996.
[RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
Extensions (MIME) Part One: Format of Internet Message
Bodies", RFC 2045, November 1996.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC2279] Yergeau, F., "UTF-8, a transformation format of ISO
10646", RFC 2279, January 1998.
Kennedy Informational [Page 14]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
[RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version 6
(IPv6) Specification", RFC 2460, December 1998.
[RFC3080] Rose, M., "The Blocks Extensible Exchange Protocol Core",
RFC 3080, March 2001.
[SOAP] Box, D., Ehnebuske, D., Kakivaya, G., Layman, A.,
Mendelsohn, N., Nielsen, H. F., Thatte, S. Winer, D.,
"Simple Object Access Protocol (SOAP) 1.1" World Wide Web
Consortium Note, May 2000 http://www.w3.org/TR/SOAP/
[XML] Bray, T., Paoli, J., Sperberg-McQueen, C. M., "Extensible
Markup Language (XML)" World Wide Web Consortium
Recommendation REC- xml-19980210.
http://www.w3.org/TR/1998/REC-xml-19980210
10. Author's Address
Hugh Kennedy
Mimezine
1060 West Addison
Chicago, IL 60613
USA
EMail: kennedyh@engin.umich.edu
Kennedy Informational [Page 15]
RFC 3252 Binary Lexical Octet Ad-hoc Transport 1 April 2002
11. Full Copyright Statement
Copyright (C) The Internet Society (2002). All Rights Reserved.
This document and translations of it may be copied and furnished to
others, and derivative works that comment on or otherwise explain it
or assist in its implementation may be prepared, copied, published
and distributed, in whole or in part, without restriction of any
kind, provided that the above copyright notice and this paragraph are
included on all such copies and derivative works. However, this
document itself may not be modified in any way, such as by removing
the copyright notice or references to the Internet Society or other
Internet organizations, except as needed for the purpose of
developing Internet standards in which case the procedures for
copyrights defined in the Internet Standards process must be
followed, or as required to translate it into languages other than
English.
The limited permissions granted above are perpetual and will not be
revoked by the Internet Society or its successors or assigns.
This document and the information contained herein is provided on an
"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Acknowledgement
Funding for the RFC Editor function is currently provided by the
Internet Society.
Kennedy Informational [Page 16]

View File

@ -0,0 +1 @@
This is 34 bytes. Do not change...

View File

@ -0,0 +1,47 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## 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
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../"
SOURCES
../tst_qnetworkreply.cpp
../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"
)
add_dependencies(tst_qnetworkreply echo)
# Resources:
set(qnetworkreply_resource_files
"../resource"
)
qt_internal_add_resource(tst_qnetworkreply "qnetworkreply"
PREFIX
"/"
BASE
".."
FILES
${qnetworkreply_resource_files}
)

View File

@ -0,0 +1,3 @@
<h1>Welcome to qt-test-server</h1>
<img src="fluke.gif" alt="fluke">
<p>This is a network test server. It serves as a caching ftp and http proxy, transparent http/socks5 proxy, imap, ftp and http server, and more.</p>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkrequest Test:
#####################################################################
qt_internal_add_test(tst_qnetworkrequest
SOURCES
tst_qnetworkrequest.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,556 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#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
{
Q_OBJECT
private slots:
void ctor_data();
void ctor();
void implicitDefaultCtor();
void setUrl_data();
void setUrl();
void setRawHeader_data();
void setRawHeader();
void rawHeaderList_data();
void rawHeaderList();
void setHeader_data();
void setHeader();
void rawHeaderParsing_data();
void rawHeaderParsing();
void originatingObject();
void removeHeader();
};
void tst_QNetworkRequest::ctor_data()
{
QTest::addColumn<QUrl>("url");
QTest::newRow("nothing") << QUrl();
QTest::newRow("empty") << QUrl();
QTest::newRow("http") << QUrl("http://qt-project.org");
}
void tst_QNetworkRequest::ctor()
{
QFETCH(QUrl, url);
if (qstrcmp(QTest::currentDataTag(), "nothing") == 0) {
QNetworkRequest request;
QCOMPARE(request.url(), url);
} else {
QNetworkRequest request(url);
QCOMPARE(request.url(), url);
}
}
void tst_QNetworkRequest::implicitDefaultCtor()
{
QNetworkRequest r = {};
Q_UNUSED(r);
}
void tst_QNetworkRequest::setUrl_data()
{
ctor_data();
}
void tst_QNetworkRequest::setUrl()
{
QFETCH(QUrl, url);
QNetworkRequest request;
if (qstrcmp(QTest::currentDataTag(), "nothing") != 0)
request.setUrl(url);
QCOMPARE(request.url(), url);
}
void tst_QNetworkRequest::setRawHeader_data()
{
QTest::addColumn<QByteArray>("header");
QTest::addColumn<QByteArray>("value");
QTest::addColumn<QByteArray>("headerToGet");
QTest::addColumn<QByteArray>("expectedValue");
QTest::addColumn<bool>("hasHeader");
QTest::newRow("null-header") << QByteArray() << QByteArray("abc")
<< QByteArray() << QByteArray() << false;
QTest::newRow("empty-header") << QByteArray("") << QByteArray("abc")
<< QByteArray("") << QByteArray() << false;
QTest::newRow("null-value") << QByteArray("foo") << QByteArray()
<< QByteArray("foo") << QByteArray() << false;
QTest::newRow("empty-value") << QByteArray("foo") << QByteArray("")
<< QByteArray("foo") << QByteArray("") << true;
QTest::newRow("empty-value-vs-null") << QByteArray("foo") << QByteArray("")
<< QByteArray("foo") << QByteArray() << true;
QTest::newRow("UPPER-UPPER") << QByteArray("FOO") << QByteArray("abc")
<< QByteArray("FOO") << QByteArray("abc") << true;
QTest::newRow("UPPER-Mixed") << QByteArray("FOO") << QByteArray("abc")
<< QByteArray("Foo") << QByteArray("abc") << true;
QTest::newRow("UPPER-lower") << QByteArray("FOO") << QByteArray("abc")
<< QByteArray("foo") << QByteArray("abc") << true;
QTest::newRow("Mixed-UPPER") << QByteArray("Foo") << QByteArray("abc")
<< QByteArray("FOO") << QByteArray("abc") << true;
QTest::newRow("Mixed-Mixed") << QByteArray("Foo") << QByteArray("abc")
<< QByteArray("Foo") << QByteArray("abc") << true;
QTest::newRow("Mixed-lower") << QByteArray("Foo") << QByteArray("abc")
<< QByteArray("foo") << QByteArray("abc") << true;
QTest::newRow("lower-UPPER") << QByteArray("foo") << QByteArray("abc")
<< QByteArray("FOO") << QByteArray("abc") << true;
QTest::newRow("lower-Mixed") << QByteArray("foo") << QByteArray("abc")
<< QByteArray("Foo") << QByteArray("abc") << true;
QTest::newRow("lower-lower") << QByteArray("foo") << QByteArray("abc")
<< QByteArray("foo") << QByteArray("abc") << true;
}
void tst_QNetworkRequest::setRawHeader()
{
QFETCH(QByteArray, header);
QFETCH(QByteArray, value);
QFETCH(QByteArray, headerToGet);
QFETCH(QByteArray, expectedValue);
QFETCH(bool, hasHeader);
QNetworkRequest request;
request.setRawHeader(header, value);
QCOMPARE(request.hasRawHeader(headerToGet), hasHeader);
QCOMPARE(request.rawHeader(headerToGet), expectedValue);
}
void tst_QNetworkRequest::rawHeaderList_data()
{
QTest::addColumn<QList<QByteArray> >("set");
QTest::addColumn<QList<QByteArray> >("expected");
QTest::newRow("empty") << QList<QByteArray>() << QList<QByteArray>();
QList<QByteArray> set;
QList<QByteArray> expected;
set << "foo";
expected = set;
QTest::newRow("one") << set << expected;
set << "bar";
expected = set;
QTest::newRow("two") << set << expected;
set.clear();
expected.clear();
set << "foo" << "foo";
expected << "foo";
QTest::newRow("repeated") << set << expected;
set.clear();
expected.clear();
set << "foo" << "bar" << "foo";
expected << "bar" << "foo";
QTest::newRow("repeated-interleaved") << set << expected;
}
void tst_QNetworkRequest::rawHeaderList()
{
QFETCH(QList<QByteArray>, set);
QFETCH(QList<QByteArray>, expected);
QNetworkRequest request;
foreach (QByteArray header, set)
request.setRawHeader(header, "a value");
QList<QByteArray> got = request.rawHeaderList();
QCOMPARE(got.size(), expected.size());
for (int i = 0; i < got.size(); ++i)
QCOMPARE(got.at(i), expected.at(i));
}
void tst_QNetworkRequest::setHeader_data()
{
QTest::addColumn<QNetworkRequest::KnownHeaders>("cookedHeader");
QTest::addColumn<QVariant>("cookedValue");
QTest::addColumn<bool>("success");
QTest::addColumn<QString>("rawHeader");
QTest::addColumn<QString>("rawValue");
QTest::newRow("Content-Type-Null") << QNetworkRequest::ContentTypeHeader << QVariant()
<< false << "Content-Type" << "";
QTest::newRow("Content-Type-String") << QNetworkRequest::ContentTypeHeader << QVariant("text/html")
<< true
<< "Content-Type" << "text/html";
QTest::newRow("Content-Type-ByteArray") << QNetworkRequest::ContentTypeHeader
<< QVariant("text/html") << true
<< "Content-Type" << "text/html";
QTest::newRow("Content-Length-Int") << QNetworkRequest::ContentLengthHeader << QVariant(1)
<< true << "Content-Length" << "1";
QTest::newRow("Content-Length-Int64") << QNetworkRequest::ContentLengthHeader << QVariant(qint64(1))
<< true << "Content-Length" << "1";
QTest::newRow("Location-String") << QNetworkRequest::LocationHeader << QVariant("http://foo/with space")
<< true << "Location" << "http://foo/with space";
QTest::newRow("Location-ByteArray") << QNetworkRequest::LocationHeader
<< QVariant("http://foo/with space")
<< true << "Location" << "http://foo/with space";
QTest::newRow("Location-Url") << QNetworkRequest::LocationHeader
<< QVariant(QUrl("http://foo/with space"))
<< true << "Location" << "http://foo/with%20space";
QTest::newRow("Last-Modified-Date") << QNetworkRequest::LastModifiedHeader
<< QVariant(QDate(2007, 11, 01))
<< true << "Last-Modified"
<< "Thu, 01 Nov 2007 00:00:00 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))
<< true << "If-Modified-Since"
<< "Sat, 01 Jul 2017 00:00:00 GMT";
QTest::newRow("If-Modified-Since-DateTime") << QNetworkRequest::IfModifiedSinceHeader
<< QVariant(QDateTime(QDate(2017, 7, 01),
QTime(3, 14, 15),
QTimeZone::UTC))
<< true << "If-Modified-Since"
<< "Sat, 01 Jul 2017 03:14:15 GMT";
QTest::newRow("Etag-strong") << QNetworkRequest::ETagHeader << QVariant(R"("xyzzy")")
<< true << "ETag" << R"("xyzzy")";
QTest::newRow("Etag-weak") << QNetworkRequest::ETagHeader << QVariant(R"(W/"xyzzy")")
<< true << "ETag" << R"(W/"xyzzy")";
QTest::newRow("Etag-empty") << QNetworkRequest::ETagHeader << QVariant(R"("")")
<< true << "ETag" << R"("")";
QTest::newRow("If-Match-empty") << QNetworkRequest::IfMatchHeader << QVariant(R"("")")
<< true << "If-Match" << R"("")";
QTest::newRow("If-Match-any") << QNetworkRequest::IfMatchHeader << QVariant(R"("*")")
<< true << "If-Match" << R"("*")";
QTest::newRow("If-Match-single") << QNetworkRequest::IfMatchHeader << QVariant(R"("xyzzy")")
<< true << "If-Match" << R"("xyzzy")";
QTest::newRow("If-Match-multiple") << QNetworkRequest::IfMatchHeader
<< QVariant(R"("xyzzy", "r2d2xxxx", "c3piozzzz")")
<< true << "If-Match"
<< R"("xyzzy", "r2d2xxxx", "c3piozzzz")";
QTest::newRow("If-None-Match-empty") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("")")
<< true << "If-None-Match" << R"("")";
QTest::newRow("If-None-Match-any") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("*")")
<< true << "If-None-Match" << R"("*")";
QTest::newRow("If-None-Match-single") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("xyzzy")")
<< true << "If-None-Match" << R"("xyzzy")";
QTest::newRow("If-None-Match-multiple") << QNetworkRequest::IfNoneMatchHeader
<< QVariant(R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")")
<< true << "If-None-Match"
<< R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")";
QNetworkCookie cookie;
cookie.setName("a");
cookie.setValue("b");
QTest::newRow("Cookie-1") << QNetworkRequest::CookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Cookie"
<< "a=b";
QTest::newRow("SetCookie-1") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Set-Cookie"
<< "a=b";
cookie.setPath("/");
QTest::newRow("Cookie-2") << QNetworkRequest::CookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Cookie"
<< "a=b";
QTest::newRow("SetCookie-2") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Set-Cookie"
<< "a=b; path=/";
QNetworkCookie cookie2;
cookie2.setName("c");
cookie2.setValue("d");
QTest::newRow("Cookie-3") << QNetworkRequest::CookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie << cookie2)
<< true << "Cookie"
<< "a=b; c=d";
QTest::newRow("SetCookie-3") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie << cookie2)
<< true << "Set-Cookie"
<< "a=b; path=/, c=d";
}
void tst_QNetworkRequest::setHeader()
{
QFETCH(QNetworkRequest::KnownHeaders, cookedHeader);
QFETCH(QVariant, cookedValue);
QFETCH(bool, success);
QFETCH(QString, rawHeader);
QFETCH(QString, rawValue);
QNetworkRequest request;
request.setHeader(cookedHeader, cookedValue);
QCOMPARE(request.header(cookedHeader).isNull(), !success);
QCOMPARE(request.hasRawHeader(rawHeader.toLatin1()), success);
QCOMPARE(request.rawHeader(rawHeader.toLatin1()).isEmpty(), !success);
if (success) {
QCOMPARE(request.header(cookedHeader), cookedValue);
QCOMPARE(QString(request.rawHeader(rawHeader.toLatin1())), rawValue);
}
}
void tst_QNetworkRequest::rawHeaderParsing_data()
{
QTest::addColumn<QNetworkRequest::KnownHeaders>("cookedHeader");
QTest::addColumn<QVariant>("cookedValue");
QTest::addColumn<bool>("success");
QTest::addColumn<QString>("rawHeader");
QTest::addColumn<QString>("rawValue");
QTest::newRow("Content-Type") << QNetworkRequest::ContentTypeHeader << QVariant("text/html")
<< true
<< "Content-Type" << "text/html";
QTest::newRow("Content-Length") << QNetworkRequest::ContentLengthHeader << QVariant(qint64(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),
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),
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),
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),
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),
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),
QTimeZone::UTC))
<< true << "If-Modified-Since"
<< "Sun Aug 6 08:49:37 1994";
QTest::newRow("Etag-strong") << QNetworkRequest::ETagHeader << QVariant(R"("xyzzy")")
<< true << "ETag" << R"("xyzzy")";
QTest::newRow("Etag-weak") << QNetworkRequest::ETagHeader << QVariant(R"(W/"xyzzy")")
<< true << "ETag" << R"(W/"xyzzy")";
QTest::newRow("Etag-empty") << QNetworkRequest::ETagHeader << QVariant(R"("")")
<< true << "ETag" << R"("")";
QTest::newRow("If-Match-empty") << QNetworkRequest::IfMatchHeader << QVariant(QStringList(R"("")"))
<< true << "If-Match" << R"("")";
QTest::newRow("If-Match-any") << QNetworkRequest::IfMatchHeader << QVariant(QStringList(R"("*")"))
<< true << "If-Match" << R"("*")";
QTest::newRow("If-Match-single") << QNetworkRequest::IfMatchHeader
<< QVariant(QStringList(R"("xyzzy")"))
<< true << "If-Match" << R"("xyzzy")";
QTest::newRow("If-Match-multiple") << QNetworkRequest::IfMatchHeader
<< QVariant(QStringList({R"("xyzzy")",
R"("r2d2xxxx")",
R"("c3piozzzz")"}))
<< true << "If-Match"
<< R"("xyzzy", "r2d2xxxx", "c3piozzzz")";
QTest::newRow("If-None-Match-empty") << QNetworkRequest::IfNoneMatchHeader
<< QVariant(QStringList(R"("")"))
<< true << "If-None-Match" << R"("")";
QTest::newRow("If-None-Match-any") << QNetworkRequest::IfNoneMatchHeader
<< QVariant(QStringList(R"("*")"))
<< true << "If-None-Match" << R"("*")";
QTest::newRow("If-None-Match-single") << QNetworkRequest::IfNoneMatchHeader
<< QVariant(QStringList(R"("xyzzy")"))
<< true << "If-None-Match" << R"("xyzzy")";
QTest::newRow("If-None-Match-multiple") << QNetworkRequest::IfNoneMatchHeader
<< QVariant(QStringList({R"("xyzzy")",
R"(W/"r2d2xxxx")",
R"("c3piozzzz")"}))
<< true << "If-None-Match"
<< R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")";
QTest::newRow("Content-Length-invalid1") << QNetworkRequest::ContentLengthHeader << QVariant()
<< false << "Content-Length" << "1a";
QTest::newRow("Content-Length-invalid2") << QNetworkRequest::ContentLengthHeader << QVariant()
<< false << "Content-Length" << "a";
QTest::newRow("Location-invalid1") << QNetworkRequest::LocationHeader << QVariant() << false
<< "Location" << "abc";
QTest::newRow("Location-invalid2") << QNetworkRequest::LocationHeader << QVariant() << false
<< "Location" << "1http://foo";
QTest::newRow("Location-invalid3") << QNetworkRequest::LocationHeader << QVariant() << false
<< "Location" << "http://foo/%gg";
// don't test for invalid dates because we may want to support broken servers in the future
QNetworkCookie cookie;
cookie.setName("a");
cookie.setValue("b");
QTest::newRow("Cookie-1") << QNetworkRequest::CookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Cookie"
<< "a=b";
QTest::newRow("SetCookie-1") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Set-Cookie"
<< "a=b";
cookie.setPath("/");
QTest::newRow("SetCookie-2") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie)
<< true << "Set-Cookie"
<< "a=b; path=/";
QNetworkCookie cookie2;
cookie.setPath("");
cookie2.setName("c");
cookie2.setValue("d");
QTest::newRow("Cookie-3") << QNetworkRequest::CookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie << cookie2)
<< true << "Cookie"
<< "a=b; c=d";
cookie.setPath("/");
QTest::newRow("SetCookie-3") << QNetworkRequest::SetCookieHeader
<< QVariant::fromValue(QList<QNetworkCookie>() << cookie << cookie2)
<< true << "Set-Cookie"
<< "a=b; path=/\nc=d";
QTest::newRow("Content-Disposition") << QNetworkRequest::ContentDispositionHeader
<< QVariant("attachment; filename=\"test.txt\"") << true
<< "Content-Disposition" << "attachment; filename=\"test.txt\"";
}
void tst_QNetworkRequest::rawHeaderParsing()
{
QFETCH(QNetworkRequest::KnownHeaders, cookedHeader);
QFETCH(QVariant, cookedValue);
QFETCH(bool, success);
QFETCH(QString, rawHeader);
QFETCH(QString, rawValue);
QNetworkRequest request;
request.setRawHeader(rawHeader.toLatin1(), rawValue.toLatin1());
// even if it doesn't parse, it's as a raw header
QVERIFY(request.hasRawHeader(rawHeader.toLatin1()));
QVERIFY(request.hasRawHeader(rawHeader.toLower().toLatin1()));
QCOMPARE(QString(request.rawHeader(rawHeader.toLatin1())), rawValue);
QCOMPARE(request.header(cookedHeader).isNull(), !success);
if (cookedValue.metaType().id() < QMetaType::User)
QCOMPARE(request.header(cookedHeader), cookedValue);
else if (cookedValue.userType() == qMetaTypeId<QList<QNetworkCookie> >())
QCOMPARE(qvariant_cast<QList<QNetworkCookie> >(request.header(cookedHeader)),
qvariant_cast<QList<QNetworkCookie> >(cookedValue));
}
void tst_QNetworkRequest::removeHeader()
{
QNetworkRequest request;
request.setRawHeader("Foo", "1");
QVERIFY(request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("foo"));
request.setRawHeader("Foo", QByteArray());
QVERIFY(!request.hasRawHeader("Foo"));
// same, but remove with different capitalisation
request.setRawHeader("Foo", "1");
QVERIFY(request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("foo"));
request.setRawHeader("foo", QByteArray());
QVERIFY(!request.hasRawHeader("Foo"));
// same, but not the first
request.setRawHeader("Bar", "2");
request.setRawHeader("Foo", "1");
QVERIFY(request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("foo"));
request.setRawHeader("foo", QByteArray());
QVERIFY(!request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("bar"));
// same, but not the first nor last
request.setRawHeader("Foo", "1");
request.setRawHeader("Bar", "3");
QVERIFY(request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("foo"));
request.setRawHeader("foo", QByteArray());
QVERIFY(!request.hasRawHeader("Foo"));
QVERIFY(request.hasRawHeader("bar"));
}
void tst_QNetworkRequest::originatingObject()
{
QNetworkRequest request;
QVERIFY(!request.originatingObject());
{
QObject dummy;
request.setOriginatingObject(&dummy);
QCOMPARE(request.originatingObject(), &dummy);
}
QVERIFY(!request.originatingObject());
}
QTEST_MAIN(tst_QNetworkRequest)
#include "tst_qnetworkrequest.moc"

View File

@ -0,0 +1,23 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT INTEGRITY)
add_subdirectory(qdnslookup)
add_subdirectory(qdnslookup_appless)
endif()
if(QT_FEATURE_networkinterface)
add_subdirectory(qnetworkproxyfactory)
add_subdirectory(qnetworkinterface)
endif()
add_subdirectory(qnetworkproxy)
add_subdirectory(qnetworkdatagram)
add_subdirectory(qnetworkaddressentry)
add_subdirectory(qhostaddress)
if(QT_FEATURE_private_tests AND NOT MACOS AND NOT INTEGRITY)
add_subdirectory(qhostinfo)
endif()
if(QT_FEATURE_private_tests)
add_subdirectory(qauthenticator)
add_subdirectory(qnetworkinformation)
add_subdirectory(qnetworkinformation_appless)
endif()

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qauthenticator Test:
#####################################################################
qt_internal_add_test(tst_qauthenticator
SOURCES
tst_qauthenticator.cpp
LIBRARIES
Qt::NetworkPrivate
)

View File

@ -0,0 +1,192 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QString>
#include <QTest>
#include <QtCore/QCoreApplication>
#include <QtNetwork/QAuthenticator>
#include <private/qauthenticator_p.h>
class tst_QAuthenticator : public QObject
{
Q_OBJECT
public:
tst_QAuthenticator();
private Q_SLOTS:
void basicAuth();
void basicAuth_data();
void ntlmAuth_data();
void ntlmAuth();
void sha256AndMd5Digest();
void equalityOperators();
void isMethodSupported();
};
tst_QAuthenticator::tst_QAuthenticator()
{
}
void tst_QAuthenticator::basicAuth_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<QString>("realm");
QTest::addColumn<QString>("user");
QTest::addColumn<QString>("password");
QTest::addColumn<QByteArray>("expectedReply");
QTest::newRow("just-user") << "" << "" << "foo" << "" << QByteArray("foo:").toBase64();
QTest::newRow("user-password") << "" << "" << "foo" << "bar" << QByteArray("foo:bar").toBase64();
QTest::newRow("user-password-realm") << "realm=\"secure area\"" << "secure area" << "foo" << "bar" << QByteArray("foo:bar").toBase64();
}
void tst_QAuthenticator::basicAuth()
{
QFETCH(QString, data);
QFETCH(QString, realm);
QFETCH(QString, user);
QFETCH(QString, password);
QFETCH(QByteArray, expectedReply);
QAuthenticator auth;
auth.detach();
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth);
QCOMPARE(priv->phase, QAuthenticatorPrivate::Start);
QList<QPair<QByteArray, QByteArray> > headers;
headers << qMakePair(QByteArray("WWW-Authenticate"), "Basic " + data.toUtf8());
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
QCOMPARE(auth.realm(), realm);
QCOMPARE(auth.option("realm").toString(), realm);
auth.setUser(user);
auth.setPassword(password);
QCOMPARE(priv->phase, QAuthenticatorPrivate::Start);
QCOMPARE(priv->calculateResponse("GET", "/", u"").constData(), QByteArray("Basic " + expectedReply).constData());
}
void tst_QAuthenticator::ntlmAuth_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<QString>("realm");
QTest::addColumn<bool>("sso");
QTest::newRow("no-realm") << "TlRMTVNTUAACAAAAHAAcADAAAAAFAoEATFZ3OLRQADIAAAAAAAAAAJYAlgBMAAAAUQBUAC0AVABFAFMAVAAtAEQATwBNAEEASQBOAAIAHABRAFQALQBUAEUAUwBUAC0ARABPAE0AQQBJAE4AAQAcAFEAVAAtAFQARQBTAFQALQBTAEUAUgBWAEUAUgAEABYAcQB0AC0AdABlAHMAdAAtAG4AZQB0AAMANABxAHQALQB0AGUAcwB0AC0AcwBlAHIAdgBlAHIALgBxAHQALQB0AGUAcwB0AC0AbgBlAHQAAAAAAA==" << "" << false;
QTest::newRow("with-realm") << "TlRMTVNTUAACAAAADAAMADgAAAAFAoECWCZkccFFAzwAAAAAAAAAAL4AvgBEAAAABQLODgAAAA9NAEcARABOAE8ASwACAAwATQBHAEQATgBPAEsAAQAcAE4ATwBLAC0AQQBNAFMAUwBTAEYARQAtADAAMQAEACAAbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQADAD4AbgBvAGsALQBhAG0AcwBzAHMAZgBlAC0AMAAxAC4AbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQAFACAAbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQAAAAAA" << "NOE" << false;
QTest::newRow("no-realm-sso") << "TlRMTVNTUAACAAAAHAAcADAAAAAFAoEATFZ3OLRQADIAAAAAAAAAAJYAlgBMAAAAUQBUAC0AVABFAFMAVAAtAEQATwBNAEEASQBOAAIAHABRAFQALQBUAEUAUwBUAC0ARABPAE0AQQBJAE4AAQAcAFEAVAAtAFQARQBTAFQALQBTAEUAUgBWAEUAUgAEABYAcQB0AC0AdABlAHMAdAAtAG4AZQB0AAMANABxAHQALQB0AGUAcwB0AC0AcwBlAHIAdgBlAHIALgBxAHQALQB0AGUAcwB0AC0AbgBlAHQAAAAAAA==" << "" << true;
QTest::newRow("with-realm-sso") << "TlRMTVNTUAACAAAADAAMADgAAAAFAoECWCZkccFFAzwAAAAAAAAAAL4AvgBEAAAABQLODgAAAA9NAEcARABOAE8ASwACAAwATQBHAEQATgBPAEsAAQAcAE4ATwBLAC0AQQBNAFMAUwBTAEYARQAtADAAMQAEACAAbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQADAD4AbgBvAGsALQBhAG0AcwBzAHMAZgBlAC0AMAAxAC4AbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQAFACAAbQBnAGQAbgBvAGsALgBuAG8AawBpAGEALgBjAG8AbQAAAAAA" << "NOE" << true;
}
void tst_QAuthenticator::ntlmAuth()
{
QFETCH(QString, data);
QFETCH(QString, realm);
QFETCH(bool, sso);
QAuthenticator auth;
if (!sso) {
auth.setUser("unimportant");
auth.setPassword("unimportant");
}
auth.detach();
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth);
QCOMPARE(priv->phase, QAuthenticatorPrivate::Start);
QList<QPair<QByteArray, QByteArray> > headers;
// NTLM phase 1: negotiate
// This phase of NTLM contains no information, other than what we're willing to negotiate
// Current implementation uses flags:
// NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET
headers << qMakePair(QByteArrayLiteral("WWW-Authenticate"), QByteArrayLiteral("NTLM"));
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
if (sso)
QVERIFY(priv->calculateResponse("GET", "/", u"").startsWith("NTLM "));
else
QCOMPARE(priv->calculateResponse("GET", "/", u"").constData(), "NTLM TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAA=");
// NTLM phase 2: challenge
headers.clear();
headers << qMakePair(QByteArray("WWW-Authenticate"), "NTLM " + data.toUtf8());
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
QEXPECT_FAIL("with-realm", "NTLM authentication code doesn't extract the realm", Continue);
QEXPECT_FAIL("with-realm-sso", "NTLM authentication code doesn't extract the realm", Continue);
QCOMPARE(auth.realm(), realm);
QVERIFY(priv->calculateResponse("GET", "/", u"").startsWith("NTLM "));
}
// We don't (currently) support SHA256. So, when presented with the option of MD5 or SHA256,
// we should always pick MD5.
void tst_QAuthenticator::sha256AndMd5Digest()
{
QByteArray md5 = "Digest realm=\"\", nonce=\"\", algorithm=MD5, qop=\"auth\"";
QByteArray sha256 = "Digest realm=\"\", nonce=\"\", algorithm=SHA-256, qop=\"auth\"";
QAuthenticator auth;
auth.setUser("unimportant");
auth.setPassword("unimportant");
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(auth);
QVERIFY(priv->isMethodSupported("digest")); // sanity check
QCOMPARE(priv->phase, QAuthenticatorPrivate::Start);
QList<QPair<QByteArray, QByteArray>> headers;
// Put sha256 first, so that its parsed first...
headers.emplace_back("WWW-Authenticate", sha256);
headers.emplace_back("WWW-Authenticate", md5);
priv->parseHttpResponse(headers, false, QString());
QByteArray response = priv->calculateResponse("GET", "/index", {});
QCOMPARE(priv->phase, QAuthenticatorPrivate::Done);
QVERIFY(!response.isEmpty());
QVERIFY(!response.contains("algorithm=SHA-256"));
QVERIFY(response.contains("algorithm=MD5"));
}
void tst_QAuthenticator::equalityOperators()
{
QAuthenticator s1, s2;
QVERIFY(s2 == s1);
QVERIFY(s1 == s2);
QVERIFY(!(s1 != s2));
QVERIFY(!(s2 != s1));
s1.setUser("User");
QVERIFY(!(s2 == s1));
QVERIFY(!(s1 == s2));
QVERIFY(s1 != s2);
QVERIFY(s2 != s1);
}
void tst_QAuthenticator::isMethodSupported()
{
QVERIFY(QAuthenticatorPrivate::isMethodSupported("basic"));
QVERIFY(QAuthenticatorPrivate::isMethodSupported("Basic realm=\"Shadow\""));
QVERIFY(QAuthenticatorPrivate::isMethodSupported("DIgesT"));
QVERIFY(QAuthenticatorPrivate::isMethodSupported("NTLM"));
QVERIFY(QAuthenticatorPrivate::isMethodSupported("ntlm"));
#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
QVERIFY(QAuthenticatorPrivate::isMethodSupported("negotiate"));
#else
QVERIFY(!QAuthenticatorPrivate::isMethodSupported("negotiate"));
#endif
QVERIFY(!QAuthenticatorPrivate::isMethodSupported("Bearer"));
}
QTEST_MAIN(tst_QAuthenticator);
#include "tst_qauthenticator.moc"

View File

@ -0,0 +1,2 @@
[lookup]
*

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qdnslookup Test:
#####################################################################
qt_internal_add_test(tst_qdnslookup
SOURCES
tst_qdnslookup.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,403 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QSignalSpy>
#include <QtNetwork/QDnsLookup>
#include <QtNetwork/QHostAddress>
static const int Timeout = 15000; // 15s
class tst_QDnsLookup: public QObject
{
Q_OBJECT
QString domainName(const QString &input);
QString domainNameList(const QString &input);
QStringList domainNameListAlternatives(const QString &input);
public slots:
void initTestCase();
private slots:
void lookup_data();
void lookup();
void lookupReuse();
void lookupAbortRetry();
void bindingsAndProperties();
};
void tst_QDnsLookup::initTestCase()
{
QTest::addColumn<QString>("tld");
QTest::newRow("normal") << ".test.qt-project.org";
QTest::newRow("idn") << ".alqualond\xc3\xab.test.qt-project.org";
}
QString tst_QDnsLookup::domainName(const QString &input)
{
if (input.isEmpty())
return input;
if (input.endsWith(QLatin1Char('.'))) {
QString nodot = input;
nodot.chop(1);
return nodot;
}
QFETCH_GLOBAL(QString, tld);
return input + tld;
}
QString tst_QDnsLookup::domainNameList(const QString &input)
{
QStringList list = input.split(QLatin1Char(';'));
QString result;
foreach (const QString &s, list) {
if (!result.isEmpty())
result += ';';
result += domainName(s);
}
return result;
}
QStringList tst_QDnsLookup::domainNameListAlternatives(const QString &input)
{
QStringList alternatives = input.split('|');
for (int i = 0; i < alternatives.size(); ++i)
alternatives[i] = domainNameList(alternatives[i]);
return alternatives;
}
void tst_QDnsLookup::lookup_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("domain");
QTest::addColumn<int>("error");
QTest::addColumn<QString>("cname");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("mx");
QTest::addColumn<QString>("ns");
QTest::addColumn<QString>("ptr");
QTest::addColumn<QString>("srv");
QTest::addColumn<QString>("txt");
QTest::newRow("a-empty") << int(QDnsLookup::A) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << ""<< "" << "";
QTest::newRow("a-notfound") << int(QDnsLookup::A) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("a-single") << int(QDnsLookup::A) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "";
QTest::newRow("a-multi") << int(QDnsLookup::A) << "a-multi" << int(QDnsLookup::NoError) << "" << "192.0.2.1;192.0.2.2;192.0.2.3" << "" << "" << "" << "" << "";
QTest::newRow("aaaa-empty") << int(QDnsLookup::AAAA) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("aaaa-notfound") << int(QDnsLookup::AAAA) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("aaaa-single") << int(QDnsLookup::AAAA) << "aaaa-single" << int(QDnsLookup::NoError) << "" << "2001:db8::1" << "" << "" << "" << "" << "";
QTest::newRow("aaaa-multi") << int(QDnsLookup::AAAA) << "aaaa-multi" << int(QDnsLookup::NoError) << "" << "2001:db8::1;2001:db8::2;2001:db8::3" << "" << "" << "" << "" << "";
QTest::newRow("any-empty") << int(QDnsLookup::ANY) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("any-notfound") << int(QDnsLookup::ANY) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("any-a-single") << int(QDnsLookup::ANY) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "";
QTest::newRow("any-a-plus-aaaa") << int(QDnsLookup::ANY) << "a-plus-aaaa" << int(QDnsLookup::NoError) << "" << "198.51.100.1;2001:db8::1:1" << "" << "" << "" << "" << "";
QTest::newRow("any-multi") << int(QDnsLookup::ANY) << "multi" << int(QDnsLookup::NoError) << "" << "198.51.100.1;198.51.100.2;198.51.100.3;2001:db8::1:1;2001:db8::1:2" << "" << "" << "" << "" << "";
QTest::newRow("mx-empty") << int(QDnsLookup::MX) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("mx-notfound") << int(QDnsLookup::MX) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("mx-single") << int(QDnsLookup::MX) << "mx-single" << int(QDnsLookup::NoError) << "" << "" << "10 multi" << "" << "" << "" << "";
QTest::newRow("mx-single-cname") << int(QDnsLookup::MX) << "mx-single-cname" << int(QDnsLookup::NoError) << "" << "" << "10 cname" << "" << "" << "" << "";
QTest::newRow("mx-multi") << int(QDnsLookup::MX) << "mx-multi" << int(QDnsLookup::NoError) << "" << "" << "10 multi;20 a-single" << "" << "" << "" << "";
QTest::newRow("mx-multi-sameprio") << int(QDnsLookup::MX) << "mx-multi-sameprio" << int(QDnsLookup::NoError) << "" << ""
<< "10 multi;10 a-single|"
"10 a-single;10 multi" << "" << "" << "" << "";
QTest::newRow("ns-empty") << int(QDnsLookup::NS) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("ns-notfound") << int(QDnsLookup::NS) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("ns-single") << int(QDnsLookup::NS) << "ns-single" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net." << "" << "" << "";
QTest::newRow("ns-multi") << int(QDnsLookup::NS) << "ns-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net.;ns12.cloudns.net." << "" << "" << "";
QTest::newRow("ptr-empty") << int(QDnsLookup::PTR) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("ptr-notfound") << int(QDnsLookup::PTR) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
#if 0
// temporarily disabled since the new hosting provider can't insert
// PTR records outside of the in-addr.arpa zone
QTest::newRow("ptr-single") << int(QDnsLookup::PTR) << "ptr-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "a-single" << "" << "";
#endif
QTest::newRow("srv-empty") << int(QDnsLookup::SRV) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("srv-notfound") << int(QDnsLookup::SRV) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("srv-single") << int(QDnsLookup::SRV) << "_echo._tcp.srv-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 7 multi" << "";
QTest::newRow("srv-prio") << int(QDnsLookup::SRV) << "_echo._tcp.srv-prio" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "1 0 7 multi;2 0 7 a-plus-aaaa" << "";
QTest::newRow("srv-weighted") << int(QDnsLookup::SRV) << "_echo._tcp.srv-weighted" << int(QDnsLookup::NoError) << "" << "" << "" << "" << ""
<< "5 75 7 multi;5 25 7 a-plus-aaaa|"
"5 25 7 a-plus-aaaa;5 75 7 multi" << "";
QTest::newRow("srv-multi") << int(QDnsLookup::SRV) << "_echo._tcp.srv-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "" << ""
<< "1 50 7 multi;2 50 7 a-single;2 50 7 aaaa-single;3 50 7 a-multi|"
"1 50 7 multi;2 50 7 aaaa-single;2 50 7 a-single;3 50 7 a-multi" << "";
QTest::newRow("txt-empty") << int(QDnsLookup::TXT) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("txt-notfound") << int(QDnsLookup::TXT) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
QTest::newRow("txt-single") << int(QDnsLookup::TXT) << "txt-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello";
QTest::newRow("txt-multi-onerr") << int(QDnsLookup::TXT) << "txt-multi-onerr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << ""
<< QString::fromLatin1("Hello\0World", sizeof("Hello\0World") - 1);
QTest::newRow("txt-multi-multirr") << int(QDnsLookup::TXT) << "txt-multi-multirr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello;World";
}
static QByteArray msgDnsLookup(QDnsLookup::Error actualError,
int expectedError,
const QString &domain,
const QString &cname,
const QString &host,
const QString &srv,
const QString &mx,
const QString &ns,
const QString &ptr,
const QString &errorString)
{
QString result;
QTextStream str(&result);
str << "Actual error: " << actualError;
if (!errorString.isEmpty())
str << " (" << errorString << ')';
str << ", expected: " << expectedError;
str << ", domain: " << domain;
if (!cname.isEmpty())
str << ", cname: " << cname;
str << ", host: " << host;
if (!srv.isEmpty())
str << " server: " << srv;
if (!mx.isEmpty())
str << " mx: " << mx;
if (!ns.isEmpty())
str << " ns: " << ns;
if (!ptr.isEmpty())
str << " ptr: " << ptr;
return result.toLocal8Bit();
}
void tst_QDnsLookup::lookup()
{
QFETCH(int, type);
QFETCH(QString, domain);
QFETCH(int, error);
QFETCH(QString, cname);
QFETCH(QString, host);
QFETCH(QString, mx);
QFETCH(QString, ns);
QFETCH(QString, ptr);
QFETCH(QString, srv);
QFETCH(QString, txt);
// transform the inputs
domain = domainName(domain);
cname = domainName(cname);
ns = domainNameList(ns);
ptr = domainNameList(ptr);
// SRV and MX have reply entries that can change order
// and we can't sort
QStringList mx_alternatives = domainNameListAlternatives(mx);
QStringList srv_alternatives = domainNameListAlternatives(srv);
QDnsLookup lookup;
lookup.setType(static_cast<QDnsLookup::Type>(type));
lookup.setName(domain);
lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
#if defined(Q_OS_ANDROID)
if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
QEXPECT_FAIL("", "Not yet supported on Android", Abort);
#endif
QVERIFY2(int(lookup.error()) == error,
msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString()));
if (error == QDnsLookup::NoError)
QVERIFY(lookup.errorString().isEmpty());
QCOMPARE(int(lookup.type()), type);
QCOMPARE(lookup.name(), domain);
// canonical names
if (!cname.isEmpty()) {
QVERIFY(!lookup.canonicalNameRecords().isEmpty());
const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first();
QCOMPARE(cnameRecord.name(), domain);
QCOMPARE(cnameRecord.value(), cname);
} else {
QVERIFY(lookup.canonicalNameRecords().isEmpty());
}
// host addresses
const QString hostName = cname.isEmpty() ? domain : cname;
QStringList addresses;
foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) {
//reply may include A & AAAA records for nameservers, ignore them and only look at records matching the query
if (record.name() == hostName)
addresses << record.value().toString().toLower();
}
addresses.sort();
QCOMPARE(addresses.join(';'), host);
// mail exchanges
QStringList mailExchanges;
foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) {
QCOMPARE(record.name(), domain);
mailExchanges << QString::number(record.preference()) + QLatin1Char(' ') + record.exchange();
}
QVERIFY2(mx_alternatives.contains(mailExchanges.join(';')),
qPrintable("Actual: " + mailExchanges.join(';') + "\nExpected one of:\n" + mx_alternatives.join('\n')));
// name servers
QStringList nameServers;
foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) {
//reply may include NS records for authoritative nameservers, ignore them and only look at records matching the query
if (record.name() == domain)
nameServers << record.value();
}
nameServers.sort();
QCOMPARE(nameServers.join(';'), ns);
// pointers
if (!ptr.isEmpty()) {
QVERIFY(!lookup.pointerRecords().isEmpty());
const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first();
QCOMPARE(ptrRecord.name(), domain);
QCOMPARE(ptrRecord.value(), ptr);
} else {
QVERIFY(lookup.pointerRecords().isEmpty());
}
// services
QStringList services;
foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) {
QCOMPARE(record.name(), domain);
services << (QString::number(record.priority()) + QLatin1Char(' ')
+ QString::number(record.weight()) + QLatin1Char(' ')
+ QString::number(record.port()) + QLatin1Char(' ') + record.target());
}
QVERIFY2(srv_alternatives.contains(services.join(';')),
qPrintable("Actual: " + services.join(';') + "\nExpected one of:\n" + srv_alternatives.join('\n')));
// text
QStringList texts;
foreach (const QDnsTextRecord &record, lookup.textRecords()) {
QCOMPARE(record.name(), domain);
QString text;
foreach (const QByteArray &ba, record.values()) {
if (!text.isEmpty())
text += '\0';
text += QString::fromLatin1(ba);
}
texts << text;
}
texts.sort();
QCOMPARE(texts.join(';'), txt);
}
void tst_QDnsLookup::lookupReuse()
{
QDnsLookup lookup;
// first lookup
lookup.setType(QDnsLookup::A);
lookup.setName(domainName("a-single"));
lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
#if defined(Q_OS_ANDROID)
if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
QEXPECT_FAIL("", "Not yet supported on Android", Abort);
#endif
QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
QVERIFY(!lookup.hostAddressRecords().isEmpty());
QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single"));
QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1"));
// second lookup
lookup.setType(QDnsLookup::AAAA);
lookup.setName(domainName("aaaa-single"));
lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
QVERIFY(!lookup.hostAddressRecords().isEmpty());
QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));
QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1"));
}
void tst_QDnsLookup::lookupAbortRetry()
{
QDnsLookup lookup;
// try and abort the lookup
lookup.setType(QDnsLookup::A);
lookup.setName(domainName("a-single"));
lookup.lookup();
lookup.abort();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError));
QVERIFY(lookup.hostAddressRecords().isEmpty());
// retry a different lookup
lookup.setType(QDnsLookup::AAAA);
lookup.setName(domainName("aaaa-single"));
lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
#if defined(Q_OS_ANDROID)
if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
QEXPECT_FAIL("", "Not yet supported on Android", Abort);
#endif
QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
QVERIFY(!lookup.hostAddressRecords().isEmpty());
QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));
QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1"));
}
void tst_QDnsLookup::bindingsAndProperties()
{
QFETCH_GLOBAL(const QString, tld);
if (tld == QStringLiteral("idn"))
return;
QDnsLookup lookup;
lookup.setType(QDnsLookup::A);
QProperty<QDnsLookup::Type> dnsTypeProp;
lookup.bindableType().setBinding(Qt::makePropertyBinding(dnsTypeProp));
const QSignalSpy typeChangeSpy(&lookup, &QDnsLookup::typeChanged);
dnsTypeProp = QDnsLookup::AAAA;
QCOMPARE(typeChangeSpy.size(), 1);
QCOMPARE(lookup.type(), QDnsLookup::AAAA);
dnsTypeProp.setBinding(lookup.bindableType().makeBinding());
lookup.setType(QDnsLookup::A);
QCOMPARE(dnsTypeProp.value(), QDnsLookup::A);
QProperty<QString> nameProp;
lookup.bindableName().setBinding(Qt::makePropertyBinding(nameProp));
const QSignalSpy nameChangeSpy(&lookup, &QDnsLookup::nameChanged);
nameProp = QStringLiteral("a-plus-aaaa");
QCOMPARE(nameChangeSpy.size(), 1);
QCOMPARE(lookup.name(), QStringLiteral("a-plus-aaaa"));
nameProp.setBinding(lookup.bindableName().makeBinding());
lookup.setName(QStringLiteral("a-single"));
QCOMPARE(nameProp.value(), QStringLiteral("a-single"));
QProperty<QHostAddress> nameserverProp;
lookup.bindableNameserver().setBinding(Qt::makePropertyBinding(nameserverProp));
const QSignalSpy nameserverChangeSpy(&lookup, &QDnsLookup::nameserverChanged);
nameserverProp = QHostAddress::LocalHost;
QCOMPARE(nameserverChangeSpy.size(), 1);
QCOMPARE(lookup.nameserver(), QHostAddress::LocalHost);
nameserverProp.setBinding(lookup.bindableNameserver().makeBinding());
lookup.setNameserver(QHostAddress::Any);
QCOMPARE(nameserverProp.value(), QHostAddress::Any);
}
QTEST_MAIN(tst_QDnsLookup)
#include "tst_qdnslookup.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qdnslookup_appless Test:
#####################################################################
qt_internal_add_test(tst_qdnslookup_appless
SOURCES
tst_qdnslookup_appless.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,55 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QCoreApplication>
#include <QtNetwork/QDnsLookup>
#include <QTest>
#include <QTestEventLoop>
class tst_QDnsLookup_Appless : public QObject
{
Q_OBJECT
private slots:
void noApplication();
void recreateApplication();
void destroyApplicationDuringLookup();
};
void tst_QDnsLookup_Appless::noApplication()
{
QTest::ignoreMessage(QtWarningMsg, "QDnsLookup requires a QCoreApplication");
QDnsLookup dns(QDnsLookup::A, "a-single.test.qt-project.org");
dns.lookup();
}
void tst_QDnsLookup_Appless::recreateApplication()
{
int argc = 0;
char **argv = 0;
for (int i = 0; i < 10; ++i) {
QCoreApplication app(argc, argv);
QDnsLookup dns(QDnsLookup::A, "a-single.test.qt-project.org");
dns.lookup();
if (!dns.isFinished()) {
QObject::connect(&dns, SIGNAL(finished()),
&QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(10);
}
QVERIFY(dns.isFinished());
}
}
void tst_QDnsLookup_Appless::destroyApplicationDuringLookup()
{
int argc = 0;
char **argv = 0;
for (int i = 0; i < 10; ++i) {
QCoreApplication app(argc, argv);
QDnsLookup dns(QDnsLookup::A, "a-single.test.macieira.info");
dns.lookup();
}
}
QTEST_APPLESS_MAIN(tst_QDnsLookup_Appless)
#include "tst_qdnslookup_appless.moc"

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qhostaddress Test:
#####################################################################
qt_internal_add_test(tst_qhostaddress
SOURCES
tst_qhostaddress.cpp
LIBRARIES
Qt::NetworkPrivate
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qhostaddress CONDITION WIN32
LIBRARIES
ws2_32
)

View File

@ -0,0 +1,763 @@
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <qhostaddress.h>
#include <private/qhostaddress_p.h>
#include <qcoreapplication.h>
#include <QTest>
#include <qplatformdefs.h>
#include <qdebug.h>
#include <qhash.h>
#include <qbytearray.h>
#include <qdatastream.h>
#ifdef Q_OS_WIN
# include <qt_windows.h>
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_WASM)
# include <netinet/in.h>
#endif
Q_DECLARE_METATYPE(AddressClassification)
Q_DECLARE_METATYPE(QHostAddress::SpecialAddress)
class tst_QHostAddress : public QObject
{
Q_OBJECT
public:
tst_QHostAddress();
private slots:
void constructor_QString_data();
void constructor_QString();
void setAddress_QString_data();
void setAddress_QString();
void specialAddresses_data();
void specialAddresses();
void compare_data();
void compare();
void isEqual_data();
void isEqual();
void assignment();
void scopeId();
void hashKey();
void streaming_data();
void streaming();
void parseSubnet_data();
void parseSubnet();
void isInSubnet_data();
void isInSubnet();
void classification_data();
void classification();
void convertv4v6_data();
void convertv4v6();
};
Q_DECLARE_METATYPE(QHostAddress)
tst_QHostAddress::tst_QHostAddress()
{
qRegisterMetaType<QHostAddress>("QHostAddress");
}
void tst_QHostAddress::constructor_QString_data()
{
setAddress_QString_data();
}
void tst_QHostAddress::constructor_QString()
{
QFETCH(QString, address);
QFETCH(bool, ok);
QFETCH(int, protocol);
QHostAddress hostAddr(address);
if (address == "0.0.0.0" || address == "::") {
QVERIFY(ok);
} else {
QVERIFY(hostAddr.isNull() != ok);
}
if (ok)
QTEST(hostAddr.toString(), "resAddr");
if ( protocol == 4 ) {
QVERIFY( hostAddr.protocol() == QHostAddress::IPv4Protocol || hostAddr.protocol() == QHostAddress::UnknownNetworkLayerProtocol );
QVERIFY( hostAddr.protocol() != QHostAddress::IPv6Protocol );
} else if ( protocol == 6 ) {
QVERIFY( hostAddr.protocol() != QHostAddress::IPv4Protocol && hostAddr.protocol() != QHostAddress::UnknownNetworkLayerProtocol );
QVERIFY( hostAddr.protocol() == QHostAddress::IPv6Protocol );
} else {
QVERIFY( hostAddr.isNull() );
QVERIFY( hostAddr.protocol() == QHostAddress::UnknownNetworkLayerProtocol );
}
}
void tst_QHostAddress::setAddress_QString_data()
{
QTest::addColumn<QString>("address");
QTest::addColumn<bool>("ok");
QTest::addColumn<QString>("resAddr");
QTest::addColumn<int>("protocol"); // 4: IPv4, 6: IPv6, other: undefined
//next we fill it with data
QTest::newRow("ip4_00") << QString("127.0.0.1") << true << QString("127.0.0.1") << 4;
QTest::newRow("ip4_01") << QString("255.3.2.1") << true << QString("255.3.2.1") << 4;
QTest::newRow("ip4_03") << QString(" 255.3.2.1") << true << QString("255.3.2.1") << 4;
QTest::newRow("ip4_04") << QString("255.3.2.1\r ") << true << QString("255.3.2.1") << 4;
QTest::newRow("ip4_05") << QString("0.0.0.0") << true << QString("0.0.0.0") << 4;
QTest::newRow("ip4_06") << QString("123.0.0") << true << QString("123.0.0.0") << 4;
// for the format of IPv6 addresses see also RFC 5952
// rule 4.1: Leading zeros MUST be suppressed
// rule 4.2.1: Shorten as Much as Possible
// rule 4.2.2: The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
// rule 4.2.3: the longest run of consecutive 16-bit 0 fields MUST be shortened
// When the length of the consecutive 16-bit 0 fields, the first sequence
// of zero bits MUST be shortened
// rule 4.3: The characters "a", "b", "c", "d", "e", and "f" in an IPv6 address
// MUST be represented in lowercase
QTest::newRow("ip6_00") << QString("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210") << true << QString("fedc:ba98:7654:3210:fedc:ba98:7654:3210") << 6; // 4.3
QTest::newRow("ip6_01") << QString("1080:0000:0000:0000:0008:0800:200C:417A") << true << QString("1080::8:800:200c:417a") << 6; // 4.1, 4.2.1
QTest::newRow("ip6_02") << QString("1080:0:0:0:8:800:200C:417A") << true << QString("1080::8:800:200c:417a") << 6;
QTest::newRow("ip6_03") << QString("1080::8:800:200C:417A") << true << QString("1080::8:800:200c:417a") << 6;
QTest::newRow("ip6_04") << QString("FF01::43") << true << QString("ff01::43") << 6;
QTest::newRow("ip6_05") << QString("::1") << true << QString("::1") << 6;
QTest::newRow("ip6_06") << QString("1::") << true << QString("1::") << 6;
QTest::newRow("ip6_07") << QString("::") << true << QString("::") << 6;
QTest::newRow("ip6_08") << QString("0:0:0:0:0:0:13.1.68.3") << true << QString("::13.1.68.3") << 6;
QTest::newRow("ip6_09") << QString("::13.1.68.3") << true << QString("::13.1.68.3") << 6;
QTest::newRow("ip6_10") << QString("0:0:0:0:0:FFFF:129.144.52.38") << true << QString("::ffff:129.144.52.38") << 6;
QTest::newRow("ip6_11") << QString("::FFFF:129.144.52.38") << true << QString("::ffff:129.144.52.38") << 6;
QTest::newRow("ip6_12") << QString("1::FFFF:129.144.52.38") << true << QString("1::ffff:8190:3426") << 6;
QTest::newRow("ip6_13") << QString("A:B::D:E") << true << QString("a:b::d:e") << 6;
QTest::newRow("ip6_14") << QString("1080:0:1:0:8:800:200C:417A") << true << QString("1080:0:1:0:8:800:200c:417a") << 6; // 4.2.2
QTest::newRow("ip6_15") << QString("1080:0:1:0:8:800:200C:0") << true << QString("1080:0:1:0:8:800:200c:0") << 6;
QTest::newRow("ip6_16") << QString("1080:0:1:0:8:800:0:0") << true << QString("1080:0:1:0:8:800::") << 6;
QTest::newRow("ip6_17a") << QString("1080:0:0:8:800:0:0:0") << true << QString("1080:0:0:8:800::") << 6; // 4.2.3a
QTest::newRow("ip6_17b") << QString("1080:0:0:0:8:0:0:0") << true << QString("1080::8:0:0:0") << 6; // 4.2.3b
QTest::newRow("ip6_18") << QString("0:1:1:1:8:800:0:0") << true << QString("0:1:1:1:8:800::") << 6;
QTest::newRow("ip6_19") << QString("0:1:1:1:8:800:0:1") << true << QString("0:1:1:1:8:800:0:1") << 6;
QTest::newRow("error_00") << QString("foobarcom") << false << QString() << 0;
QTest::newRow("error_01") << QString("foo.bar.com") << false << QString() << 0;
QTest::newRow("error_02") << QString("") << false << QString() << 0;
QTest::newRow("error_03") << QString() << false << QString() << 0;
QTest::newRow("error_04") << QString(" \t\r") << false << QString() << 0;
QTest::newRow("error_ip4_00") << QString("256.9.9.9") << false << QString() << 0;
QTest::newRow("error_ip4_01") << QString("-1.9.9.9") << false << QString() << 0;
//QTest::newRow("error_ip4_02") << QString("123.0.0") << false << QString() << 0; // no longer invalid in Qt5
QTest::newRow("error_ip4_02") << QString("123.0.0.") << false << QString() << 0;
QTest::newRow("error_ip4_03") << QString("123.0.0.0.0") << false << QString() << 0;
QTest::newRow("error_ip4_04") << QString("255.2 3.2.1") << false << QString() << 0;
QTest::newRow("error_ip6_00") << QString(":") << false << QString() << 0;
QTest::newRow("error_ip6_01") << QString(":::") << false << QString() << 0;
QTest::newRow("error_ip6_02") << QString("::AAAA:") << false << QString() << 0;
QTest::newRow("error_ip6_03") << QString(":AAAA::") << false << QString() << 0;
QTest::newRow("error_ip6_04") << QString("FFFF:::129.144.52.38") << false << QString() << 0;
QTest::newRow("error_ip6_05") << QString("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210:1234") << false << QString() << 0;
QTest::newRow("error_ip6_06") << QString("129.144.52.38::") << false << QString() << 0;
QTest::newRow("error_ip6_07") << QString("::129.144.52.38:129.144.52.38") << false << QString() << 0;
QTest::newRow("error_ip6_08") << QString(":::129.144.52.38") << false << QString() << 0;
QTest::newRow("error_ip6_09") << QString("1FEDC:BA98:7654:3210:FEDC:BA98:7654:3210") << false << QString() << 0;
QTest::newRow("error_ip6_10") << QString("::FFFFFFFF") << false << QString() << 0;
QTest::newRow("error_ip6_11") << QString("::EFGH") << false << QString() << 0;
QTest::newRow("error_ip6_12") << QString("ABCD:ABCD:ABCD") << false << QString() << 0;
QTest::newRow("error_ip6_13") << QString("::ABCD:ABCD::") << false << QString() << 0;
QTest::newRow("error_ip6_14") << QString("1::2::3") << false << QString() << 0;
QTest::newRow("error_ip6_15") << QString("1:2:::") << false << QString() << 0;
QTest::newRow("error_ip6_16") << QString(":::1:2") << false << QString() << 0;
QTest::newRow("error_ip6_17") << QString("1:::2") << false << QString() << 0;
QTest::newRow("error_ip6_18") << QString("FEDC::7654:3210:FEDC:BA98::3210") << false << QString() << 0;
QTest::newRow("error_ip6_19") << QString("ABCD:ABCD:ABCD:1.2.3.4") << false << QString() << 0;
QTest::newRow("error_ip6_20") << QString("ABCD::ABCD::ABCD:1.2.3.4") << false << QString() << 0;
}
void tst_QHostAddress::setAddress_QString()
{
QFETCH(QString, address);
QFETCH(bool, ok);
QFETCH(int, protocol);
QHostAddress hostAddr;
QCOMPARE(hostAddr.setAddress(address), ok);
if (ok)
QTEST(hostAddr.toString(), "resAddr");
if ( protocol == 4 ) {
QVERIFY( hostAddr.protocol() == QHostAddress::IPv4Protocol || hostAddr.protocol() == QHostAddress::UnknownNetworkLayerProtocol );
QVERIFY( hostAddr.protocol() != QHostAddress::IPv6Protocol );
} else if ( protocol == 6 ) {
QVERIFY( hostAddr.protocol() != QHostAddress::IPv4Protocol && hostAddr.protocol() != QHostAddress::UnknownNetworkLayerProtocol );
QVERIFY( hostAddr.protocol() == QHostAddress::IPv6Protocol );
} else {
QVERIFY( hostAddr.isNull() );
QVERIFY( hostAddr.protocol() == QHostAddress::UnknownNetworkLayerProtocol );
}
}
void tst_QHostAddress::specialAddresses_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QHostAddress::SpecialAddress>("address");
QTest::addColumn<bool>("result");
QTest::newRow("localhost_1") << QString("127.0.0.1") << QHostAddress::LocalHost << true;
QTest::newRow("localhost_2") << QString("127.0.0.2") << QHostAddress::LocalHost << false;
QTest::newRow("localhost_3") << QString("127.0.0.2") << QHostAddress::LocalHostIPv6 << false;
QTest::newRow("localhost_ipv6_4") << QString("::1") << QHostAddress::LocalHostIPv6 << true;
QTest::newRow("localhost_ipv6_5") << QString("::2") << QHostAddress::LocalHostIPv6 << false;
QTest::newRow("localhost_ipv6_6") << QString("::1") << QHostAddress::LocalHost << false;
QTest::newRow("null_1") << QString("") << QHostAddress::Null << true;
QTest::newRow("null_2") << QString("bjarne") << QHostAddress::Null << true;
QTest::newRow("compare_from_null") << QString("") << QHostAddress::Broadcast << false;
QTest::newRow("broadcast_1") << QString("255.255.255.255") << QHostAddress::Any << false;
QTest::newRow("broadcast_2") << QString("255.255.255.255") << QHostAddress::Broadcast << true;
QTest::newRow("any_ipv6") << QString("::") << QHostAddress::AnyIPv6 << true;
QTest::newRow("any_ipv4") << QString("0.0.0.0") << QHostAddress::AnyIPv4 << true;
QTest::newRow("dual_not_ipv6") << QString("::") << QHostAddress::Any << false;
QTest::newRow("dual_not_ipv4") << QString("0.0.0.0") << QHostAddress::Any << false;
}
void tst_QHostAddress::specialAddresses()
{
QFETCH(QString, text);
QFETCH(QHostAddress::SpecialAddress, address);
QFETCH(bool, result);
QCOMPARE(QHostAddress(text) == address, result);
//check special address equal to itself (QTBUG-22898), note two overloads of operator==
QVERIFY(QHostAddress(address) == QHostAddress(address));
QVERIFY(QHostAddress(address) == address);
QVERIFY(address == QHostAddress(address));
QVERIFY(!(QHostAddress(address) != QHostAddress(address)));
QVERIFY(!(QHostAddress(address) != address));
QVERIFY(!(address != QHostAddress(address)));
{
QHostAddress ha;
ha.setAddress(address);
QVERIFY(ha == address);
}
QHostAddress setter;
setter.setAddress(text);
QCOMPARE(setter == address, result);
}
void tst_QHostAddress::compare_data()
{
QTest::addColumn<QHostAddress>("first");
QTest::addColumn<QHostAddress>("second");
QTest::addColumn<bool>("result");
QTest::newRow("1") << QHostAddress() << QHostAddress() << true;
QTest::newRow("2") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::Any) << true;
QTest::newRow("3") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv6) << true;
QTest::newRow("4") << QHostAddress(QHostAddress::Broadcast) << QHostAddress(QHostAddress::Broadcast) << true;
QTest::newRow("5") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::Broadcast) << false;
QTest::newRow("6") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << false;
QTest::newRow("7") << QHostAddress() << QHostAddress(QHostAddress::LocalHostIPv6) << false;
QTest::newRow("any4-any6") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv6) << false;
Q_IPV6ADDR localhostv4mapped = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1 } };
QTest::newRow("v4-v4mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("::ffff:127.0.0.1") << false;
QTest::newRow("v4-v4mapped-2") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(localhostv4mapped) << false;
}
void tst_QHostAddress::compare()
{
QFETCH(QHostAddress, first);
QFETCH(QHostAddress, second);
QFETCH(bool, result);
QCOMPARE(first == second, result);
QCOMPARE(second == first, result);
if (result == true)
QCOMPARE(qHash(first), qHash(second));
}
void tst_QHostAddress::isEqual_data()
{
QTest::addColumn<QHostAddress>("first");
QTest::addColumn<QHostAddress>("second");
QTest::addColumn<int>("flags");
QTest::addColumn<bool>("result");
// QHostAddress::StrictConversion is already tested in compare()
QTest::newRow("localhost4to6-local") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << true;
QTest::newRow("localhost4to6-compat") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("localhost4to6-mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("localhost4to6-unspec") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("0.0.0.1-::1-local") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("v4-v4compat-local") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("v4-v4mapped-local") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("0.0.0.1-::1-unspec") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("v4-v4compat-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("v4-v4mapped-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("0.0.0.1-::1-compat") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("v4-v4compat-compat") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << true;
QTest::newRow("v4-v4mapped-compat") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("0.0.0.1-::1-mapped") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("v4-v4compat-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("v4-v4mapped-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::FFFF:192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << true;
QTest::newRow("undef-any-local") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("undef-any-unspec") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("anyv6-anyv4-compat") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4CompatToIPv4 << true;
QTest::newRow("anyv6-anyv4-mapped") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("anyv6-anyv4-unspec") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("any-anyv4-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("any-anyv6-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("anyv6-anyv4-local") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("any-anyv4-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("any-anyv6-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("localhostv6-any-tolerant") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::Any) << (int)QHostAddress::TolerantConversion << false;
QTest::newRow("localhostv4-any-tolerant") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::Any) << (int)QHostAddress::TolerantConversion << false;
QTest::newRow("localhostv6-anyv6-tolerant") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::TolerantConversion << false;
QTest::newRow("localhostv4-anyv6-tolerant") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::TolerantConversion << false;
QTest::newRow("localhostv6-anyv4-tolerant") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::TolerantConversion << false;
QTest::newRow("localhostv4-anyv4-tolerant") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::TolerantConversion << false;
}
void tst_QHostAddress::isEqual()
{
QFETCH(QHostAddress, first);
QFETCH(QHostAddress, second);
QFETCH(int, flags);
QFETCH(bool, result);
QCOMPARE(first.isEqual(second, QHostAddress::ConversionModeFlag(flags)), result);
QCOMPARE(second.isEqual(first, QHostAddress::ConversionModeFlag(flags)), result);
}
void tst_QHostAddress::assignment()
{
QHostAddress address;
QHostAddress addr("4.2.2.1");
sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(addr.toIPv4Address());
address.setAddress((sockaddr *)&sockAddr);
QCOMPARE(address, addr);
}
void tst_QHostAddress::scopeId()
{
QHostAddress address("fe80::2e0:4cff:fefb:662a%eth0");
QCOMPARE(address.scopeId(), QString("eth0"));
QCOMPARE(address.toString().toLower(), QString("fe80::2e0:4cff:fefb:662a%eth0"));
QHostAddress address2("fe80::2e0:4cff:fefb:662a");
QCOMPARE(address2.scopeId(), QString());
address2.setScopeId(QString("en0"));
QCOMPARE(address2.toString().toLower(), QString("fe80::2e0:4cff:fefb:662a%en0"));
address2 = address;
QCOMPARE(address2.scopeId(), QString("eth0"));
QCOMPARE(address2.toString().toLower(), QString("fe80::2e0:4cff:fefb:662a%eth0"));
}
void tst_QHostAddress::hashKey()
{
QHash<QHostAddress, QString> hostHash;
hostHash.insert(QHostAddress(), "ole");
}
void tst_QHostAddress::streaming_data()
{
QTest::addColumn<QHostAddress>("address");
QTest::newRow("1") << QHostAddress();
QTest::newRow("2") << QHostAddress(0xDEADBEEF);
QTest::newRow("3") << QHostAddress("127.128.129.130");
QTest::newRow("4") << QHostAddress("1080:0000:0000:0000:0008:0800:200C:417A");
QTest::newRow("5") << QHostAddress("fe80::2e0:4cff:fefb:662a%eth0");
QTest::newRow("6") << QHostAddress(QHostAddress::Null);
QTest::newRow("7") << QHostAddress(QHostAddress::LocalHost);
QTest::newRow("8") << QHostAddress(QHostAddress::LocalHostIPv6);
QTest::newRow("9") << QHostAddress(QHostAddress::Broadcast);
QTest::newRow("10") << QHostAddress(QHostAddress::Any);
QTest::newRow("11") << QHostAddress(QHostAddress::AnyIPv4);
QTest::newRow("12") << QHostAddress(QHostAddress::AnyIPv6);
QTest::newRow("13") << QHostAddress("foo.bar.com");
}
void tst_QHostAddress::streaming()
{
QFETCH(QHostAddress, address);
QByteArray ba;
QDataStream ds1(&ba, QIODevice::WriteOnly);
ds1 << address;
QCOMPARE(ds1.status(), QDataStream::Ok);
QDataStream ds2(&ba, QIODevice::ReadOnly);
QHostAddress address2;
ds2 >> address2;
QCOMPARE(ds2.status(), QDataStream::Ok);
QCOMPARE(address, address2);
}
void tst_QHostAddress::parseSubnet_data()
{
QTest::addColumn<QString>("subnet");
QTest::addColumn<QHostAddress>("prefix");
QTest::addColumn<int>("prefixLength");
// invalid/error values
QTest::newRow("empty") << QString() << QHostAddress() << -1;
QTest::newRow("invalid_01") << "foobar" << QHostAddress() << -1;
QTest::newRow("invalid_02") << " " << QHostAddress() << -1;
QTest::newRow("invalid_03") << "1.2.3.a" << QHostAddress() << -1;
QTest::newRow("invalid_04") << "1.2.3.4.5" << QHostAddress() << -1;
QTest::newRow("invalid_05") << "1.2.3.4:80" << QHostAddress() << -1;
QTest::newRow("invalid_06") << "1.2.3.4/33" << QHostAddress() << -1;
QTest::newRow("invalid_07") << "1.2.3.4/-1" << QHostAddress() << -1;
QTest::newRow("invalid_08") << "1.2.3.4/256.0.0.0" << QHostAddress() << -1;
QTest::newRow("invalid_09") << "1.2.3.4/255.253.0.0" << QHostAddress() << -1;
QTest::newRow("invalid_10") << "1.2.3.4/255.0.0.255" << QHostAddress() << -1;
QTest::newRow("invalid_11") << "1.2.3.4." << QHostAddress() << -1;
QTest::newRow("invalid_20") << "ffff::/-1" << QHostAddress() << -1;
QTest::newRow("invalid_21") << "ffff::/129" << QHostAddress() << -1;
QTest::newRow("invalid_22") << "ffff::/255.255.0.0" << QHostAddress() << -1;
QTest::newRow("invalid_23") << "ffff::/ff00::" << QHostAddress() << -1;
// correct IPv4 with netmask
QTest::newRow("netmask_0") << "0.0.0.0/0.0.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 0;
QTest::newRow("netmask_1") << "0.0.0.0/255.128.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 9;
QTest::newRow("netmask_2") << "0.0.0.0/255.192.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 10;
QTest::newRow("netmask_3") << "0.0.0.0/255.224.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 11;
QTest::newRow("netmask_4") << "0.0.0.0/255.240.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 12;
QTest::newRow("netmask_5") << "0.0.0.0/255.248.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 13;
QTest::newRow("netmask_6") << "0.0.0.0/255.252.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 14;
QTest::newRow("netmask_7") << "0.0.0.0/255.254.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 15;
QTest::newRow("netmask_8") << "0.0.0.0/255.255.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 16;
QTest::newRow("netmask_16") << "0.0.0.0/255.255.0.0" << QHostAddress(QHostAddress::AnyIPv4) << 16;
QTest::newRow("netmask_24") << "0.0.0.0/255.255.255.0" << QHostAddress(QHostAddress::AnyIPv4) << 24;
QTest::newRow("netmask_31") << "0.0.0.0/255.255.255.254" << QHostAddress(QHostAddress::AnyIPv4) << 31;
QTest::newRow("netmask_32") << "0.0.0.0/255.255.255.255" << QHostAddress(QHostAddress::AnyIPv4) << 32;
// correct IPv4 with prefix
QTest::newRow("prefix_0") << "0.0.0.0/0" << QHostAddress(QHostAddress::AnyIPv4) << 0;
QTest::newRow("prefix_1") << "0.0.0.0/1" << QHostAddress(QHostAddress::AnyIPv4) << 1;
QTest::newRow("prefix_9") << "0.0.0.0/9" << QHostAddress(QHostAddress::AnyIPv4) << 9;
QTest::newRow("prefix_31") << "0.0.0.0/31" << QHostAddress(QHostAddress::AnyIPv4) << 31;
QTest::newRow("prefix_32") << "0.0.0.0/32" << QHostAddress(QHostAddress::AnyIPv4) << 32;
// correct IPv4 without prefix or netmask
QTest::newRow("classA") << "10" << QHostAddress("10.0.0.0") << 8;
QTest::newRow("classA+dot") << "10." << QHostAddress("10.0.0.0") << 8;
QTest::newRow("classB") << "172.16" << QHostAddress("172.16.0.0") << 16;
QTest::newRow("classB+dot") << "172.16." << QHostAddress("172.16.0.0") << 16;
QTest::newRow("classC") << "192.168.0" << QHostAddress("192.168.0.0") << 24;
QTest::newRow("classC+dot") << "192.168.0" << QHostAddress("192.168.0.0") << 24;
QTest::newRow("full-ipv4") << "192.168.0.1" << QHostAddress("192.168.0.1") << 32;
// correct IPv6 with prefix
QTest::newRow("ipv6_01") << "::/0" << QHostAddress(QHostAddress::AnyIPv6) << 0;
QTest::newRow("ipv6_03") << "::/3" << QHostAddress(QHostAddress::AnyIPv6) << 3;
QTest::newRow("ipv6_16") << "::/16" << QHostAddress(QHostAddress::AnyIPv6) << 16;
QTest::newRow("ipv6_48") << "::/48" << QHostAddress(QHostAddress::AnyIPv6) << 48;
QTest::newRow("ipv6_127") << "::/127" << QHostAddress(QHostAddress::AnyIPv6) << 127;
QTest::newRow("ipv6_128") << "::/128" << QHostAddress(QHostAddress::AnyIPv6) << 128;
// tail bit clearing:
QTest::newRow("clear_01") << "255.255.255.255/31" << QHostAddress("255.255.255.254") << 31;
QTest::newRow("clear_08") << "255.255.255.255/24" << QHostAddress("255.255.255.0") << 24;
QTest::newRow("clear_09") << "255.255.255.255/23" << QHostAddress("255.255.254.0") << 23;
QTest::newRow("clear_10") << "255.255.255.255/22" << QHostAddress("255.255.252.0") << 22;
QTest::newRow("clear_11") << "255.255.255.255/21" << QHostAddress("255.255.248.0") << 21;
QTest::newRow("clear_12") << "255.255.255.255/20" << QHostAddress("255.255.240.0") << 20;
QTest::newRow("clear_13") << "255.255.255.255/19" << QHostAddress("255.255.224.0") << 19;
QTest::newRow("clear_14") << "255.255.255.255/18" << QHostAddress("255.255.192.0") << 18;
QTest::newRow("clear_15") << "255.255.255.255/17" << QHostAddress("255.255.128.0") << 17;
QTest::newRow("clear_16") << "255.255.255.255/16" << QHostAddress("255.255.0.0") << 16;
QTest::newRow("clear_24") << "255.255.255.255/8" << QHostAddress("255.0.0.0") << 8;
QTest::newRow("clear_31") << "255.255.255.255/1" << QHostAddress("128.0.0.0") << 1;
QTest::newRow("clear_32") << "255.255.255.255/0" << QHostAddress("0.0.0.0") << 0;
// same for IPv6:
QTest::newRow("ipv6_clear_01") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127"
<< QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")
<< 127;
QTest::newRow("ipv6_clear_07") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/121"
<< QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80")
<< 121;
QTest::newRow("ipv6_clear_08") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/120"
<< QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00")
<< 120;
QTest::newRow("ipv6_clear_16") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/112"
<< QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:0")
<< 112;
QTest::newRow("ipv6_clear_80") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/48"
<< QHostAddress("ffff:ffff:ffff::")
<< 48;
QTest::newRow("ipv6_clear_81") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/47"
<< QHostAddress("ffff:ffff:fffe::")
<< 47;
QTest::newRow("ipv6_clear_82") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/46"
<< QHostAddress("ffff:ffff:fffc::")
<< 46;
QTest::newRow("ipv6_clear_83") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/45"
<< QHostAddress("ffff:ffff:fff8::")
<< 45;
QTest::newRow("ipv6_clear_84") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/44"
<< QHostAddress("ffff:ffff:fff0::")
<< 44;
QTest::newRow("ipv6_clear_85") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/43"
<< QHostAddress("ffff:ffff:ffe0::")
<< 43;
QTest::newRow("ipv6_clear_86") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/42"
<< QHostAddress("ffff:ffff:ffc0::")
<< 42;
QTest::newRow("ipv6_clear_87") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/41"
<< QHostAddress("ffff:ffff:ff80::")
<< 41;
QTest::newRow("ipv6_clear_88") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/40"
<< QHostAddress("ffff:ffff:ff00::")
<< 40;
QTest::newRow("ipv6_clear_125") << "3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/3"
<< QHostAddress("2000::")
<< 3;
QTest::newRow("ipv6_clear_127") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/1"
<< QHostAddress("8000::")
<< 1;
QTest::newRow("ipv6_clear_128") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0"
<< QHostAddress(QHostAddress::AnyIPv6)
<< 0;
}
void tst_QHostAddress::parseSubnet()
{
QFETCH(QString, subnet);
QFETCH(QHostAddress, prefix);
QFETCH(int, prefixLength);
QPair<QHostAddress, int> result = QHostAddress::parseSubnet(subnet);
QCOMPARE(result.first, prefix);
QCOMPARE(result.second, prefixLength);
}
void tst_QHostAddress::isInSubnet_data()
{
QTest::addColumn<QHostAddress>("address");
QTest::addColumn<QHostAddress>("prefix");
QTest::addColumn<int>("prefixLength");
QTest::addColumn<bool>("result");
// invalid QHostAddresses are never in any subnets
QTest::newRow("invalid_01") << QHostAddress() << QHostAddress() << 32 << false;
QTest::newRow("invalid_02") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv4) << 32 << false;
QTest::newRow("invalid_03") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv4) << 8 << false;
QTest::newRow("invalid_04") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv4) << 0 << false;
QTest::newRow("invalid_05") << QHostAddress() << QHostAddress("255.255.255.0") << 24 << false;
QTest::newRow("invalid_06") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv6) << 0 << false;
QTest::newRow("invalid_07") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv6) << 32 << false;
QTest::newRow("invalid_08") << QHostAddress() << QHostAddress(QHostAddress::AnyIPv6) << 128<< false;
// and no host address can be in a subnet whose prefix is invalid
QTest::newRow("invalid_20") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress() << 16 << false;
QTest::newRow("invalid_21") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress() << 16 << false;
QTest::newRow("invalid_22") << QHostAddress(QHostAddress::LocalHost) << QHostAddress() << 16 << false;
QTest::newRow("invalid_23") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress() << 16 << false;
// negative netmasks don't make sense:
QTest::newRow("invalid_30") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::Any) << -1 << false;
QTest::newRow("invalid_31") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv6) << -1 << false;
// we don't support IPv4 belonging in an IPv6 netmask and vice-versa
QTest::newRow("v4-in-v6") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::AnyIPv6) << 0 << false;
QTest::newRow("v6-in-v4") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::Any) << 0 << false;
QTest::newRow("v4-in-v6mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:255.0.0.0") << 113 << false;
QTest::newRow("v4-in-v6mapped2") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("::ffff:255.0.0.0") << 113 << false;
// IPv4 correct ones
QTest::newRow("netmask_0") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::AnyIPv4) << 0 << true;
QTest::newRow("netmask_0bis") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("255.255.0.0") << 0 << true;
QTest::newRow("netmask_0ter") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("1.2.3.4") << 0 << true;
QTest::newRow("netmask_1") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::AnyIPv4) << 1 << true;
QTest::newRow("~netmask_1") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("128.0.0.0") << 1 << false;
QTest::newRow("netmask_1bis") << QHostAddress("224.0.0.1") << QHostAddress("128.0.0.0") << 1 << true;
QTest::newRow("~netmask_1bis") << QHostAddress("224.0.0.1") << QHostAddress("0.0.0.0") << 1 << false;
QTest::newRow("netmask_8") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("127.0.0.0") << 8 << true;
QTest::newRow("~netmask_8") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("126.0.0.0") << 8 << false;
QTest::newRow("netmask_15") << QHostAddress("10.0.1.255") << QHostAddress("10.0.0.0") << 15 << true;
QTest::newRow("netmask_16") << QHostAddress("172.16.0.1") << QHostAddress("172.16.0.0") << 16 << true;
// the address is always in the subnet containing its address, regardless of length:
QTest::newRow("same_01") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 1 << true;
QTest::newRow("same_07") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 7 << true;
QTest::newRow("same_8") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 8 << true;
QTest::newRow("same_24") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 23 << true;
QTest::newRow("same_31") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 31 << true;
QTest::newRow("same_32") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHost) << 32 << true;
// IPv6 correct ones:
QTest::newRow("ipv6_netmask_0") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::AnyIPv6) << 0 << true;
QTest::newRow("ipv6_netmask_0bis") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::LocalHostIPv6) << 0 << true;
QTest::newRow("ipv6_netmask_0ter") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress("ffff::") << 0 << true;
QTest::newRow("ipv6_netmask_1") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress(QHostAddress::AnyIPv6) << 1 << true;
QTest::newRow("ipv6_netmask_1bis") << QHostAddress("fec0::1") << QHostAddress("8000::") << 1 << true;
QTest::newRow("~ipv6_netmask_1") << QHostAddress(QHostAddress::LocalHostIPv6) << QHostAddress("8000::") << 1 << false;
QTest::newRow("~ipv6_netmask_1bis") << QHostAddress("fec0::1") << QHostAddress("::") << 1 << false;
QTest::newRow("ipv6_netmask_47") << QHostAddress("2:3:5::1") << QHostAddress("2:3:4::") << 47 << true;
QTest::newRow("ipv6_netmask_48") << QHostAddress("2:3:4::1") << QHostAddress("2:3:4::") << 48 << true;
QTest::newRow("~ipv6_netmask_48") << QHostAddress("2:3:5::1") << QHostAddress("2:3:4::") << 48 << false;
QTest::newRow("ipv6_netmask_127") << QHostAddress("2:3:4:5::1") << QHostAddress("2:3:4:5::") << 127 << true;
QTest::newRow("ipv6_netmask_128") << QHostAddress("2:3:4:5::1") << QHostAddress("2:3:4:5::1") << 128 << true;
QTest::newRow("~ipv6_netmask_128") << QHostAddress("2:3:4:5::1") << QHostAddress("2:3:4:5::0") << 128 << false;
}
void tst_QHostAddress::isInSubnet()
{
QFETCH(QHostAddress, address);
QFETCH(QHostAddress, prefix);
QFETCH(int, prefixLength);
QTEST(address.isInSubnet(prefix, prefixLength), "result");
}
void tst_QHostAddress::classification_data()
{
QTest::addColumn<QHostAddress>("address");
QTest::addColumn<AddressClassification>("result");
QTest::newRow("default") << QHostAddress() << UnknownAddress;
QTest::newRow("invalid") << QHostAddress("&&&") << UnknownAddress;
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << LocalNetAddress;
QTest::newRow("Null") << QHostAddress(QHostAddress::Null) << UnknownAddress;
// IPv6 address space
auto addV6 = [](const char *str, AddressClassification cl) {
QTest::newRow(str) << QHostAddress(str) << cl;
};
QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << LocalNetAddress;
QTest::newRow("ipv6_loop") << QHostAddress(QHostAddress::LocalHostIPv6) << LoopbackAddress;
addV6("::", LocalNetAddress);
addV6("::1", LoopbackAddress);
addV6("::2", GlobalAddress);
addV6("2000::", GlobalAddress);
addV6("3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", GlobalAddress);
addV6("fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", GlobalAddress);
addV6("fc00::", UniqueLocalAddress);
addV6("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UniqueLocalAddress);
addV6("fe00::", UnknownAddress);
addV6("fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UnknownAddress);
addV6("fe80::", LinkLocalAddress);
addV6("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", LinkLocalAddress);
addV6("fec0::", SiteLocalAddress);
addV6("feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", SiteLocalAddress);
addV6("ff00::", MulticastAddress);
addV6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", MulticastAddress);
// IPv4 address space
auto addV4 = [](const char *str, AddressClassification cl) {
QTest::newRow(str) << QHostAddress(str) << cl;
QByteArray v6 = "::ffff:";
v6 += str;
QTest::newRow(v6.constData()) << QHostAddress(QString::fromLatin1(v6)) << cl;
};
QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << LocalNetAddress;
QTest::newRow("ipv4_loop") << QHostAddress(QHostAddress::LocalHost) << LoopbackAddress;
QTest::newRow("Broadcast") << QHostAddress(QHostAddress::Broadcast) << BroadcastAddress;
addV4("0.0.0.0", LocalNetAddress);
addV4("0.0.0.1", LocalNetAddress);
addV4("0.255.255.255", LocalNetAddress);
addV4("1.0.0.0", GlobalAddress);
addV4("1.2.3.4", GlobalAddress);
addV4("10.0.0.4", PrivateNetworkAddress);
addV4("127.0.0.1", LoopbackAddress);
addV4("127.0.0.2", LoopbackAddress);
addV4("127.255.255.255", LoopbackAddress);
addV4("192.168.3.4", PrivateNetworkAddress);
addV4("223.255.255.255", GlobalAddress);
addV4("224.0.0.0", MulticastAddress);
addV4("239.255.255.255", MulticastAddress);
addV4("240.0.0.0", UnknownAddress);
addV4("255.255.255.254", UnknownAddress);
addV4("255.255.255.255", BroadcastAddress);
}
void tst_QHostAddress::classification()
{
QFETCH(QHostAddress, address);
QFETCH(AddressClassification, result);
bool isLoopback = (result == LoopbackAddress);
bool isGlobal = (result & GlobalAddress); // GlobalAddress is a bit
bool isLinkLocal = (result == LinkLocalAddress);
bool isSiteLocal = (result == SiteLocalAddress);
bool isUniqueLocalAddress = (result == UniqueLocalAddress);
bool isMulticast = (result == MulticastAddress);
bool isBroadcast = (result == BroadcastAddress);
QCOMPARE(address.isLoopback(), isLoopback);
QCOMPARE(address.isGlobal(), isGlobal);
QCOMPARE(address.isLinkLocal(), isLinkLocal);
QCOMPARE(address.isSiteLocal(), isSiteLocal);
QCOMPARE(address.isUniqueLocalUnicast(), isUniqueLocalAddress);
QCOMPARE(address.isMulticast(), isMulticast);
QCOMPARE(address.isBroadcast(), isBroadcast);
}
void tst_QHostAddress::convertv4v6_data()
{
QTest::addColumn<QHostAddress>("source");
QTest::addColumn<int>("protocol");
QTest::addColumn<QHostAddress>("result");
QTest::newRow("any-to-v4") << QHostAddress(QHostAddress::Any) << 4 << QHostAddress(QHostAddress::AnyIPv4);
QTest::newRow("any-to-v6") << QHostAddress(QHostAddress::Any) << 6 << QHostAddress(QHostAddress::AnyIPv6);
QTest::newRow("anyv4-to-v6") << QHostAddress(QHostAddress::AnyIPv4) << 6 << QHostAddress(QHostAddress::AnyIPv6);
QTest::newRow("anyv6-to-v4") << QHostAddress(QHostAddress::AnyIPv6) << 4 << QHostAddress(QHostAddress::AnyIPv4);
QTest::newRow("v4mapped-to-v4") << QHostAddress("::ffff:192.0.2.1") << 4 << QHostAddress("192.0.2.1");
QTest::newRow("v4-to-v4mapped") << QHostAddress("192.0.2.1") << 6 << QHostAddress("::ffff:192.0.2.1");
// we won't convert 127.0.0.1 to ::1 or vice-versa:
// you can connect to a v4 server socket with ::ffff:127.0.0.1, but not with ::1
QTest::newRow("localhost-to-v4mapped") << QHostAddress(QHostAddress::LocalHost) << 6 << QHostAddress("::ffff:127.0.0.1");
QTest::newRow("v4mapped-to-localhost") << QHostAddress("::ffff:127.0.0.1") << 4 << QHostAddress(QHostAddress::LocalHost);
// in turn, that means localhost6 doesn't convert to v4
QTest::newRow("localhost6-to-v4") << QHostAddress(QHostAddress::LocalHostIPv6) << 4 << QHostAddress();
// some other v6 addresses that won't convert to v4
QTest::newRow("v4compat-to-v4") << QHostAddress("::192.0.2.1") << 4 << QHostAddress();
QTest::newRow("localhostv4compat-to-v4") << QHostAddress("::127.0.0.1") << 4 << QHostAddress();
QTest::newRow("v6global-to-v4") << QHostAddress("2001:db8::1") << 4 << QHostAddress();
QTest::newRow("v6multicast-to-v4") << QHostAddress("ff02::1") << 4 << QHostAddress();
}
void tst_QHostAddress::convertv4v6()
{
QFETCH(QHostAddress, source);
QFETCH(int, protocol);
QFETCH(QHostAddress, result);
if (protocol == 4) {
bool ok;
quint32 v4 = source.toIPv4Address(&ok);
QCOMPARE(ok, result.protocol() == QHostAddress::IPv4Protocol);
if (ok)
QCOMPARE(QHostAddress(v4), result);
} else if (protocol == 6) {
QCOMPARE(QHostAddress(source.toIPv6Address()), result);
}
}
QTEST_MAIN(tst_QHostAddress)
#include "tst_qhostaddress.moc"

View File

@ -0,0 +1,6 @@
# These tests fail due to a DNS server issue
# (this is not a Qt bug)
[lookupIPv6:a-plus-aaaa]
windows ci
[blockingLookup:a-plus-aaaa]
windows ci

View File

@ -0,0 +1,26 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qhostinfo Test:
#####################################################################
qt_internal_add_test(tst_qhostinfo
SOURCES
tst_qhostinfo.cpp
LIBRARIES
Qt::CorePrivate
Qt::NetworkPrivate
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qhostinfo CONDITION WIN32
LIBRARIES
ws2_32
)

View File

@ -0,0 +1,713 @@
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// When using WinSock2 on Windows, it's the first thing that can be included
// (except qglobal.h), or else you'll get tons of compile errors
#include <qglobal.h>
// To prevent windows system header files from re-defining min/max
#define NOMINMAX 1
#if defined(Q_OS_WIN)
# include <winsock2.h>
# include <ws2tcpip.h>
#endif
#include <QTest>
#include <QTestEventLoop>
#include <QProcess>
#include <QCoreApplication>
#include <QDebug>
#include <QTcpSocket>
#include <QTcpServer>
#include <private/qthread_p.h>
#include <time.h>
#if defined(Q_OS_WIN)
#include <qt_windows.h>
#else
#include <unistd.h>
#include <signal.h>
#endif
#include <qhostinfo.h>
#include "private/qhostinfo_p.h"
#include <sys/types.h>
#if defined(Q_OS_UNIX)
# include <sys/socket.h>
# include <netdb.h>
#endif
#include "../../../network-settings.h"
#define TEST_DOMAIN ".test.qt-project.org"
class tst_QHostInfo : public QObject
{
Q_OBJECT
private slots:
void init();
void initTestCase();
void swapFunction();
void moveOperator();
void getSetCheck();
void staticInformation();
void lookupIPv4_data();
void lookupIPv4();
void lookupIPv6_data();
void lookupIPv6();
void lookupConnectToFunctionPointer_data();
void lookupConnectToFunctionPointer();
void lookupConnectToFunctionPointerDeleted();
void lookupConnectToLambda_data();
void lookupConnectToLambda();
void reverseLookup_data();
void reverseLookup();
void blockingLookup_data();
void blockingLookup();
void raceCondition();
void threadSafety();
void threadSafetyAsynchronousAPI();
void multipleSameLookups();
void multipleDifferentLookups_data();
void multipleDifferentLookups();
void cache();
void abortHostLookup();
protected slots:
void resultsReady(const QHostInfo &);
private:
bool ipv6LookupsAvailable;
bool ipv6Available;
bool lookupDone;
int lookupsDoneCounter;
QHostInfo lookupResults;
};
void tst_QHostInfo::swapFunction()
{
QHostInfo obj1, obj2;
obj1.setError(QHostInfo::HostInfoError(0));
obj2.setError(QHostInfo::HostInfoError(1));
obj1.swap(obj2);
QCOMPARE(QHostInfo::HostInfoError(0), obj2.error());
QCOMPARE(QHostInfo::HostInfoError(1), obj1.error());
}
void tst_QHostInfo::moveOperator()
{
QHostInfo obj1, obj2, obj3(1);
obj1.setError(QHostInfo::HostInfoError(0));
obj2.setError(QHostInfo::HostInfoError(1));
obj1 = std::move(obj2);
obj2 = obj3;
QCOMPARE(QHostInfo::HostInfoError(1), obj1.error());
QCOMPARE(obj3.lookupId(), obj2.lookupId());
}
// Testing get/set functions
void tst_QHostInfo::getSetCheck()
{
QHostInfo obj1;
// HostInfoError QHostInfo::error()
// void QHostInfo::setError(HostInfoError)
obj1.setError(QHostInfo::HostInfoError(0));
QCOMPARE(QHostInfo::HostInfoError(0), obj1.error());
obj1.setError(QHostInfo::HostInfoError(1));
QCOMPARE(QHostInfo::HostInfoError(1), obj1.error());
// int QHostInfo::lookupId()
// void QHostInfo::setLookupId(int)
obj1.setLookupId(0);
QCOMPARE(0, obj1.lookupId());
obj1.setLookupId(INT_MIN);
QCOMPARE(INT_MIN, obj1.lookupId());
obj1.setLookupId(INT_MAX);
QCOMPARE(INT_MAX, obj1.lookupId());
}
void tst_QHostInfo::staticInformation()
{
qDebug() << "Hostname:" << QHostInfo::localHostName();
qDebug() << "Domain name:" << QHostInfo::localDomainName();
}
void tst_QHostInfo::initTestCase()
{
ipv6Available = false;
ipv6LookupsAvailable = false;
QTcpServer server;
if (server.listen(QHostAddress("::1"))) {
// We have IPv6 support
ipv6Available = true;
}
// check if the system getaddrinfo can do IPv6 lookups
struct addrinfo hint, *result = 0;
memset(&hint, 0, sizeof hint);
hint.ai_family = AF_UNSPEC;
#ifdef AI_ADDRCONFIG
hint.ai_flags = AI_ADDRCONFIG;
#endif
int res = getaddrinfo("::1", "80", &hint, &result);
if (res == 0) {
// this test worked
freeaddrinfo(result);
res = getaddrinfo("aaaa-single" TEST_DOMAIN, "80", &hint, &result);
if (res == 0 && result != 0 && result->ai_family != AF_INET) {
freeaddrinfo(result);
ipv6LookupsAvailable = true;
}
}
// run each testcase with and without test enabled
QTest::addColumn<bool>("cache");
QTest::newRow("WithCache") << true;
QTest::newRow("WithoutCache") << false;
}
void tst_QHostInfo::init()
{
// delete the cache so inidividual testcase results are independent from each other
qt_qhostinfo_clear_cache();
QFETCH_GLOBAL(bool, cache);
qt_qhostinfo_enable_cache(cache);
}
void tst_QHostInfo::lookupIPv4_data()
{
QTest::addColumn<QString>("hostname");
QTest::addColumn<QString>("addresses");
QTest::addColumn<int>("err");
QTest::newRow("empty") << "" << "" << int(QHostInfo::HostNotFound);
QTest::newRow("single_ip4") << "a-single" TEST_DOMAIN << "192.0.2.1" << int(QHostInfo::NoError);
QTest::newRow("multiple_ip4") << "a-multi" TEST_DOMAIN << "192.0.2.1 192.0.2.2 192.0.2.3" << int(QHostInfo::NoError);
QTest::newRow("literal_ip4") << "192.0.2.1" << "192.0.2.1" << int(QHostInfo::NoError);
QTest::newRow("notfound") << "invalid" TEST_DOMAIN << "" << int(QHostInfo::HostNotFound);
QTest::newRow("idn-ace") << "a-single.xn--alqualond-34a" TEST_DOMAIN << "192.0.2.1" << int(QHostInfo::NoError);
QTest::newRow("idn-unicode") << QString::fromLatin1("a-single.alqualond\353" TEST_DOMAIN) << "192.0.2.1" << int(QHostInfo::NoError);
}
void tst_QHostInfo::lookupIPv4()
{
QFETCH(QString, hostname);
QFETCH(int, err);
QFETCH(QString, addresses);
lookupDone = false;
QHostInfo::lookupHost(hostname, this, SLOT(resultsReady(QHostInfo)));
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(lookupDone);
if ((int)lookupResults.error() != (int)err) {
qWarning() << hostname << "=>" << lookupResults.errorString();
}
QCOMPARE((int)lookupResults.error(), (int)err);
QStringList tmp;
for (int i = 0; i < lookupResults.addresses().size(); ++i)
tmp.append(lookupResults.addresses().at(i).toString());
tmp.sort();
QStringList expected = addresses.split(' ');
expected.sort();
QCOMPARE(tmp.join(' '), expected.join(' '));
}
void tst_QHostInfo::lookupIPv6_data()
{
QTest::addColumn<QString>("hostname");
QTest::addColumn<QString>("addresses");
QTest::addColumn<int>("err");
QTest::newRow("aaaa-single") << "aaaa-single" TEST_DOMAIN << "2001:db8::1" << int(QHostInfo::NoError);
QTest::newRow("aaaa-multi") << "aaaa-multi" TEST_DOMAIN << "2001:db8::1 2001:db8::2 2001:db8::3" << int(QHostInfo::NoError);
QTest::newRow("a-plus-aaaa") << "a-plus-aaaa" TEST_DOMAIN << "198.51.100.1 2001:db8::1:1" << int(QHostInfo::NoError);
// avoid using real IPv6 addresses here because this will do a DNS query
// real addresses are between 2000:: and 3fff:ffff:ffff:ffff:ffff:ffff:ffff
QTest::newRow("literal_ip6") << "f001:6b0:1:ea:202:a5ff:fecd:13a6" << "f001:6b0:1:ea:202:a5ff:fecd:13a6" << int(QHostInfo::NoError);
QTest::newRow("literal_shortip6") << "f001:618:1401::4" << "f001:618:1401::4" << int(QHostInfo::NoError);
}
void tst_QHostInfo::lookupIPv6()
{
QFETCH(QString, hostname);
QFETCH(int, err);
QFETCH(QString, addresses);
if (!ipv6LookupsAvailable)
QSKIP("This platform does not support IPv6 lookups");
lookupDone = false;
QHostInfo::lookupHost(hostname, this, SLOT(resultsReady(QHostInfo)));
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(lookupDone);
QCOMPARE((int)lookupResults.error(), (int)err);
QStringList tmp;
for (int i = 0; i < lookupResults.addresses().size(); ++i)
tmp.append(lookupResults.addresses().at(i).toString());
tmp.sort();
QStringList expected = addresses.split(' ');
expected.sort();
QCOMPARE(tmp.join(' ').toLower(), expected.join(' ').toLower());
}
void tst_QHostInfo::lookupConnectToFunctionPointer_data()
{
lookupIPv4_data();
}
void tst_QHostInfo::lookupConnectToFunctionPointer()
{
QFETCH(QString, hostname);
QFETCH(int, err);
QFETCH(QString, addresses);
lookupDone = false;
QHostInfo::lookupHost(hostname, this, &tst_QHostInfo::resultsReady);
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(lookupDone);
if (int(lookupResults.error()) != int(err))
qWarning() << hostname << "=>" << lookupResults.errorString();
QCOMPARE(int(lookupResults.error()), int(err));
QStringList tmp;
for (const auto &result : lookupResults.addresses())
tmp.append(result.toString());
tmp.sort();
QStringList expected = addresses.split(' ');
expected.sort();
QCOMPARE(tmp.join(' '), expected.join(' '));
}
void tst_QHostInfo::lookupConnectToFunctionPointerDeleted()
{
{
QObject contextObject;
QHostInfo::lookupHost("localhost", &contextObject, [](const QHostInfo){
QFAIL("This should never be called!");
});
}
QTestEventLoop::instance().enterLoop(3);
}
void tst_QHostInfo::lookupConnectToLambda_data()
{
lookupIPv4_data();
}
void tst_QHostInfo::lookupConnectToLambda()
{
QFETCH(QString, hostname);
QFETCH(int, err);
QFETCH(QString, addresses);
lookupDone = false;
QHostInfo::lookupHost(hostname, [this](const QHostInfo &hostInfo) {
resultsReady(hostInfo);
});
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(lookupDone);
if (int(lookupResults.error()) != int(err))
qWarning() << hostname << "=>" << lookupResults.errorString();
QCOMPARE(int(lookupResults.error()), int(err));
QStringList tmp;
for (int i = 0; i < lookupResults.addresses().size(); ++i)
tmp.append(lookupResults.addresses().at(i).toString());
tmp.sort();
QStringList expected = addresses.split(' ');
expected.sort();
QCOMPARE(tmp.join(' '), expected.join(' '));
}
static QStringList reverseLookupHelper(const QString &ip)
{
QStringList results;
const QString pythonCode =
"import socket;"
"import sys;"
"print (socket.getnameinfo((sys.argv[1], 0), 0)[0]);";
QList<QByteArray> lines;
QProcess python;
python.setProcessChannelMode(QProcess::ForwardedErrorChannel);
python.start("python", QStringList() << QString("-c") << pythonCode << ip);
if (python.waitForFinished()) {
if (python.exitStatus() == QProcess::NormalExit && python.exitCode() == 0)
lines = python.readAllStandardOutput().split('\n');
for (QByteArray line : lines) {
if (!line.isEmpty())
results << line.trimmed();
}
if (!results.isEmpty())
return results;
}
qDebug() << "Python failed, falling back to nslookup";
QProcess lookup;
lookup.setProcessChannelMode(QProcess::ForwardedErrorChannel);
lookup.start("nslookup", QStringList(ip));
if (!lookup.waitForFinished()) {
results << "nslookup failure";
qDebug() << "nslookup failure";
return results;
}
lines = lookup.readAllStandardOutput().split('\n');
QByteArray name;
const QByteArray nameMarkerNix("name =");
const QByteArray nameMarkerWin("Name:");
const QByteArray addressMarkerWin("Address:");
for (QByteArray line : lines) {
int index = -1;
if ((index = line.indexOf(nameMarkerNix)) != -1) { // Linux and macOS
name = line.mid(index + nameMarkerNix.size()).chopped(1).trimmed();
results << name;
} else if (line.startsWith(nameMarkerWin)) { // Windows formatting
name = line.mid(line.lastIndexOf(" ")).trimmed();
} else if (line.startsWith(addressMarkerWin)) {
QByteArray address = line.mid(addressMarkerWin.size()).trimmed();
if (address == ip.toUtf8()) {
results << name;
}
}
}
if (results.isEmpty()) {
qDebug() << "Failure to parse nslookup output: " << lines;
}
return results;
}
void tst_QHostInfo::reverseLookup_data()
{
QTest::addColumn<QString>("address");
QTest::addColumn<QStringList>("hostNames");
QTest::addColumn<int>("err");
QTest::addColumn<bool>("ipv6");
QTest::newRow("dns.google") << QString("8.8.8.8") << reverseLookupHelper("8.8.8.8") << 0 << false;
QTest::newRow("one.one.one.one") << QString("1.1.1.1") << reverseLookupHelper("1.1.1.1") << 0 << false;
QTest::newRow("dns.google IPv6") << QString("2001:4860:4860::8888") << reverseLookupHelper("2001:4860:4860::8888") << 0 << true;
QTest::newRow("cloudflare IPv6") << QString("2606:4700:4700::1111") << reverseLookupHelper("2606:4700:4700::1111") << 0 << true;
QTest::newRow("bogus-name IPv6") << QString("1::2::3::4") << QStringList() << 1 << true;
}
void tst_QHostInfo::reverseLookup()
{
QFETCH(QString, address);
QFETCH(QStringList, hostNames);
QFETCH(int, err);
QFETCH(bool, ipv6);
if (ipv6 && !ipv6LookupsAvailable) {
QSKIP("IPv6 reverse lookups are not supported on this platform");
}
QHostInfo info = QHostInfo::fromName(address);
if (err == 0) {
if (!hostNames.contains(info.hostName()))
qDebug() << "Failure: expecting" << hostNames << ",got " << info.hostName();
QVERIFY(hostNames.contains(info.hostName()));
QCOMPARE(info.addresses().first(), QHostAddress(address));
} else {
QCOMPARE(info.hostName(), address);
QCOMPARE(info.error(), QHostInfo::HostNotFound);
}
}
void tst_QHostInfo::blockingLookup_data()
{
lookupIPv4_data();
if (ipv6LookupsAvailable)
lookupIPv6_data();
}
void tst_QHostInfo::blockingLookup()
{
QFETCH(QString, hostname);
QFETCH(int, err);
QFETCH(QString, addresses);
QHostInfo hostInfo = QHostInfo::fromName(hostname);
QStringList tmp;
for (int i = 0; i < hostInfo.addresses().size(); ++i)
tmp.append(hostInfo.addresses().at(i).toString());
tmp.sort();
if ((int)hostInfo.error() != (int)err) {
qWarning() << hostname << "=>" << lookupResults.errorString();
}
QCOMPARE((int)hostInfo.error(), (int)err);
QStringList expected = addresses.split(' ');
expected.sort();
QCOMPARE(tmp.join(' ').toUpper(), expected.join(' ').toUpper());
}
void tst_QHostInfo::raceCondition()
{
for (int i = 0; i < 1000; ++i) {
QTcpSocket socket;
socket.connectToHost("invalid" TEST_DOMAIN, 80);
}
}
class LookupThread : public QThread
{
protected:
inline void run() override
{
QHostInfo info = QHostInfo::fromName("a-single" TEST_DOMAIN);
QCOMPARE(info.error(), QHostInfo::NoError);
QVERIFY(info.addresses().size() > 0);
QCOMPARE(info.addresses().at(0).toString(), QString("192.0.2.1"));
}
};
void tst_QHostInfo::threadSafety()
{
const int nattempts = 5;
const int runs = 100;
LookupThread thr[nattempts];
for (int j = 0; j < runs; ++j) {
for (int i = 0; i < nattempts; ++i)
thr[i].start();
for (int k = nattempts - 1; k >= 0; --k)
thr[k].wait();
}
}
class LookupReceiver : public QObject
{
Q_OBJECT
public slots:
void start();
void resultsReady(const QHostInfo&);
public:
QHostInfo result;
int numrequests;
};
void LookupReceiver::start()
{
for (int i=0;i<numrequests;i++)
QHostInfo::lookupHost(QString("a-single" TEST_DOMAIN), this, SLOT(resultsReady(QHostInfo)));
}
void LookupReceiver::resultsReady(const QHostInfo &info)
{
result = info;
numrequests--;
if (numrequests == 0 || info.error() != QHostInfo::NoError)
QThread::currentThread()->quit();
}
void tst_QHostInfo::threadSafetyAsynchronousAPI()
{
const int nattempts = 10;
const int lookupsperthread = 10;
QList<QThread*> threads;
QList<LookupReceiver*> receivers;
for (int i = 0; i < nattempts; ++i) {
QThread* thread = new QThread;
LookupReceiver* receiver = new LookupReceiver;
receiver->numrequests = lookupsperthread;
receivers.append(receiver);
receiver->moveToThread(thread);
connect(thread, SIGNAL(started()), receiver, SLOT(start()));
thread->start();
threads.append(thread);
}
for (int k = threads.size() - 1; k >= 0; --k)
QVERIFY(threads.at(k)->wait(60000));
foreach (LookupReceiver* receiver, receivers) {
QCOMPARE(receiver->result.error(), QHostInfo::NoError);
QCOMPARE(receiver->result.addresses().at(0).toString(), QString("192.0.2.1"));
QCOMPARE(receiver->numrequests, 0);
}
}
// this test is for the multi-threaded QHostInfo rewrite. It is about getting results at all,
// not about getting correct IPs
void tst_QHostInfo::multipleSameLookups()
{
const int COUNT = 10;
lookupsDoneCounter = 0;
for (int i = 0; i < COUNT; i++)
QHostInfo::lookupHost("localhost", this, SLOT(resultsReady(QHostInfo)));
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 10000 && lookupsDoneCounter < COUNT) {
QTestEventLoop::instance().enterLoop(2);
}
QCOMPARE(lookupsDoneCounter, COUNT);
}
// this test is for the multi-threaded QHostInfo rewrite. It is about getting results at all,
// not about getting correct IPs
void tst_QHostInfo::multipleDifferentLookups_data()
{
QTest::addColumn<int>("repeats");
QTest::newRow("1") << 1;
QTest::newRow("2") << 2;
QTest::newRow("5") << 5;
QTest::newRow("10") << 10;
}
void tst_QHostInfo::multipleDifferentLookups()
{
QStringList hostnameList;
hostnameList << "a-single" TEST_DOMAIN
<< "a-multi" TEST_DOMAIN
<< "aaaa-single" TEST_DOMAIN
<< "aaaa-multi" TEST_DOMAIN
<< "a-plus-aaaa" TEST_DOMAIN
<< "multi" TEST_DOMAIN
<< "localhost" TEST_DOMAIN
<< "cname" TEST_DOMAIN
<< "127.0.0.1" << "----";
QFETCH(int, repeats);
const int COUNT = hostnameList.size();
lookupsDoneCounter = 0;
for (int i = 0; i < hostnameList.size(); i++)
for (int j = 0; j < repeats; ++j)
QHostInfo::lookupHost(hostnameList.at(i), this, SLOT(resultsReady(QHostInfo)));
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 60000 && lookupsDoneCounter < repeats*COUNT) {
QTestEventLoop::instance().enterLoop(2);
//qDebug() << "t:" << timer.elapsed();
}
QCOMPARE(lookupsDoneCounter, repeats*COUNT);
}
void tst_QHostInfo::cache()
{
QFETCH_GLOBAL(bool, cache);
if (!cache)
return; // test makes only sense when cache enabled
// reset slot counter
lookupsDoneCounter = 0;
// lookup once, wait in event loop, result should not come directly.
bool valid = true;
int id = -1;
QHostInfo result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(!valid);
QVERIFY(result.addresses().isEmpty());
// loopkup second time, result should come directly
valid = false;
result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QVERIFY(valid);
QVERIFY(!result.addresses().isEmpty());
// clear the cache
qt_qhostinfo_clear_cache();
// lookup third time, result should not come directly.
valid = true;
result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(!valid);
QVERIFY(result.addresses().isEmpty());
// the slot should have been called 2 times.
QCOMPARE(lookupsDoneCounter, 2);
}
void tst_QHostInfo::resultsReady(const QHostInfo &hi)
{
QVERIFY(QThread::currentThread() == thread());
lookupDone = true;
lookupResults = hi;
lookupsDoneCounter++;
QTestEventLoop::instance().exitLoop();
}
void tst_QHostInfo::abortHostLookup()
{
//reset counter
lookupsDoneCounter = 0;
bool valid = false;
int id = -1;
QHostInfo result = qt_qhostinfo_lookup("a-single" TEST_DOMAIN, this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QVERIFY(!valid);
//it is assumed that the DNS request/response in the backend is slower than it takes to call abort
QHostInfo::abortHostLookup(id);
QTestEventLoop::instance().enterLoop(5);
QCOMPARE(lookupsDoneCounter, 0);
}
class LookupAborter : public QObject
{
Q_OBJECT
public slots:
void abort()
{
QHostInfo::abortHostLookup(id);
QThread::currentThread()->quit();
}
public:
int id;
};
QTEST_MAIN(tst_QHostInfo)
#include "tst_qhostinfo.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkaddressentry Test:
#####################################################################
qt_internal_add_test(tst_qnetworkaddressentry
SOURCES
tst_qnetworkaddressentry.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,146 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <qcoreapplication.h>
#include <qnetworkinterface.h>
Q_DECLARE_METATYPE(QHostAddress)
class tst_QNetworkAddressEntry: public QObject
{
Q_OBJECT
private slots:
void getSetCheck();
void prefixAndNetmask_data();
void prefixAndNetmask();
};
void tst_QNetworkAddressEntry::getSetCheck()
{
QNetworkAddressEntry entry;
QVERIFY(entry.ip().isNull());
QVERIFY(entry.netmask().isNull());
QVERIFY(entry.broadcast().isNull());
QCOMPARE(entry.prefixLength(), -1);
entry.setIp(QHostAddress::LocalHost);
QCOMPARE(entry.ip(), QHostAddress(QHostAddress::LocalHost));
entry.setIp(QHostAddress());
QVERIFY(entry.ip().isNull());
entry.setBroadcast(QHostAddress::LocalHost);
QCOMPARE(entry.broadcast(), QHostAddress(QHostAddress::LocalHost));
entry.setBroadcast(QHostAddress());
QVERIFY(entry.broadcast().isNull());
// netmask and prefix length tested in the next test
entry.setIp(QHostAddress::LocalHost);
entry.setBroadcast(QHostAddress::LocalHost);
QNetworkAddressEntry entry2;
QVERIFY(entry != entry2);
QVERIFY(!(entry == entry2));
entry = entry2;
QCOMPARE(entry, entry2);
QCOMPARE(entry, entry);
QVERIFY(!(entry != entry2));
}
void tst_QNetworkAddressEntry::prefixAndNetmask_data()
{
QTest::addColumn<QHostAddress>("ip");
QTest::addColumn<QHostAddress>("netmask");
QTest::addColumn<int>("prefix");
// IPv4 set:
QHostAddress ipv4(QHostAddress::LocalHost);
QTest::newRow("v4/0") << ipv4 << QHostAddress(QHostAddress::AnyIPv4) << 0;
QTest::newRow("v4/32") << ipv4 << QHostAddress("255.255.255.255") << 32;
QTest::newRow("v4/24") << ipv4 << QHostAddress("255.255.255.0") << 24;
QTest::newRow("v4/23") << ipv4 << QHostAddress("255.255.254.0") << 23;
QTest::newRow("v4/20") << ipv4 << QHostAddress("255.255.240.0") << 20;
QTest::newRow("v4/invalid1") << ipv4 << QHostAddress(QHostAddress::LocalHost) << -1;
QTest::newRow("v4/invalid2") << ipv4 << QHostAddress(QHostAddress::AnyIPv6) << -1;
QTest::newRow("v4/invalid3") << ipv4 << QHostAddress("255.255.253.0") << -1;
QTest::newRow("v4/invalid4") << ipv4 << QHostAddress() << -2;
QTest::newRow("v4/invalid5") << ipv4 << QHostAddress() << 33;
// IPv6 set:
QHostAddress ipv6(QHostAddress::LocalHostIPv6);
QTest::newRow("v6/0") << ipv6 << QHostAddress(QHostAddress::AnyIPv6) << 0;
QTest::newRow("v6/128") << ipv6 << QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") << 128;
QTest::newRow("v6/64") << ipv6 << QHostAddress("ffff:ffff:ffff:ffff::") << 64;
QTest::newRow("v6/63") << ipv6 << QHostAddress("ffff:ffff:ffff:fffe::") << 63;
QTest::newRow("v6/60") << ipv6 << QHostAddress("ffff:ffff:ffff:fff0::") << 60;
QTest::newRow("v6/48") << ipv6 << QHostAddress("ffff:ffff:ffff::") << 48;
QTest::newRow("v6/3") << ipv6 << QHostAddress("e000::") << 3;
QTest::newRow("v6/invalid1") << ipv6 << QHostAddress(QHostAddress::LocalHostIPv6) << -1;
QTest::newRow("v6/invalid2") << ipv6 << QHostAddress(QHostAddress::Any) << -1;
QTest::newRow("v6/invalid3") << ipv6 << QHostAddress("fffd::") << -1;
QTest::newRow("v6/invalid4") << ipv6 << QHostAddress() << -2;
QTest::newRow("v6/invalid5") << ipv6 << QHostAddress() << 129;
}
void tst_QNetworkAddressEntry::prefixAndNetmask()
{
QFETCH(QHostAddress, ip);
QFETCH(QHostAddress, netmask);
QFETCH(int, prefix);
QNetworkAddressEntry entry;
// first, without setting the IP, all must be invalid:
entry.setNetmask(netmask);
QVERIFY(entry.netmask().isNull());
entry.setPrefixLength(prefix);
QCOMPARE(entry.prefixLength(), -1);
// set the IP:
entry.setIp(ip);
// set the netmask:
if (!netmask.isNull()) {
entry.setNetmask(netmask);
// was it a valid one?
if (prefix != -1) {
QVERIFY(!entry.netmask().isNull());
QCOMPARE(entry.netmask(), netmask);
QCOMPARE(entry.prefixLength(), prefix);
} else {
// not valid
QVERIFY(entry.netmask().isNull());
QCOMPARE(entry.prefixLength(), -1);
}
}
entry.setNetmask(QHostAddress());
QVERIFY(entry.netmask().isNull());
QCOMPARE(entry.prefixLength(), -1);
// set the prefix
if (prefix != -1) {
entry.setPrefixLength(prefix);
// was it a valid one?
if (!netmask.isNull()) {
QVERIFY(!entry.netmask().isNull());
QCOMPARE(entry.netmask(), netmask);
QCOMPARE(entry.prefixLength(), prefix);
} else {
// not valid
QVERIFY(entry.netmask().isNull());
QCOMPARE(entry.prefixLength(), -1);
}
}
entry.setPrefixLength(-1);
QVERIFY(entry.netmask().isNull());
QCOMPARE(entry.prefixLength(), -1);
}
QTEST_MAIN(tst_QNetworkAddressEntry)
#include "tst_qnetworkaddressentry.moc"

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qnetworkdatagram Test:
#####################################################################
qt_internal_add_test(tst_qnetworkdatagram
SOURCES
tst_qnetworkdatagram.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,132 @@
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QNetworkDatagram>
#include <QTest>
#include <QCoreApplication>
class tst_QNetworkDatagram : public QObject
{
Q_OBJECT
public:
tst_QNetworkDatagram();
private Q_SLOTS:
void getSetCheck();
void makeReply_data();
void makeReply();
};
tst_QNetworkDatagram::tst_QNetworkDatagram()
{
}
void tst_QNetworkDatagram::getSetCheck()
{
QNetworkDatagram dg;
QVERIFY(dg.isNull());
QVERIFY(!dg.isValid());
QCOMPARE(dg.senderAddress(), QHostAddress());
QCOMPARE(dg.destinationAddress(), QHostAddress());
QCOMPARE(dg.senderPort(), -1);
QCOMPARE(dg.destinationPort(), -1);
QCOMPARE(dg.hopLimit(), -1);
QCOMPARE(dg.interfaceIndex(), 0U);
dg.setHopLimit(1);
QCOMPARE(dg.hopLimit(), 1);
dg.setHopLimit(255);
QCOMPARE(dg.hopLimit(), 255);
dg.setInterfaceIndex(1);
QCOMPARE(dg.interfaceIndex(), 1U);
dg.setInterfaceIndex(1234567U);
QCOMPARE(dg.interfaceIndex(), 1234567U);
dg.setSender(QHostAddress::Any, 12345);
QCOMPARE(dg.senderAddress(), QHostAddress(QHostAddress::Any));
QCOMPARE(dg.senderPort(), 12345);
dg.setSender(QHostAddress::LocalHost);
QCOMPARE(dg.senderAddress(), QHostAddress(QHostAddress::LocalHost));
QCOMPARE(dg.senderPort(), 0);
dg.setDestination(QHostAddress::LocalHostIPv6, 12345);
QCOMPARE(dg.destinationAddress(), QHostAddress(QHostAddress::LocalHostIPv6));
QCOMPARE(dg.destinationPort(), 12345);
dg.setDestination(QHostAddress::Broadcast, 137);
QCOMPARE(dg.destinationAddress(), QHostAddress(QHostAddress::Broadcast));
QCOMPARE(dg.destinationPort(), 137);
auto dg2 = dg;
QCOMPARE(dg2.hopLimit(), dg.hopLimit());
QCOMPARE(dg2.interfaceIndex(), dg.interfaceIndex());
QCOMPARE(dg2.senderAddress(), dg.senderAddress());
QCOMPARE(dg2.senderPort(), dg.senderPort());
QCOMPARE(dg2.destinationAddress(), dg.destinationAddress());
QCOMPARE(dg2.destinationPort(), dg.destinationPort());
dg.clear();
QVERIFY(dg.isNull());
}
void tst_QNetworkDatagram::makeReply_data()
{
qRegisterMetaType<QNetworkDatagram>();
QTest::addColumn<QNetworkDatagram>("dgram");
QTest::addColumn<QString>("localAddress");
QNetworkDatagram dgram("some data", QHostAddress("192.0.2.1"), 10001);
dgram.setHopLimit(64);
dgram.setSender(QHostAddress::LocalHost, 12345);
QTest::newRow("ipv4") << dgram << "192.0.2.1";
dgram.setDestination(QHostAddress("224.0.0.1"), 10002);
QTest::newRow("ipv4-multicast") << dgram << QString();
dgram.setSender(QHostAddress::LocalHostIPv6, 12346);
dgram.setDestination(QHostAddress("2001:db8::1"), 12347);
QTest::newRow("ipv6") << dgram << "2001:db8::1";
dgram.setSender(QHostAddress("fe80::1%1"), 10003);
dgram.setDestination(QHostAddress("fe80::2%1"), 10004);
dgram.setInterfaceIndex(1);
QTest::newRow("ipv6-linklocal") << dgram << "fe80::2%1";
dgram.setDestination(QHostAddress("ff02::1%1"), 10005);
QTest::newRow("ipv6-multicast") << dgram << QString();
}
void tst_QNetworkDatagram::makeReply()
{
QFETCH(QNetworkDatagram, dgram);
QFETCH(QString, localAddress);
{
QNetworkDatagram reply = dgram.makeReply("World");
QCOMPARE(reply.data(), QByteArray("World"));
QCOMPARE(reply.senderAddress(), QHostAddress(localAddress));
QCOMPARE(reply.senderPort(), localAddress.isEmpty() ? -1 : dgram.destinationPort());
QCOMPARE(reply.destinationAddress(), dgram.senderAddress());
QCOMPARE(reply.destinationPort(), dgram.senderPort());
QCOMPARE(reply.interfaceIndex(), dgram.interfaceIndex());
QCOMPARE(reply.hopLimit(), -1);
}
QNetworkDatagram copy = dgram;
copy.setData(copy.data());
{
QNetworkDatagram reply = std::move(copy).makeReply("World");
QCOMPARE(reply.data(), QByteArray("World"));
QCOMPARE(reply.senderAddress(), QHostAddress(localAddress));
QCOMPARE(reply.senderPort(), localAddress.isEmpty() ? -1 : dgram.destinationPort());
QCOMPARE(reply.destinationAddress(), dgram.senderAddress());
QCOMPARE(reply.destinationPort(), dgram.senderPort());
QCOMPARE(reply.interfaceIndex(), dgram.interfaceIndex());
QCOMPARE(reply.hopLimit(), -1);
}
}
QTEST_MAIN(tst_QNetworkDatagram)
#include "tst_qnetworkdatagram.moc"

View File

@ -0,0 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_test(tst_qnetworkinformation
SOURCES
tst_qnetworkinformation.cpp
LIBRARIES
Qt::NetworkPrivate
)

View File

@ -0,0 +1,255 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtNetwork/private/qnetworkinformation_p.h>
#include <QtNetwork/qnetworkinformation.h>
#include <QtTest/qtest.h>
#include <QtTest/qsignalspy.h>
#include <limits>
#include <memory>
class MockFactory;
class tst_QNetworkInformation : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void supportedFeatures();
void reachability();
void behindCaptivePortal();
void transportMedium();
void isMetered();
void cleanupTestCase();
private:
std::unique_ptr<MockFactory> mockFactory;
};
static const QString mockName = QStringLiteral("mock");
class MockBackend : public QNetworkInformationBackend
{
Q_OBJECT
public:
MockBackend()
{
Q_ASSERT(!instance);
instance = this;
setReachability(QNetworkInformation::Reachability::Online);
setNewBehindCaptivePortal(false);
}
~MockBackend() { instance = nullptr; }
QString name() const override { return mockName; }
QNetworkInformation::Features featuresSupported() const override
{
return featuresSupportedStatic();
}
static void setNewReachability(QNetworkInformation::Reachability value)
{
Q_ASSERT(instance);
instance->setReachability(value);
}
static void setNewBehindCaptivePortal(bool value)
{
Q_ASSERT(instance);
instance->setBehindCaptivePortal(value);
}
static void setNewTransportMedium(QNetworkInformation::TransportMedium medium)
{
Q_ASSERT(instance);
instance->setTransportMedium(medium);
}
static void setNewMetered(bool metered)
{
Q_ASSERT(instance);
instance->setMetered(metered);
}
static QNetworkInformation::Features featuresSupportedStatic()
{
return { QNetworkInformation::Feature::Reachability
| QNetworkInformation::Feature::CaptivePortal
| QNetworkInformation::Feature::TransportMedium
| QNetworkInformation::Feature::Metered };
}
private:
static inline MockBackend *instance = nullptr;
};
class MockFactory : public QNetworkInformationBackendFactory
{
Q_OBJECT
public:
QString name() const override { return mockName; }
QNetworkInformationBackend *
create(QNetworkInformation::Features requiredFeatures) const override
{
if ((requiredFeatures & featuresSupported()) != requiredFeatures)
return nullptr;
return new MockBackend();
}
QNetworkInformation::Features featuresSupported() const override
{
return MockBackend::featuresSupportedStatic();
}
};
void tst_QNetworkInformation::initTestCase()
{
auto prevBackends = QNetworkInformation::availableBackends();
qDebug() << "available backends:" << prevBackends;
// Creating the factory registers it as a backend
mockFactory = std::make_unique<MockFactory>();
auto backends = QNetworkInformation::availableBackends();
QVERIFY(backends.size() > prevBackends.size());
QVERIFY(backends.contains(u"mock"));
QVERIFY(QNetworkInformation::loadBackendByName(u"mock"));
QVERIFY(QNetworkInformation::loadBackendByName(u"mock"));
QVERIFY(QNetworkInformation::loadBackendByName(u"mOcK"));
QVERIFY(!QNetworkInformation::loadBackendByName(u"mocks"));
}
void tst_QNetworkInformation::cleanupTestCase()
{
// Make sure the factory gets unregistered on destruction:
mockFactory.reset();
auto backends = QNetworkInformation::availableBackends();
QVERIFY(!backends.contains(u"mock"));
}
void tst_QNetworkInformation::supportedFeatures()
{
auto info = QNetworkInformation::instance();
auto allFeatures = QNetworkInformation::Features(QNetworkInformation::Feature::CaptivePortal
| QNetworkInformation::Feature::Reachability
| QNetworkInformation::Feature::TransportMedium
| QNetworkInformation::Feature::Metered);
QCOMPARE(info->supportedFeatures(), allFeatures);
QVERIFY(info->supports(allFeatures));
QVERIFY(info->supports(QNetworkInformation::Feature::CaptivePortal));
QVERIFY(info->supports(QNetworkInformation::Feature::Reachability));
QVERIFY(info->supports(QNetworkInformation::Feature::TransportMedium));
QVERIFY(info->supports(QNetworkInformation::Feature::Metered));
}
void tst_QNetworkInformation::reachability()
{
auto info = QNetworkInformation::instance();
QNetworkInformation::Reachability boundIsOnline = QNetworkInformation::Reachability::Unknown;
bool signalEmitted = false;
connect(info, &QNetworkInformation::reachabilityChanged, this, [&, info]() {
signalEmitted = true;
boundIsOnline = info->reachability();
});
QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Online);
MockBackend::setNewReachability(QNetworkInformation::Reachability::Disconnected);
QCoreApplication::processEvents();
QVERIFY(signalEmitted);
QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Disconnected);
QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Disconnected);
// Set the same value again, signal should not be emitted again
signalEmitted = false;
MockBackend::setNewReachability(QNetworkInformation::Reachability::Disconnected);
QCoreApplication::processEvents();
QVERIFY(!signalEmitted);
MockBackend::setNewReachability(QNetworkInformation::Reachability::Local);
QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Local);
QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Local);
MockBackend::setNewReachability(QNetworkInformation::Reachability::Site);
QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Site);
QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Site);
}
void tst_QNetworkInformation::behindCaptivePortal()
{
auto info = QNetworkInformation::instance();
bool behindPortal = false;
bool signalEmitted = false;
connect(info, &QNetworkInformation::isBehindCaptivePortalChanged, this,
[&, info](bool state) {
signalEmitted = true;
QCOMPARE(state, info->isBehindCaptivePortal());
behindPortal = info->isBehindCaptivePortal();
});
QVERIFY(!info->isBehindCaptivePortal());
MockBackend::setNewBehindCaptivePortal(true);
QCoreApplication::processEvents();
QVERIFY(signalEmitted);
QVERIFY(info->isBehindCaptivePortal());
QVERIFY(behindPortal);
// Set the same value again, signal should not be emitted again
signalEmitted = false;
MockBackend::setNewBehindCaptivePortal(true);
QCoreApplication::processEvents();
QVERIFY(!signalEmitted);
}
void tst_QNetworkInformation::transportMedium()
{
auto info = QNetworkInformation::instance();
using TransportMedium = QNetworkInformation::TransportMedium;
TransportMedium medium = TransportMedium::Unknown;
bool signalEmitted = false;
connect(info, &QNetworkInformation::transportMediumChanged, this, [&, info](TransportMedium tm) {
signalEmitted = true;
QCOMPARE(tm, info->transportMedium());
medium = info->transportMedium();
});
QCOMPARE(info->transportMedium(), TransportMedium::Unknown); // Default is unknown
auto transportMediumEnum = QMetaEnum::fromType<TransportMedium>();
auto mediumCount = transportMediumEnum.keyCount();
// Verify index 0 is Unknown and skip it in the loop, it's the default.
QCOMPARE(TransportMedium(transportMediumEnum.value(0)), TransportMedium::Unknown);
for (int i = 1; i < mediumCount; ++i) {
signalEmitted = false;
TransportMedium m = TransportMedium(transportMediumEnum.value(i));
MockBackend::setNewTransportMedium(m);
QCoreApplication::processEvents();
QVERIFY(signalEmitted);
QCOMPARE(info->transportMedium(), m);
QCOMPARE(medium, m);
}
// Set the current value again, signal should not be emitted again
signalEmitted = false;
MockBackend::setNewTransportMedium(medium);
QCoreApplication::processEvents();
QVERIFY(!signalEmitted);
}
void tst_QNetworkInformation::isMetered()
{
auto info = QNetworkInformation::instance();
QSignalSpy spy(info, &QNetworkInformation::isMeteredChanged);
QVERIFY(!info->isMetered());
MockBackend::setNewMetered(true);
QCOMPARE(spy.size(), 1);
QVERIFY(info->isMetered());
QVERIFY(spy[0][0].toBool());
spy.clear();
// Set the same value again, signal should not be emitted again
MockBackend::setNewMetered(true);
QCOMPARE(spy.size(), 0);
}
QTEST_MAIN(tst_QNetworkInformation);
#include "tst_qnetworkinformation.moc"

View File

@ -0,0 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_test(tst_qnetworkinformation_appless
SOURCES
tst_qnetworkinformation_appless.cpp
LIBRARIES
Qt::Network
)

Some files were not shown because too many files have changed in this diff Show More