Adapt copyright header
[contrib/qtwebsockets.git] / src / websockets / qwebsocketserver_p.cpp
index 10f328a..061fb3f 100644 (file)
@@ -1,6 +1,6 @@
 /****************************************************************************
 **
-** 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.
@@ -41,6 +41,9 @@
 
 #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"
@@ -57,39 +60,79 @@ QT_BEGIN_NAMESPACE
 /*!
     \internal
  */
-QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName, QWebSocketServer * const pWebSocketServer, QObject *parent) :
-    QObject(parent),
+QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
+                                                 QWebSocketServerPrivate::SslMode secureMode,
+                                                 QWebSocketServer * const pWebSocketServer) :
+    QObjectPrivate(),
     q_ptr(pWebSocketServer),
     m_pTcpServer(Q_NULLPTR),
     m_serverName(serverName),
-    m_pendingConnections()
+    m_secureMode(secureMode),
+    m_pendingConnections(),
+    m_error(QWebSocketProtocol::CloseCodeNormal),
+    m_errorString()
 {
     Q_ASSERT(pWebSocketServer);
-    m_pTcpServer = new QTcpServer(this);
-    connect(m_pTcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), q_ptr, SIGNAL(acceptError(QAbstractSocket::SocketError)));
-    connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
 }
 
 /*!
     \internal
  */
-QWebSocketServerPrivate::~QWebSocketServerPrivate()
+void QWebSocketServerPrivate::init()
 {
-    while (!m_pendingConnections.isEmpty())
-    {
-        QWebSocket *pWebSocket = m_pendingConnections.dequeue();
-        pWebSocket->close(QWebSocketProtocol::CC_GOING_AWAY, tr("Server closed."));
-        pWebSocket->deleteLater();
+    if (m_secureMode == NonSecureMode) {
+        m_pTcpServer = new QTcpServer();
+        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();
+        m_pTcpServer = pSslServer;
+        if (Q_LIKELY(m_pTcpServer)) {
+            QObjectPrivate::connect(pSslServer, &QSslServer::newEncryptedConnection,
+                                    this, &QWebSocketServerPrivate::onNewConnection);
+            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
     }
+    QObject::connect(m_pTcpServer, &QTcpServer::acceptError, q_ptr, &QWebSocketServer::acceptError);
+}
+
+/*!
+    \internal
+ */
+QWebSocketServerPrivate::~QWebSocketServerPrivate()
+{
+    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::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);
+    }
 }
 
 /*!
@@ -97,7 +140,10 @@ void QWebSocketServerPrivate::close()
  */
 QString QWebSocketServerPrivate::errorString() const
 {
-    return m_pTcpServer->errorString();
+    if (m_errorString.isEmpty())
+        return m_pTcpServer->errorString();
+    else
+        return m_errorString;
 }
 
 /*!
@@ -121,7 +167,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;
 }
 
 /*!
@@ -138,9 +187,17 @@ int QWebSocketServerPrivate::maxPendingConnections() const
 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);
 }
 
 /*!
@@ -149,10 +206,8 @@ void QWebSocketServerPrivate::addPendingConnection(QWebSocket *pWebSocket)
 QWebSocket *QWebSocketServerPrivate::nextPendingConnection()
 {
     QWebSocket *pWebSocket = Q_NULLPTR;
-    if (!m_pendingConnections.isEmpty())
-    {
+    if (Q_LIKELY(!m_pendingConnections.isEmpty()))
         pWebSocket = m_pendingConnections.dequeue();
-    }
     return pWebSocket;
 }
 
@@ -200,9 +255,9 @@ QHostAddress QWebSocketServerPrivate::serverAddress() const
 /*!
     \internal
  */
-QAbstractSocket::SocketError QWebSocketServerPrivate::serverError() const
+QWebSocketProtocol::CloseCode QWebSocketServerPrivate::serverError() const
 {
-    return m_pTcpServer->serverError();
+    return m_error;
 }
 
 /*!
@@ -240,14 +295,6 @@ qintptr QWebSocketServerPrivate::socketDescriptor() const
 /*!
     \internal
  */
-bool QWebSocketServerPrivate::waitForNewConnection(int msec, bool *timedOut)
-{
-    return m_pTcpServer->waitForNewConnection(msec, timedOut);
-}
-
-/*!
-    \internal
- */
 QList<QWebSocketProtocol::Version> QWebSocketServerPrivate::supportedVersions() const
 {
     QList<QWebSocketProtocol::Version> supportedVersions;
@@ -258,18 +305,18 @@ QList<QWebSocketProtocol::Version> QWebSocketServerPrivate::supportedVersions()
 /*!
     \internal
  */
-QList<QString> QWebSocketServerPrivate::supportedProtocols() const
+QStringList QWebSocketServerPrivate::supportedProtocols() const
 {
-    QList<QString> supportedProtocols;
+    QStringList supportedProtocols;
     return supportedProtocols; //no protocols are currently supported
 }
 
 /*!
     \internal
  */
-QList<QString> QWebSocketServerPrivate::supportedExtensions() const
+QStringList QWebSocketServerPrivate::supportedExtensions() const
 {
-    QList<QString> supportedExtensions;
+    QStringList supportedExtensions;
     return supportedExtensions;        //no extensions are currently supported
 }
 
@@ -278,7 +325,8 @@ QList<QString> QWebSocketServerPrivate::supportedExtensions() const
  */
 void QWebSocketServerPrivate::setServerName(const QString &serverName)
 {
-    m_serverName = serverName;
+    if (m_serverName != serverName)
+        m_serverName = serverName;
 }
 
 /*!
@@ -290,12 +338,51 @@ QString QWebSocketServerPrivate::serverName() const
 }
 
 /*!
+  \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()
 {
     QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection();
-    connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(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);
 }
 
 /*!
@@ -303,10 +390,10 @@ void QWebSocketServerPrivate::onNewConnection()
  */
 void QWebSocketServerPrivate::onCloseConnection()
 {
-    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender());
-    if (pTcpSocket)
-    {
-        pTcpSocket->close();
+    if (Q_LIKELY(currentSender)) {
+        QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->sender);
+        if (Q_LIKELY(pTcpSocket))
+            pTcpSocket->close();
     }
 }
 
@@ -315,75 +402,76 @@ void QWebSocketServerPrivate::onCloseConnection()
  */
 void QWebSocketServerPrivate::handshakeReceived()
 {
+    if (Q_UNLIKELY(!currentSender)) {
+        qWarning() << QWebSocketServer::tr("Sender is NULL. This is a Qt bug.");
+        return;
+    }
+    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(currentSender->sender);
+    if (Q_UNLIKELY(!pTcpSocket)) {
+        qWarning() << QWebSocketServer::tr("Sender is not a QTcpSocket. This is a Qt bug!!!");
+        return;
+    }
     Q_Q(QWebSocketServer);
-    QTcpSocket *pTcpSocket = qobject_cast<QTcpSocket*>(sender());
-    if (pTcpSocket)
-    {
-        bool success = false;
-        bool isSecure = false;
-
-        disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived()));
-
-        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
-                    {
-                        //TODO: should set or emit error
-                        qDebug() << tr("Upgrading to websocket failed.");
-                    }
-                }
-                else
-                {
-                    //TODO: should set or emit error
-                    qDebug() << tr("Cannot upgrade to websocket.");
+    bool success = false;
+    bool isSecure = false;
+
+    disconnect(pTcpSocket, &QTcpSocket::readyRead,
+               this, &QWebSocketServerPrivate::handshakeReceived);
+
+    if (m_pendingConnections.length() >= maxPendingConnections()) {
+        pTcpSocket->close();
+        pTcpSocket->deleteLater();
+        qWarning() << QWebSocketServer::tr("Too many pending connections: " \
+                                           "New websocket connection not accepted.");
+        setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
+                 QWebSocketServer::tr("Too many pending connections."));
+        return;
+    }
+
+    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("Upgrading to websocket failed."));
                 }
             }
-            else
-            {
-                //TODO: should set or emit error
-                qDebug() << tr("Invalid response received.");
+            else {
+                setError(response.error(), response.errorString());
             }
-        }
-        if (!success)
-        {
-            //TODO: should set or emit error
-            qDebug() << tr("Closing socket because of invalid or unsupported request.");
-            pTcpSocket->close();
+        } else {
+            setError(QWebSocketProtocol::CloseCodeProtocolError,
+                     QWebSocketServer::tr("Invalid response received."));
         }
     }
-    else
-    {
-        qWarning() << "Sender socket is NULL. This should not happen, otherwise it is a Qt bug!!!";
+    if (!success) {
+        qWarning() << QWebSocketServer::tr("Closing socket because of invalid or unsupported request.");
+        pTcpSocket->close();
     }
 }