From 8116975eec6d4a441db320076e87dfd36ed89a31 Mon Sep 17 00:00:00 2001 From: Kurt Pattyn Date: Sat, 24 Aug 2013 21:16:50 +0200 Subject: Renamed WebSocket to QWebSocket Renamed WebSocketProtocol to QWebSocketProtocol Renamed WebSocketServer to QWebSocketServer Adapted source files to include renamed headers Added qwebsocketsglobal.h file --- examples/echoclient/echoclient.h | 4 +- examples/echoserver/echoserver.cpp | 14 +- examples/echoserver/echoserver.h | 8 +- source/dataprocessor_p.cpp | 64 +- source/dataprocessor_p.h | 8 +- source/handshakerequest_p.cpp | 8 +- source/handshakerequest_p.h | 6 +- source/handshakeresponse_p.cpp | 16 +- source/handshakeresponse_p.h | 10 +- source/qwebsocket.cpp | 1352 ++++++++++++++++++++++++++++++++++++ source/qwebsocket.h | 155 +++++ source/qwebsocketprotocol.cpp | 218 ++++++ source/qwebsocketprotocol.h | 89 +++ source/qwebsocketserver.cpp | 417 +++++++++++ source/qwebsocketserver.h | 68 ++ source/qwebsocketsglobal.h | 24 + source/websocket.cpp | 1352 ------------------------------------ source/websocket.h | 155 ----- source/websocket.pri | 15 +- source/websocketprotocol.cpp | 218 ------ source/websocketprotocol.h | 89 --- source/websocketserver.cpp | 417 ----------- source/websocketserver.h | 68 -- test/tst_compliance.cpp | 14 +- test/tst_websockets.cpp | 6 +- 25 files changed, 2410 insertions(+), 2385 deletions(-) create mode 100644 source/qwebsocket.cpp create mode 100644 source/qwebsocket.h create mode 100644 source/qwebsocketprotocol.cpp create mode 100644 source/qwebsocketprotocol.h create mode 100644 source/qwebsocketserver.cpp create mode 100644 source/qwebsocketserver.h create mode 100644 source/qwebsocketsglobal.h delete mode 100644 source/websocket.cpp delete mode 100644 source/websocket.h delete mode 100644 source/websocketprotocol.cpp delete mode 100644 source/websocketprotocol.h delete mode 100644 source/websocketserver.cpp delete mode 100644 source/websocketserver.h diff --git a/examples/echoclient/echoclient.h b/examples/echoclient/echoclient.h index 4405071..36a3cad 100644 --- a/examples/echoclient/echoclient.h +++ b/examples/echoclient/echoclient.h @@ -2,7 +2,7 @@ #define ECHOCLIENT_H #include -#include "websocket.h" +#include "qwebsocket.h" class EchoClient : public QObject { @@ -19,7 +19,7 @@ private Q_SLOTS: void onTextMessageReceived(QString message); private: - WebSocket m_webSocket; + QWebSocket m_webSocket; }; #endif // ECHOCLIENT_H diff --git a/examples/echoserver/echoserver.cpp b/examples/echoserver/echoserver.cpp index 1e223c9..c08aad1 100644 --- a/examples/echoserver/echoserver.cpp +++ b/examples/echoserver/echoserver.cpp @@ -1,6 +1,6 @@ #include "echoserver.h" -#include "websocketserver.h" -#include "websocket.h" +#include "qwebsocketserver.h" +#include "qwebsocket.h" #include //! [constructor] @@ -9,7 +9,7 @@ EchoServer::EchoServer(quint16 port, QObject *parent) : m_pWebSocketServer(0), m_clients() { - m_pWebSocketServer = new WebSocketServer("Echo Server", this); + m_pWebSocketServer = new QWebSocketServer("Echo Server", this); if (m_pWebSocketServer->listen(QHostAddress::Any, port)) { qDebug() << "Echoserver listening on port" << port; @@ -21,7 +21,7 @@ EchoServer::EchoServer(quint16 port, QObject *parent) : //! [onNewConnection] void EchoServer::onNewConnection() { - WebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); + QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processMessage(QString))); connect(pSocket, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(processBinaryMessage(QByteArray))); @@ -35,7 +35,7 @@ void EchoServer::onNewConnection() //! [processMessage] void EchoServer::processMessage(QString message) { - WebSocket *pClient = qobject_cast(sender()); + QWebSocket *pClient = qobject_cast(sender()); if (pClient != 0) { pClient->send(message); @@ -46,7 +46,7 @@ void EchoServer::processMessage(QString message) //! [processBinaryMessage] void EchoServer::processBinaryMessage(QByteArray message) { - WebSocket *pClient = qobject_cast(sender()); + QWebSocket *pClient = qobject_cast(sender()); if (pClient != 0) { pClient->send(message); @@ -57,7 +57,7 @@ void EchoServer::processBinaryMessage(QByteArray message) //! [socketDisconnected] void EchoServer::socketDisconnected() { - WebSocket *pClient = qobject_cast(sender()); + QWebSocket *pClient = qobject_cast(sender()); if (pClient != 0) { m_clients.removeAll(pClient); diff --git a/examples/echoserver/echoserver.h b/examples/echoserver/echoserver.h index 21cf0de..aa8c2ba 100644 --- a/examples/echoserver/echoserver.h +++ b/examples/echoserver/echoserver.h @@ -5,8 +5,8 @@ #include #include -class WebSocketServer; -class WebSocket; +class QWebSocketServer; +class QWebSocket; class EchoServer : public QObject { @@ -23,8 +23,8 @@ private Q_SLOTS: void socketDisconnected(); private: - WebSocketServer *m_pWebSocketServer; - QList m_clients; + QWebSocketServer *m_pWebSocketServer; + QList m_clients; }; #endif //ECHOSERVER_H diff --git a/source/dataprocessor_p.cpp b/source/dataprocessor_p.cpp index a45cfad..a760787 100644 --- a/source/dataprocessor_p.cpp +++ b/source/dataprocessor_p.cpp @@ -1,5 +1,5 @@ #include "dataprocessor_p.h" -#include "websocketprotocol.h" +#include "qwebsocketprotocol.h" #include #include #include @@ -17,7 +17,7 @@ public: const Frame &operator =(const Frame &other); - WebSocketProtocol::CloseCode getCloseCode() const; + QWebSocketProtocol::CloseCode getCloseCode() const; QString getCloseReason() const; bool isFinalFrame() const; bool isControlFrame() const; @@ -28,7 +28,7 @@ public: int getRsv1() const; int getRsv2() const; int getRsv3() const; - WebSocketProtocol::OpCode getOpCode() const; + QWebSocketProtocol::OpCode getOpCode() const; QByteArray getPayload() const; void clear(); //resets all member variables, and invalidates the object @@ -38,14 +38,14 @@ public: static Frame readFrame(QTcpSocket *pSocket); private: - WebSocketProtocol::CloseCode m_closeCode; + QWebSocketProtocol::CloseCode m_closeCode; QString m_closeReason; bool m_isFinalFrame; quint32 m_mask; int m_rsv1; //reserved field 1 int m_rsv2; //reserved field 2 int m_rsv3; //reserved field 3 - WebSocketProtocol::OpCode m_opCode; + QWebSocketProtocol::OpCode m_opCode; quint8 m_length; //length field as read from the header; this is 1 byte, which when 126 or 127, indicates a large payload QByteArray m_payload; @@ -63,19 +63,19 @@ private: PS_WAIT_FOR_MORE_DATA }; - void setError(WebSocketProtocol::CloseCode code, QString closeReason); + void setError(QWebSocketProtocol::CloseCode code, QString closeReason); bool checkValidity(); }; Frame::Frame() : - m_closeCode(WebSocketProtocol::CC_NORMAL), + m_closeCode(QWebSocketProtocol::CC_NORMAL), m_closeReason(), m_isFinalFrame(true), m_mask(0), m_rsv1(0), m_rsv2(0), m_rsv3(0), - m_opCode(WebSocketProtocol::OC_RESERVED_V), + m_opCode(QWebSocketProtocol::OC_RESERVED_V), m_length(0), m_payload(), m_isValid(false) @@ -114,7 +114,7 @@ const Frame &Frame::operator =(const Frame &other) return *this; } -WebSocketProtocol::CloseCode Frame::getCloseCode() const +QWebSocketProtocol::CloseCode Frame::getCloseCode() const { return m_closeCode; } @@ -141,7 +141,7 @@ bool Frame::isDataFrame() const bool Frame::isContinuationFrame() const { - return isDataFrame() && (m_opCode == WebSocketProtocol::OC_CONTINUE); + return isDataFrame() && (m_opCode == QWebSocketProtocol::OC_CONTINUE); } bool Frame::hasMask() const @@ -169,7 +169,7 @@ int Frame::getRsv3() const return m_rsv3; } -WebSocketProtocol::OpCode Frame::getOpCode() const +QWebSocketProtocol::OpCode Frame::getOpCode() const { return m_opCode; } @@ -181,14 +181,14 @@ QByteArray Frame::getPayload() const void Frame::clear() { - m_closeCode = WebSocketProtocol::CC_NORMAL; + m_closeCode = QWebSocketProtocol::CC_NORMAL; m_closeReason.clear(); m_isFinalFrame = true; m_mask = 0; m_rsv1 = 0; m_rsv2 =0; m_rsv3 = 0; - m_opCode = WebSocketProtocol::OC_RESERVED_V; + m_opCode = QWebSocketProtocol::OC_RESERVED_V; m_length = 0; m_payload.clear(); m_isValid = false; @@ -221,7 +221,7 @@ Frame Frame::readFrame(QTcpSocket *pSocket) bool ok = pSocket->waitForReadyRead(5000); if (!ok) { - frame.setError(WebSocketProtocol::CC_GOING_AWAY, "Timeout when reading data from socket."); + frame.setError(QWebSocketProtocol::CC_GOING_AWAY, "Timeout when reading data from socket."); isDone = true; } else @@ -241,7 +241,7 @@ Frame Frame::readFrame(QTcpSocket *pSocket) frame.m_rsv1 = (header[0] & 0x40); frame.m_rsv2 = (header[0] & 0x20); frame.m_rsv3 = (header[0] & 0x10); - frame.m_opCode = static_cast(header[0] & 0x0F); + frame.m_opCode = static_cast(header[0] & 0x0F); //Mask, PayloadLength hasMask = (header[1] & 0x80) != 0; @@ -338,7 +338,7 @@ Frame Frame::readFrame(QTcpSocket *pSocket) } else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES) { - frame.setError(WebSocketProtocol::CC_TOO_MUCH_DATA, "Maximum framesize exceeded."); + frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, "Maximum framesize exceeded."); processingState = PS_DISPATCH_RESULT; } else @@ -349,7 +349,7 @@ Frame Frame::readFrame(QTcpSocket *pSocket) frame.m_payload = pSocket->read(payloadLength); if (hasMask) { - WebSocketProtocol::mask(&frame.m_payload, frame.m_mask); + QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask); } processingState = PS_DISPATCH_RESULT; } @@ -382,7 +382,7 @@ Frame Frame::readFrame(QTcpSocket *pSocket) return frame; } -void Frame::setError(WebSocketProtocol::CloseCode code, QString closeReason) +void Frame::setError(QWebSocketProtocol::CloseCode code, QString closeReason) { clear(); m_closeCode = code; @@ -396,21 +396,21 @@ bool Frame::checkValidity() { if (m_rsv1 || m_rsv2 || m_rsv3) { - setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Rsv field is non-zero"); + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, "Rsv field is non-zero"); } - else if (WebSocketProtocol::isOpCodeReserved(m_opCode)) + else if (QWebSocketProtocol::isOpCodeReserved(m_opCode)) { - setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Used reserved opcode"); + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, "Used reserved opcode"); } else if (isControlFrame()) { if (m_length > 125) { - setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frame is larger than 125 bytes"); + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frame is larger than 125 bytes"); } else if (!m_isFinalFrame) { - setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frames cannot be fragmented"); + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frames cannot be fragmented"); } else { @@ -430,7 +430,7 @@ DataProcessor::DataProcessor(QObject *parent) : m_processingState(PS_READ_HEADER), m_isFinalFrame(false), m_isFragmented(false), - m_opCode(WebSocketProtocol::OC_CLOSE), + m_opCode(QWebSocketProtocol::OC_CLOSE), m_isControlFrame(false), m_hasMask(false), m_mask(0), @@ -472,13 +472,13 @@ void DataProcessor::process(QTcpSocket *pSocket) if (!m_isFragmented && frame.isContinuationFrame()) { clear(); - Q_EMIT errorEncountered(WebSocketProtocol::CC_PROTOCOL_ERROR, "Received Continuation frame /*with FIN=true*/, while there is nothing to continue."); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, "Received Continuation frame /*with FIN=true*/, while there is nothing to continue."); return; } if (m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame()) { clear(); - Q_EMIT errorEncountered(WebSocketProtocol::CC_PROTOCOL_ERROR, "All data frames after the initial data frame must have opcode 0 (continuation)."); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, "All data frames after the initial data frame must have opcode 0 (continuation)."); return; } if (!frame.isContinuationFrame()) @@ -486,22 +486,22 @@ void DataProcessor::process(QTcpSocket *pSocket) m_opCode = frame.getOpCode(); m_isFragmented = !frame.isFinalFrame(); } - quint64 messageLength = (quint64)(m_opCode == WebSocketProtocol::OC_TEXT) ? m_textMessage.length() : m_binaryMessage.length(); + quint64 messageLength = (quint64)(m_opCode == QWebSocketProtocol::OC_TEXT) ? m_textMessage.length() : m_binaryMessage.length(); if ((messageLength + quint64(frame.getPayload().length())) > MAX_MESSAGE_SIZE_IN_BYTES) { clear(); - Q_EMIT errorEncountered(WebSocketProtocol::CC_TOO_MUCH_DATA, "Received message is too big."); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_TOO_MUCH_DATA, "Received message is too big."); return; } - if (m_opCode == WebSocketProtocol::OC_TEXT) + if (m_opCode == QWebSocketProtocol::OC_TEXT) { QString frameTxt = m_pTextCodec->toUnicode(frame.getPayload().constData(), frame.getPayload().size(), m_pConverterState); bool failed = (m_pConverterState->invalidChars != 0) || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0)); if (failed) { clear(); - Q_EMIT errorEncountered(WebSocketProtocol::CC_WRONG_DATATYPE, "Invalid UTF-8 code encountered."); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_WRONG_DATATYPE, "Invalid UTF-8 code encountered."); return; } else @@ -518,7 +518,7 @@ void DataProcessor::process(QTcpSocket *pSocket) if (frame.isFinalFrame()) { - if (m_opCode == WebSocketProtocol::OC_TEXT) + if (m_opCode == QWebSocketProtocol::OC_TEXT) { Q_EMIT textMessageReceived(m_textMessage); } @@ -545,7 +545,7 @@ void DataProcessor::clear() m_processingState = PS_READ_HEADER; m_isFinalFrame = false; m_isFragmented = false; - m_opCode = WebSocketProtocol::OC_CLOSE; + m_opCode = QWebSocketProtocol::OC_CLOSE; m_hasMask = false; m_mask = 0; m_binaryMessage.clear(); diff --git a/source/dataprocessor_p.h b/source/dataprocessor_p.h index f184f35..be23788 100644 --- a/source/dataprocessor_p.h +++ b/source/dataprocessor_p.h @@ -5,7 +5,7 @@ #include #include #include -#include "websocketprotocol.h" +#include "qwebsocketprotocol.h" class QTcpSocket; @@ -21,12 +21,12 @@ public: virtual ~DataProcessor(); Q_SIGNALS: - void controlFrameReceived(WebSocketProtocol::OpCode opCode, QByteArray frame); + void controlFrameReceived(QWebSocketProtocol::OpCode opCode, QByteArray frame); void textFrameReceived(QString frame, bool lastFrame); void binaryFrameReceived(QByteArray frame, bool lastFrame); void textMessageReceived(QString message); void binaryMessageReceived(QByteArray message); - void errorEncountered(WebSocketProtocol::CloseCode code, QString description); + void errorEncountered(QWebSocketProtocol::CloseCode code, QString description); public Q_SLOTS: void process(QTcpSocket *pSocket); @@ -46,7 +46,7 @@ private: bool m_isFinalFrame; bool m_isFragmented; - WebSocketProtocol::OpCode m_opCode; + QWebSocketProtocol::OpCode m_opCode; bool m_isControlFrame; bool m_hasMask; quint32 m_mask; diff --git a/source/handshakerequest_p.cpp b/source/handshakerequest_p.cpp index 84f9778..c2b2403 100644 --- a/source/handshakerequest_p.cpp +++ b/source/handshakerequest_p.cpp @@ -5,7 +5,7 @@ #include #include #include -#include "websocketprotocol.h" +#include "qwebsocketprotocol.h" HandshakeRequest::HandshakeRequest(int port, bool isSecure) : m_port(port), @@ -59,7 +59,7 @@ QMap HandshakeRequest::getHeaders() const return m_headers; } -QList HandshakeRequest::getVersions() const +QList HandshakeRequest::getVersions() const { return m_versions; } @@ -140,11 +140,11 @@ QTextStream &HandshakeRequest::readFromStream(QTextStream &textStream) QStringList versions = versionLine.split(",", QString::SkipEmptyParts); Q_FOREACH(QString version, versions) { - WebSocketProtocol::Version ver = WebSocketProtocol::versionFromString(version.trimmed()); + QWebSocketProtocol::Version ver = QWebSocketProtocol::versionFromString(version.trimmed()); m_versions << ver; } } - qStableSort(m_versions.begin(), m_versions.end(), qGreater()); //sort in descending order + qStableSort(m_versions.begin(), m_versions.end(), qGreater()); //sort in descending order m_key = m_headers.value("Sec-WebSocket-Key", ""); QString upgrade = m_headers.value("Upgrade", ""); //must be equal to "websocket", case-insensitive QString connection = m_headers.value("Connection", ""); //must contain "Upgrade", case-insensitive diff --git a/source/handshakerequest_p.h b/source/handshakerequest_p.h index 708950a..2e8fc5e 100644 --- a/source/handshakerequest_p.h +++ b/source/handshakerequest_p.h @@ -7,7 +7,7 @@ #include #include -#include "websocketprotocol.h" +#include "qwebsocketprotocol.h" class QTextStream; @@ -23,7 +23,7 @@ public: bool isSecure() const; bool isValid() const; QMap getHeaders() const; - QList getVersions() const; + QList getVersions() const; QString getKey() const; QString getOrigin() const; QList getProtocols() const; @@ -41,7 +41,7 @@ private: bool m_isSecure; bool m_isValid; QMap m_headers; - QList m_versions; + QList m_versions; QString m_key; QString m_origin; QList m_protocols; diff --git a/source/handshakeresponse_p.cpp b/source/handshakeresponse_p.cpp index fdee13d..ca7bd0d 100644 --- a/source/handshakeresponse_p.cpp +++ b/source/handshakeresponse_p.cpp @@ -12,7 +12,7 @@ HandshakeResponse::HandshakeResponse(const HandshakeRequest &request, const QString &serverName, bool isOriginAllowed, - const QList &supportedVersions, + const QList &supportedVersions, const QList &supportedProtocols, const QList &supportedExtensions) : m_isValid(false), @@ -20,7 +20,7 @@ HandshakeResponse::HandshakeResponse(const HandshakeRequest &request, m_response(), m_acceptedProtocol(), m_acceptedExtension(), - m_acceptedVersion(WebSocketProtocol::V_Unknow) + m_acceptedVersion(QWebSocketProtocol::V_Unknow) { m_response = getHandshakeResponse(request, serverName, isOriginAllowed, supportedVersions, supportedProtocols, supportedExtensions); m_isValid = true; @@ -55,7 +55,7 @@ QString HandshakeResponse::calculateAcceptKey(const QString &key) const QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, const QString &serverName, bool isOriginAllowed, - const QList &supportedVersions, + const QList &supportedVersions, const QList &supportedProtocols, const QList &supportedExtensions) { @@ -76,8 +76,8 @@ QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, QString acceptKey = calculateAcceptKey(request.getKey()); QList matchingProtocols = supportedProtocols.toSet().intersect(request.getProtocols().toSet()).toList(); QList matchingExtensions = supportedExtensions.toSet().intersect(request.getExtensions().toSet()).toList(); - QList matchingVersions = request.getVersions().toSet().intersect(supportedVersions.toSet()).toList(); - qStableSort(matchingVersions.begin(), matchingVersions.end(), qGreater()); //sort in descending order + QList matchingVersions = request.getVersions().toSet().intersect(supportedVersions.toSet()).toList(); + qStableSort(matchingVersions.begin(), matchingVersions.end(), qGreater()); //sort in descending order if (matchingVersions.isEmpty()) { @@ -112,7 +112,7 @@ QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, "Access-Control-Allow-Origin: " + origin << "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'"); - m_acceptedVersion = WebSocketProtocol::currentVersion(); + m_acceptedVersion = QWebSocketProtocol::currentVersion(); m_canUpgrade = true; } } @@ -124,7 +124,7 @@ QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, { response << "HTTP/1.1 400 Bad Request"; QStringList versions; - Q_FOREACH(WebSocketProtocol::Version version, supportedVersions) + Q_FOREACH(QWebSocketProtocol::Version version, supportedVersions) { versions << QString::number(static_cast(version)); } @@ -153,7 +153,7 @@ QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response) return response.writeToStream(stream); } -WebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const +QWebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const { return m_acceptedVersion; } diff --git a/source/handshakeresponse_p.h b/source/handshakeresponse_p.h index 38e60b5..ed7ada2 100644 --- a/source/handshakeresponse_p.h +++ b/source/handshakeresponse_p.h @@ -2,7 +2,7 @@ #define HANDSHAKERESPONSE_P_H #include -#include "websocketprotocol.h" +#include "qwebsocketprotocol.h" class HandshakeRequest; class QString; @@ -15,7 +15,7 @@ public: HandshakeResponse(const HandshakeRequest &request, const QString &serverName, bool isOriginAllowed, - const QList &supportedVersions, + const QList &supportedVersions, const QList &supportedProtocols, const QList &supportedExtensions); @@ -25,7 +25,7 @@ public: bool canUpgrade() const; QString getAcceptedProtocol() const; QString getAcceptedExtension() const; - WebSocketProtocol::Version getAcceptedVersion() const; + QWebSocketProtocol::Version getAcceptedVersion() const; public Q_SLOTS: @@ -38,13 +38,13 @@ private: QString m_response; QString m_acceptedProtocol; QString m_acceptedExtension; - WebSocketProtocol::Version m_acceptedVersion; + QWebSocketProtocol::Version m_acceptedVersion; QString calculateAcceptKey(const QString &key) const; QString getHandshakeResponse(const HandshakeRequest &request, const QString &serverName, bool isOriginAllowed, - const QList &supportedVersions, + const QList &supportedVersions, const QList &supportedProtocols, const QList &supportedExtensions); diff --git a/source/qwebsocket.cpp b/source/qwebsocket.cpp new file mode 100644 index 0000000..8ac59c3 --- /dev/null +++ b/source/qwebsocket.cpp @@ -0,0 +1,1352 @@ +#include "qwebsocket.h" +#include "handshakerequest_p.h" +#include "handshakeresponse_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/*! + \class QWebSocket + \brief The class QWebSocket implements a TCP socket that talks the websocket protocol. + + WebSockets is a web technology providing full-duplex communications channels over a single TCP connection. + The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011 (see http://tools.ietf.org/html/rfc6455). + It can both be used in a client application and server application. + + This class was modeled after QAbstractSocket. + + \ref echoclient + + \author Kurt Pattyn (pattyn.kurt@gmail.com) +*/ +/*! + \page echoclient QWebSocket client example + \brief A sample websocket client that sends a message and displays the message that it receives back. + + \section Description + The EchoClient example implements a web socket client that sends a message to a websocket server and dumps the answer that it gets back. + This example should ideally be used with the EchoServer example. + \section Code + We start by connecting to the `connected()` signal. + \snippet echoclient.cpp constructor + After the connection, we open the socket to the given \a url. + + \snippet echoclient.cpp onConnected + When the client is connected successfully, we connect to the `onTextMessageReceived()` signal, and send out "Hello, world!". + If connected with the EchoServer, we will receive the same message back. + + \snippet echoclient.cpp onTextMessageReceived + Whenever a message is received, we write it out. +*/ + +/*! + \fn void QWebSocket::connected() + \brief Emitted when a connection is successfully established. + \sa open(), disconnected() +*/ +/*! + \fn void QWebSocket::disconnected() + \brief Emitted when the socket is disconnected. + \sa close(), connected() +*/ +/*! + \fn void QWebSocket::aboutToClose() + + This signal is emitted when the socket is about to close. + Connect this signal if you have operations that need to be performed before the socket closes + (e.g., if you have data in a separate buffer that needs to be written to the device). + + \sa close() + */ +/*! +\fn void QWebSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + +This signal can be emitted when a \a proxy that requires +authentication is used. The \a authenticator object can then be +filled in with the required details to allow authentication and +continue the connection. + +\note It is not possible to use a QueuedConnection to connect to +this signal, as the connection will fail if the authenticator has +not been filled in with new information when the signal returns. + +\sa QAuthenticator, QNetworkProxy +*/ +/*! + \fn void QWebSocket::stateChanged(QAbstractSocket::SocketState state); + + This signal is emitted whenever QWebSocket's state changes. + The \a socketState parameter is the new state. + + QAbstractSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_REGISTER_METATYPE() and + qRegisterMetaType(). + + \sa state() +*/ +/*! + \fn void QWebSocket::readChannelFinished() + + This signal is emitted when the input (reading) stream is closed in this device. It is emitted as soon as the closing is detected. + + \sa close() +*/ + +/*! + \fn void QWebSocket::textFrameReceived(QString frame, bool isLastFrame); + + This signal is emitted whenever a text frame is received. The \a frame contains the data and + \a isLastFrame indicates whether this is the last frame of the complete message. + + This signal can be used to process large messages frame by frame, instead of waiting for the complete + message to arrive. + + \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) +*/ +/*! + \fn void QWebSocket::binaryFrameReceived(QByteArray frame, bool isLastFrame); + + This signal is emitted whenever a binary frame is received. The \a frame contains the data and + \a isLastFrame indicates whether this is the last frame of the complete message. + + This signal can be used to process large messages frame by frame, instead of waiting for the complete + message to arrive. + + \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) +*/ +/*! + \fn void QWebSocket::textMessageReceived(QString message); + + This signal is emitted whenever a text message is received. The \a message contains the received text. + + \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) +*/ +/*! + \fn void QWebSocket::binaryMessageReceived(QByteArray message); + + This signal is emitted whenever a binary message is received. The \a message contains the received bytes. + + \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) +*/ +/*! + \fn void QWebSocket::error(QAbstractSocket::SocketError error); + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QAbstractSocket::SocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa error(), errorString() +*/ +/*! + \fn void QWebSocket::pong(quint64 elapsedTime) + + Emitted when a pong message is received in reply to a previous ping. + + \sa ping() + */ + +const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message + +/*! + * \brief Creates a new QWebSocket with the given \a origin, the \a version of the protocol to use and \a parent. + * + * The \a origin of the client is as specified in http://tools.ietf.org/html/rfc6454. + * (The \a origin is not required for non-web browser clients (see RFC 6455)). + * \note Currently only V13 (RFC 6455) is supported + */ +QWebSocket::QWebSocket(QString origin, QWebSocketProtocol::Version version, QObject *parent) : + QObject(parent), + 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() +{ + makeConnections(m_pSocket); + qsrand(static_cast(QDateTime::currentMSecsSinceEpoch())); +} + +//only called by upgradeFrom +/*! + \internal + Constructor used for the server implementation. Should never be called directly. + + pTcpSocket The tcp socket to use for this websocket + version The version of the protocol to speak (currently only V13 is supported) + parent The parent object of the QWebSocket object +*/ +QWebSocket::QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent) : + QObject(parent), + 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() +{ + makeConnections(m_pSocket); +} + +/*! + * \brief Destroys the QWebSocket. Closes the socket if it is still open, and releases any used resources. + */ +QWebSocket::~QWebSocket() +{ + if (state() == QAbstractSocket::ConnectedState) + { + close(QWebSocketProtocol::CC_GOING_AWAY, "Connection closed"); + } + releaseConnections(m_pSocket); + m_pSocket->deleteLater(); + m_pSocket = 0; +} + +/*! + * \brief Aborts the current socket and resets the socket. Unlike close(), this function immediately closes the socket, discarding any pending data in the write buffer. + */ +void QWebSocket::abort() +{ + m_pSocket->abort(); +} + +/*! + * Returns the type of error that last occurred + * \sa errorString() + */ +QAbstractSocket::SocketError QWebSocket::error() const +{ + return m_pSocket->error(); +} + +/*! + * Returns a human-readable description of the last error that occurred + * + * \sa error() + */ +QString QWebSocket::errorString() const +{ + if (!m_errorString.isEmpty()) + { + return m_errorString; + } + else + { + return m_pSocket->errorString(); + } +} + +/*! + This function writes as much as possible from the internal write buffer to the underlying network socket, without blocking. + If any data was written, this function returns true; otherwise false is returned. + Call this function if you need WebSocket to start sending buffered data immediately. + The number of bytes successfully written depends on the operating system. + In most cases, you do not need to call this function, because WebSocket will start sending data automatically once control goes back to the event loop. + In the absence of an event loop, call waitForBytesWritten() instead. + + \sa send() and waitForBytesWritten(). +*/ +bool QWebSocket::flush() +{ + return m_pSocket->flush(); +} + +/*! + * Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. + * \param message Text message to be sent. Must be '\0' terminated. + * \return The number of bytes actually sent. + * \sa send(const QString &message) + */ +qint64 QWebSocket::send(const char *message) +{ + return send(QString::fromUtf8(message)); +} + +/** + * @brief Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. + * @param message The message to be sent + * @return The number of bytes actually sent. + */ +qint64 QWebSocket::send(const QString &message) +{ + return doWriteData(message.toUtf8(), false); +} + +/** + * @brief Sends the given \a data over the socket as a binary message and returns the number of bytes actually sent. + * @param data The binary data to be sent. + * @return The number of bytes actually sent. + */ +qint64 QWebSocket::send(const QByteArray &data) +{ + return doWriteData(data, true); +} + +/*! + \internal + */ +QWebSocket *QWebSocket::upgradeFrom(QTcpSocket *pTcpSocket, + const HandshakeRequest &request, + const HandshakeResponse &response, + QObject *parent) +{ + QWebSocket *pWebSocket = new QWebSocket(pTcpSocket, response.getAcceptedVersion(), parent); + pWebSocket->setExtension(response.getAcceptedExtension()); + pWebSocket->setOrigin(request.getOrigin()); + pWebSocket->setRequestUrl(request.getRequestUrl()); + pWebSocket->setProtocol(response.getAcceptedProtocol()); + pWebSocket->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); + pWebSocket->enableMasking(false); //a server should not send masked frames + + return pWebSocket; +} + +/*! + * \brief Gracefully closes the socket with the given \a closeCode and \a reason. Any data in the write buffer is flushed before the socket is closed. + * \param closeCode The QWebSocketProtocol::CloseCode indicating the reason to close. + * \param reason A string describing the error more in detail + */ +void QWebSocket::close(QWebSocketProtocol::CloseCode closeCode, QString reason) +{ + if (!m_isClosingHandshakeSent) + { + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + quint16 code = qToBigEndian(closeCode); + QByteArray payload; + payload.append(static_cast(static_cast(&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 aboutToClose(); + } + m_pSocket->close(); +} + +/*! + * \brief Opens a websocket connection using the given \a url. + * If \a mask is true, all frames will be masked; this is only necessary for client side sockets; servers should never mask + * \param url The url to connect to + * \param mask When true, all frames are masked + * \note A client socket must *always* mask its frames; servers may *never* mask its frames + */ +void QWebSocket::open(const QUrl &url, bool mask) +{ + m_dataProcessor.clear(); + m_isClosingHandshakeReceived = false; + m_isClosingHandshakeSent = false; + + setRequestUrl(url); + QString resourceName = url.path() + url.query(); + if (resourceName.isEmpty()) + { + resourceName = "/"; + } + setResourceName(resourceName); + enableMasking(mask); + + setSocketState(QAbstractSocket::ConnectingState); + + m_pSocket->connectToHost(url.host(), url.port(80)); +} + +/*! + * \brief Pings the server to indicate that the connection is still alive. + * + * \sa pong() + */ +void QWebSocket::ping() +{ + m_pingTimer.restart(); + QByteArray pingFrame = getFrameHeader(QWebSocketProtocol::OC_PING, 0, 0, true); + writeFrame(pingFrame); +} + +/*! + \internal + Sets the version to use for the websocket protocol; this must be set before the socket is opened. +*/ +void QWebSocket::setVersion(QWebSocketProtocol::Version version) +{ + m_version = version; +} + +/*! + \internal + Sets the resource name of the connection; must be set before the socket is openend +*/ +void QWebSocket::setResourceName(QString resourceName) +{ + m_resourceName = resourceName; +} + +/*! + \internal + */ +void QWebSocket::setRequestUrl(QUrl requestUrl) +{ + m_requestUrl = requestUrl; +} + +/*! + \internal + */ +void QWebSocket::setOrigin(QString origin) +{ + m_origin = origin; +} + +/*! + \internal + */ +void QWebSocket::setProtocol(QString protocol) +{ + m_protocol = protocol; +} + +/*! + \internal + */ +void QWebSocket::setExtension(QString extension) +{ + m_extension = extension; +} + +/*! + \internal + */ +void QWebSocket::enableMasking(bool enable) +{ + m_mustMask = enable; +} + +/*! + * \internal + */ +qint64 QWebSocket::doWriteData(const QByteArray &data, bool isBinary) +{ + return doWriteFrames(data, isBinary); +} + +/*! + * \internal + */ +void QWebSocket::makeConnections(const QTcpSocket *pTcpSocket) +{ + //pass through signals + connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); + connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + connect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); + //connect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); + //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, 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(controlFrameReceived(QWebSocketProtocol::OpCode, QByteArray)), this, SLOT(processControlFrame(QWebSocketProtocol::OpCode, QByteArray))); + connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); + connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); + connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); + connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); + connect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + * \internal + */ +void QWebSocket::releaseConnections(const QTcpSocket *pTcpSocket) +{ + if (pTcpSocket) + { + //pass through signals + disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); + disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + disconnect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); + //disconnect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); + //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, 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(controlFrameReceived(QWebSocketProtocol::OpCode,QByteArray)), this, SLOT(processControlFrame(QWebSocketProtocol::OpCode,QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); + disconnect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + * \brief Returns the version the socket is currently using + */ +QWebSocketProtocol::Version QWebSocket::version() +{ + return m_version; +} + +/** + * @brief Returns the name of the resource currently accessed. + */ +QString QWebSocket::resourceName() +{ + return m_resourceName; +} + +/*! + * \brief Returns the url the socket is connected to or will connect to. + */ +QUrl QWebSocket::requestUrl() +{ + return m_requestUrl; +} + +/*! + Returns the current origin + */ +QString QWebSocket::origin() +{ + return m_origin; +} + +/*! + Returns the currently used protocol. + */ +QString QWebSocket::protocol() +{ + return m_protocol; +} + +/*! + Returns the currently used extension. + */ +QString QWebSocket::extension() +{ + return m_extension; +} + +/*! + * \internal + */ +QByteArray QWebSocket::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((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00)); //FIN, opcode + //RSV-1, RSV-2 and RSV-3 are zero + header.append(static_cast(byte)); + + //Now write the masking bit and the payload length byte + byte = 0x00; + if (maskingKey != 0) + { + byte |= 0x80; + } + if (payloadLength <= 125) + { + byte |= static_cast(payloadLength); + header.append(static_cast(byte)); + } + else if (payloadLength <= 0xFFFFU) + { + byte |= 126; + header.append(static_cast(byte)); + quint16 swapped = qToBigEndian(static_cast(payloadLength)); + header.append(static_cast(static_cast(&swapped)), 2); + } + else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) + { + byte |= 127; + header.append(static_cast(byte)); + quint64 swapped = qToBigEndian(payloadLength); + header.append(static_cast(static_cast(&swapped)), 8); + } + + //Write mask + if (maskingKey != 0) + { + header.append(static_cast(static_cast(&maskingKey)), sizeof(quint32)); + } + } + else + { + //setErrorString("WebSocket::getHeader: payload too big!"); + //Q_EMIT error(QAbstractSocket::DatagramTooLargeError); + qDebug() << "WebSocket::getHeader: payload too big!"; + } + + return header; +} + +/*! + * \internal + */ +qint64 QWebSocket::doWriteFrames(const QByteArray &data, bool isBinary) +{ + 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(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(size)); + if (written > 0) + { + bytesWritten += written; + payloadWritten += written; + } + else + { + setErrorString("WebSocket::doWriteFrames: Error writing bytes to socket: " + m_pSocket->errorString()); + qDebug() << errorString(); + m_pSocket->flush(); + Q_EMIT error(QAbstractSocket::NetworkError); + break; + } + } + currentPosition += size; + bytesLeft -= size; + } + if (payloadWritten != data.size()) + { + setErrorString("Bytes written " + QString::number(payloadWritten) + " != " + QString::number(data.size())); + qDebug() << errorString(); + Q_EMIT error(QAbstractSocket::NetworkError); + } + return payloadWritten; +} + +/*! + * \internal + */ +quint32 QWebSocket::generateRandomNumber() const +{ + return static_cast((static_cast(qrand()) / RAND_MAX) * std::numeric_limits::max()); +} + +/*! + \internal + */ +quint32 QWebSocket::generateMaskingKey() const +{ + return generateRandomNumber(); +} + +/*! + \internal + */ +QByteArray QWebSocket::generateKey() const +{ + QByteArray key; + + for (int i = 0; i < 4; ++i) + { + quint32 tmp = generateRandomNumber(); + key.append(static_cast(static_cast(&tmp)), sizeof(quint32)); + } + + return key.toBase64(); +} + + +/*! + \internal + */ +QString QWebSocket::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 QWebSocket::writeFrames(const QList &frames) +{ + qint64 written = 0; + for (int i = 0; i < frames.size(); ++i) + { + written += writeFrame(frames[i]); + } + return written; +} + +/*! + \internal + */ +qint64 QWebSocket::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 QWebSocket::processHandshake(QTcpSocket *pSocket) +{ + 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 = "WebSocket::processHandshake: Invalid statusline in response: " + statusLine; + } + else + { + QString headerLine = readLine(pSocket); + QMap 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 = "WebSocket::processHandshake: Accept-Key received from server " + acceptKey + " does not match the client key " + accept; + } + } + else + { + errorDescription = "WebSocket::processHandshake: Invalid statusline in response: " + 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 = "WebSocket::processHandshake: Server requests a version that we don't support: " + versions.join(", "); + ok = false; + } + else + { + //we tried v13, but something different went wrong + errorDescription = "WebSocket::processHandshake: Unknown error condition encountered. Aborting connection."; + ok = false; + } + } + } + else + { + errorDescription = "WebSocket::processHandshake: Unhandled http status code " + QString::number(httpStatusCode); + ok = false; + } + + if (!ok) + { + qDebug() << errorDescription; + setErrorString(errorDescription); + Q_EMIT error(QAbstractSocket::ConnectionRefusedError); + } + else + { + //handshake succeeded + setSocketState(QAbstractSocket::ConnectedState); + Q_EMIT connected(); + } + } +} + +/*! + \internal + */ +void QWebSocket::processStateChanged(QAbstractSocket::SocketState socketState) +{ + 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 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 QWebSocket::processData() +{ + while (m_pSocket->bytesAvailable()) + { + if (state() == QAbstractSocket::ConnectingState) + { + processHandshake(m_pSocket); + } + else + { + m_dataProcessor.process(m_pSocket); + } + } +} + +/*! + \internal + */ +void QWebSocket::processControlFrame(QWebSocketProtocol::OpCode opCode, QByteArray frame) +{ + switch (opCode) + { + case QWebSocketProtocol::OC_PING: + { + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + m_pSocket->write(getFrameHeader(QWebSocketProtocol::OC_PONG, frame.size(), maskingKey, true)); + if (frame.size() > 0) + { + if (m_mustMask) + { + QWebSocketProtocol::mask(&frame, maskingKey); + } + m_pSocket->write(frame); + } + break; + } + case QWebSocketProtocol::OC_PONG: + { + Q_EMIT pong(static_cast(m_pingTimer.elapsed())); + break; + } + case QWebSocketProtocol::OC_CLOSE: + { + quint16 closeCode = QWebSocketProtocol::CC_NORMAL; + QString closeReason; + if (frame.size() > 0) //close frame can have a close code and reason + { + closeCode = qFromBigEndian(reinterpret_cast(frame.constData())); + if (!QWebSocketProtocol::isCloseCodeValid(closeCode)) + { + closeCode = QWebSocketProtocol::CC_PROTOCOL_ERROR; + closeReason = QString("Invalid close code %1 detected").arg(closeCode); + } + else + { + if (frame.size() > 2) + { + QTextCodec *tc = QTextCodec::codecForName("UTF-8"); + QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); + closeReason = tc->toUnicode(frame.constData() + 2, frame.size() - 2, &state); + bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); + if (failed) + { + closeCode = QWebSocketProtocol::CC_WRONG_DATATYPE; + closeReason = "Invalid UTF-8 code encountered."; + } + } + } + } + m_isClosingHandshakeReceived = true; + close(static_cast(closeCode), closeReason); + break; + } + case QWebSocketProtocol::OC_CONTINUE: + case QWebSocketProtocol::OC_BINARY: + case QWebSocketProtocol::OC_TEXT: + case QWebSocketProtocol::OC_RESERVED_3: + case QWebSocketProtocol::OC_RESERVED_4: + case QWebSocketProtocol::OC_RESERVED_5: + case QWebSocketProtocol::OC_RESERVED_6: + case QWebSocketProtocol::OC_RESERVED_7: + case QWebSocketProtocol::OC_RESERVED_B: + case QWebSocketProtocol::OC_RESERVED_D: + case QWebSocketProtocol::OC_RESERVED_E: + case QWebSocketProtocol::OC_RESERVED_F: + case QWebSocketProtocol::OC_RESERVED_V: + { + //do nothing + //case added to make C++ compiler happy + break; + } + default: + { + qDebug() << "WebSocket::processData: Invalid opcode detected:" << static_cast(opCode); + //Do nothing + break; + } + } +} + +/*! + \internal + */ +QString QWebSocket::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"); +} + +/*! + Returns the current state of the socket + */ +QAbstractSocket::SocketState QWebSocket::state() const +{ + return m_socketState; +} + +/** + @brief Waits until the socket is connected, up to \a msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error. + The following example waits up to one second for a connection to be established: + + ~~~{.cpp} + socket->open("ws://localhost:1234", false); + if (socket->waitForConnected(1000)) + { + qDebug("Connected!"); + } + ~~~ + + If \a msecs is -1, this function will not time out. + @note This function may wait slightly longer than msecs, depending on the time it takes to complete the host lookup. + @note Multiple calls to this functions do not accumulate the time. If the function times out, the connecting process will be aborted. + + \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is connected. + + \sa connected(), open(), state() + */ +bool QWebSocket::waitForConnected(int msecs) +{ + bool retVal = false; + if (m_pSocket) + { + retVal = m_pSocket->waitForConnected(msecs); + } + return retVal; +} + +/*! + Waits \a msecs for the socket to be disconnected. + If the socket was successfully disconnected within time, this method returns true. + Otherwise false is returned. + + \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is disconnected. + + \sa close(), state() +*/ +bool QWebSocket::waitForDisconnected(int msecs) +{ + bool retVal = true; + if (m_pSocket) + { + retVal = m_pSocket->waitForDisconnected(msecs); + } + return retVal; +} + +/*! + \internal + Sets the internal socket state +*/ +void QWebSocket::setSocketState(QAbstractSocket::SocketState state) +{ + if (m_socketState != state) + { + m_socketState = state; + Q_EMIT stateChanged(m_socketState); + } +} + +/*! + \internal + Sets the error string. + Only used internally. +*/ +void QWebSocket::setErrorString(QString errorString) +{ + m_errorString = errorString; +} + +/*! + Returns the local address + */ +QHostAddress QWebSocket::localAddress() const +{ + QHostAddress address; + if (m_pSocket) + { + address = m_pSocket->localAddress(); + } + return address; +} + +/*! + Returns the local port + */ +quint16 QWebSocket::localPort() const +{ + quint16 port = 0; + if (m_pSocket) + { + port = m_pSocket->localPort(); + } + return port; +} + +/*! + Returns the peer address + */ +QHostAddress QWebSocket::peerAddress() const +{ + QHostAddress peer; + if (m_pSocket) + { + peer = m_pSocket->peerAddress(); + } + return peer; +} + +/*! + Returns the peerName + */ +QString QWebSocket::peerName() const +{ + QString name; + if (m_pSocket) + { + name = m_pSocket->peerName(); + } + return name; +} + +/*! + Returns the peerport + */ +quint16 QWebSocket::peerPort() const +{ + quint16 port = 0; + if (m_pSocket) + { + port = m_pSocket->peerPort(); + } + return port; +} + +/*! + * Returns the currently configured proxy + */ +QNetworkProxy QWebSocket::proxy() const +{ + QNetworkProxy proxy; + if (m_pSocket) + { + proxy = m_pSocket->proxy(); + } + return proxy; +} + +/*! + * Returns the size in bytes of the readbuffer that is used by the socket. + */ +qint64 QWebSocket::readBufferSize() const +{ + qint64 readBuffer = 0; + if (m_pSocket) + { + readBuffer = m_pSocket->readBufferSize(); + } + return readBuffer; +} + +/*! + Sets the proxy to \a networkProxy + */ +void QWebSocket::setProxy(const QNetworkProxy &networkProxy) +{ + if (m_pSocket) + { + m_pSocket->setProxy(networkProxy); + } +} + +/** + Sets the size of WebSocket's internal read buffer to be size bytes. + If the buffer size is limited to a certain size, WebSocket won't buffer more than this size of data. + Exceptionally, a buffer size of 0 means that the read buffer is unlimited and all incoming data is buffered. This is the default. + This option is useful if you only read the data at certain points in time (e.g., in a real-time streaming application) or if you want to protect your socket against receiving too much data, which may eventually cause your application to run out of memory. + \sa readBufferSize() and read(). +*/ +void QWebSocket::setReadBufferSize(qint64 size) +{ + if (m_pSocket) + { + m_pSocket->setReadBufferSize(size); + } +} + +/*! + Sets the given \a option to the value described by \a value. + \sa socketOption(). +*/ +void QWebSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ + if (m_pSocket) + { + m_pSocket->setSocketOption(option, value); + } +} + +/*! + Returns the value of the option \a option. + \sa setSocketOption(). +*/ +QVariant QWebSocket::socketOption(QAbstractSocket::SocketOption option) +{ + QVariant result; + if (m_pSocket) + { + result = m_pSocket->socketOption(option); + } + return result; +} + +/*! + Returns true if the WebSocket is valid. + */ +bool QWebSocket::isValid() +{ + bool valid = false; + if (m_pSocket) + { + valid = m_pSocket->isValid(); + } + return valid; +} diff --git a/source/qwebsocket.h b/source/qwebsocket.h new file mode 100644 index 0000000..8a861b7 --- /dev/null +++ b/source/qwebsocket.h @@ -0,0 +1,155 @@ +/** + * @file websocket.h + * @brief Defines the WebSocket class. + * + * \note Currently, only V13 (RFC6455) is supported. + * \note Both text and binary websockets are supported. + * \note The secure version (wss) is currently not implemented. + * @author Kurt Pattyn (pattyn.kurt@gmail.com) + */ + +#ifndef QWEBSOCKET_H +#define QWEBSOCKET_H + +#include +#include +#include +#include "qwebsocketprotocol.h" +#include "dataprocessor_p.h" +#include +#include + +class HandshakeRequest; +class HandshakeResponse; +class QTcpSocket; + +class QWebSocket:public QObject +{ + Q_OBJECT + +public: + explicit QWebSocket(QString origin = QString(), QWebSocketProtocol::Version version = QWebSocketProtocol::V_LATEST, QObject *parent = 0); + virtual ~QWebSocket(); + + void abort(); + QAbstractSocket::SocketError error() const; + QString errorString() const; + bool flush(); + bool isValid(); + QHostAddress localAddress() const; + quint16 localPort() const; + QHostAddress peerAddress() const; + QString peerName() const; + quint16 peerPort() const; + QNetworkProxy proxy() const; + qint64 readBufferSize() const; + void setProxy(const QNetworkProxy &networkProxy); + void setReadBufferSize(qint64 size); + void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); + QVariant socketOption(QAbstractSocket::SocketOption option); + QAbstractSocket::SocketState state() const; + + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + + QWebSocketProtocol::Version version(); + QString resourceName(); + QUrl requestUrl(); + QString origin(); + QString protocol(); + QString extension(); + + qint64 send(const char *message); + qint64 send(const QString &message); //send data as text + qint64 send(const QByteArray &data); //send data as binary + +public Q_SLOTS: + virtual void close(QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CC_NORMAL, QString reason = QString()); + virtual void open(const QUrl &url, bool mask = true); + void ping(); + +Q_SIGNALS: + void aboutToClose(); + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState state); + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *pAuthenticator); + void readChannelFinished(); + void textFrameReceived(QString frame, bool isLastFrame); + void binaryFrameReceived(QByteArray frame, bool isLastFrame); + void textMessageReceived(QString message); + void binaryMessageReceived(QByteArray message); + void error(QAbstractSocket::SocketError error); + void pong(quint64 elapsedTime); + +private Q_SLOTS: + void processData(); + void processControlFrame(QWebSocketProtocol::OpCode opCode, QByteArray frame); + void processHandshake(QTcpSocket *pSocket); + void processStateChanged(QAbstractSocket::SocketState socketState); + +private: + Q_DISABLE_COPY(QWebSocket) + + QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent = 0); + void setVersion(QWebSocketProtocol::Version version); + void setResourceName(QString resourceName); + void setRequestUrl(QUrl requestUrl); + void setOrigin(QString origin); + void setProtocol(QString protocol); + void setExtension(QString extension); + void enableMasking(bool enable); + void setSocketState(QAbstractSocket::SocketState state); + void setErrorString(QString errorString); + + qint64 doWriteData(const QByteArray &data, bool isBinary); + qint64 doWriteFrames(const QByteArray &data, bool isBinary); + + void makeConnections(const QTcpSocket *pTcpSocket); + void releaseConnections(const QTcpSocket *pTcpSocket); + + QByteArray getFrameHeader(QWebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const; + QString calculateAcceptKey(const QString &key) const; + QString createHandShakeRequest(QString resourceName, + QString host, + QString origin, + QString extensions, + QString protocols, + QByteArray key); + + quint32 generateMaskingKey() const; + QByteArray generateKey() const; + quint32 generateRandomNumber() const; + qint64 writeFrames(const QList &frames); + qint64 writeFrame(const QByteArray &frame); + + static QWebSocket *upgradeFrom(QTcpSocket *tcpSocket, + const HandshakeRequest &request, + const HandshakeResponse &response, + QObject *parent = 0); + friend class QWebSocketServer; + + QTcpSocket *m_pSocket; + QString m_errorString; + QWebSocketProtocol::Version m_version; + QUrl m_resource; + QString m_resourceName; + QUrl m_requestUrl; + QString m_origin; + QString m_protocol; + QString m_extension; + QAbstractSocket::SocketState m_socketState; + + QByteArray m_key; //identification key used in handshake requests + + bool m_mustMask; //a server must not mask the frames it sends + + bool m_isClosingHandshakeSent; + bool m_isClosingHandshakeReceived; + + QTime m_pingTimer; + + DataProcessor m_dataProcessor; +}; + +#endif // QWEBSOCKET_H diff --git a/source/qwebsocketprotocol.cpp b/source/qwebsocketprotocol.cpp new file mode 100644 index 0000000..44694dc --- /dev/null +++ b/source/qwebsocketprotocol.cpp @@ -0,0 +1,218 @@ +#include "qwebsocketprotocol.h" +#include +#include +#include + +/*! + \enum WebSocketProtocol::CloseCode + + The close codes supported by WebSockets V13 + \sa WebSocket::close() +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_NORMAL + Normal closure +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_GOING_AWAY + Going away +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_PROTOCOL_ERROR + Protocol error +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_DATATYPE_NOT_SUPPORTED + Unsupported data +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_RESERVED_1004 + Reserved +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_MISSING_STATUS_CODE + No status received +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_ABNORMAL_DISCONNECTION + Abnormal closure +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_WRONG_DATATYPE + Invalid frame payload data +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_POLICY_VIOLATED + Policy violation +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_TOO_MUCH_DATA + Message too big +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_MISSING_EXTENSION + Mandatory extension missing +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_BAD_OPERATION + Internal server error +*/ +/*! + \var WebSocketProtocol::CloseCode::CC_TLS_HANDSHAKE_FAILED + TLS handshake failed +*/ +/*! + \enum WebSocketProtocol::Version + + \brief The different defined versions of the Websocket protocol. + + For an overview of the differences between the different protocols, see + +*/ +/*! + \var WebSocketProtocol::Version::V_Unknow +*/ +/*! + \var WebSocketProtocol::Version::V_0 + hixie76: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 & hybi-00: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00. + Works with key1, key2 and a key in the payload.\n + Attribute: Sec-WebSocket-Draft value 0. +*/ +/*! + \var WebSocketProtocol::Version::V_4 + hybi-04: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-04.txt. + Changed handshake: key1, key2, key3 ==> Sec-WebSocket-Key, Sec-WebSocket-Nonce, Sec-WebSocket-Accept\n + Sec-WebSocket-Draft renamed to Sec-WebSocket-Version\n + Sec-WebSocket-Version = 4 +*/ +/*! + \var WebSocketProtocol::Version::V_5 + hybi-05: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-05.txt. + Sec-WebSocket-Version = 5\n + Removed Sec-WebSocket-Nonce\n + Added Sec-WebSocket-Accept\n +*/ +/*! + \var WebSocketProtocol::Version::V_6 + Sec-WebSocket-Version = 6. +*/ +/*! + \var WebSocketProtocol::Version::V_7 + hybi-07: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07. + Sec-WebSocket-Version = 7 +*/ +/*! + \var WebSocketProtocol::Version::V_8 + hybi-8, hybi-9, hybi-10, hybi-11 and hybi-12. + Status codes 1005 and 1006 are added and all codes are now unsigned\n + Internal error results in 1006 +*/ +/*! + \var WebSocketProtocol::Version::V_13 + hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and RFC 6455. + Sec-WebSocket-Version = 13\n + Status code 1004 is now reserved\n + Added 1008, 1009 and 1010\n + Must support TLS\n + Clarify multiple version support +*/ +/*! + \var WebSocketProtocol::Version::V_LATEST + Refers to the latest know version to QWebSockets. +*/ + +/*! + \fn WebSocketProtocol::isOpCodeReserved(OpCode code) + Checks if \a code is a valid OpCode + \internal +*/ + +/*! + \fn WebSocketProtocol::isCloseCodeValid(int closeCode) + Checks if \a closeCode is a valid web socket close code + \internal +*/ + +/*! + \fn WebSocketProtocol::getCurrentVersion() + Returns the latest version that WebSocket is supporting + \internal +*/ + +/** + * @brief Contains constants related to the WebSocket standard. + */ +namespace QWebSocketProtocol +{ + /*! + Parses the \a versionString and converts it to a Version value + \internal + */ + Version versionFromString(const QString &versionString) + { + bool ok = false; + Version version = V_Unknow; + int ver = versionString.toInt(&ok); + QSet supportedVersions; + supportedVersions << V_0 << V_4 << V_5 << V_6 << V_7 << V_8 << V_13; + if (ok) + { + if (supportedVersions.contains(static_cast(ver))) + { + version = static_cast(ver); + } + } + return version; + } + + /*! + Mask the \a payload with the given \a maskingKey and stores the result back in \a payload. + \internal + */ + void mask(QByteArray *payload, quint32 maskingKey) + { + quint32 *payloadData = reinterpret_cast(payload->data()); + quint32 numIterations = static_cast(payload->size()) / sizeof(quint32); + quint32 remainder = static_cast(payload->size()) % sizeof(quint32); + quint32 i; + for (i = 0; i < numIterations; ++i) + { + *(payloadData + i) ^= maskingKey; + } + if (remainder) + { + const quint32 offset = i * static_cast(sizeof(quint32)); + char *payloadBytes = payload->data(); + uchar *mask = reinterpret_cast(&maskingKey); + for (quint32 i = 0; i < remainder; ++i) + { + *(payloadBytes + offset + i) ^= static_cast(mask[(i + offset) % 4]); + } + } + } + + /*! + Masks the \a payload of length \a size with the given \a maskingKey and stores the result back in \a payload. + \internal + */ + void mask(char *payload, quint64 size, quint32 maskingKey) + { + quint32 *payloadData = reinterpret_cast(payload); + quint32 numIterations = static_cast(size / sizeof(quint32)); + quint32 remainder = size % sizeof(quint32); + quint32 i; + for (i = 0; i < numIterations; ++i) + { + *(payloadData + i) ^= maskingKey; + } + if (remainder) + { + const quint32 offset = i * static_cast(sizeof(quint32)); + uchar *mask = reinterpret_cast(&maskingKey); + for (quint32 i = 0; i < remainder; ++i) + { + *(payload + offset + i) ^= static_cast(mask[(i + offset) % 4]); + } + } + } +} //end namespace WebSocketProtocol diff --git a/source/qwebsocketprotocol.h b/source/qwebsocketprotocol.h new file mode 100644 index 0000000..a2f39f1 --- /dev/null +++ b/source/qwebsocketprotocol.h @@ -0,0 +1,89 @@ +/** + * @file websocketprotocol.h + * @brief Contains constants related to the WebSocket standard. + * @author Kurt Pattyn (pattyn.kurt@gmail.com) + */ +#ifndef QWEBSOCKETPROTOCOL_H +#define QWEBSOCKETPROTOCOL_H + +class QString; +class QByteArray; +#include + +namespace QWebSocketProtocol +{ + enum Version + { + V_Unknow = -1, + V_0 = 0, + //hybi-01, hybi-02 and hybi-03 not supported + V_4 = 4, + V_5 = 5, + V_6 = 6, + V_7 = 7, + V_8 = 8, + V_13 = 13, + V_LATEST = V_13 + }; + + Version versionFromString(const QString &versionString); + + enum CloseCode + { + CC_NORMAL = 1000, + CC_GOING_AWAY = 1001, + CC_PROTOCOL_ERROR = 1002, + CC_DATATYPE_NOT_SUPPORTED = 1003, + CC_RESERVED_1004 = 1004, + CC_MISSING_STATUS_CODE = 1005, + CC_ABNORMAL_DISCONNECTION = 1006, + CC_WRONG_DATATYPE = 1007, + CC_POLICY_VIOLATED = 1008, + CC_TOO_MUCH_DATA = 1009, + CC_MISSING_EXTENSION = 1010, + CC_BAD_OPERATION = 1011, + CC_TLS_HANDSHAKE_FAILED = 1015 + }; + + enum OpCode + { + OC_CONTINUE = 0x0, + OC_TEXT = 0x1, + OC_BINARY = 0x2, + OC_RESERVED_3 = 0x3, + OC_RESERVED_4 = 0x4, + OC_RESERVED_5 = 0x5, + OC_RESERVED_6 = 0x6, + OC_RESERVED_7 = 0x7, + OC_CLOSE = 0x8, + OC_PING = 0x9, + OC_PONG = 0xA, + OC_RESERVED_B = 0xB, + OC_RESERVED_V = 0xC, + OC_RESERVED_D = 0xD, + OC_RESERVED_E = 0xE, + OC_RESERVED_F = 0xF + }; + + + inline bool isOpCodeReserved(OpCode code) + { + return ((code > OC_BINARY) && (code < OC_CLOSE)) || (code > OC_PONG); + } + inline bool isCloseCodeValid(int closeCode) + { + return (closeCode > 999) && (closeCode < 5000) && + (closeCode != CC_RESERVED_1004) && //see RFC6455 7.4.1 + (closeCode != CC_MISSING_STATUS_CODE) && + (closeCode != CC_ABNORMAL_DISCONNECTION) && + ((closeCode >= 3000) || (closeCode < 1012)); + } + + void mask(QByteArray *payload, quint32 maskingKey); + void mask(char *payload, quint64 size, quint32 maskingKey); + + inline Version currentVersion() { return V_LATEST; } + +} //end namespace QWebSocketProtocol + +#endif // QWEBSOCKETPROTOCOL_H diff --git a/source/qwebsocketserver.cpp b/source/qwebsocketserver.cpp new file mode 100644 index 0000000..9866cef --- /dev/null +++ b/source/qwebsocketserver.cpp @@ -0,0 +1,417 @@ +#include "qwebsocketserver.h" +#include +#include +#include +#include "qwebsocketprotocol.h" +#include "handshakerequest_p.h" +#include "handshakeresponse_p.h" +#include "qwebsocket.h" + +/*! + \class WebSocketServer + + The WebSocketServer class provides a websocket-based server. + It is modeled after QTcpServer, and behaves the same. So, if you know how to use QTcpServer, you know how to use WebSocketServer.\n + This class makes it possible to accept incoming websocket connections.\n + You can specify the port or have WebSocketServer pick one automatically.\n + You can listen on a specific address or on all the machine's addresses.\n + Call listen() to have the server listen for incoming connections.\n + + The newConnection() signal is then emitted each time a client connects to the server.\n + Call nextPendingConnection() to accept the pending connection as a connected WebSocket. + The function returns a pointer to a WebSocket in QAbstractSocket::ConnectedState that you can use for communicating with the client.\n + If an error occurs, serverError() returns the type of error, and errorString() can be called to get a human readable description of what happened.\n + When listening for connections, the address and port on which the server is listening are available as serverAddress() and serverPort().\n + Calling close() makes WebSocketServer stop listening for incoming connections.\n + Although WebSocketServer is mostly designed for use with an event loop, it's possible to use it without one. In that case, you must use waitForNewConnection(), which blocks until either a connection is available or a timeout expires. + + \ref echoserver + + \author Kurt Pattyn (pattyn.kurt@gmail.com) + + \sa WebSocket +*/ + +/*! + \page echoserver WebSocket server example + \brief A sample websocket server echoing back messages sent to it. + + \section Description + The echoserver example implements a web socket server that echoes back everything that is sent to it. + \section Code + We start by creating a WebSocketServer (`new WebSocketServer()`). After the creation, we listen on all local network interfaces (`QHostAddress::Any`) on the specified \a port. + \snippet echoserver.cpp constructor + If listening is successful, we connect the `newConnection()` signal to the slot `onNewConnection()`. + The `newConnection()` signal will be thrown whenever a new web socket client is connected to our server. + + \snippet echoserver.cpp onNewConnection + When a new connection is received, the client WebSocket is retrieved (`nextPendingConnection()`), and the signals we are interested in + are connected to our slots (`textMessageReceived()`, `binaryMessageReceived()` and `disconnected()`). + The client socket is remembered in a list, in case we would like to use it later (in this example, nothing is done with it). + + \snippet echoserver.cpp processMessage + Whenever `processMessage()` is triggered, we retrieve the sender, and if valid, send back the original message (`send()`). + The same is done with binary messages. + \snippet echoserver.cpp processBinaryMessage + The only difference is that the message now is a QByteArray instead of a QString. + + \snippet echoserver.cpp socketDisconnected + Whenever a socket is disconnected, we remove it from the clients list and delete the socket. + Note: it is best to use `deleteLater()` to delete the socket. +*/ + +/*! + \fn void WebSocketServer::newConnection() + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections() and nextPendingConnection(). +*/ + +/*! + Constructs a new WebSocketServer. + + \a parent is passed to the QObject constructor. + */ +QWebSocketServer::QWebSocketServer(const QString &serverName, QObject *parent) : + QObject(parent), + m_pTcpServer(0), + m_serverName(serverName), + m_pendingConnections() +{ + m_pTcpServer = new QTcpServer(this); + connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); +} + +/*! + Destroys the WebSocketServer object. If the server is listening for connections, the socket is automatically closed. + Any client WebSockets that are still connected are closed and deleted. + + \sa close() + */ +QWebSocketServer::~QWebSocketServer() +{ + while (!m_pendingConnections.isEmpty()) + { + QWebSocket *pWebSocket = m_pendingConnections.dequeue(); + pWebSocket->close(QWebSocketProtocol::CC_GOING_AWAY, "Server closed."); + pWebSocket->deleteLater(); + } + m_pTcpServer->deleteLater(); +} + +/*! + Closes the server. The server will no longer listen for incoming connections. + */ +void QWebSocketServer::close() +{ + m_pTcpServer->close(); +} + +/*! + Returns a human readable description of the last error that occurred. + + \sa serverError(). +*/ +QString QWebSocketServer::errorString() const +{ + return m_pTcpServer->errorString(); +} + +/*! + Returns true if the server has pending connections; otherwise returns false. + + \sa nextPendingConnection() and setMaxPendingConnections(). + */ +bool QWebSocketServer::hasPendingConnections() const +{ + return !m_pendingConnections.isEmpty(); +} + +/*! + Returns true if the server is currently listening for incoming connections; otherwise returns false. + + \sa listen(). + */ +bool QWebSocketServer::isListening() const +{ + return m_pTcpServer->isListening(); +} + +/*! + Tells the server to listen for incoming connections on address \a address and port \a port. + If \a port is 0, a port is chosen automatically.\n + If \a address is QHostAddress::Any, the server will listen on all network interfaces. + + Returns true on success; otherwise returns false. + + \sa isListening(). + */ +bool QWebSocketServer::listen(const QHostAddress &address, quint16 port) +{ + return m_pTcpServer->listen(address, port); +} + +/*! + Returns the maximum number of pending accepted connections. The default is 30. + + \sa setMaxPendingConnections() and hasPendingConnections(). + */ +int QWebSocketServer::maxPendingConnections() const +{ + return m_pTcpServer->maxPendingConnections(); +} + +/*! + This function is called to add the socket to the list of pending incoming websocket connections. + + \sa nextPendingConnection() and hasPendingConnections() +*/ +void QWebSocketServer::addPendingConnection(QWebSocket *pWebSocket) +{ + if (m_pendingConnections.size() < maxPendingConnections()) + { + m_pendingConnections.enqueue(pWebSocket); + } +} + +/*! + Returns the next pending connection as a connected WebSocket object. + The socket is created as a child of the server, which means that it is automatically deleted when the WebSocketServer object is destroyed. It is still a good idea to delete the object explicitly when you are done with it, to avoid wasting memory. + 0 is returned if this function is called when there are no pending connections. + + Note: The returned WebSocket object cannot be used from another thread.. + + \sa hasPendingConnections(). +*/ +QWebSocket *QWebSocketServer::nextPendingConnection() +{ + QWebSocket *pWebSocket = 0; + if (!m_pendingConnections.isEmpty()) + { + pWebSocket = m_pendingConnections.dequeue(); + } + return pWebSocket; +} + +/*! + Returns the network proxy for this socket. By default QNetworkProxy::DefaultProxy is used. + + \sa setProxy(). +*/ +QNetworkProxy QWebSocketServer::proxy() const +{ + return m_pTcpServer->proxy(); +} + +/*! + Returns the server's address if the server is listening for connections; otherwise returns QHostAddress::Null. + + \sa serverPort() and listen(). + */ +QHostAddress QWebSocketServer::serverAddress() const +{ + return m_pTcpServer->serverAddress(); +} + +/*! + Returns an error code for the last error that occurred. + \sa errorString(). + */ +QAbstractSocket::SocketError QWebSocketServer::serverError() const +{ + return m_pTcpServer->serverError(); +} + +/*! + Returns the server's port if the server is listening for connections; otherwise returns 0. + \sa serverAddress() and listen(). + */ +quint16 QWebSocketServer::serverPort() const +{ + return m_pTcpServer->serverPort(); +} + +/*! + Sets the maximum number of pending accepted connections to \a numConnections. + WebSocketServer will accept no more than \a numConnections incoming connections before nextPendingConnection() is called.\n + By default, the limit is 30 pending connections. + + Clients may still able to connect after the server has reached its maximum number of pending connections (i.e., WebSocket can still emit the connected() signal). WebSocketServer will stop accepting the new connections, but the operating system may still keep them in queue. + \sa maxPendingConnections() and hasPendingConnections(). + */ +void QWebSocketServer::setMaxPendingConnections(int numConnections) +{ + m_pTcpServer->setMaxPendingConnections(numConnections); +} + +/*! + \brief Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the QNetworkProxy::NoProxy proxy type: + + \code + server->setProxy(QNetworkProxy::NoProxy); + \endcode + + \sa proxy(). +*/ +void QWebSocketServer::setProxy(const QNetworkProxy &networkProxy) +{ + m_pTcpServer->setProxy(networkProxy); +} + +/*! + Sets the socket descriptor this server should use when listening for incoming connections to \a socketDescriptor. + + Returns true if the socket is set successfully; otherwise returns false.\n + The socket is assumed to be in listening state. + + \sa socketDescriptor() and isListening(). + */ +bool QWebSocketServer::setSocketDescriptor(int socketDescriptor) +{ + return m_pTcpServer->setSocketDescriptor(socketDescriptor); +} + +/*! + Returns the native socket descriptor the server uses to listen for incoming instructions, or -1 if the server is not listening. + If the server is using QNetworkProxy, the returned descriptor may not be usable with native socket functions. + + \sa setSocketDescriptor() and isListening(). + */ +int QWebSocketServer::socketDescriptor() const +{ + return m_pTcpServer->socketDescriptor(); +} + +/*! + Waits for at most \a msec milliseconds or until an incoming connection is available. + Returns true if a connection is available; otherwise returns false. + If the operation timed out and \a timedOut is not 0, \a *timedOut will be set to true. + + \note This is a blocking function call. + \note Its use is disadvised in a single-threaded GUI application, since the whole application will stop responding until the function returns. waitForNewConnection() is mostly useful when there is no event loop available. + \note The non-blocking alternative is to connect to the newConnection() signal. + + If \a msec is -1, this function will not time out. + + \sa hasPendingConnections() and nextPendingConnection(). +*/ +bool QWebSocketServer::waitForNewConnection(int msec, bool *timedOut) +{ + return m_pTcpServer->waitForNewConnection(msec, timedOut); +} + +/*! + Returns a list of websocket versions that this server is supporting. + */ +QList QWebSocketServer::supportedVersions() const +{ + QList supportedVersions; + supportedVersions << QWebSocketProtocol::currentVersion(); //we only support V13 + return supportedVersions; +} + +/*! + Returns a list of websocket subprotocols that this server supports. + */ +QList QWebSocketServer::supportedProtocols() const +{ + QList supportedProtocols; + return supportedProtocols; //no protocols are currently supported +} + +/*! + Returns a list of websocket extensions that this server supports. + */ +QList QWebSocketServer::supportedExtensions() const +{ + QList supportedExtensions; + return supportedExtensions; //no extensions are currently supported +} + +//Checking on the origin does not make much sense when the server is accessed +//via a non-browser client, as that client can set whatever origin header it likes +//In case of a browser client, the server SHOULD check the validity of the origin +//see http://tools.ietf.org/html/rfc6455#section-10 +bool QWebSocketServer::isOriginAllowed(const QString &origin) const +{ + Q_UNUSED(origin) + return true; +} + +void QWebSocketServer::onNewConnection() +{ + QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection(); + connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); +} + +void QWebSocketServer::onCloseConnection() +{ + QTcpSocket *pTcpSocket = qobject_cast(sender()); + if (pTcpSocket != 0) + { + pTcpSocket->close(); + } +} + +void QWebSocketServer::handshakeReceived() +{ + QTcpSocket *pTcpSocket = qobject_cast(sender()); + if (pTcpSocket != 0) + { + bool success = false; + bool isSecure = false; + HandshakeRequest request(pTcpSocket->peerPort(), isSecure); + QTextStream textStream(pTcpSocket); + textStream >> request; + + HandshakeResponse response(request, + m_serverName, + isOriginAllowed(request.getOrigin()), + supportedVersions(), + supportedProtocols(), + supportedExtensions()); + disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); + + if (response.isValid()) + { + QTextStream httpStream(pTcpSocket); + httpStream << response; + httpStream.flush(); + + if (response.canUpgrade()) + { + QWebSocket *pWebSocket = QWebSocket::upgradeFrom(pTcpSocket, request, response); + if (pWebSocket) + { + pWebSocket->setParent(this); + addPendingConnection(pWebSocket); + Q_EMIT newConnection(); + success = true; + } + else + { + qDebug() << "WebSocketServer::handshakeReceived: Upgrading to WebSocket failed."; + } + } + else + { + qDebug() << "WebSocketServer::handshakeReceived: Cannot upgrade to websocket."; + } + } + else + { + qDebug() << "WebSocketServer::handshakeReceived: Invalid response. This should not happen!!!"; + } + if (!success) + { + qDebug() << "WebSocketServer::handshakeReceived: Closing socket because of invalid or unsupported request"; + pTcpSocket->close(); + } + } + else + { + qDebug() << "WebSocketServerImp::handshakeReceived: Sender socket is NULL. This should not happen!!!"; + } +} diff --git a/source/qwebsocketserver.h b/source/qwebsocketserver.h new file mode 100644 index 0000000..136c839 --- /dev/null +++ b/source/qwebsocketserver.h @@ -0,0 +1,68 @@ +/** + * @file websocketserver.h + * @author Kurt Pattyn (pattyn.kurt@gmail.com) + * @brief Defines the WebSocketServer class. + */ + +#ifndef QWEBSOCKETSERVER_H +#define QWEBSOCKETSERVER_H + +#include +#include +#include +#include +#include "qwebsocket.h" + +class QTcpServer; + +class QWebSocketServer : public QObject +{ + Q_OBJECT + +public: + explicit QWebSocketServer(const QString &serverName, QObject *parent = 0); + virtual ~QWebSocketServer(); + + void close(); + QString errorString() const; + bool hasPendingConnections() const; + bool isListening() const; + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + int maxPendingConnections() const; + virtual QWebSocket *nextPendingConnection(); + QNetworkProxy proxy() const; + QHostAddress serverAddress() const; + QAbstractSocket::SocketError serverError() const; + quint16 serverPort() const; + void setMaxPendingConnections(int numConnections); + void setProxy(const QNetworkProxy &networkProxy); + bool setSocketDescriptor(int socketDescriptor); + int socketDescriptor() const; + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + + QList supportedVersions() const; + QList supportedProtocols() const; + QList supportedExtensions() const; + +protected: + virtual bool isOriginAllowed(const QString &origin) const; + +Q_SIGNALS: + void newConnection(); + +private Q_SLOTS: + void onNewConnection(); + void onCloseConnection(); + void handshakeReceived(); + +private: + Q_DISABLE_COPY(QWebSocketServer) + + QTcpServer *m_pTcpServer; + QString m_serverName; + QQueue m_pendingConnections; + + void addPendingConnection(QWebSocket *pWebSocket); +}; + +#endif // QWEBSOCKETSERVER_H diff --git a/source/qwebsocketsglobal.h b/source/qwebsocketsglobal.h new file mode 100644 index 0000000..822f41b --- /dev/null +++ b/source/qwebsocketsglobal.h @@ -0,0 +1,24 @@ +#ifndef QWEBSOCKETSGLOBAL_H +#define QWEBSOCKETSGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_SERIALPORT_LIB) +# define Q_WEBSOCKETS_EXPORT Q_DECL_EXPORT +# else +# define Q_WEBSOCKETS_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_WEBSOCKETS_EXPORT +#endif + +// The macro has been available only since Qt 5.0 +#ifndef Q_DECL_OVERRIDE +#define Q_DECL_OVERRIDE +#endif + +QT_END_NAMESPACE +#endif // QWEBSOCKETSGLOBAL_H diff --git a/source/websocket.cpp b/source/websocket.cpp deleted file mode 100644 index 1eaf240..0000000 --- a/source/websocket.cpp +++ /dev/null @@ -1,1352 +0,0 @@ -#include "websocket.h" -#include "handshakerequest_p.h" -#include "handshakeresponse_p.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -/*! - \class WebSocket - \brief The class WebSocket implements a TCP socket that talks the websocket protocol. - - WebSockets is a web technology providing full-duplex communications channels over a single TCP connection. - The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011 (see http://tools.ietf.org/html/rfc6455). - It can both be used in a client application and server application. - - This class was modeled after QAbstractSocket. - - \ref echoclient - - \author Kurt Pattyn (pattyn.kurt@gmail.com) -*/ -/*! - \page echoclient WebSocket client example - \brief A sample websocket client that sends a message and displays the message that it receives back. - - \section Description - The EchoClient example implements a web socket client that sends a message to a websocket server and dumps the answer that it gets back. - This example should ideally be used with the EchoServer example. - \section Code - We start by connecting to the `connected()` signal. - \snippet echoclient.cpp constructor - After the connection, we open the socket to the given \a url. - - \snippet echoclient.cpp onConnected - When the client is connected successfully, we connect to the `onTextMessageReceived()` signal, and send out "Hello, world!". - If connected with the EchoServer, we will receive the same message back. - - \snippet echoclient.cpp onTextMessageReceived - Whenever a message is received, we write it out. -*/ - -/*! - \fn void WebSocket::connected() - \brief Emitted when a connection is successfully established. - \sa open(), disconnected() -*/ -/*! - \fn void WebSocket::disconnected() - \brief Emitted when the socket is disconnected. - \sa close(), connected() -*/ -/*! - \fn void WebSocket::aboutToClose() - - This signal is emitted when the socket is about to close. - Connect this signal if you have operations that need to be performed before the socket closes - (e.g., if you have data in a separate buffer that needs to be written to the device). - - \sa close() - */ -/*! -\fn void WebSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) - -This signal can be emitted when a \a proxy that requires -authentication is used. The \a authenticator object can then be -filled in with the required details to allow authentication and -continue the connection. - -\note It is not possible to use a QueuedConnection to connect to -this signal, as the connection will fail if the authenticator has -not been filled in with new information when the signal returns. - -\sa QAuthenticator, QNetworkProxy -*/ -/*! - \fn void WebSocket::stateChanged(QAbstractSocket::SocketState state); - - This signal is emitted whenever WebSocket's state changes. - The \a socketState parameter is the new state. - - QAbstractSocket::SocketState is not a registered metatype, so for queued - connections, you will have to register it with Q_REGISTER_METATYPE() and - qRegisterMetaType(). - - \sa state() -*/ -/*! - \fn void WebSocket::readChannelFinished() - - This signal is emitted when the input (reading) stream is closed in this device. It is emitted as soon as the closing is detected. - - \sa close() -*/ - -/*! - \fn void WebSocket::textFrameReceived(QString frame, bool isLastFrame); - - This signal is emitted whenever a text frame is received. The \a frame contains the data and - \a isLastFrame indicates whether this is the last frame of the complete message. - - This signal can be used to process large messages frame by frame, instead of waiting for the complete - message to arrive. - - \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) -*/ -/*! - \fn void WebSocket::binaryFrameReceived(QByteArray frame, bool isLastFrame); - - This signal is emitted whenever a binary frame is received. The \a frame contains the data and - \a isLastFrame indicates whether this is the last frame of the complete message. - - This signal can be used to process large messages frame by frame, instead of waiting for the complete - message to arrive. - - \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) -*/ -/*! - \fn void WebSocket::textMessageReceived(QString message); - - This signal is emitted whenever a text message is received. The \a message contains the received text. - - \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) -*/ -/*! - \fn void WebSocket::binaryMessageReceived(QByteArray message); - - This signal is emitted whenever a binary message is received. The \a message contains the received bytes. - - \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) -*/ -/*! - \fn void WebSocket::error(QAbstractSocket::SocketError error); - - This signal is emitted after an error occurred. The \a socketError - parameter describes the type of error that occurred. - - QAbstractSocket::SocketError is not a registered metatype, so for queued - connections, you will have to register it with Q_DECLARE_METATYPE() and - qRegisterMetaType(). - - \sa error(), errorString() -*/ -/*! - \fn void WebSocket::pong(quint64 elapsedTime) - - Emitted when a pong message is received in reply to a previous ping. - - \sa ping() - */ - -const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message - -/*! - * \brief Creates a new WebSocket with the given \a origin, the \a version of the protocol to use and \a parent. - * - * The \a origin of the client is as specified in http://tools.ietf.org/html/rfc6454. - * (The \a origin is not required for non-web browser clients (see RFC 6455)). - * \note Currently only V13 (RFC 6455) is supported - */ -WebSocket::WebSocket(QString origin, WebSocketProtocol::Version version, QObject *parent) : - QObject(parent), - 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() -{ - makeConnections(m_pSocket); - qsrand(static_cast(QDateTime::currentMSecsSinceEpoch())); -} - -//only called by upgradeFrom -/*! - \internal - Constructor used for the server implementation. Should never be called directly. - - pTcpSocket The tcp socket to use for this websocket - version The version of the protocol to speak (currently only V13 is supported) - parent The parent object of the WebSocket object -*/ -WebSocket::WebSocket(QTcpSocket *pTcpSocket, WebSocketProtocol::Version version, QObject *parent) : - QObject(parent), - 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() -{ - makeConnections(m_pSocket); -} - -/*! - * \brief Destroys the WebSocket. Closes the socket if it is still open, and releases any used resources. - */ -WebSocket::~WebSocket() -{ - if (state() == QAbstractSocket::ConnectedState) - { - close(WebSocketProtocol::CC_GOING_AWAY, "Connection closed"); - } - releaseConnections(m_pSocket); - m_pSocket->deleteLater(); - m_pSocket = 0; -} - -/*! - * \brief Aborts the current socket and resets the socket. Unlike close(), this function immediately closes the socket, discarding any pending data in the write buffer. - */ -void WebSocket::abort() -{ - m_pSocket->abort(); -} - -/*! - * Returns the type of error that last occurred - * \sa errorString() - */ -QAbstractSocket::SocketError WebSocket::error() const -{ - return m_pSocket->error(); -} - -/*! - * Returns a human-readable description of the last error that occurred - * - * \sa error() - */ -QString WebSocket::errorString() const -{ - if (!m_errorString.isEmpty()) - { - return m_errorString; - } - else - { - return m_pSocket->errorString(); - } -} - -/*! - This function writes as much as possible from the internal write buffer to the underlying network socket, without blocking. - If any data was written, this function returns true; otherwise false is returned. - Call this function if you need WebSocket to start sending buffered data immediately. - The number of bytes successfully written depends on the operating system. - In most cases, you do not need to call this function, because WebSocket will start sending data automatically once control goes back to the event loop. - In the absence of an event loop, call waitForBytesWritten() instead. - - \sa send() and waitForBytesWritten(). -*/ -bool WebSocket::flush() -{ - return m_pSocket->flush(); -} - -/*! - * Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. - * \param message Text message to be sent. Must be '\0' terminated. - * \return The number of bytes actually sent. - * \sa send(const QString &message) - */ -qint64 WebSocket::send(const char *message) -{ - return send(QString::fromUtf8(message)); -} - -/** - * @brief Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. - * @param message The message to be sent - * @return The number of bytes actually sent. - */ -qint64 WebSocket::send(const QString &message) -{ - return doWriteData(message.toUtf8(), false); -} - -/** - * @brief Sends the given \a data over the socket as a binary message and returns the number of bytes actually sent. - * @param data The binary data to be sent. - * @return The number of bytes actually sent. - */ -qint64 WebSocket::send(const QByteArray &data) -{ - return doWriteData(data, true); -} - -/*! - \internal - */ -WebSocket *WebSocket::upgradeFrom(QTcpSocket *pTcpSocket, - const HandshakeRequest &request, - const HandshakeResponse &response, - QObject *parent) -{ - WebSocket *pWebSocket = new WebSocket(pTcpSocket, response.getAcceptedVersion(), parent); - pWebSocket->setExtension(response.getAcceptedExtension()); - pWebSocket->setOrigin(request.getOrigin()); - pWebSocket->setRequestUrl(request.getRequestUrl()); - pWebSocket->setProtocol(response.getAcceptedProtocol()); - pWebSocket->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); - pWebSocket->enableMasking(false); //a server should not send masked frames - - return pWebSocket; -} - -/*! - * \brief Gracefully closes the socket with the given \a closeCode and \a reason. Any data in the write buffer is flushed before the socket is closed. - * \param closeCode The WebSocketProtocol::CloseCode indicating the reason to close. - * \param reason A string describing the error more in detail - */ -void WebSocket::close(WebSocketProtocol::CloseCode closeCode, QString reason) -{ - if (!m_isClosingHandshakeSent) - { - quint32 maskingKey = 0; - if (m_mustMask) - { - maskingKey = generateMaskingKey(); - } - quint16 code = qToBigEndian(closeCode); - QByteArray payload; - payload.append(static_cast(static_cast(&code)), 2); - if (!reason.isEmpty()) - { - payload.append(reason.toUtf8()); - } - if (m_mustMask) - { - WebSocketProtocol::mask(payload.data(), payload.size(), maskingKey); - } - QByteArray frame = getFrameHeader(WebSocketProtocol::OC_CLOSE, payload.size(), maskingKey, true); - frame.append(payload); - m_pSocket->write(frame); - m_pSocket->flush(); - - m_isClosingHandshakeSent = true; - - Q_EMIT aboutToClose(); - } - m_pSocket->close(); -} - -/*! - * \brief Opens a websocket connection using the given \a url. - * If \a mask is true, all frames will be masked; this is only necessary for client side sockets; servers should never mask - * \param url The url to connect to - * \param mask When true, all frames are masked - * \note A client socket must *always* mask its frames; servers may *never* mask its frames - */ -void WebSocket::open(const QUrl &url, bool mask) -{ - m_dataProcessor.clear(); - m_isClosingHandshakeReceived = false; - m_isClosingHandshakeSent = false; - - setRequestUrl(url); - QString resourceName = url.path() + url.query(); - if (resourceName.isEmpty()) - { - resourceName = "/"; - } - setResourceName(resourceName); - enableMasking(mask); - - setSocketState(QAbstractSocket::ConnectingState); - - m_pSocket->connectToHost(url.host(), url.port(80)); -} - -/*! - * \brief Pings the server to indicate that the connection is still alive. - * - * \sa pong() - */ -void WebSocket::ping() -{ - m_pingTimer.restart(); - QByteArray pingFrame = getFrameHeader(WebSocketProtocol::OC_PING, 0, 0, true); - writeFrame(pingFrame); -} - -/*! - \internal - Sets the version to use for the websocket protocol; this must be set before the socket is opened. -*/ -void WebSocket::setVersion(WebSocketProtocol::Version version) -{ - m_version = version; -} - -/*! - \internal - Sets the resource name of the connection; must be set before the socket is openend -*/ -void WebSocket::setResourceName(QString resourceName) -{ - m_resourceName = resourceName; -} - -/*! - \internal - */ -void WebSocket::setRequestUrl(QUrl requestUrl) -{ - m_requestUrl = requestUrl; -} - -/*! - \internal - */ -void WebSocket::setOrigin(QString origin) -{ - m_origin = origin; -} - -/*! - \internal - */ -void WebSocket::setProtocol(QString protocol) -{ - m_protocol = protocol; -} - -/*! - \internal - */ -void WebSocket::setExtension(QString extension) -{ - m_extension = extension; -} - -/*! - \internal - */ -void WebSocket::enableMasking(bool enable) -{ - m_mustMask = enable; -} - -/*! - * \internal - */ -qint64 WebSocket::doWriteData(const QByteArray &data, bool isBinary) -{ - return doWriteFrames(data, isBinary); -} - -/*! - * \internal - */ -void WebSocket::makeConnections(const QTcpSocket *pTcpSocket) -{ - //pass through signals - connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); - connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); - connect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); - //connect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); - //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, 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(controlFrameReceived(WebSocketProtocol::OpCode, QByteArray)), this, SLOT(processControlFrame(WebSocketProtocol::OpCode, QByteArray))); - connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); - connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); - connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); - connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); - connect(&m_dataProcessor, SIGNAL(errorEncountered(WebSocketProtocol::CloseCode,QString)), this, SLOT(close(WebSocketProtocol::CloseCode,QString))); -} - -/*! - * \internal - */ -void WebSocket::releaseConnections(const QTcpSocket *pTcpSocket) -{ - if (pTcpSocket) - { - //pass through signals - disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); - disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); - disconnect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); - //disconnect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); - //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, 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(controlFrameReceived(WebSocketProtocol::OpCode,QByteArray)), this, SLOT(processControlFrame(WebSocketProtocol::OpCode,QByteArray))); - disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); - disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); - disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); - disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); - disconnect(&m_dataProcessor, SIGNAL(errorEncountered(WebSocketProtocol::CloseCode,QString)), this, SLOT(close(WebSocketProtocol::CloseCode,QString))); -} - -/*! - * \brief Returns the version the socket is currently using - */ -WebSocketProtocol::Version WebSocket::version() -{ - return m_version; -} - -/** - * @brief Returns the name of the resource currently accessed. - */ -QString WebSocket::resourceName() -{ - return m_resourceName; -} - -/*! - * \brief Returns the url the socket is connected to or will connect to. - */ -QUrl WebSocket::requestUrl() -{ - return m_requestUrl; -} - -/*! - Returns the current origin - */ -QString WebSocket::origin() -{ - return m_origin; -} - -/*! - Returns the currently used protocol. - */ -QString WebSocket::protocol() -{ - return m_protocol; -} - -/*! - Returns the currently used extension. - */ -QString WebSocket::extension() -{ - return m_extension; -} - -/*! - * \internal - */ -QByteArray WebSocket::getFrameHeader(WebSocketProtocol::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((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00)); //FIN, opcode - //RSV-1, RSV-2 and RSV-3 are zero - header.append(static_cast(byte)); - - //Now write the masking bit and the payload length byte - byte = 0x00; - if (maskingKey != 0) - { - byte |= 0x80; - } - if (payloadLength <= 125) - { - byte |= static_cast(payloadLength); - header.append(static_cast(byte)); - } - else if (payloadLength <= 0xFFFFU) - { - byte |= 126; - header.append(static_cast(byte)); - quint16 swapped = qToBigEndian(static_cast(payloadLength)); - header.append(static_cast(static_cast(&swapped)), 2); - } - else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) - { - byte |= 127; - header.append(static_cast(byte)); - quint64 swapped = qToBigEndian(payloadLength); - header.append(static_cast(static_cast(&swapped)), 8); - } - - //Write mask - if (maskingKey != 0) - { - header.append(static_cast(static_cast(&maskingKey)), sizeof(quint32)); - } - } - else - { - //setErrorString("WebSocket::getHeader: payload too big!"); - //Q_EMIT error(QAbstractSocket::DatagramTooLargeError); - qDebug() << "WebSocket::getHeader: payload too big!"; - } - - return header; -} - -/*! - * \internal - */ -qint64 WebSocket::doWriteFrames(const QByteArray &data, bool isBinary) -{ - const WebSocketProtocol::OpCode firstOpCode = isBinary ? WebSocketProtocol::OC_BINARY : WebSocketProtocol::OC_TEXT; - - int numFrames = data.size() / FRAME_SIZE_IN_BYTES; - QByteArray tmpData(data); - tmpData.detach(); - char *payload = tmpData.data(); - quint64 sizeLeft = static_cast(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); - WebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : WebSocketProtocol::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) - { - WebSocketProtocol::mask(currentData, size, maskingKey); - } - qint64 written = m_pSocket->write(currentData, static_cast(size)); - if (written > 0) - { - bytesWritten += written; - payloadWritten += written; - } - else - { - setErrorString("WebSocket::doWriteFrames: Error writing bytes to socket: " + m_pSocket->errorString()); - qDebug() << errorString(); - m_pSocket->flush(); - Q_EMIT error(QAbstractSocket::NetworkError); - break; - } - } - currentPosition += size; - bytesLeft -= size; - } - if (payloadWritten != data.size()) - { - setErrorString("Bytes written " + QString::number(payloadWritten) + " != " + QString::number(data.size())); - qDebug() << errorString(); - Q_EMIT error(QAbstractSocket::NetworkError); - } - return payloadWritten; -} - -/*! - * \internal - */ -quint32 WebSocket::generateRandomNumber() const -{ - return static_cast((static_cast(qrand()) / RAND_MAX) * std::numeric_limits::max()); -} - -/*! - \internal - */ -quint32 WebSocket::generateMaskingKey() const -{ - return generateRandomNumber(); -} - -/*! - \internal - */ -QByteArray WebSocket::generateKey() const -{ - QByteArray key; - - for (int i = 0; i < 4; ++i) - { - quint32 tmp = generateRandomNumber(); - key.append(static_cast(static_cast(&tmp)), sizeof(quint32)); - } - - return key.toBase64(); -} - - -/*! - \internal - */ -QString WebSocket::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 WebSocket::writeFrames(const QList &frames) -{ - qint64 written = 0; - for (int i = 0; i < frames.size(); ++i) - { - written += writeFrame(frames[i]); - } - return written; -} - -/*! - \internal - */ -qint64 WebSocket::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 WebSocket::processHandshake(QTcpSocket *pSocket) -{ - 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 = "WebSocket::processHandshake: Invalid statusline in response: " + statusLine; - } - else - { - QString headerLine = readLine(pSocket); - QMap 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 = "WebSocket::processHandshake: Accept-Key received from server " + acceptKey + " does not match the client key " + accept; - } - } - else - { - errorDescription = "WebSocket::processHandshake: Invalid statusline in response: " + 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(WebSocketProtocol::currentVersion()))) - { - //if needed to switch protocol version, then we are finished here - //because we cannot handle other protocols than the RFC one (v13) - errorDescription = "WebSocket::processHandshake: Server requests a version that we don't support: " + versions.join(", "); - ok = false; - } - else - { - //we tried v13, but something different went wrong - errorDescription = "WebSocket::processHandshake: Unknown error condition encountered. Aborting connection."; - ok = false; - } - } - } - else - { - errorDescription = "WebSocket::processHandshake: Unhandled http status code " + QString::number(httpStatusCode); - ok = false; - } - - if (!ok) - { - qDebug() << errorDescription; - setErrorString(errorDescription); - Q_EMIT error(QAbstractSocket::ConnectionRefusedError); - } - else - { - //handshake succeeded - setSocketState(QAbstractSocket::ConnectedState); - Q_EMIT connected(); - } - } -} - -/*! - \internal - */ -void WebSocket::processStateChanged(QAbstractSocket::SocketState socketState) -{ - 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 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 WebSocket::processData() -{ - while (m_pSocket->bytesAvailable()) - { - if (state() == QAbstractSocket::ConnectingState) - { - processHandshake(m_pSocket); - } - else - { - m_dataProcessor.process(m_pSocket); - } - } -} - -/*! - \internal - */ -void WebSocket::processControlFrame(WebSocketProtocol::OpCode opCode, QByteArray frame) -{ - switch (opCode) - { - case WebSocketProtocol::OC_PING: - { - quint32 maskingKey = 0; - if (m_mustMask) - { - maskingKey = generateMaskingKey(); - } - m_pSocket->write(getFrameHeader(WebSocketProtocol::OC_PONG, frame.size(), maskingKey, true)); - if (frame.size() > 0) - { - if (m_mustMask) - { - WebSocketProtocol::mask(&frame, maskingKey); - } - m_pSocket->write(frame); - } - break; - } - case WebSocketProtocol::OC_PONG: - { - Q_EMIT pong(static_cast(m_pingTimer.elapsed())); - break; - } - case WebSocketProtocol::OC_CLOSE: - { - quint16 closeCode = WebSocketProtocol::CC_NORMAL; - QString closeReason; - if (frame.size() > 0) //close frame can have a close code and reason - { - closeCode = qFromBigEndian(reinterpret_cast(frame.constData())); - if (!WebSocketProtocol::isCloseCodeValid(closeCode)) - { - closeCode = WebSocketProtocol::CC_PROTOCOL_ERROR; - closeReason = QString("Invalid close code %1 detected").arg(closeCode); - } - else - { - if (frame.size() > 2) - { - QTextCodec *tc = QTextCodec::codecForName("UTF-8"); - QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); - closeReason = tc->toUnicode(frame.constData() + 2, frame.size() - 2, &state); - bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); - if (failed) - { - closeCode = WebSocketProtocol::CC_WRONG_DATATYPE; - closeReason = "Invalid UTF-8 code encountered."; - } - } - } - } - m_isClosingHandshakeReceived = true; - close(static_cast(closeCode), closeReason); - break; - } - case WebSocketProtocol::OC_CONTINUE: - case WebSocketProtocol::OC_BINARY: - case WebSocketProtocol::OC_TEXT: - case WebSocketProtocol::OC_RESERVED_3: - case WebSocketProtocol::OC_RESERVED_4: - case WebSocketProtocol::OC_RESERVED_5: - case WebSocketProtocol::OC_RESERVED_6: - case WebSocketProtocol::OC_RESERVED_7: - case WebSocketProtocol::OC_RESERVED_B: - case WebSocketProtocol::OC_RESERVED_D: - case WebSocketProtocol::OC_RESERVED_E: - case WebSocketProtocol::OC_RESERVED_F: - case WebSocketProtocol::OC_RESERVED_V: - { - //do nothing - //case added to make C++ compiler happy - break; - } - default: - { - qDebug() << "WebSocket::processData: Invalid opcode detected:" << static_cast(opCode); - //Do nothing - break; - } - } -} - -/*! - \internal - */ -QString WebSocket::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(WebSocketProtocol::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"); -} - -/*! - Returns the current state of the socket - */ -QAbstractSocket::SocketState WebSocket::state() const -{ - return m_socketState; -} - -/** - @brief Waits until the socket is connected, up to \a msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error. - The following example waits up to one second for a connection to be established: - - ~~~{.cpp} - socket->open("ws://localhost:1234", false); - if (socket->waitForConnected(1000)) - { - qDebug("Connected!"); - } - ~~~ - - If \a msecs is -1, this function will not time out. - @note This function may wait slightly longer than msecs, depending on the time it takes to complete the host lookup. - @note Multiple calls to this functions do not accumulate the time. If the function times out, the connecting process will be aborted. - - \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is connected. - - \sa connected(), open(), state() - */ -bool WebSocket::waitForConnected(int msecs) -{ - bool retVal = false; - if (m_pSocket) - { - retVal = m_pSocket->waitForConnected(msecs); - } - return retVal; -} - -/*! - Waits \a msecs for the socket to be disconnected. - If the socket was successfully disconnected within time, this method returns true. - Otherwise false is returned. - - \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is disconnected. - - \sa close(), state() -*/ -bool WebSocket::waitForDisconnected(int msecs) -{ - bool retVal = true; - if (m_pSocket) - { - retVal = m_pSocket->waitForDisconnected(msecs); - } - return retVal; -} - -/*! - \internal - Sets the internal socket state -*/ -void WebSocket::setSocketState(QAbstractSocket::SocketState state) -{ - if (m_socketState != state) - { - m_socketState = state; - Q_EMIT stateChanged(m_socketState); - } -} - -/*! - \internal - Sets the error string. - Only used internally. -*/ -void WebSocket::setErrorString(QString errorString) -{ - m_errorString = errorString; -} - -/*! - Returns the local address - */ -QHostAddress WebSocket::localAddress() const -{ - QHostAddress address; - if (m_pSocket) - { - address = m_pSocket->localAddress(); - } - return address; -} - -/*! - Returns the local port - */ -quint16 WebSocket::localPort() const -{ - quint16 port = 0; - if (m_pSocket) - { - port = m_pSocket->localPort(); - } - return port; -} - -/*! - Returns the peer address - */ -QHostAddress WebSocket::peerAddress() const -{ - QHostAddress peer; - if (m_pSocket) - { - peer = m_pSocket->peerAddress(); - } - return peer; -} - -/*! - Returns the peerName - */ -QString WebSocket::peerName() const -{ - QString name; - if (m_pSocket) - { - name = m_pSocket->peerName(); - } - return name; -} - -/*! - Returns the peerport - */ -quint16 WebSocket::peerPort() const -{ - quint16 port = 0; - if (m_pSocket) - { - port = m_pSocket->peerPort(); - } - return port; -} - -/*! - * Returns the currently configured proxy - */ -QNetworkProxy WebSocket::proxy() const -{ - QNetworkProxy proxy; - if (m_pSocket) - { - proxy = m_pSocket->proxy(); - } - return proxy; -} - -/*! - * Returns the size in bytes of the readbuffer that is used by the socket. - */ -qint64 WebSocket::readBufferSize() const -{ - qint64 readBuffer = 0; - if (m_pSocket) - { - readBuffer = m_pSocket->readBufferSize(); - } - return readBuffer; -} - -/*! - Sets the proxy to \a networkProxy - */ -void WebSocket::setProxy(const QNetworkProxy &networkProxy) -{ - if (m_pSocket) - { - m_pSocket->setProxy(networkProxy); - } -} - -/** - Sets the size of WebSocket's internal read buffer to be size bytes. - If the buffer size is limited to a certain size, WebSocket won't buffer more than this size of data. - Exceptionally, a buffer size of 0 means that the read buffer is unlimited and all incoming data is buffered. This is the default. - This option is useful if you only read the data at certain points in time (e.g., in a real-time streaming application) or if you want to protect your socket against receiving too much data, which may eventually cause your application to run out of memory. - \sa readBufferSize() and read(). -*/ -void WebSocket::setReadBufferSize(qint64 size) -{ - if (m_pSocket) - { - m_pSocket->setReadBufferSize(size); - } -} - -/*! - Sets the given \a option to the value described by \a value. - \sa socketOption(). -*/ -void WebSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) -{ - if (m_pSocket) - { - m_pSocket->setSocketOption(option, value); - } -} - -/*! - Returns the value of the option \a option. - \sa setSocketOption(). -*/ -QVariant WebSocket::socketOption(QAbstractSocket::SocketOption option) -{ - QVariant result; - if (m_pSocket) - { - result = m_pSocket->socketOption(option); - } - return result; -} - -/*! - Returns true if the WebSocket is valid. - */ -bool WebSocket::isValid() -{ - bool valid = false; - if (m_pSocket) - { - valid = m_pSocket->isValid(); - } - return valid; -} diff --git a/source/websocket.h b/source/websocket.h deleted file mode 100644 index 10e9676..0000000 --- a/source/websocket.h +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @file websocket.h - * @brief Defines the WebSocket class. - * - * \note Currently, only V13 (RFC6455) is supported. - * \note Both text and binary websockets are supported. - * \note The secure version (wss) is currently not implemented. - * @author Kurt Pattyn (pattyn.kurt@gmail.com) - */ - -#ifndef WEBSOCKET_H -#define WEBSOCKET_H - -#include -#include -#include -#include "websocketprotocol.h" -#include "dataprocessor_p.h" -#include -#include - -class HandshakeRequest; -class HandshakeResponse; -class QTcpSocket; - -class WebSocket:public QObject -{ - Q_OBJECT - -public: - explicit WebSocket(QString origin = QString(), WebSocketProtocol::Version version = WebSocketProtocol::V_LATEST, QObject *parent = 0); - virtual ~WebSocket(); - - void abort(); - QAbstractSocket::SocketError error() const; - QString errorString() const; - bool flush(); - bool isValid(); - QHostAddress localAddress() const; - quint16 localPort() const; - QHostAddress peerAddress() const; - QString peerName() const; - quint16 peerPort() const; - QNetworkProxy proxy() const; - qint64 readBufferSize() const; - void setProxy(const QNetworkProxy &networkProxy); - void setReadBufferSize(qint64 size); - void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); - QVariant socketOption(QAbstractSocket::SocketOption option); - QAbstractSocket::SocketState state() const; - - bool waitForConnected(int msecs = 30000); - bool waitForDisconnected(int msecs = 30000); - - WebSocketProtocol::Version version(); - QString resourceName(); - QUrl requestUrl(); - QString origin(); - QString protocol(); - QString extension(); - - qint64 send(const char *message); - qint64 send(const QString &message); //send data as text - qint64 send(const QByteArray &data); //send data as binary - -public Q_SLOTS: - virtual void close(WebSocketProtocol::CloseCode closeCode = WebSocketProtocol::CC_NORMAL, QString reason = QString()); - virtual void open(const QUrl &url, bool mask = true); - void ping(); - -Q_SIGNALS: - void aboutToClose(); - void connected(); - void disconnected(); - void stateChanged(QAbstractSocket::SocketState state); - void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *pAuthenticator); - void readChannelFinished(); - void textFrameReceived(QString frame, bool isLastFrame); - void binaryFrameReceived(QByteArray frame, bool isLastFrame); - void textMessageReceived(QString message); - void binaryMessageReceived(QByteArray message); - void error(QAbstractSocket::SocketError error); - void pong(quint64 elapsedTime); - -private Q_SLOTS: - void processData(); - void processControlFrame(WebSocketProtocol::OpCode opCode, QByteArray frame); - void processHandshake(QTcpSocket *pSocket); - void processStateChanged(QAbstractSocket::SocketState socketState); - -private: - Q_DISABLE_COPY(WebSocket) - - WebSocket(QTcpSocket *pTcpSocket, WebSocketProtocol::Version version, QObject *parent = 0); - void setVersion(WebSocketProtocol::Version version); - void setResourceName(QString resourceName); - void setRequestUrl(QUrl requestUrl); - void setOrigin(QString origin); - void setProtocol(QString protocol); - void setExtension(QString extension); - void enableMasking(bool enable); - void setSocketState(QAbstractSocket::SocketState state); - void setErrorString(QString errorString); - - qint64 doWriteData(const QByteArray &data, bool isBinary); - qint64 doWriteFrames(const QByteArray &data, bool isBinary); - - void makeConnections(const QTcpSocket *pTcpSocket); - void releaseConnections(const QTcpSocket *pTcpSocket); - - QByteArray getFrameHeader(WebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const; - QString calculateAcceptKey(const QString &key) const; - QString createHandShakeRequest(QString resourceName, - QString host, - QString origin, - QString extensions, - QString protocols, - QByteArray key); - - quint32 generateMaskingKey() const; - QByteArray generateKey() const; - quint32 generateRandomNumber() const; - qint64 writeFrames(const QList &frames); - qint64 writeFrame(const QByteArray &frame); - - static WebSocket *upgradeFrom(QTcpSocket *tcpSocket, - const HandshakeRequest &request, - const HandshakeResponse &response, - QObject *parent = 0); - friend class WebSocketServer; - - QTcpSocket *m_pSocket; - QString m_errorString; - WebSocketProtocol::Version m_version; - QUrl m_resource; - QString m_resourceName; - QUrl m_requestUrl; - QString m_origin; - QString m_protocol; - QString m_extension; - QAbstractSocket::SocketState m_socketState; - - QByteArray m_key; //identification key used in handshake requests - - bool m_mustMask; //a server must not mask the frames it sends - - bool m_isClosingHandshakeSent; - bool m_isClosingHandshakeReceived; - - QTime m_pingTimer; - - DataProcessor m_dataProcessor; -}; - -#endif // WEBSOCKET_H diff --git a/source/websocket.pri b/source/websocket.pri index f70a1d0..dc44611 100644 --- a/source/websocket.pri +++ b/source/websocket.pri @@ -1,18 +1,19 @@ QT *= network -SOURCES += $$PWD/websocket.cpp \ - $$PWD/websocketserver.cpp \ - $$PWD/websocketprotocol.cpp \ +SOURCES += $$PWD/qwebsocket.cpp \ + $$PWD/qwebsocketserver.cpp \ + $$PWD/qwebsocketprotocol.cpp \ $$PWD/handshakerequest_p.cpp \ $$PWD/handshakeresponse_p.cpp \ $$PWD/dataprocessor_p.cpp -HEADERS += $$PWD/websocket.h \ - $$PWD/websocketserver.h \ - $$PWD/websocketprotocol.h \ +HEADERS += $$PWD/qwebsocket.h \ + $$PWD/qwebsocketserver.h \ + $$PWD/qwebsocketprotocol.h \ $$PWD/handshakerequest_p.h \ $$PWD/handshakeresponse_p.h \ - $$PWD/dataprocessor_p.h + $$PWD/dataprocessor_p.h \ + $$PWD/qwebsocketsglobal.h INCLUDEPATH += $$PWD DEPENDPATH += $$PWD diff --git a/source/websocketprotocol.cpp b/source/websocketprotocol.cpp deleted file mode 100644 index 269c1cb..0000000 --- a/source/websocketprotocol.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "websocketprotocol.h" -#include -#include -#include - -/*! - \enum WebSocketProtocol::CloseCode - - The close codes supported by WebSockets V13 - \sa WebSocket::close() -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_NORMAL - Normal closure -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_GOING_AWAY - Going away -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_PROTOCOL_ERROR - Protocol error -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_DATATYPE_NOT_SUPPORTED - Unsupported data -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_RESERVED_1004 - Reserved -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_MISSING_STATUS_CODE - No status received -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_ABNORMAL_DISCONNECTION - Abnormal closure -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_WRONG_DATATYPE - Invalid frame payload data -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_POLICY_VIOLATED - Policy violation -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_TOO_MUCH_DATA - Message too big -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_MISSING_EXTENSION - Mandatory extension missing -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_BAD_OPERATION - Internal server error -*/ -/*! - \var WebSocketProtocol::CloseCode::CC_TLS_HANDSHAKE_FAILED - TLS handshake failed -*/ -/*! - \enum WebSocketProtocol::Version - - \brief The different defined versions of the Websocket protocol. - - For an overview of the differences between the different protocols, see - -*/ -/*! - \var WebSocketProtocol::Version::V_Unknow -*/ -/*! - \var WebSocketProtocol::Version::V_0 - hixie76: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 & hybi-00: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00. - Works with key1, key2 and a key in the payload.\n - Attribute: Sec-WebSocket-Draft value 0. -*/ -/*! - \var WebSocketProtocol::Version::V_4 - hybi-04: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-04.txt. - Changed handshake: key1, key2, key3 ==> Sec-WebSocket-Key, Sec-WebSocket-Nonce, Sec-WebSocket-Accept\n - Sec-WebSocket-Draft renamed to Sec-WebSocket-Version\n - Sec-WebSocket-Version = 4 -*/ -/*! - \var WebSocketProtocol::Version::V_5 - hybi-05: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-05.txt. - Sec-WebSocket-Version = 5\n - Removed Sec-WebSocket-Nonce\n - Added Sec-WebSocket-Accept\n -*/ -/*! - \var WebSocketProtocol::Version::V_6 - Sec-WebSocket-Version = 6. -*/ -/*! - \var WebSocketProtocol::Version::V_7 - hybi-07: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07. - Sec-WebSocket-Version = 7 -*/ -/*! - \var WebSocketProtocol::Version::V_8 - hybi-8, hybi-9, hybi-10, hybi-11 and hybi-12. - Status codes 1005 and 1006 are added and all codes are now unsigned\n - Internal error results in 1006 -*/ -/*! - \var WebSocketProtocol::Version::V_13 - hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and RFC 6455. - Sec-WebSocket-Version = 13\n - Status code 1004 is now reserved\n - Added 1008, 1009 and 1010\n - Must support TLS\n - Clarify multiple version support -*/ -/*! - \var WebSocketProtocol::Version::V_LATEST - Refers to the latest know version to QWebSockets. -*/ - -/*! - \fn WebSocketProtocol::isOpCodeReserved(OpCode code) - Checks if \a code is a valid OpCode - \internal -*/ - -/*! - \fn WebSocketProtocol::isCloseCodeValid(int closeCode) - Checks if \a closeCode is a valid web socket close code - \internal -*/ - -/*! - \fn WebSocketProtocol::getCurrentVersion() - Returns the latest version that WebSocket is supporting - \internal -*/ - -/** - * @brief Contains constants related to the WebSocket standard. - */ -namespace WebSocketProtocol -{ - /*! - Parses the \a versionString and converts it to a Version value - \internal - */ - Version versionFromString(const QString &versionString) - { - bool ok = false; - Version version = V_Unknow; - int ver = versionString.toInt(&ok); - QSet supportedVersions; - supportedVersions << V_0 << V_4 << V_5 << V_6 << V_7 << V_8 << V_13; - if (ok) - { - if (supportedVersions.contains(static_cast(ver))) - { - version = static_cast(ver); - } - } - return version; - } - - /*! - Mask the \a payload with the given \a maskingKey and stores the result back in \a payload. - \internal - */ - void mask(QByteArray *payload, quint32 maskingKey) - { - quint32 *payloadData = reinterpret_cast(payload->data()); - quint32 numIterations = static_cast(payload->size()) / sizeof(quint32); - quint32 remainder = static_cast(payload->size()) % sizeof(quint32); - quint32 i; - for (i = 0; i < numIterations; ++i) - { - *(payloadData + i) ^= maskingKey; - } - if (remainder) - { - const quint32 offset = i * static_cast(sizeof(quint32)); - char *payloadBytes = payload->data(); - uchar *mask = reinterpret_cast(&maskingKey); - for (quint32 i = 0; i < remainder; ++i) - { - *(payloadBytes + offset + i) ^= static_cast(mask[(i + offset) % 4]); - } - } - } - - /*! - Masks the \a payload of length \a size with the given \a maskingKey and stores the result back in \a payload. - \internal - */ - void mask(char *payload, quint64 size, quint32 maskingKey) - { - quint32 *payloadData = reinterpret_cast(payload); - quint32 numIterations = static_cast(size / sizeof(quint32)); - quint32 remainder = size % sizeof(quint32); - quint32 i; - for (i = 0; i < numIterations; ++i) - { - *(payloadData + i) ^= maskingKey; - } - if (remainder) - { - const quint32 offset = i * static_cast(sizeof(quint32)); - uchar *mask = reinterpret_cast(&maskingKey); - for (quint32 i = 0; i < remainder; ++i) - { - *(payload + offset + i) ^= static_cast(mask[(i + offset) % 4]); - } - } - } -} //end namespace WebSocketProtocol diff --git a/source/websocketprotocol.h b/source/websocketprotocol.h deleted file mode 100644 index ba385c5..0000000 --- a/source/websocketprotocol.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @file websocketprotocol.h - * @brief Contains constants related to the WebSocket standard. - * @author Kurt Pattyn (pattyn.kurt@gmail.com) - */ -#ifndef WEBSOCKETPROTOCOL_H -#define WEBSOCKETPROTOCOL_H - -class QString; -class QByteArray; -#include - -namespace WebSocketProtocol -{ - enum Version - { - V_Unknow = -1, - V_0 = 0, - //hybi-01, hybi-02 and hybi-03 not supported - V_4 = 4, - V_5 = 5, - V_6 = 6, - V_7 = 7, - V_8 = 8, - V_13 = 13, - V_LATEST = V_13 - }; - - Version versionFromString(const QString &versionString); - - enum CloseCode - { - CC_NORMAL = 1000, - CC_GOING_AWAY = 1001, - CC_PROTOCOL_ERROR = 1002, - CC_DATATYPE_NOT_SUPPORTED = 1003, - CC_RESERVED_1004 = 1004, - CC_MISSING_STATUS_CODE = 1005, - CC_ABNORMAL_DISCONNECTION = 1006, - CC_WRONG_DATATYPE = 1007, - CC_POLICY_VIOLATED = 1008, - CC_TOO_MUCH_DATA = 1009, - CC_MISSING_EXTENSION = 1010, - CC_BAD_OPERATION = 1011, - CC_TLS_HANDSHAKE_FAILED = 1015 - }; - - enum OpCode - { - OC_CONTINUE = 0x0, - OC_TEXT = 0x1, - OC_BINARY = 0x2, - OC_RESERVED_3 = 0x3, - OC_RESERVED_4 = 0x4, - OC_RESERVED_5 = 0x5, - OC_RESERVED_6 = 0x6, - OC_RESERVED_7 = 0x7, - OC_CLOSE = 0x8, - OC_PING = 0x9, - OC_PONG = 0xA, - OC_RESERVED_B = 0xB, - OC_RESERVED_V = 0xC, - OC_RESERVED_D = 0xD, - OC_RESERVED_E = 0xE, - OC_RESERVED_F = 0xF - }; - - - inline bool isOpCodeReserved(OpCode code) - { - return ((code > OC_BINARY) && (code < OC_CLOSE)) || (code > OC_PONG); - } - inline bool isCloseCodeValid(int closeCode) - { - return (closeCode > 999) && (closeCode < 5000) && - (closeCode != CC_RESERVED_1004) && //see RFC6455 7.4.1 - (closeCode != CC_MISSING_STATUS_CODE) && - (closeCode != CC_ABNORMAL_DISCONNECTION) && - ((closeCode >= 3000) || (closeCode < 1012)); - } - - void mask(QByteArray *payload, quint32 maskingKey); - void mask(char *payload, quint64 size, quint32 maskingKey); - - inline Version currentVersion() { return V_LATEST; } - -} //end namespace WebSocketProtocol - -#endif // WEBSOCKETPROTOCOL_H diff --git a/source/websocketserver.cpp b/source/websocketserver.cpp deleted file mode 100644 index b9308e5..0000000 --- a/source/websocketserver.cpp +++ /dev/null @@ -1,417 +0,0 @@ -#include "websocketserver.h" -#include -#include -#include -#include "websocketprotocol.h" -#include "handshakerequest_p.h" -#include "handshakeresponse_p.h" -#include "websocket.h" - -/*! - \class WebSocketServer - - The WebSocketServer class provides a websocket-based server. - It is modeled after QTcpServer, and behaves the same. So, if you know how to use QTcpServer, you know how to use WebSocketServer.\n - This class makes it possible to accept incoming websocket connections.\n - You can specify the port or have WebSocketServer pick one automatically.\n - You can listen on a specific address or on all the machine's addresses.\n - Call listen() to have the server listen for incoming connections.\n - - The newConnection() signal is then emitted each time a client connects to the server.\n - Call nextPendingConnection() to accept the pending connection as a connected WebSocket. - The function returns a pointer to a WebSocket in QAbstractSocket::ConnectedState that you can use for communicating with the client.\n - If an error occurs, serverError() returns the type of error, and errorString() can be called to get a human readable description of what happened.\n - When listening for connections, the address and port on which the server is listening are available as serverAddress() and serverPort().\n - Calling close() makes WebSocketServer stop listening for incoming connections.\n - Although WebSocketServer is mostly designed for use with an event loop, it's possible to use it without one. In that case, you must use waitForNewConnection(), which blocks until either a connection is available or a timeout expires. - - \ref echoserver - - \author Kurt Pattyn (pattyn.kurt@gmail.com) - - \sa WebSocket -*/ - -/*! - \page echoserver WebSocket server example - \brief A sample websocket server echoing back messages sent to it. - - \section Description - The echoserver example implements a web socket server that echoes back everything that is sent to it. - \section Code - We start by creating a WebSocketServer (`new WebSocketServer()`). After the creation, we listen on all local network interfaces (`QHostAddress::Any`) on the specified \a port. - \snippet echoserver.cpp constructor - If listening is successful, we connect the `newConnection()` signal to the slot `onNewConnection()`. - The `newConnection()` signal will be thrown whenever a new web socket client is connected to our server. - - \snippet echoserver.cpp onNewConnection - When a new connection is received, the client WebSocket is retrieved (`nextPendingConnection()`), and the signals we are interested in - are connected to our slots (`textMessageReceived()`, `binaryMessageReceived()` and `disconnected()`). - The client socket is remembered in a list, in case we would like to use it later (in this example, nothing is done with it). - - \snippet echoserver.cpp processMessage - Whenever `processMessage()` is triggered, we retrieve the sender, and if valid, send back the original message (`send()`). - The same is done with binary messages. - \snippet echoserver.cpp processBinaryMessage - The only difference is that the message now is a QByteArray instead of a QString. - - \snippet echoserver.cpp socketDisconnected - Whenever a socket is disconnected, we remove it from the clients list and delete the socket. - Note: it is best to use `deleteLater()` to delete the socket. -*/ - -/*! - \fn void WebSocketServer::newConnection() - This signal is emitted every time a new connection is available. - - \sa hasPendingConnections() and nextPendingConnection(). -*/ - -/*! - Constructs a new WebSocketServer. - - \a parent is passed to the QObject constructor. - */ -WebSocketServer::WebSocketServer(const QString &serverName, QObject *parent) : - QObject(parent), - m_pTcpServer(0), - m_serverName(serverName), - m_pendingConnections() -{ - m_pTcpServer = new QTcpServer(this); - connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); -} - -/*! - Destroys the WebSocketServer object. If the server is listening for connections, the socket is automatically closed. - Any client WebSockets that are still connected are closed and deleted. - - \sa close() - */ -WebSocketServer::~WebSocketServer() -{ - while (!m_pendingConnections.isEmpty()) - { - WebSocket *pWebSocket = m_pendingConnections.dequeue(); - pWebSocket->close(WebSocketProtocol::CC_GOING_AWAY, "Server closed."); - pWebSocket->deleteLater(); - } - m_pTcpServer->deleteLater(); -} - -/*! - Closes the server. The server will no longer listen for incoming connections. - */ -void WebSocketServer::close() -{ - m_pTcpServer->close(); -} - -/*! - Returns a human readable description of the last error that occurred. - - \sa serverError(). -*/ -QString WebSocketServer::errorString() const -{ - return m_pTcpServer->errorString(); -} - -/*! - Returns true if the server has pending connections; otherwise returns false. - - \sa nextPendingConnection() and setMaxPendingConnections(). - */ -bool WebSocketServer::hasPendingConnections() const -{ - return !m_pendingConnections.isEmpty(); -} - -/*! - Returns true if the server is currently listening for incoming connections; otherwise returns false. - - \sa listen(). - */ -bool WebSocketServer::isListening() const -{ - return m_pTcpServer->isListening(); -} - -/*! - Tells the server to listen for incoming connections on address \a address and port \a port. - If \a port is 0, a port is chosen automatically.\n - If \a address is QHostAddress::Any, the server will listen on all network interfaces. - - Returns true on success; otherwise returns false. - - \sa isListening(). - */ -bool WebSocketServer::listen(const QHostAddress &address, quint16 port) -{ - return m_pTcpServer->listen(address, port); -} - -/*! - Returns the maximum number of pending accepted connections. The default is 30. - - \sa setMaxPendingConnections() and hasPendingConnections(). - */ -int WebSocketServer::maxPendingConnections() const -{ - return m_pTcpServer->maxPendingConnections(); -} - -/*! - This function is called to add the socket to the list of pending incoming websocket connections. - - \sa nextPendingConnection() and hasPendingConnections() -*/ -void WebSocketServer::addPendingConnection(WebSocket *pWebSocket) -{ - if (m_pendingConnections.size() < maxPendingConnections()) - { - m_pendingConnections.enqueue(pWebSocket); - } -} - -/*! - Returns the next pending connection as a connected WebSocket object. - The socket is created as a child of the server, which means that it is automatically deleted when the WebSocketServer object is destroyed. It is still a good idea to delete the object explicitly when you are done with it, to avoid wasting memory. - 0 is returned if this function is called when there are no pending connections. - - Note: The returned WebSocket object cannot be used from another thread.. - - \sa hasPendingConnections(). -*/ -WebSocket *WebSocketServer::nextPendingConnection() -{ - WebSocket *pWebSocket = 0; - if (!m_pendingConnections.isEmpty()) - { - pWebSocket = m_pendingConnections.dequeue(); - } - return pWebSocket; -} - -/*! - Returns the network proxy for this socket. By default QNetworkProxy::DefaultProxy is used. - - \sa setProxy(). -*/ -QNetworkProxy WebSocketServer::proxy() const -{ - return m_pTcpServer->proxy(); -} - -/*! - Returns the server's address if the server is listening for connections; otherwise returns QHostAddress::Null. - - \sa serverPort() and listen(). - */ -QHostAddress WebSocketServer::serverAddress() const -{ - return m_pTcpServer->serverAddress(); -} - -/*! - Returns an error code for the last error that occurred. - \sa errorString(). - */ -QAbstractSocket::SocketError WebSocketServer::serverError() const -{ - return m_pTcpServer->serverError(); -} - -/*! - Returns the server's port if the server is listening for connections; otherwise returns 0. - \sa serverAddress() and listen(). - */ -quint16 WebSocketServer::serverPort() const -{ - return m_pTcpServer->serverPort(); -} - -/*! - Sets the maximum number of pending accepted connections to \a numConnections. - WebSocketServer will accept no more than \a numConnections incoming connections before nextPendingConnection() is called.\n - By default, the limit is 30 pending connections. - - Clients may still able to connect after the server has reached its maximum number of pending connections (i.e., WebSocket can still emit the connected() signal). WebSocketServer will stop accepting the new connections, but the operating system may still keep them in queue. - \sa maxPendingConnections() and hasPendingConnections(). - */ -void WebSocketServer::setMaxPendingConnections(int numConnections) -{ - m_pTcpServer->setMaxPendingConnections(numConnections); -} - -/*! - \brief Sets the explicit network proxy for this socket to \a networkProxy. - - To disable the use of a proxy for this socket, use the QNetworkProxy::NoProxy proxy type: - - \code - server->setProxy(QNetworkProxy::NoProxy); - \endcode - - \sa proxy(). -*/ -void WebSocketServer::setProxy(const QNetworkProxy &networkProxy) -{ - m_pTcpServer->setProxy(networkProxy); -} - -/*! - Sets the socket descriptor this server should use when listening for incoming connections to \a socketDescriptor. - - Returns true if the socket is set successfully; otherwise returns false.\n - The socket is assumed to be in listening state. - - \sa socketDescriptor() and isListening(). - */ -bool WebSocketServer::setSocketDescriptor(int socketDescriptor) -{ - return m_pTcpServer->setSocketDescriptor(socketDescriptor); -} - -/*! - Returns the native socket descriptor the server uses to listen for incoming instructions, or -1 if the server is not listening. - If the server is using QNetworkProxy, the returned descriptor may not be usable with native socket functions. - - \sa setSocketDescriptor() and isListening(). - */ -int WebSocketServer::socketDescriptor() const -{ - return m_pTcpServer->socketDescriptor(); -} - -/*! - Waits for at most \a msec milliseconds or until an incoming connection is available. - Returns true if a connection is available; otherwise returns false. - If the operation timed out and \a timedOut is not 0, \a *timedOut will be set to true. - - \note This is a blocking function call. - \note Its use is disadvised in a single-threaded GUI application, since the whole application will stop responding until the function returns. waitForNewConnection() is mostly useful when there is no event loop available. - \note The non-blocking alternative is to connect to the newConnection() signal. - - If \a msec is -1, this function will not time out. - - \sa hasPendingConnections() and nextPendingConnection(). -*/ -bool WebSocketServer::waitForNewConnection(int msec, bool *timedOut) -{ - return m_pTcpServer->waitForNewConnection(msec, timedOut); -} - -/*! - Returns a list of websocket versions that this server is supporting. - */ -QList WebSocketServer::supportedVersions() const -{ - QList supportedVersions; - supportedVersions << WebSocketProtocol::currentVersion(); //we only support V13 - return supportedVersions; -} - -/*! - Returns a list of websocket subprotocols that this server supports. - */ -QList WebSocketServer::supportedProtocols() const -{ - QList supportedProtocols; - return supportedProtocols; //no protocols are currently supported -} - -/*! - Returns a list of websocket extensions that this server supports. - */ -QList WebSocketServer::supportedExtensions() const -{ - QList supportedExtensions; - return supportedExtensions; //no extensions are currently supported -} - -//Checking on the origin does not make much sense when the server is accessed -//via a non-browser client, as that client can set whatever origin header it likes -//In case of a browser client, the server SHOULD check the validity of the origin -//see http://tools.ietf.org/html/rfc6455#section-10 -bool WebSocketServer::isOriginAllowed(const QString &origin) const -{ - Q_UNUSED(origin) - return true; -} - -void WebSocketServer::onNewConnection() -{ - QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection(); - connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); -} - -void WebSocketServer::onCloseConnection() -{ - QTcpSocket *pTcpSocket = qobject_cast(sender()); - if (pTcpSocket != 0) - { - pTcpSocket->close(); - } -} - -void WebSocketServer::handshakeReceived() -{ - QTcpSocket *pTcpSocket = qobject_cast(sender()); - if (pTcpSocket != 0) - { - bool success = false; - bool isSecure = false; - HandshakeRequest request(pTcpSocket->peerPort(), isSecure); - QTextStream textStream(pTcpSocket); - textStream >> request; - - HandshakeResponse response(request, - m_serverName, - isOriginAllowed(request.getOrigin()), - supportedVersions(), - supportedProtocols(), - supportedExtensions()); - disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); - - if (response.isValid()) - { - QTextStream httpStream(pTcpSocket); - httpStream << response; - httpStream.flush(); - - if (response.canUpgrade()) - { - WebSocket *pWebSocket = WebSocket::upgradeFrom(pTcpSocket, request, response); - if (pWebSocket) - { - pWebSocket->setParent(this); - addPendingConnection(pWebSocket); - Q_EMIT newConnection(); - success = true; - } - else - { - qDebug() << "WebSocketServer::handshakeReceived: Upgrading to WebSocket failed."; - } - } - else - { - qDebug() << "WebSocketServer::handshakeReceived: Cannot upgrade to websocket."; - } - } - else - { - qDebug() << "WebSocketServer::handshakeReceived: Invalid response. This should not happen!!!"; - } - if (!success) - { - qDebug() << "WebSocketServer::handshakeReceived: Closing socket because of invalid or unsupported request"; - pTcpSocket->close(); - } - } - else - { - qDebug() << "WebSocketServerImp::handshakeReceived: Sender socket is NULL. This should not happen!!!"; - } -} diff --git a/source/websocketserver.h b/source/websocketserver.h deleted file mode 100644 index 0401050..0000000 --- a/source/websocketserver.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file websocketserver.h - * @author Kurt Pattyn (pattyn.kurt@gmail.com) - * @brief Defines the WebSocketServer class. - */ - -#ifndef WEBSOCKETSERVER_H -#define WEBSOCKETSERVER_H - -#include -#include -#include -#include -#include "websocket.h" - -class QTcpServer; - -class WebSocketServer : public QObject -{ - Q_OBJECT - -public: - explicit WebSocketServer(const QString &serverName, QObject *parent = 0); - virtual ~WebSocketServer(); - - void close(); - QString errorString() const; - bool hasPendingConnections() const; - bool isListening() const; - bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); - int maxPendingConnections() const; - virtual WebSocket *nextPendingConnection(); - QNetworkProxy proxy() const; - QHostAddress serverAddress() const; - QAbstractSocket::SocketError serverError() const; - quint16 serverPort() const; - void setMaxPendingConnections(int numConnections); - void setProxy(const QNetworkProxy &networkProxy); - bool setSocketDescriptor(int socketDescriptor); - int socketDescriptor() const; - bool waitForNewConnection(int msec = 0, bool *timedOut = 0); - - QList supportedVersions() const; - QList supportedProtocols() const; - QList supportedExtensions() const; - -protected: - virtual bool isOriginAllowed(const QString &origin) const; - -Q_SIGNALS: - void newConnection(); - -private Q_SLOTS: - void onNewConnection(); - void onCloseConnection(); - void handshakeReceived(); - -private: - Q_DISABLE_COPY(WebSocketServer) - - QTcpServer *m_pTcpServer; - QString m_serverName; - QQueue m_pendingConnections; - - void addPendingConnection(WebSocket *pWebSocket); -}; - -#endif // WEBSOCKETSERVER_H diff --git a/test/tst_compliance.cpp b/test/tst_compliance.cpp index 1992631..b450887 100644 --- a/test/tst_compliance.cpp +++ b/test/tst_compliance.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "websocket.h" +#include "qwebsocket.h" #include "unittests.h" class ComplianceTest : public QObject @@ -57,16 +57,16 @@ void ComplianceTest::runTestCase(int nbr, int total) { return; } - WebSocket *pWebSocket = new WebSocket; + QWebSocket *pWebSocket = new QWebSocket; QSignalSpy spy(pWebSocket, SIGNAL(disconnected())); //next for every case, connect to url //ws://ipaddress:port/runCase?case=&agent= //where agent name will be QWebSocket - QObject::connect(pWebSocket, &WebSocket::textMessageReceived, [=](QString message) { + QObject::connect(pWebSocket, &QWebSocket::textMessageReceived, [=](QString message) { pWebSocket->send(message); }); - QObject::connect(pWebSocket, &WebSocket::binaryMessageReceived, [=](QByteArray message) { + QObject::connect(pWebSocket, &QWebSocket::binaryMessageReceived, [=](QByteArray message) { pWebSocket->send(message); }); @@ -93,11 +93,11 @@ void ComplianceTest::runTestCases(int startNbr, int stopNbr) void ComplianceTest::autobahnTest() { //connect to autobahn server at url ws://ipaddress:port/getCaseCount - WebSocket *pWebSocket = new WebSocket; + QWebSocket *pWebSocket = new QWebSocket; QUrl url = m_url; int numberOfTestCases = 0; QSignalSpy spy(pWebSocket, SIGNAL(disconnected())); - QObject::connect(pWebSocket, &WebSocket::textMessageReceived, [&](QString message) { + QObject::connect(pWebSocket, &QWebSocket::textMessageReceived, [&](QString message) { numberOfTestCases = message.toInt(); }); @@ -106,7 +106,7 @@ void ComplianceTest::autobahnTest() spy.wait(60000); QVERIFY(numberOfTestCases > 0); - QObject::disconnect(pWebSocket, &WebSocket::textMessageReceived, 0, 0); + QObject::disconnect(pWebSocket, &QWebSocket::textMessageReceived, 0, 0); runTestCases(0, numberOfTestCases); diff --git a/test/tst_websockets.cpp b/test/tst_websockets.cpp index 2b06cf4..fe914dc 100644 --- a/test/tst_websockets.cpp +++ b/test/tst_websockets.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "websocket.h" +#include "qwebsocket.h" #include "unittests.h" class WebSocketsTest : public QObject @@ -46,7 +46,7 @@ private Q_SLOTS: //void autobahnTest(); private: - WebSocket *m_pWebSocket; + QWebSocket *m_pWebSocket; QUrl m_url; }; @@ -58,7 +58,7 @@ WebSocketsTest::WebSocketsTest() : void WebSocketsTest::initTestCase() { - m_pWebSocket = new WebSocket(); + m_pWebSocket = new QWebSocket(); m_pWebSocket->open(m_url, true); QTRY_VERIFY_WITH_TIMEOUT(m_pWebSocket->state() == QAbstractSocket::ConnectedState, 1000); QVERIFY(m_pWebSocket->isValid()); -- cgit v1.2.1