diff options
author | Lena Biliaieva <lena.biliaieva@qt.io> | 2024-04-03 20:56:29 +0200 |
---|---|---|
committer | Lena Biliaieva <lena.biliaieva@qt.io> | 2024-05-07 12:39:23 +0200 |
commit | 0fef8f53c3e52dd31648c55a2376006f223a7054 (patch) | |
tree | ef0e5fa542982ee3f5988cffd3dd51470f02272b | |
parent | a32c152d39e80277107cb62758f0bdac6b363b5d (diff) |
Use QHttpHeaders: Update internal users of QNRequest, QNReply, QNProxy
Replace QNetworkHeadersPrivate's main headers storage, which was
RawHeadersList, with QHttpHeaders. Replace internal usage of raw and
cooked header methods with the QHttpHeaders API.
[ChangeLog][QtNetwork][QNetworkRequest] Header value added by
QNetworkRequest::setRawHeader() method is trimmed now.
Task-number: QTBUG-107751
Change-Id: I8882978afa430651e6c798a4fed00beef6c4cfd2
Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
25 files changed, 656 insertions, 394 deletions
diff --git a/src/network/access/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp index 2b5f1163c8..a695969f00 100644 --- a/src/network/access/qhttpmultipart.cpp +++ b/src/network/access/qhttpmultipart.cpp @@ -386,9 +386,12 @@ void QHttpPartPrivate::checkHeaderCreated() const { if (!headerCreated) { // copied from QHttpNetworkRequestPrivate::header() and adapted - const auto fields = allRawHeaders(); - for (const auto &[name, value] : fields) - header += name + ": " + value + "\r\n"; + const auto h = headers(); + for (qsizetype i = 0; i < h.size(); ++i) { + const auto name = h.nameAt(i); + header += QByteArrayView(name.data(), name.size()) + ": " + h.valueAt(i) + "\r\n"; + } + header += "\r\n"; headerCreated = true; } @@ -530,8 +533,8 @@ QDebug operator<<(QDebug debug, const QHttpPart &part) debug << "QHttpPart(headers = [" << part.d->cookedHeaders - << "], raw headers = [" - << part.d->rawHeaders + << "], http headers = [" + << part.d->httpHeaders << "],"; if (part.d->bodyDevice) { diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h index d485fcf5cd..7a12ce8424 100644 --- a/src/network/access/qhttpmultipart_p.h +++ b/src/network/access/qhttpmultipart_p.h @@ -18,6 +18,7 @@ #include <QtNetwork/private/qtnetworkglobal_p.h> #include "QtCore/qshareddata.h" #include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate +#include "qhttpheadershelper_p.h" #include "private/qobject_p.h" #ifndef Q_OS_WASM @@ -47,8 +48,10 @@ public: inline bool operator==(const QHttpPartPrivate &other) const { - return rawHeaders == other.rawHeaders && body == other.body && - bodyDevice == other.bodyDevice && readPointer == other.readPointer; + return QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders) + && body == other.body + && bodyDevice == other.bodyDevice + && readPointer == other.readPointer; } void setBodyDevice(QIODevice *device) { diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 71b880f6f8..e178d65356 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -354,7 +354,9 @@ bool QHttpNetworkConnectionChannel::ensureConnection() } if (!value.isEmpty()) { QNetworkProxy proxy(socket->proxy()); - proxy.setRawHeader("User-Agent", value); //detaches + auto h = proxy.headers(); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::UserAgent, value); + proxy.setHeaders(std::move(h)); socket->setProxy(proxy); } } diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp index 5dbcef4bbe..3c7fee567d 100644 --- a/src/network/access/qnetworkaccessbackend.cpp +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -568,6 +568,43 @@ void QNetworkAccessBackend::setRawHeader(const QByteArray &header, const QByteAr } /*! + \since 6.8 + + Returns headers that are set in this QNetworkAccessBackend instance. + + \sa setHeaders() +*/ +QHttpHeaders QNetworkAccessBackend::headers() const +{ + return d_func()->m_reply->headers(); +} + +/*! + \since 6.8 + + Sets \a newHeaders as headers, overriding any previously set headers. + + These headers are accessible on the QNetworkReply instance which was + returned when calling one of the appropriate functions on + QNetworkAccessManager. + + \sa headers() +*/ +void QNetworkAccessBackend::setHeaders(QHttpHeaders &&newHeaders) +{ + d_func()->m_reply->setHeaders(std::move(newHeaders)); +} + +/*! + \overload + \since 6.8 +*/ +void QNetworkAccessBackend::setHeaders(const QHttpHeaders &newHeaders) +{ + d_func()->m_reply->setHeaders(newHeaders); +} + +/*! Returns the operation which was requested when calling QNetworkAccessManager. */ diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h index 799ae3faad..4f8d7e4372 100644 --- a/src/network/access/qnetworkaccessbackend_p.h +++ b/src/network/access/qnetworkaccessbackend_p.h @@ -104,6 +104,9 @@ public: void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); QByteArray rawHeader(const QByteArray &header) const; void setRawHeader(const QByteArray &header, const QByteArray &value); + QHttpHeaders headers() const; + void setHeaders(const QHttpHeaders &newHeaders); + void setHeaders(QHttpHeaders &&newHeaders); QNetworkAccessManager::Operation operation() const; bool isCachingEnabled() const; diff --git a/src/network/access/qnetworkaccesscachebackend.cpp b/src/network/access/qnetworkaccesscachebackend.cpp index fd8174c143..ead5fe2ef5 100644 --- a/src/network/access/qnetworkaccesscachebackend.cpp +++ b/src/network/access/qnetworkaccesscachebackend.cpp @@ -47,21 +47,21 @@ bool QNetworkAccessCacheBackend::sendCacheContents() return false; QNetworkCacheMetaData::AttributesMap attributes = item.attributes(); - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, attributes.value(QNetworkRequest::HttpStatusCodeAttribute)); - setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); - - // set the raw headers - const QNetworkCacheMetaData::RawHeaderList rawHeaders = item.rawHeaders(); - for (const auto &header : rawHeaders) { - if (header.first.compare("cache-control", Qt::CaseInsensitive) == 0) { - const QLatin1StringView cacheControlValue(header.second); - if (cacheControlValue.contains("must-revalidate"_L1, Qt::CaseInsensitive) - || cacheControlValue.contains("no-cache"_L1, Qt::CaseInsensitive)) { - return false; - } - } - setRawHeader(header.first, header.second); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, + attributes.value(QNetworkRequest::HttpStatusCodeAttribute)); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, + attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); + + // set the headers + auto headers = item.headers(); + const auto cacheControlValue = QLatin1StringView( + headers.value(QHttpHeaders::WellKnownHeader::CacheControl)); + // RFC 9111 Section 5.2 Cache Control + if (cacheControlValue.contains("must-revalidate"_L1, Qt::CaseInsensitive) + || cacheControlValue.contains("no-cache"_L1, Qt::CaseInsensitive)) { + return false; } + setHeaders(std::move(headers)); // handle a possible redirect QVariant redirectionTarget = attributes.value(QNetworkRequest::RedirectionTargetAttribute); diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp index 4b12f9fe31..e0a89e3646 100644 --- a/src/network/access/qnetworkaccessdebugpipebackend.cpp +++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp @@ -97,7 +97,9 @@ qint64 QNetworkAccessDebugPipeBackend::read(char *data, qint64 maxlen) if (haveRead == -1) { hasDownloadFinished = true; // this ensures a good last downloadProgress is emitted - setHeader(QNetworkRequest::ContentLengthHeader, QVariant()); + auto h = headers(); + h.removeAll(QHttpHeaders::WellKnownHeader::ContentLength); + setHeaders(std::move(h)); possiblyFinish(); return haveRead; } diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 4e13c9924b..7ef062a54d 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -1255,12 +1255,14 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera } } QNetworkRequest request = req; + auto h = request.headers(); #ifndef Q_OS_WASM // Content-length header is not allowed to be set by user in wasm - if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() && + if (!h.contains(QHttpHeaders::WellKnownHeader::ContentLength) && outgoingData && !outgoingData->isSequential()) { // request has no Content-Length // but the data that is outgoing is random-access - request.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData->size()); + h.append(QHttpHeaders::WellKnownHeader::ContentLength, + QByteArray::number(outgoingData->size())); } #endif if (static_cast<QNetworkRequest::LoadControl> @@ -1269,9 +1271,11 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera if (d->cookieJar) { QList<QNetworkCookie> cookies = d->cookieJar->cookiesForUrl(request.url()); if (!cookies.isEmpty()) - request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookies)); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie, + QNetworkHeadersPrivate::fromCookieList(cookies)); } } + request.setHeaders(std::move(h)); #ifdef Q_OS_WASM Q_UNUSED(isLocalFile); // Support http, https, and relative urls @@ -1746,9 +1750,10 @@ QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkReq { // copy the request, we probably need to add some headers QNetworkRequest newRequest(request); + auto h = newRequest.headers(); // add Content-Type header if not there already - if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) { + if (!h.contains(QHttpHeaders::WellKnownHeader::ContentType)) { QByteArray contentType; contentType.reserve(34 + multiPart->d_func()->boundary.size()); contentType += "multipart/"; @@ -1768,14 +1773,15 @@ QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkReq } // putting the boundary into quotes, recommended in RFC 2046 section 5.1.1 contentType += "; boundary=\"" + multiPart->d_func()->boundary + '"'; - newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType)); + h.append(QHttpHeaders::WellKnownHeader::ContentType, contentType); } // add MIME-Version header if not there already (we must include the header // if the message conforms to RFC 2045, see section 4 of that RFC) - auto mimeHeader = "MIME-Version"_ba; - if (!request.hasRawHeader(mimeHeader)) - newRequest.setRawHeader(mimeHeader, "1.0"_ba); + if (!h.contains(QHttpHeaders::WellKnownHeader::MIMEVersion)) + h.append(QHttpHeaders::WellKnownHeader::MIMEVersion, "1.0"_ba); + + newRequest.setHeaders(std::move(h)); QIODevice *device = multiPart->d_func()->device; if (!device->isReadable()) { diff --git a/src/network/access/qnetworkfile.cpp b/src/network/access/qnetworkfile.cpp index bfedf044de..fb9ce8232d 100644 --- a/src/network/access/qnetworkfile.cpp +++ b/src/network/access/qnetworkfile.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qnetworkfile_p.h" +#include "qnetworkrequest_p.h" #include <QtCore/QDebug> #include <QNetworkReply> @@ -31,8 +32,10 @@ void QNetworkFile::open() "Cannot open %1: Path is a directory").arg(fileName()); emit error(QNetworkReply::ContentOperationNotPermittedError, msg); } else { - emit headerRead(QNetworkRequest::LastModifiedHeader, QVariant::fromValue(fi.lastModified())); - emit headerRead(QNetworkRequest::ContentLengthHeader, QVariant::fromValue(fi.size())); + emit headerRead(QHttpHeaders::WellKnownHeader::LastModified, + QNetworkHeadersPrivate::toHttpDate(fi.lastModified())); + emit headerRead(QHttpHeaders::WellKnownHeader::ContentLength, + QByteArray::number(fi.size())); opened = QFile::open(QIODevice::ReadOnly | QIODevice::Unbuffered); if (!opened) { QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", diff --git a/src/network/access/qnetworkfile_p.h b/src/network/access/qnetworkfile_p.h index df772251b4..9dcb63711e 100644 --- a/src/network/access/qnetworkfile_p.h +++ b/src/network/access/qnetworkfile_p.h @@ -35,7 +35,7 @@ public Q_SLOTS: Q_SIGNALS: void finished(bool ok); - void headerRead(QNetworkRequest::KnownHeaders header, const QVariant &value); + void headerRead(QHttpHeaders::WellKnownHeader, const QByteArray &value); void error(QNetworkReply::NetworkError error, const QString &message); }; diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp index 0613a65c34..3301ae85d2 100644 --- a/src/network/access/qnetworkreply.cpp +++ b/src/network/access/qnetworkreply.cpp @@ -617,7 +617,7 @@ QVariant QNetworkReply::header(QNetworkRequest::KnownHeaders header) const bool QNetworkReply::hasRawHeader(QAnyStringView headerName) const { Q_D(const QNetworkReply); - return d->findRawHeader(headerName) != d->rawHeaders.constEnd(); + return d->headers().contains(headerName); } /*! @@ -633,9 +633,7 @@ bool QNetworkReply::hasRawHeader(QAnyStringView headerName) const QByteArray QNetworkReply::rawHeader(QAnyStringView headerName) const { Q_D(const QNetworkReply); - if (const auto it = d->findRawHeader(headerName); it != d->rawHeaders.constEnd()) - return it->second; - return QByteArray(); + return d->rawHeader(headerName); } /*! \typedef QNetworkReply::RawHeaderPair @@ -650,7 +648,7 @@ QByteArray QNetworkReply::rawHeader(QAnyStringView headerName) const const QList<QNetworkReply::RawHeaderPair>& QNetworkReply::rawHeaderPairs() const { Q_D(const QNetworkReply); - return d->rawHeaders; + return d->allRawHeaders(); } /*! diff --git a/src/network/access/qnetworkreplydataimpl.cpp b/src/network/access/qnetworkreplydataimpl.cpp index 7cb7621bca..006cfd57cb 100644 --- a/src/network/access/qnetworkreplydataimpl.cpp +++ b/src/network/access/qnetworkreplydataimpl.cpp @@ -36,8 +36,11 @@ QNetworkReplyDataImpl::QNetworkReplyDataImpl(QObject *parent, const QNetworkRequ QByteArray payload; if (qDecodeDataUrl(url, mimeType, payload)) { qint64 size = payload.size(); - setHeader(QNetworkRequest::ContentTypeHeader, mimeType); - setHeader(QNetworkRequest::ContentLengthHeader, size); + auto h = headers(); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentType, mimeType); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentLength, QByteArray::number(size)); + setHeaders(std::move(h)); + QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection); d->decodedData.setData(payload); diff --git a/src/network/access/qnetworkreplyfileimpl.cpp b/src/network/access/qnetworkreplyfileimpl.cpp index e6208a5c85..bad0bb7b0a 100644 --- a/src/network/access/qnetworkreplyfileimpl.cpp +++ b/src/network/access/qnetworkreplyfileimpl.cpp @@ -85,7 +85,7 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, con if (req.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open auto realFile = new QNetworkFile(fileName); - connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setHeader, + connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setWellKnownHeader, Qt::QueuedConnection); connect(realFile, &QNetworkFile::error, this, &QNetworkReplyFileImpl::setError, Qt::QueuedConnection); @@ -128,8 +128,12 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, con QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); return; } - setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified()); - setHeader(QNetworkRequest::ContentLengthHeader, fi.size()); + auto h = headers(); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::LastModified, + QNetworkHeadersPrivate::toHttpDate(fi.lastModified())); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentLength, + QByteArray::number(fi.size())); + setHeaders(std::move(h)); QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection, @@ -171,9 +175,9 @@ bool QNetworkReplyFileImpl::isSequential () const qint64 QNetworkReplyFileImpl::size() const { - bool ok; - int size = header(QNetworkRequest::ContentLengthHeader).toInt(&ok); - return ok ? size : 0; + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + return totalSizeOpt.value_or(0); } /*! diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 1eee98f834..b23550f364 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -202,7 +202,10 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage if (bufferingDisallowed) { // if a valid content-length header for the request was supplied, we can disable buffering // if not, we will buffer anyway - if (request.header(QNetworkRequest::ContentLengthHeader).isValid()) { + + const auto sizeOpt = QNetworkHeadersPrivate::toInt( + request.headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + if (sizeOpt) { QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection); // FIXME make direct call? } else { @@ -478,10 +481,12 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h { QNetworkRequest::CacheLoadControl CacheLoadControlAttribute = (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(); + + auto requestHeaders = request.headers(); if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) { // If the request does not already specify preferred cache-control // force reload from the network and tell any caching proxy servers to reload too - if (!request.rawHeaderList().contains(cacheControlName())) { + if (!requestHeaders.contains(QHttpHeaders::WellKnownHeader::CacheControl)) { const auto noCache = "no-cache"_ba; httpRequest.setHeaderField(cacheControlName(), noCache); httpRequest.setHeaderField("Pragma"_ba, noCache); @@ -491,7 +496,7 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h // The disk cache API does not currently support partial content retrieval. // That is why we don't use the disk cache for any such requests. - if (request.hasRawHeader(rangeName())) + if (requestHeaders.contains(QHttpHeaders::WellKnownHeader::Range)) return false; QAbstractNetworkCache *nc = managerPrivate->networkCache; @@ -505,28 +510,27 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h if (!metaData.saveToDisk()) return false; - QNetworkHeadersPrivate cacheHeaders; - QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; - cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); + QHttpHeaders cacheHeaders = metaData.headers(); - it = cacheHeaders.findRawHeader("content-length"); - if (it != cacheHeaders.rawHeaders.constEnd()) { + const auto sizeOpt = QNetworkHeadersPrivate::toInt( + cacheHeaders.value(QHttpHeaders::WellKnownHeader::ContentLength)); + if (sizeOpt) { std::unique_ptr<QIODevice> data(nc->data(httpRequest.url())); - if (!data || data->size() < it->second.toLongLong()) + if (!data || data->size() < sizeOpt.value()) return false; // The data is smaller than the content-length specified } - it = cacheHeaders.findRawHeader("etag"); - if (it != cacheHeaders.rawHeaders.constEnd()) - httpRequest.setHeaderField("If-None-Match"_ba, it->second); + auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::ETag); + if (!value.empty()) + httpRequest.setHeaderField("If-None-Match"_ba, value.toByteArray()); QDateTime lastModified = metaData.lastModified(); if (lastModified.isValid()) httpRequest.setHeaderField("If-Modified-Since"_ba, QNetworkHeadersPrivate::toHttpDate(lastModified)); - it = cacheHeaders.findRawHeader(cacheControlName()); - if (it != cacheHeaders.rawHeaders.constEnd()) { - QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second); + value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl); + if (!value.empty()) { + QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value); if (cacheControl.contains("must-revalidate"_ba)) return false; if (cacheControl.contains("no-cache"_ba)) @@ -553,16 +557,15 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h * now * is the current (local) time */ - qint64 age_value = 0; - it = cacheHeaders.findRawHeader("age"); - if (it != cacheHeaders.rawHeaders.constEnd()) - age_value = it->second.toLongLong(); + const auto ageOpt = QNetworkHeadersPrivate::toInt( + cacheHeaders.value(QHttpHeaders::WellKnownHeader::Age)); + const qint64 age_value = ageOpt.value_or(0); QDateTime dateHeader; qint64 date_value = 0; - it = cacheHeaders.findRawHeader("date"); - if (it != cacheHeaders.rawHeaders.constEnd()) { - dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second); + value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::Date); + if (!value.empty()) { + dateHeader = QNetworkHeadersPrivate::fromHttpDate(value); date_value = dateHeader.toSecsSinceEpoch(); } @@ -744,16 +747,15 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq break; // can't happen } - QList<QByteArray> headers = newHttpRequest.rawHeaderList(); + QHttpHeaders newRequestHeaders = newHttpRequest.headers(); if (resumeOffset != 0) { - const int rangeIndex = headers.indexOf(rangeName()); - if (rangeIndex != -1) { + if (newRequestHeaders.contains(QHttpHeaders::WellKnownHeader::Range)) { // Need to adjust resume offset for user specified range - headers.removeAt(rangeIndex); + newRequestHeaders.removeAll(QHttpHeaders::WellKnownHeader::Range); // We've already verified that requestRange starts with "bytes=", see canResume. - const auto rangeHeader = newHttpRequest.rawHeader(rangeName()); + const auto rangeHeader = newHttpRequest.headers().value(QHttpHeaders::WellKnownHeader::Range); const auto requestRange = QByteArrayView(rangeHeader).mid(bytesEqualPrefix().size()); int index = requestRange.indexOf('-'); @@ -771,8 +773,11 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq } } - for (const QByteArray &header : std::as_const(headers)) - httpRequest.setHeaderField(header, newHttpRequest.rawHeader(header)); + for (int i = 0; i < newRequestHeaders.size(); i++) { + const auto name = newRequestHeaders.nameAt(i); + const auto value = newRequestHeaders.valueAt(i); + httpRequest.setHeaderField(QByteArray(name.data(), name.size()), value.toByteArray()); + } if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool()) httpRequest.setPipeliningAllowed(true); @@ -1151,7 +1156,8 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d) } lastReadyReadEmittedSize = bytesDownloaded; - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); emit q->readyRead(); // emit readyRead before downloadProgress in case this will cause events to be @@ -1159,8 +1165,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d) if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval && (!decompressHelper.isValid() || decompressHelper.isCountingBytes())) { downloadProgressSignalChoke.restart(); - emit q->downloadProgress(bytesDownloaded, - totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1)); } } @@ -1255,6 +1260,7 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt // Clear stale headers, the relevant ones get set again later httpRequest.clearHeaders(); + auto newHeaders = redirectRequest.headers(); if ((operation == QNetworkAccessManager::GetOperation || operation == QNetworkAccessManager::HeadOperation) && !getOperationKeepsBody) { // possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device @@ -1269,18 +1275,20 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt outgoingData = nullptr; outgoingDataBuffer.reset(); // We need to explicitly unset these headers so they're not reapplied to the httpRequest - redirectRequest.setHeader(QNetworkRequest::ContentLengthHeader, QVariant()); - redirectRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant()); + newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentLength); + newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentType); } if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) { auto cookies = cookieJar->cookiesForUrl(url); if (!cookies.empty()) { - redirectRequest.setHeader(QNetworkRequest::KnownHeaders::CookieHeader, - QVariant::fromValue(cookies)); + auto cookieHeader = QNetworkHeadersPrivate::fromCookieList(cookies); + newHeaders.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie, cookieHeader); } } + redirectRequest.setHeaders(std::move(newHeaders)); + if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy) followRedirect(); @@ -1293,8 +1301,7 @@ void QNetworkReplyHttpImplPrivate::followRedirect() Q_ASSERT(managerPrivate); decompressHelper.clear(); - rawHeaders.clear(); - cookedHeaders.clear(); + clearHeaders(); if (managerPrivate->thread) managerPrivate->thread->disconnect(); @@ -1317,7 +1324,7 @@ void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode) // What do we do about the caching of the HTML note? // The response to a 303 MUST NOT be cached, while the response to // all of the others is cacheable if the headers indicate it to be - QByteArray header = q->rawHeader(locationHeader()); + QByteArrayView header = q->headers().value(locationHeader()); QUrl url = QUrl(QString::fromUtf8(header)); if (!url.isValid()) url = QUrl(QLatin1StringView(header)); @@ -1361,20 +1368,19 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm, // A user having manually defined which encodings they accept is, for // somwehat unknown (presumed legacy compatibility) reasons treated as // disabling our decompression: - const bool autoDecompress = request.rawHeader("accept-encoding").isEmpty(); + const bool autoDecompress = !request.headers().contains(QHttpHeaders::WellKnownHeader::AcceptEncoding); const bool shouldDecompress = isCompressed && autoDecompress; // reconstruct the HTTP header + auto h = q->headers(); for (qsizetype i = 0; i < hm.size(); ++i) { const auto key = hm.nameAt(i); const auto originValue = hm.valueAt(i); - QByteArray value = q->rawHeader(key); - // Reset any previous "location" header set in the reply. In case of // redirects, we don't want to 'append' multiple location header values, // rather we keep only the latest one - if (key == locationHeader()) - value.clear(); + if (key.compare(locationHeader(), Qt::CaseInsensitive) == 0) + h.removeAll(key); if (shouldDecompress && !decompressHelper.isValid() && key == "content-encoding"_L1) { if (!synchronous) // with synchronous all the data is expected to be handled at once @@ -1390,17 +1396,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm, request.decompressedSafetyCheckThreshold()); } - if (!value.isEmpty()) { - // Why are we appending values for headers which are already - // present? - if (key == "set-cookie"_L1) - value += '\n'; - else - value += ", "; - } - value += originValue; - q->setRawHeader({key.data(), key.size()}, value); + h.append(key, originValue); } + q->setHeaders(std::move(h)); q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase); @@ -1415,13 +1413,10 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm, QAbstractNetworkCache *nc = managerPrivate->networkCache; if (nc) { QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url()); - QNetworkHeadersPrivate cacheHeaders; - cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); - QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; - it = cacheHeaders.findRawHeader(cacheControlName()); + auto value = metaData.headers().value(QHttpHeaders::WellKnownHeader::CacheControl); bool mustReValidate = false; - if (it != cacheHeaders.rawHeaders.constEnd()) { - QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second); + if (!value.empty()) { + QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value); if (cacheControl.contains("must-revalidate"_ba)) mustReValidate = true; } @@ -1660,16 +1655,21 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); - QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders(); - QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(), - end = rawHeaders.constEnd(); + QHttpHeaders cachedHeaders = metaData.headers(); + QHttpHeaders h = headers(); QUrl redirectUrl; - for ( ; it != end; ++it) { - if (httpRequest.isFollowRedirects() && - !it->first.compare(locationHeader(), Qt::CaseInsensitive)) - redirectUrl = QUrl::fromEncoded(it->second); - setRawHeader(it->first, it->second); + for (qsizetype i = 0; i < cachedHeaders.size(); ++i) { + const auto name = cachedHeaders.nameAt(i); + const auto value = cachedHeaders.valueAt(i); + + if (httpRequest.isFollowRedirects() + && !name.compare(locationHeader(), Qt::CaseInsensitive)) { + redirectUrl = QUrl::fromEncoded(value); + } + + h.replaceOrAppend(name, value); } + setHeaders(std::move(h)); if (!isHttpRedirectResponse()) checkForRedirect(status); @@ -1729,17 +1729,17 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe Q_Q(const QNetworkReplyHttpImpl); QNetworkCacheMetaData metaData = oldMetaData; + QHttpHeaders cacheHeaders = metaData.headers(); - QNetworkHeadersPrivate cacheHeaders; - cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); - QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; + const auto newHeaders = q->headers(); + for (qsizetype i = 0; i < newHeaders.size(); ++i) { + const auto name = newHeaders.nameAt(i); + const auto value = newHeaders.valueAt(i); - const QList<QByteArray> newHeaders = q->rawHeaderList(); - for (const QByteArray& header : newHeaders) { - if (isHopByHop(header)) + if (isHopByHop(name)) continue; - if (header.compare("set-cookie", Qt::CaseInsensitive) == 0) + if (name.compare("set-cookie", Qt::CaseInsensitive) == 0) continue; // for 4.6.0, we were planning to not store the date header in the @@ -1752,50 +1752,46 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe //continue; // Don't store Warning 1xx headers - if (header.compare("warning", Qt::CaseInsensitive) == 0) { - const QByteArray v = q->rawHeader(header); - if (v.size() == 3 - && v[0] == '1' - && isAsciiDigit(v[1]) - && isAsciiDigit(v[2])) + if (name.compare("warning", Qt::CaseInsensitive) == 0) { + if (value.size() == 3 + && value[0] == '1' + && isAsciiDigit(value[1]) + && isAsciiDigit(value[2])) continue; } - it = cacheHeaders.findRawHeader(header); - if (it != cacheHeaders.rawHeaders.constEnd()) { + if (cacheHeaders.contains(name)) { // Match the behavior of Firefox and assume Cache-Control: "no-transform" constexpr QByteArrayView headers[]= {"content-encoding", "content-range", "content-type"}; - if (std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(header))) + if (std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(name))) continue; } // IIS has been known to send "Content-Length: 0" on 304 responses, so // ignore this too - if (statusCode == 304 && header.compare("content-length", Qt::CaseInsensitive) == 0) + if (statusCode == 304 && name.compare("content-length", Qt::CaseInsensitive) == 0) continue; #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) - QByteArray n = q->rawHeader(header); - QByteArray o; - if (it != cacheHeaders.rawHeaders.constEnd()) - o = (*it).second; - if (n != o && headerheader.compare("date", Qt::CaseInsensitive) != 0) { - qDebug() << "replacing" << header; + QByteArrayView n = newHeaders.value(name); + QByteArrayView o = cacheHeaders.value(name); + if (n != o && name.compare("date", Qt::CaseInsensitive) != 0) { + qDebug() << "replacing" << name; qDebug() << "new" << n; qDebug() << "old" << o; } #endif - cacheHeaders.setRawHeader(header, q->rawHeader(header)); + cacheHeaders.replaceOrAppend(name, value); } - metaData.setRawHeaders(cacheHeaders.rawHeaders); + metaData.setHeaders(cacheHeaders); bool checkExpired = true; QHash<QByteArray, QByteArray> cacheControl; - it = cacheHeaders.findRawHeader(cacheControlName()); - if (it != cacheHeaders.rawHeaders.constEnd()) { - cacheControl = parseHttpOptionHeader(it->second); + auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl); + if (!value.empty()) { + cacheControl = parseHttpOptionHeader(value); QByteArray maxAge = cacheControl.value("max-age"_ba); if (!maxAge.isEmpty()) { checkExpired = false; @@ -1805,16 +1801,18 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe } } if (checkExpired) { - it = cacheHeaders.findRawHeader("expires"); - if (it != cacheHeaders.rawHeaders.constEnd()) { - QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second); + if (const auto value = cacheHeaders.value( + QHttpHeaders::WellKnownHeader::Expires); !value.isEmpty()) { + QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(value); metaData.setExpirationDate(expiredDateTime); } } - it = cacheHeaders.findRawHeader("last-modified"); - if (it != cacheHeaders.rawHeaders.constEnd()) - metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second)); + if (const auto value = cacheHeaders.value( + QHttpHeaders::WellKnownHeader::LastModified); !value.isEmpty()) { + metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(value)); + } + bool canDiskCache; // only cache GET replies by default, all other replies (POST, PUT, DELETE) @@ -1862,14 +1860,16 @@ bool QNetworkReplyHttpImplPrivate::canResume() const if (operation != QNetworkAccessManager::GetOperation) return false; + const auto h = q->headers(); + // Can only resume if server/resource supports Range header. - constexpr auto acceptRangesheaderName = QByteArrayView("Accept-Ranges"); - if (!q->hasRawHeader(acceptRangesheaderName) || q->rawHeader(acceptRangesheaderName) == "none") + const auto acceptRanges = h.value(QHttpHeaders::WellKnownHeader::AcceptRanges); + if (acceptRanges.empty() || acceptRanges == "none") return false; // We only support resuming for byte ranges. - if (request.hasRawHeader(rangeName())) { - QByteArray range = request.rawHeader(rangeName()); + const auto range = h.value(QHttpHeaders::WellKnownHeader::Range); + if (!range.empty()) { if (!range.startsWith(bytesEqualPrefix())) return false; } @@ -1918,8 +1918,8 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead() // Needs to be done where sendCacheContents() (?) of HTTP is emitting // metaDataChanged ? - - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); // emit readyRead before downloadProgress in case this will cause events to be // processed and we get into a recursive call (as in QProgressDialog). @@ -1930,8 +1930,7 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead() if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) { downloadProgressSignalChoke.restart(); - emit q->downloadProgress(bytesDownloaded, - totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1)); } } @@ -2118,11 +2117,13 @@ void QNetworkReplyHttpImplPrivate::finished() if (state == Finished || state == Aborted) return; - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + const qint64 totalSize = totalSizeOpt.value_or(-1); // if we don't know the total size of or we received everything save the cache. // If the data is compressed then this is done in readData() - if ((totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize) + if ((totalSize == -1 || bytesDownloaded == totalSize) && !decompressHelper.isValid()) { completeCacheSave(); } @@ -2135,10 +2136,10 @@ void QNetworkReplyHttpImplPrivate::finished() state = Finished; q->setFinished(true); - if (totalSize.isNull() || totalSize == -1) { + if (totalSize == -1) { emit q->downloadProgress(bytesDownloaded, bytesDownloaded); } else { - emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSize); } if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer)) @@ -2182,14 +2183,15 @@ void QNetworkReplyHttpImplPrivate::_q_metaDataChanged() // 1. do we have cookies? // 2. are we allowed to set them? Q_ASSERT(manager); - const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader); - if (it != cookedHeaders.cend() + + const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList( + headers().values(QHttpHeaders::WellKnownHeader::SetCookie)); + const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>()); + if (!cookies.empty() && request.attribute(QNetworkRequest::CookieSaveControlAttribute, QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) { QNetworkCookieJar *jar = manager->cookieJar(); if (jar) { - QList<QNetworkCookie> cookies = - qvariant_cast<QList<QNetworkCookie> >(it.value()); jar->setCookiesFromUrl(cookies, url); } } diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index 8b2acfdb4e..b90ec1cc4c 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -118,15 +118,16 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead() return; } - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + pauseNotificationHandling(); // emit readyRead before downloadProgress in case this will cause events to be // processed and we get into a recursive call (as in QProgressDialog). emit q->readyRead(); if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) { downloadProgressSignalChoke.restart(); - emit q->downloadProgress(bytesDownloaded, - totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1)); } resumeNotificationHandling(); } @@ -243,7 +244,10 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const if (bufferingDisallowed) { // if a valid content-length header for the request was supplied, we can disable buffering // if not, we will buffer anyway - if (req.header(QNetworkRequest::ContentLengthHeader).isValid()) { + const auto sizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + + if (sizeOpt) { QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); } else { state = Buffering; @@ -478,7 +482,8 @@ void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions() { Q_Q(QNetworkReplyImpl); - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); pauseNotificationHandling(); // important: At the point of this readyRead(), the data parameter list must be empty, // else implicit sharing will trigger memcpy when the user is reading data! @@ -487,8 +492,7 @@ void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions() // processed and we get into a recursive call (as in QProgressDialog). if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) { downloadProgressSignalChoke.restart(); - emit q->downloadProgress(bytesDownloaded, - totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1)); } resumeNotificationHandling(); @@ -589,7 +593,9 @@ void QNetworkReplyImplPrivate::finished() return; pauseNotificationHandling(); - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + const auto totalSize = totalSizeOpt.value_or(-1); resumeNotificationHandling(); @@ -599,10 +605,10 @@ void QNetworkReplyImplPrivate::finished() pendingNotifications.clear(); pauseNotificationHandling(); - if (totalSize.isNull() || totalSize == -1) { + if (totalSize == -1) { emit q->downloadProgress(bytesDownloaded, bytesDownloaded); } else { - emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong()); + emit q->downloadProgress(bytesDownloaded, totalSize); } if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer)) @@ -610,7 +616,7 @@ void QNetworkReplyImplPrivate::finished() resumeNotificationHandling(); // if we don't know the total size of or we received everything save the cache - if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize) + if (totalSize == -1 || bytesDownloaded == totalSize) completeCacheSave(); // note: might not be a good idea, since users could decide to delete us @@ -646,14 +652,14 @@ void QNetworkReplyImplPrivate::metaDataChanged() // 1. do we have cookies? // 2. are we allowed to set them? if (!manager.isNull()) { - const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader); - if (it != cookedHeaders.cend() + const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList( + headers().values(QHttpHeaders::WellKnownHeader::SetCookie)); + const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>()); + if (!cookies.empty() && request.attribute(QNetworkRequest::CookieSaveControlAttribute, QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) { QNetworkCookieJar *jar = manager->cookieJar(); if (jar) { - QList<QNetworkCookie> cookies = - qvariant_cast<QList<QNetworkCookie> >(it.value()); jar->setCookiesFromUrl(cookies, url); } } @@ -857,9 +863,10 @@ qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen) break; } } - QVariant totalSize = d->cookedHeaders.value(QNetworkRequest::ContentLengthHeader); - emit downloadProgress(bytesRead, - totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + + const auto totalSizeOpt = QNetworkHeadersPrivate::toInt( + headers().value(QHttpHeaders::WellKnownHeader::ContentLength)); + emit downloadProgress(bytesRead, totalSizeOpt.value_or(-1)); return bytesRead; } else if (d->backend && d->backend->bytesAvailable()) { return d->backend->read(data, maxlen); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index a59de42a44..7a1b5426d2 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -17,6 +17,7 @@ #include "QtCore/qlocale.h" #include "QtCore/qshareddata.h" #include "QtCore/qtimezone.h" +#include "QtCore/private/qduplicatetracker_p.h" #include "QtCore/private/qtools_p.h" #include <ctype.h> @@ -25,6 +26,7 @@ #endif #include <algorithm> +#include <q20algorithm.h> QT_BEGIN_NAMESPACE @@ -110,6 +112,8 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_ \value ServerHeader The Server header received by HTTP clients. + \omitvalue NumKnownHeaders + \sa header(), setHeader(), rawHeader(), setRawHeader() */ @@ -466,7 +470,6 @@ public: { return url == other.url && priority == other.priority && - rawHeaders == other.rawHeaders && attributes == other.attributes && maxRedirectsAllowed == other.maxRedirectsAllowed && peerVerifyName == other.peerVerifyName @@ -672,7 +675,7 @@ void QNetworkRequest::setHeader(KnownHeaders header, const QVariant &value) */ bool QNetworkRequest::hasRawHeader(QAnyStringView headerName) const { - return d->findRawHeader(headerName) != d->rawHeaders.constEnd(); + return d->headers().contains(headerName); } /*! @@ -688,9 +691,7 @@ bool QNetworkRequest::hasRawHeader(QAnyStringView headerName) const */ QByteArray QNetworkRequest::rawHeader(QAnyStringView headerName) const { - if (const auto it = d->findRawHeader(headerName); it != d->rawHeaders.constEnd()) - return it->second; - return QByteArray(); + return d->rawHeader(headerName); } /*! @@ -1077,63 +1078,72 @@ void QNetworkRequest::setTransferTimeout(std::chrono::milliseconds duration) } #endif // QT_CONFIG(http) || defined (Q_OS_WASM) -static QByteArray headerName(QNetworkRequest::KnownHeaders header) -{ - switch (header) { - case QNetworkRequest::ContentTypeHeader: - return "Content-Type"_ba; - - case QNetworkRequest::ContentLengthHeader: - return "Content-Length"_ba; - - case QNetworkRequest::LocationHeader: - return "Location"_ba; - - case QNetworkRequest::LastModifiedHeader: - return "Last-Modified"_ba; - - case QNetworkRequest::IfModifiedSinceHeader: - return "If-Modified-Since"_ba; - - case QNetworkRequest::ETagHeader: - return "ETag"_ba; +namespace { - case QNetworkRequest::IfMatchHeader: - return "If-Match"_ba; - - case QNetworkRequest::IfNoneMatchHeader: - return "If-None-Match"_ba; +struct HeaderPair { + QHttpHeaders::WellKnownHeader wellKnownHeader; + QNetworkRequest::KnownHeaders knownHeader; +}; - case QNetworkRequest::CookieHeader: - return "Cookie"_ba; +constexpr bool operator<(const HeaderPair &lhs, const HeaderPair &rhs) +{ + return lhs.wellKnownHeader < rhs.wellKnownHeader; +} - case QNetworkRequest::SetCookieHeader: - return "Set-Cookie"_ba; +constexpr bool operator<(const HeaderPair &lhs, QHttpHeaders::WellKnownHeader rhs) +{ + return lhs.wellKnownHeader < rhs; +} - case QNetworkRequest::ContentDispositionHeader: - return "Content-Disposition"_ba; +constexpr bool operator<(QHttpHeaders::WellKnownHeader lhs, const HeaderPair &rhs) +{ + return lhs < rhs.wellKnownHeader; +} - case QNetworkRequest::UserAgentHeader: - return "User-Agent"_ba; +} // anonymous namespace - case QNetworkRequest::ServerHeader: - return "Server"_ba; +static constexpr HeaderPair knownHeadersArr[] = { + { QHttpHeaders::WellKnownHeader::ContentDisposition, QNetworkRequest::KnownHeaders::ContentDispositionHeader }, + { QHttpHeaders::WellKnownHeader::ContentLength, QNetworkRequest::KnownHeaders::ContentLengthHeader }, + { QHttpHeaders::WellKnownHeader::ContentType, QNetworkRequest::KnownHeaders::ContentTypeHeader }, + { QHttpHeaders::WellKnownHeader::Cookie, QNetworkRequest::KnownHeaders::CookieHeader }, + { QHttpHeaders::WellKnownHeader::ETag, QNetworkRequest::KnownHeaders::ETagHeader }, + { QHttpHeaders::WellKnownHeader::IfMatch , QNetworkRequest::KnownHeaders::IfMatchHeader }, + { QHttpHeaders::WellKnownHeader::IfModifiedSince, QNetworkRequest::KnownHeaders::IfModifiedSinceHeader }, + { QHttpHeaders::WellKnownHeader::IfNoneMatch, QNetworkRequest::KnownHeaders::IfNoneMatchHeader }, + { QHttpHeaders::WellKnownHeader::LastModified, QNetworkRequest::KnownHeaders::LastModifiedHeader}, + { QHttpHeaders::WellKnownHeader::Location, QNetworkRequest::KnownHeaders::LocationHeader}, + { QHttpHeaders::WellKnownHeader::Server, QNetworkRequest::KnownHeaders::ServerHeader }, + { QHttpHeaders::WellKnownHeader::SetCookie, QNetworkRequest::KnownHeaders::SetCookieHeader }, + { QHttpHeaders::WellKnownHeader::UserAgent, QNetworkRequest::KnownHeaders::UserAgentHeader } +}; - // no default: - // if new values are added, this will generate a compiler warning - } +static_assert(std::size(knownHeadersArr) == size_t(QNetworkRequest::KnownHeaders::NumKnownHeaders)); +static_assert(q20::is_sorted(std::begin(knownHeadersArr), std::end(knownHeadersArr))); - return QByteArray(); +static std::optional<QNetworkRequest::KnownHeaders> toKnownHeader(QHttpHeaders::WellKnownHeader key) +{ + const auto it = std::lower_bound(std::begin(knownHeadersArr), std::end(knownHeadersArr), key); + if (it == std::end(knownHeadersArr) || key < *it) + return std::nullopt; + return it->knownHeader; } -static QByteArray makeCookieHeader(const QVariant &value, QNetworkCookie::RawForm type, QByteArrayView separator) +static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QNetworkRequest::KnownHeaders key) { - QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(value); - if (cookies.isEmpty() && value.userType() == qMetaTypeId<QNetworkCookie>()) - cookies << qvariant_cast<QNetworkCookie>(value); + auto pred = [key](const HeaderPair &pair) { return pair.knownHeader == key; }; + const auto it = std::find_if(std::begin(knownHeadersArr), std::end(knownHeadersArr), pred); + if (it == std::end(knownHeadersArr)) + return std::nullopt; + return it->wellKnownHeader; +} +static QByteArray makeCookieHeader(const QList<QNetworkCookie> &cookies, + QNetworkCookie::RawForm type, + QByteArrayView separator) +{ QByteArray result; - for (const QNetworkCookie &cookie : std::as_const(cookies)) { + for (const QNetworkCookie &cookie : cookies) { result += cookie.toRawForm(type); result += separator; } @@ -1142,6 +1152,15 @@ static QByteArray makeCookieHeader(const QVariant &value, QNetworkCookie::RawFor return result; } +static QByteArray makeCookieHeader(const QVariant &value, QNetworkCookie::RawForm type, + QByteArrayView separator) +{ + const QList<QNetworkCookie> *cookies = get_if<QList<QNetworkCookie>>(&value); + if (!cookies) + return {}; + return makeCookieHeader(*cookies, type, separator); +} + static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVariant &value) { switch (header) { @@ -1182,9 +1201,10 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria case QNetworkRequest::SetCookieHeader: return makeCookieHeader(value, QNetworkCookie::Full, ", "); - } - return QByteArray(); + default: + Q_UNREACHABLE_RETURN({}); + } } static int parseHeaderName(QByteArrayView headerName) @@ -1245,7 +1265,7 @@ static int parseHeaderName(QByteArrayView headerName) return -1; // nothing found } -static QVariant parseHttpDate(const QByteArray &raw) +static QVariant parseHttpDate(QByteArrayView raw) { QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(raw); if (dt.isValid()) @@ -1253,18 +1273,18 @@ static QVariant parseHttpDate(const QByteArray &raw) return QVariant(); // transform an invalid QDateTime into a null QVariant } -static QVariant parseCookieHeader(QByteArrayView raw) +static QList<QNetworkCookie> parseCookieHeader(QByteArrayView raw) { QList<QNetworkCookie> result; for (auto cookie : QLatin1StringView(raw).tokenize(';'_L1)) { QList<QNetworkCookie> parsed = QNetworkCookie::parseCookies(cookie.trimmed()); if (parsed.size() != 1) - return QVariant(); // invalid Cookie: header + return {}; // invalid Cookie: header result += parsed; } - return QVariant::fromValue(result); + return result; } static QVariant parseETag(QByteArrayView raw) @@ -1280,7 +1300,7 @@ static QVariant parseETag(QByteArrayView raw) } template<typename T> -static QVariant parseMatchImpl(QByteArrayView raw, T op) +static QStringList parseMatchImpl(QByteArrayView raw, T op) { const QByteArrayView trimmedRaw = raw.trimmed(); if (trimmedRaw == "*") @@ -1295,14 +1315,14 @@ static QVariant parseMatchImpl(QByteArrayView raw, T op) } -static QVariant parseIfMatch(QByteArrayView raw) +static QStringList parseIfMatch(QByteArrayView raw) { return parseMatchImpl(raw, [](QByteArrayView element) { return element.startsWith('"') && element.endsWith('"'); }); } -static QVariant parseIfNoneMatch(QByteArrayView raw) +static QStringList parseIfNoneMatch(QByteArrayView raw) { return parseMatchImpl(raw, [](QByteArrayView element) { return (element.startsWith('"') || element.startsWith(R"(W/")")) && element.endsWith('"'); @@ -1310,7 +1330,7 @@ static QVariant parseIfNoneMatch(QByteArrayView raw) } -static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QByteArray &value) +static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, QByteArrayView value) { // header is always a valid value switch (header) { @@ -1350,40 +1370,127 @@ static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QBy return parseIfNoneMatch(value); case QNetworkRequest::CookieHeader: - return parseCookieHeader(value); + return QVariant::fromValue(parseCookieHeader(value)); case QNetworkRequest::SetCookieHeader: return QVariant::fromValue(QNetworkCookie::parseCookies(value)); default: - Q_ASSERT(0); + Q_UNREACHABLE_RETURN({}); + } +} + +static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, QList<QByteArray> values) +{ + if (values.empty()) + return QVariant(); + + // header is always a valid value + switch (header) { + case QNetworkRequest::IfMatchHeader: { + QStringList res; + for (const auto &val : values) + res << parseIfMatch(val); + return res; + } + case QNetworkRequest::IfNoneMatchHeader: { + QStringList res; + for (const auto &val : values) + res << parseIfNoneMatch(val); + return res; + } + case QNetworkRequest::CookieHeader: { + auto listOpt = QNetworkHeadersPrivate::toCookieList(values); + return listOpt.has_value() ? QVariant::fromValue(listOpt.value()) : QVariant(); + } + case QNetworkRequest::SetCookieHeader: { + QList<QNetworkCookie> res; + for (const auto &val : values) + res << QNetworkCookie::parseCookies(val); + return QVariant::fromValue(res); + } + default: + return parseHeaderValue(header, values.first()); } return QVariant(); } -QNetworkHeadersPrivate::RawHeadersList::ConstIterator -QNetworkHeadersPrivate::findRawHeader(QAnyStringView key) const +static bool isSetCookie(QByteArrayView name) { - auto isKeyEqual = [key](const auto &headerPair) - { - QLatin1StringView name{headerPair.first}; - return QAnyStringView::compare(name, key, Qt::CaseInsensitive) == 0; - }; - return std::find_if(rawHeaders.begin(), rawHeaders.end(), isKeyEqual); + return name.compare(QHttpHeaders::wellKnownHeaderName(QHttpHeaders::WellKnownHeader::SetCookie), + Qt::CaseInsensitive) == 0; } -QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::allRawHeaders() const +static bool isSetCookie(QHttpHeaders::WellKnownHeader name) { - return rawHeaders; + return name == QHttpHeaders::WellKnownHeader::SetCookie; +} + +template<class HeaderName> +static void setFromRawHeader(QHttpHeaders &headers, HeaderName header, + QByteArrayView value) +{ + headers.removeAll(header); + + if (value.isNull()) + // only wanted to erase key + return; + + if (isSetCookie(header)) { + for (auto cookie : QLatin1StringView(value).tokenize('\n'_L1)) + headers.append(QHttpHeaders::WellKnownHeader::SetCookie, cookie); + } else { + headers.append(header, value); + } +} + +const QNetworkHeadersPrivate::RawHeadersList &QNetworkHeadersPrivate::allRawHeaders() const +{ + if (rawHeaderCache.isCached) + return rawHeaderCache.headersList; + + rawHeaderCache.headersList = fromHttpToRaw(httpHeaders); + rawHeaderCache.isCached = true; + return rawHeaderCache.headersList; } QList<QByteArray> QNetworkHeadersPrivate::rawHeadersKeys() const { + if (httpHeaders.isEmpty()) + return {}; + QList<QByteArray> result; - result.reserve(rawHeaders.size()); - for (const auto &pair : rawHeaders) - result << pair.first; + result.reserve(httpHeaders.size()); + QDuplicateTracker<QByteArray> seen(httpHeaders.size()); + + for (qsizetype i = 0; i < httpHeaders.size(); i++) { + const auto nameL1 = httpHeaders.nameAt(i); + const auto name = QByteArray(nameL1.data(), nameL1.size()); + if (seen.hasSeen(name)) + continue; + + result << name; + } + + return result; +} +QByteArray QNetworkHeadersPrivate::rawHeader(QAnyStringView headerName) const +{ + QByteArrayView setCookieStr = QHttpHeaders::wellKnownHeaderName( + QHttpHeaders::WellKnownHeader::SetCookie); + if (QAnyStringView::compare(headerName, setCookieStr, Qt::CaseInsensitive) != 0) + return httpHeaders.combinedValue(headerName); + + QByteArray result; + const char* separator = ""; + for (qsizetype i = 0; i < httpHeaders.size(); ++i) { + if (QAnyStringView::compare(httpHeaders.nameAt(i), headerName, Qt::CaseInsensitive) == 0) { + result.append(separator); + result.append(httpHeaders.valueAt(i)); + separator = "\n"; + } + } return result; } @@ -1393,51 +1500,39 @@ void QNetworkHeadersPrivate::setRawHeader(const QByteArray &key, const QByteArra // refuse to accept an empty raw header return; - setRawHeaderInternal(key, value); + setFromRawHeader(httpHeaders, key, value); parseAndSetHeader(key, value); -} - -/*! - \internal - Sets the internal raw headers list to match \a list. The cooked headers - will also be updated. - If \a list contains duplicates, they will be stored, but only the first one - is usually accessed. -*/ -void QNetworkHeadersPrivate::setAllRawHeaders(const RawHeadersList &list) -{ - cookedHeaders.clear(); - rawHeaders = list; - - for (const auto &[key, value] : std::as_const(rawHeaders)) - parseAndSetHeader(key, value); + invalidateHeaderCache(); } void QNetworkHeadersPrivate::setCookedHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) { - QByteArray name = headerName(header); - if (name.isEmpty()) { - // headerName verifies that \a header is a known value + const auto wellKnownOpt = toWellKnownHeader(header); + if (!wellKnownOpt) { + // verifies that \a header is a known value qWarning("QNetworkRequest::setHeader: invalid header value KnownHeader(%d) received", header); return; } if (value.isNull()) { - setRawHeaderInternal(name, QByteArray()); + httpHeaders.removeAll(wellKnownOpt.value()); cookedHeaders.remove(header); } else { QByteArray rawValue = headerValue(header, value); if (rawValue.isEmpty()) { qWarning("QNetworkRequest::setHeader: QVariant of type %s cannot be used with header %s", - value.typeName(), name.constData()); + value.typeName(), + QHttpHeaders::wellKnownHeaderName(wellKnownOpt.value()).constData()); return; } - setRawHeaderInternal(name, rawValue); + setFromRawHeader(httpHeaders, wellKnownOpt.value(), rawValue); cookedHeaders.insert(header, value); } + + invalidateHeaderCache(); } QHttpHeaders QNetworkHeadersPrivate::headers() const @@ -1448,58 +1543,58 @@ QHttpHeaders QNetworkHeadersPrivate::headers() const void QNetworkHeadersPrivate::setHeaders(const QHttpHeaders &newHeaders) { httpHeaders = newHeaders; + setCookedFromHttp(httpHeaders); + invalidateHeaderCache(); } void QNetworkHeadersPrivate::setHeaders(QHttpHeaders &&newHeaders) { httpHeaders = std::move(newHeaders); + setCookedFromHttp(httpHeaders); + invalidateHeaderCache(); } void QNetworkHeadersPrivate::setHeader(QHttpHeaders::WellKnownHeader name, QByteArrayView value) { httpHeaders.replaceOrAppend(name, value); + + // set cooked header + const auto knownHeaderOpt = toKnownHeader(name); + if (knownHeaderOpt) + parseAndSetHeader(knownHeaderOpt.value(), value); + + invalidateHeaderCache(); } void QNetworkHeadersPrivate::clearHeaders() { httpHeaders.clear(); - rawHeaders.clear(); cookedHeaders.clear(); + invalidateHeaderCache(); } -void QNetworkHeadersPrivate::setRawHeaderInternal(const QByteArray &key, const QByteArray &value) -{ - auto firstEqualsKey = [&key](const RawHeaderPair &header) { - return header.first.compare(key, Qt::CaseInsensitive) == 0; - }; - rawHeaders.removeIf(firstEqualsKey); - - if (value.isNull()) - return; // only wanted to erase key - - RawHeaderPair pair; - pair.first = key; - pair.second = value; - rawHeaders.append(pair); -} - -void QNetworkHeadersPrivate::parseAndSetHeader(const QByteArray &key, const QByteArray &value) +void QNetworkHeadersPrivate::parseAndSetHeader(QByteArrayView key, QByteArrayView value) { // is it a known header? const int parsedKeyAsInt = parseHeaderName(key); if (parsedKeyAsInt != -1) { const QNetworkRequest::KnownHeaders parsedKey = static_cast<QNetworkRequest::KnownHeaders>(parsedKeyAsInt); - if (value.isNull()) { - cookedHeaders.remove(parsedKey); - } else if (parsedKey == QNetworkRequest::ContentLengthHeader - && cookedHeaders.contains(QNetworkRequest::ContentLengthHeader)) { - // Only set the cooked header "Content-Length" once. - // See bug QTBUG-15311 - } else { - cookedHeaders.insert(parsedKey, parseHeaderValue(parsedKey, value)); - } + parseAndSetHeader(parsedKey, value); + } +} +void QNetworkHeadersPrivate::parseAndSetHeader(QNetworkRequest::KnownHeaders key, + QByteArrayView value) +{ + if (value.isNull()) { + cookedHeaders.remove(key); + } else if (key == QNetworkRequest::ContentLengthHeader + && cookedHeaders.contains(QNetworkRequest::ContentLengthHeader)) { + // Only set the cooked header "Content-Length" once. + // See bug QTBUG-15311 + } else { + cookedHeaders.insert(key, parseHeaderValue(key, value)); } } @@ -1553,7 +1648,7 @@ static int name_to_month(const char* month_str) return 0; } -QDateTime QNetworkHeadersPrivate::fromHttpDate(const QByteArray &value) +QDateTime QNetworkHeadersPrivate::fromHttpDate(QByteArrayView value) { // HTTP dates have three possible formats: // RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT" @@ -1602,7 +1697,8 @@ 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) +QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::fromHttpToRaw( + const QHttpHeaders &headers) { if (headers.isEmpty()) return {}; @@ -1655,6 +1751,83 @@ QHttpHeaders QNetworkHeadersPrivate::fromRawToHttp(const RawHeadersList &raw) return headers; } +std::optional<qint64> QNetworkHeadersPrivate::toInt(QByteArrayView value) +{ + if (value.empty()) + return std::nullopt; + + bool ok; + qint64 res = value.toLongLong(&ok); + if (ok) + return res; + return std::nullopt; +} + +std::optional<QNetworkHeadersPrivate::NetworkCookieList> QNetworkHeadersPrivate::toSetCookieList( + const QList<QByteArray> &values) +{ + if (values.empty()) + return std::nullopt; + + QList<QNetworkCookie> cookies; + for (const auto &s : values) + cookies += QNetworkCookie::parseCookies(s); + + if (cookies.empty()) + return std::nullopt; + return cookies; +} + +QByteArray QNetworkHeadersPrivate::fromCookieList(const QList<QNetworkCookie> &cookies) +{ + return makeCookieHeader(cookies, QNetworkCookie::NameAndValueOnly, "; "); +} + +std::optional<QNetworkHeadersPrivate::NetworkCookieList> QNetworkHeadersPrivate::toCookieList( + const QList<QByteArray> &values) +{ + if (values.empty()) + return std::nullopt; + + QList<QNetworkCookie> cookies; + for (const auto &s : values) + cookies += parseCookieHeader(s); + + if (cookies.empty()) + return std::nullopt; + return cookies; +} + +void QNetworkHeadersPrivate::invalidateHeaderCache() +{ + rawHeaderCache.headersList.clear(); + rawHeaderCache.isCached = false; +} + +void QNetworkHeadersPrivate::setCookedFromHttp(const QHttpHeaders &newHeaders) +{ + cookedHeaders.clear(); + + QMap<QNetworkRequest::KnownHeaders, QList<QByteArray>> multipleHeadersMap; + for (int i = 0; i < newHeaders.size(); ++i) { + const auto name = newHeaders.nameAt(i); + const auto value = newHeaders.valueAt(i); + + const int parsedKeyAsInt = parseHeaderName(name); + if (parsedKeyAsInt == -1) + continue; + + const QNetworkRequest::KnownHeaders parsedKey + = static_cast<QNetworkRequest::KnownHeaders>(parsedKeyAsInt); + + auto &list = multipleHeadersMap[parsedKey]; + list.append(value.toByteArray()); + } + + for (auto i = multipleHeadersMap.cbegin(), end = multipleHeadersMap.cend(); i != end; ++i) + cookedHeaders.insert(i.key(), parseHeaderValue(i.key(), i.value())); +} + QT_END_NAMESPACE #include "moc_qnetworkrequest.cpp" diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 875c787673..e281c74834 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -35,7 +35,8 @@ public: IfModifiedSinceHeader, ETagHeader, IfMatchHeader, - IfNoneMatchHeader + IfNoneMatchHeader, + NumKnownHeaders }; Q_ENUM(KnownHeaders) diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h index a73d8f2162..7268e1a4aa 100644 --- a/src/network/access/qnetworkrequest_p.h +++ b/src/network/access/qnetworkrequest_p.h @@ -27,6 +27,8 @@ QT_BEGIN_NAMESPACE +class QNetworkCookie; + // this is the common part between QNetworkRequestPrivate, QNetworkReplyPrivate and QHttpPartPrivate class QNetworkHeadersPrivate { @@ -36,17 +38,20 @@ public: typedef QHash<QNetworkRequest::KnownHeaders, QVariant> CookedHeadersMap; typedef QHash<QNetworkRequest::Attribute, QVariant> AttributesMap; - RawHeadersList rawHeaders; + mutable struct { + RawHeadersList headersList; + bool isCached = false; + } rawHeaderCache; + QHttpHeaders httpHeaders; CookedHeadersMap cookedHeaders; AttributesMap attributes; QPointer<QObject> originatingObject; - RawHeadersList::ConstIterator findRawHeader(QAnyStringView key) const; - RawHeadersList allRawHeaders() const; + const RawHeadersList &allRawHeaders() const; QList<QByteArray> rawHeadersKeys() const; + QByteArray rawHeader(QAnyStringView headerName) const; void setRawHeader(const QByteArray &key, const QByteArray &value); - void setAllRawHeaders(const RawHeadersList &list); void setCookedHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); QHttpHeaders headers() const; @@ -56,15 +61,26 @@ public: void clearHeaders(); - static QDateTime fromHttpDate(const QByteArray &value); + static QDateTime fromHttpDate(QByteArrayView value); static QByteArray toHttpDate(const QDateTime &dt); + static std::optional<qint64> toInt(QByteArrayView value); + + typedef QList<QNetworkCookie> NetworkCookieList; + static QByteArray fromCookieList(const NetworkCookieList &cookies); + static std::optional<NetworkCookieList> toSetCookieList(const QList<QByteArray> &values); + static std::optional<NetworkCookieList> toCookieList(const QList<QByteArray> &values); + 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); + void invalidateHeaderCache(); + + void setCookedFromHttp(const QHttpHeaders &newHeaders); + void parseAndSetHeader(QByteArrayView key, QByteArrayView value); + void parseAndSetHeader(QNetworkRequest::KnownHeaders key, QByteArrayView value); + }; Q_DECLARE_TYPEINFO(QNetworkHeadersPrivate::RawHeaderPair, Q_RELOCATABLE_TYPE); diff --git a/src/network/access/qnetworkrequestfactory.cpp b/src/network/access/qnetworkrequestfactory.cpp index 87738d9086..d9c536cef2 100644 --- a/src/network/access/qnetworkrequestfactory.cpp +++ b/src/network/access/qnetworkrequestfactory.cpp @@ -615,17 +615,11 @@ QNetworkRequest QNetworkRequestFactoryPrivate::newRequest(const QUrl &url) const if (!sslConfig.isNull()) request.setSslConfiguration(sslConfig); #endif - // Set the header entries to the request. Combine values as there - // may be multiple values per name. Note: this would not necessarily - // produce right result for 'Set-Cookie' header if it has multiple values, - // but since it is a purely server-side (response) header, not relevant here. - const auto headerNames = headers.toMultiMap().uniqueKeys(); // ### fixme: port QNR to QHH - for (const auto &name : headerNames) - request.setRawHeader(name, headers.combinedValue(name)); - + auto h = headers; constexpr char Bearer[] = "Bearer "; if (!bearerToken.isEmpty()) - request.setRawHeader("Authorization"_ba, Bearer + bearerToken); + h.replaceOrAppend(QHttpHeaders::WellKnownHeader::Authorization, Bearer + bearerToken); + request.setHeaders(std::move(h)); request.setTransferTimeout(transferTimeout); request.setPriority(priority); diff --git a/src/network/access/qrestaccessmanager_p.h b/src/network/access/qrestaccessmanager_p.h index 51af299f5c..2e6c1afb90 100644 --- a/src/network/access/qrestaccessmanager_p.h +++ b/src/network/access/qrestaccessmanager_p.h @@ -61,10 +61,12 @@ public: return warnNoAccessManager(); verifyThreadAffinity(context); QNetworkRequest req(request); - if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) { - req.setHeader(QNetworkRequest::ContentTypeHeader, - QLatin1StringView{"application/json"}); + auto h = req.headers(); + if (!h.contains(QHttpHeaders::WellKnownHeader::ContentType)) { + h.append(QHttpHeaders::WellKnownHeader::ContentType, + QLatin1StringView{"application/json"}); } + req.setHeaders(std::move(h)); QNetworkReply *reply = requestOperation(qnam, req, jsonDoc.toJson(QJsonDocument::Compact)); return createActiveRequest(reply, context, slot); } diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp index cdd264ba6e..2d8d101084 100644 --- a/src/network/access/qrestreply.cpp +++ b/src/network/access/qrestreply.cpp @@ -335,7 +335,7 @@ QDebug operator<<(QDebug debug, const QRestReply &reply) << ", bytesAvailable = " << reply.networkReply()->bytesAvailable() << ", url " << reply.networkReply()->url() << ", operation = " << operationName(reply.networkReply()->operation()) - << ", reply headers = " << reply.networkReply()->rawHeaderPairs() + << ", reply headers = " << reply.networkReply()->headers() << ")"; return debug; } @@ -596,7 +596,7 @@ QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply) // text/plain; charset ="utf-8" const QByteArray contentTypeValue = - reply->header(QNetworkRequest::KnownHeaders::ContentTypeHeader).toByteArray(); + reply->headers().value(QHttpHeaders::WellKnownHeader::ContentType).toByteArray(); const auto r = parse_content_type(contentTypeValue); if (r && !r.charset.empty()) diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp index 6875e93581..9b91b11d6b 100644 --- a/src/network/kernel/qnetworkproxy.cpp +++ b/src/network/kernel/qnetworkproxy.cpp @@ -842,7 +842,7 @@ bool QNetworkProxy::hasRawHeader(const QByteArray &headerName) const { if (d->type != HttpProxy && d->type != HttpCachingProxy) return false; - return d->headers.findRawHeader(headerName) != d->headers.rawHeaders.constEnd(); + return d->headers.headers().contains(headerName); } /*! @@ -861,11 +861,7 @@ QByteArray QNetworkProxy::rawHeader(const QByteArray &headerName) const { if (d->type != HttpProxy && d->type != HttpCachingProxy) return QByteArray(); - QNetworkHeadersPrivate::RawHeadersList::ConstIterator it = - d->headers.findRawHeader(headerName); - if (it != d->headers.rawHeaders.constEnd()) - return it->second; - return QByteArray(); + return d->headers.rawHeader(headerName); } /*! diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp index a700023bd5..725bc21359 100644 --- a/src/network/socket/qhttpsocketengine.cpp +++ b/src/network/socket/qhttpsocketengine.cpp @@ -467,11 +467,14 @@ void QHttpSocketEngine::slotSocketConnected() data += " HTTP/1.1\r\n"; data += "Proxy-Connection: keep-alive\r\n"; data += "Host: " + peerAddress + "\r\n"; - if (!d->proxy.hasRawHeader("User-Agent")) + const auto headers = d->proxy.headers(); + if (!headers.contains(QHttpHeaders::WellKnownHeader::UserAgent)) data += "User-Agent: Mozilla/5.0\r\n"; - const auto headers = d->proxy.rawHeaderList(); - for (const QByteArray &header : headers) - data += header + ": " + d->proxy.rawHeader(header) + "\r\n"; + for (qsizetype i = 0; i < headers.size(); ++i) { + const auto name = headers.nameAt(i); + data += QByteArrayView(name.data(), name.size()) + ": " + + headers.valueAt(i) + "\r\n"; + } QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); //qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1); if (priv && priv->method != QAuthenticatorPrivate::None) { diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 66cbeaac9b..97e6fa8518 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -129,14 +129,14 @@ class tst_QNetworkReply: public QObject static QString tempRedirectReplyStr() { QString s = "HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "\r\n"; return s; } static QString movedReplyStr() { QString s = "HTTP/1.1 301 Moved Permanently\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "\r\n"; return s; @@ -144,7 +144,7 @@ class tst_QNetworkReply: public QObject static QString foundReplyStr() { QString s = "HTTP/1.1 302 Found\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "\r\n"; return s; @@ -152,7 +152,7 @@ class tst_QNetworkReply: public QObject static QString permRedirectReplyStr() { QString s = "HTTP/1.1 308 Permanent Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "\r\n"; return s; @@ -2756,7 +2756,7 @@ void tst_QNetworkReply::postToHttp() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -2783,7 +2783,7 @@ void tst_QNetworkReply::postToHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -2844,8 +2844,8 @@ void tst_QNetworkReply::postToHttpMultipart_data() QHttpMultiPart *customMultiPart = new QHttpMultiPart; customMultiPart->append(textPart); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n"; QTest::newRow("text-custom") << url << customMultiPart << expectedData << QByteArray("custom"); @@ -2881,18 +2881,18 @@ void tst_QNetworkReply::postToHttpMultipart_data() multiPart3->append(textPart); multiPart3->append(textPart2); multiPart3->append(textPart3); - expectedData = "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + expectedData = "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text\"'\n" "content: 7 bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: myRawHeader, value: 'myValue'\n" - "header: Content-Disposition, value: 'form-data; name=\"text2\"'\n" + "header: content-type, value: 'text/plain'\n" + "header: myrawheader, value: 'myValue'\n" + "header: content-disposition, value: 'form-data; name=\"text2\"'\n" "content: some more bytes\n" "\n" - "header: Content-Type, value: 'text/plain'\n" - "header: Content-Disposition, value: 'form-data; name=\"text3\"'\n" - "header: Content-Location, value: 'http://my.test.location.tld'\n" + "header: content-type, value: 'text/plain'\n" + "header: content-disposition, value: 'form-data; name=\"text3\"'\n" + "header: content-location, value: 'http://my.test.location.tld'\n" "content: even more bytes\n\n"; QTest::newRow("text-text-text") << url << multiPart3 << expectedData << QByteArray("alternative"); @@ -3311,7 +3311,7 @@ void tst_QNetworkReply::postToHttps() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; QFETCH(QByteArray, data); @@ -3345,7 +3345,7 @@ void tst_QNetworkReply::postToHttpsSynchronous() QSslConfiguration conf; conf.setCaCertificates(certs); request.setSslConfiguration(conf); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, @@ -4461,7 +4461,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply200 = "HTTP/1.0 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-store\r\n" "Content-length: 8\r\n" "\r\n" @@ -4596,7 +4596,7 @@ void tst_QNetworkReply::ioGetFromHttpWithCache_data() QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -5205,7 +5205,7 @@ void tst_QNetworkReply::ioPostToHttpFromFile() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); @@ -5282,7 +5282,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() socketpair.endPoints[0]->write(data); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); manager.setProxy(proxy); QNetworkReplyPtr reply(manager.post(request, socketpair.endPoints[1])); @@ -5356,7 +5356,7 @@ void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -5387,7 +5387,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileToEnd() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -5413,7 +5413,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // only send 5 bytes request.setHeader(QNetworkRequest::ContentLengthHeader, 5); QVERIFY(request.header(QNetworkRequest::ContentLengthHeader).isValid()); @@ -5444,7 +5444,7 @@ void tst_QNetworkReply::ioPostToHttpFromMiddleOfQBufferFiveBytes() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &uploadBuffer)); connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), @@ -5472,7 +5472,7 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() QUrl url = "http://" + QtNetworkSettings::httpServerName() + "/qtest/protected/cgi-bin/md5sum.cgi"; QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); // disallow buffering request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); @@ -5557,7 +5557,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress() QUrl url = QUrl(QLatin1String("https://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); @@ -5711,7 +5711,7 @@ void tst_QNetworkReply::ioPostToHttpUploadProgress() // create the request QUrl url = QUrl(QString("http://127.0.0.1:%1/").arg(server.serverPort())); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &sourceFile)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5779,7 +5779,7 @@ void tst_QNetworkReply::emitAllUploadProgressSignals() QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest normalRequest(url); - normalRequest.setRawHeader("Content-Type", "application/octet-stream"); + normalRequest.setRawHeader("content-type", "application/octet-stream"); QNetworkRequest catchAllSignalsRequest(normalRequest); catchAllSignalsRequest.setAttribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute, true); @@ -5830,7 +5830,7 @@ void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() // create the request QUrl url = QUrl(QLatin1String("http://127.0.0.1:") + QString::number(server.serverPort()) + QLatin1Char('/')); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply(manager.post(request, &buffer)); QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64))); connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); @@ -5876,7 +5876,11 @@ void tst_QNetworkReply::lastModifiedHeaderForFile() QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); - QCOMPARE(header, fileInfo.lastModified()); + QDateTime expected = fileInfo.lastModified(); + // remove msecs, HTTP dates don't support it + expected = expected.addMSecs(-expected.time().msec()); + + QCOMPARE(header.toUTC(), expected.toUTC()); } void tst_QNetworkReply::lastModifiedHeaderForHttp() @@ -6182,7 +6186,7 @@ void tst_QNetworkReply::receiveCookiesFromHttp() QByteArray data = cookieString.toLatin1() + '\n'; QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); QNetworkReplyPtr reply; RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PostOperation, request, reply, data)); @@ -6210,7 +6214,7 @@ void tst_QNetworkReply::receiveCookiesFromHttpSynchronous() QUrl url("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/set-cookie.cgi"); QNetworkRequest request(url); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); request.setAttribute( QNetworkRequest::SynchronousRequestAttribute, true); @@ -8046,7 +8050,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray getReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n" "GET"; @@ -8054,7 +8058,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray postReply = "HTTP/1.1 200\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "Content-length: 4\r\n" "\r\n" @@ -8063,7 +8067,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { QByteArray putReply = "HTTP/1.1 201\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: max-age = 6000\r\n" "\r\n"; @@ -8101,7 +8105,7 @@ void tst_QNetworkReply::qtbug28035browserDoesNotLoadQtProjectOrgCorrectly() { server.clearHeaderParserState(); server.setDataToTransmit(postReply); - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); reply.reset(manager.post(request, postData)); QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); @@ -8597,7 +8601,7 @@ void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() QByteArray reply206 = "HTTP/1.0 206\r\n" "Connection: keep-alive\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-control: no-cache\r\n" "Content-Range: bytes 2-6/8\r\n" "Content-length: 4\r\n" @@ -8654,7 +8658,7 @@ void tst_QNetworkReply::synchronousAuthenticationCache() "WWW-Authenticate: Basic realm=\"QNetworkAccessManager Test Realm\"\r\n" "Content-Length: 4\r\n" "Connection: close\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "\r\n" "auth"; QRegularExpression rx("authorization: Basic ([^\r\n]*)\r\n"); @@ -8663,7 +8667,7 @@ void tst_QNetworkReply::synchronousAuthenticationCache() if (QByteArray::fromBase64(match.captured(1).toLatin1()) == "login:password") { dataToTransmit = "HTTP/1.0 200 OK\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Content-Length: 2\r\n" "\r\n" "OK"; @@ -9033,7 +9037,7 @@ void tst_QNetworkReply::ioHttpRedirectErrors_data() QTest::addColumn<QNetworkReply::NetworkError>("error"); QString tempRedirectReply = QString("HTTP/1.1 307 Temporary Redirect\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: http://localhost:%1\r\n\r\n"); QTest::newRow("too-many-redirects") << "http://localhost" << tempRedirectReply << QNetworkReply::TooManyRedirectsError; @@ -9341,7 +9345,7 @@ void tst_QNetworkReply::ioHttpRedirect() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1(), false); @@ -9369,7 +9373,7 @@ void tst_QNetworkReply::ioHttpRedirectWithCache() { // Disallow caching the result so that the second request must also send the request QByteArray http200ResponseNoCache = "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "Cache-Control: no-cache\r\n" "\r\nHello"; @@ -9379,7 +9383,7 @@ void tst_QNetworkReply::ioHttpRedirectWithCache() // A cache-able redirect reply QString redirectReply = QStringLiteral("HTTP/1.1 308\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %1\r\n" "Cache-Control: max-age=3600\r\n" "\r\nYou're being redirected").arg(targetUrl.toString()); @@ -9491,7 +9495,7 @@ void tst_QNetworkReply::ioHttpRedirectPostPut() QUrl targetUrl("http://" + QtNetworkSettings::httpServerName() + "/qtest/cgi-bin/md5sum.cgi"); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -9645,7 +9649,7 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() targetUrl.setPort(target.serverPort()); QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n" - "Content-Type: text/plain\r\n" + "content-type: text/plain\r\n" "location: %2\r\n" "\r\n").arg(status, targetUrl.toString()); MiniHttpServer redirectServer(redirectReply.toLatin1()); @@ -9679,8 +9683,8 @@ void tst_QNetworkReply::ioHttpRedirectWithUploadDevice() // we shouldn't send Content-Length with not content (esp. for GET) QVERIFY2(!target.receivedData.contains("Content-Length"), "Target server should not have received a Content-Length header"); - QVERIFY2(!target.receivedData.contains("Content-Type"), - "Target server should not have received a Content-Type header"); + QVERIFY2(!target.receivedData.contains("content-type"), + "Target server should not have received a content-type header"); } } @@ -10048,7 +10052,7 @@ void tst_QNetworkReply::requestWithTimeout() server.stopTransfer = true; QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); - request.setRawHeader("Content-Type", "application/octet-stream"); + request.setRawHeader("content-type", "application/octet-stream"); if (reqInt > 0) request.setTransferTimeout(reqInt); if (reqChrono > 0ms) @@ -10100,7 +10104,7 @@ void tst_QNetworkReply::moreActivitySignals() QNetworkRequest request(url); QNetworkReplyPtr reply; if (postWithData) { - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); reply.reset(manager.post(request, "Hello, world!")); } else { reply.reset(manager.get(request)); @@ -10121,7 +10125,7 @@ void tst_QNetworkReply::moreActivitySignals() // Second request will not send socketStartedConnecting because of keep-alive, so don't check it. QNetworkReplyPtr secondreply; if (postWithData) { - request.setRawHeader("Content-Type", "text/plain"); + request.setRawHeader("content-type", "text/plain"); secondreply.reset(manager.post(request, "Hello, world!")); } else { secondreply.reset(manager.get(request)); diff --git a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp index ed978e6b0f..43a5fbc75a 100644 --- a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp +++ b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp @@ -349,7 +349,7 @@ void tst_QNetworkRequest::rawHeaderParsing_data() << true << "Content-Type" << "text/html"; QTest::newRow("Content-Length") << QNetworkRequest::ContentLengthHeader << QVariant(qint64(1)) - << true << "Content-Length" << " 1 "; + << true << "Content-Length" << "1"; QTest::newRow("Location") << QNetworkRequest::LocationHeader << QVariant(QUrl("http://foo/with space")) << true << "Location" << "http://foo/with%20space"; |