Revision control

Copy as Markdown

// 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
// Qt-Security score:critical reason:network-protocol
#include "qwebsocket_p.h"
#if QT_CONFIG(thread)
#include <QtCore/qthread.h>
#include <emscripten/threading.h>
#endif
#include <emscripten.h>
#include <emscripten/websocket.h>
#include <emscripten/val.h>
#include <QHostAddress>
static EM_BOOL q_onWebSocketErrorCallback(int eventType,
const EmscriptenWebSocketErrorEvent *e,
void *userData)
{
Q_UNUSED(eventType)
Q_UNUSED(e)
QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate *>(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<QWebSocketPrivate *>(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<QWebSocketPrivate *>(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<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 {
QString buffer = QString::fromUtf8(reinterpret_cast<const char *>(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<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)
{
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);
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<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;
}
}
setRequest(request, options);
EmscriptenWebSocketCreateAttributes attr;
emscripten_websocket_init_create_attributes(&attr); // memset
QByteArray thisUrl = url.toString(QUrl::FullyEncoded).toUtf8();
attr.url = thisUrl.constData();
#if QT_CONFIG(thread)
// 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("");
}