diff options
Diffstat (limited to 'src/network/access')
21 files changed, 1035 insertions, 46 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri index a129beda15..b068f96283 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -68,6 +68,13 @@ qtConfig(networkdiskcache) { mac: LIBS_PRIVATE += -framework Security +wasm { + SOURCES += \ + access/qnetworkreplywasmimpl.cpp + HEADERS += \ + access/qnetworkreplywasmimpl_p.h +} + include($$PWD/../../3rdparty/zlib_dependency.pri) qtConfig(http) { diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp index f51af4be5c..0be72042c6 100644 --- a/src/network/access/http2/http2protocol.cpp +++ b/src/network/access/http2/http2protocol.cpp @@ -287,7 +287,8 @@ bool is_protocol_upgraded(const QHttpNetworkReply &reply) // Do some minimal checks here - we expect 'Upgrade: h2c' to be found. const auto &header = reply.header(); for (const QPair<QByteArray, QByteArray> &field : header) { - if (field.first.toLower() == "upgrade" && field.second.toLower() == "h2c") + if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 && + field.second.compare("h2c", Qt::CaseInsensitive) == 0) return true; } } diff --git a/src/network/access/qhsts.cpp b/src/network/access/qhsts.cpp index 43a8a3663e..a015feb044 100644 --- a/src/network/access/qhsts.cpp +++ b/src/network/access/qhsts.cpp @@ -453,8 +453,7 @@ bool QHstsHeaderParser::processDirective(const QByteArray &name, const QByteArra { Q_ASSERT(name.size()); // RFC6797 6.1/3 Directive names are case-insensitive - const auto lcName = name.toLower(); - if (lcName == "max-age") { + if (name.compare("max-age", Qt::CaseInsensitive) == 0) { // RFC 6797, 6.1.1 // The syntax of the max-age directive's REQUIRED value (after // quoted-string unescaping, if necessary) is defined as: @@ -477,7 +476,7 @@ bool QHstsHeaderParser::processDirective(const QByteArray &name, const QByteArra maxAge = age; maxAgeFound = true; - } else if (lcName == "includesubdomains") { + } else if (name.compare("includesubdomains", Qt::CaseInsensitive) == 0) { // RFC 6797, 6.1.2. The includeSubDomains Directive. // The OPTIONAL "includeSubDomains" directive is a valueless directive. diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index c207d6e240..df7f87efd4 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -97,16 +97,18 @@ HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxH if (size.second > maxHeaderListSize) break; - QByteArray key(field.first.toLower()); - if (key == "connection" || key == "host" || key == "keep-alive" - || key == "proxy-connection" || key == "transfer-encoding") + if (field.first.compare("connection", Qt::CaseInsensitive) == 0 || + field.first.compare("host", Qt::CaseInsensitive) == 0 || + field.first.compare("keep-alive", Qt::CaseInsensitive) == 0 || + field.first.compare("proxy-connection", Qt::CaseInsensitive) == 0 || + field.first.compare("transfer-encoding", Qt::CaseInsensitive) == 0) continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler // TODO: verify with specs, which fields are valid to send .... // toLower - 8.1.2 .... "header field names MUST be converted to lowercase prior // to their encoding in HTTP/2. // A request or response containing uppercase header field names // MUST be treated as malformed (Section 8.1.2.6)". - header.push_back(HeaderField(key, field.second)); + header.push_back(HeaderField(field.first.toLower(), field.second)); } return header; @@ -1404,8 +1406,9 @@ bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFram return false; } - const auto method = pseudoHeaders[":method"].toLower(); - if (method != "get" && method != "head") + const QByteArray method = pseudoHeaders[":method"]; + if (method.compare("get", Qt::CaseInsensitive) != 0 && + method.compare("head", Qt::CaseInsensitive) != 0) return false; QUrl url; diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 0e2c257952..c58fd24a44 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -528,7 +528,7 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke QUrl redirectUrl; const QList<QPair<QByteArray, QByteArray> > fields = reply->header(); for (const QNetworkReply::RawHeaderPair &header : fields) { - if (header.first.toLower() == "location") { + if (header.first.compare("location", Qt::CaseInsensitive) == 0) { redirectUrl = QUrl::fromEncoded(header.second); break; } diff --git a/src/network/access/qhttpnetworkheader.cpp b/src/network/access/qhttpnetworkheader.cpp index 3326f89d2f..8ad01174b4 100644 --- a/src/network/access/qhttpnetworkheader.cpp +++ b/src/network/access/qhttpnetworkheader.cpp @@ -64,7 +64,7 @@ qint64 QHttpNetworkHeaderPrivate::contentLength() const QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(), end = fields.constEnd(); for ( ; it != end; ++it) - if (qstricmp("content-length", it->first) == 0) { + if (it->first.compare("content-length", Qt::CaseInsensitive) == 0) { value = it->second; break; } @@ -95,7 +95,7 @@ QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(), end = fields.constEnd(); for ( ; it != end; ++it) - if (qstricmp(name.constData(), it->first) == 0) + if (name.compare(it->first, Qt::CaseInsensitive) == 0) result += it->second; return result; @@ -104,7 +104,7 @@ QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray void QHttpNetworkHeaderPrivate::setHeaderField(const QByteArray &name, const QByteArray &data) { auto firstEqualsName = [&name](const QPair<QByteArray, QByteArray> &header) { - return qstricmp(name.constData(), header.first) == 0; + return name.compare(header.first, Qt::CaseInsensitive) == 0; }; fields.erase(std::remove_if(fields.begin(), fields.end(), firstEqualsName), diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index a657346958..c9c3172304 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -390,7 +390,8 @@ qint64 QHttpNetworkReplyPrivate::bytesAvailable() const bool QHttpNetworkReplyPrivate::isCompressed() { QByteArray encoding = headerField("content-encoding"); - return qstricmp(encoding.constData(), "gzip") == 0 || qstricmp(encoding.constData(), "deflate") == 0; + return encoding.compare("gzip", Qt::CaseInsensitive) == 0 || + encoding.compare("deflate", Qt::CaseInsensitive) == 0; } void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() @@ -401,7 +402,7 @@ void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(), end = fields.end(); while (it != end) { - if (qstricmp(name.constData(), it->first.constData()) == 0) { + if (name.compare(it->first, Qt::CaseInsensitive) == 0) { removedContentLength = strtoull(it->second.constData(), nullptr, 0); fields.erase(it); break; diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp index d7914e4143..67a856506c 100644 --- a/src/network/access/qnetworkaccessdebugpipebackend.cpp +++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp @@ -107,8 +107,8 @@ void QNetworkAccessDebugPipeBackend::open() bareProtocol = QUrlQuery(url()).queryItemValue(QLatin1String("bare")) == QLatin1String("1"); if (operation() == QNetworkAccessManager::PutOperation) { - uploadByteDevice = createUploadByteDevice(); - QObject::connect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); + createUploadByteDevice(); + QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection); } } diff --git a/src/network/access/qnetworkaccessdebugpipebackend_p.h b/src/network/access/qnetworkaccessdebugpipebackend_p.h index d9a7aabdad..761c7055b8 100644 --- a/src/network/access/qnetworkaccessdebugpipebackend_p.h +++ b/src/network/access/qnetworkaccessdebugpipebackend_p.h @@ -77,7 +77,6 @@ protected: void pushFromSocketToDownstream(); void pushFromUpstreamToSocket(); void possiblyFinish(); - QNonContiguousByteDevice *uploadByteDevice; private slots: void uploadReadyReadSlot(); diff --git a/src/network/access/qnetworkaccessfilebackend.cpp b/src/network/access/qnetworkaccessfilebackend.cpp index d4ca9c22fc..60353cb03e 100644 --- a/src/network/access/qnetworkaccessfilebackend.cpp +++ b/src/network/access/qnetworkaccessfilebackend.cpp @@ -99,7 +99,7 @@ QNetworkAccessFileBackendFactory::create(QNetworkAccessManager::Operation op, } QNetworkAccessFileBackend::QNetworkAccessFileBackend() - : uploadByteDevice(0), totalBytes(0), hasUploadFinished(false) + : totalBytes(0), hasUploadFinished(false) { } @@ -154,8 +154,8 @@ void QNetworkAccessFileBackend::open() break; case QNetworkAccessManager::PutOperation: mode = QIODevice::WriteOnly | QIODevice::Truncate; - uploadByteDevice = createUploadByteDevice(); - QObject::connect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); + createUploadByteDevice(); + QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection); break; default: diff --git a/src/network/access/qnetworkaccessfilebackend_p.h b/src/network/access/qnetworkaccessfilebackend_p.h index 2c01fb1121..2204958ee0 100644 --- a/src/network/access/qnetworkaccessfilebackend_p.h +++ b/src/network/access/qnetworkaccessfilebackend_p.h @@ -73,8 +73,6 @@ public: public slots: void uploadReadyReadSlot(); -protected: - QNonContiguousByteDevice *uploadByteDevice; private: QFile file; qint64 totalBytes; diff --git a/src/network/access/qnetworkaccessftpbackend.cpp b/src/network/access/qnetworkaccessftpbackend.cpp index c5404e4221..269845ed39 100644 --- a/src/network/access/qnetworkaccessftpbackend.cpp +++ b/src/network/access/qnetworkaccessftpbackend.cpp @@ -102,8 +102,8 @@ public: }; QNetworkAccessFtpBackend::QNetworkAccessFtpBackend() - : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1), - supportsSize(false), supportsMdtm(false), state(Idle) + : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1), pwdId(-1), + supportsSize(false), supportsMdtm(false), supportsPwd(false), state(Idle) { } @@ -302,13 +302,38 @@ void QNetworkAccessFtpBackend::ftpDone() if (state == LoggingIn) { state = CheckingFeatures; - if (operation() == QNetworkAccessManager::GetOperation) { - // send help command to find out if server supports "SIZE" and "MDTM" + // send help command to find out if server supports SIZE, MDTM, and PWD + if (operation() == QNetworkAccessManager::GetOperation + || operation() == QNetworkAccessManager::PutOperation) { helpId = ftp->rawCommand(QLatin1String("HELP")); // get supported commands } else { ftpDone(); } } else if (state == CheckingFeatures) { + // If a URL path starts with // prefix (/%2F decoded), the resource will + // be retrieved by an absolute path starting with the root directory. + // For the other URLs, the working directory is retrieved by PWD command + // and prepended to the resource path as an absolute path starting with + // the working directory. + state = ResolvingPath; + QString path = url().path(); + if (path.startsWith(QLatin1String("//")) || supportsPwd == false) { + ftpDone(); // no commands sent, move to the next state + } else { + // If a path starts with /~/ prefix, its prefix will be replaced by + // the working directory as an absolute path starting with working + // directory. + if (path.startsWith(QLatin1String("/~/"))) { + // Remove leading /~ symbols + QUrl newUrl = url(); + newUrl.setPath(path.mid(2)); + setUrl(newUrl); + } + + // send PWD command to retrieve the working directory + pwdId = ftp->rawCommand(QLatin1String("PWD")); + } + } else if (state == ResolvingPath) { state = Statting; if (operation() == QNetworkAccessManager::GetOperation) { // logged in successfully, send the stat requests (if supported) @@ -366,6 +391,34 @@ void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text) supportsSize = true; if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive)) supportsMdtm = true; + if (text.contains(QLatin1String("PWD"), Qt::CaseSensitive)) + supportsPwd = true; + } else if (id == pwdId && code == 257) { + QString pwdPath; + int startIndex = text.indexOf('"'); + int stopIndex = text.lastIndexOf('"'); + if (stopIndex - startIndex) { + // The working directory is a substring between \" symbols. + startIndex++; // skip the first \" symbol + pwdPath = text.mid(startIndex, stopIndex - startIndex); + } else { + // If there is no or only one \" symbol, use all the characters of + // text. + pwdPath = text; + } + + // If a URL path starts with the working directory prefix, its resource + // will be retrieved from the working directory. Otherwise, the path of + // the working directory is prepended to the resource path. + QString urlPath = url().path(); + if (!urlPath.startsWith(pwdPath)) { + if (pwdPath.endsWith(QLatin1Char('/'))) + pwdPath.chop(1); + // Prepend working directory to the URL path + QUrl newUrl = url(); + newUrl.setPath(pwdPath % urlPath); + setUrl(newUrl); + } } else if (code == 213) { // file status if (id == sizeId) { // reply to the size command diff --git a/src/network/access/qnetworkaccessftpbackend_p.h b/src/network/access/qnetworkaccessftpbackend_p.h index 4bd082fb67..0b3d35dcd3 100644 --- a/src/network/access/qnetworkaccessftpbackend_p.h +++ b/src/network/access/qnetworkaccessftpbackend_p.h @@ -76,6 +76,7 @@ public: //Connecting, LoggingIn, CheckingFeatures, + ResolvingPath, Statting, Transferring, Disconnecting @@ -107,8 +108,8 @@ private: QPointer<QNetworkAccessCachedFtpConnection> ftp; QIODevice *uploadDevice; qint64 totalBytes; - int helpId, sizeId, mdtmId; - bool supportsSize, supportsMdtm; + int helpId, sizeId, mdtmId, pwdId; + bool supportsSize, supportsMdtm, supportsPwd; State state; }; diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 96e3f92db1..bec98a3f58 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -82,6 +82,9 @@ #include <SystemConfiguration/SystemConfiguration.h> #include <Security/SecKeychain.h> #endif +#ifdef Q_OS_WASM +#include "qnetworkreplywasmimpl_p.h" +#endif QT_BEGIN_NAMESPACE @@ -1344,6 +1347,16 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera bool isLocalFile = req.url().isLocalFile(); QString scheme = req.url().scheme(); +#ifdef Q_OS_WASM + if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) { + QNetworkReplyWasmImpl *reply = new QNetworkReplyWasmImpl(this); + QNetworkReplyWasmImplPrivate *priv = reply->d_func(); + priv->manager = this; + priv->setup(op, req, outgoingData); + return reply; + } +#endif + // fast path for GET on file:// URLs // The QNetworkAccessFileBackend will right now only be used for PUT if (op == QNetworkAccessManager::GetOperation diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index a0ce3eddcd..67b3a8b71b 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -195,6 +195,9 @@ private: friend class QNetworkReplyHttpImplPrivate; friend class QNetworkReplyFileImpl; +#ifdef Q_OS_WASM + friend class QNetworkReplyWasmImpl; +#endif Q_DECLARE_PRIVATE(QNetworkAccessManager) Q_PRIVATE_SLOT(d_func(), void _q_replyFinished()) Q_PRIVATE_SLOT(d_func(), void _q_replyEncrypted()) diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index c9d658225e..df2e4902a4 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -189,7 +189,7 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) const auto headers = metaData.rawHeaders(); for (const auto &header : headers) { - if (header.first.toLower() == "content-length") { + if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) { const qint64 size = header.second.toLongLong(); if (size > (maximumCacheSize() * 3)/4) return 0; @@ -642,7 +642,7 @@ bool QCacheItem::canCompress() const bool typeOk = false; const auto headers = metaData.rawHeaders(); for (const auto &header : headers) { - if (header.first.toLower() == "content-length") { + if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) { qint64 size = header.second.toLongLong(); if (size > MAX_COMPRESSION_SIZE) return false; @@ -650,7 +650,7 @@ bool QCacheItem::canCompress() const sizeOk = true; } - if (header.first.toLower() == "content-type") { + if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) { QByteArray type = header.second; if (type.startsWith("text/") || (type.startsWith("application/") diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 9067dea8e3..8750a841f6 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -1318,7 +1318,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByte if (!value.isEmpty()) { // Why are we appending values for headers which are already // present? - if (qstricmp(it->first.constData(), "set-cookie") == 0) + if (it->first.compare("set-cookie", Qt::CaseInsensitive) == 0) value += '\n'; else value += ", "; @@ -1584,7 +1584,7 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData QUrl redirectUrl; for ( ; it != end; ++it) { if (httpRequest.isFollowRedirects() && - !qstricmp(it->first.toLower().constData(), "location")) + !it->first.compare("location", Qt::CaseInsensitive)) redirectUrl = QUrl::fromEncoded(it->second); setRawHeader(it->first, it->second); } diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp new file mode 100644 index 0000000000..9c2ff8fb89 --- /dev/null +++ b/src/network/access/qnetworkreplywasmimpl.cpp @@ -0,0 +1,640 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkreplywasmimpl_p.h" +#include "qnetworkrequest.h" + +#include <QtCore/qtimer.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qthread.h> + +#include <private/qnetworkaccessmanager_p.h> +#include <private/qnetworkfile_p.h> + +#include <iostream> + +QT_BEGIN_NAMESPACE + +QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate() + : QNetworkReplyPrivate() + , managerPrivate(0) + , downloadBufferReadPosition(0) + , downloadBufferCurrentSize(0) + , totalDownloadSize(0) + , percentFinished(0) +{ +} + +QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate() +{ +} + +QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl() +{ +} + +QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent) + : QNetworkReply(*new QNetworkReplyWasmImplPrivate(), parent) +{ +} + +QByteArray QNetworkReplyWasmImpl::methodName() const +{ + switch (operation()) { + case QNetworkAccessManager::HeadOperation: + return "HEAD"; + case QNetworkAccessManager::GetOperation: + return "GET"; + case QNetworkAccessManager::PutOperation: + return "PUT"; + case QNetworkAccessManager::PostOperation: + return "POST"; + case QNetworkAccessManager::DeleteOperation: + return "DELETE"; + default: + break; + } + return QByteArray(); +} + +void QNetworkReplyWasmImpl::close() +{ + QNetworkReply::close(); +} + +void QNetworkReplyWasmImpl::abort() +{ + close(); +} + +qint64 QNetworkReplyWasmImpl::bytesAvailable() const +{ + Q_D(const QNetworkReplyWasmImpl); + + if (!d->isFinished) + return QNetworkReply::bytesAvailable(); + + return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition; +} + +bool QNetworkReplyWasmImpl::isSequential() const +{ + return true; +} + +qint64 QNetworkReplyWasmImpl::size() const +{ + return QNetworkReply::size(); +} + +/*! + \internal +*/ +qint64 QNetworkReplyWasmImpl::readData(char *data, qint64 maxlen) +{ + Q_D(QNetworkReplyWasmImpl); + + qint64 howMuch = qMin(maxlen, (d->downloadBuffer.size() - d->downloadBufferReadPosition)); + memcpy(data, d->downloadBuffer.constData(), howMuch); + d->downloadBufferReadPosition += howMuch; + + return howMuch; +} + +void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data) +{ + Q_Q(QNetworkReplyWasmImpl); + + outgoingData = data; + request = req; + url = request.url(); + operation = op; + + q->QIODevice::open(QIODevice::ReadOnly); + if (outgoingData && outgoingData->isSequential()) { + bool bufferingDisallowed = + request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, false).toBool(); + + 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()) { + state = Buffering; + _q_bufferOutgoingData(); + return; + } + } else { + // doSendRequest will be called when the buffering has finished. + state = Buffering; + _q_bufferOutgoingData(); + return; + } + } + // No outgoing data (POST, ..) + doSendRequest(); +} + +void QNetworkReplyWasmImplPrivate::onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int buffer, int bufferSize) +{ + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data); + + const QString reasonStr = QString::fromUtf8(reinterpret_cast<char *>(statusReason)); + + switch (readyState) { + case 0://unsent + break; + case 1://opened + break; + case 2://headers received + break; + case 3://loading + break; + case 4: {//done + handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + if (!reasonStr.isEmpty()) + handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr); + + if (statusCode >= 400) { + if (!reasonStr.isEmpty()) + handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr); + } else { + handler->dataReceived(reinterpret_cast<char *>(buffer), bufferSize); + } + } + break; + }; + } + +void QNetworkReplyWasmImplPrivate::onProgressCallback(void* data, int bytesWritten, int total, uint timestamp) +{ + Q_UNUSED(timestamp); + + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data); + handler->emitDataReadProgress(bytesWritten, total); +} + +void QNetworkReplyWasmImplPrivate::onRequestErrorCallback(void* data, int statusCode, int statusReason) +{ + QString reasonStr = QString::fromUtf8(reinterpret_cast<char *>(statusReason)); + + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data); + + handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + if (!reasonStr.isEmpty()) + handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr); + + if (statusCode >= 400) { + if (!reasonStr.isEmpty()) + handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr); + } +} + +void QNetworkReplyWasmImplPrivate::onResponseHeadersCallback(void* data, int headers) +{ + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data); + handler->headersReceived(reinterpret_cast<char *>(headers)); +} + +void QNetworkReplyWasmImplPrivate::doSendRequest() +{ + Q_Q(QNetworkReplyWasmImpl); + totalDownloadSize = 0; + jsRequest(QString::fromUtf8(q->methodName()), // GET POST + request.url().toString(), + (void *)&onLoadCallback, + (void *)&onProgressCallback, + (void *)&onRequestErrorCallback, + (void *)&onResponseHeadersCallback); +} + +/* const QString &body, const QList<QPair<QByteArray, QByteArray> > &headers ,*/ +void QNetworkReplyWasmImplPrivate::jsRequest(const QString &verb, const QString &url, + void *loadCallback, void *progressCallback, + void *errorCallback, void *onResponseHeadersCallback) +{ + QString extraDataString; + + QByteArray extraData; + if (outgoingData) + extraData = outgoingData->readAll(); + + if (extraData.size() > 0) + extraDataString.fromUtf8(extraData); + + if (extraDataString.size() >= 0 && verb == QStringLiteral("POST") && extraDataString.startsWith(QStringLiteral("?"))) + extraDataString.remove(QStringLiteral("?")); + + // Probably a good idea to save any shared pointers as members in C++ + // so the objects they point to survive as long as you need them + + QStringList headersList; + for (auto header : request.rawHeaderList()) + headersList << QString::fromUtf8(header + ":" + request.rawHeader(header)); + + EM_ASM_ARGS({ + var verb = Pointer_stringify($0); + var url = Pointer_stringify($1); + var onLoadCallbackPointer = $2; + var onProgressCallbackPointer = $3; + var onErrorCallbackPointer = $4; + var onHeadersCallback = $5; + var handler = $8; + + var dataToSend; + var extraRequestData = Pointer_stringify($6); // request parameters + var headersData = Pointer_stringify($7); + + var xhr; + xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + + xhr.open(verb, url, true); //async + + function handleError(xhrStatusCode, xhrStatusText) { + var errorPtr = allocate(intArrayFromString(xhrStatusText), 'i8', ALLOC_NORMAL); + Runtime.dynCall('viii', onErrorCallbackPointer, [handler, xhrStatusCode, errorPtr]); + _free(errorPtr); + } + + if (headersData) { + var headers = headersData.split("&"); + for (var i = 0; i < headers.length; i++) { + var header = headers[i].split(":")[0]; + var value = headers[i].split(":")[1]; + + if (verb === 'POST' && value.toLowerCase().includes('json')) { + if (extraRequestData) { + xhr.responseType = 'json'; + dataToSend = extraRequestData; + } + } + if (verb === 'POST' && value.toLowerCase().includes('form')) { + if (extraRequestData) { + var formData = new FormData(); + var extra = extraRequestData.split("&"); + for (var i = 0; i < extra.length; i++) { + formData.append(extra[i].split("=")[0],extra[i].split("=")[1]); + } + dataToSend = formData; + } + } + xhr.setRequestHeader(header, value); + } + } + + xhr.onprogress = function(e) { + switch (xhr.status) { + case 200: + case 206: + case 300: + case 301: + case 302: { + var date = xhr.getResponseHeader('Last-Modified'); + date = ((date != null) ? new Date(date).getTime() / 1000 : 0); + Runtime.dynCall('viiii', onProgressCallbackPointer, [handler, e.loaded, e.total, date]); + } + break; + } + }; + + xhr.onreadystatechange = function() { + if (this.readyState == this.HEADERS_RECEIVED) { + var responseStr = this.getAllResponseHeaders(); + if (responseStr.length > 0) { + var ptr = allocate(intArrayFromString(responseStr), 'i8', ALLOC_NORMAL); + Runtime.dynCall('vii', onHeadersCallback, [handler, ptr]); + _free(ptr); + } + } + }; + + xhr.onload = function(e) { + if (xhr.status >= 300) { //error + handleError(xhr.status, xhr.statusText); + } else { + if (this.status == 200 || this.status == 203) { + var datalength; + var byteArray = 0; + var buffer; + if (this.responseType.length === 0 || this.responseType === 'document') { + byteArray = new Uint8Array(this.responseText); + } else if (this.responseType === 'json') { + var jsonResponse = JSON.stringify(this.response); + buffer = allocate(intArrayFromString(jsonResponse), 'i8', ALLOC_NORMAL); + datalength = jsonResponse.length; + } else if (this.responseType === 'arraybuffer') { + byteArray = new Uint8Array(xhr.response); + } + if (byteArray != 0 ) { + datalength = byteArray.length; + buffer = _malloc(datalength); + HEAPU8.set(byteArray, buffer); + } + var reasonPtr = allocate(intArrayFromString(this.statusText), 'i8', ALLOC_NORMAL); + Runtime.dynCall('viiiiii', onLoadCallbackPointer, [handler, this.status, reasonPtr, this.readyState, buffer, datalength]); + _free(buffer); + _free(reasonPtr); + } + } + }; + + xhr.onerror = function(e) { + handleError(xhr.status, xhr.statusText); + }; + //TODO other operations, handle user/pass, handle binary data, data streaming + xhr.send(dataToSend); + + }, verb.toLatin1().data(), + url.toLatin1().data(), + loadCallback, + progressCallback, + errorCallback, + onResponseHeadersCallback, + extraDataString.size() > 0 ? extraDataString.toLatin1().data() : extraData.data(), + headersList.join(QStringLiteral("&")).toLatin1().data(), + this + ); +} + +void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString) +{ + Q_UNUSED(errorCode) + Q_Q(QNetworkReplyWasmImpl); + + q->setError(errorCode, errorString); + emit q->error(errorCode); + + q->setFinished(true); + emit q->finished(); +} + +void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + Q_Q(QNetworkReplyWasmImpl); + + totalDownloadSize = bytesTotal; + + percentFinished = (bytesReceived / bytesTotal) * 100; + + emit q->downloadProgress(bytesReceived, totalDownloadSize); +} + +void QNetworkReplyWasmImplPrivate::dataReceived(char *buffer, int bufferSize) +{ + Q_Q(QNetworkReplyWasmImpl); + + if (bufferSize > 0) + q->setReadBufferSize(bufferSize); + + bytesDownloaded = bufferSize; + + if (percentFinished != 100) + downloadBufferCurrentSize += bufferSize; + else + downloadBufferCurrentSize = bufferSize; + + totalDownloadSize = downloadBufferCurrentSize; + + downloadBuffer.append(buffer, bufferSize); + + if (downloadBufferCurrentSize == totalDownloadSize) { + q->setFinished(true); + emit q->finished(); + } +} + +//taken from qnetworkrequest.cpp +static int parseHeaderName(const QByteArray &headerName) +{ + if (headerName.isEmpty()) + return -1; + + switch (tolower(headerName.at(0))) { + case 'c': + if (qstricmp(headerName.constData(), "content-type") == 0) + return QNetworkRequest::ContentTypeHeader; + else if (qstricmp(headerName.constData(), "content-length") == 0) + return QNetworkRequest::ContentLengthHeader; + else if (qstricmp(headerName.constData(), "cookie") == 0) + return QNetworkRequest::CookieHeader; + break; + + case 'l': + if (qstricmp(headerName.constData(), "location") == 0) + return QNetworkRequest::LocationHeader; + else if (qstricmp(headerName.constData(), "last-modified") == 0) + return QNetworkRequest::LastModifiedHeader; + break; + + case 's': + if (qstricmp(headerName.constData(), "set-cookie") == 0) + return QNetworkRequest::SetCookieHeader; + else if (qstricmp(headerName.constData(), "server") == 0) + return QNetworkRequest::ServerHeader; + break; + + case 'u': + if (qstricmp(headerName.constData(), "user-agent") == 0) + return QNetworkRequest::UserAgentHeader; + break; + } + + return -1; // nothing found +} + + +void QNetworkReplyWasmImplPrivate::headersReceived(char *buffer) +{ + Q_Q(QNetworkReplyWasmImpl); + + QString bufferString = QString::fromUtf8(buffer); + if (!bufferString.isEmpty()) { + QStringList headers = bufferString.split(QString::fromUtf8("\r\n"), QString::SkipEmptyParts); + + for (int i = 0; i < headers.size(); i++) { + QString headerName = headers.at(i).split(QString::fromUtf8(": ")).at(0); + QString headersValue = headers.at(i).split(QString::fromUtf8(": ")).at(1); + if (headerName.isEmpty() || headersValue.isEmpty()) + continue; + + int headerIndex = parseHeaderName(headerName.toLocal8Bit()); + + if (headerIndex == -1) + q->setRawHeader(headerName.toLocal8Bit(), headersValue.toLocal8Bit()); + else + q->setHeader(static_cast<QNetworkRequest::KnownHeaders>(headerIndex), (QVariant)headersValue); + } + } + emit q->metaDataChanged(); +} + +void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingDataFinished() +{ + Q_Q(QNetworkReplyWasmImpl); + + // make sure this is only called once, ever. + //_q_bufferOutgoingData may call it or the readChannelFinished emission + if (state != Buffering) + return; + + // disconnect signals + QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + + // finally, start the request + doSendRequest(); +} + +void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData() +{ + Q_Q(QNetworkReplyWasmImpl); + + if (!outgoingDataBuffer) { + // first call, create our buffer + outgoingDataBuffer = QSharedPointer<QRingBuffer>::create(); + + QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + } + + qint64 bytesBuffered = 0; + qint64 bytesToBuffer = 0; + + // read data into our buffer + forever { + bytesToBuffer = outgoingData->bytesAvailable(); + // unknown? just try 2 kB, this also ensures we always try to read the EOF + if (bytesToBuffer <= 0) + bytesToBuffer = 2*1024; + + char *dst = outgoingDataBuffer->reserve(bytesToBuffer); + bytesBuffered = outgoingData->read(dst, bytesToBuffer); + + if (bytesBuffered == -1) { + // EOF has been reached. + outgoingDataBuffer->chop(bytesToBuffer); + + _q_bufferOutgoingDataFinished(); + break; + } else if (bytesBuffered == 0) { + // nothing read right now, just wait until we get called again + outgoingDataBuffer->chop(bytesToBuffer); + + break; + } else { + // don't break, try to read() again + outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered); + } + } +} + +//taken from qhttpthreaddelegate.cpp +QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url) +{ + QNetworkReply::NetworkError code; + // we've got an error + switch (httpStatusCode) { + case 400: // Bad Request + code = QNetworkReply::ProtocolInvalidOperationError; + break; + + case 401: // Authorization required + code = QNetworkReply::AuthenticationRequiredError; + break; + + case 403: // Access denied + code = QNetworkReply::ContentAccessDenied; + break; + + case 404: // Not Found + code = QNetworkReply::ContentNotFoundError; + break; + + case 405: // Method Not Allowed + code = QNetworkReply::ContentOperationNotPermittedError; + break; + + case 407: + code = QNetworkReply::ProxyAuthenticationRequiredError; + break; + + case 409: // Resource Conflict + code = QNetworkReply::ContentConflictError; + break; + + case 410: // Content no longer available + code = QNetworkReply::ContentGoneError; + break; + + case 418: // I'm a teapot + code = QNetworkReply::ProtocolInvalidOperationError; + break; + + case 500: // Internal Server Error + code = QNetworkReply::InternalServerError; + break; + + case 501: // Server does not support this functionality + code = QNetworkReply::OperationNotImplementedError; + break; + + case 503: // Service unavailable + code = QNetworkReply::ServiceUnavailableError; + break; + + default: + if (httpStatusCode > 500) { + // some kind of server error + code = QNetworkReply::UnknownServerError; + } else if (httpStatusCode >= 400) { + // content error we did not handle above + code = QNetworkReply::UnknownContentError; + } else { + qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"", + httpStatusCode, qPrintable(url.toString())); + code = QNetworkReply::ProtocolFailure; + } + }; + + return code; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkreplywasmimpl_p.h b/src/network/access/qnetworkreplywasmimpl_p.h new file mode 100644 index 0000000000..a707390503 --- /dev/null +++ b/src/network/access/qnetworkreplywasmimpl_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREPLYWASMIMPL_H +#define QNETWORKREPLYWASMIMPL_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 "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include "qnetworkaccessmanager.h" + +#include <QtCore/qfile.h> + +#include <private/qtnetworkglobal_p.h> +#include <private/qabstractfileengine_p.h> + +#include <emscripten.h> +#include <emscripten/html5.h> + +QT_BEGIN_NAMESPACE + +class QIODevice; + +class QNetworkReplyWasmImplPrivate; +class QNetworkReplyWasmImpl: public QNetworkReply +{ + Q_OBJECT +public: + QNetworkReplyWasmImpl(QObject *parent = nullptr); + ~QNetworkReplyWasmImpl(); + virtual void abort() override; + + // reimplemented from QNetworkReply + virtual void close() override; + virtual qint64 bytesAvailable() const override; + virtual bool isSequential () const override; + qint64 size() const override; + + virtual qint64 readData(char *data, qint64 maxlen) override; + + void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QIODevice *outgoingData); + + Q_DECLARE_PRIVATE(QNetworkReplyWasmImpl) + + Q_PRIVATE_SLOT(d_func(), void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)) + Q_PRIVATE_SLOT(d_func(), void emitDataReadProgress(qint64 done, qint64 total)) + Q_PRIVATE_SLOT(d_func(), void dataReceived(char *buffer, int bufferSize)) + +private: + QByteArray methodName() const; + +}; + +class QNetworkReplyWasmImplPrivate: public QNetworkReplyPrivate +{ +public: + QNetworkReplyWasmImplPrivate(); + ~QNetworkReplyWasmImplPrivate(); + + QNetworkAccessManagerPrivate *managerPrivate; + void doSendRequest(); + + void jsRequest(const QString &verb, const QString &url, void *, void *, void *, void *); + + static void onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int textBuffer, int size); + static void onProgressCallback(void *data, int done, int bytesTotal, uint timestamp); + static void onRequestErrorCallback(void *data, int statusCode, int statusReason); + static void onStateChangedCallback(int status); + static void onResponseHeadersCallback(void *data, int headers); + + void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &); + void emitDataReadProgress(qint64 done, qint64 total); + void dataReceived(char *buffer, int bufferSize); + void headersReceived(char *buffer); + + void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QIODevice *outgoingData); + + State state; + void _q_bufferOutgoingData(); + void _q_bufferOutgoingDataFinished(); + + QSharedPointer<QAtomicInt> pendingDownloadData; + QSharedPointer<QAtomicInt> pendingDownloadProgress; + + qint64 bytesDownloaded; + qint64 bytesBuffered; + + qint64 downloadBufferReadPosition; + qint64 downloadBufferCurrentSize; + qint64 totalDownloadSize; + qint64 percentFinished; + QByteArray downloadBuffer; + + QIODevice *outgoingData; + QSharedPointer<QRingBuffer> outgoingDataBuffer; + + static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url); + Q_DECLARE_PUBLIC(QNetworkReplyWasmImpl) +}; + +QT_END_NAMESPACE + +//Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders) + +#endif // QNETWORKREPLYWASMIMPL_H diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 2fc50a66de..57529761ee 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -98,6 +98,25 @@ QT_BEGIN_NAMESPACE header and contains a QDateTime representing the last modification date of the contents. + \value IfModifiedSinceHeader Corresponds to the HTTP If-Modified-Since + header and contains a QDateTime. It is usually added to a + QNetworkRequest. The server shall send a 304 (Not Modified) response + if the resource has not changed since this time. + + \value ETagHeader Corresponds to the HTTP ETag + header and contains a QString representing the last modification + state of the contents. + + \value IfMatchHeader Corresponds to the HTTP If-Match + header and contains a QStringList. It is usually added to a + QNetworkRequest. The server shall send a 412 (Precondition Failed) + response if the resource does not match. + + \value IfNoneMatchHeader Corresponds to the HTTP If-None-Match + header and contains a QStringList. It is usually added to a + QNetworkRequest. The server shall send a 304 (Not Modified) response + if the resource does match. + \value CookieHeader Corresponds to the HTTP Cookie header and contains a QList<QNetworkCookie> representing the cookies to be sent back to the server. @@ -785,6 +804,18 @@ static QByteArray headerName(QNetworkRequest::KnownHeaders header) case QNetworkRequest::LastModifiedHeader: return "Last-Modified"; + case QNetworkRequest::IfModifiedSinceHeader: + return "If-Modified-Since"; + + case QNetworkRequest::ETagHeader: + return "ETag"; + + case QNetworkRequest::IfMatchHeader: + return "If-Match"; + + case QNetworkRequest::IfNoneMatchHeader: + return "If-None-Match"; + case QNetworkRequest::CookieHeader: return "Cookie"; @@ -815,6 +846,9 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria case QNetworkRequest::ContentDispositionHeader: case QNetworkRequest::UserAgentHeader: case QNetworkRequest::ServerHeader: + case QNetworkRequest::ETagHeader: + case QNetworkRequest::IfMatchHeader: + case QNetworkRequest::IfNoneMatchHeader: return value.toByteArray(); case QNetworkRequest::LocationHeader: @@ -827,6 +861,7 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria } case QNetworkRequest::LastModifiedHeader: + case QNetworkRequest::IfModifiedSinceHeader: switch (value.userType()) { case QMetaType::QDate: case QMetaType::QDateTime: @@ -880,32 +915,46 @@ static int parseHeaderName(const QByteArray &headerName) switch (tolower(headerName.at(0))) { case 'c': - if (qstricmp(headerName.constData(), "content-type") == 0) + if (headerName.compare("content-type", Qt::CaseInsensitive) == 0) return QNetworkRequest::ContentTypeHeader; - else if (qstricmp(headerName.constData(), "content-length") == 0) + else if (headerName.compare("content-length", Qt::CaseInsensitive) == 0) return QNetworkRequest::ContentLengthHeader; - else if (qstricmp(headerName.constData(), "cookie") == 0) + else if (headerName.compare("cookie", Qt::CaseInsensitive) == 0) return QNetworkRequest::CookieHeader; else if (qstricmp(headerName.constData(), "content-disposition") == 0) return QNetworkRequest::ContentDispositionHeader; break; + case 'e': + if (qstricmp(headerName.constData(), "etag") == 0) + return QNetworkRequest::ETagHeader; + break; + + case 'i': + if (qstricmp(headerName.constData(), "if-modified-since") == 0) + return QNetworkRequest::IfModifiedSinceHeader; + if (qstricmp(headerName.constData(), "if-match") == 0) + return QNetworkRequest::IfMatchHeader; + if (qstricmp(headerName.constData(), "if-none-match") == 0) + return QNetworkRequest::IfNoneMatchHeader; + break; + case 'l': - if (qstricmp(headerName.constData(), "location") == 0) + if (headerName.compare("location", Qt::CaseInsensitive) == 0) return QNetworkRequest::LocationHeader; - else if (qstricmp(headerName.constData(), "last-modified") == 0) + else if (headerName.compare("last-modified", Qt::CaseInsensitive) == 0) return QNetworkRequest::LastModifiedHeader; break; case 's': - if (qstricmp(headerName.constData(), "set-cookie") == 0) + if (headerName.compare("set-cookie", Qt::CaseInsensitive) == 0) return QNetworkRequest::SetCookieHeader; - else if (qstricmp(headerName.constData(), "server") == 0) + else if (headerName.compare("server", Qt::CaseInsensitive) == 0) return QNetworkRequest::ServerHeader; break; case 'u': - if (qstricmp(headerName.constData(), "user-agent") == 0) + if (headerName.compare("user-agent", Qt::CaseInsensitive) == 0) return QNetworkRequest::UserAgentHeader; break; } @@ -936,6 +985,61 @@ static QVariant parseCookieHeader(const QByteArray &raw) return QVariant::fromValue(result); } +static QVariant parseETag(const QByteArray &raw) +{ + const QByteArray trimmed = raw.trimmed(); + if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")")) + return QVariant(); + + if (!trimmed.endsWith('"')) + return QVariant(); + + return QString::fromLatin1(trimmed); +} + +static QVariant parseIfMatch(const QByteArray &raw) +{ + const QByteArray trimmedRaw = raw.trimmed(); + if (trimmedRaw == "*") + return QStringList(QStringLiteral("*")); + + QStringList tags; + const QList<QByteArray> split = trimmedRaw.split(','); + for (const QByteArray &element : split) { + const QByteArray trimmed = element.trimmed(); + if (!trimmed.startsWith('"')) + continue; + + if (!trimmed.endsWith('"')) + continue; + + tags += QString::fromLatin1(trimmed); + } + return tags; +} + +static QVariant parseIfNoneMatch(const QByteArray &raw) +{ + const QByteArray trimmedRaw = raw.trimmed(); + if (trimmedRaw == "*") + return QStringList(QStringLiteral("*")); + + QStringList tags; + const QList<QByteArray> split = trimmedRaw.split(','); + for (const QByteArray &element : split) { + const QByteArray trimmed = element.trimmed(); + if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")")) + continue; + + if (!trimmed.endsWith('"')) + continue; + + tags += QString::fromLatin1(trimmed); + } + return tags; +} + + static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QByteArray &value) { // header is always a valid value @@ -963,8 +1067,18 @@ static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QBy } case QNetworkRequest::LastModifiedHeader: + case QNetworkRequest::IfModifiedSinceHeader: return parseHttpDate(value); + case QNetworkRequest::ETagHeader: + return parseETag(value); + + case QNetworkRequest::IfMatchHeader: + return parseIfMatch(value); + + case QNetworkRequest::IfNoneMatchHeader: + return parseIfNoneMatch(value); + case QNetworkRequest::CookieHeader: return parseCookieHeader(value); @@ -983,7 +1097,7 @@ QNetworkHeadersPrivate::findRawHeader(const QByteArray &key) const RawHeadersList::ConstIterator it = rawHeaders.constBegin(); RawHeadersList::ConstIterator end = rawHeaders.constEnd(); for ( ; it != end; ++it) - if (qstricmp(it->first.constData(), key.constData()) == 0) + if (it->first.compare(key, Qt::CaseInsensitive) == 0) return it; return end; // not found @@ -1064,7 +1178,7 @@ void QNetworkHeadersPrivate::setCookedHeader(QNetworkRequest::KnownHeaders heade void QNetworkHeadersPrivate::setRawHeaderInternal(const QByteArray &key, const QByteArray &value) { auto firstEqualsKey = [&key](const RawHeaderPair &header) { - return qstricmp(header.first.constData(), key.constData()) == 0; + return header.first.compare(key, Qt::CaseInsensitive) == 0; }; rawHeaders.erase(std::remove_if(rawHeaders.begin(), rawHeaders.end(), firstEqualsKey), diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index e104c139d9..8462eae8c8 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -63,7 +63,11 @@ public: SetCookieHeader, ContentDispositionHeader, // added for QMultipartMessage UserAgentHeader, - ServerHeader + ServerHeader, + IfModifiedSinceHeader, + ETagHeader, + IfMatchHeader, + IfNoneMatchHeader }; enum Attribute { HttpStatusCodeAttribute, |