diff options
Diffstat (limited to 'src/websockets/qwebsocket_wasm_p.cpp')
-rw-r--r-- | src/websockets/qwebsocket_wasm_p.cpp | 364 |
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(""); } |