Add QHttpHeaders class
New QHttpHeaders class for use in place of std::pair<QBA,QBA>, QMap<QBA>, and QMultiMap/Hash<QBA,QBA> to represent HTTP headers. [ChangeLog][QtNetwork][QHttpHeaders] New QHttpHeaders class Task-number: QTBUG-107042 Change-Id: I54766886a491acfc9a813a3414322a75011acb9d Reviewed-by: Marc Mutz <marc.mutz@qt.io>bb10
parent
0919f97811
commit
925ce9e908
|
|
@ -123,6 +123,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http
|
|||
access/qhttp1configuration.cpp access/qhttp1configuration.h
|
||||
access/qhttp2configuration.cpp access/qhttp2configuration.h
|
||||
access/qhttp2protocolhandler.cpp access/qhttp2protocolhandler_p.h
|
||||
access/qhttpheaders.cpp access/qhttpheaders.h
|
||||
access/qhttpmultipart.cpp access/qhttpmultipart.h access/qhttpmultipart_p.h
|
||||
access/qhttpnetworkconnection.cpp access/qhttpnetworkconnection_p.h
|
||||
access/qhttpnetworkconnectionchannel.cpp access/qhttpnetworkconnectionchannel_p.h
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QHTTPHEADERS_H
|
||||
#define QHTTPHEADERS_H
|
||||
|
||||
#include <QtNetwork/qnetworkrequest.h>
|
||||
#include <QtCore/qshareddata.h>
|
||||
#include <QtCore/qcontainerfwd.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QDebug;
|
||||
|
||||
class QHttpHeadersPrivate;
|
||||
QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QHttpHeadersPrivate, Q_NETWORK_EXPORT)
|
||||
class QHttpHeaders
|
||||
{
|
||||
Q_GADGET_EXPORT(Q_NETWORK_EXPORT)
|
||||
public:
|
||||
enum class WellKnownHeader {
|
||||
// IANA Permanent status:
|
||||
AIM,
|
||||
Accept,
|
||||
AcceptAdditions,
|
||||
AcceptCH,
|
||||
AcceptDatetime,
|
||||
AcceptEncoding,
|
||||
AcceptFeatures,
|
||||
AcceptLanguage,
|
||||
AcceptPatch,
|
||||
AcceptPost,
|
||||
AcceptRanges,
|
||||
AcceptSignature,
|
||||
AccessControlAllowCredentials,
|
||||
AccessControlAllowHeaders,
|
||||
AccessControlAllowMethods,
|
||||
AccessControlAllowOrigin,
|
||||
AccessControlExposeHeaders,
|
||||
AccessControlMaxAge,
|
||||
AccessControlRequestHeaders,
|
||||
AccessControlRequestMethod,
|
||||
Age,
|
||||
Allow,
|
||||
ALPN,
|
||||
AltSvc,
|
||||
AltUsed,
|
||||
Alternates,
|
||||
ApplyToRedirectRef,
|
||||
AuthenticationControl,
|
||||
AuthenticationInfo,
|
||||
Authorization,
|
||||
CacheControl,
|
||||
CacheStatus,
|
||||
CalManagedID,
|
||||
CalDAVTimezones,
|
||||
CapsuleProtocol,
|
||||
CDNCacheControl,
|
||||
CDNLoop,
|
||||
CertNotAfter,
|
||||
CertNotBefore,
|
||||
ClearSiteData,
|
||||
ClientCert,
|
||||
ClientCertChain,
|
||||
Close,
|
||||
Connection,
|
||||
ContentDigest,
|
||||
ContentDisposition,
|
||||
ContentEncoding,
|
||||
ContentID,
|
||||
ContentLanguage,
|
||||
ContentLength,
|
||||
ContentLocation,
|
||||
ContentRange,
|
||||
ContentSecurityPolicy,
|
||||
ContentSecurityPolicyReportOnly,
|
||||
ContentType,
|
||||
Cookie,
|
||||
CrossOriginEmbedderPolicy,
|
||||
CrossOriginEmbedderPolicyReportOnly,
|
||||
CrossOriginOpenerPolicy,
|
||||
CrossOriginOpenerPolicyReportOnly,
|
||||
CrossOriginResourcePolicy,
|
||||
DASL,
|
||||
Date,
|
||||
DAV,
|
||||
DeltaBase,
|
||||
Depth,
|
||||
Destination,
|
||||
DifferentialID,
|
||||
DPoP,
|
||||
DPoPNonce,
|
||||
EarlyData,
|
||||
ETag,
|
||||
Expect,
|
||||
ExpectCT,
|
||||
Expires,
|
||||
Forwarded,
|
||||
From,
|
||||
Hobareg,
|
||||
Host,
|
||||
If,
|
||||
IfMatch,
|
||||
IfModifiedSince,
|
||||
IfNoneMatch,
|
||||
IfRange,
|
||||
IfScheduleTagMatch,
|
||||
IfUnmodifiedSince,
|
||||
IM,
|
||||
IncludeReferredTokenBindingID,
|
||||
KeepAlive,
|
||||
Label,
|
||||
LastEventID,
|
||||
LastModified,
|
||||
Link,
|
||||
Location,
|
||||
LockToken,
|
||||
MaxForwards,
|
||||
MementoDatetime,
|
||||
Meter,
|
||||
MIMEVersion,
|
||||
Negotiate,
|
||||
NEL,
|
||||
ODataEntityId,
|
||||
ODataIsolation,
|
||||
ODataMaxVersion,
|
||||
ODataVersion,
|
||||
OptionalWWWAuthenticate,
|
||||
OrderingType,
|
||||
Origin,
|
||||
OriginAgentCluster,
|
||||
OSCORE,
|
||||
OSLCCoreVersion,
|
||||
Overwrite,
|
||||
PingFrom,
|
||||
PingTo,
|
||||
Position,
|
||||
Prefer,
|
||||
PreferenceApplied,
|
||||
Priority,
|
||||
ProxyAuthenticate,
|
||||
ProxyAuthenticationInfo,
|
||||
ProxyAuthorization,
|
||||
ProxyStatus,
|
||||
PublicKeyPins,
|
||||
PublicKeyPinsReportOnly,
|
||||
Range,
|
||||
RedirectRef,
|
||||
Referer,
|
||||
Refresh,
|
||||
ReplayNonce,
|
||||
ReprDigest,
|
||||
RetryAfter,
|
||||
ScheduleReply,
|
||||
ScheduleTag,
|
||||
SecPurpose,
|
||||
SecTokenBinding,
|
||||
SecWebSocketAccept,
|
||||
SecWebSocketExtensions,
|
||||
SecWebSocketKey,
|
||||
SecWebSocketProtocol,
|
||||
SecWebSocketVersion,
|
||||
Server,
|
||||
ServerTiming,
|
||||
SetCookie,
|
||||
Signature,
|
||||
SignatureInput,
|
||||
SLUG,
|
||||
SoapAction,
|
||||
StatusURI,
|
||||
StrictTransportSecurity,
|
||||
Sunset,
|
||||
SurrogateCapability,
|
||||
SurrogateControl,
|
||||
TCN,
|
||||
TE,
|
||||
Timeout,
|
||||
Topic,
|
||||
Traceparent,
|
||||
Tracestate,
|
||||
Trailer,
|
||||
TransferEncoding,
|
||||
TTL,
|
||||
Upgrade,
|
||||
Urgency,
|
||||
UserAgent,
|
||||
VariantVary,
|
||||
Vary,
|
||||
Via,
|
||||
WantContentDigest,
|
||||
WantReprDigest,
|
||||
WWWAuthenticate,
|
||||
XContentTypeOptions,
|
||||
XFrameOptions,
|
||||
// IANA Deprecated status:
|
||||
AcceptCharset,
|
||||
CPEPInfo,
|
||||
Pragma,
|
||||
ProtocolInfo,
|
||||
ProtocolQuery,
|
||||
};
|
||||
Q_ENUM(WellKnownHeader)
|
||||
|
||||
enum class CompareOption {
|
||||
OrderInsensitive = 0x01,
|
||||
OrderSensitive = 0x02,
|
||||
};
|
||||
Q_DECLARE_FLAGS(CompareOptions, CompareOption)
|
||||
|
||||
Q_NETWORK_EXPORT QHttpHeaders();
|
||||
Q_NETWORK_EXPORT ~QHttpHeaders();
|
||||
|
||||
Q_NETWORK_EXPORT QHttpHeaders(const QHttpHeaders &other);
|
||||
QHttpHeaders(QHttpHeaders &&other) noexcept = default;
|
||||
Q_NETWORK_EXPORT QHttpHeaders &operator=(const QHttpHeaders &other);
|
||||
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QHttpHeaders)
|
||||
void swap(QHttpHeaders &other) noexcept { d.swap(other.d); }
|
||||
|
||||
Q_NETWORK_EXPORT bool append(QAnyStringView name, QAnyStringView value);
|
||||
Q_NETWORK_EXPORT bool append(WellKnownHeader name, QAnyStringView value);
|
||||
|
||||
Q_NETWORK_EXPORT bool insert(qsizetype i, QAnyStringView name, QAnyStringView value);
|
||||
Q_NETWORK_EXPORT bool insert(qsizetype i, WellKnownHeader name, QAnyStringView value);
|
||||
|
||||
Q_NETWORK_EXPORT bool replace(qsizetype i, QAnyStringView name, QAnyStringView value);
|
||||
Q_NETWORK_EXPORT bool replace(qsizetype i, WellKnownHeader name, QAnyStringView value);
|
||||
|
||||
Q_NETWORK_EXPORT bool has(QAnyStringView name) const;
|
||||
Q_NETWORK_EXPORT bool has(WellKnownHeader name) const;
|
||||
|
||||
Q_NETWORK_EXPORT QList<QByteArray> names() const;
|
||||
|
||||
Q_NETWORK_EXPORT void clear();
|
||||
Q_NETWORK_EXPORT void removeAll(QAnyStringView name);
|
||||
Q_NETWORK_EXPORT void removeAll(WellKnownHeader name);
|
||||
Q_NETWORK_EXPORT void removeAt(qsizetype i);
|
||||
|
||||
Q_NETWORK_EXPORT QList<QByteArray> values(QAnyStringView name) const;
|
||||
Q_NETWORK_EXPORT QList<QByteArray> values(WellKnownHeader name) const;
|
||||
|
||||
Q_NETWORK_EXPORT QByteArray combinedValue(QAnyStringView name) const;
|
||||
Q_NETWORK_EXPORT QByteArray combinedValue(WellKnownHeader name) const;
|
||||
|
||||
Q_NETWORK_EXPORT qsizetype size() const noexcept;
|
||||
bool isEmpty() const noexcept { return size() == 0; }
|
||||
|
||||
Q_NETWORK_EXPORT bool equals(const QHttpHeaders &other,
|
||||
CompareOptions options = CompareOption::OrderInsensitive) const noexcept;
|
||||
|
||||
Q_NETWORK_EXPORT static QHttpHeaders
|
||||
fromListOfPairs(const QList<std::pair<QByteArray, QByteArray>> &headers);
|
||||
Q_NETWORK_EXPORT static QHttpHeaders
|
||||
fromMultiMap(const QMultiMap<QByteArray, QByteArray> &headers);
|
||||
Q_NETWORK_EXPORT static QHttpHeaders
|
||||
fromMultiHash(const QMultiHash<QByteArray, QByteArray> &headers);
|
||||
|
||||
Q_NETWORK_EXPORT QList<std::pair<QByteArray, QByteArray>> toListOfPairs() const;
|
||||
Q_NETWORK_EXPORT QMultiMap<QByteArray, QByteArray> toMultiMap() const;
|
||||
Q_NETWORK_EXPORT QMultiHash<QByteArray, QByteArray> toMultiHash() const;
|
||||
|
||||
private:
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QHttpHeaders &headers);
|
||||
#endif
|
||||
QExplicitlySharedDataPointer<QHttpHeadersPrivate> d;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(QHttpHeaders::CompareOptions)
|
||||
|
||||
Q_DECLARE_SHARED(QHttpHeaders)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QHTTPHEADERS_H
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
add_subdirectory(qhttpheaders)
|
||||
add_subdirectory(qnetworkdiskcache)
|
||||
add_subdirectory(qnetworkcookiejar)
|
||||
add_subdirectory(qnetworkaccessmanager)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_test(tst_qhttpheaders
|
||||
SOURCES
|
||||
tst_qhttpheaders.cpp
|
||||
LIBRARIES
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
)
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QtNetwork/qhttpheaders.h>
|
||||
|
||||
#include <QtTest/qtest.h>
|
||||
|
||||
#include <QtCore/qset.h>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
class tst_QHttpHeaders : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void comparison();
|
||||
void constructors();
|
||||
void accessors();
|
||||
void headerNameField();
|
||||
void headerValueField();
|
||||
void valueEncoding();
|
||||
|
||||
private:
|
||||
static constexpr QAnyStringView n1{"name1"};
|
||||
static constexpr QAnyStringView n2{"name2"};
|
||||
static constexpr QAnyStringView n3{"name3"};
|
||||
static constexpr QAnyStringView v1{"value1"};
|
||||
static constexpr QAnyStringView v2{"value2"};
|
||||
static constexpr QAnyStringView v3{"value3"};
|
||||
static constexpr QAnyStringView N1{"NAME1"};
|
||||
static constexpr QAnyStringView N2{"NAME2"};
|
||||
static constexpr QAnyStringView N3{"NAME3"};
|
||||
static constexpr QAnyStringView V1{"VALUE1"};
|
||||
static constexpr QAnyStringView V2{"VALUE2"};
|
||||
static constexpr QAnyStringView V3{"VALUE3"};
|
||||
};
|
||||
|
||||
void tst_QHttpHeaders::comparison()
|
||||
{
|
||||
// Basic comparisons
|
||||
QHttpHeaders h1;
|
||||
QHttpHeaders h2;
|
||||
QVERIFY(h1.equals(h2)); // empties
|
||||
h1.append(n1, v1);
|
||||
QVERIFY(h1.equals(h1)); // self
|
||||
h2.append(n1, v1);
|
||||
QVERIFY(h1.equals(h2));
|
||||
h1.append(n2, v2);
|
||||
QVERIFY(!h1.equals(h2));
|
||||
h1.removeAll(n2);
|
||||
QVERIFY(h1.equals(h2));
|
||||
|
||||
// 'name' case-insensitivity and 'value' case-sensitivity
|
||||
h1.removeAll(n1);
|
||||
QVERIFY(h1.isEmpty());
|
||||
h1.append(N1, v1);
|
||||
QVERIFY(h1.equals(h2));
|
||||
h1.removeAll(n1);
|
||||
QVERIFY(h1.isEmpty());
|
||||
h1.append(n1, V1);
|
||||
QVERIFY(!h1.equals(h2));
|
||||
|
||||
// Order-insensitivity
|
||||
h1.clear();
|
||||
h2.clear();
|
||||
QVERIFY(h1.isEmpty() && h2.isEmpty());
|
||||
// Same headers but in different order
|
||||
h1.append(n1, v1);
|
||||
h1.append(n2, v2);
|
||||
h2.append(n2, v2);
|
||||
h2.append(n1, v1);
|
||||
QVERIFY(h1.equals(h2));
|
||||
// Add header with different name casing
|
||||
h1.insert(0, n1, v1);
|
||||
h2.append(N1, v1);
|
||||
QVERIFY(h1.equals(h2));
|
||||
// Add header with different value casing
|
||||
h1.insert(0, n1, v1);
|
||||
h2.append(n1, V1);
|
||||
QVERIFY(!h1.equals(h2));
|
||||
|
||||
// Order-sensitivity
|
||||
h1.clear();
|
||||
h2.clear();
|
||||
QVERIFY(h1.equals(h2, QHttpHeaders::CompareOption::OrderSensitive));
|
||||
// Same headers but in different order
|
||||
h1.append(n1, v1);
|
||||
h1.append(n2, v2);
|
||||
h2.append(n2, v2);
|
||||
h2.append(n1, v1);
|
||||
QVERIFY(!h1.equals(h2, QHttpHeaders::CompareOption::OrderSensitive));
|
||||
|
||||
// Different number of headers
|
||||
h1.clear();
|
||||
h2.clear();
|
||||
h1.append(n1, v1);
|
||||
h2.append(n1, v1);
|
||||
h2.append(n2, v2);
|
||||
QVERIFY(!h1.equals(h2));
|
||||
QVERIFY(!h1.equals(h2, QHttpHeaders::CompareOption::OrderSensitive));
|
||||
}
|
||||
|
||||
void tst_QHttpHeaders::constructors()
|
||||
{
|
||||
// Default ctor
|
||||
QHttpHeaders h1;
|
||||
QVERIFY(h1.isEmpty());
|
||||
|
||||
// Copy ctor
|
||||
QHttpHeaders h2(h1);
|
||||
QVERIFY(h2.equals(h1));
|
||||
|
||||
// Copy assignment
|
||||
QHttpHeaders h3;
|
||||
h3 = h1;
|
||||
QVERIFY(h3.equals(h1));
|
||||
|
||||
// Move assignment
|
||||
QHttpHeaders h4;
|
||||
h4 = std::move(h2);
|
||||
QVERIFY(h4.equals(h1));
|
||||
|
||||
// Move ctor
|
||||
QHttpHeaders h5(std::move(h4));
|
||||
QVERIFY(h5.equals(h1));
|
||||
|
||||
// Constructors that are counterparts to 'toXXX()' conversion getters
|
||||
const QByteArray nb1{"name1"};
|
||||
const QByteArray nb2{"name2"};
|
||||
const QByteArray nv1{"value1"};
|
||||
const QByteArray nv2{"value2"};
|
||||
// Initialize three QHttpHeaders with same content and verify they match
|
||||
QList<std::pair<QByteArray, QByteArray>> list{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}};
|
||||
QMultiMap<QByteArray, QByteArray> map{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}};
|
||||
QMultiHash<QByteArray, QByteArray> hash{{nb1, nv1}, {nb2, nv2}, {nb2, nv2}};
|
||||
QHttpHeaders hlist = QHttpHeaders::fromListOfPairs(list);
|
||||
QHttpHeaders hmap = QHttpHeaders::fromMultiMap(map);
|
||||
QHttpHeaders hhash = QHttpHeaders::fromMultiHash(hash);
|
||||
QVERIFY(hlist.has(nb1) && hmap.has(nb1) && hhash.has(nb1));
|
||||
QVERIFY(!hlist.has(n3) && !hmap.has(n3) && !hhash.has(n3));
|
||||
QVERIFY(hlist.equals(hmap));
|
||||
QVERIFY(hmap.equals(hhash));
|
||||
}
|
||||
|
||||
void tst_QHttpHeaders::accessors()
|
||||
{
|
||||
QHttpHeaders h1;
|
||||
|
||||
// isEmpty(), clear(), size()
|
||||
h1.append(n1,v1);
|
||||
QVERIFY(!h1.isEmpty());
|
||||
QCOMPARE(h1.size(), 1);
|
||||
QVERIFY(h1.append(n1, v1));
|
||||
QCOMPARE(h1.size(), 2);
|
||||
h1.insert(0, n1, v1);
|
||||
QCOMPARE(h1.size(), 3);
|
||||
h1.clear();
|
||||
QVERIFY(h1.isEmpty());
|
||||
|
||||
// has()
|
||||
h1.append(n1, v1);
|
||||
QVERIFY(h1.has(n1));
|
||||
QVERIFY(h1.has(N1));
|
||||
QVERIFY(!h1.has(n2));
|
||||
QVERIFY(!h1.has(QHttpHeaders::WellKnownHeader::Allow));
|
||||
h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing");
|
||||
QVERIFY(h1.has(QHttpHeaders::WellKnownHeader::Accept));
|
||||
QVERIFY(h1.has("accept"));
|
||||
|
||||
// values()
|
||||
QCOMPARE(h1.values(n1).at(0), v1);
|
||||
QCOMPARE(h1.values(N1).at(0), v1);
|
||||
QCOMPARE(h1.values(QHttpHeaders::WellKnownHeader::Accept).at(0), "nothing");
|
||||
QCOMPARE(h1.values("Accept").at(0), "nothing");
|
||||
QVERIFY(h1.values(N2).isEmpty());
|
||||
QVERIFY(h1.values(QHttpHeaders::WellKnownHeader::Allow).isEmpty());
|
||||
h1.clear();
|
||||
QVERIFY(h1.values(n1).isEmpty());
|
||||
h1.append(n1, v1);
|
||||
h1.append(n1, v2);
|
||||
h1.append(n1, v3);
|
||||
h1.append(n2, v2);
|
||||
h1.append(n3, ""); // empty value
|
||||
QCOMPARE(h1.values(n1).size(), 3);
|
||||
QCOMPARE(h1.values(n1).at(0), v1);
|
||||
QCOMPARE(h1.values(n1).at(1), v2);
|
||||
QCOMPARE(h1.values(n1).at(2), v3);
|
||||
QCOMPARE(h1.values(n1), h1.values(N1));
|
||||
QVERIFY(!h1.values(n3).isEmpty());
|
||||
QCOMPARE(h1.values(n3).at(0), "");
|
||||
QVERIFY(!h1.combinedValue(n1).isNull());
|
||||
QCOMPARE(h1.combinedValue(n1), "value1,value2,value3"_ba);
|
||||
h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing");
|
||||
h1.append(QHttpHeaders::WellKnownHeader::Accept, "ever");
|
||||
QVERIFY(!h1.combinedValue(QHttpHeaders::WellKnownHeader::Accept).isNull());
|
||||
QCOMPARE(h1.combinedValue(QHttpHeaders::WellKnownHeader::Accept), "nothing,ever");
|
||||
QVERIFY(h1.combinedValue("nonexistent").isNull());
|
||||
|
||||
// names()
|
||||
h1.clear();
|
||||
QVERIFY(h1.names().isEmpty());
|
||||
h1.append(n1, v1);
|
||||
QCOMPARE(h1.names().size(), 1);
|
||||
QCOMPARE(h1.size(), 1);
|
||||
QVERIFY(h1.names().contains(n1.toString().toLatin1()));
|
||||
h1.append(n2, v2);
|
||||
QCOMPARE(h1.names().size(), 2);
|
||||
QCOMPARE(h1.size(), 2);
|
||||
QVERIFY(h1.names().contains(n1.toString().toLatin1()));
|
||||
QVERIFY(h1.names().contains(n2.toString().toLatin1()));
|
||||
h1.append(n1, v1);
|
||||
h1.append(n1, v1);
|
||||
QCOMPARE(h1.size(), 4);
|
||||
QCOMPARE(h1.names().size(), 2);
|
||||
h1.append(N1, v1); // uppercase of n1
|
||||
QCOMPARE(h1.size(), 5);
|
||||
QCOMPARE(h1.names().size(), 2);
|
||||
|
||||
// removeAll()
|
||||
h1.clear();
|
||||
QVERIFY(h1.append(n1, v1));
|
||||
QVERIFY(h1.append(QHttpHeaders::WellKnownHeader::Accept, "nothing"));
|
||||
QVERIFY(h1.append(n1, v1));
|
||||
QCOMPARE(h1.size(), 3);
|
||||
h1.removeAll(n1);
|
||||
QVERIFY(!h1.has(n1));
|
||||
QCOMPARE(h1.size(), 1);
|
||||
QVERIFY(h1.has("accept"));
|
||||
h1.removeAll(QHttpHeaders::WellKnownHeader::Accept);
|
||||
QVERIFY(!h1.has(QHttpHeaders::WellKnownHeader::Accept));
|
||||
|
||||
// removeAt()
|
||||
h1.clear();
|
||||
h1.append(n1, v1);
|
||||
h1.append(n2, v2);
|
||||
h1.append(n3, v3);
|
||||
|
||||
// Valid removals
|
||||
QVERIFY(h1.has(n3));
|
||||
h1.removeAt(2);
|
||||
QVERIFY(!h1.has(n3));
|
||||
QVERIFY(h1.has(n1));
|
||||
h1.removeAt(0);
|
||||
QVERIFY(!h1.has(n1));
|
||||
QVERIFY(h1.has(n2));
|
||||
h1.removeAt(0);
|
||||
QVERIFY(!h1.has(n2));
|
||||
QVERIFY(h1.isEmpty());
|
||||
|
||||
// toListOfPairs()
|
||||
h1.clear();
|
||||
h1.append(n1, v1);
|
||||
h1.append(n2, v2);
|
||||
h1.append(N3, V3); // uppercase of n3
|
||||
auto list = h1.toListOfPairs();
|
||||
QCOMPARE(list.size(), h1.size());
|
||||
QCOMPARE(list.at(0).first, n1);
|
||||
QCOMPARE(list.at(0).second, v1);
|
||||
QCOMPARE(list.at(1).first, n2);
|
||||
QCOMPARE(list.at(1).second, v2);
|
||||
QCOMPARE(list.at(2).first, n3); // N3 has been lower-cased
|
||||
QCOMPARE(list.at(2).second, V3);
|
||||
|
||||
// toMultiMap()
|
||||
auto map = h1.toMultiMap();
|
||||
QCOMPARE(map.size(), h1.size());
|
||||
QCOMPARE(map.value(n1.toString().toLatin1()), v1);
|
||||
QCOMPARE(map.value(n2.toString().toLatin1()), v2);
|
||||
QCOMPARE(map.value(n3.toString().toLatin1()), V3);
|
||||
|
||||
// toMultiHash()
|
||||
auto hash = h1.toMultiHash();
|
||||
QCOMPARE(hash.size(), h1.size());
|
||||
QCOMPARE(hash.value(n1.toString().toLatin1()), v1);
|
||||
QCOMPARE(hash.value(n2.toString().toLatin1()), v2);
|
||||
QCOMPARE(hash.value(n3.toString().toLatin1()), V3);
|
||||
|
||||
// insert()
|
||||
h1.clear();
|
||||
h1.append(n3, v3);
|
||||
QVERIFY(h1.insert(0, n1, v1));
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.size(), 2);
|
||||
QCOMPARE(list.at(0).first, n1);
|
||||
QCOMPARE(list.at(0).second, v1);
|
||||
QCOMPARE(list.at(1).first, n3);
|
||||
QCOMPARE(list.at(1).second, v3);
|
||||
QVERIFY(h1.insert(1, n2, v2));
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.size(), 3);
|
||||
QCOMPARE(list.at(0).first, n1);
|
||||
QCOMPARE(list.at(0).second, v1);
|
||||
QCOMPARE(list.at(1).first, n2);
|
||||
QCOMPARE(list.at(1).second, v2);
|
||||
QCOMPARE(list.at(2).first, n3);
|
||||
QCOMPARE(list.at(2).second, v3);
|
||||
QVERIFY(h1.insert(1, QHttpHeaders::WellKnownHeader::Accept, "nothing"));
|
||||
QCOMPARE(h1.size(), 4);
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.at(1).first, "accept");
|
||||
QCOMPARE(list.at(1).second, "nothing");
|
||||
QVERIFY(h1.insert(list.size(), "LastName", "lastValue"));
|
||||
QCOMPARE(h1.size(), 5);
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.last().first, "lastname");
|
||||
QCOMPARE(list.last().second, "lastValue");
|
||||
// Failed insert
|
||||
QRegularExpression re("HTTP header name contained*");
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, re);
|
||||
QVERIFY(!h1.insert(0, "a€", "b"));
|
||||
|
||||
// replace
|
||||
h1.clear();
|
||||
h1.append(n1, v1);
|
||||
h1.append(n2, v2);
|
||||
QCOMPARE(h1.size(), 2);
|
||||
QVERIFY(h1.replace(0, n3, v3));
|
||||
QVERIFY(h1.replace(1, QHttpHeaders::WellKnownHeader::Accept, "nothing"));
|
||||
QCOMPARE(h1.size(), 2);
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.at(0).first, n3);
|
||||
QCOMPARE(list.at(0).second, v3);
|
||||
QCOMPARE(list.at(1).first, "accept");
|
||||
QCOMPARE(list.at(1).second, "nothing");
|
||||
QVERIFY(h1.replace(1, "ACCEPT", "NOTHING"));
|
||||
QCOMPARE(h1.size(), 2);
|
||||
list = h1.toListOfPairs();
|
||||
QCOMPARE(list.at(0).first, n3);
|
||||
QCOMPARE(list.at(0).second, v3);
|
||||
QCOMPARE(list.at(1).first, "accept");
|
||||
QCOMPARE(list.at(1).second, "NOTHING");
|
||||
// Failed replace
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, re);
|
||||
QVERIFY(!h1.replace(0, "a€", "b"));
|
||||
|
||||
}
|
||||
|
||||
#define TEST_ILLEGAL_HEADER_NAME_CHARACTER(NAME) \
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \
|
||||
QVERIFY(!h1.append(NAME, v1)); \
|
||||
QVERIFY(h1.isEmpty()); \
|
||||
|
||||
void tst_QHttpHeaders::headerNameField()
|
||||
{
|
||||
QHttpHeaders h1;
|
||||
|
||||
// All allowed characters in different encodings and types
|
||||
// const char[]
|
||||
h1.append("abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1);
|
||||
QCOMPARE(h1.size(), 1);
|
||||
// UTF-8
|
||||
h1.append(u8"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~",
|
||||
v1);
|
||||
QCOMPARE(h1.size(), 2);
|
||||
// UTF-16
|
||||
h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~", v1);
|
||||
QCOMPARE(h1.size(), 3);
|
||||
// QString (UTF-16)
|
||||
h1.append(u"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"_s,
|
||||
v1);
|
||||
QCOMPARE(h1.size(), 4);
|
||||
QCOMPARE(h1.names().size(), 1);
|
||||
h1.clear();
|
||||
|
||||
// Error cases
|
||||
// Header name must contain at least 1 character
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "HTTP header name cannot be empty");
|
||||
h1.append("", v1);
|
||||
QVERIFY(h1.isEmpty());
|
||||
// Disallowed ASCII/extended ASCII characters (not exhaustive list)
|
||||
QRegularExpression re("HTTP header name contained illegal character*");
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x08" "bar"); // BS
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo\x7F" "bar"); // DEL
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo()" "bar"); // parantheses
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("foobar" "¿"); // extended ASCII
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("©" "foobar"); // extended ASCII
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER("foo,bar"); // comma
|
||||
// Disallowed UTF-8 characters
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"€");
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏");
|
||||
// Disallowed UTF-16 characters
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"€");
|
||||
TEST_ILLEGAL_HEADER_NAME_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏");
|
||||
|
||||
// Non-null-terminated name. The 'x' below is to make sure the strings don't
|
||||
// null-terminate by happenstance
|
||||
h1.clear();
|
||||
constexpr char L1Array[] = {'a','b','c','x'};
|
||||
const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc
|
||||
|
||||
constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78};
|
||||
const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def
|
||||
|
||||
constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'};
|
||||
QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi
|
||||
|
||||
h1.append(nonNullLatin1, v1);
|
||||
QCOMPARE(h1.size(), 1);
|
||||
QVERIFY(h1.has(nonNullLatin1));
|
||||
QCOMPARE(h1.combinedValue(nonNullLatin1), v1);
|
||||
|
||||
h1.append(nonNullUTF8, v2);
|
||||
QCOMPARE(h1.size(), 2);
|
||||
QVERIFY(h1.has(nonNullUTF8));
|
||||
QCOMPARE(h1.combinedValue(nonNullUTF8), v2);
|
||||
|
||||
h1.append(nonNullUTF16, v3);
|
||||
QCOMPARE(h1.size(), 3);
|
||||
QVERIFY(h1.has(nonNullUTF16));
|
||||
QCOMPARE(h1.combinedValue(nonNullUTF16), v3);
|
||||
}
|
||||
|
||||
#define TEST_ILLEGAL_HEADER_VALUE_CHARACTER(VALUE) \
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, re); \
|
||||
QVERIFY(!h1.append(n1, VALUE)); \
|
||||
QVERIFY(h1.isEmpty()); \
|
||||
|
||||
void tst_QHttpHeaders::headerValueField()
|
||||
{
|
||||
QHttpHeaders h1;
|
||||
|
||||
// Visible ASCII characters and space and horizontal tab
|
||||
// const char[]
|
||||
h1.append(n1, "!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||||
"`abcdefghijklmnopqrstuvwxyz{|}~");
|
||||
QCOMPARE(h1.size(), 1);
|
||||
// UTF-8
|
||||
h1.append(n1, u8"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||||
"`abcdefghijklmnopqrstuvwxyz{|}~");
|
||||
QCOMPARE(h1.size(), 2);
|
||||
// UTF-16
|
||||
h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||||
"`abcdefghijklmnopqrstuvwxyz{|}~");
|
||||
QCOMPARE(h1.size(), 3);
|
||||
// QString / UTF-16
|
||||
h1.append(n1, u"!\"#$%&'()*+,-./0123456789:; \t<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||||
"`abcdefghijklmnopqrstuvwxyz{|}~"_s);
|
||||
QCOMPARE(h1.size(), 4);
|
||||
const auto values = h1.values(n1);
|
||||
QVERIFY(!values.isEmpty() && values.size() == 4);
|
||||
QVERIFY(values[0] == values[1]
|
||||
&& values[1] == values[2]
|
||||
&& values[2] == values[3]);
|
||||
// Extended ASCII (explicit on Latin-1 to avoid UTF-8 interpretation)
|
||||
h1.append(n1, "\x80\x09\xB2\xFF"_L1);
|
||||
QCOMPARE(h1.size(), 5);
|
||||
// Empty value
|
||||
h1.append(n1, "");
|
||||
QCOMPARE(h1.size(), 6);
|
||||
// Leading and trailing space
|
||||
h1.clear();
|
||||
h1.append(n1, " foo ");
|
||||
QCOMPARE(h1.combinedValue(n1), "foo");
|
||||
h1.append(n1, "\tbar\t");
|
||||
QCOMPARE(h1.combinedValue(n1), "foo,bar");
|
||||
QCOMPARE(h1.size(), 2);
|
||||
|
||||
h1.clear();
|
||||
QRegularExpression re("HTTP header value contained illegal character*");
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x08" "bar"); // BS
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER("foo\x1B" "bar"); // ESC
|
||||
// Disallowed UTF-8 characters
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"€");
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u8"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏");
|
||||
// Disallowed UTF-16 characters
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"€");
|
||||
TEST_ILLEGAL_HEADER_VALUE_CHARACTER(u"𝒜𝒴𝟘𝟡𐎀𐎜𐒀𐒐𝓐𝓩𝔸𝔹𝕀𝕁𝕌𝕍𓂀𓂁𓃀𓃁𓇋𓇌𓉐𓉑𓋴𓋵𓎡𓎢𓎣𓏏");
|
||||
|
||||
// Non-null-terminated value. The 'x' below is to make sure the strings don't
|
||||
// null-terminate by happenstance
|
||||
h1.clear();
|
||||
constexpr char L1Array[] = {'a','b','c','x'};
|
||||
const QLatin1StringView nonNullLatin1{L1Array, sizeof(L1Array) - 1}; // abc
|
||||
|
||||
constexpr char UTF8Array[] = {0x64, 0x65, 0x66, 0x78};
|
||||
const QUtf8StringView nonNullUTF8(UTF8Array, sizeof(UTF8Array) - 1); // def
|
||||
|
||||
constexpr QChar UTF16Array[] = {'g', 'h', 'i', 'x'};
|
||||
QStringView nonNullUTF16(UTF16Array, sizeof(UTF16Array) / sizeof(QChar) - 1); // ghi
|
||||
|
||||
h1.append(n1, nonNullLatin1);
|
||||
QCOMPARE(h1.size(), 1);
|
||||
QVERIFY(h1.has(n1));
|
||||
QCOMPARE(h1.combinedValue(n1), "abc");
|
||||
|
||||
h1.append(n2, nonNullUTF8);
|
||||
QCOMPARE(h1.size(), 2);
|
||||
QVERIFY(h1.has(n2));
|
||||
QCOMPARE(h1.combinedValue(n2), "def");
|
||||
|
||||
h1.append(n3, nonNullUTF16);
|
||||
QCOMPARE(h1.size(), 3);
|
||||
QVERIFY(h1.has(n3));
|
||||
QCOMPARE(h1.combinedValue(n3), "ghi");
|
||||
}
|
||||
|
||||
void tst_QHttpHeaders::valueEncoding()
|
||||
{
|
||||
// Test that common encodings are possible to set and not blocked by
|
||||
// header value character filter (ie. don't contain disallowed characters as per RFC 9110)
|
||||
QHttpHeaders h1;
|
||||
// Within visible ASCII range
|
||||
QVERIFY(h1.append(n1, "foo"_ba.toBase64()));
|
||||
QCOMPARE(h1.values(n1).at(0), "Zm9v");
|
||||
h1.replace(0, n1, "foo"_ba.toPercentEncoding());
|
||||
QCOMPARE(h1.values(n1).at(0), "foo");
|
||||
|
||||
// Outside of ASCII/Latin-1 range (€)
|
||||
h1.replace(0, n1, "foo€"_ba.toBase64());
|
||||
QCOMPARE(h1.values(n1).at(0), "Zm9v4oKs");
|
||||
h1.replace(0, n1, "foo€"_ba.toPercentEncoding());
|
||||
QCOMPARE(h1.values(n1).at(0), "foo%E2%82%AC");
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QHttpHeaders)
|
||||
#include "tst_qhttpheaders.moc"
|
||||
Loading…
Reference in New Issue