summaryrefslogtreecommitdiffstats
path: root/src/network/access/qnetworkreplywasmimpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access/qnetworkreplywasmimpl.cpp')
-rw-r--r--src/network/access/qnetworkreplywasmimpl.cpp394
1 files changed, 250 insertions, 144 deletions
diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp
index 4dfdd99543..7d2b6a701e 100644
--- a/src/network/access/qnetworkreplywasmimpl.cpp
+++ b/src/network/access/qnetworkreplywasmimpl.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qnetworkreplywasmimpl_p.h"
#include "qnetworkrequest.h"
@@ -45,6 +9,9 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qthread.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
+#include <QtCore/private/qoffsetstringarray_p.h>
+#include <QtCore/private/qtools_p.h>
#include <private/qnetworkaccessmanager_p.h>
#include <private/qnetworkfile_p.h>
@@ -54,6 +21,41 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
+namespace {
+
+static constexpr auto BannedHeaders = qOffsetStringArray(
+ "accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "date",
+ "dnt",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "via"
+);
+
+bool isUnsafeHeader(QLatin1StringView header) noexcept
+{
+ return header.startsWith("proxy-"_L1, Qt::CaseInsensitive)
+ || header.startsWith("sec-"_L1, Qt::CaseInsensitive)
+ || BannedHeaders.contains(header, Qt::CaseInsensitive);
+}
+} // namespace
+
QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate()
: QNetworkReplyPrivate()
, managerPrivate(0)
@@ -61,16 +63,28 @@ QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate()
, downloadBufferCurrentSize(0)
, totalDownloadSize(0)
, percentFinished(0)
- , m_fetch(0)
+ , m_fetch(nullptr)
+ , m_fetchContext(nullptr)
{
}
QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate()
{
- if (m_fetch) {
- emscripten_fetch_close(m_fetch);
- m_fetch = 0;
+
+ if (m_fetchContext) { // fetch has been initiated
+ std::unique_lock lock{ m_fetchContext->mutex };
+
+ if (m_fetchContext->state == FetchContext::State::SCHEDULED
+ || m_fetchContext->state == FetchContext::State::SENT
+ || m_fetchContext->state == FetchContext::State::CANCELED) {
+ m_fetchContext->reply = nullptr;
+ m_fetchContext->state = FetchContext::State::TO_BE_DESTROYED;
+ } else if (m_fetchContext->state == FetchContext::State::FINISHED) {
+ lock.unlock();
+ delete m_fetchContext;
+ }
}
+
}
QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
@@ -82,6 +96,9 @@ QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl()
{
+ if (isRunning())
+ abort();
+ close();
}
QByteArray QNetworkReplyWasmImpl::methodName() const
@@ -108,20 +125,44 @@ QByteArray QNetworkReplyWasmImpl::methodName() const
void QNetworkReplyWasmImpl::close()
{
+ Q_D(QNetworkReplyWasmImpl);
+
+ if (d->state != QNetworkReplyPrivate::Aborted &&
+ d->state != QNetworkReplyPrivate::Finished &&
+ d->state != QNetworkReplyPrivate::Idle) {
+ d->state = QNetworkReplyPrivate::Finished;
+ d->setCanceled();
+ }
+ emscripten_fetch_close(d->m_fetch);
QNetworkReply::close();
- setFinished(true);
- emit finished();
}
void QNetworkReplyWasmImpl::abort()
{
- Q_D( QNetworkReplyWasmImpl);
+ Q_D(QNetworkReplyWasmImpl);
+
if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
return;
d->state = QNetworkReplyPrivate::Aborted;
- d->doAbort();
- close();
+ d->setCanceled();
+}
+
+void QNetworkReplyWasmImplPrivate::setCanceled()
+{
+ Q_Q(QNetworkReplyWasmImpl);
+ {
+ if (m_fetchContext) {
+ std::scoped_lock lock{ m_fetchContext->mutex };
+ if (m_fetchContext->state == FetchContext::State::SCHEDULED
+ || m_fetchContext->state == FetchContext::State::SENT)
+ m_fetchContext->state = FetchContext::State::CANCELED;
+ }
+ }
+
+ emitReplyError(QNetworkReply::OperationCanceledError, QStringLiteral("Operation canceled"));
+ q->setFinished(true);
+ emit q->finished();
}
qint64 QNetworkReplyWasmImpl::bytesAvailable() const
@@ -198,11 +239,6 @@ void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusC
handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusReason);
}
-void QNetworkReplyWasmImplPrivate::doAbort() const
-{
- emscripten_fetch_close(m_fetch);
-}
-
constexpr int getArraySize (int factor) {
return 2 * factor + 1;
}
@@ -214,36 +250,7 @@ void QNetworkReplyWasmImplPrivate::doSendRequest()
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
- strcpy(attr.requestMethod, q->methodName().constData());
-
- QList<QByteArray> headersData = request.rawHeaderList();
- int arrayLength = getArraySize(headersData.count());
-
- if (headersData.count() > 0) {
- const char* customHeaders[arrayLength];
- int i = 0;
- for (i; i < headersData.count(); i++) {
- customHeaders[i] = headersData[i].constData();
- customHeaders[i + 1] = request.rawHeader(headersData[i]).constData();
- }
- customHeaders[i] = nullptr;
- attr.requestHeaders = customHeaders;
- }
-
- if (outgoingData) { // data from post request
- // handle extra data
- requestData = outgoingData->readAll(); // is there a size restriction here?
- if (!requestData.isEmpty()) {
- attr.requestData = requestData.data();
- attr.requestDataSize = requestData.size();
- }
- }
-
- // username & password
- if (!request.url().userInfo().isEmpty()) {
- attr.userName = request.url().userName().toUtf8();
- attr.password = request.url().password().toUtf8();
- }
+ qstrncpy(attr.requestMethod, q->methodName().constData(), 32); // requestMethod is char[32] in emscripten
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
@@ -262,17 +269,74 @@ void QNetworkReplyWasmImplPrivate::doSendRequest()
attr.attributes -= EMSCRIPTEN_FETCH_PERSIST_FILE;
}
+ attr.withCredentials = request.attribute(QNetworkRequest::UseCredentialsAttribute, false).toBool();
attr.onsuccess = QNetworkReplyWasmImplPrivate::downloadSucceeded;
attr.onerror = QNetworkReplyWasmImplPrivate::downloadFailed;
attr.onprogress = QNetworkReplyWasmImplPrivate::downloadProgress;
attr.onreadystatechange = QNetworkReplyWasmImplPrivate::stateChange;
- attr.timeoutMSecs = QNetworkRequest::DefaultTransferTimeoutConstant;
- attr.userData = reinterpret_cast<void *>(this);
+ attr.timeoutMSecs = request.transferTimeout();
+
+ m_fetchContext = new FetchContext(this);;
+ attr.userData = static_cast<void *>(m_fetchContext);
+ if (outgoingData) { // data from post request
+ m_fetchContext->requestData = outgoingData->readAll(); // is there a size restriction here?
+ if (!m_fetchContext->requestData.isEmpty()) {
+ attr.requestData = m_fetchContext->requestData.data();
+ attr.requestDataSize = m_fetchContext->requestData.size();
+ }
+ }
- QString dPath = QStringLiteral("/home/web_user/") + request.url().fileName();
- attr.destinationPath = dPath.toUtf8();
+ QEventDispatcherWasm::runOnMainThread([attr, fetchContext = m_fetchContext]() mutable {
+ std::unique_lock lock{ fetchContext->mutex };
+ if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ }
+ const auto reply = fetchContext->reply;
+ const auto &request = reply->request;
+
+ QByteArray userName, password;
+ if (!request.url().userInfo().isEmpty()) {
+ userName = request.url().userName().toUtf8();
+ password = request.url().password().toUtf8();
+ attr.userName = userName.constData();
+ attr.password = password.constData();
+ }
- m_fetch = emscripten_fetch(&attr, request.url().toString().toUtf8());
+ QList<QByteArray> headersData = request.rawHeaderList();
+ int arrayLength = getArraySize(headersData.count());
+ const char *customHeaders[arrayLength];
+ QStringList trimmedHeaders;
+ if (headersData.count() > 0) {
+ int i = 0;
+ for (const auto &headerName : headersData) {
+ if (isUnsafeHeader(QLatin1StringView(headerName.constData()))) {
+ trimmedHeaders.push_back(QString::fromLatin1(headerName));
+ } else {
+ customHeaders[i++] = headerName.constData();
+ customHeaders[i++] = request.rawHeader(headerName).constData();
+ }
+ }
+ if (!trimmedHeaders.isEmpty()) {
+ qWarning() << "Qt has trimmed the following forbidden headers from the request:"
+ << trimmedHeaders.join(QLatin1StringView(", "));
+ }
+ customHeaders[i] = nullptr;
+ attr.requestHeaders = customHeaders;
+ }
+
+ auto url = request.url().toString().toUtf8();
+ QString dPath = "/home/web_user/"_L1 + request.url().fileName();
+ QByteArray destinationPath = dPath.toUtf8();
+ attr.destinationPath = destinationPath.constData();
+ reply->m_fetch = emscripten_fetch(&attr, url.constData());
+ fetchContext->state = FetchContext::State::SENT;
+ });
+ state = Working;
}
void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)
@@ -289,15 +353,16 @@ void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qi
totalDownloadSize = bytesTotal;
- percentFinished = (bytesReceived / bytesTotal) * 100;
+ percentFinished = bytesTotal ? (bytesReceived / bytesTotal) * 100 : 100;
emit q->downloadProgress(bytesReceived, bytesTotal);
}
-void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bufferSize)
+void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer)
{
Q_Q(QNetworkReplyWasmImpl);
+ const qsizetype bufferSize = buffer.size();
if (bufferSize > 0)
q->setReadBufferSize(bufferSize);
@@ -310,15 +375,9 @@ void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bu
totalDownloadSize = downloadBufferCurrentSize;
- downloadBuffer.append(buffer, bufferSize);
+ downloadBuffer.append(buffer);
emit q->readyRead();
-
- if (downloadBufferCurrentSize == totalDownloadSize) {
- q->setFinished(true);
- emit q->readChannelFinished();
- emit q->finished();
- }
}
//taken from qnetworkrequest.cpp
@@ -327,32 +386,36 @@ static int parseHeaderName(const QByteArray &headerName)
if (headerName.isEmpty())
return -1;
- switch (tolower(headerName.at(0))) {
+ auto is = [&](const char *what) {
+ return qstrnicmp(headerName.data(), headerName.size(), what) == 0;
+ };
+
+ switch (QtMiscUtils::toAsciiLower(headerName.front())) {
case 'c':
- if (qstricmp(headerName.constData(), "content-type") == 0)
+ if (is("content-type"))
return QNetworkRequest::ContentTypeHeader;
- else if (qstricmp(headerName.constData(), "content-length") == 0)
+ else if (is("content-length"))
return QNetworkRequest::ContentLengthHeader;
- else if (qstricmp(headerName.constData(), "cookie") == 0)
+ else if (is("cookie"))
return QNetworkRequest::CookieHeader;
break;
case 'l':
- if (qstricmp(headerName.constData(), "location") == 0)
+ if (is("location"))
return QNetworkRequest::LocationHeader;
- else if (qstricmp(headerName.constData(), "last-modified") == 0)
+ else if (is("last-modified"))
return QNetworkRequest::LastModifiedHeader;
break;
case 's':
- if (qstricmp(headerName.constData(), "set-cookie") == 0)
+ if (is("set-cookie"))
return QNetworkRequest::SetCookieHeader;
- else if (qstricmp(headerName.constData(), "server") == 0)
+ else if (is("server"))
return QNetworkRequest::ServerHeader;
break;
case 'u':
- if (qstricmp(headerName.constData(), "user-agent") == 0)
+ if (is("user-agent"))
return QNetworkRequest::UserAgentHeader;
break;
}
@@ -370,8 +433,8 @@ void QNetworkReplyWasmImplPrivate::headersReceived(const QByteArray &buffer)
for (int i = 0; i < headers.size(); i++) {
if (headers.at(i).contains(':')) { // headers include final \x00, so skip
- QByteArray headerName = headers.at(i).split(': ').at(0).trimmed();
- QByteArray headersValue = headers.at(i).split(': ').at(1).trimmed();
+ QByteArray headerName = headers.at(i).split(':').at(0).trimmed();
+ QByteArray headersValue = headers.at(i).split(':').at(1).trimmed();
if (headerName.isEmpty() || headersValue.isEmpty())
continue;
@@ -411,7 +474,7 @@ void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData()
if (!outgoingDataBuffer) {
// first call, create our buffer
- outgoingDataBuffer = QSharedPointer<QRingBuffer>::create();
+ outgoingDataBuffer = std::make_shared<QRingBuffer>();
QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
@@ -450,14 +513,39 @@ void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData()
void QNetworkReplyWasmImplPrivate::downloadSucceeded(emscripten_fetch_t *fetch)
{
- QNetworkReplyWasmImplPrivate *reply =
- reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- if (reply) {
- QByteArray buffer(fetch->data, fetch->numBytes);
- reply->dataReceived(buffer, buffer.size());
+ auto fetchContext = static_cast<FetchContext *>(fetch->userData);
+ std::unique_lock lock{ fetchContext->mutex };
+
+ if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ } else if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::SENT) {
+ const auto reply = fetchContext->reply;
+ if (reply->state != QNetworkReplyPrivate::Aborted) {
+ QByteArray buffer(fetch->data, fetch->numBytes);
+ reply->dataReceived(buffer);
+ QByteArray statusText(fetch->statusText);
+ reply->setStatusCode(fetch->status, statusText);
+ reply->setReplyFinished();
+ }
+ reply->m_fetch = nullptr;
+ fetchContext->state = FetchContext::State::FINISHED;
}
}
+void QNetworkReplyWasmImplPrivate::setReplyFinished()
+{
+ Q_Q(QNetworkReplyWasmImpl);
+ state = QNetworkReplyPrivate::Finished;
+ q->setFinished(true);
+ emit q->readChannelFinished();
+ emit q->finished();
+}
+
void QNetworkReplyWasmImplPrivate::setStatusCode(int status, const QByteArray &statusText)
{
Q_Q(QNetworkReplyWasmImpl);
@@ -467,48 +555,64 @@ void QNetworkReplyWasmImplPrivate::setStatusCode(int status, const QByteArray &s
void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch)
{
- if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) {
- size_t headerLength = emscripten_fetch_get_response_headers_length(fetch);
- QByteArray str(headerLength, Qt::Uninitialized);
- emscripten_fetch_get_response_headers(fetch, str.data(), str.size());
- QNetworkReplyWasmImplPrivate *reply =
- reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- reply->headersReceived(str);
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ const auto reply = fetchContext->reply;
+ if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
+ if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) {
+ size_t headerLength = emscripten_fetch_get_response_headers_length(fetch);
+ QByteArray str(headerLength, Qt::Uninitialized);
+ emscripten_fetch_get_response_headers(fetch, str.data(), str.size());
+ reply->headersReceived(str);
+ }
}
}
void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch)
{
- QNetworkReplyWasmImplPrivate *reply =
- reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- Q_ASSERT(reply);
-
- if (fetch->status < 400) {
- uint64_t bytes = fetch->dataOffset + fetch->numBytes;
- uint64_t tBytes = fetch->totalBytes; // totalBytes can be 0 if server not reporting content length
- if (tBytes == 0)
- tBytes = bytes;
- reply->emitDataReadProgress(bytes, tBytes);
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ const auto reply = fetchContext->reply;
+ if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
+ if (fetch->status < 400) {
+ uint64_t bytes = fetch->dataOffset + fetch->numBytes;
+ uint64_t tBytes = fetch->totalBytes; // totalBytes can be 0 if server not reporting content length
+ if (tBytes == 0)
+ tBytes = bytes;
+ reply->emitDataReadProgress(bytes, tBytes);
+ }
}
}
void QNetworkReplyWasmImplPrivate::downloadFailed(emscripten_fetch_t *fetch)
{
- QNetworkReplyWasmImplPrivate *reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- if (reply) {
- QString reasonStr;
- if (fetch->status > 600 || reply->state == QNetworkReplyPrivate::Aborted)
- reasonStr = QStringLiteral("Operation canceled");
- else
- reasonStr = QString::fromUtf8(fetch->statusText);
-
- QByteArray statusText(fetch->statusText);
- reply->setStatusCode(fetch->status, statusText);
- reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()), reasonStr);
- }
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ std::unique_lock lock{ fetchContext->mutex };
- if (fetch->status >= 400)
- emscripten_fetch_close(fetch); // Also free data on failure.
+ if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ } else if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::SENT) {
+ const auto reply = fetchContext->reply;
+ if (reply->state != QNetworkReplyPrivate::Aborted) {
+ QString reasonStr;
+ if (fetch->status > 600)
+ reasonStr = QStringLiteral("Operation canceled");
+ else
+ reasonStr = QString::fromUtf8(fetch->statusText);
+ QByteArray buffer(fetch->data, fetch->numBytes);
+ reply->dataReceived(buffer);
+ QByteArray statusText(fetch->statusText);
+ reply->setStatusCode(fetch->status, statusText);
+ reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()),
+ reasonStr);
+ reply->setReplyFinished();
+ }
+ reply->m_fetch = nullptr;
+ fetchContext->state = FetchContext::State::FINISHED;
+ }
}
//taken from qhttpthreaddelegate.cpp
@@ -586,3 +690,5 @@ QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int
}
QT_END_NAMESPACE
+
+#include "moc_qnetworkreplywasmimpl_p.cpp"