/****************************************************************************
**
-** 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.
*/
QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName,
QWebSocketServerPrivate::SslMode secureMode,
- QWebSocketServer * const pWebSocketServer,
- QObject *parent) :
- QObject(parent),
+ QWebSocketServer * const pWebSocketServer) :
+ QObjectPrivate(),
q_ptr(pWebSocketServer),
m_pTcpServer(Q_NULLPTR),
m_serverName(serverName),
m_secureMode(secureMode),
m_pendingConnections(),
m_error(QWebSocketProtocol::CloseCodeNormal),
- m_errorString()
+ m_errorString(),
+ m_maxPendingConnections(30)
{
Q_ASSERT(pWebSocketServer);
+}
+
+/*!
+ \internal
+ */
+void QWebSocketServerPrivate::init()
+{
if (m_secureMode == NonSecureMode) {
- m_pTcpServer = new QTcpServer(this);
+ 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);
}
/*!
*/
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::CloseCodeGoingAway, 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);
+ }
}
/*!
*/
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;
}
/*!
*/
int QWebSocketServerPrivate::maxPendingConnections() const
{
- return m_pTcpServer->maxPendingConnections();
+ return m_maxPendingConnections;
}
/*!
/*!
\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;
*/
void QWebSocketServerPrivate::setMaxPendingConnections(int numConnections)
{
- m_pTcpServer->setMaxPendingConnections(numConnections);
+ if (m_pTcpServer->maxPendingConnections() <= numConnections)
+ m_pTcpServer->setMaxPendingConnections(numConnections + 1);
+ m_maxPendingConnections = numConnections;
}
/*!
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);
}
/*!
*/
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();
+ }
}
/*!
*/
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;
+ }
+ //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;
-
- disconnect(pTcpSocket, &QTcpSocket::readyRead,
- this, &QWebSocketServerPrivate::handshakeReceived);
+ bool success = false;
+ bool isSecure = false;
- if (m_pendingConnections.length() >= maxPendingConnections()) {
- pTcpSocket->close();
- setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
- tr("Too many pending connections."));
- return;
- }
+ 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) {
- pWebSocket->setParent(this);
- addPendingConnection(pWebSocket);
- Q_EMIT q->newConnection();
- success = true;
- } else {
- setError(QWebSocketProtocol::CloseCodeAbnormalDisconnection,
- tr("Upgrading to websocket failed."));
- }
+ 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 {
- setError(response.error(), response.errorString());
- }
- } else {
- setError(QWebSocketProtocol::CloseCodeProtocolError, 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) {
+ qWarning() << QWebSocketServer::tr("Closing socket because of invalid or unsupported request.");
+ pTcpSocket->close();
}
}