// 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" #if QT_CONFIG(thread) #include #include #endif #include #include #include #include static EM_BOOL q_onWebSocketErrorCallback(int eventType, const EmscriptenWebSocketErrorEvent *e, void *userData) { Q_UNUSED(eventType) Q_UNUSED(e) QWebSocketPrivate *wsp = reinterpret_cast(userData); Q_ASSERT (wsp); emit wsp->q_func()->errorOccurred(wsp->error()); return EM_FALSE; } static EM_BOOL q_onWebSocketCloseCallback(int eventType, const EmscriptenWebSocketCloseEvent *emCloseEvent, void *userData) { Q_UNUSED(eventType) QWebSocketPrivate *wsp = reinterpret_cast(userData); Q_ASSERT (wsp); wsp->setSocketClosed(emCloseEvent); return EM_FALSE; } static EM_BOOL q_onWebSocketOpenCallback(int eventType, const EmscriptenWebSocketOpenEvent *e, void *userData) { Q_UNUSED(eventType) Q_UNUSED(e) QWebSocketPrivate *wsp = reinterpret_cast(userData); Q_ASSERT (wsp); wsp->setSocketState(QAbstractSocket::ConnectedState); emit wsp->q_func()->connected(); return EM_FALSE; } static EM_BOOL q_onWebSocketIncomingMessageCallback(int eventType, const EmscriptenWebSocketMessageEvent *e, void *userData) { Q_UNUSED(eventType) QWebSocketPrivate *wsp = reinterpret_cast(userData); Q_ASSERT(wsp); if (!e->isText) { QByteArray buffer(reinterpret_cast(e->data), e->numBytes); if (!buffer.isEmpty()) emit wsp->q_func()->binaryMessageReceived(buffer); } else { QString buffer = QString::fromUtf8(reinterpret_cast(e->data), e->numBytes - 1); emit wsp->q_func()->textMessageReceived(buffer); } return 0; } qint64 QWebSocketPrivate::sendTextMessage(const QString &message) { 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) { 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(reinterpret_cast(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) { Q_Q(QWebSocket); m_closeCode = closeCode; m_closeReason = reason; Q_EMIT q->aboutToClose(); setSocketState(QAbstractSocket::ClosingState); 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, const QWebSocketHandshakeOptions &options, bool mask) { Q_UNUSED(mask); 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(); 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().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; } } 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. // 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 (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(""); }