summaryrefslogtreecommitdiff
path: root/src/websockets/qwebsocket_wasm_p.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/websockets/qwebsocket_wasm_p.cpp')
-rw-r--r--src/websockets/qwebsocket_wasm_p.cpp364
1 files changed, 226 insertions, 138 deletions
diff --git a/src/websockets/qwebsocket_wasm_p.cpp b/src/websockets/qwebsocket_wasm_p.cpp
index 73c596c..6d3f770 100644
--- a/src/websockets/qwebsocket_wasm_p.cpp
+++ b/src/websockets/qwebsocket_wasm_p.cpp
@@ -1,156 +1,113 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWebSockets 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 "qwebsocket_p.h"
-#include <QtCore/qcoreapplication.h>
+#if QT_CONFIG(thread)
+#include <QtCore/qthread.h>
+#include <emscripten/threading.h>
+#endif
#include <emscripten.h>
-#include <emscripten/bind.h>
+#include <emscripten/websocket.h>
+#include <emscripten/val.h>
-using namespace emscripten;
+#include <QHostAddress>
-static void q_onErrorCallback(val event)
+static EM_BOOL q_onWebSocketErrorCallback(int eventType,
+ const EmscriptenWebSocketErrorEvent *e,
+ void *userData)
{
- val target = event["target"];
+ Q_UNUSED(eventType)
+ Q_UNUSED(e)
- QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>());
+ QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate *>(userData);
Q_ASSERT (wsp);
- emit wsp->q_func()->error(wsp->error());
+ emit wsp->q_func()->errorOccurred(wsp->error());
+ return EM_FALSE;
}
-static void q_onCloseCallback(val event)
+static EM_BOOL q_onWebSocketCloseCallback(int eventType,
+ const EmscriptenWebSocketCloseEvent *emCloseEvent,
+ void *userData)
{
- val target = event["target"];
-
- QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>());
+ Q_UNUSED(eventType)
+ QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate *>(userData);
Q_ASSERT (wsp);
- wsp->setSocketState(QAbstractSocket::UnconnectedState);
- emit wsp->q_func()->disconnected();
+ wsp->setSocketClosed(emCloseEvent);
+ return EM_FALSE;
}
-static void q_onOpenCallback(val event)
+static EM_BOOL q_onWebSocketOpenCallback(int eventType,
+ const EmscriptenWebSocketOpenEvent *e, void *userData)
{
- val target = event["target"];
+ Q_UNUSED(eventType)
+ Q_UNUSED(e)
- QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>());
+ QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate *>(userData);
Q_ASSERT (wsp);
wsp->setSocketState(QAbstractSocket::ConnectedState);
emit wsp->q_func()->connected();
+ return EM_FALSE;
}
-static void q_onIncomingMessageCallback(val event)
+static EM_BOOL q_onWebSocketIncomingMessageCallback(int eventType,
+ const EmscriptenWebSocketMessageEvent *e,
+ void *userData)
{
- val target = event["target"];
-
- if (event["data"].typeOf().as<std::string>() == "string") {
- QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>());
- Q_ASSERT (wsp);
-
- const QString message = QString::fromStdString(event["data"].as<std::string>());
- if (!message.isEmpty())
- wsp->q_func()->textMessageReceived(message);
+ Q_UNUSED(eventType)
+ QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate *>(userData);
+ Q_ASSERT(wsp);
+
+ if (!e->isText) {
+ QByteArray buffer(reinterpret_cast<const char *>(e->data), e->numBytes);
+ if (!buffer.isEmpty())
+ emit wsp->q_func()->binaryMessageReceived(buffer);
} else {
- val reader = val::global("FileReader").new_();
- reader.set("onload", val::module_property("QWebSocketPrivate_readBlob"));
- reader.set("data-context", target["data-context"]);
- reader.call<void>("readAsArrayBuffer", event["data"]);
+ QString buffer = QString::fromUtf8(reinterpret_cast<const char *>(e->data), e->numBytes - 1);
+ emit wsp->q_func()->textMessageReceived(buffer);
}
-}
-
-static void q_readBlob(val event)
-{
- val fileReader = event["target"];
-
- QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(fileReader["data-context"].as<quintptr>());
- Q_ASSERT (wsp);
-
- // Set up source typed array
- val result = fileReader["result"]; // ArrayBuffer
- val Uint8Array = val::global("Uint8Array");
- val sourceTypedArray = Uint8Array.new_(result);
-
- // Allocate and set up destination typed array
- const size_t size = result["byteLength"].as<size_t>();
- QByteArray buffer(size, Qt::Uninitialized);
-
- val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"],
- reinterpret_cast<quintptr>(buffer.data()), size);
- destinationTypedArray.call<void>("set", sourceTypedArray);
-
- wsp->q_func()->binaryMessageReceived(buffer);
-}
-
-EMSCRIPTEN_BINDINGS(wasm_module) {
- function("QWebSocketPrivate_onErrorCallback", q_onErrorCallback);
- function("QWebSocketPrivate_onCloseCallback", q_onCloseCallback);
- function("QWebSocketPrivate_onOpenCallback", q_onOpenCallback);
- function("QWebSocketPrivate_onIncomingMessageCallback", q_onIncomingMessageCallback);
- function("QWebSocketPrivate_readBlob", q_readBlob);
+ return 0;
}
qint64 QWebSocketPrivate::sendTextMessage(const QString &message)
{
- socketContext.call<void>("send", message.toStdString());
- return message.length();
+ int result = 0;
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
+
+ if (m_readyState == 1) {
+ QByteArray messageArray = message.toUtf8();
+ result = emscripten_websocket_send_utf8_text(m_socketContext, messageArray);
+ if (result < 0)
+ emitErrorOccurred(QAbstractSocket::UnknownSocketError);
+ else
+ return messageArray.length();
+ } else
+ qWarning() << "Could not send message. Websocket is not open";
+
+ return result;
}
qint64 QWebSocketPrivate::sendBinaryMessage(const QByteArray &data)
{
- // Make a copy of the payload data; we don't know how long WebSocket.send() will
- // retain the memory view, while the QByteArray passed to this function may be
- // destroyed as soon as this function returns. In addition, the WebSocket.send()
- // API does not accept data from a view backet by a SharedArrayBuffer, which will
- // be the case for the view produced by typed_memory_view() when threads are enabled.
- val Uint8Array = val::global("Uint8Array");
- val dataCopy = Uint8Array.new_(data.size());
- val dataView = val(typed_memory_view(data.size(),
- reinterpret_cast<const unsigned char *>
- (data.constData())));
- dataCopy.call<void>("set", dataView);
-
- socketContext.call<void>("send", dataCopy);
- return data.length();
+ int result = 0;
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
+ if (m_readyState == 1) {
+ result = emscripten_websocket_send_binary(
+ m_socketContext, const_cast<void *>(reinterpret_cast<const void *>(data.constData())),
+ data.size());
+ if (result < 0)
+ emitErrorOccurred(QAbstractSocket::UnknownSocketError);
+ else
+ return data.size();
+ } else
+ qWarning() << "Could not send message. Websocket is not open";
+
+ return result;
}
void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason)
@@ -161,43 +118,174 @@ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString r
Q_EMIT q->aboutToClose();
setSocketState(QAbstractSocket::ClosingState);
- socketContext.call<void>("close", static_cast<quint16>(closeCode),
- reason.toLatin1().toStdString());
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
+
+ if (m_readyState == 1 || m_readyState == 0) {
+ emscripten_websocket_close(m_socketContext, (int)closeCode, reason.toUtf8());
+ }
+ setSocketState(QAbstractSocket::UnconnectedState);
+ emit q->disconnected();
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
}
-void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask)
+void QWebSocketPrivate::open(const QNetworkRequest &request,
+ const QWebSocketHandshakeOptions &options, bool mask)
{
Q_UNUSED(mask);
- Q_Q(QWebSocket);
+ Q_UNUSED(options)
+
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
+
+ if ((m_readyState == 1 || m_readyState == 3) && m_socketContext != 0) {
+ emitErrorOccurred(QAbstractSocket::OperationError);
+ return;
+ }
+
const QUrl url = request.url();
- if (!url.isValid() || url.toString().contains(QStringLiteral("\r\n"))) {
- setErrorString(QWebSocket::tr("Invalid URL."));
- Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
+
+ emscripten::val navProtocol = emscripten::val::global("self")["location"]["protocol"];
+
+ // An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
+ // and causes emscripten to assert
+ bool isSecureContext = (navProtocol.as<std::string>().find("https") == 0);
+
+ if (!url.isValid()
+ || url.toString().contains(QStringLiteral("\r\n"))) {
+ setErrorString(QWebSocket::tr("Connection refused"));
+
+ emitErrorOccurred(QAbstractSocket::ConnectionRefusedError);
+
return;
}
+ // exception for localhost/127.0.0.1/[::1]
+ // localhost has special privledges
+
+ QHostAddress hostAddress(url.host());
+
+ bool hostAddressIsLocal = (hostAddress == QHostAddress::LocalHost
+ || hostAddress == QHostAddress::LocalHostIPv6);
+
+ if (url.host() != QStringLiteral("localhost") && !hostAddressIsLocal) {
+ if (isSecureContext && url.scheme() == QStringLiteral("ws")) {
+ const QString message =
+ QWebSocket::tr("Unsupported WebSocket scheme: %1").arg(url.scheme());
+ setErrorString(message);
+ emitErrorOccurred(QAbstractSocket::UnsupportedSocketOperationError);
+ return;
+ }
+ }
- setSocketState(QAbstractSocket::ConnectingState);
- const std::string urlbytes = url.toString().toStdString();
+ EmscriptenWebSocketCreateAttributes attr;
+
+ emscripten_websocket_init_create_attributes(&attr); // memset
+ QByteArray thisUrl = url.toString(QUrl::FullyEncoded).toUtf8();
+ attr.url = thisUrl.constData();
+#if QT_CONFIG(thread)
+ // see https://github.com/emscripten-core/emscripten/blob/main/system/include/emscripten/websocket.h
+ // choose a default: create websocket on calling thread
+ attr.createOnMainThread = false;
+#endif
// HTML WebSockets do not support arbitrary request headers, but
// do support the WebSocket protocol header. This header is
// required for some use cases like MQTT.
- const std::string protocolHeaderValue = request.rawHeader("Sec-WebSocket-Protocol").toStdString();
- val webSocket = val::global("WebSocket");
-
- socketContext = !protocolHeaderValue.empty()
- ? webSocket.new_(urlbytes, protocolHeaderValue)
- : webSocket.new_(urlbytes);
-
- socketContext.set("onerror", val::module_property("QWebSocketPrivate_onErrorCallback"));
- socketContext.set("onclose", val::module_property("QWebSocketPrivate_onCloseCallback"));
- socketContext.set("onopen", val::module_property("QWebSocketPrivate_onOpenCallback"));
- socketContext.set("onmessage", val::module_property("QWebSocketPrivate_onIncomingMessageCallback"));
- socketContext.set("data-context", val(quintptr(reinterpret_cast<void *>(this))));
+
+ // add user subprotocol options
+ QStringList protocols = requestedSubProtocols();
+ QByteArray secProto;
+ if (!protocols.isEmpty()) {
+ // comma-separated list of protocol strings, no spaces
+ secProto = protocols.join(QStringLiteral(",")).toLatin1();
+ attr.protocols = secProto.constData();
+ }
+
+ // create and connect
+ setSocketState(QAbstractSocket::ConnectingState);
+ m_socketContext = emscripten_websocket_new(&attr);
+
+ if (m_socketContext <= 0) { // m_readyState might not be changed yet
+ // error
+ emitErrorOccurred(QAbstractSocket::UnknownSocketError);
+ return;
+ }
+
+#if QT_CONFIG(thread)
+ emscripten_websocket_set_onopen_callback_on_thread(m_socketContext, (void *)this,
+ q_onWebSocketOpenCallback,
+ (quintptr)QThread::currentThreadId());
+ emscripten_websocket_set_onmessage_callback_on_thread(m_socketContext, (void *)this,
+ q_onWebSocketIncomingMessageCallback,
+ (quintptr)QThread::currentThreadId());
+ emscripten_websocket_set_onerror_callback_on_thread(m_socketContext, (void *)this,
+ q_onWebSocketErrorCallback,
+ (quintptr)QThread::currentThreadId());
+ emscripten_websocket_set_onclose_callback_on_thread(m_socketContext, (void *)this,
+ q_onWebSocketCloseCallback,
+ (quintptr)QThread::currentThreadId());
+#else
+ emscripten_websocket_set_onopen_callback(m_socketContext, (void *)this,
+ q_onWebSocketOpenCallback);
+ emscripten_websocket_set_onmessage_callback(m_socketContext, (void *)this,
+ q_onWebSocketIncomingMessageCallback);
+ emscripten_websocket_set_onerror_callback(m_socketContext, (void *)this,
+ q_onWebSocketErrorCallback);
+ emscripten_websocket_set_onclose_callback(m_socketContext, (void *)this,
+ q_onWebSocketCloseCallback);
+#endif
}
bool QWebSocketPrivate::isValid() const
{
- return (!socketContext.isUndefined() &&
- (m_socketState == QAbstractSocket::ConnectedState));
+ return (m_socketContext > 0 && m_socketState == QAbstractSocket::ConnectedState);
+}
+
+void QWebSocketPrivate::setSocketClosed(const EmscriptenWebSocketCloseEvent *emCloseEvent)
+{
+ Q_Q(QWebSocket);
+ m_closeCode = (QWebSocketProtocol::CloseCode)emCloseEvent->code;
+
+ m_closeReason = QString::fromUtf8(emCloseEvent->reason);
+
+ if (m_closeReason.isEmpty()) {
+ m_closeReason = closeCodeToString(m_closeCode);
+ }
+
+ if (m_socketState == QAbstractSocket::ConnectedState) {
+ Q_EMIT q->aboutToClose();
+ setSocketState(QAbstractSocket::ClosingState);
+ }
+
+ if (!emCloseEvent->wasClean) {
+ m_errorString = QStringLiteral("The remote host closed the connection");
+ emit q->errorOccurred(error());
+ }
+
+ emscripten_websocket_get_ready_state(m_socketContext, &m_readyState);
+
+ if (m_readyState == 3) { // closed
+ setSocketState(QAbstractSocket::UnconnectedState);
+ emit q->disconnected();
+ emscripten_websocket_delete(emCloseEvent->socket);
+ m_socketContext = 0;
+ }
+}
+
+QString QWebSocketPrivate::closeCodeToString(QWebSocketProtocol::CloseCode code)
+{
+ switch (code) {
+ case QWebSocketProtocol::CloseCodeNormal: return QStringLiteral("Normal closure");
+ case QWebSocketProtocol::CloseCodeGoingAway: return QStringLiteral("Going away");
+ case QWebSocketProtocol::CloseCodeProtocolError: return QStringLiteral("Protocol error");
+ case QWebSocketProtocol::CloseCodeDatatypeNotSupported: return QStringLiteral("Unsupported data");
+ case QWebSocketProtocol::CloseCodeReserved1004: return QStringLiteral("Reserved");
+ case QWebSocketProtocol::CloseCodeMissingStatusCode: return QStringLiteral("No status received");
+ case QWebSocketProtocol::CloseCodeAbnormalDisconnection: return QStringLiteral("Abnormal closure");
+ case QWebSocketProtocol::CloseCodeWrongDatatype: return QStringLiteral("Invalid frame payload data");
+ case QWebSocketProtocol::CloseCodePolicyViolated: return QStringLiteral("Policy violation");
+ case QWebSocketProtocol::CloseCodeTooMuchData: return QStringLiteral("Message too big");
+ case QWebSocketProtocol::CloseCodeMissingExtension: return QStringLiteral("Mandatory extension missing");
+ case QWebSocketProtocol::CloseCodeBadOperation: return QStringLiteral("Internal server error");
+ case QWebSocketProtocol::CloseCodeTlsHandshakeFailed: return QStringLiteral("TLS handshake failed");
+ };
+ return QStringLiteral("");
}