summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/http2/http2protocol_p.h2
-rw-r--r--src/network/access/qformdatabuilder.cpp363
-rw-r--r--src/network/access/qformdatabuilder.h117
-rw-r--r--src/network/access/qhttp2connection.cpp2
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp8
-rw-r--r--src/network/access/qhttpheaders.cpp52
-rw-r--r--src/network/access/qhttpmultipart.cpp8
-rw-r--r--src/network/access/qhttpmultipart_p.h15
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp67
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h7
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp147
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h5
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp10
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h4
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp36
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp40
-rw-r--r--src/network/access/qnetworkaccessmanager.h12
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp19
-rw-r--r--src/network/access/qnetworkreplywasmimpl.cpp1
-rw-r--r--src/network/access/qnetworkrequest.cpp13
-rw-r--r--src/network/access/qnetworkrequest.h1
-rw-r--r--src/network/access/qnetworkrequestfactory.cpp4
-rw-r--r--src/network/access/qnetworkrequestfactory.h2
-rw-r--r--src/network/access/qrestaccessmanager.cpp2
-rw-r--r--src/network/access/qrestaccessmanager.h2
-rw-r--r--src/network/access/qrestaccessmanager_p.h3
-rw-r--r--src/network/access/qrestreply.cpp4
-rw-r--r--src/network/access/qrestreply.h2
28 files changed, 772 insertions, 176 deletions
diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h
index fb5ff199c5..f0f18d1dd5 100644
--- a/src/network/access/http2/http2protocol_p.h
+++ b/src/network/access/http2/http2protocol_p.h
@@ -117,7 +117,7 @@ const qint32 maxSessionReceiveWindowSize((quint32(1) << 31) - 1);
// Presumably, we never use up to 100 streams so let it be 10 simultaneous:
const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / 10;
-struct Frame configurationToSettingsFrame(const QHttp2Configuration &configuration);
+struct Frame Q_AUTOTEST_EXPORT configurationToSettingsFrame(const QHttp2Configuration &configuration);
QByteArray settingsFrameToBase64(const Frame &settingsFrame);
void appendProtocolUpgradeHeaders(const QHttp2Configuration &configuration, QHttpNetworkRequest *request);
std::vector<uchar> assemble_hpack_block(const std::vector<Frame> &frames);
diff --git a/src/network/access/qformdatabuilder.cpp b/src/network/access/qformdatabuilder.cpp
new file mode 100644
index 0000000000..1d2054c956
--- /dev/null
+++ b/src/network/access/qformdatabuilder.cpp
@@ -0,0 +1,363 @@
+// Copyright (C) 2024 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
+
+#include "qformdatabuilder.h"
+
+#include <QtCore/private/qstringconverter_p.h>
+#if QT_CONFIG(mimetype)
+#include "QtCore/qmimedatabase.h"
+#endif
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QFormDataPartBuilder
+ \brief The QFormDataPartBuilder class is a convenience class to simplify
+ the construction of QHttpPart objects.
+ \since 6.8
+
+ \ingroup network
+ \ingroup shared
+ \inmodule QtNetwork
+
+ The QFormDataPartBuilder class can be used to build a QHttpPart object with
+ the content disposition header set to be form-data by default. Then the
+ generated object can be used as part of a multipart message (which is
+ represented by the QHttpMultiPart class).
+
+ \sa QHttpPart, QHttpMultiPart, QFormDataBuilder
+*/
+
+static QByteArray nameToByteArray(QStringView view)
+{
+ return view.toUtf8();
+}
+
+static QByteArray nameToByteArray(QLatin1StringView view)
+{
+ if (!QtPrivate::isAscii(view))
+ return view.toString().toUtf8(); // ### optimize
+
+ return QByteArray::fromRawData(view.data(), view.size());
+}
+
+static QByteArray nameToByteArray(QUtf8StringView view)
+{
+ return QByteArray::fromRawData(view.data(), view.size());
+}
+
+static void escapeNameAndAppend(QByteArray &dst, QByteArrayView src)
+{
+ for (auto c : src) {
+ if (c == '"' || c == '\\')
+ dst += '\\';
+ dst += c;
+ }
+}
+
+/*!
+ Constructs a QFormDataPartBuilder object and sets \a name as the name
+ parameter of the form-data.
+*/
+QFormDataPartBuilder::QFormDataPartBuilder(QAnyStringView name, PrivateConstructor /*unused*/)
+{
+ static_assert(std::is_nothrow_move_constructible_v<decltype(m_body)>);
+ static_assert(std::is_nothrow_move_assignable_v<decltype(m_body)>);
+
+ const auto enc = name.visit([](auto name) { return nameToByteArray(name); });
+
+ m_headerValue += "form-data; name=\"";
+ escapeNameAndAppend(m_headerValue, enc);
+ m_headerValue += "\"";
+}
+
+/*!
+ \fn QFormDataPartBuilder::QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept
+
+ Move-constructs a QFormDataPartBuilder instance, making it point at the same
+ object that \a other was pointing to.
+*/
+
+/*!
+ \fn QFormDataPartBuilder &QFormDataPartBuilder::operator=(QFormDataPartBuilder &&other)
+
+ Move-assigns \a other to this QFormDataPartBuilder instance.
+*/
+
+/*!
+ Destroys the QFormDataPartBuilder object.
+*/
+
+QFormDataPartBuilder::~QFormDataPartBuilder()
+ = default;
+
+static void convertInto_impl(QByteArray &dst, QUtf8StringView in)
+{
+ dst.clear();
+ dst += QByteArrayView{in}; // it's ASCII, anyway
+}
+
+static void convertInto_impl(QByteArray &dst, QLatin1StringView in)
+{
+ dst.clear();
+ dst += QByteArrayView{in}; // it's ASCII, anyway
+}
+
+static void convertInto_impl(QByteArray &dst, QStringView in)
+{
+ dst.resize(in.size());
+ (void)QLatin1::convertFromUnicode(dst.data(), in);
+}
+
+static void convertInto(QByteArray &dst, QAnyStringView in)
+{
+ in.visit([&dst](auto in) { convertInto_impl(dst, in); });
+}
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBodyHelper(const QByteArray &data,
+ QAnyStringView name,
+ QAnyStringView mimeType)
+{
+ m_originalBodyName = name.toString();
+ convertInto(m_mimeType, mimeType);
+ m_body = data;
+ return *this;
+}
+
+/*!
+ Sets \a data as the body of this MIME part and, if given, \a fileName as the
+ file name parameter in the content disposition header.
+
+ If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
+ auto-detect the mime-type of \a data using QMimeDatabase.
+
+ A subsequent call to setBodyDevice() discards the body and the device will
+ be used instead.
+
+ For a large amount of data (e.g. an image), setBodyDevice() is preferred,
+ which will not copy the data internally.
+
+ \sa setBodyDevice()
+*/
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBody(QByteArrayView data,
+ QAnyStringView fileName,
+ QAnyStringView mimeType)
+{
+ return setBody(data.toByteArray(), fileName, mimeType);
+}
+
+/*!
+ Sets \a body as the body device of this part and \a fileName as the file
+ name parameter in the content disposition header.
+
+ If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
+ auto-detect the mime-type of \a body using QMimeDatabase.
+
+ A subsequent call to setBody() discards the body device and the data set by
+ setBody() will be used instead.
+
+ For large amounts of data this method should be preferred over setBody(),
+ because the content is not copied when using this method, but read
+ directly from the device.
+
+ \a body must be open and readable. QFormDataPartBuilder does not take
+ ownership of \a body, i.e. the device must be closed and destroyed if
+ necessary.
+
+ \sa setBody(), QHttpPart::setBodyDevice()
+ */
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBodyDevice(QIODevice *body, QAnyStringView fileName,
+ QAnyStringView mimeType)
+{
+ m_originalBodyName = fileName.toString();
+ convertInto(m_mimeType, mimeType);
+ m_body = body;
+ return *this;
+}
+
+/*!
+ Sets the headers specified in \a headers.
+
+ \note The "content-type" and "content-disposition" headers, if any are
+ specified in \a headers, will be overwritten by the class.
+*/
+
+QFormDataPartBuilder &QFormDataPartBuilder::setHeaders(const QHttpHeaders &headers)
+{
+ m_httpHeaders = headers;
+ return *this;
+}
+
+/*!
+ \internal
+
+ Generates a QHttpPart and sets the content disposition header as form-data.
+
+ When this function called, it uses the MIME database to deduce the type the
+ body based on its name and then sets the deduced type as the content type
+ header.
+*/
+
+QHttpPart QFormDataPartBuilder::build()
+{
+ QHttpPart httpPart;
+
+ if (!m_originalBodyName.isNull()) {
+ const bool utf8 = !QtPrivate::isAscii(m_originalBodyName);
+ const auto enc = utf8 ? m_originalBodyName.toUtf8() : m_originalBodyName.toLatin1();
+ m_headerValue += "; filename=\"";
+ escapeNameAndAppend(m_headerValue, enc);
+ m_headerValue += "\"";
+ if (utf8) {
+ // For 'filename*' production see
+ // https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1
+ // For providing both filename and filename* parameters see
+ // https://datatracker.ietf.org/doc/html/rfc6266#section-4.3 and
+ // https://datatracker.ietf.org/doc/html/rfc8187#section-4.2
+ m_headerValue += "; filename*=UTF-8''" + enc.toPercentEncoding();
+ }
+ }
+
+#if QT_CONFIG(mimetype)
+ if (m_mimeType.isEmpty()) {
+ // auto-detect
+ QMimeDatabase db;
+ convertInto(m_mimeType, std::visit([&](auto &arg) {
+ return db.mimeTypeForFileNameAndData(m_originalBodyName, arg);
+ }, m_body).name());
+ }
+#endif
+
+ for (qsizetype i = 0; i < m_httpHeaders.size(); i++) {
+ httpPart.setRawHeader(QByteArrayView(m_httpHeaders.nameAt(i)).toByteArray(),
+ m_httpHeaders.valueAt(i).toByteArray());
+ }
+
+ if (!m_mimeType.isEmpty())
+ httpPart.setHeader(QNetworkRequest::ContentTypeHeader, m_mimeType);
+
+ httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, m_headerValue);
+
+ if (auto d = std::get_if<QIODevice*>(&m_body))
+ httpPart.setBodyDevice(*d);
+ else if (auto b = std::get_if<QByteArray>(&m_body))
+ httpPart.setBody(*b);
+ else
+ Q_UNREACHABLE();
+
+ return httpPart;
+}
+
+/*!
+ \class QFormDataBuilder
+ \brief The QFormDataBuilder class is a convenience class to simplify
+ the construction of QHttpMultiPart objects.
+ \since 6.8
+
+ \ingroup network
+ \ingroup shared
+ \inmodule QtNetwork
+
+ The QFormDataBuilder class can be used to build a QHttpMultiPart object
+ with the content type set to be FormDataType by default.
+
+ The snippet below demonstrates how to build a multipart message with
+ QFormDataBuilder:
+
+ \code
+ QFormDataBuilder builder;
+ QFile image(u"../../pic.png"_s); image.open(QFile::ReadOnly);
+ QFile mask(u"../../mask.png"_s); mask.open(QFile::ReadOnly);
+
+ builder.part("image"_L1).setBodyDevice(&image, "the actual image");
+ builder.part("mask"_L1).setBodyDevice(&mask, "the mask image");
+ builder.part("prompt"_L1).setBody("Lobster wearing a beret");
+ builder.part("n"_L1).setBody("2");
+ builder.part("size"_L1).setBody("512x512");
+
+ std::unique_ptr<QHttpMultiPart> mp = builder.buildMultiPart();
+ \endcode
+
+ \sa QHttpPart, QHttpMultiPart, QFormDataPartBuilder
+*/
+
+class QFormDataBuilderPrivate
+{
+public:
+ std::vector<QFormDataPartBuilder> parts;
+};
+
+/*!
+ Constructs an empty QFormDataBuilder object.
+*/
+
+QFormDataBuilder::QFormDataBuilder()
+ : d_ptr(new QFormDataBuilderPrivate())
+{
+
+}
+
+/*!
+ Destroys the QFormDataBuilder object.
+*/
+
+QFormDataBuilder::~QFormDataBuilder()
+{
+ delete d_ptr;
+}
+
+/*!
+ \fn QFormDataBuilder::QFormDataBuilder(QFormDataBuilder &&other) noexcept
+
+ Move-constructs a QFormDataBuilder instance, making it point at the same
+ object that \a other was pointing to.
+*/
+
+/*!
+ \fn QFormDataBuilder &QFormDataBuilder::operator=(QFormDataBuilder &&other) noexcept
+
+ Move-assigns \a other to this QFormDataBuilder instance.
+*/
+
+/*!
+ Constructs and returns a reference to a QFormDataPartBuilder object and sets
+ \a name as the name parameter of the form-data. The returned reference is
+ valid until the next call to this function.
+
+ Limiting \a name characters to US-ASCII is
+ \l {https://datatracker.ietf.org/doc/html/rfc7578#section-5.1.1}{strongly recommended}
+ for interoperability reasons.
+
+ \sa QFormDataPartBuilder, QHttpPart
+*/
+
+QFormDataPartBuilder &QFormDataBuilder::part(QAnyStringView name)
+{
+ Q_D(QFormDataBuilder);
+
+ return d->parts.emplace_back(name, QFormDataPartBuilder::PrivateConstructor());
+}
+
+/*!
+ Constructs and returns a pointer to a QHttpMultipart object.
+
+ \sa QHttpMultiPart
+*/
+
+std::unique_ptr<QHttpMultiPart> QFormDataBuilder::buildMultiPart()
+{
+ Q_D(QFormDataBuilder);
+
+ auto multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType);
+
+ for (auto &part : d->parts)
+ multiPart->append(part.build());
+
+ return multiPart;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qformdatabuilder.h b/src/network/access/qformdatabuilder.h
new file mode 100644
index 0000000000..4305ff8cb7
--- /dev/null
+++ b/src/network/access/qformdatabuilder.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2024 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 QFORMDATABUILDER_H
+#define QFORMDATABUILDER_H
+
+#include <QtNetwork/qtnetworkglobal.h>
+#include <QtNetwork/qhttpheaders.h>
+#include <QtNetwork/qhttpmultipart.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qstring.h>
+
+#include <memory>
+#include <variant>
+
+#ifndef Q_OS_WASM
+QT_REQUIRE_CONFIG(http);
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QHttpPartPrivate;
+class QHttpMultiPart;
+class QDebug;
+
+class QFormDataBuilderPrivate;
+
+class QFormDataPartBuilder
+{
+ struct PrivateConstructor { explicit PrivateConstructor() = default; };
+public:
+ Q_NETWORK_EXPORT explicit QFormDataPartBuilder(QAnyStringView name, PrivateConstructor);
+
+ QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept
+ : m_headerValue(std::move(other.m_headerValue)),
+ m_originalBodyName(std::move(other.m_originalBodyName)),
+ m_httpHeaders(std::move(other.m_httpHeaders)),
+ m_body(std::move(other.m_body)),
+ m_reserved(std::exchange(other.m_reserved, nullptr))
+ {
+
+ }
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QFormDataPartBuilder)
+ void swap(QFormDataPartBuilder &other) noexcept
+ {
+ m_headerValue.swap(other.m_headerValue);
+ m_originalBodyName.swap(other.m_originalBodyName);
+ m_httpHeaders.swap(other.m_httpHeaders);
+ m_body.swap(other.m_body);
+ qt_ptr_swap(m_reserved, other.m_reserved);
+ }
+
+ Q_NETWORK_EXPORT ~QFormDataPartBuilder();
+
+ Q_WEAK_OVERLOAD QFormDataPartBuilder &setBody(const QByteArray &data,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {})
+ { return setBodyHelper(data, fileName, mimeType); }
+
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBody(QByteArrayView data,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {});
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBodyDevice(QIODevice *body,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {});
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setHeaders(const QHttpHeaders &headers);
+private:
+ Q_DISABLE_COPY(QFormDataPartBuilder)
+
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBodyHelper(const QByteArray &data,
+ QAnyStringView fileName,
+ QAnyStringView mimeType);
+ QHttpPart build();
+
+ QByteArray m_headerValue;
+ QByteArray m_mimeType;
+ QString m_originalBodyName;
+ QHttpHeaders m_httpHeaders;
+ std::variant<QIODevice*, QByteArray> m_body;
+ void *m_reserved = nullptr;
+
+ friend class QFormDataBuilder;
+ friend void swap(QFormDataPartBuilder &lhs, QFormDataPartBuilder &rhs) noexcept
+ { lhs.swap(rhs); }
+};
+
+class QFormDataBuilder
+{
+public:
+ Q_NETWORK_EXPORT QFormDataBuilder();
+
+ QFormDataBuilder(QFormDataBuilder &&other) noexcept : d_ptr(std::exchange(other.d_ptr, nullptr)) {}
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QFormDataBuilder)
+ void swap(QFormDataBuilder &other) noexcept
+ {
+ qt_ptr_swap(d_ptr, other.d_ptr);
+ }
+
+ Q_NETWORK_EXPORT ~QFormDataBuilder();
+ Q_NETWORK_EXPORT QFormDataPartBuilder &part(QAnyStringView name);
+ Q_NETWORK_EXPORT std::unique_ptr<QHttpMultiPart> buildMultiPart();
+private:
+ QFormDataBuilderPrivate *d_ptr;
+
+ Q_DECLARE_PRIVATE(QFormDataBuilder)
+ Q_DISABLE_COPY(QFormDataBuilder)
+};
+
+Q_DECLARE_SHARED(QFormDataBuilder)
+
+QT_END_NAMESPACE
+
+#endif // QFORMDATABUILDER_H
diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp
index 8560e0da38..2d92684863 100644
--- a/src/network/access/qhttp2connection.cpp
+++ b/src/network/access/qhttp2connection.cpp
@@ -17,7 +17,7 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(qHttp2ConnectionLog, "qt.network.http2.connection", QtCriticalMsg)
+Q_STATIC_LOGGING_CATEGORY(qHttp2ConnectionLog, "qt.network.http2.connection", QtCriticalMsg)
using namespace Qt::StringLiterals;
using namespace Http2;
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index d9341dc643..3d55cee1eb 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -1341,8 +1341,8 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, b
replyPrivate->connectionChannel = m_channel;
reply->setHttp2WasUsed(true);
streamIDs.insert(reply, newStreamID);
- connect(reply, SIGNAL(destroyed(QObject*)),
- this, SLOT(_q_replyDestroyed(QObject*)));
+ connect(reply, &QHttpNetworkReply::destroyed,
+ this, &QHttp2ProtocolHandler::_q_replyDestroyed);
const Stream newStream(message, newStreamID,
streamInitialSendWindowSize,
@@ -1350,8 +1350,8 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, b
if (!uploadDone) {
if (auto src = newStream.data()) {
- connect(src, SIGNAL(readyRead()), this,
- SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
+ connect(src, &QNonContiguousByteDevice::readyRead, this,
+ &QHttp2ProtocolHandler::_q_uploadDataReadyRead, Qt::QueuedConnection);
connect(src, &QHttp2ProtocolHandler::destroyed,
this, &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
streamIDs.insert(src, newStreamID);
diff --git a/src/network/access/qhttpheaders.cpp b/src/network/access/qhttpheaders.cpp
index c63da899a8..a4ec7b422d 100644
--- a/src/network/access/qhttpheaders.cpp
+++ b/src/network/access/qhttpheaders.cpp
@@ -18,7 +18,7 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcQHttpHeaders, "qt.network.http.headers");
+Q_STATIC_LOGGING_CATEGORY(lcQHttpHeaders, "qt.network.http.headers");
/*!
\class QHttpHeaders
@@ -988,50 +988,6 @@ QDebug operator<<(QDebug debug, const QHttpHeaders &headers)
}
#endif
-// A clarification on string encoding:
-// Setters and getters only accept names and values that are Latin-1 representable:
-// Either they are directly ASCII/Latin-1, or if they are UTF-X, they only use first 256
-// of the unicode points. For example using a '€' (U+20AC) in value would yield a warning
-// and the call is ignored.
-// Furthermore the 'name' has more strict rules than the 'value'
-
-// TODO FIXME REMOVEME once this is merged:
-// https://codereview.qt-project.org/c/qt/qtbase/+/508829
-static bool isUtf8Latin1Representable(QUtf8StringView s) noexcept
-{
- // L1 encoded in UTF8 has at most the form
- // - 0b0XXX'XXXX - US-ASCII
- // - 0b1100'00XX 0b10XX'XXXX - at most 8 non-zero LSB bits allowed in L1
- bool inMultibyte = false;
- for (unsigned char c : s) {
- if (c < 128) { // US-ASCII
- if (inMultibyte)
- return false; // invalid sequence
- } else {
- // decode as UTF-8:
- if ((c & 0b1110'0000) == 0b1100'0000) { // two-octet UTF-8 leader
- if (inMultibyte)
- return false; // invalid sequence
- inMultibyte = true;
- const auto bits_7_to_11 = c & 0b0001'1111;
- if (bits_7_to_11 < 0b10)
- return false; // invalid sequence (US-ASCII encoded in two octets)
- if (bits_7_to_11 > 0b11) // more than the two LSB
- return false; // outside L1
- } else if ((c & 0b1100'0000) == 0b1000'0000) { // trailing UTF-8 octet
- if (!inMultibyte)
- return false; // invalid sequence
- inMultibyte = false; // only one continuation allowed
- } else {
- return false; // invalid sequence or outside of L1
- }
- }
- }
- if (inMultibyte)
- return false; // invalid sequence: premature end
- return true;
-}
-
static constexpr auto isValidHttpHeaderNameChar = [](uchar c) noexcept
{
// RFC 9110 Chapters "5.1 Field Names" and "5.6.2 Tokens"
@@ -1106,8 +1062,10 @@ static bool headerValueValidImpl(QLatin1StringView value) noexcept
static bool headerValueValidImpl(QUtf8StringView value) noexcept
{
- if (!isUtf8Latin1Representable(value)) // TODO FIXME see the function
- return false;
+ // UTF-8 byte sequences are also used as values directly
+ // => allow them as such. UTF-8 byte sequences for characters
+ // outside of ASCII should all fit into obs-text (>= 0x80)
+ // (see isValidHttpHeaderValueChar)
return std::all_of(value.begin(), value.end(), isValidHttpHeaderValueChar);
}
diff --git a/src/network/access/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp
index a695969f00..711d89544c 100644
--- a/src/network/access/qhttpmultipart.cpp
+++ b/src/network/access/qhttpmultipart.cpp
@@ -409,6 +409,14 @@ QHttpMultiPartPrivate::QHttpMultiPartPrivate() : contentType(QHttpMultiPart::Mix
Q_ASSERT(boundary.size() <= 70);
}
+QHttpMultiPartPrivate::~QHttpMultiPartPrivate()
+{
+ delete device;
+}
+
+QHttpMultiPartIODevice::~QHttpMultiPartIODevice()
+ = default;
+
qint64 QHttpMultiPartIODevice::size() const
{
// if not done yet, we calculate the size and the offsets of each part,
diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h
index 7a12ce8424..39c147d2bc 100644
--- a/src/network/access/qhttpmultipart_p.h
+++ b/src/network/access/qhttpmultipart_p.h
@@ -16,10 +16,14 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QtNetwork/qhttpmultipart.h>
+
#include "QtCore/qshareddata.h"
#include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate
#include "qhttpheadershelper_p.h"
+
#include "private/qobject_p.h"
+#include <QtCore/qiodevice.h>
#ifndef Q_OS_WASM
QT_REQUIRE_CONFIG(http);
@@ -92,8 +96,7 @@ public:
QIODevice(), multiPart(parentMultiPart), readPointer(0), deviceSize(-1) {
}
- ~QHttpMultiPartIODevice() {
- }
+ ~QHttpMultiPartIODevice() override;
virtual bool atEnd() const override {
return readPointer == size();
@@ -128,15 +131,17 @@ public:
-class QHttpMultiPartPrivate: public QObjectPrivate
+class Q_AUTOTEST_EXPORT QHttpMultiPartPrivate: public QObjectPrivate
{
public:
QHttpMultiPartPrivate();
+ ~QHttpMultiPartPrivate() override;
- ~QHttpMultiPartPrivate()
+ static QHttpMultiPartPrivate *get(QHttpMultiPart *message) { return message->d_func(); }
+ static const QHttpMultiPartPrivate *get(const QHttpMultiPart *message)
{
- delete device;
+ return message->d_func();
}
QList<QHttpPart> parts;
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 419491a711..3ef07c6993 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -52,10 +52,11 @@ static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
- QHttpNetworkConnection::ConnectionType type)
+ bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
: hostName(hostName),
port(port),
encrypt(encrypt),
+ isLocalSocket(isLocalSocket),
activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
channelCount(connectionCount),
channels(new QHttpNetworkConnectionChannel[channelCount]),
@@ -64,6 +65,8 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
#endif
connectionType(type)
{
+ if (isLocalSocket) // Don't try to do host lookup for local sockets
+ networkLayerState = IPv4;
// We allocate all 6 channels even if it's an HTTP/2-enabled
// connection: in case the protocol negotiation via NPN/ALPN fails,
// we will have normally working HTTP/1.1.
@@ -102,13 +105,18 @@ void QHttpNetworkConnectionPrivate::pauseConnection()
// Disable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
- if (channels[i].socket) {
+ if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
- QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
- QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket);
+ QAbstractSocketPrivate::pauseSocketNotifiers(absSocket);
+ } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
+ // @todo how would we do this?
+#if 0 // @todo Enable this when there is a debug category for this
+ qDebug() << "Should pause socket but there is no way to do it for local sockets";
+#endif
}
}
}
@@ -118,17 +126,21 @@ void QHttpNetworkConnectionPrivate::resumeConnection()
state = RunningState;
// Enable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
- if (channels[i].socket) {
+ if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
- QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
- QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket);
+ QAbstractSocketPrivate::resumeSocketNotifiers(absSocket);
// Resume pending upload if needed
if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
+ } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
+#if 0 // @todo Enable this when there is a debug category for this
+ qDebug() << "Should resume socket but there is no way to do it for local sockets";
+#endif
}
}
@@ -292,7 +304,12 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
- if (value.isEmpty()) {
+ if (isLocalSocket && value.isEmpty()) {
+ // The local socket connections might have a full file path, and that
+ // may not be suitable for the Host header. But we can use whatever the
+ // user has set in the URL.
+ request.prependHeaderField("Host", request.url().host().toLocal8Bit());
+ } else if (value.isEmpty()) {
QHostAddress add;
QByteArray host;
if (add.setAddress(hostName)) {
@@ -524,7 +541,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
// Check redirect url protocol
const QUrl priorUrl(reply->request().url());
- if (redirectUrl.scheme() == "http"_L1 || redirectUrl.scheme() == "https"_L1) {
+ const QString targetUrlScheme = redirectUrl.scheme();
+ if (targetUrlScheme == "http"_L1 || targetUrlScheme == "https"_L1
+ || targetUrlScheme.startsWith("unix"_L1)) {
switch (reply->request().redirectPolicy()) {
case QNetworkRequest::NoLessSafeRedirectPolicy:
// Here we could handle https->http redirects as InsecureProtocolError.
@@ -535,7 +554,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
break;
case QNetworkRequest::SameOriginRedirectPolicy:
if (priorUrl.host() != redirectUrl.host()
- || priorUrl.scheme() != redirectUrl.scheme()
+ || priorUrl.scheme() != targetUrlScheme
|| priorUrl.port() != redirectUrl.port()) {
return {{}, QNetworkReply::InsecureRedirectError};
}
@@ -1024,7 +1043,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
if (!channels[i].socket
- || channels[i].socket->state() == QAbstractSocket::UnconnectedState) {
+ || QSocketAbstraction::socketState(channels[i].socket) == QAbstractSocket::UnconnectedState) {
if (!channels[i].ensureConnection())
continue;
}
@@ -1048,7 +1067,9 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// try to get a free AND connected socket
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].socket) {
- if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
+ if (!channels[i].reply && !channels[i].isSocketBusy()
+ && QSocketAbstraction::socketState(channels[i].socket)
+ == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
@@ -1068,7 +1089,8 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
else if (networkLayerState == IPv6)
channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[0].ensureConnection();
- if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
+ if (auto *s = channels[0].socket; s
+ && QSocketAbstraction::socketState(s) == QAbstractSocket::ConnectedState
&& !channels[0].pendingEncrypt) {
if (channels[0].h2RequestsToSend.size()) {
channels[0].sendRequest();
@@ -1095,9 +1117,13 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
- for (int i = 0; i < activeChannelCount; i++)
- if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState)
+ for (int i = 0; i < activeChannelCount; i++) {
+ if (channels[i].socket
+ && QSocketAbstraction::socketState(channels[i].socket)
+ == QAbstractSocket::ConnectedState) {
fillPipeline(channels[i].socket);
+ }
+ }
// If there is not already any connected channels we need to connect a new one.
// We do not pair the channel with the request until we know if it is
@@ -1122,15 +1148,16 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
if (!channels[i].socket)
continue;
- if ((channels[i].socket->state() == QAbstractSocket::ConnectingState)
- || (channels[i].socket->state() == QAbstractSocket::HostLookupState)
+ using State = QAbstractSocket::SocketState;
+ if ((QSocketAbstraction::socketState(channels[i].socket) == State::ConnectingState)
+ || (QSocketAbstraction::socketState(channels[i].socket) == State::HostLookupState)
|| channels[i].pendingEncrypt) { // pendingEncrypt == "EncryptingState"
neededOpenChannels--;
continue;
}
if (!channels[i].reply && !channels[i].isSocketBusy()
- && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) {
+ && (QSocketAbstraction::socketState(channels[i].socket) == State::UnconnectedState)) {
channelsToConnect.push_back(i);
neededOpenChannels--;
}
@@ -1329,9 +1356,9 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
}
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
- quint16 port, bool encrypt, QObject *parent,
+ quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
- : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
+ : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index c2d062fb16..5e4bce5eb0 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -64,7 +64,8 @@ public:
};
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80,
- bool encrypt = false, QObject *parent = nullptr,
+ bool encrypt = false, bool isLocalSocket = false,
+ QObject *parent = nullptr,
ConnectionType connectionType = ConnectionTypeHTTP);
~QHttpNetworkConnection();
@@ -155,7 +156,8 @@ public:
};
QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port,
- bool encrypt, QHttpNetworkConnection::ConnectionType type);
+ bool encrypt, bool isLocalSocket,
+ QHttpNetworkConnection::ConnectionType type);
~QHttpNetworkConnectionPrivate();
void init();
@@ -205,6 +207,7 @@ public:
QString hostName;
quint16 port;
bool encrypt;
+ bool isLocalSocket;
bool delayIpv4 = true;
// Number of channels we are trying to use at the moment:
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index e178d65356..8688e4b8d7 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -12,6 +12,7 @@
#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/http2protocol_p.h>
+#include <private/qsocketabstraction_p.h>
#ifndef QT_NO_SSL
# include <private/qsslsocket_p.h>
@@ -78,6 +79,8 @@ void QHttpNetworkConnectionChannel::init()
#ifndef QT_NO_SSL
if (connection->d_func()->encrypt)
socket = new QSslSocket;
+ else if (connection->d_func()->isLocalSocket)
+ socket = new QLocalSocket;
else
socket = new QTcpSocket;
#else
@@ -85,7 +88,8 @@ void QHttpNetworkConnectionChannel::init()
#endif
#ifndef QT_NO_NETWORKPROXY
// Set by QNAM anyway, but let's be safe here
- socket->setProxy(QNetworkProxy::NoProxy);
+ if (auto s = qobject_cast<QAbstractSocket *>(socket))
+ s->setProxy(QNetworkProxy::NoProxy);
#endif
// After some back and forth in all the last years, this is now a DirectConnection because otherwise
@@ -94,32 +98,48 @@ void QHttpNetworkConnectionChannel::init()
QObject::connect(socket, &QIODevice::bytesWritten,
this, &QHttpNetworkConnectionChannel::_q_bytesWritten,
Qt::DirectConnection);
- QObject::connect(socket, &QAbstractSocket::connected,
- this, &QHttpNetworkConnectionChannel::_q_connected,
- Qt::DirectConnection);
QObject::connect(socket, &QIODevice::readyRead,
this, &QHttpNetworkConnectionChannel::_q_readyRead,
Qt::DirectConnection);
- // The disconnected() and error() signals may already come
- // while calling connectToHost().
- // In case of a cached hostname or an IP this
- // will then emit a signal to the user of QNetworkReply
- // but cannot be caught because the user did not have a chance yet
- // to connect to QNetworkReply's signals.
- qRegisterMetaType<QAbstractSocket::SocketError>();
- QObject::connect(socket, &QAbstractSocket::disconnected,
- this, &QHttpNetworkConnectionChannel::_q_disconnected,
- Qt::DirectConnection);
- QObject::connect(socket, &QAbstractSocket::errorOccurred,
- this, &QHttpNetworkConnectionChannel::_q_error,
- Qt::DirectConnection);
+
+ QSocketAbstraction::visit([this](auto *socket){
+ using SocketType = std::remove_pointer_t<decltype(socket)>;
+ QObject::connect(socket, &SocketType::connected,
+ this, &QHttpNetworkConnectionChannel::_q_connected,
+ Qt::DirectConnection);
+
+ // The disconnected() and error() signals may already come
+ // while calling connectToHost().
+ // In case of a cached hostname or an IP this
+ // will then emit a signal to the user of QNetworkReply
+ // but cannot be caught because the user did not have a chance yet
+ // to connect to QNetworkReply's signals.
+ QObject::connect(socket, &SocketType::disconnected,
+ this, &QHttpNetworkConnectionChannel::_q_disconnected,
+ Qt::DirectConnection);
+ if constexpr (std::is_same_v<SocketType, QAbstractSocket>) {
+ QObject::connect(socket, &QAbstractSocket::errorOccurred,
+ this, &QHttpNetworkConnectionChannel::_q_error,
+ Qt::DirectConnection);
+ } else if constexpr (std::is_same_v<SocketType, QLocalSocket>) {
+ auto convertAndForward = [this](QLocalSocket::LocalSocketError error) {
+ _q_error(static_cast<QAbstractSocket::SocketError>(error));
+ };
+ QObject::connect(socket, &SocketType::errorOccurred,
+ this, std::move(convertAndForward),
+ Qt::DirectConnection);
+ }
+ }, socket);
+
#ifndef QT_NO_NETWORKPROXY
- QObject::connect(socket, &QAbstractSocket::proxyAuthenticationRequired,
- this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired,
- Qt::DirectConnection);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket)) {
+ QObject::connect(s, &QAbstractSocket::proxyAuthenticationRequired,
+ this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired,
+ Qt::DirectConnection);
+ }
#endif
#ifndef QT_NO_SSL
@@ -156,8 +176,10 @@ void QHttpNetworkConnectionChannel::init()
#endif
#ifndef QT_NO_NETWORKPROXY
- if (proxy.type() != QNetworkProxy::NoProxy)
- socket->setProxy(proxy);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket);
+ s && proxy.type() != QNetworkProxy::NoProxy) {
+ s->setProxy(proxy);
+ }
#endif
isInitialized = true;
}
@@ -170,7 +192,7 @@ void QHttpNetworkConnectionChannel::close()
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
@@ -190,7 +212,7 @@ void QHttpNetworkConnectionChannel::abort()
{
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
@@ -201,7 +223,10 @@ void QHttpNetworkConnectionChannel::abort()
if (socket) {
// socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
// there is no socket yet.
- socket->abort();
+ auto callAbort = [](auto *s) {
+ s->abort();
+ };
+ QSocketAbstraction::visit(callAbort, socket);
}
}
@@ -268,7 +293,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
if (!isInitialized)
init();
- QAbstractSocket::SocketState socketState = socket->state();
+ QAbstractSocket::SocketState socketState = QSocketAbstraction::socketState(socket);
// resend this request after we receive the disconnected signal
// If !socket->isOpen() then we have already called close() on the socket, but there was still a
@@ -335,7 +360,8 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
connectHost = connection->d_func()->networkProxy.hostName();
connectPort = connection->d_func()->networkProxy.port();
}
- if (socket->proxy().type() == QNetworkProxy::HttpProxy) {
+ if (auto *abSocket = qobject_cast<QAbstractSocket *>(socket);
+ abSocket && abSocket->proxy().type() == QNetworkProxy::HttpProxy) {
// Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
QByteArray value;
// ensureConnection is called before any request has been assigned, but can also be
@@ -353,11 +379,11 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
value = request.headerField("user-agent");
}
if (!value.isEmpty()) {
- QNetworkProxy proxy(socket->proxy());
+ QNetworkProxy proxy(abSocket->proxy());
auto h = proxy.headers();
h.replaceOrAppend(QHttpHeaders::WellKnownHeader::UserAgent, value);
proxy.setHeaders(std::move(h));
- socket->setProxy(proxy);
+ abSocket->setProxy(proxy);
}
}
#endif
@@ -380,7 +406,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
// limit the socket read buffer size. we will read everything into
// the QHttpNetworkReply anyway, so let's grow only that and not
// here and there.
- socket->setReadBufferSize(64*1024);
+ sslSocket->setReadBufferSize(64*1024);
#else
// Need to dequeue the request so that we can emit the error.
if (!reply)
@@ -394,17 +420,24 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
&& connection->cacheProxy().type() == QNetworkProxy::NoProxy
&& connection->transparentProxy().type() == QNetworkProxy::NoProxy) {
#endif
- socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered, networkLayerPreference);
- // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
- socket->setReadBufferSize(1*1024);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket)) {
+ s->connectToHost(connectHost, connectPort,
+ QIODevice::ReadWrite | QIODevice::Unbuffered,
+ networkLayerPreference);
+ // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
+ s->setReadBufferSize(1 * 1024);
+ } else if (auto *s = qobject_cast<QLocalSocket *>(socket)) {
+ s->connectToServer(connectHost);
+ }
#ifndef QT_NO_NETWORKPROXY
} else {
- socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
-
+ auto *s = qobject_cast<QAbstractSocket *>(socket);
+ Q_ASSERT(s);
// limit the socket read buffer size. we will read everything into
// the QHttpNetworkReply anyway, so let's grow only that and not
// here and there.
- socket->setReadBufferSize(64*1024);
+ s->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
+ s->setReadBufferSize(64 * 1024);
}
#endif
}
@@ -507,7 +540,7 @@ void QHttpNetworkConnectionChannel::allDone()
// move next from pipeline to current request
if (!alreadyPipelinedRequests.isEmpty()) {
- if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
+ if (resendCurrent || connectionCloseEnabled || QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState) {
// move the pipelined ones back to the main queue
requeueCurrentlyPipelinedRequests();
close();
@@ -538,7 +571,7 @@ void QHttpNetworkConnectionChannel::allDone()
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
} else if (alreadyPipelinedRequests.isEmpty()) {
if (connectionCloseEnabled)
- if (socket->state() != QAbstractSocket::UnconnectedState)
+ if (QSocketAbstraction::socketState(socket) != QAbstractSocket::UnconnectedState)
close();
if (qobject_cast<QHttpNetworkConnection*>(connection))
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
@@ -556,7 +589,7 @@ void QHttpNetworkConnectionChannel::detectPipeliningSupport()
// check for not having connection close
&& (!reply->d_func()->isConnectionCloseEnabled())
// check if it is still connected
- && (socket->state() == QAbstractSocket::ConnectedState)
+ && (QSocketAbstraction::socketState(socket) == QAbstractSocket::ConnectedState)
// check for broken servers in server reply header
// this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
&& (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
@@ -679,8 +712,8 @@ bool QHttpNetworkConnectionChannel::resetUploadData()
void QHttpNetworkConnectionChannel::setProxy(const QNetworkProxy &networkProxy)
{
- if (socket)
- socket->setProxy(networkProxy);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket))
+ s->setProxy(networkProxy);
proxy = networkProxy;
}
@@ -841,7 +874,7 @@ void QHttpNetworkConnectionChannel::_q_disconnected()
}
-void QHttpNetworkConnectionChannel::_q_connected()
+void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket *absSocket)
{
// For the Happy Eyeballs we need to check if this is the first channel to connect.
if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) {
@@ -852,7 +885,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
else if (networkLayerPreference == QAbstractSocket::IPv6Protocol)
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
else {
- if (socket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
+ if (absSocket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
else
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
@@ -875,7 +908,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
}
// improve performance since we get the request sent by the kernel ASAP
- //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+ //absSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
// We have this commented out now. It did not have the effect we wanted. If we want to
// do this properly, Qt has to combine multiple HTTP requests into one buffer
// and send this to the kernel in one syscall and then the kernel immediately sends
@@ -884,7 +917,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
// the requests into one TCP packet.
// not sure yet if it helps, but it makes sense
- socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
+ absSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
@@ -893,7 +926,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
if (!connectionPrivate->connectionMonitor.isMonitoring()) {
// Now that we have a pair of addresses, we can start monitoring the
// connection status to handle its loss properly.
- if (connectionPrivate->connectionMonitor.setTargets(socket->localAddress(), socket->peerAddress()))
+ if (connectionPrivate->connectionMonitor.setTargets(absSocket->localAddress(), absSocket->peerAddress()))
connectionPrivate->connectionMonitor.startMonitoring();
}
}
@@ -905,7 +938,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
if (!connection->sslContext()) {
// this socket is making the 1st handshake for this connection,
// we need to set the SSL context so new sockets can reuse it
- if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket)))
+ if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(absSocket)))
connection->setSslContext(std::move(socketSslContext));
}
#endif
@@ -927,7 +960,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
switchedToHttp2 = false;
if (!reply)
- connection->d_func()->dequeueRequest(socket);
+ connection->d_func()->dequeueRequest(absSocket);
if (reply) {
if (tryProtocolUpgrade) {
@@ -940,6 +973,22 @@ void QHttpNetworkConnectionChannel::_q_connected()
}
}
+void QHttpNetworkConnectionChannel::_q_connected_local_socket(QLocalSocket *localSocket)
+{
+ state = QHttpNetworkConnectionChannel::IdleState;
+ if (!reply) // No reply object, try to dequeue a request (which is paired with a reply):
+ connection->d_func()->dequeueRequest(localSocket);
+ if (reply)
+ sendRequest();
+}
+
+void QHttpNetworkConnectionChannel::_q_connected()
+{
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket))
+ _q_connected_abstract_socket(s);
+ else if (auto *s = qobject_cast<QLocalSocket *>(socket))
+ _q_connected_local_socket(s);
+}
void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
{
@@ -1116,7 +1165,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
//signal emission triggered event loop
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
index c42290feca..853b647ecc 100644
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -19,6 +19,7 @@
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qabstractsocket.h>
+#include <QtNetwork/qlocalsocket.h>
#include <private/qobject_p.h>
#include <qauthenticator.h>
@@ -71,7 +72,7 @@ public:
ClosingState = 16,
BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|ClosingState)
};
- QAbstractSocket *socket;
+ QIODevice *socket;
bool ssl;
bool isInitialized;
ChannelState state;
@@ -156,6 +157,8 @@ public:
void _q_bytesWritten(qint64 bytes); // proceed sending
void _q_readyRead(); // pending data to read
void _q_disconnected(); // disconnected from host
+ void _q_connected_abstract_socket(QAbstractSocket *socket);
+ void _q_connected_local_socket(QLocalSocket *socket);
void _q_connected(); // start sending request
void _q_error(QAbstractSocket::SocketError); // error from socket
#ifndef QT_NO_NETWORKPROXY
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index 7a4ffb1684..06cc0b4464 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -381,5 +381,15 @@ void QHttpNetworkRequest::setPeerVerifyName(const QString &peerName)
d->peerVerifyName = peerName;
}
+QString QHttpNetworkRequest::fullLocalServerName() const
+{
+ return d->fullLocalServerName;
+}
+
+void QHttpNetworkRequest::setFullLocalServerName(const QString &fullServerName)
+{
+ d->fullLocalServerName = fullServerName;
+}
+
QT_END_NAMESPACE
diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h
index 131885f6d2..4444020402 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -117,6 +117,9 @@ public:
QString peerVerifyName() const;
void setPeerVerifyName(const QString &peerName);
+ QString fullLocalServerName() const;
+ void setFullLocalServerName(const QString &fullServerName);
+
private:
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
friend class QHttpNetworkRequestPrivate;
@@ -140,6 +143,7 @@ public:
QHttpNetworkRequest::Operation operation;
QByteArray customVerb;
+ QString fullLocalServerName; // for local sockets
QHttpNetworkRequest::Priority priority;
mutable QNonContiguousByteDevice* uploadByteDevice;
bool autoDecompress;
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index b0ae0dcf44..4e5cf05aef 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -95,7 +95,9 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &p
QUrl copy = url;
QString scheme = copy.scheme();
bool isEncrypted = scheme == "https"_L1 || scheme == "preconnect-https"_L1;
- copy.setPort(copy.port(isEncrypted ? 443 : 80));
+ const bool isLocalSocket = scheme.startsWith("unix"_L1);
+ if (!isLocalSocket)
+ copy.setPort(copy.port(isEncrypted ? 443 : 80));
if (scheme == "preconnect-http"_L1)
copy.setScheme("http"_L1);
else if (scheme == "preconnect-https"_L1)
@@ -145,9 +147,9 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
{
// Q_OBJECT
public:
- QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
+ QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType connectionType)
- : QHttpNetworkConnection(connectionCount, hostName, port, encrypt, /*parent=*/nullptr, connectionType)
+ : QHttpNetworkConnection(connectionCount, hostName, port, encrypt, isLocalSocket, /*parent=*/nullptr, connectionType)
{
setExpires(true);
setShareable(true);
@@ -244,7 +246,9 @@ void QHttpThreadDelegate::startRequest()
// check if we have an open connection to this host
QUrl urlCopy = httpRequest.url();
- urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
+ const bool isLocalSocket = urlCopy.scheme().startsWith("unix"_L1);
+ if (!isLocalSocket)
+ urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
QHttpNetworkConnection::ConnectionType connectionType
= httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
@@ -279,10 +283,19 @@ void QHttpThreadDelegate::startRequest()
} else
#endif // QT_CONFIG(ssl)
{
- urlCopy.setScheme(QStringLiteral("h2"));
+ if (isLocalSocket)
+ urlCopy.setScheme(QStringLiteral("unix+h2"));
+ else
+ urlCopy.setScheme(QStringLiteral("h2"));
}
}
+ QString extraData = httpRequest.peerVerifyName();
+ if (isLocalSocket) {
+ if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
+ extraData = path;
+ }
+
#ifndef QT_NO_NETWORKPROXY
if (transparentProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
@@ -295,10 +308,19 @@ void QHttpThreadDelegate::startRequest()
// the http object is actually a QHttpNetworkConnection
httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
if (!httpConnection) {
+
+ QString host = urlCopy.host();
+ // Update the host if a unix socket path or named pipe is used:
+ if (isLocalSocket) {
+ if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
+ host = path;
+ }
+
// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
- httpConnection = new QNetworkAccessCachedHttpConnection(http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
- connectionType);
+ httpConnection = new QNetworkAccessCachedHttpConnection(
+ http1Parameters.numberOfConnectionsPerHost(), host, urlCopy.port(), ssl,
+ isLocalSocket, connectionType);
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
httpConnection->setHttp2Parameters(http2Parameters);
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 7ef062a54d..b371730c3c 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -857,22 +857,15 @@ QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, const
}
/*!
- \overload
+ \fn QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, std::nullptr_t nptr)
\since 6.8
+ \overload
+
Sends the POST request specified by \a request without a body and returns
a new QNetworkReply object.
*/
-QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, std::nullptr_t nptr)
-{
- Q_UNUSED(nptr);
- QIODevice *dev = nullptr;
-
- return d_func()->postProcess(createRequest(QNetworkAccessManager::PostOperation,
- request,
- dev));
-}
#if QT_CONFIG(http) || defined(Q_OS_WASM)
/*!
@@ -958,22 +951,16 @@ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, const
}
/*!
+ \since 6.8
+
\overload
- \since 6.8
+ \fn QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, std::nullptr_t nptr)
Sends the PUT request specified by \a request without a body and returns
a new QNetworkReply object.
*/
-QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, std::nullptr_t nptr)
-{
- Q_UNUSED(nptr);
- QIODevice *dev = nullptr;
-
- return d_func()->postProcess(createRequest(QNetworkAccessManager::PutOperation, request, dev));
-}
-
/*!
\since 4.6
@@ -1220,6 +1207,13 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
bool isLocalFile = req.url().isLocalFile();
QString scheme = req.url().scheme();
+ // Remap local+http to unix+http to make further processing easier
+ if (scheme == "local+http"_L1) {
+ scheme = u"unix+http"_s;
+ QUrl url = req.url();
+ url.setScheme(scheme);
+ req.setUrl(url);
+ }
// fast path for GET on file:// URLs
// The QNetworkAccessFileBackend will right now only be used for PUT
@@ -1296,11 +1290,15 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
u"https",
u"preconnect-https",
#endif
+ u"unix+http",
};
// Since Qt 5 we use the new QNetworkReplyHttpImpl
if (std::find(std::begin(httpSchemes), std::end(httpSchemes), scheme) != std::end(httpSchemes)) {
+
#ifndef QT_NO_SSL
- if (isStrictTransportSecurityEnabled() && d->stsCache.isKnownHost(request.url())) {
+ const bool isLocalSocket = scheme.startsWith("unix"_L1);
+ if (!isLocalSocket && isStrictTransportSecurityEnabled()
+ && d->stsCache.isKnownHost(request.url())) {
QUrl stsUrl(request.url());
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
@@ -1391,6 +1389,8 @@ QStringList QNetworkAccessManager::supportedSchemesImplementation() const
// Those ones don't exist in backends
#if QT_CONFIG(http)
schemes << QStringLiteral("http");
+ schemes << QStringLiteral("unix+http");
+ schemes << QStringLiteral("local+http");
#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
schemes << QStringLiteral("https");
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index 0d069b2a9b..4bae05772f 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -84,10 +84,18 @@ public:
QNetworkReply *get(const QNetworkRequest &request, const QByteArray &data);
QNetworkReply *post(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
- QNetworkReply *post(const QNetworkRequest &request, std::nullptr_t nptr);
+ QNetworkReply *post(const QNetworkRequest &request, std::nullptr_t)
+ {
+ return post(request, static_cast<QIODevice*>(nullptr));
+ }
+
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data);
- QNetworkReply *put(const QNetworkRequest &request, std::nullptr_t nptr);
+ QNetworkReply *put(const QNetworkRequest &request, std::nullptr_t)
+ {
+ return put(request, static_cast<QIODevice*>(nullptr));
+ }
+
QNetworkReply *deleteResource(const QNetworkRequest &request);
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = nullptr);
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data);
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index 3e1fe761ee..89458825e9 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -809,6 +809,13 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());
+ if (scheme.startsWith(("unix"_L1))) {
+ if (QVariant path = newHttpRequest.attribute(QNetworkRequest::FullLocalServerNameAttribute);
+ path.isValid() && path.canConvert<QString>()) {
+ httpRequest.setFullLocalServerName(path.toString());
+ }
+ }
+
// Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
// Propagate Http/2 settings:
@@ -1228,7 +1235,8 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
url = redirectUrl;
- if (managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
+ const bool wasLocalSocket = schemeBefore.startsWith("unix"_L1);
+ if (!wasLocalSocket && managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
// and if the URI contains an explicit port component of "80",
@@ -1242,9 +1250,12 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
url.setPort(443);
}
- const bool isLessSafe = schemeBefore == "https"_L1 && url.scheme() == "http"_L1;
- if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy
- && isLessSafe) {
+ // Just to be on the safe side for local sockets, any changes to the scheme
+ // are considered less safe
+ const bool changingLocalScheme = wasLocalSocket && url.scheme() != schemeBefore;
+ const bool isLessSafe = changingLocalScheme
+ || (schemeBefore == "https"_L1 && url.scheme() == "http"_L1);
+ if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy && isLessSafe) {
error(QNetworkReply::InsecureRedirectError,
QCoreApplication::translate("QHttp", "Insecure redirect"));
return;
diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp
index 7d2b6a701e..ccf1542958 100644
--- a/src/network/access/qnetworkreplywasmimpl.cpp
+++ b/src/network/access/qnetworkreplywasmimpl.cpp
@@ -4,7 +4,6 @@
#include "qnetworkreplywasmimpl_p.h"
#include "qnetworkrequest.h"
-#include <QtCore/qtimer.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfileinfo.h>
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 7a1b5426d2..2fb467d3a8 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -144,7 +144,8 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
server (like "Ok", "Found", "Not Found", "Access Denied",
etc.) This is the human-readable representation of the status
code (see above). If the connection was not HTTP-based, this
- attribute will not be present.
+ attribute will not be present. \e{Note:} The reason phrase is
+ not used when using HTTP/2.
\value RedirectionTargetAttribute
Replies only, type: QMetaType::QUrl (no default)
@@ -324,6 +325,16 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
same-origin requests. This only affects the WebAssembly platform.
(This value was introduced in 6.5.)
+ \value FullLocalServerNameAttribute
+ Requests only, type: QMetaType::String
+ Holds the full local server name to be used for the underlying
+ QLocalSocket. This attribute is used by the QNetworkAccessManager
+ to connect to a specific local server, when QLocalSocket's behavior for
+ a simple name isn't enough. The URL in the QNetworkRequest must still
+ use unix+http: or local+http: scheme. And the hostname in the URL will
+ be used for the Host header in the HTTP request.
+ (This value was introduced in 6.8.)
+
\value User
Special type. Additional information can be passed in
QVariants with types ranging from User to UserMax. The default
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index e281c74834..368eb99d95 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -70,6 +70,7 @@ public:
ConnectionCacheExpiryTimeoutSecondsAttribute,
Http2CleartextAllowedAttribute,
UseCredentialsAttribute,
+ FullLocalServerNameAttribute,
User = 1000,
UserMax = 32767
diff --git a/src/network/access/qnetworkrequestfactory.cpp b/src/network/access/qnetworkrequestfactory.cpp
index d9c536cef2..4666891b2e 100644
--- a/src/network/access/qnetworkrequestfactory.cpp
+++ b/src/network/access/qnetworkrequestfactory.cpp
@@ -17,7 +17,7 @@ QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QNetworkRequestFactoryPrivate)
using namespace Qt::StringLiterals;
-Q_LOGGING_CATEGORY(lcQrequestfactory, "qt.network.access.request.factory")
+Q_STATIC_LOGGING_CATEGORY(lcQrequestfactory, "qt.network.access.request.factory")
/*!
\class QNetworkRequestFactory
@@ -28,8 +28,6 @@ Q_LOGGING_CATEGORY(lcQrequestfactory, "qt.network.access.request.factory")
\brief Convenience class for grouping remote server endpoints that share
common network request properties.
- \preliminary
-
REST servers often have endpoints that require the same headers and other data.
Grouping such endpoints with a QNetworkRequestFactory makes it more
convenient to issue requests to these endpoints; only the typically
diff --git a/src/network/access/qnetworkrequestfactory.h b/src/network/access/qnetworkrequestfactory.h
index 9d955a51e7..c170b75c8e 100644
--- a/src/network/access/qnetworkrequestfactory.h
+++ b/src/network/access/qnetworkrequestfactory.h
@@ -25,7 +25,7 @@ class QSslConfiguration;
class QNetworkRequestFactoryPrivate;
QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QNetworkRequestFactoryPrivate, Q_NETWORK_EXPORT)
-class QT_TECH_PREVIEW_API QNetworkRequestFactory
+class QNetworkRequestFactory
{
public:
Q_NETWORK_EXPORT QNetworkRequestFactory();
diff --git a/src/network/access/qrestaccessmanager.cpp b/src/network/access/qrestaccessmanager.cpp
index 7ef682e955..9e0182c7cb 100644
--- a/src/network/access/qrestaccessmanager.cpp
+++ b/src/network/access/qrestaccessmanager.cpp
@@ -31,8 +31,6 @@ Q_LOGGING_CATEGORY(lcQrest, "qt.network.access.rest")
\inmodule QtNetwork
\reentrant
- \preliminary
-
QRestAccessManager is a convenience wrapper on top of
QNetworkAccessManager. It amends datatypes and HTTP methods
that are useful for typical RESTful client applications.
diff --git a/src/network/access/qrestaccessmanager.h b/src/network/access/qrestaccessmanager.h
index 3245b41785..d72c075bf8 100644
--- a/src/network/access/qrestaccessmanager.h
+++ b/src/network/access/qrestaccessmanager.h
@@ -74,7 +74,7 @@ QNetworkReply *customWithDataImpl(const QNetworkRequest& request, const QByteArr
/* end */
class QRestAccessManagerPrivate;
-class QT_TECH_PREVIEW_API Q_NETWORK_EXPORT QRestAccessManager : public QObject
+class Q_NETWORK_EXPORT QRestAccessManager : public QObject
{
Q_OBJECT
using CallbackPrototype = void(*)(QRestReply&);
diff --git a/src/network/access/qrestaccessmanager_p.h b/src/network/access/qrestaccessmanager_p.h
index 2e6c1afb90..5fa2cc9e5a 100644
--- a/src/network/access/qrestaccessmanager_p.h
+++ b/src/network/access/qrestaccessmanager_p.h
@@ -20,6 +20,7 @@
#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qhash.h>
#include <QtCore/qjsondocument.h>
@@ -28,6 +29,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQrest)
+
class QRestReply;
class QRestAccessManagerPrivate : public QObjectPrivate
{
diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp
index 2d8d101084..204ecf6553 100644
--- a/src/network/access/qrestreply.cpp
+++ b/src/network/access/qrestreply.cpp
@@ -5,6 +5,7 @@
#include "qrestreply_p.h"
#include <QtNetwork/private/qnetworkreply_p.h>
+#include <QtNetwork/private/qrestaccessmanager_p.h>
#include <QtCore/qbytearrayview.h>
#include <QtCore/qjsondocument.h>
@@ -18,7 +19,6 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_DECLARE_LOGGING_CATEGORY(lcQrest)
/*!
\class QRestReply
@@ -29,8 +29,6 @@ Q_DECLARE_LOGGING_CATEGORY(lcQrest)
\ingroup network
\inmodule QtNetwork
- \preliminary
-
QRestReply wraps a QNetworkReply and provides convenience methods for data
and status handling. The methods provide convenience for typical RESTful
client applications.
diff --git a/src/network/access/qrestreply.h b/src/network/access/qrestreply.h
index c32fff1d4e..61aa7a6788 100644
--- a/src/network/access/qrestreply.h
+++ b/src/network/access/qrestreply.h
@@ -20,7 +20,7 @@ class QJsonDocument;
class QString;
class QRestReplyPrivate;
-class QT_TECH_PREVIEW_API QRestReply
+class QRestReply
{
public:
Q_NETWORK_EXPORT explicit QRestReply(QNetworkReply *reply);