// Copyright (C) 2016 Kurt Pattyn . // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #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 #ifndef QT_NO_SSL #include "QtNetwork/QSslServer" #endif #include #include #include #include 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 &errors) { Q_UNUSED(socket); Q_EMIT q->sslErrors(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(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::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 QWebSocketServerPrivate::supportedVersions() const { QList 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(m_pTcpServer)->setSslConfiguration(sslConfiguration); } QSslConfiguration QWebSocketServerPrivate::sslConfiguration() const { if (m_secureMode == SecureMode) return qobject_cast(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); startHandshakeTimeout(pTcpSocket); handleConnection(pTcpSocket); } } /*! \internal */ void QWebSocketServerPrivate::onSocketDisconnected() { Q_Q(QWebSocketServer); QObject *sender = q->sender(); if (Q_LIKELY(sender)) { QTcpSocket *pTcpSocket = qobject_cast(sender); if (Q_LIKELY(pTcpSocket)) pTcpSocket->deleteLater(); } } /*! \internal */ void QWebSocketServerPrivate::handshakeReceived() { Q_Q(QWebSocketServer); QObject *sender = q->sender(); if (Q_UNLIKELY(!sender)) { return; } QTcpSocket *pTcpSocket = qobject_cast(sender); if (Q_UNLIKELY(!pTcpSocket)) { 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. //See: https://www.imperialviolet.org/2012/01/15/beastfollowup.html //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) const QByteArray& endOfHeaderMarker = QByteArrayLiteral("\r\n\r\n"); const qint64 byteAvailable = pTcpSocket->bytesAvailable(); QByteArray header = pTcpSocket->peek(byteAvailable); const int endOfHeaderIndex = header.indexOf(endOfHeaderMarker); if (endOfHeaderIndex < 0) { //then we don't have our header complete yet //check that no one is trying to exhaust our virtual memory const qint64 maxHeaderLength = QWebSocketPrivate::MAX_HEADERLINE_LENGTH * QWebSocketPrivate::MAX_HEADERLINES + endOfHeaderMarker.size(); if (Q_UNLIKELY(byteAvailable > maxHeaderLength)) { pTcpSocket->close(); setError(QWebSocketProtocol::CloseCodeTooMuchData, QWebSocketServer::tr("Header is too large.")); } return; } const int headerSize = endOfHeaderIndex + endOfHeaderMarker.size(); 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; } //don't read past the header header.resize(headerSize); //remove our header from the tcpSocket qint64 skippedSize = pTcpSocket->skip(headerSize); if (Q_UNLIKELY(skippedSize != headerSize)) { pTcpSocket->close(); setError(QWebSocketProtocol::CloseCodeProtocolError, QWebSocketServer::tr("Read handshake request header failed.")); 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.")); } } if (!success) { pTcpSocket->close(); } } void QWebSocketServerPrivate::handleConnection(QTcpSocket *pTcpSocket) const { if (Q_LIKELY(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) { 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(QStringLiteral("handshakeTimer"))) { handshakeTimer->stop(); delete handshakeTimer; } } QT_END_NAMESPACE