Update license headers and add new license files
[contrib/qtwebsockets.git] / src / websockets / qwebsocketserver_p.cpp
index cb811c2..28f9bea 100644 (file)
@@ -1,40 +1,32 @@
 /****************************************************************************
 **
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtWebSockets module of the Qt Toolkit.
 **
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:LGPL21$
 ** 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 Digia.  For licensing terms and
-** conditions see http://qt.digia.com/licensing.  For further information
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
 ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file.  Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 **
 ** In addition, as a special exception, Digia gives you certain additional
-** rights.  These rights are described in the Digia Qt LGPL Exception
+** rights. These rights are described in the Digia Qt LGPL Exception
 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 **
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file.  Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-**
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/
@@ -61,43 +53,51 @@ QT_BEGIN_NAMESPACE
     \internal
  */
 QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
-                                                 QWebSocketServerPrivate::SecureMode secureMode,
-                                                 QWebSocketServer * const pWebSocketServer,
-                                                 QObject *parent) :
-    QObject(parent),
+                                                 QWebSocketServerPrivate::SslMode secureMode,
+                                                 QWebSocketServer * const pWebSocketServer) :
+    QObjectPrivate(),
     q_ptr(pWebSocketServer),
     m_pTcpServer(Q_NULLPTR),
     m_serverName(serverName),
     m_secureMode(secureMode),
     m_pendingConnections(),
-    m_error(QWebSocketProtocol::CC_NORMAL),
-    m_errorString()
+    m_error(QWebSocketProtocol::CloseCodeNormal),
+    m_errorString(),
+    m_maxPendingConnections(30)
 {
     Q_ASSERT(pWebSocketServer);
-    if (m_secureMode == NON_SECURE_MODE) {
-        m_pTcpServer = new QTcpServer(this);
+}
+
+/*!
+    \internal
+ */
+void QWebSocketServerPrivate::init()
+{
+    if (m_secureMode == NonSecureMode) {
+        m_pTcpServer = new QTcpServer();
         if (Q_LIKELY(m_pTcpServer))
-            connect(m_pTcpServer, &QTcpServer::newConnection,
-                    this, &QWebSocketServerPrivate::onNewConnection);
+            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(this);
+        QSslServer *pSslServer = new QSslServer();
         m_pTcpServer = pSslServer;
         if (Q_LIKELY(m_pTcpServer)) {
-            connect(pSslServer, &QSslServer::newEncryptedConnection,
-                    this, &QWebSocketServerPrivate::onNewConnection);
-            connect(pSslServer, &QSslServer::peerVerifyError,
-                    q_ptr, &QWebSocketServer::peerVerifyError);
-            connect(pSslServer, &QSslServer::sslErrors,
-                    q_ptr, &QWebSocketServer::sslErrors);
+            QObjectPrivate::connect(pSslServer, &QSslServer::newEncryptedConnection,
+                                    this, &QWebSocketServerPrivate::onNewConnection,
+                                    Qt::QueuedConnection);
+            QObject::connect(pSslServer, &QSslServer::peerVerifyError,
+                             q_ptr, &QWebSocketServer::peerVerifyError);
+            QObject::connect(pSslServer, &QSslServer::sslErrors,
+                             q_ptr, &QWebSocketServer::sslErrors);
         }
 #else
         qFatal("SSL not supported on this platform.");
 #endif
     }
-    connect(m_pTcpServer, &QTcpServer::acceptError, q_ptr, &QWebSocketServer::acceptError);
+    QObject::connect(m_pTcpServer, &QTcpServer::acceptError, q_ptr, &QWebSocketServer::acceptError);
 }
 
 /*!
@@ -105,25 +105,28 @@ QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
  */
 QWebSocketServerPrivate::~QWebSocketServerPrivate()
 {
-    close();
+    close(true);
     m_pTcpServer->deleteLater();
 }
 
 /*!
     \internal
  */
-void QWebSocketServerPrivate::close()
+void QWebSocketServerPrivate::close(bool aboutToDestroy)
 {
     Q_Q(QWebSocketServer);
     m_pTcpServer->close();
     while (!m_pendingConnections.isEmpty()) {
         QWebSocket *pWebSocket = m_pendingConnections.dequeue();
-        pWebSocket->close(QWebSocketProtocol::CC_GOING_AWAY, tr("Server closed."));
+        pWebSocket->close(QWebSocketProtocol::CloseCodeGoingAway,
+                          QWebSocketServer::tr("Server closed."));
         pWebSocket->deleteLater();
     }
-    //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);
+    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);
+    }
 }
 
 /*!
@@ -158,7 +161,10 @@ bool QWebSocketServerPrivate::isListening() const
  */
 bool QWebSocketServerPrivate::listen(const QHostAddress &address, quint16 port)
 {
-    return m_pTcpServer->listen(address, port);
+    bool success = m_pTcpServer->listen(address, port);
+    if (!success)
+        setErrorFromSocketError(m_pTcpServer->serverError(), m_pTcpServer->errorString());
+    return success;
 }
 
 /*!
@@ -166,7 +172,7 @@ bool QWebSocketServerPrivate::listen(const QHostAddress &address, quint16 port)
  */
 int QWebSocketServerPrivate::maxPendingConnections() const
 {
-    return m_pTcpServer->maxPendingConnections();
+    return m_maxPendingConnections;
 }
 
 /*!
@@ -181,6 +187,16 @@ void QWebSocketServerPrivate::addPendingConnection(QWebSocket *pWebSocket)
 /*!
     \internal
  */
+void QWebSocketServerPrivate::setErrorFromSocketError(QAbstractSocket::SocketError error,
+                                                      const QString &errorDescription)
+{
+    Q_UNUSED(error);
+    setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection, errorDescription);
+}
+
+/*!
+    \internal
+ */
 QWebSocket *QWebSocketServerPrivate::nextPendingConnection()
 {
     QWebSocket *pWebSocket = Q_NULLPTR;
@@ -251,7 +267,9 @@ quint16 QWebSocketServerPrivate::serverPort() const
  */
 void QWebSocketServerPrivate::setMaxPendingConnections(int numConnections)
 {
-    m_pTcpServer->setMaxPendingConnections(numConnections);
+    if (m_pTcpServer->maxPendingConnections() <= numConnections)
+        m_pTcpServer->setMaxPendingConnections(numConnections + 1);
+    m_maxPendingConnections = numConnections;
 }
 
 /*!
@@ -318,7 +336,7 @@ QString QWebSocketServerPrivate::serverName() const
 /*!
   \internal
  */
-QWebSocketServerPrivate::SecureMode QWebSocketServerPrivate::secureMode() const
+QWebSocketServerPrivate::SslMode QWebSocketServerPrivate::secureMode() const
 {
     return m_secureMode;
 }
@@ -326,22 +344,20 @@ QWebSocketServerPrivate::SecureMode QWebSocketServerPrivate::secureMode() const
 #ifndef QT_NO_SSL
 void QWebSocketServerPrivate::setSslConfiguration(const QSslConfiguration &sslConfiguration)
 {
-    if (m_secureMode == SECURE_MODE)
+    if (m_secureMode == SecureMode)
         qobject_cast<QSslServer *>(m_pTcpServer)->setSslConfiguration(sslConfiguration);
-    else
-        qWarning() << tr("Cannot set SSL configuration for non-secure server.");
 }
 
 QSslConfiguration QWebSocketServerPrivate::sslConfiguration() const
 {
-    if (m_secureMode == SECURE_MODE)
+    if (m_secureMode == SecureMode)
         return qobject_cast<QSslServer *>(m_pTcpServer)->sslConfiguration();
     else
         return QSslConfiguration::defaultConfiguration();
 }
 #endif
 
-void QWebSocketServerPrivate::setError(QWebSocketProtocol::CloseCode code, QString errorString)
+void QWebSocketServerPrivate::setError(QWebSocketProtocol::CloseCode code, const QString &errorString)
 {
     if ((m_error != code) || (m_errorString != errorString)) {
         Q_Q(QWebSocketServer);
@@ -357,7 +373,12 @@ void QWebSocketServerPrivate::setError(QWebSocketProtocol::CloseCode code, QStri
 void QWebSocketServerPrivate::onNewConnection()
 {
     QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection();
-    connect(pTcpSocket, &QTcpSocket::readyRead, this, &QWebSocketServerPrivate::handshakeReceived);
+    //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);
 }
 
 /*!
@@ -365,9 +386,11 @@ void QWebSocketServerPrivate::onNewConnection()
  */
 void QWebSocketServerPrivate::onCloseConnection()
 {
-    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender());
-    if (Q_LIKELY(pTcpSocket))
-        pTcpSocket->close();
+    if (Q_LIKELY(currentSender)) {
+        QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->sender);
+        if (Q_LIKELY(pTcpSocket))
+            pTcpSocket->close();
+    }
 }
 
 /*!
@@ -375,72 +398,80 @@ void QWebSocketServerPrivate::onCloseConnection()
  */
 void QWebSocketServerPrivate::handshakeReceived()
 {
+    if (Q_UNLIKELY(!currentSender)) {
+        return;
+    }
+    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->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)
+    if (!pTcpSocket->canReadLine()) {
+        return;
+    }
+    disconnect(pTcpSocket, &QTcpSocket::readyRead,
+               this, &QWebSocketServerPrivate::handshakeReceived);
     Q_Q(QWebSocketServer);
-    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender());
-    if (Q_LIKELY(pTcpSocket)) {
-        bool success = false;
-        bool isSecure = false;
+    bool success = false;
+    bool isSecure = false;
 
-        disconnect(pTcpSocket, &QTcpSocket::readyRead,
-                   this, &QWebSocketServerPrivate::handshakeReceived);
-
-        if (m_pendingConnections.length() >= maxPendingConnections()) {
-            pTcpSocket->close();
-            qWarning() <<
-                tr("Too many pending connections: new websocket connection not accepted.");
-            setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION,
-                     tr("Too many pending connections."));
-            return;
-        }
+    if (m_pendingConnections.length() >= maxPendingConnections()) {
+        pTcpSocket->close();
+        pTcpSocket->deleteLater();
+        setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
+                 QWebSocketServer::tr("Too many pending connections."));
+        return;
+    }
 
-        QWebSocketHandshakeRequest request(pTcpSocket->peerPort(), isSecure);
-        QTextStream textStream(pTcpSocket);
-        textStream >> request;
-
-        if (request.isValid()) {
-            QWebSocketCorsAuthenticator corsAuthenticator(request.origin());
-            Q_EMIT q->originAuthenticationRequired(&corsAuthenticator);
-
-            QWebSocketHandshakeResponse response(request,
-                                                 m_serverName,
-                                                 corsAuthenticator.allowed(),
-                                                 supportedVersions(),
-                                                 supportedProtocols(),
-                                                 supportedExtensions());
-
-            if (response.isValid()) {
-                QTextStream httpStream(pTcpSocket);
-                httpStream << response;
-                httpStream.flush();
-
-                if (response.canUpgrade()) {
-                    QWebSocket *pWebSocket = QWebSocketPrivate::upgradeFrom(pTcpSocket,
-                                                                            request,
-                                                                            response);
-                    if (pWebSocket) {
-                        pWebSocket->setParent(this);
-                        addPendingConnection(pWebSocket);
-                        Q_EMIT q->newConnection();
-                        success = true;
-                    } else {
-                        setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION,
-                                 tr("Upgrading to websocket failed."));
-                    }
-                }
-                else {
-                    setError(response.error(), response.errorString());
+    QWebSocketHandshakeRequest request(pTcpSocket->peerPort(), isSecure);
+    QTextStream textStream(pTcpSocket);
+    request.readHandshake(textStream);
+
+    if (request.isValid()) {
+        QWebSocketCorsAuthenticator corsAuthenticator(request.origin());
+        Q_EMIT q->originAuthenticationRequired(&corsAuthenticator);
+
+        QWebSocketHandshakeResponse response(request,
+                                             m_serverName,
+                                             corsAuthenticator.allowed(),
+                                             supportedVersions(),
+                                             supportedProtocols(),
+                                             supportedExtensions());
+
+        if (response.isValid()) {
+            QTextStream httpStream(pTcpSocket);
+            httpStream << response;
+            httpStream.flush();
+
+            if (response.canUpgrade()) {
+                QWebSocket *pWebSocket = QWebSocketPrivate::upgradeFrom(pTcpSocket,
+                                                                        request,
+                                                                        response);
+                if (pWebSocket) {
+                    addPendingConnection(pWebSocket);
+                    Q_EMIT q->newConnection();
+                    success = true;
+                } else {
+                    setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
+                             QWebSocketServer::tr("Upgrade to WebSocket failed."));
                 }
-            } else {
-                setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, tr("Invalid response received."));
             }
+            else {
+                setError(response.error(), response.errorString());
+            }
+        } else {
+            setError(QWebSocketProtocol::CloseCodeProtocolError,
+                     QWebSocketServer::tr("Invalid response received."));
         }
-        if (!success) {
-            qWarning() << tr("Closing socket because of invalid or unsupported request.");
-            pTcpSocket->close();
-        }
-    } else {
-        qWarning() <<
-            tr("Sender socket is NULL. This should not happen, otherwise it is a Qt bug!!!");
+    }
+    if (!success) {
+        pTcpSocket->close();
     }
 }