diff options
Diffstat (limited to 'src/websockets/qwebsocket_p.cpp')
-rw-r--r-- | src/websockets/qwebsocket_p.cpp | 1108 |
1 files changed, 1108 insertions, 0 deletions
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp new file mode 100644 index 0000000..054034c --- /dev/null +++ b/src/websockets/qwebsocket_p.cpp @@ -0,0 +1,1108 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qwebsocket.h" +#include "qwebsocket_p.h" +#include "handshakerequest_p.h" +#include "handshakeresponse_p.h" +#include <QUrl> +#include <QTcpSocket> +#include <QByteArray> +#include <QtEndian> +#include <QCryptographicHash> +#include <QRegularExpression> +#include <QStringList> +#include <QHostAddress> +#include <QStringBuilder> //for more efficient string concatenation +#ifndef QT_NONETWORKPROXY +#include <QNetworkProxy> +#endif + +#include <QDebug> + +#include <limits> + +QT_BEGIN_NAMESPACE + +const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message + +/*! + \internal +*/ +QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : + QObject(parent), + q_ptr(pWebSocket), + m_pSocket(new QTcpSocket(this)), + m_errorString(), + m_version(version), + m_resourceName(), + m_requestUrl(), + m_origin(origin), + m_protocol(""), + m_extension(""), + m_socketState(QAbstractSocket::UnconnectedState), + m_key(), + m_mustMask(true), + m_isClosingHandshakeSent(false), + m_isClosingHandshakeReceived(false), + m_pingTimer(), + m_dataProcessor() +{ + Q_ASSERT(pWebSocket != 0); + makeConnections(m_pSocket); + qsrand(static_cast<uint>(QDateTime::currentMSecsSinceEpoch())); +} + +/*! + \internal +*/ +QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : + QObject(parent), + q_ptr(pWebSocket), + m_pSocket(pTcpSocket), + m_errorString(pTcpSocket->errorString()), + m_version(version), + m_resourceName(), + m_requestUrl(), + m_origin(), + m_protocol(), + m_extension(), + m_socketState(pTcpSocket->state()), + m_key(), + m_mustMask(true), + m_isClosingHandshakeSent(false), + m_isClosingHandshakeReceived(false), + m_pingTimer(), + m_dataProcessor() +{ + Q_ASSERT(pWebSocket != 0); + makeConnections(m_pSocket); +} + +/*! + \internal +*/ +QWebSocketPrivate::~QWebSocketPrivate() +{ + if (state() == QAbstractSocket::ConnectedState) + { + close(QWebSocketProtocol::CC_GOING_AWAY, tr("Connection closed")); + } + releaseConnections(m_pSocket); + m_pSocket->deleteLater(); + m_pSocket = 0; +} + +/*! + \internal + */ +void QWebSocketPrivate::abort() +{ + m_pSocket->abort(); +} + +/*! + \internal + */ +QAbstractSocket::SocketError QWebSocketPrivate::error() const +{ + return m_pSocket->error(); +} + +/*! + \internal + */ +QString QWebSocketPrivate::errorString() const +{ + if (!m_errorString.isEmpty()) + { + return m_errorString; + } + else + { + return m_pSocket->errorString(); + } +} + +/*! + \internal + */ +bool QWebSocketPrivate::flush() +{ + return m_pSocket->flush(); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const char *message) +{ + return write(QString::fromUtf8(message)); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const char *message, qint64 maxSize) +{ + return write(QString::fromUtf8(message, static_cast<int>(maxSize))); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const QString &message) +{ + return doWriteData(message.toUtf8(), false); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const QByteArray &data) +{ + return doWriteData(data, true); +} + +/*! + \internal + */ +QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket, + const HandshakeRequest &request, + const HandshakeResponse &response, + QObject *parent) +{ + QWebSocket *pWebSocket = new QWebSocket(pTcpSocket, response.getAcceptedVersion(), parent); + pWebSocket->d_func()->setExtension(response.getAcceptedExtension()); + pWebSocket->d_func()->setOrigin(request.getOrigin()); + pWebSocket->d_func()->setRequestUrl(request.getRequestUrl()); + pWebSocket->d_func()->setProtocol(response.getAcceptedProtocol()); + pWebSocket->d_func()->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); + pWebSocket->d_func()->enableMasking(false); //a server should not send masked frames + + return pWebSocket; +} + +/*! + \internal + */ +void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) +{ + Q_Q(QWebSocket); + if (!m_isClosingHandshakeSent) + { + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + quint16 code = qToBigEndian<quint16>(closeCode); + QByteArray payload; + payload.append(static_cast<const char *>(static_cast<const void *>(&code)), 2); + if (!reason.isEmpty()) + { + payload.append(reason.toUtf8()); + } + if (m_mustMask) + { + QWebSocketProtocol::mask(payload.data(), payload.size(), maskingKey); + } + QByteArray frame = getFrameHeader(QWebSocketProtocol::OC_CLOSE, payload.size(), maskingKey, true); + frame.append(payload); + m_pSocket->write(frame); + m_pSocket->flush(); + + m_isClosingHandshakeSent = true; + + Q_EMIT q->aboutToClose(); + } + m_pSocket->close(); +} + +/*! + \internal + */ +void QWebSocketPrivate::open(const QUrl &url, bool mask) +{ + m_dataProcessor.clear(); + m_isClosingHandshakeReceived = false; + m_isClosingHandshakeSent = false; + + setRequestUrl(url); + QString resourceName = url.path(); + if (!url.query().isEmpty()) + { + resourceName.append("?" + url.query()); + } + if (resourceName.isEmpty()) + { + resourceName = "/"; + } + setResourceName(resourceName); + enableMasking(mask); + + setSocketState(QAbstractSocket::ConnectingState); + + m_pSocket->connectToHost(url.host(), url.port(80)); +} + +/*! + \internal + */ +void QWebSocketPrivate::ping(const QByteArray &payload) +{ + Q_ASSERT(payload.length() < 126); + m_pingTimer.restart(); + QByteArray pingFrame = getFrameHeader(QWebSocketProtocol::OC_PING, payload.size(), 0 /*do not mask*/, true); + pingFrame.append(payload); + writeFrame(pingFrame); +} + +/*! + \internal + Sets the version to use for the websocket protocol; this must be set before the socket is opened. +*/ +void QWebSocketPrivate::setVersion(QWebSocketProtocol::Version version) +{ + m_version = version; +} + +/*! + \internal + Sets the resource name of the connection; must be set before the socket is openend +*/ +void QWebSocketPrivate::setResourceName(const QString &resourceName) +{ + m_resourceName = resourceName; +} + +/*! + \internal + */ +void QWebSocketPrivate::setRequestUrl(const QUrl &requestUrl) +{ + m_requestUrl = requestUrl; +} + +/*! + \internal + */ +void QWebSocketPrivate::setOrigin(const QString &origin) +{ + m_origin = origin; +} + +/*! + \internal + */ +void QWebSocketPrivate::setProtocol(const QString &protocol) +{ + m_protocol = protocol; +} + +/*! + \internal + */ +void QWebSocketPrivate::setExtension(const QString &extension) +{ + m_extension = extension; +} + +/*! + \internal + */ +void QWebSocketPrivate::enableMasking(bool enable) +{ + m_mustMask = enable; +} + +/*! + * \internal + */ +qint64 QWebSocketPrivate::doWriteData(const QByteArray &data, bool isBinary) +{ + return doWriteFrames(data, isBinary); +} + +/*! + * \internal + */ +void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) +{ + Q_Q(QWebSocket); + //pass through signals + connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); + connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + connect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + connect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + + //catch signals + connect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); + connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); + + connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); + connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); + connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); + connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); + connect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); + connect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); + connect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); + connect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + * \internal + */ +void QWebSocketPrivate::releaseConnections(const QTcpSocket *pTcpSocket) +{ + Q_Q(QWebSocket); + if (pTcpSocket) + { + //pass through signals + disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); + disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + disconnect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + disconnect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + + //catched signals + disconnect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); + disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); + } + disconnect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); + disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); + disconnect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + \internal + */ +QWebSocketProtocol::Version QWebSocketPrivate::version() const +{ + return m_version; +} + +/*! + \internal + */ +QString QWebSocketPrivate::resourceName() const +{ + return m_resourceName; +} + +/*! + \internal + */ +QUrl QWebSocketPrivate::requestUrl() const +{ + return m_requestUrl; +} + +/*! + \internal + */ +QString QWebSocketPrivate::origin() const +{ + return m_origin; +} + +/*! + \internal + */ +QString QWebSocketPrivate::protocol() const +{ + return m_protocol; +} + +/*! + \internal + */ +QString QWebSocketPrivate::extension() const +{ + return m_extension; +} + +/*! + * \internal + */ +QByteArray QWebSocketPrivate::getFrameHeader(QWebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const +{ + QByteArray header; + quint8 byte = 0x00; + bool ok = payloadLength <= 0x7FFFFFFFFFFFFFFFULL; + + if (ok) + { + //FIN, RSV1-3, opcode + byte = static_cast<quint8>((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00)); //FIN, opcode + //RSV-1, RSV-2 and RSV-3 are zero + header.append(static_cast<char>(byte)); + + //Now write the masking bit and the payload length byte + byte = 0x00; + if (maskingKey != 0) + { + byte |= 0x80; + } + if (payloadLength <= 125) + { + byte |= static_cast<quint8>(payloadLength); + header.append(static_cast<char>(byte)); + } + else if (payloadLength <= 0xFFFFU) + { + byte |= 126; + header.append(static_cast<char>(byte)); + quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength)); + header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2); + } + else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) + { + byte |= 127; + header.append(static_cast<char>(byte)); + quint64 swapped = qToBigEndian<quint64>(payloadLength); + header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8); + } + + //Write mask + if (maskingKey != 0) + { + header.append(static_cast<const char *>(static_cast<const void *>(&maskingKey)), sizeof(quint32)); + } + } + else + { + //setErrorString("WebSocket::getHeader: payload too big!"); + //Q_EMIT q_ptr->error(QAbstractSocket::DatagramTooLargeError); + qDebug() << "WebSocket::getHeader: payload too big!"; + } + + return header; +} + +/*! + * \internal + */ +qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) +{ + Q_Q(QWebSocket); + const QWebSocketProtocol::OpCode firstOpCode = isBinary ? QWebSocketProtocol::OC_BINARY : QWebSocketProtocol::OC_TEXT; + + int numFrames = data.size() / FRAME_SIZE_IN_BYTES; + QByteArray tmpData(data); + tmpData.detach(); + char *payload = tmpData.data(); + quint64 sizeLeft = static_cast<quint64>(data.size()) % FRAME_SIZE_IN_BYTES; + if (sizeLeft) + { + ++numFrames; + } + if (numFrames == 0) //catch the case where the payload is zero bytes; in that case, we still need to send a frame + { + numFrames = 1; + } + quint64 currentPosition = 0; + qint64 bytesWritten = 0; + qint64 payloadWritten = 0; + quint64 bytesLeft = data.size(); + + for (int i = 0; i < numFrames; ++i) + { + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + + bool isLastFrame = (i == (numFrames - 1)); + bool isFirstFrame = (i == 0); + + quint64 size = qMin(bytesLeft, FRAME_SIZE_IN_BYTES); + QWebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : QWebSocketProtocol::OC_CONTINUE; + + //write header + bytesWritten += m_pSocket->write(getFrameHeader(opcode, size, maskingKey, isLastFrame)); + + //write payload + if (size > 0) + { + char *currentData = payload + currentPosition; + if (m_mustMask) + { + QWebSocketProtocol::mask(currentData, size, maskingKey); + } + qint64 written = m_pSocket->write(currentData, static_cast<qint64>(size)); + if (written > 0) + { + bytesWritten += written; + payloadWritten += written; + } + else + { + setErrorString(tr("Error writing bytes to socket: %1.").arg(m_pSocket->errorString())); + qDebug() << errorString(); + m_pSocket->flush(); + Q_EMIT q->error(QAbstractSocket::NetworkError); + break; + } + } + currentPosition += size; + bytesLeft -= size; + } + if (payloadWritten != data.size()) + { + setErrorString(tr("Bytes written %1 != %2.").arg(payloadWritten).arg(data.size())); + qDebug() << errorString(); + Q_EMIT q->error(QAbstractSocket::NetworkError); + } + return payloadWritten; +} + +/*! + * \internal + */ +quint32 QWebSocketPrivate::generateRandomNumber() const +{ + return static_cast<quint32>((static_cast<double>(qrand()) / RAND_MAX) * std::numeric_limits<quint32>::max()); +} + +/*! + \internal + */ +quint32 QWebSocketPrivate::generateMaskingKey() const +{ + return generateRandomNumber(); +} + +/*! + \internal + */ +QByteArray QWebSocketPrivate::generateKey() const +{ + QByteArray key; + + for (int i = 0; i < 4; ++i) + { + quint32 tmp = generateRandomNumber(); + key.append(static_cast<const char *>(static_cast<const void *>(&tmp)), sizeof(quint32)); + } + + return key.toBase64(); +} + + +/*! + \internal + */ +QString QWebSocketPrivate::calculateAcceptKey(const QString &key) const +{ + QString tmpKey = key % "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); + return QString(hash.toBase64()); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::writeFrames(const QList<QByteArray> &frames) +{ + qint64 written = 0; + for (int i = 0; i < frames.size(); ++i) + { + written += writeFrame(frames[i]); + } + return written; +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::writeFrame(const QByteArray &frame) +{ + return m_pSocket->write(frame); +} + +/*! + \internal + */ +QString readLine(QTcpSocket *pSocket) +{ + QString line; + char c; + while (pSocket->getChar(&c)) + { + if (c == '\r') + { + pSocket->getChar(&c); + break; + } + else + { + line.append(QChar(c)); + } + } + return line; +} + +//called on the client for a server handshake response +/*! + \internal + */ +void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) +{ + Q_Q(QWebSocket); + if (pSocket == 0) + { + return; + } + + bool ok = false; + QString errorDescription; + + const QString regExpStatusLine("^(HTTP/[0-9]+\\.[0-9]+)\\s([0-9]+)\\s(.*)"); + const QRegularExpression regExp(regExpStatusLine); + QString statusLine = readLine(pSocket); + QString httpProtocol; + int httpStatusCode; + QString httpStatusMessage; + QRegularExpressionMatch match = regExp.match(statusLine); + if (match.hasMatch()) + { + QStringList tokens = match.capturedTexts(); + tokens.removeFirst(); //remove the search string + if (tokens.length() == 3) + { + httpProtocol = tokens[0]; + httpStatusCode = tokens[1].toInt(); + httpStatusMessage = tokens[2].trimmed(); + ok = true; + } + } + if (!ok) + { + errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); + } + else + { + QString headerLine = readLine(pSocket); + QMap<QString, QString> headers; + while (!headerLine.isEmpty()) + { + QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); + headers.insertMulti(headerField[0], headerField[1]); + headerLine = readLine(pSocket); + } + + QString acceptKey = headers.value("Sec-WebSocket-Accept", ""); + QString upgrade = headers.value("Upgrade", ""); + QString connection = headers.value("Connection", ""); + //unused for the moment + //QString extensions = headers.value("Sec-WebSocket-Extensions", ""); + //QString protocol = headers.value("Sec-WebSocket-Protocol", ""); + QString version = headers.value("Sec-WebSocket-Version", ""); + + if (httpStatusCode == 101) //HTTP/x.y 101 Switching Protocols + { + bool conversionOk = false; + float version = httpProtocol.midRef(5).toFloat(&conversionOk); + //TODO: do not check the httpStatusText right now + ok = !(acceptKey.isEmpty() || + (!conversionOk || (version < 1.1f)) || + (upgrade.toLower() != "websocket") || + (connection.toLower() != "upgrade")); + if (ok) + { + QString accept = calculateAcceptKey(m_key); + ok = (accept == acceptKey); + if (!ok) + { + errorDescription = tr("Accept-Key received from server %1 does not match the client key %2.").arg(acceptKey).arg(accept); + } + } + else + { + errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); + } + } + else if (httpStatusCode == 400) //HTTP/1.1 400 Bad Request + { + if (!version.isEmpty()) + { + QStringList versions = version.split(", ", QString::SkipEmptyParts); + if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) + { + //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.").arg(versions.join(", ")); + ok = false; + } + else + { + //we tried v13, but something different went wrong + errorDescription = tr("Unknown error condition encountered. Aborting connection."); + ok = false; + } + } + } + else + { + errorDescription = tr("Unhandled http status code: %1.").arg(httpStatusCode); + ok = false; + } + + if (!ok) + { + qDebug() << errorDescription; + setErrorString(errorDescription); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); + } + else + { + //handshake succeeded + setSocketState(QAbstractSocket::ConnectedState); + Q_EMIT q->connected(); + } + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketState) +{ + Q_Q(QWebSocket); + QAbstractSocket::SocketState webSocketState = this->state(); + switch (socketState) + { + case QAbstractSocket::ConnectedState: + { + if (webSocketState == QAbstractSocket::ConnectingState) + { + m_key = generateKey(); + QString handshake = createHandShakeRequest(m_resourceName, m_requestUrl.host() % ":" % QString::number(m_requestUrl.port(80)), origin(), "", "", m_key); + m_pSocket->write(handshake.toLatin1()); + } + break; + } + case QAbstractSocket::ClosingState: + { + if (webSocketState == QAbstractSocket::ConnectedState) + { + setSocketState(QAbstractSocket::ClosingState); + } + break; + } + case QAbstractSocket::UnconnectedState: + { + if (webSocketState != QAbstractSocket::UnconnectedState) + { + setSocketState(QAbstractSocket::UnconnectedState); + Q_EMIT q->disconnected(); + } + break; + } + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + { + //do nothing + //to make C++ compiler happy; + break; + } + default: + { + break; + } + } +} + +//order of events: +//connectToHost is called +//our socket state is set to "connecting", and tcpSocket->connectToHost is called +//the tcpsocket is opened, a handshake message is sent; a readyRead signal is thrown +//this signal is catched by processData +//when OUR socket state is in the "connecting state", this means that +//we have received data from the server (response to handshake), and that we +//should "upgrade" our socket to a websocket (connected state) +//if our socket was already upgraded, then we need to process websocket data +/*! + \internal + */ +void QWebSocketPrivate::processData() +{ + while (m_pSocket->bytesAvailable()) + { + if (state() == QAbstractSocket::ConnectingState) + { + processHandshake(m_pSocket); + } + else + { + m_dataProcessor.process(m_pSocket); + } + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processPing(QByteArray data) +{ + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + m_pSocket->write(getFrameHeader(QWebSocketProtocol::OC_PONG, data.size(), maskingKey, true)); + if (data.size() > 0) + { + if (m_mustMask) + { + QWebSocketProtocol::mask(&data, maskingKey); + } + m_pSocket->write(data); + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processPong(QByteArray data) +{ + Q_Q(QWebSocket); + Q_EMIT q->pong(static_cast<quint64>(m_pingTimer.elapsed()), data); +} + +/*! + \internal + */ +void QWebSocketPrivate::processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason) +{ + m_isClosingHandshakeReceived = true; + close(closeCode, closeReason); +} + +/*! + \internal + */ +QString QWebSocketPrivate::createHandShakeRequest(QString resourceName, + QString host, + QString origin, + QString extensions, + QString protocols, + QByteArray key) +{ + QStringList handshakeRequest; + + handshakeRequest << "GET " % resourceName % " HTTP/1.1" << + "Host: " % host << + "Upgrade: websocket" << + "Connection: Upgrade" << + "Sec-WebSocket-Key: " % QString(key); + if (!origin.isEmpty()) + { + handshakeRequest << "Origin: " % origin; + } + handshakeRequest << "Sec-WebSocket-Version: " % QString::number(QWebSocketProtocol::currentVersion()); + if (extensions.length() > 0) + { + handshakeRequest << "Sec-WebSocket-Extensions: " % extensions; + } + if (protocols.length() > 0) + { + handshakeRequest << "Sec-WebSocket-Protocol: " % protocols; + } + handshakeRequest << "\r\n"; + + return handshakeRequest.join("\r\n"); +} + +/*! + \internal + */ +QAbstractSocket::SocketState QWebSocketPrivate::state() const +{ + return m_socketState; +} + +/*! + \internal + */ +bool QWebSocketPrivate::waitForConnected(int msecs) +{ + return m_pSocket->waitForConnected(msecs); +} + +/*! + \internal + */ +bool QWebSocketPrivate::waitForDisconnected(int msecs) +{ + return m_pSocket->waitForDisconnected(msecs); +} + +/*! + \internal + */ +void QWebSocketPrivate::setSocketState(QAbstractSocket::SocketState state) +{ + Q_Q(QWebSocket); + if (m_socketState != state) + { + m_socketState = state; + Q_EMIT q->stateChanged(m_socketState); + } +} + +/*! + \internal + */ +void QWebSocketPrivate::setErrorString(const QString &errorString) +{ + m_errorString = errorString; +} + +/*! + \internal + */ +QHostAddress QWebSocketPrivate::localAddress() const +{ + return m_pSocket->localAddress(); +} + +/*! + \internal + */ +quint16 QWebSocketPrivate::localPort() const +{ + return m_pSocket->localPort(); +} + +/*! + \internal + */ +QAbstractSocket::PauseModes QWebSocketPrivate::pauseMode() const +{ + return m_pSocket->pauseMode(); +} + +/*! + \internal + */ +QHostAddress QWebSocketPrivate::peerAddress() const +{ + return m_pSocket->peerAddress(); +} + +/*! + \internal + */ +QString QWebSocketPrivate::peerName() const +{ + return m_pSocket->peerName(); +} + +/*! + \internal + */ +quint16 QWebSocketPrivate::peerPort() const +{ + return m_pSocket->peerPort(); +} + +/*! + \internal + */ +QNetworkProxy QWebSocketPrivate::proxy() const +{ + return m_pSocket->proxy(); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::readBufferSize() const +{ + return m_pSocket->readBufferSize(); +} + +/*! + \internal + */ +void QWebSocketPrivate::resume() +{ + m_pSocket->resume(); +} + +/*! + \internal + */ +void QWebSocketPrivate::setPauseMode(QAbstractSocket::PauseModes pauseMode) +{ + m_pSocket->setPauseMode(pauseMode); +} + +/*! + \internal + */ +void QWebSocketPrivate::setProxy(const QNetworkProxy &networkProxy) +{ + m_pSocket->setProxy(networkProxy); +} + +/*! + \internal + */ +void QWebSocketPrivate::setReadBufferSize(qint64 size) +{ + m_pSocket->setReadBufferSize(size); +} + +/*! + \internal + */ +void QWebSocketPrivate::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ + m_pSocket->setSocketOption(option, value); +} + +/*! + \internal + */ +QVariant QWebSocketPrivate::socketOption(QAbstractSocket::SocketOption option) +{ + return m_pSocket->socketOption(option); +} + +/*! + \internal + */ +bool QWebSocketPrivate::isValid() const +{ + return m_pSocket->isValid(); +} + +QT_END_NAMESPACE |