/****************************************************************************
**
-** 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.
#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"
/*!
\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);
+ }
}
/*!
*/
QString QWebSocketServerPrivate::errorString() const
{
- return m_pTcpServer->errorString();
+ if (m_errorString.isEmpty())
+ return m_pTcpServer->errorString();
+ else
+ return m_errorString;
}
/*!
*/
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;
}
/*!
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);
}
/*!
QWebSocket *QWebSocketServerPrivate::nextPendingConnection()
{
QWebSocket *pWebSocket = Q_NULLPTR;
- if (!m_pendingConnections.isEmpty())
- {
+ if (Q_LIKELY(!m_pendingConnections.isEmpty()))
pWebSocket = m_pendingConnections.dequeue();
- }
return pWebSocket;
}
/*!
\internal
*/
-QAbstractSocket::SocketError QWebSocketServerPrivate::serverError() const
+QWebSocketProtocol::CloseCode QWebSocketServerPrivate::serverError() const
{
- return m_pTcpServer->serverError();
+ return m_error;
}
/*!
/*!
\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;
/*!
\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
}
*/
void QWebSocketServerPrivate::setServerName(const QString &serverName)
{
- m_serverName = serverName;
+ if (m_serverName != serverName)
+ m_serverName = 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()
{
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);
}
/*!
*/
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();
}
}
*/
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();
}
}