| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>. |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwebsocketserver.h" |
| #include "qwebsocketserver_p.h" |
| #ifndef QT_NO_SSL |
| #include "qsslserver_p.h" |
| #endif |
| #include "qwebsocketprotocol.h" |
| #include "qwebsockethandshakerequest_p.h" |
| #include "qwebsockethandshakeresponse_p.h" |
| #include "qwebsocket.h" |
| #include "qwebsocket_p.h" |
| #include "qwebsocketcorsauthenticator.h" |
| |
| #include <QtCore/QTimer> |
| #include <QtNetwork/QTcpServer> |
| #include <QtNetwork/QTcpSocket> |
| #include <QtNetwork/QNetworkProxy> |
| |
| QT_BEGIN_NAMESPACE |
| |
| //both constants are taken from the default settings of Apache |
| //see: http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfieldsize and |
| //http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfields |
| const int MAX_HEADERLINE_LENGTH = 8 * 1024; //maximum length of a http request header line |
| const int MAX_HEADERLINES = 100; //maximum number of http request header lines |
| |
| /*! |
| \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); |
| if (m_secureMode == NonSecureMode) { |
| m_pTcpServer = new QTcpServer(q); |
| if (Q_LIKELY(m_pTcpServer)) |
| QObjectPrivate::connect(m_pTcpServer, &QTcpServer::newConnection, |
| 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; |
| if (Q_LIKELY(m_pTcpServer)) { |
| QObjectPrivate::connect(pSslServer, &QSslServer::newEncryptedConnection, |
| this, &QWebSocketServerPrivate::onNewConnection, |
| Qt::QueuedConnection); |
| QObjectPrivate::connect(pSslServer, &QSslServer::startedEncryptionHandshake, |
| this, &QWebSocketServerPrivate::startHandshakeTimeout); |
| QObject::connect(pSslServer, &QSslServer::peerVerifyError, |
| q, &QWebSocketServer::peerVerifyError); |
| QObject::connect(pSslServer, &QSslServer::sslErrors, |
| q, &QWebSocketServer::sslErrors); |
| QObject::connect(pSslServer, &QSslServer::preSharedKeyAuthenticationRequired, |
| q, &QWebSocketServer::preSharedKeyAuthenticationRequired); |
| } |
| #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 |
| */ |
| 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 |
| */ |
| QStringList QWebSocketServerPrivate::supportedProtocols() const |
| { |
| QStringList supportedProtocols; |
| return supportedProtocols; //no protocols are currently supported |
| } |
| |
| /*! |
| \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(); |
| if (Q_LIKELY(pTcpSocket) && m_secureMode == NonSecureMode) |
| startHandshakeTimeout(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)) |
| pTcpSocket->deleteLater(); |
| } |
| } |
| |
| /*! |
| \internal |
| */ |
| void QWebSocketServerPrivate::handshakeReceived() |
| { |
| Q_Q(QWebSocketServer); |
| QObject *sender = q->sender(); |
| if (Q_UNLIKELY(!sender)) { |
| return; |
| } |
| QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(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 = MAX_HEADERLINE_LENGTH * 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.length() >= 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); |
| QTextStream textStream(header, QIODevice::ReadOnly); |
| request.readHandshake(textStream, MAX_HEADERLINE_LENGTH, MAX_HEADERLINES); |
| |
| if (request.isValid()) { |
| QWebSocketCorsAuthenticator corsAuthenticator(request.origin()); |
| Q_EMIT q->originAuthenticationRequired(&corsAuthenticator); |
| |
| QWebSocketHandshakeResponse response(request, |
| m_serverName, |
| corsAuthenticator.allowed(), |
| supportedVersions(), |
| supportedProtocols(), |
| 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->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 |