summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/qabstractnetworkcache.cpp43
-rw-r--r--src/network/access/qabstractnetworkcache.h3
-rw-r--r--src/network/access/qabstractprotocolhandler_p.h4
-rw-r--r--src/network/access/qhttpheaders.h3
-rw-r--r--src/network/access/qhttpheadershelper.cpp25
-rw-r--r--src/network/access/qhttpheadershelper_p.h30
-rw-r--r--src/network/access/qhttpmultipart.cpp42
-rw-r--r--src/network/access/qhttpmultipart.h4
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp22
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h12
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp40
-rw-r--r--src/network/access/qhttpprotocolhandler.cpp12
-rw-r--r--src/network/access/qnetworkdiskcache.cpp58
-rw-r--r--src/network/access/qnetworkreply.cpp52
-rw-r--r--src/network/access/qnetworkreply.h4
-rw-r--r--src/network/access/qnetworkrequest.cpp119
-rw-r--r--src/network/access/qnetworkrequest.h5
-rw-r--r--src/network/access/qnetworkrequest_p.h12
-rw-r--r--src/network/access/qrestreply.cpp274
19 files changed, 658 insertions, 106 deletions
diff --git a/src/network/access/qabstractnetworkcache.cpp b/src/network/access/qabstractnetworkcache.cpp
index c8b940d801..3cd55d46fa 100644
--- a/src/network/access/qabstractnetworkcache.cpp
+++ b/src/network/access/qabstractnetworkcache.cpp
@@ -3,6 +3,8 @@
#include "qabstractnetworkcache.h"
#include "qabstractnetworkcache_p.h"
+#include "qnetworkrequest_p.h"
+#include "qhttpheadershelper_p.h"
#include <qdatastream.h>
#include <qdatetime.h>
@@ -28,14 +30,14 @@ public:
url == other.url
&& lastModified == other.lastModified
&& expirationDate == other.expirationDate
- && headers == other.headers
- && saveToDisk == other.saveToDisk;
+ && saveToDisk == other.saveToDisk
+ && QHttpHeadersHelper::compareStrict(headers, other.headers);
}
QUrl url;
QDateTime lastModified;
QDateTime expirationDate;
- QNetworkCacheMetaData::RawHeaderList headers;
+ QHttpHeaders headers;
QNetworkCacheMetaData::AttributesMap attributes;
bool saveToDisk;
@@ -207,21 +209,45 @@ void QNetworkCacheMetaData::setUrl(const QUrl &url)
Returns a list of all raw headers that are set in this meta data.
The list is in the same order that the headers were set.
- \sa setRawHeaders()
+ \sa setRawHeaders(), headers()
*/
QNetworkCacheMetaData::RawHeaderList QNetworkCacheMetaData::rawHeaders() const
{
- return d->headers;
+ return QNetworkHeadersPrivate::fromHttpToRaw(d->headers);
}
/*!
Sets the raw headers to \a list.
- \sa rawHeaders()
+ \sa rawHeaders(), setHeaders()
*/
void QNetworkCacheMetaData::setRawHeaders(const RawHeaderList &list)
{
- d->headers = list;
+ d->headers = QNetworkHeadersPrivate::fromRawToHttp(list);
+}
+
+/*!
+ \since 6.8
+
+ Returns headers in form of QHttpHeaders that are set in this meta data.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkCacheMetaData::headers() const
+{
+ return d->headers;
+}
+
+/*!
+ \since 6.8
+
+ Sets the headers of this network cache meta data to \a headers.
+
+ \sa headers()
+*/
+void QNetworkCacheMetaData::setHeaders(const QHttpHeaders &headers)
+{
+ d->headers = headers;
}
/*!
@@ -367,7 +393,8 @@ void QNetworkCacheMetaDataPrivate::load(QDataStream &in, QNetworkCacheMetaData &
in >> p->lastModified;
in >> p->saveToDisk;
in >> p->attributes;
- in >> p->headers;
+ QNetworkCacheMetaData::RawHeaderList list; in >> list;
+ metaData.setRawHeaders(list);
}
/*!
diff --git a/src/network/access/qabstractnetworkcache.h b/src/network/access/qabstractnetworkcache.h
index c70dcf737c..b12fd4f863 100644
--- a/src/network/access/qabstractnetworkcache.h
+++ b/src/network/access/qabstractnetworkcache.h
@@ -48,6 +48,9 @@ public:
RawHeaderList rawHeaders() const;
void setRawHeaders(const RawHeaderList &headers);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &headers);
+
QDateTime lastModified() const;
void setLastModified(const QDateTime &dateTime);
diff --git a/src/network/access/qabstractprotocolhandler_p.h b/src/network/access/qabstractprotocolhandler_p.h
index ad82aae66e..da5eaeeb74 100644
--- a/src/network/access/qabstractprotocolhandler_p.h
+++ b/src/network/access/qabstractprotocolhandler_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QHttpNetworkConnectionChannel;
class QHttpNetworkReply;
-class QAbstractSocket;
+class QIODevice;
class QHttpNetworkConnection;
class QAbstractProtocolHandler {
@@ -39,7 +39,7 @@ public:
protected:
QHttpNetworkConnectionChannel *m_channel;
QHttpNetworkReply *m_reply;
- QAbstractSocket *m_socket;
+ QIODevice *m_socket;
QHttpNetworkConnection *m_connection;
};
diff --git a/src/network/access/qhttpheaders.h b/src/network/access/qhttpheaders.h
index 97dc415e55..260df1421b 100644
--- a/src/network/access/qhttpheaders.h
+++ b/src/network/access/qhttpheaders.h
@@ -4,7 +4,8 @@
#ifndef QHTTPHEADERS_H
#define QHTTPHEADERS_H
-#include <QtNetwork/qnetworkrequest.h>
+#include <QtNetwork/qtnetworkglobal.h>
+#include <QtCore/qmetaobject.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qcontainerfwd.h>
diff --git a/src/network/access/qhttpheadershelper.cpp b/src/network/access/qhttpheadershelper.cpp
new file mode 100644
index 0000000000..d3cc9e439f
--- /dev/null
+++ b/src/network/access/qhttpheadershelper.cpp
@@ -0,0 +1,25 @@
+// 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 "qhttpheadershelper_p.h"
+
+#include <QtNetwork/qhttpheaders.h>
+
+QT_BEGIN_NAMESPACE
+
+bool QHttpHeadersHelper::compareStrict(const QHttpHeaders &left, const QHttpHeaders &right)
+{
+ if (left.size() != right.size())
+ return false;
+
+ for (qsizetype i = 0; i < left.size(); ++i) {
+ if (left.nameAt(i) != right.nameAt(i))
+ return false;
+ if (left.valueAt(i) != right.valueAt(i))
+ return false;
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpheadershelper_p.h b/src/network/access/qhttpheadershelper_p.h
new file mode 100644
index 0000000000..d1e38a1a8e
--- /dev/null
+++ b/src/network/access/qhttpheadershelper_p.h
@@ -0,0 +1,30 @@
+// 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 QHTTPHEADERSHELPER_H
+#define QHTTPHEADERSHELPER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QHttpHeaders;
+
+namespace QHttpHeadersHelper {
+ Q_NETWORK_EXPORT bool compareStrict(const QHttpHeaders &left, const QHttpHeaders &right);
+};
+
+QT_END_NAMESPACE
+
+#endif // QHTTPHEADERSHELPER_H
diff --git a/src/network/access/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp
index 6d81f1b957..2b5f1163c8 100644
--- a/src/network/access/qhttpmultipart.cpp
+++ b/src/network/access/qhttpmultipart.cpp
@@ -511,6 +511,48 @@ qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
return -1;
}
+#ifndef QT_NO_DEBUG_STREAM
+
+/*!
+ \fn QDebug QHttpPart::operator<<(QDebug debug, const QHttpPart &part)
+
+ Writes the \a part into the \a debug object for debugging purposes.
+ Unless a device is set, the size of the body is shown.
+
+ \sa {Debugging Techniques}
+ \since 6.8
+*/
+
+QDebug operator<<(QDebug debug, const QHttpPart &part)
+{
+ const QDebugStateSaver saver(debug);
+ debug.resetFormat().nospace().noquote();
+
+ debug << "QHttpPart(headers = ["
+ << part.d->cookedHeaders
+ << "], raw headers = ["
+ << part.d->rawHeaders
+ << "],";
+
+ if (part.d->bodyDevice) {
+ debug << " bodydevice = ["
+ << part.d->bodyDevice
+ << ", is open: "
+ << part.d->bodyDevice->isOpen()
+ << "]";
+ } else {
+ debug << " size of body = "
+ << part.d->body.size()
+ << " bytes";
+ }
+
+ debug << ")";
+
+ return debug;
+}
+
+#endif // QT_NO_DEBUG_STREAM
+
QT_END_NAMESPACE
diff --git a/src/network/access/qhttpmultipart.h b/src/network/access/qhttpmultipart.h
index 26e5fafdf2..9732bbd99d 100644
--- a/src/network/access/qhttpmultipart.h
+++ b/src/network/access/qhttpmultipart.h
@@ -19,6 +19,7 @@ QT_BEGIN_NAMESPACE
class QHttpPartPrivate;
class QHttpMultiPart;
+class QDebug;
class Q_NETWORK_EXPORT QHttpPart
{
@@ -45,6 +46,9 @@ private:
QSharedDataPointer<QHttpPartPrivate> d;
friend class QHttpMultiPartIODevice;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QHttpPart &httpPart);
+#endif
};
Q_DECLARE_SHARED(QHttpPart)
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 1897380e0e..419491a711 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -13,6 +13,7 @@
#include <qauthenticator.h>
#include <qcoreapplication.h>
#include <private/qdecompresshelper_p.h>
+#include <private/qsocketabstraction_p.h>
#include <qbuffer.h>
#include <qpair.h>
@@ -319,7 +320,7 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
-void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket,
+void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket,
QHttpNetworkReply *reply,
QNetworkReply::NetworkError errorCode)
{
@@ -384,7 +385,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica
// handles the authentication for one channel and eventually re-starts the other channels
-bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply,
+bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply,
bool isProxy, bool &resend)
{
Q_ASSERT(socket);
@@ -486,7 +487,7 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
}
// Used by the HTTP1 code-path
-QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket,
+QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QIODevice *socket,
QHttpNetworkReply *reply)
{
ParseRedirectResult result = parseRedirectResponse(reply);
@@ -740,7 +741,7 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::predictNextRequestsReply() con
}
// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
-void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
+void QHttpNetworkConnectionPrivate::fillPipeline(QIODevice *socket)
{
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
@@ -768,7 +769,7 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
return;
// check if socket is connected
- if (socket->state() != QAbstractSocket::ConnectedState)
+ if (QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState)
return;
// check for resendCurrent
@@ -862,16 +863,15 @@ bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue,
}
-QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket, const QString &extraDetail)
+QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket, const QString &extraDetail)
{
QString errorString;
switch (errorCode) {
- case QNetworkReply::HostNotFoundError:
- if (socket)
- errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(socket->peerName());
- else
- errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(hostName);
+ case QNetworkReply::HostNotFoundError: {
+ const QString peerName = socket ? QSocketAbstraction::socketPeerName(socket) : hostName;
+ errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(peerName);
break;
+ }
case QNetworkReply::ConnectionRefusedError:
errorString = QCoreApplication::translate("QHttp", "Connection refused");
break;
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index 9cd4d75bbc..c2d062fb16 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -177,7 +177,7 @@ public:
QHttpNetworkRequest predictNextRequest() const;
QHttpNetworkReply* predictNextRequestsReply() const;
- void fillPipeline(QAbstractSocket *socket);
+ void fillPipeline(QIODevice *socket);
bool fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel);
// read more HTTP body after the next event loop spin
@@ -197,7 +197,7 @@ public:
void createAuthorization(QIODevice *socket, QHttpNetworkRequest &request);
- QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket,
+ QString errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket,
const QString &extraDetail = QString());
void removeReply(QHttpNetworkReply *reply);
@@ -212,22 +212,22 @@ public:
// The total number of channels we reserved:
const int channelCount;
QTimer delayedConnectionTimer;
- QHttpNetworkConnectionChannel *channels; // parallel connections to the server
+ QHttpNetworkConnectionChannel * const channels; // parallel connections to the server
bool shouldEmitChannelError(QIODevice *socket);
qint64 uncompressedBytesAvailable(const QHttpNetworkReply &reply) const;
qint64 uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const;
- void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
- bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
+ void emitReplyError(QIODevice *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
+ bool handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
struct ParseRedirectResult {
QUrl redirectUrl;
QNetworkReply::NetworkError errorCode;
};
static ParseRedirectResult parseRedirectResponse(QHttpNetworkReply *reply);
// Used by the HTTP1 code-path
- QUrl parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply);
+ QUrl parseRedirectResponse(QIODevice *socket, QHttpNetworkReply *reply);
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy networkProxy;
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 6766989690..71b880f6f8 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -91,14 +91,14 @@ void QHttpNetworkConnectionChannel::init()
// After some back and forth in all the last years, this is now a DirectConnection because otherwise
// the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
// which behave slightly differently on Windows vs Linux
- QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
- this, SLOT(_q_bytesWritten(qint64)),
+ QObject::connect(socket, &QIODevice::bytesWritten,
+ this, &QHttpNetworkConnectionChannel::_q_bytesWritten,
Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(connected()),
- this, SLOT(_q_connected()),
+ QObject::connect(socket, &QAbstractSocket::connected,
+ this, &QHttpNetworkConnectionChannel::_q_connected,
Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(readyRead()),
- this, SLOT(_q_readyRead()),
+ QObject::connect(socket, &QIODevice::readyRead,
+ this, &QHttpNetworkConnectionChannel::_q_readyRead,
Qt::DirectConnection);
// The disconnected() and error() signals may already come
@@ -108,17 +108,17 @@ void QHttpNetworkConnectionChannel::init()
// 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, SIGNAL(disconnected()),
- this, SLOT(_q_disconnected()),
+ QObject::connect(socket, &QAbstractSocket::disconnected,
+ this, &QHttpNetworkConnectionChannel::_q_disconnected,
Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
- this, SLOT(_q_error(QAbstractSocket::SocketError)),
+ QObject::connect(socket, &QAbstractSocket::errorOccurred,
+ this, &QHttpNetworkConnectionChannel::_q_error,
Qt::DirectConnection);
#ifndef QT_NO_NETWORKPROXY
- QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
- this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ QObject::connect(socket, &QAbstractSocket::proxyAuthenticationRequired,
+ this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired,
Qt::DirectConnection);
#endif
@@ -126,17 +126,17 @@ void QHttpNetworkConnectionChannel::init()
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
if (sslSocket) {
// won't be a sslSocket if encrypt is false
- QObject::connect(sslSocket, SIGNAL(encrypted()),
- this, SLOT(_q_encrypted()),
+ QObject::connect(sslSocket, &QSslSocket::encrypted,
+ this, &QHttpNetworkConnectionChannel::_q_encrypted,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
- this, SLOT(_q_sslErrors(QList<QSslError>)),
+ QObject::connect(sslSocket, &QSslSocket::sslErrors,
+ this, &QHttpNetworkConnectionChannel::_q_sslErrors,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
- this, SLOT(_q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
+ QObject::connect(sslSocket, &QSslSocket::preSharedKeyAuthenticationRequired,
+ this, &QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
- this, SLOT(_q_encryptedBytesWritten(qint64)),
+ QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten,
+ this, &QHttpNetworkConnectionChannel::_q_encryptedBytesWritten,
Qt::DirectConnection);
if (ignoreAllSslErrors)
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
index 28eab03890..fb584eb9cc 100644
--- a/src/network/access/qhttpprotocolhandler.cpp
+++ b/src/network/access/qhttpprotocolhandler.cpp
@@ -5,6 +5,7 @@
#include <private/qhttpprotocolhandler_p.h>
#include <private/qnoncontiguousbytedevice_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
+#include <private/qsocketabstraction_p.h>
QT_BEGIN_NAMESPACE
@@ -34,10 +35,8 @@ void QHttpProtocolHandler::_q_receiveReply()
return;
}
- QAbstractSocket::SocketState socketState = m_socket->state();
-
// connection might be closed to signal the end of data
- if (socketState == QAbstractSocket::UnconnectedState) {
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::UnconnectedState) {
if (m_socket->bytesAvailable() <= 0) {
if (m_reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
// finish this reply. this case happens when the server did not send a content length
@@ -115,7 +114,7 @@ void QHttpProtocolHandler::_q_receiveReply()
}
case QHttpNetworkReplyPrivate::ReadingDataState: {
QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
- if (m_socket->state() == QAbstractSocket::ConnectedState &&
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState &&
replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
// (only do the following when still connected, not when we have already been disconnected and there is still data)
// We already have some HTTP body data. We don't read more from the socket until
@@ -193,7 +192,8 @@ void QHttpProtocolHandler::_q_receiveReply()
void QHttpProtocolHandler::_q_readyRead()
{
- if (m_socket->state() == QAbstractSocket::ConnectedState && m_socket->bytesAvailable() == 0) {
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState
+ && m_socket->bytesAvailable() == 0) {
// We got a readyRead but no bytes are available..
// This happens for the Unbuffered QTcpSocket
// Also check if socket is in ConnectedState since
@@ -201,7 +201,7 @@ void QHttpProtocolHandler::_q_readyRead()
char c;
qint64 ret = m_socket->peek(&c, 1);
if (ret < 0) {
- m_channel->_q_error(m_socket->error());
+ m_channel->_q_error(QSocketAbstraction::socketError(m_socket));
// We still need to handle the reply so it emits its signals etc.
if (m_reply)
_q_receiveReply();
diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp
index c883a61886..b39924025e 100644
--- a/src/network/access/qnetworkdiskcache.cpp
+++ b/src/network/access/qnetworkdiskcache.cpp
@@ -154,15 +154,11 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
return nullptr;
}
- const auto headers = metaData.rawHeaders();
- for (const auto &header : headers) {
- if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
- const qint64 size = header.second.toLongLong();
- if (size > (maximumCacheSize() * 3)/4)
- return nullptr;
- break;
- }
- }
+ const auto sizeValue = metaData.headers().value(QHttpHeaders::WellKnownHeader::ContentLength);
+ const qint64 size = sizeValue.toLongLong();
+ if (size > (maximumCacheSize() * 3)/4)
+ return nullptr;
+
std::unique_ptr<QCacheItem> cacheItem = std::make_unique<QCacheItem>();
cacheItem->metaData = metaData;
@@ -578,31 +574,27 @@ QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
*/
bool QCacheItem::canCompress() const
{
- bool sizeOk = false;
- bool typeOk = false;
- const auto headers = metaData.rawHeaders();
- for (const auto &header : headers) {
- if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
- qint64 size = header.second.toLongLong();
- if (size > MAX_COMPRESSION_SIZE)
- return false;
- else
- sizeOk = true;
- }
+ const auto h = metaData.headers();
- if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) {
- QByteArray type = header.second;
- if (type.startsWith("text/")
- || (type.startsWith("application/")
- && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
- typeOk = true;
- else
- return false;
- }
- if (sizeOk && typeOk)
- return true;
+ const auto sizeValue = h.value(QHttpHeaders::WellKnownHeader::ContentLength);
+ if (sizeValue.empty())
+ return false;
+
+ qint64 size = sizeValue.toLongLong();
+ if (size > MAX_COMPRESSION_SIZE)
+ return false;
+
+ const auto type = h.value(QHttpHeaders::WellKnownHeader::ContentType);
+ if (!type.empty())
+ return false;
+
+ if (!type.startsWith("text/")
+ && !(type.startsWith("application/")
+ && (type.endsWith("javascript") || type.endsWith("ecmascript")))) {
+ return false;
}
- return false;
+
+ return true;
}
enum
@@ -673,7 +665,7 @@ bool QCacheItem::read(QFileDevice *device, bool readData)
if (!device->fileName().endsWith(expectedFilename))
return false;
- return metaData.isValid() && !metaData.rawHeaders().isEmpty();
+ return metaData.isValid() && !metaData.headers().isEmpty();
}
QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp
index 9334b01de6..0613a65c34 100644
--- a/src/network/access/qnetworkreply.cpp
+++ b/src/network/access/qnetworkreply.cpp
@@ -654,6 +654,19 @@ const QList<QNetworkReply::RawHeaderPair>& QNetworkReply::rawHeaderPairs() const
}
/*!
+ \since 6.8
+
+ Returns headers that were sent by the remote server.
+
+ \sa setHeaders(), QNetworkRequest::setAttribute(), QNetworkRequest::Attribute
+*/
+QHttpHeaders QNetworkReply::headers() const
+{
+ Q_D(const QNetworkReply);
+ return d->headers();
+}
+
+/*!
Returns a list of headers fields that were sent by the remote
server, in the order that they were sent. Duplicate headers are
merged together and take place of the latter duplicate.
@@ -888,6 +901,45 @@ void QNetworkReply::setUrl(const QUrl &url)
}
/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers in this network reply, overriding
+ any previously set headers.
+
+ If some headers correspond to the known headers, they will be
+ parsed and the corresponding parsed form will also be set.
+
+ \sa headers(), QNetworkRequest::KnownHeaders
+*/
+void QNetworkReply::setHeaders(const QHttpHeaders &newHeaders)
+{
+ Q_D(QNetworkReply);
+ d->setHeaders(newHeaders);
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkReply::setHeaders(QHttpHeaders &&newHeaders)
+{
+ Q_D(QNetworkReply);
+ d->setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \since 6.8
+
+ Sets the header \a name to be of value \a value. If \a
+ name was previously set, it is overridden.
+*/
+void QNetworkReply::setWellKnownHeader(QHttpHeaders::WellKnownHeader name, const QByteArray &value)
+{
+ Q_D(QNetworkReply);
+ d->setHeader(name, value);
+}
+
+/*!
Sets the known header \a header to be of value \a value. The
corresponding raw form of the header will be set as well.
diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h
index 390a6f2f51..48ec0397c6 100644
--- a/src/network/access/qnetworkreply.h
+++ b/src/network/access/qnetworkreply.h
@@ -109,6 +109,7 @@ public:
typedef QPair<QByteArray, QByteArray> RawHeaderPair;
const QList<RawHeaderPair>& rawHeaderPairs() const;
+ QHttpHeaders headers() const;
// attributes
QVariant attribute(QNetworkRequest::Attribute code) const;
@@ -152,6 +153,9 @@ protected:
void setUrl(const QUrl &url);
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
void setRawHeader(const QByteArray &headerName, const QByteArray &value);
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+ void setWellKnownHeader(QHttpHeaders::WellKnownHeader name, const QByteArray &value);
void setAttribute(QNetworkRequest::Attribute code, const QVariant &value);
#if QT_CONFIG(ssl)
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 6f5a7ff19a..a59de42a44 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -6,6 +6,7 @@
#include "qplatformdefs.h"
#include "qnetworkcookie.h"
#include "qsslconfiguration.h"
+#include "qhttpheadershelper_p.h"
#if QT_CONFIG(http)
#include "qhttp1configuration.h"
#include "qhttp2configuration.h"
@@ -475,6 +476,7 @@ public:
&& decompressedSafetyCheckThreshold == other.decompressedSafetyCheckThreshold
#endif
&& transferTimeout == other.transferTimeout
+ && QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders)
;
// don't compare cookedHeaders
}
@@ -601,6 +603,43 @@ void QNetworkRequest::setUrl(const QUrl &url)
}
/*!
+ \since 6.8
+
+ Returns headers that are set in this network request.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkRequest::headers() const
+{
+ return d->headers();
+}
+
+/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers in this network request, overriding
+ any previously set headers.
+
+ If some headers correspond to the known headers, the values will
+ be parsed and the corresponding parsed form will also be set.
+
+ \sa headers(), KnownHeaders
+*/
+void QNetworkRequest::setHeaders(QHttpHeaders &&newHeaders)
+{
+ d->setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkRequest::setHeaders(const QHttpHeaders &newHeaders)
+{
+ d->setHeaders(newHeaders);
+}
+
+/*!
Returns the value of the known network header \a header if it is
present in this request. If it is not present, returns QVariant()
(i.e., an invalid variant).
@@ -1401,6 +1440,33 @@ void QNetworkHeadersPrivate::setCookedHeader(QNetworkRequest::KnownHeaders heade
}
}
+QHttpHeaders QNetworkHeadersPrivate::headers() const
+{
+ return httpHeaders;
+}
+
+void QNetworkHeadersPrivate::setHeaders(const QHttpHeaders &newHeaders)
+{
+ httpHeaders = newHeaders;
+}
+
+void QNetworkHeadersPrivate::setHeaders(QHttpHeaders &&newHeaders)
+{
+ httpHeaders = std::move(newHeaders);
+}
+
+void QNetworkHeadersPrivate::setHeader(QHttpHeaders::WellKnownHeader name, QByteArrayView value)
+{
+ httpHeaders.replaceOrAppend(name, value);
+}
+
+void QNetworkHeadersPrivate::clearHeaders()
+{
+ httpHeaders.clear();
+ rawHeaders.clear();
+ cookedHeaders.clear();
+}
+
void QNetworkHeadersPrivate::setRawHeaderInternal(const QByteArray &key, const QByteArray &value)
{
auto firstEqualsKey = [&key](const RawHeaderPair &header) {
@@ -1536,6 +1602,59 @@ QByteArray QNetworkHeadersPrivate::toHttpDate(const QDateTime &dt)
return QLocale::c().toString(dt.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT'").toLatin1();
}
+QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::fromHttpToRaw(const QHttpHeaders &headers)
+{
+ if (headers.isEmpty())
+ return {};
+
+ QNetworkHeadersPrivate::RawHeadersList list;
+ QHash<QByteArray, qsizetype> nameToIndex;
+ list.reserve(headers.size());
+ nameToIndex.reserve(headers.size());
+
+ for (qsizetype i = 0; i < headers.size(); ++i) {
+ const auto nameL1 = headers.nameAt(i);
+ const auto value = headers.valueAt(i);
+
+ const bool isSetCookie = nameL1 == QHttpHeaders::wellKnownHeaderName(
+ QHttpHeaders::WellKnownHeader::SetCookie);
+
+ const auto name = QByteArray(nameL1.data(), nameL1.size());
+ if (auto it = nameToIndex.find(name); it != nameToIndex.end()) {
+ list[it.value()].second += isSetCookie ? "\n" : ", ";
+ list[it.value()].second += value;
+ } else {
+ nameToIndex[name] = list.size();
+ list.emplaceBack(name, value.toByteArray());
+ }
+ }
+
+ return list;
+}
+
+QHttpHeaders QNetworkHeadersPrivate::fromRawToHttp(const RawHeadersList &raw)
+{
+ if (raw.empty())
+ return {};
+
+ QHttpHeaders headers;
+ headers.reserve(raw.size());
+
+ for (const auto &[key, value] : raw) {
+ const bool isSetCookie = key.compare(QHttpHeaders::wellKnownHeaderName(
+ QHttpHeaders::WellKnownHeader::SetCookie),
+ Qt::CaseInsensitive) == 0;
+ if (isSetCookie) {
+ for (auto header : QLatin1StringView(value).tokenize('\n'_L1))
+ headers.append(key, header);
+ } else {
+ headers.append(key, value);
+ }
+ }
+
+ return headers;
+}
+
QT_END_NAMESPACE
#include "moc_qnetworkrequest.cpp"
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index 3ca61a2ee3..875c787673 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -5,6 +5,7 @@
#define QNETWORKREQUEST_H
#include <QtNetwork/qtnetworkglobal.h>
+#include <QtNetwork/qhttpheaders.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QString>
#include <QtCore/QUrl>
@@ -119,6 +120,10 @@ public:
QUrl url() const;
void setUrl(const QUrl &url);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+
// "cooked" headers
QVariant header(KnownHeaders header) const;
void setHeader(KnownHeaders header, const QVariant &value);
diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h
index 48fcdcf1ed..a73d8f2162 100644
--- a/src/network/access/qnetworkrequest_p.h
+++ b/src/network/access/qnetworkrequest_p.h
@@ -16,6 +16,7 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QtNetwork/qhttpheaders.h>
#include "qnetworkrequest.h"
#include "QtCore/qbytearray.h"
#include "QtCore/qlist.h"
@@ -36,6 +37,7 @@ public:
typedef QHash<QNetworkRequest::Attribute, QVariant> AttributesMap;
RawHeadersList rawHeaders;
+ QHttpHeaders httpHeaders;
CookedHeadersMap cookedHeaders;
AttributesMap attributes;
QPointer<QObject> originatingObject;
@@ -47,9 +49,19 @@ public:
void setAllRawHeaders(const RawHeadersList &list);
void setCookedHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+ void setHeader(QHttpHeaders::WellKnownHeader name, QByteArrayView value);
+
+ void clearHeaders();
+
static QDateTime fromHttpDate(const QByteArray &value);
static QByteArray toHttpDate(const QDateTime &dt);
+ static RawHeadersList fromHttpToRaw(const QHttpHeaders &headers);
+ static QHttpHeaders fromRawToHttp(const RawHeadersList &raw);
+
private:
void setRawHeaderInternal(const QByteArray &key, const QByteArray &value);
void parseAndSetHeader(const QByteArray &key, const QByteArray &value);
diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp
index 3cc62c7efa..cdd264ba6e 100644
--- a/src/network/access/qrestreply.cpp
+++ b/src/network/access/qrestreply.cpp
@@ -4,11 +4,17 @@
#include "qrestreply.h"
#include "qrestreply_p.h"
+#include <QtNetwork/private/qnetworkreply_p.h>
+
+#include <QtCore/qbytearrayview.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qlatin1stringmatcher.h>
+#include <QtCore/qlatin1stringview.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qstringconverter.h>
+#include <QtCore/qxpfunctional.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -335,6 +341,249 @@ QDebug operator<<(QDebug debug, const QRestReply &reply)
}
#endif // QT_NO_DEBUG_STREAM
+static constexpr auto parse_OWS(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView ows, tail;
+ };
+
+ constexpr auto is_OWS_char = [](auto ch) { return ch == ' ' || ch == '\t'; };
+
+ qsizetype i = 0;
+ while (i < data.size() && is_OWS_char(data[i]))
+ ++i;
+
+ return R{data.first(i), data.sliced(i)};
+}
+
+static constexpr void eat_OWS(QByteArrayView &data) noexcept
+{
+ data = parse_OWS(data).tail;
+}
+
+static constexpr auto parse_quoted_string(QByteArrayView data, qxp::function_ref<void(char) const> yield)
+{
+ struct R {
+ QByteArrayView quotedString, tail;
+ constexpr explicit operator bool() const noexcept { return !quotedString.isEmpty(); }
+ };
+
+ if (!data.startsWith('"'))
+ return R{{}, data};
+
+ qsizetype i = 1; // one past initial DQUOTE
+ while (i < data.size()) {
+ switch (auto ch = data[i++]) {
+ case '"': // final DQUOTE -> end of string
+ return R{data.first(i), data.sliced(i)};
+ case '\\': // quoted-pair
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.4-3:
+ // Recipients that process the value of a quoted-string MUST handle a
+ // quoted-pair as if it were replaced by the octet following the backslash.
+ if (i == data.size())
+ break; // premature end
+ ch = data[i++]; // eat '\\'
+ [[fallthrough]];
+ default:
+ // we don't validate quoted-string octets to be only qdtext (Postel's Law)
+ yield(ch);
+ }
+ }
+
+ return R{{}, data}; // premature end
+}
+
+static constexpr bool is_tchar(char ch) noexcept
+{
+ // ### optimize
+ switch (ch) {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return true;
+ default:
+ return (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || (ch >= 'A' && ch <= 'Z');
+ }
+}
+
+static constexpr auto parse_comment(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView comment, tail;
+ constexpr explicit operator bool() const noexcept { return !comment.isEmpty(); }
+ };
+
+ const auto invalid = R{{}, data}; // preserves original `data`
+
+ // comment = "(" *( ctext / quoted-pair / comment ) ")"
+ // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
+
+ if (!data.startsWith('('))
+ return invalid;
+
+ qsizetype i = 1;
+ qsizetype level = 1;
+ while (i < data.size()) {
+ switch (data[i++]) {
+ case '(': // nested comment
+ ++level;
+ break;
+ case ')': // end of comment
+ if (--level == 0)
+ return R{data.first(i), data.sliced(i)};
+ break;
+ case '\\': // quoted-pair
+ if (i == data.size())
+ return invalid; // premature end
+ ++i; // eat escaped character
+ break;
+ default:
+ ; // don't validate ctext - accept everything (Postel's Law)
+ }
+ }
+
+ return invalid; // premature end / unbalanced nesting levels
+}
+
+static constexpr void eat_CWS(QByteArrayView &data) noexcept
+{
+ eat_OWS(data);
+ while (const auto comment = parse_comment(data)) {
+ data = comment.tail;
+ eat_OWS(data);
+ }
+}
+
+static constexpr auto parse_token(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView token, tail;
+ constexpr explicit operator bool() const noexcept { return !token.isEmpty(); }
+ };
+
+ qsizetype i = 0;
+ while (i < data.size() && is_tchar(data[i]))
+ ++i;
+
+ return R{data.first(i), data.sliced(i)};
+}
+
+static constexpr auto parse_parameter(QByteArrayView data, qxp::function_ref<void(char) const> yield)
+{
+ struct R {
+ QLatin1StringView name; QByteArrayView value; QByteArrayView tail;
+ constexpr explicit operator bool() const noexcept { return !name.isEmpty(); }
+ };
+
+ const auto invalid = R{{}, {}, data}; // preserves original `data`
+
+ // parameter = parameter-name "=" parameter-value
+ // parameter-name = token
+ // parameter-value = ( token / quoted-string )
+
+ const auto name = parse_token(data);
+ if (!name)
+ return invalid;
+ data = name.tail;
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (!data.startsWith('='))
+ return invalid;
+ data = data.sliced(1);
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (Q_UNLIKELY(data.startsWith('"'))) { // value is a quoted-string
+
+ const auto value = parse_quoted_string(data, yield);
+ if (!value)
+ return invalid;
+ data = value.tail;
+
+ return R{QLatin1StringView{name.token}, value.quotedString, data};
+
+ } else { // value is a token
+
+ const auto value = parse_token(data);
+ if (!value)
+ return invalid;
+ data = value.tail;
+
+ return R{QLatin1StringView{name.token}, value.token, data};
+ }
+}
+
+static auto parse_content_type(QByteArrayView data)
+{
+ struct R {
+ QLatin1StringView type, subtype;
+ std::string charset;
+ constexpr explicit operator bool() const noexcept { return !type.isEmpty(); }
+ };
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ const auto type = parse_token(data);
+ if (!type)
+ return R{};
+ data = type.tail;
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (!data.startsWith('/'))
+ return R{};
+ data = data.sliced(1);
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ const auto subtype = parse_token(data);
+ if (!subtype)
+ return R{};
+ data = subtype.tail;
+
+ eat_CWS(data);
+
+ auto r = R{QLatin1StringView{type.token}, QLatin1StringView{subtype.token}, {}};
+
+ while (data.startsWith(';')) {
+
+ data = data.sliced(1); // eat ';'
+
+ eat_CWS(data);
+
+ const auto param = parse_parameter(data, [&](char ch) { r.charset.append(1, ch); });
+ if (param.name.compare("charset"_L1, Qt::CaseInsensitive) == 0) {
+ if (r.charset.empty() && !param.value.startsWith('"')) // wasn't a quoted-string
+ r.charset.assign(param.value.begin(), param.value.end());
+ return r; // charset found
+ }
+ r.charset.clear(); // wasn't an actual charset
+ if (param.tail.size() == data.size()) // no progress was made
+ break; // returns {type, subtype}
+ // otherwise, continue (accepting e.g. `;;`)
+ data = param.tail;
+
+ eat_CWS(data);
+ }
+
+ return r; // no charset found
+}
+
QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply)
{
// Content-type consists of mimetype and optional parameters, of which one may be 'charset'
@@ -345,28 +594,15 @@ QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply)
// text/plain; charset=utf-8;version=1.7
// text/plain; charset = utf-8
// text/plain; charset ="utf-8"
- // Default to the most commonly used UTF-8.
- QByteArray charset{"UTF-8"};
+
const QByteArray contentTypeValue =
reply->header(QNetworkRequest::KnownHeaders::ContentTypeHeader).toByteArray();
- QList<QByteArray> parameters = contentTypeValue.split(';');
- if (parameters.size() >= 2) { // Need at least one parameter in addition to the mimetype itself
- parameters.removeFirst(); // Exclude the mimetype itself, only interested in parameters
- QLatin1StringMatcher matcher("charset="_L1, Qt::CaseSensitivity::CaseInsensitive);
- qsizetype matchIndex = -1;
- for (auto &parameter : parameters) {
- // Remove whitespaces and parantheses
- const QByteArray curated = parameter.replace(" ", "").replace("\"","");
- // Check for match
- matchIndex = matcher.indexIn(QLatin1String(curated.constData()));
- if (matchIndex >= 0) {
- charset = curated.sliced(matchIndex + 8); // 8 is size of "charset="
- break;
- }
- }
- }
- return charset;
+ const auto r = parse_content_type(contentTypeValue);
+ if (r && !r.charset.empty())
+ return QByteArrayView(r.charset).toByteArray();
+ else
+ return "UTF-8"_ba; // Default to the most commonly used UTF-8.
}
QT_END_NAMESPACE