summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/access.pri7
-rw-r--r--src/network/access/http2/http2protocol.cpp3
-rw-r--r--src/network/access/qhsts.cpp5
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp15
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp2
-rw-r--r--src/network/access/qhttpnetworkheader.cpp6
-rw-r--r--src/network/access/qhttpnetworkreply.cpp5
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend.cpp4
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend_p.h1
-rw-r--r--src/network/access/qnetworkaccessfilebackend.cpp6
-rw-r--r--src/network/access/qnetworkaccessfilebackend_p.h2
-rw-r--r--src/network/access/qnetworkaccessftpbackend.cpp61
-rw-r--r--src/network/access/qnetworkaccessftpbackend_p.h5
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp13
-rw-r--r--src/network/access/qnetworkaccessmanager.h3
-rw-r--r--src/network/access/qnetworkdiskcache.cpp6
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp4
-rw-r--r--src/network/access/qnetworkreplywasmimpl.cpp640
-rw-r--r--src/network/access/qnetworkreplywasmimpl_p.h153
-rw-r--r--src/network/access/qnetworkrequest.cpp134
-rw-r--r--src/network/access/qnetworkrequest.h6
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,