Revision control

++ // Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
++ // 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 "qwebsocketserver.h"
++ #include "qwebsocketserver_p.h"
++ #include "qwebsocketprotocol.h"
++ #include "qwebsockethandshakerequest_p.h"
++ #include "qwebsockethandshakeresponse_p.h"
++ #include "qwebsocket.h"
++ #include "qwebsocket_p.h"
++ #include "qwebsocketcorsauthenticator.h"
++ #include <limits>
++
++ #ifndef QT_NO_SSL
++ #include "QtNetwork/QSslServer"
++ #endif
++ #include <QtCore/QTimer>
++ #include <QtNetwork/QTcpServer>
++ #include <QtNetwork/QTcpSocket>
++ #include <QtNetwork/QNetworkProxy>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \internal
++ */
++ QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
++ QWebSocketServerPrivate::SslMode secureMode) :
++ QObjectPrivate(),
++ m_pTcpServer(nullptr),
++ m_serverName(serverName),
++ m_secureMode(secureMode),
++ m_pendingConnections(),
++ m_error(QWebSocketProtocol::CloseCodeNormal),
++ m_errorString(),
++ m_maxPendingConnections(30),
++ m_handshakeTimeout(10000)
++ {}
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::init()
++ {
++ Q_Q(QWebSocketServer);
++
++ #ifdef Q_OS_WASM
++ qWarning("QWebSocketServer: WebSocket servers are not supported by Qt for WebAssembly");
++ #endif
++
++ if (m_secureMode == NonSecureMode) {
++ m_pTcpServer = new QTcpServer(q);
++ if (Q_LIKELY(m_pTcpServer))
++ QObjectPrivate::connect(m_pTcpServer, &QTcpServer::pendingConnectionAvailable, this,
++ &QWebSocketServerPrivate::onNewConnection);
++ else
++ qFatal("Could not allocate memory for tcp server.");
++ } else {
++ #ifndef QT_NO_SSL
++ QSslServer *pSslServer = new QSslServer(q);
++ m_pTcpServer = pSslServer;
++ // Update the QSslServer with the timeout we have:
++ setHandshakeTimeout(m_handshakeTimeout);
++ if (Q_LIKELY(m_pTcpServer)) {
++ QObjectPrivate::connect(pSslServer, &QTcpServer::pendingConnectionAvailable, this,
++ &QWebSocketServerPrivate::onNewConnection,
++ Qt::QueuedConnection);
++ QObject::connect(pSslServer, &QSslServer::peerVerifyError, q,
++ [q](QSslSocket *socket, const QSslError &error) {
++ Q_UNUSED(socket);
++ Q_EMIT q->peerVerifyError(error);
++ });
++ QObject::connect(pSslServer, &QSslServer::sslErrors, q,
++ [q](QSslSocket *socket, const QList<QSslError> &errors) {
++ Q_EMIT q->sslErrors(errors);
++ Q_EMIT q->sslErrorsOccurred(socket, errors);
++ });
++ QObject::connect(pSslServer, &QSslServer::preSharedKeyAuthenticationRequired, q,
++ [q](QSslSocket *socket,
++ QSslPreSharedKeyAuthenticator *authenticator) {
++ Q_UNUSED(socket);
++ Q_EMIT q->preSharedKeyAuthenticationRequired(authenticator);
++ });
++ QObject::connect(pSslServer, &QSslServer::alertSent, q,
++ [q](QSslSocket *socket, QSsl::AlertLevel level,
++ QSsl::AlertType type, const QString &description) {
++ Q_UNUSED(socket);
++ Q_EMIT q->alertSent(level, type, description);
++ });
++ QObject::connect(pSslServer, &QSslServer::alertReceived, q,
++ [q](QSslSocket *socket, QSsl::AlertLevel level,
++ QSsl::AlertType type, const QString &description) {
++ Q_UNUSED(socket);
++ Q_EMIT q->alertReceived(level, type, description);
++ });
++ QObject::connect(pSslServer, &QSslServer::handshakeInterruptedOnError, q,
++ [q](QSslSocket *socket, const QSslError &error) {
++ Q_UNUSED(socket);
++ Q_EMIT q->handshakeInterruptedOnError(error);
++ });
++ }
++ #else
++ qFatal("SSL not supported on this platform.");
++ #endif
++ }
++ QObject::connect(m_pTcpServer, &QTcpServer::acceptError, q, &QWebSocketServer::acceptError);
++ }
++
++ /*!
++ \internal
++ */
++ QWebSocketServerPrivate::~QWebSocketServerPrivate()
++ {
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::close(bool aboutToDestroy)
++ {
++ Q_Q(QWebSocketServer);
++ m_pTcpServer->close();
++ while (!m_pendingConnections.isEmpty()) {
++ QWebSocket *pWebSocket = m_pendingConnections.dequeue();
++ pWebSocket->close(QWebSocketProtocol::CloseCodeGoingAway,
++ QWebSocketServer::tr("Server closed."));
++ pWebSocket->deleteLater();
++ }
++ if (!aboutToDestroy) {
++ //emit signal via the event queue, so the server gets time
++ //to process any hanging events, like flushing buffers aso
++ QMetaObject::invokeMethod(q, "closed", Qt::QueuedConnection);
++ }
++ }
++
++ /*!
++ \internal
++ */
++ QString QWebSocketServerPrivate::errorString() const
++ {
++ if (m_errorString.isEmpty())
++ return m_pTcpServer->errorString();
++ else
++ return m_errorString;
++ }
++
++ /*!
++ \internal
++ */
++ bool QWebSocketServerPrivate::hasPendingConnections() const
++ {
++ return !m_pendingConnections.isEmpty();
++ }
++
++ /*!
++ \internal
++ */
++ bool QWebSocketServerPrivate::isListening() const
++ {
++ return m_pTcpServer->isListening();
++ }
++
++ /*!
++ \internal
++ */
++ bool QWebSocketServerPrivate::listen(const QHostAddress &address, quint16 port)
++ {
++ bool success = m_pTcpServer->listen(address, port);
++ if (!success)
++ setErrorFromSocketError(m_pTcpServer->serverError(), m_pTcpServer->errorString());
++ return success;
++ }
++
++ /*!
++ \internal
++ */
++ int QWebSocketServerPrivate::maxPendingConnections() const
++ {
++ return m_maxPendingConnections;
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::addPendingConnection(QWebSocket *pWebSocket)
++ {
++ if (m_pendingConnections.size() < maxPendingConnections())
++ m_pendingConnections.enqueue(pWebSocket);
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setErrorFromSocketError(QAbstractSocket::SocketError error,
++ const QString &errorDescription)
++ {
++ Q_UNUSED(error);
++ setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, errorDescription);
++ }
++
++ /*!
++ \internal
++ */
++ QWebSocket *QWebSocketServerPrivate::nextPendingConnection()
++ {
++ QWebSocket *pWebSocket = nullptr;
++ if (Q_LIKELY(!m_pendingConnections.isEmpty()))
++ pWebSocket = m_pendingConnections.dequeue();
++ return pWebSocket;
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::pauseAccepting()
++ {
++ m_pTcpServer->pauseAccepting();
++ }
++
++ #ifndef QT_NO_NETWORKPROXY
++ /*!
++ \internal
++ */
++ QNetworkProxy QWebSocketServerPrivate::proxy() const
++ {
++ return m_pTcpServer->proxy();
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setProxy(const QNetworkProxy &networkProxy)
++ {
++ m_pTcpServer->setProxy(networkProxy);
++ }
++ #endif
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::resumeAccepting()
++ {
++ m_pTcpServer->resumeAccepting();
++ }
++
++ /*!
++ \internal
++ */
++ QHostAddress QWebSocketServerPrivate::serverAddress() const
++ {
++ return m_pTcpServer->serverAddress();
++ }
++
++ /*!
++ \internal
++ */
++ QWebSocketProtocol::CloseCode QWebSocketServerPrivate::serverError() const
++ {
++ return m_error;
++ }
++
++ /*!
++ \internal
++ */
++ quint16 QWebSocketServerPrivate::serverPort() const
++ {
++ return m_pTcpServer->serverPort();
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setMaxPendingConnections(int numConnections)
++ {
++ if (m_pTcpServer->maxPendingConnections() <= numConnections)
++ m_pTcpServer->setMaxPendingConnections(numConnections + 1);
++ m_maxPendingConnections = numConnections;
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setHandshakeTimeout(int msec)
++ {
++ #if QT_CONFIG(ssl)
++ if (auto *server = qobject_cast<QSslServer *>(m_pTcpServer)) {
++ int timeout = msec;
++ // Since QSslServer doesn't deal with negative numbers we set a very
++ // large one instead to keep some level of compatibility:
++ if (timeout < 0)
++ timeout = std::numeric_limits<int>::max();
++ server->setHandshakeTimeout(timeout);
++ }
++ #endif
++ m_handshakeTimeout = msec;
++ }
++
++ /*!
++ \internal
++ */
++ bool QWebSocketServerPrivate::setSocketDescriptor(qintptr socketDescriptor)
++ {
++ return m_pTcpServer->setSocketDescriptor(socketDescriptor);
++ }
++
++ /*!
++ \internal
++ */
++ qintptr QWebSocketServerPrivate::socketDescriptor() const
++ {
++ return m_pTcpServer->socketDescriptor();
++ }
++
++ /*!
++ \internal
++ */
++ QList<QWebSocketProtocol::Version> QWebSocketServerPrivate::supportedVersions() const
++ {
++ QList<QWebSocketProtocol::Version> supportedVersions;
++ supportedVersions << QWebSocketProtocol::currentVersion(); //we only support V13
++ return supportedVersions;
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setSupportedSubprotocols(const QStringList &protocols)
++ {
++ m_supportedSubprotocols = protocols;
++ }
++
++ /*!
++ \internal
++ */
++ QStringList QWebSocketServerPrivate::supportedSubprotocols() const
++ {
++ return m_supportedSubprotocols;
++ }
++
++ /*!
++ \internal
++ */
++ QStringList QWebSocketServerPrivate::supportedExtensions() const
++ {
++ QStringList supportedExtensions;
++ return supportedExtensions; //no extensions are currently supported
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::setServerName(const QString &serverName)
++ {
++ if (m_serverName != serverName)
++ m_serverName = serverName;
++ }
++
++ /*!
++ \internal
++ */
++ QString QWebSocketServerPrivate::serverName() const
++ {
++ return m_serverName;
++ }
++
++ /*!
++ \internal
++ */
++ QWebSocketServerPrivate::SslMode QWebSocketServerPrivate::secureMode() const
++ {
++ return m_secureMode;
++ }
++
++ #ifndef QT_NO_SSL
++ void QWebSocketServerPrivate::setSslConfiguration(const QSslConfiguration &sslConfiguration)
++ {
++ if (m_secureMode == SecureMode)
++ qobject_cast<QSslServer *>(m_pTcpServer)->setSslConfiguration(sslConfiguration);
++ }
++
++ QSslConfiguration QWebSocketServerPrivate::sslConfiguration() const
++ {
++ if (m_secureMode == SecureMode)
++ return qobject_cast<QSslServer *>(m_pTcpServer)->sslConfiguration();
++ else
++ return QSslConfiguration::defaultConfiguration();
++ }
++ #endif
++
++ void QWebSocketServerPrivate::setError(QWebSocketProtocol::CloseCode code, const QString &errorString)
++ {
++ if ((m_error != code) || (m_errorString != errorString)) {
++ Q_Q(QWebSocketServer);
++ m_error = code;
++ m_errorString = errorString;
++ Q_EMIT q->serverError(code);
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::onNewConnection()
++ {
++ while (m_pTcpServer->hasPendingConnections()) {
++ QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection();
++ Q_ASSERT(pTcpSocket);
++ handleConnection(pTcpSocket);
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::onSocketDisconnected()
++ {
++ Q_Q(QWebSocketServer);
++ QObject *sender = q->sender();
++ if (Q_LIKELY(sender)) {
++ QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender);
++ if (Q_LIKELY(pTcpSocket)) {
++ // Drops the partial request of a peer that left mid-handshake
++ m_preHandshakeHeaders.remove(pTcpSocket);
++ pTcpSocket->deleteLater();
++ m_error = QWebSocketProtocol::CloseCode::CloseCodeNormal;
++ m_errorString = QString();
++ }
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void QWebSocketServerPrivate::handshakeReceived()
++ {
++ Q_Q(QWebSocketServer);
++ QObject *sender = q->sender();
++ if (Q_UNLIKELY(!sender)) {
++ return;
++ }
++ QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender);
++ // We are invoked through a queued connection, so on some platforms this runs after
++ // disconnected() has already been emitted.
++ if (Q_UNLIKELY(!pTcpSocket || pTcpSocket->state() != QAbstractSocket::ConnectedState))
++ return;
++ //When using Google Chrome the handshake in received in two parts.
++ //Therefore, the readyRead signal is emitted twice.
++ //This is a guard against the BEAST attack.
++ //For Safari, the handshake is delivered at once
++ //FIXME: For FireFox, the readyRead signal is never emitted
++ //This is a bug in FireFox (see https://bugzilla.mozilla.org/show_bug.cgi?id=594502)
++
++ // According to RFC822 the body is separated from the headers by a null line (CRLF)
++ constexpr QByteArrayView endOfHeaderMarker = "\r\n\r\n";
++
++ const qint64 maxHeaderLength = QWebSocketPrivate::MAX_HEADERLINE_LENGTH
++ * QWebSocketPrivate::MAX_HEADERLINES + endOfHeaderMarker.size();
++
++ // Collected across readyRead emissions. readLine() never reads past a '\n', so bytes
++ // after the terminator stay in the socket and it can only complete at our end.
++ QByteArray &accumulated = m_preHandshakeHeaders[pTcpSocket];
++
++ // Reused by every iteration below, so a request arriving as many lines at once does
++ // not need a fresh buffer for each of them.
++ QByteArray chunk;
++
++ while (!accumulated.endsWith(endOfHeaderMarker) && pTcpSocket->bytesAvailable() > 0) {
++ // readLine() writes up to maxSize - 1 bytes plus a '\0'
++ const qint64 maxSize = qMin(pTcpSocket->bytesAvailable() + 1,
++ qint64(QWebSocketPrivate::MAX_HEADERLINE_LENGTH) + 1);
++ chunk.resize(maxSize);
++ const qint64 lineLength = pTcpSocket->readLine(chunk.data(), maxSize);
++ if (Q_UNLIKELY(lineLength <= 0))
++ break;
++
++ //check that no one is trying to exhaust our virtual memory
++ if (Q_UNLIKELY(accumulated.size() + lineLength >= maxHeaderLength)) {
++ m_preHandshakeHeaders.remove(pTcpSocket);
++ pTcpSocket->close();
++ setError(QWebSocketProtocol::CloseCodeTooMuchData,
++ QWebSocketServer::tr("Header is too large."));
++ return;
++ }
++ accumulated.append(chunk.constData(), lineLength);
++ }
++
++ if (!accumulated.endsWith(endOfHeaderMarker)) {
++ // The header isn't complete yet
++ return;
++ }
++ const QByteArray header = m_preHandshakeHeaders.take(pTcpSocket);
++
++ disconnect(pTcpSocket, &QTcpSocket::readyRead,
++ this, &QWebSocketServerPrivate::handshakeReceived);
++ bool success = false;
++ bool isSecure = (m_secureMode == SecureMode);
++
++ if (Q_UNLIKELY(m_pendingConnections.size() >= maxPendingConnections())) {
++ pTcpSocket->close();
++ setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
++ QWebSocketServer::tr("Too many pending connections."));
++ return;
++ }
++
++ QWebSocketHandshakeRequest request(pTcpSocket->peerPort(), isSecure);
++ request.readHandshake(header, QWebSocketPrivate::MAX_HEADERLINE_LENGTH);
++
++ if (request.isValid()) {
++ QWebSocketCorsAuthenticator corsAuthenticator(request.origin());
++ Q_EMIT q->originAuthenticationRequired(&corsAuthenticator);
++
++ QWebSocketHandshakeResponse response(request,
++ m_serverName,
++ corsAuthenticator.allowed(),
++ supportedVersions(),
++ supportedSubprotocols(),
++ supportedExtensions());
++
++ if (Q_LIKELY(response.isValid())) {
++ QTextStream httpStream(pTcpSocket);
++ httpStream << response;
++ httpStream.flush();
++
++ if (Q_LIKELY(response.canUpgrade())) {
++ QWebSocket *pWebSocket = QWebSocketPrivate::upgradeFrom(pTcpSocket,
++ request,
++ response);
++ if (Q_LIKELY(pWebSocket)) {
++ finishHandshakeTimeout(pTcpSocket);
++ addPendingConnection(pWebSocket);
++ Q_EMIT q->newConnection();
++ success = true;
++ } else {
++ setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
++ QWebSocketServer::tr("Upgrade to WebSocket failed."));
++ }
++ }
++ else {
++ setError(response.error(), response.errorString());
++ }
++ } else {
++ setError(QWebSocketProtocol::CloseCodeProtocolError,
++ QWebSocketServer::tr("Invalid response received."));
++ }
++ } else {
++ setError(QWebSocketProtocol::CloseCodeProtocolError,
++ QWebSocketServer::tr("Invalid handshake request."));
++ }
++ if (!success) {
++ pTcpSocket->close();
++ }
++ }
++
++ void QWebSocketServerPrivate::handleConnection(QTcpSocket *pTcpSocket) const
++ {
++ if (Q_LIKELY(pTcpSocket)) {
++ startHandshakeTimeout(pTcpSocket);
++
++ // Use a queued connection because a QSslSocket needs the event loop to process incoming
++ // data. If not queued, data is incomplete when handshakeReceived is called.
++ QObjectPrivate::connect(pTcpSocket, &QTcpSocket::readyRead,
++ this, &QWebSocketServerPrivate::handshakeReceived,
++ Qt::QueuedConnection);
++
++ // We received some data! We must emit now to be sure that handshakeReceived is called
++ // since the data could have been received before the signal and slot was connected.
++ if (pTcpSocket->bytesAvailable()) {
++ Q_EMIT pTcpSocket->readyRead();
++ }
++
++ QObjectPrivate::connect(pTcpSocket, &QTcpSocket::disconnected,
++ this, &QWebSocketServerPrivate::onSocketDisconnected);
++ }
++ }
++
++ void QWebSocketServerPrivate::startHandshakeTimeout(QTcpSocket *pTcpSocket) const
++ {
++ if (m_handshakeTimeout < 0)
++ return;
++
++ QTimer *handshakeTimer = new QTimer(pTcpSocket);
++ handshakeTimer->setSingleShot(true);
++ handshakeTimer->setObjectName(QStringLiteral("handshakeTimer"));
++ QObject::connect(handshakeTimer, &QTimer::timeout, pTcpSocket, &QAbstractSocket::close);
++ handshakeTimer->start(m_handshakeTimeout);
++ }
++
++ void QWebSocketServerPrivate::finishHandshakeTimeout(QTcpSocket *pTcpSocket)
++ {
++ if (QTimer *handshakeTimer = pTcpSocket->findChild<QTimer *>(QStringLiteral("handshakeTimer"))) {
++ handshakeTimer->stop();
++ delete handshakeTimer;
++ }
++ }
++
++ QT_END_NAMESPACE