Add user configurable mask generation
[contrib/qtwebsockets.git] / src / websockets / qwebsocket_p.cpp
index 646e24f..bff060f 100644 (file)
@@ -44,6 +44,7 @@
 #include "qwebsocketprotocol_p.h"
 #include "qwebsockethandshakerequest_p.h"
 #include "qwebsockethandshakeresponse_p.h"
+#include "qdefaultmaskgenerator_p.h"
 
 #include <QtCore/QUrl>
 #include <QtNetwork/QAuthenticator>
@@ -87,11 +88,11 @@ QWebSocketConfiguration::QWebSocketConfiguration() :
     \internal
 */
 QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::Version version,
-                                     QWebSocket *pWebSocket, QObject *parent) :
-    QObject(parent),
+                                     QWebSocket *pWebSocket) :
+    QObjectPrivate(),
     q_ptr(pWebSocket),
     m_pSocket(),
-    m_errorString(),
+    m_errorString(QWebSocket::tr("Unknown error")),
     m_version(version),
     m_resourceName(),
     m_requestUrl(),
@@ -109,17 +110,18 @@ QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::
     m_closeReason(),
     m_pingTimer(),
     m_dataProcessor(),
-    m_configuration()
+    m_configuration(),
+    m_pMaskGenerator(&m_defaultMaskGenerator),
+    m_defaultMaskGenerator()
 {
-    init();
 }
 
 /*!
     \internal
 */
 QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version,
-                                     QWebSocket *pWebSocket, QObject *parent) :
-    QObject(parent),
+                                     QWebSocket *pWebSocket) :
+    QObjectPrivate(),
     q_ptr(pWebSocket),
     m_pSocket(pTcpSocket),
     m_errorString(pTcpSocket->errorString()),
@@ -140,10 +142,10 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol:
     m_closeReason(),
     m_pingTimer(),
     m_dataProcessor(),
-    m_configuration()
+    m_configuration(),
+    m_pMaskGenerator(&m_defaultMaskGenerator),
+    m_defaultMaskGenerator()
 {
-    init();
-    makeConnections(m_pSocket.data());
 }
 
 /*!
@@ -152,8 +154,13 @@ QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol:
 void QWebSocketPrivate::init()
 {
     Q_ASSERT(q_ptr);
-    //TODO: need a better randomizer
-    qsrand(static_cast<uint>(QDateTime::currentMSecsSinceEpoch()));
+    Q_ASSERT(m_pMaskGenerator);
+
+    m_pMaskGenerator->seed();
+
+    if (m_pSocket) {
+        makeConnections(m_pSocket.data());
+    }
 }
 
 /*!
@@ -164,7 +171,7 @@ QWebSocketPrivate::~QWebSocketPrivate()
     if (!m_pSocket)
         return;
     if (state() == QAbstractSocket::ConnectedState)
-        close(QWebSocketProtocol::CloseCodeGoingAway, tr("Connection closed"));
+        close(QWebSocketProtocol::CloseCodeGoingAway, QWebSocket::tr("Connection closed"));
     releaseConnections(m_pSocket.data());
 }
 
@@ -182,7 +189,7 @@ void QWebSocketPrivate::abort()
  */
 QAbstractSocket::SocketError QWebSocketPrivate::error() const
 {
-    QAbstractSocket::SocketError err = QAbstractSocket::OperationError;
+    QAbstractSocket::SocketError err = QAbstractSocket::UnknownSocketError;
     if (Q_LIKELY(m_pSocket))
         err = m_pSocket->error();
     return err;
@@ -329,8 +336,14 @@ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString r
 void QWebSocketPrivate::open(const QUrl &url, bool mask)
 {
     //just delete the old socket for the moment;
-    //later, we can add more 'intelligent' handling by looking at the url
+    //later, we can add more 'intelligent' handling by looking at the URL
     //m_pSocket.reset();
+    Q_Q(QWebSocket);
+    if (!url.isValid() || url.toString().contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("Invalid URL."));
+        Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
+        return;
+    }
     QTcpSocket *pTcpSocket = m_pSocket.take();
     if (pTcpSocket) {
         releaseConnections(pTcpSocket);
@@ -338,14 +351,18 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
     }
     //if (m_url != url)
     if (Q_LIKELY(!m_pSocket)) {
-        Q_Q(QWebSocket);
-
         m_dataProcessor.clear();
         m_isClosingHandshakeReceived = false;
         m_isClosingHandshakeSent = false;
 
         setRequestUrl(url);
         QString resourceName = url.path();
+        if (resourceName.contains(QStringLiteral("\r\n"))) {
+            setRequestUrl(QUrl());  //clear requestUrl
+            setErrorString(QWebSocket::tr("Invalid resource name."));
+            Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
+            return;
+        }
         if (!url.query().isEmpty()) {
             if (!resourceName.endsWith(QChar::fromLatin1('?'))) {
                 resourceName.append(QChar::fromLatin1('?'));
@@ -360,11 +377,12 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
     #ifndef QT_NO_SSL
         if (url.scheme() == QStringLiteral("wss")) {
             if (!QSslSocket::supportsSsl()) {
-                const QString message = tr("SSL Sockets are not supported on this platform.");
+                const QString message =
+                        QWebSocket::tr("SSL Sockets are not supported on this platform.");
                 setErrorString(message);
                 Q_EMIT q->error(QAbstractSocket::UnsupportedSocketOperationError);
             } else {
-                QSslSocket *sslSocket = new QSslSocket(this);
+                QSslSocket *sslSocket = new QSslSocket;
                 m_pSocket.reset(sslSocket);
                 if (Q_LIKELY(m_pSocket)) {
                     m_pSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
@@ -373,8 +391,12 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
                     m_pSocket->setPauseMode(m_pauseMode);
 
                     makeConnections(m_pSocket.data());
-                    connect(sslSocket, &QSslSocket::encryptedBytesWritten, q,
-                            &QWebSocket::bytesWritten);
+                    QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten, q,
+                                     &QWebSocket::bytesWritten);
+                    typedef void (QSslSocket:: *sslErrorSignalType)(const QList<QSslError> &);
+                    QObject::connect(sslSocket,
+                                     static_cast<sslErrorSignalType>(&QSslSocket::sslErrors),
+                                     q, &QWebSocket::sslErrors);
                     setSocketState(QAbstractSocket::ConnectingState);
 
                     sslSocket->setSslConfiguration(m_configuration.m_sslConfiguration);
@@ -387,7 +409,7 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
     #endif
                     sslSocket->connectToHostEncrypted(url.host(), url.port(443));
                 } else {
-                    const QString message = tr("Out of memory.");
+                    const QString message = QWebSocket::tr("Out of memory.");
                     setErrorString(message);
                     Q_EMIT q->error(QAbstractSocket::SocketResourceError);
                 }
@@ -395,7 +417,7 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
         } else
     #endif
         if (url.scheme() == QStringLiteral("ws")) {
-            m_pSocket.reset(new QTcpSocket(this));
+            m_pSocket.reset(new QTcpSocket);
             if (Q_LIKELY(m_pSocket)) {
                 m_pSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
                 m_pSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
@@ -403,20 +425,21 @@ void QWebSocketPrivate::open(const QUrl &url, bool mask)
                 m_pSocket->setPauseMode(m_pauseMode);
 
                 makeConnections(m_pSocket.data());
-                connect(m_pSocket.data(), &QAbstractSocket::bytesWritten, q,
-                        &QWebSocket::bytesWritten);
+                QObject::connect(m_pSocket.data(), &QAbstractSocket::bytesWritten, q,
+                                 &QWebSocket::bytesWritten);
                 setSocketState(QAbstractSocket::ConnectingState);
     #ifndef QT_NO_NETWORKPROXY
                 m_pSocket->setProxy(m_configuration.m_proxy);
     #endif
                 m_pSocket->connectToHost(url.host(), url.port(80));
             } else {
-                const QString message = tr("Out of memory.");
+                const QString message = QWebSocket::tr("Out of memory.");
                 setErrorString(message);
                 Q_EMIT q->error(QAbstractSocket::SocketResourceError);
             }
         } else {
-            const QString message = tr("Unsupported websockets scheme: %1").arg(url.scheme());
+            const QString message =
+                    QWebSocket::tr("Unsupported websockets scheme: %1").arg(url.scheme());
             setErrorString(message);
             Q_EMIT q->error(QAbstractSocket::UnsupportedSocketOperationError);
         }
@@ -515,40 +538,40 @@ void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket)
         //pass through signals
         typedef void (QAbstractSocket:: *ASErrorSignal)(QAbstractSocket::SocketError);
         typedef void (QWebSocket:: *WSErrorSignal)(QAbstractSocket::SocketError);
-        connect(pTcpSocket,
-                static_cast<ASErrorSignal>(&QAbstractSocket::error),
-                q, static_cast<WSErrorSignal>(&QWebSocket::error));
-        connect(pTcpSocket, &QAbstractSocket::proxyAuthenticationRequired, q,
-                &QWebSocket::proxyAuthenticationRequired);
-        connect(pTcpSocket, &QAbstractSocket::readChannelFinished, q,
-                &QWebSocket::readChannelFinished);
-        connect(pTcpSocket, &QAbstractSocket::aboutToClose, q, &QWebSocket::aboutToClose);
+        QObject::connect(pTcpSocket,
+                         static_cast<ASErrorSignal>(&QAbstractSocket::error),
+                         q, static_cast<WSErrorSignal>(&QWebSocket::error));
+        QObject::connect(pTcpSocket, &QAbstractSocket::proxyAuthenticationRequired, q,
+                         &QWebSocket::proxyAuthenticationRequired);
+        QObject::connect(pTcpSocket, &QAbstractSocket::readChannelFinished, q,
+                         &QWebSocket::readChannelFinished);
+        QObject::connect(pTcpSocket, &QAbstractSocket::aboutToClose, q, &QWebSocket::aboutToClose);
 
         //catch signals
-        connect(pTcpSocket, &QAbstractSocket::stateChanged, this,
-                &QWebSocketPrivate::processStateChanged);
+        QObjectPrivate::connect(pTcpSocket, &QAbstractSocket::stateChanged, this,
+                                &QWebSocketPrivate::processStateChanged);
         //!!!important to use a QueuedConnection here;
         //with QTcpSocket there is no problem, but with QSslSocket the processing hangs
-        connect(pTcpSocket, &QAbstractSocket::readyRead, this,
-                &QWebSocketPrivate::processData, Qt::QueuedConnection);
+        QObjectPrivate::connect(pTcpSocket, &QAbstractSocket::readyRead, this,
+                                &QWebSocketPrivate::processData, Qt::QueuedConnection);
     }
 
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::textFrameReceived, q,
-            &QWebSocket::textFrameReceived);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::binaryFrameReceived, q,
-            &QWebSocket::binaryFrameReceived);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::binaryMessageReceived, q,
-            &QWebSocket::binaryMessageReceived);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::textMessageReceived, q,
-            &QWebSocket::textMessageReceived);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::errorEncountered, this,
-            &QWebSocketPrivate::close);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::pingReceived, this,
-            &QWebSocketPrivate::processPing);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::pongReceived, this,
-            &QWebSocketPrivate::processPong);
-    connect(&m_dataProcessor, &QWebSocketDataProcessor::closeReceived, this,
-            &QWebSocketPrivate::processClose);
+    QObject::connect(&m_dataProcessor, &QWebSocketDataProcessor::textFrameReceived, q,
+                     &QWebSocket::textFrameReceived);
+    QObject::connect(&m_dataProcessor, &QWebSocketDataProcessor::binaryFrameReceived, q,
+                     &QWebSocket::binaryFrameReceived);
+    QObject::connect(&m_dataProcessor, &QWebSocketDataProcessor::binaryMessageReceived, q,
+                     &QWebSocket::binaryMessageReceived);
+    QObject::connect(&m_dataProcessor, &QWebSocketDataProcessor::textMessageReceived, q,
+                     &QWebSocket::textMessageReceived);
+    QObjectPrivate::connect(&m_dataProcessor, &QWebSocketDataProcessor::errorEncountered, this,
+                            &QWebSocketPrivate::close);
+    QObjectPrivate::connect(&m_dataProcessor, &QWebSocketDataProcessor::pingReceived, this,
+                            &QWebSocketPrivate::processPing);
+    QObjectPrivate::connect(&m_dataProcessor, &QWebSocketDataProcessor::pongReceived, this,
+                            &QWebSocketPrivate::processPong);
+    QObjectPrivate::connect(&m_dataProcessor, &QWebSocketDataProcessor::closeReceived, this,
+                            &QWebSocketPrivate::processClose);
 }
 
 /*!
@@ -557,8 +580,8 @@ void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket)
 void QWebSocketPrivate::releaseConnections(const QTcpSocket *pTcpSocket)
 {
     if (Q_LIKELY(pTcpSocket))
-        disconnect(pTcpSocket);
-    disconnect(&m_dataProcessor);
+        pTcpSocket->disconnect(pTcpSocket);
+    m_dataProcessor.disconnect();
 }
 
 /*!
@@ -678,7 +701,7 @@ QByteArray QWebSocketPrivate::getFrameHeader(QWebSocketProtocol::OpCode opCode,
 qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary)
 {
     qint64 payloadWritten = 0;
-    if (Q_UNLIKELY(!m_pSocket))
+    if (Q_UNLIKELY(!m_pSocket) || (state() != QAbstractSocket::ConnectedState))
         return payloadWritten;
 
     Q_Q(QWebSocket);
@@ -727,7 +750,7 @@ qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary)
                 payloadWritten += written;
             } else {
                 m_pSocket->flush();
-                setErrorString(tr("Error writing bytes to socket: %1.")
+                setErrorString(QWebSocket::tr("Error writing bytes to socket: %1.")
                                .arg(m_pSocket->errorString()));
                 Q_EMIT q->error(QAbstractSocket::NetworkError);
                 break;
@@ -737,27 +760,19 @@ qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary)
         bytesLeft -= size;
     }
     if (Q_UNLIKELY(payloadWritten != data.size())) {
-        setErrorString(tr("Bytes written %1 != %2.").arg(payloadWritten).arg(data.size()));
+        setErrorString(QWebSocket::tr("Bytes written %1 != %2.")
+                       .arg(payloadWritten).arg(data.size()));
         Q_EMIT q->error(QAbstractSocket::NetworkError);
     }
     return payloadWritten;
 }
 
 /*!
- * \internal
- */
-quint32 QWebSocketPrivate::generateRandomNumber() const
-{
-    //TODO: need a better randomizer
-    return quint32((double(qrand()) / RAND_MAX) * std::numeric_limits<quint32>::max());
-}
-
-/*!
     \internal
  */
 quint32 QWebSocketPrivate::generateMaskingKey() const
 {
-    return generateRandomNumber();
+    return m_pMaskGenerator->nextMask();
 }
 
 /*!
@@ -768,7 +783,7 @@ QByteArray QWebSocketPrivate::generateKey() const
     QByteArray key;
 
     for (int i = 0; i < 4; ++i) {
-        const quint32 tmp = generateRandomNumber();
+        const quint32 tmp = m_pMaskGenerator->nextMask();
         key.append(static_cast<const char *>(static_cast<const void *>(&tmp)), sizeof(quint32));
     }
 
@@ -861,14 +876,16 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket)
         }
     }
     if (Q_UNLIKELY(!ok)) {
-        errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine);
+        errorDescription = QWebSocket::tr("Invalid statusline in response: %1.").arg(statusLine);
     } else {
         QString headerLine = readLine(pSocket);
         QMap<QString, QString> headers;
         while (!headerLine.isEmpty()) {
             const QStringList headerField = headerLine.split(QStringLiteral(": "),
                                                              QString::SkipEmptyParts);
-            headers.insertMulti(headerField[0], headerField[1]);
+            if (headerField.size() == 2) {
+                headers.insertMulti(headerField[0], headerField[1]);
+            }
             headerLine = readLine(pSocket);
         }
 
@@ -898,11 +915,11 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket)
                 ok = (accept == acceptKey);
                 if (!ok)
                     errorDescription =
-                      tr("Accept-Key received from server %1 does not match the client key %2.")
+                      QWebSocket::tr("Accept-Key received from server %1 does not match the client key %2.")
                             .arg(acceptKey).arg(accept);
             } else {
                 errorDescription =
-                    tr("QWebSocketPrivate::processHandshake: Invalid statusline in response: %1.")
+                    QWebSocket::tr("QWebSocketPrivate::processHandshake: Invalid statusline in response: %1.")
                         .arg(statusLine);
             }
         } else if (httpStatusCode == 400) {
@@ -914,21 +931,20 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket)
                     //if needed to switch protocol version, then we are finished here
                     //because we cannot handle other protocols than the RFC one (v13)
                     errorDescription =
-                            tr("Handshake: Server requests a version that we don't support: %1.")
+                            QWebSocket::tr("Handshake: Server requests a version that we don't support: %1.")
                             .arg(versions.join(QStringLiteral(", ")));
                     ok = false;
                 } else {
                     //we tried v13, but something different went wrong
                     errorDescription =
-                        tr("QWebSocketPrivate::processHandshake: Unknown error condition " \
-                           "encountered. Aborting connection.");
+                        QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection.");
                     ok = false;
                 }
             }
         } else {
             errorDescription =
-                    tr("QWebSocketPrivate::processHandshake: Unhandled http status code: %1 (%2).")
-                    .arg(httpStatusCode).arg(httpStatusMessage);
+                    QWebSocket::tr("QWebSocketPrivate::processHandshake: Unhandled http status code: %1 (%2).")
+                        .arg(httpStatusCode).arg(httpStatusMessage);
             ok = false;
         }
 
@@ -964,6 +980,11 @@ void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketS
                                            QString(),
                                            QString(),
                                            m_key);
+            if (handshake.isEmpty()) {
+                m_pSocket->abort();
+                Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
+                return;
+            }
             m_pSocket->write(handshake.toLatin1());
         }
         break;
@@ -1053,6 +1074,31 @@ QString QWebSocketPrivate::createHandShakeRequest(QString resourceName,
                                                   QByteArray key)
 {
     QStringList handshakeRequest;
+    if (resourceName.contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("The resource name contains newlines. " \
+                                      "Possible attack detected."));
+        return QString();
+    }
+    if (host.contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("The hostname contains newlines. " \
+                                      "Possible attack detected."));
+        return QString();
+    }
+    if (origin.contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("The origin contains newlines. " \
+                                      "Possible attack detected."));
+        return QString();
+    }
+    if (extensions.contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("The extensions attribute contains newlines. " \
+                                      "Possible attack detected."));
+        return QString();
+    }
+    if (protocols.contains(QStringLiteral("\r\n"))) {
+        setErrorString(QWebSocket::tr("The protocols attribute contains newlines. " \
+                                      "Possible attack detected."));
+        return QString();
+    }
 
     handshakeRequest << QStringLiteral("GET ") % resourceName % QStringLiteral(" HTTP/1.1") <<
                         QStringLiteral("Host: ") % host <<
@@ -1186,6 +1232,26 @@ void QWebSocketPrivate::setProxy(const QNetworkProxy &networkProxy)
 /*!
     \internal
  */
+void QWebSocketPrivate::setMaskGenerator(const QMaskGenerator *maskGenerator)
+{
+    if (!maskGenerator)
+        m_pMaskGenerator = &m_defaultMaskGenerator;
+    else if (maskGenerator != m_pMaskGenerator)
+        m_pMaskGenerator = const_cast<QMaskGenerator *>(maskGenerator);
+}
+
+/*!
+    \internal
+ */
+const QMaskGenerator *QWebSocketPrivate::maskGenerator() const
+{
+    Q_ASSERT(m_pMaskGenerator);
+    return m_pMaskGenerator;
+}
+
+/*!
+    \internal
+ */
 qint64 QWebSocketPrivate::readBufferSize() const
 {
     return m_readBufferSize;
@@ -1225,7 +1291,8 @@ void QWebSocketPrivate::setReadBufferSize(qint64 size)
  */
 bool QWebSocketPrivate::isValid() const
 {
-    return (m_pSocket && m_pSocket->isValid());
+    return (m_pSocket && m_pSocket->isValid() &&
+            (m_socketState == QAbstractSocket::ConnectedState));
 }
 
 QT_END_NAMESPACE