From 6e1e387174d20fb62a804b6e27438bcc5ea59f28 Mon Sep 17 00:00:00 2001 From: Steven Ceuppens Date: Fri, 27 Sep 2013 14:27:27 +0200 Subject: Changes to integrate the websocket module as a full Qt Add-On module - Changed project files to use Qt structure - Moved existing sources into subdirectory, to make room for moduels - Created a "import/qmlwebsocket" module skeleton (works, but needs to be extended) - Modified examples to not use "include(.pri)", but use "QT += websocket" - Added qml example skeleton (works, but no useful functionality yet) Project can be build with: $ qmake $ make $ make install Module can be used in other projects with QT += websockets Change-Id: I2123026958b264670dbf8a978dee76edf5855806 Reviewed-by: Kurt Pattyn --- .gitignore | 41 +- .qmake.conf | 5 + examples/echoclient/echoclient.pro | 4 +- examples/echoserver/echoserver.pro | 4 +- examples/examples.pro | 6 +- examples/qmlwebsocketclient/data.qrc | 5 + examples/qmlwebsocketclient/main.cpp | 32 + .../qml/qmlwebsocketclient/main.qml | 42 + examples/qmlwebsocketclient/qmlwebsocketclient.pro | 11 + qwebsockets.pro | 8 - src/dataprocessor_p.cpp | 843 --------------- src/dataprocessor_p.h | 102 -- src/doc/qwebsockets.qdoc | 8 - src/doc/qwebsockets.qdocconfig | 23 - src/handshakerequest_p.cpp | 269 ----- src/handshakerequest_p.h | 87 -- src/handshakeresponse_p.cpp | 220 ---- src/handshakeresponse_p.h | 88 -- src/imports/imports.pro | 3 + src/imports/qmlwebsockets/qmldir | 3 + src/imports/qmlwebsockets/qmlwebsockets.pro | 11 + src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp | 34 + src/imports/qmlwebsockets/qmlwebsockets_plugin.h | 40 + src/imports/qmlwebsockets/qqmlwebsocket.cpp | 35 + src/imports/qmlwebsockets/qqmlwebsocket.h | 40 + src/qcorsauthenticator.cpp | 132 --- src/qcorsauthenticator.h | 48 - src/qcorsauthenticator_p.h | 29 - src/qwebsocket.cpp | 592 ----------- src/qwebsocket.h | 116 -- src/qwebsocket_p.cpp | 1108 -------------------- src/qwebsocket_p.h | 183 ---- src/qwebsocketprotocol.cpp | 179 ---- src/qwebsocketprotocol.h | 108 -- src/qwebsockets.pri | 28 - src/qwebsockets.pro | 10 - src/qwebsocketserver.cpp | 407 ------- src/qwebsocketserver.h | 92 -- src/qwebsocketserver_p.cpp | 362 ------- src/qwebsocketserver_p.h | 101 -- src/qwebsocketsglobal.h | 43 - src/src.pro | 4 +- src/websockets/dataprocessor_p.cpp | 843 +++++++++++++++ src/websockets/dataprocessor_p.h | 102 ++ src/websockets/doc/qwebsockets.qdoc | 8 + src/websockets/doc/qwebsockets.qdocconfig | 23 + src/websockets/handshakerequest_p.cpp | 269 +++++ src/websockets/handshakerequest_p.h | 87 ++ src/websockets/handshakeresponse_p.cpp | 220 ++++ src/websockets/handshakeresponse_p.h | 88 ++ src/websockets/qcorsauthenticator.cpp | 132 +++ src/websockets/qcorsauthenticator.h | 48 + src/websockets/qcorsauthenticator_p.h | 29 + src/websockets/qwebsocket.cpp | 592 +++++++++++ src/websockets/qwebsocket.h | 116 ++ src/websockets/qwebsocket_p.cpp | 1108 ++++++++++++++++++++ src/websockets/qwebsocket_p.h | 183 ++++ src/websockets/qwebsocketprotocol.cpp | 179 ++++ src/websockets/qwebsocketprotocol.h | 108 ++ src/websockets/qwebsocketserver.cpp | 407 +++++++ src/websockets/qwebsocketserver.h | 92 ++ src/websockets/qwebsocketserver_p.cpp | 362 +++++++ src/websockets/qwebsocketserver_p.h | 101 ++ src/websockets/qwebsocketsglobal.h | 43 + src/websockets/websockets.pro | 45 + sync.profile | 9 +- tests/tests.pro | 18 +- websockets.pro | 1 + 68 files changed, 5488 insertions(+), 5231 deletions(-) create mode 100644 .qmake.conf create mode 100644 examples/qmlwebsocketclient/data.qrc create mode 100644 examples/qmlwebsocketclient/main.cpp create mode 100644 examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml create mode 100644 examples/qmlwebsocketclient/qmlwebsocketclient.pro delete mode 100644 qwebsockets.pro delete mode 100644 src/dataprocessor_p.cpp delete mode 100644 src/dataprocessor_p.h delete mode 100644 src/doc/qwebsockets.qdoc delete mode 100644 src/doc/qwebsockets.qdocconfig delete mode 100644 src/handshakerequest_p.cpp delete mode 100644 src/handshakerequest_p.h delete mode 100644 src/handshakeresponse_p.cpp delete mode 100644 src/handshakeresponse_p.h create mode 100644 src/imports/imports.pro create mode 100644 src/imports/qmlwebsockets/qmldir create mode 100644 src/imports/qmlwebsockets/qmlwebsockets.pro create mode 100644 src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp create mode 100644 src/imports/qmlwebsockets/qmlwebsockets_plugin.h create mode 100644 src/imports/qmlwebsockets/qqmlwebsocket.cpp create mode 100644 src/imports/qmlwebsockets/qqmlwebsocket.h delete mode 100644 src/qcorsauthenticator.cpp delete mode 100644 src/qcorsauthenticator.h delete mode 100644 src/qcorsauthenticator_p.h delete mode 100644 src/qwebsocket.cpp delete mode 100644 src/qwebsocket.h delete mode 100644 src/qwebsocket_p.cpp delete mode 100644 src/qwebsocket_p.h delete mode 100644 src/qwebsocketprotocol.cpp delete mode 100644 src/qwebsocketprotocol.h delete mode 100644 src/qwebsockets.pri delete mode 100644 src/qwebsockets.pro delete mode 100644 src/qwebsocketserver.cpp delete mode 100644 src/qwebsocketserver.h delete mode 100644 src/qwebsocketserver_p.cpp delete mode 100644 src/qwebsocketserver_p.h delete mode 100644 src/qwebsocketsglobal.h create mode 100644 src/websockets/dataprocessor_p.cpp create mode 100644 src/websockets/dataprocessor_p.h create mode 100644 src/websockets/doc/qwebsockets.qdoc create mode 100644 src/websockets/doc/qwebsockets.qdocconfig create mode 100644 src/websockets/handshakerequest_p.cpp create mode 100644 src/websockets/handshakerequest_p.h create mode 100644 src/websockets/handshakeresponse_p.cpp create mode 100644 src/websockets/handshakeresponse_p.h create mode 100644 src/websockets/qcorsauthenticator.cpp create mode 100644 src/websockets/qcorsauthenticator.h create mode 100644 src/websockets/qcorsauthenticator_p.h create mode 100644 src/websockets/qwebsocket.cpp create mode 100644 src/websockets/qwebsocket.h create mode 100644 src/websockets/qwebsocket_p.cpp create mode 100644 src/websockets/qwebsocket_p.h create mode 100644 src/websockets/qwebsocketprotocol.cpp create mode 100644 src/websockets/qwebsocketprotocol.h create mode 100644 src/websockets/qwebsocketserver.cpp create mode 100644 src/websockets/qwebsocketserver.h create mode 100644 src/websockets/qwebsocketserver_p.cpp create mode 100644 src/websockets/qwebsocketserver_p.h create mode 100644 src/websockets/qwebsocketsglobal.h create mode 100644 src/websockets/websockets.pro create mode 100644 websockets.pro diff --git a/.gitignore b/.gitignore index 3425f4b..218d86e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,6 @@ *.dylib *.obj Makefile* -moc_*.cpp -*.moc # Directories to ignore # --------------------- @@ -34,19 +32,28 @@ build-unittests-Qt_5_1_0_clang_64-Debug/unittests build-unittests-Qt_5_1_0_clang_64-Release/unittests examples/.coverity-submit -reports -doc/html -build-examples-Qt_5_1_0_clang_64_temporary-Debug -build-unittests-Qt_5_1_0_clang_64-Release -src/doc/html +# Qt specific .qmake.cache -examples/echoclient/echoclient -src/libQWebSockets.0.9.0.dylib -src/libQWebSockets.0.9.dylib -src/libQWebSockets.0.dylib -src/libQWebSockets.1.0.0.dylib -src/libQWebSockets.1.0.dylib -src/libQWebSockets.1.dylib -src/libQWebSockets.dylib -examples/echoserver/echoserver -tests/unittests +*.pro.user +*.pro.shared +moc_*.cpp +*.moc +*_resource.rc +ui_*.h +qrc_*.cpp + +*~ +*.pdb +*.exe +*.lib +*.res +*.exp +*.manifest +config.log +tmp +imports/* +include +tests/auto/cmake/build +lib/ +mkspecs/ +qml/ diff --git a/.qmake.conf b/.qmake.conf new file mode 100644 index 0000000..4fb101b --- /dev/null +++ b/.qmake.conf @@ -0,0 +1,5 @@ +load(qt_build_config) + +CONFIG += qt_example_installs + +MODULE_VERSION = 5.1.1 diff --git a/examples/echoclient/echoclient.pro b/examples/echoclient/echoclient.pro index 565eea3..f6891ef 100644 --- a/examples/echoclient/echoclient.pro +++ b/examples/echoclient/echoclient.pro @@ -1,4 +1,4 @@ -QT += core +QT += core websockets QT -= gui TARGET = echoclient @@ -9,8 +9,6 @@ mac:QMAKE_CXXFLAGS += -Wall -Werror -Wextra TEMPLATE = app -include(../../src/qwebsockets.pri) - SOURCES += \ main.cpp \ echoclient.cpp diff --git a/examples/echoserver/echoserver.pro b/examples/echoserver/echoserver.pro index 913cb6e..d2edcae 100644 --- a/examples/echoserver/echoserver.pro +++ b/examples/echoserver/echoserver.pro @@ -1,4 +1,4 @@ -QT += core +QT += core websockets QT -= gui TARGET = echoserver @@ -9,8 +9,6 @@ mac:QMAKE_CXXFLAGS += -Wall -Werror -Wextra TEMPLATE = app -include(../../src/qwebsockets.pri) - SOURCES += \ main.cpp \ echoserver.cpp diff --git a/examples/examples.pro b/examples/examples.pro index b350436..653df67 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -1,5 +1,5 @@ -cache() TEMPLATE = subdirs -SUBDIRS = echoclient \ - echoserver +SUBDIRS = echoclient \ + echoserver \ + qmlwebsocketclient diff --git a/examples/qmlwebsocketclient/data.qrc b/examples/qmlwebsocketclient/data.qrc new file mode 100644 index 0000000..3ac4604 --- /dev/null +++ b/examples/qmlwebsocketclient/data.qrc @@ -0,0 +1,5 @@ + + + qml/qmlwebsocketclient/main.qml + + diff --git a/examples/qmlwebsocketclient/main.cpp b/examples/qmlwebsocketclient/main.cpp new file mode 100644 index 0000000..1df79e3 --- /dev/null +++ b/examples/qmlwebsocketclient/main.cpp @@ -0,0 +1,32 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQuickView view; + view.setSource(QUrl(QStringLiteral("qrc:/qml/qmlwebsocketclient/main.qml"))); + view.show(); + + return app.exec(); +} diff --git a/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml b/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml new file mode 100644 index 0000000..7c227a4 --- /dev/null +++ b/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml @@ -0,0 +1,42 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +import QtQuick 2.0 +import Qt.Playground.WebSockets 1.0 + +Rectangle { + width: 360 + height: 360 + + WebSocket { + + } + + Text { + text: qsTr("Hello World") + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + Qt.quit(); + } + } +} diff --git a/examples/qmlwebsocketclient/qmlwebsocketclient.pro b/examples/qmlwebsocketclient/qmlwebsocketclient.pro new file mode 100644 index 0000000..e4a7d13 --- /dev/null +++ b/examples/qmlwebsocketclient/qmlwebsocketclient.pro @@ -0,0 +1,11 @@ +QT += qml quick + +TARGET = qmlwebsocketclient + +CONFIG -= app_bundle + +SOURCES += main.cpp + +RESOURCES += \ + data.qrc + diff --git a/qwebsockets.pro b/qwebsockets.pro deleted file mode 100644 index 3c981a3..0000000 --- a/qwebsockets.pro +++ /dev/null @@ -1,8 +0,0 @@ -cache() -TEMPLATE = subdirs - -SUBDIRS = \ - src \ - examples \ - tests -CONFIG += ordered diff --git a/src/dataprocessor_p.cpp b/src/dataprocessor_p.cpp deleted file mode 100644 index f666ab3..0000000 --- a/src/dataprocessor_p.cpp +++ /dev/null @@ -1,843 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -/*! - \class DataProcessor - The class DataProcessor is responsible for reading, validating and interpreting data from a websocket. - It reads data from a QIODevice, validates it against RFC 6455, and parses it into frames (data, control). - It emits signals that correspond to the type of the frame: textFrameReceived(), binaryFrameReceived(), - textMessageReceived(), binaryMessageReceived(), pingReceived(), pongReceived() and closeReceived(). - Whenever an error is detected, the errorEncountered() signal is emitted. - DataProcessor also checks if a frame is allowed in a sequence of frames (e.g. a continuation frame cannot follow a final frame). - This class is an internal class used by QWebSocketInternal for data processing and validation. - - \sa Frame() - - \internal -*/ -#include "dataprocessor_p.h" -#include "qwebsocketprotocol.h" -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1; -const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1; - -/*! - \class Frame - The class Frame is responsible for reading, validating and interpreting frames from a websocket. - It reads data from a QIODevice, validates it against RFC 6455, and parses it into a frame (data, control). - Whenever an error is detected, the isValid() returns false. - - \note The Frame class does not look at valid sequences of frames. It processes frames one at a time. - \note It is the DataProcessor that takes the sequence into account. - - \sa DataProcessor() - \internal - */ -class Frame -{ -public: - Frame(); - Frame(const Frame &other); - - const Frame &operator =(const Frame &other); - - QWebSocketProtocol::CloseCode getCloseCode() const; - QString getCloseReason() const; - bool isFinalFrame() const; - bool isControlFrame() const; - bool isDataFrame() const; - bool isContinuationFrame() const; - bool hasMask() const; - quint32 getMask() const; //returns 0 if no mask - int getRsv1() const; - int getRsv2() const; - int getRsv3() const; - QWebSocketProtocol::OpCode getOpCode() const; - QByteArray getPayload() const; - - void clear(); //resets all member variables, and invalidates the object - - bool isValid() const; - - static Frame readFrame(QIODevice *pIoDevice); - -private: - 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 - 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; - - bool m_isValid; - - enum ProcessingState - { - PS_READ_HEADER, - PS_READ_PAYLOAD_LENGTH, - PS_READ_BIG_PAYLOAD_LENGTH, - PS_READ_MASK, - PS_READ_PAYLOAD, - PS_DISPATCH_RESULT, - PS_WAIT_FOR_MORE_DATA - }; - - void setError(QWebSocketProtocol::CloseCode code, QString closeReason); - bool checkValidity(); -}; - -/*! - \internal - */ -Frame::Frame() : - m_closeCode(QWebSocketProtocol::CC_NORMAL), - m_closeReason(), - m_isFinalFrame(true), - m_mask(0), - m_rsv1(0), - m_rsv2(0), - m_rsv3(0), - m_opCode(QWebSocketProtocol::OC_RESERVED_C), - m_length(0), - m_payload(), - m_isValid(false) -{ -} - -/*! - \internal - */ -Frame::Frame(const Frame &other) : - m_closeCode(other.m_closeCode), - m_closeReason(other.m_closeReason), - m_isFinalFrame(other.m_isFinalFrame), - m_mask(other.m_mask), - m_rsv1(other.m_rsv1), - m_rsv2(other.m_rsv2), - m_rsv3(other.m_rsv3), - m_opCode(other.m_opCode), - m_length(other.m_length), - m_payload(other.m_payload), - m_isValid(other.m_isValid) -{ -} - -/*! - \internal - */ -const Frame &Frame::operator =(const Frame &other) -{ - m_closeCode = other.m_closeCode; - m_closeReason = other.m_closeReason; - m_isFinalFrame = other.m_isFinalFrame; - m_mask = other.m_mask; - m_rsv1 = other.m_rsv1; - m_rsv2 = other.m_rsv2; - m_rsv3 = other.m_rsv2; - m_opCode = other.m_opCode; - m_length = other.m_length; - m_payload = other.m_payload; - m_isValid = other.m_isValid; - - return *this; -} - -/*! - \internal - */ -QWebSocketProtocol::CloseCode Frame::getCloseCode() const -{ - return m_closeCode; -} - -/*! - \internal - */ -QString Frame::getCloseReason() const -{ - return m_closeReason; -} - -/*! - \internal - */ -bool Frame::isFinalFrame() const -{ - return m_isFinalFrame; -} - -/*! - \internal - */ -bool Frame::isControlFrame() const -{ - return (m_opCode & 0x08) == 0x08; -} - -/*! - \internal - */ -bool Frame::isDataFrame() const -{ - return !isControlFrame(); -} - -/*! - \internal - */ -bool Frame::isContinuationFrame() const -{ - return isDataFrame() && (m_opCode == QWebSocketProtocol::OC_CONTINUE); -} - -/*! - \internal - */ -bool Frame::hasMask() const -{ - return m_mask != 0; -} - -/*! - \internal - */ -quint32 Frame::getMask() const -{ - return m_mask; -} - -/*! - \internal - */ -int Frame::getRsv1() const -{ - return m_rsv1; -} - -/*! - \internal - */ -int Frame::getRsv2() const -{ - return m_rsv2; -} - -/*! - \internal - */ -int Frame::getRsv3() const -{ - return m_rsv3; -} - -/*! - \internal - */ -QWebSocketProtocol::OpCode Frame::getOpCode() const -{ - return m_opCode; -} - -/*! - \internal - */ -QByteArray Frame::getPayload() const -{ - return m_payload; -} - -/*! - \internal - */ -void Frame::clear() -{ - 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 = QWebSocketProtocol::OC_RESERVED_C; - m_length = 0; - m_payload.clear(); - m_isValid = false; -} - -/*! - \internal - */ -bool Frame::isValid() const -{ - return m_isValid; -} - -#define WAIT_FOR_MORE_DATA(dataSizeInBytes) { returnState = processingState; processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; } - -/*! - \internal - */ -Frame Frame::readFrame(QIODevice *pIoDevice) -{ - bool isDone = false; - qint64 bytesRead = 0; - Frame frame; - quint64 dataWaitSize = 0; - ProcessingState processingState = PS_READ_HEADER; - ProcessingState returnState = PS_READ_HEADER; - bool hasMask = false; - quint64 payloadLength = 0; - - while (!isDone) - { - switch (processingState) - { - case PS_WAIT_FOR_MORE_DATA: - { - bool ok = pIoDevice->waitForReadyRead(5000); - if (!ok) - { - frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Timeout when reading data from socket.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - processingState = returnState; - } - break; - } - case PS_READ_HEADER: - { - if (pIoDevice->bytesAvailable() >= 2) - { - //FIN, RSV1-3, Opcode - char header[2] = {0}; - bytesRead = pIoDevice->read(header, 2); - frame.m_isFinalFrame = (header[0] & 0x80) != 0; - 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); - - //Mask, PayloadLength - hasMask = (header[1] & 0x80) != 0; - frame.m_length = (header[1] & 0x7F); - - switch (frame.m_length) - { - case 126: - { - processingState = PS_READ_PAYLOAD_LENGTH; - break; - } - case 127: - { - processingState = PS_READ_BIG_PAYLOAD_LENGTH; - break; - } - default: - { - payloadLength = frame.m_length; - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - break; - } - } - if (!frame.checkValidity()) - { - processingState = PS_DISPATCH_RESULT; - } - } - else - { - WAIT_FOR_MORE_DATA(2); - } - break; - } - - case PS_READ_PAYLOAD_LENGTH: - { - if (pIoDevice->bytesAvailable() >= 2) - { - uchar length[2] = {0}; - bytesRead = pIoDevice->read(reinterpret_cast(length), 2); - if (bytesRead == -1) - { - frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error occurred while reading from the network: %1").arg(pIoDevice->errorString())); - processingState = PS_DISPATCH_RESULT; - } - else - { - payloadLength = qFromBigEndian(reinterpret_cast(length)); - if (payloadLength < 126) - { - //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 - //"in all cases, the minimal number of bytes MUST be used to encode - //the length, for example, the length of a 124-byte-long string - //can't be encoded as the sequence 126, 0, 124" - frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 126 must be expressed as one byte.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - } - } - } - else - { - WAIT_FOR_MORE_DATA(2); - } - break; - } - - case PS_READ_BIG_PAYLOAD_LENGTH: - { - if (pIoDevice->bytesAvailable() >= 8) - { - uchar length[8] = {0}; - bytesRead = pIoDevice->read(reinterpret_cast(length), 8); - if (bytesRead < 8) - { - frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Something went wrong during reading from the network.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - //Most significant bit must be set to 0 as per http://tools.ietf.org/html/rfc6455#section-5.2 - //TODO: Do we check for that? Now we just strip off the highest bit - payloadLength = qFromBigEndian(length) & ~(1ULL << 63); - if (payloadLength <= 0xFFFFu) - { - //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 - //"in all cases, the minimal number of bytes MUST be used to encode - //the length, for example, the length of a 124-byte-long string - //can't be encoded as the sequence 126, 0, 124" - frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; - } - } - } - else - { - WAIT_FOR_MORE_DATA(8); - } - - break; - } - - case PS_READ_MASK: - { - if (pIoDevice->bytesAvailable() >= 4) - { - bytesRead = pIoDevice->read(reinterpret_cast(&frame.m_mask), sizeof(frame.m_mask)); - if (bytesRead == -1) - { - frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error while reading from the network: %1.").arg(pIoDevice->errorString())); - processingState = PS_DISPATCH_RESULT; - } - else - { - processingState = PS_READ_PAYLOAD; - } - } - else - { - WAIT_FOR_MORE_DATA(4); - } - break; - } - - case PS_READ_PAYLOAD: - { - if (!payloadLength) - { - processingState = PS_DISPATCH_RESULT; - } - else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES) - { - frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, QObject::tr("Maximum framesize exceeded.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - quint64 bytesAvailable = static_cast(pIoDevice->bytesAvailable()); - if (bytesAvailable >= payloadLength) - { - frame.m_payload = pIoDevice->read(payloadLength); - //payloadLength can be safely cast to an integer, as the MAX_FRAME_SIZE_IN_BYTES = MAX_INT - if (frame.m_payload.length() != static_cast(payloadLength)) //some error occurred; refer to the Qt documentation - { - frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Some serious error occurred while reading from the network.")); - processingState = PS_DISPATCH_RESULT; - } - else - { - if (hasMask) - { - QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask); - } - processingState = PS_DISPATCH_RESULT; - } - } - else - { - WAIT_FOR_MORE_DATA(payloadLength); //if payload is too big, then this will timeout - } - } - break; - } - - case PS_DISPATCH_RESULT: - { - processingState = PS_READ_HEADER; - isDone = true; - break; - } - - default: - { - //should not come here - qWarning() << "DataProcessor::process: Found invalid state. This should not happen!"; - frame.clear(); - isDone = true; - break; - } - } //end switch - } - - return frame; -} - -/*! - \internal - */ -void Frame::setError(QWebSocketProtocol::CloseCode code, QString closeReason) -{ - clear(); - m_closeCode = code; - m_closeReason = closeReason; - m_isValid = false; -} - -/*! - \internal - */ -bool Frame::checkValidity() -{ - if (!isValid()) - { - if (m_rsv1 || m_rsv2 || m_rsv3) - { - setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Rsv field is non-zero")); - } - else if (QWebSocketProtocol::isOpCodeReserved(m_opCode)) - { - setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Used reserved opcode")); - } - else if (isControlFrame()) - { - if (m_length > 125) - { - setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frame is larger than 125 bytes")); - } - else if (!m_isFinalFrame) - { - setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frames cannot be fragmented")); - } - else - { - m_isValid = true; - } - } - else - { - m_isValid = true; - } - } - return m_isValid; -} - -/*! - \internal - */ -DataProcessor::DataProcessor(QObject *parent) : - QObject(parent), - m_processingState(PS_READ_HEADER), - m_isFinalFrame(false), - m_isFragmented(false), - m_opCode(QWebSocketProtocol::OC_CLOSE), - m_isControlFrame(false), - m_hasMask(false), - m_mask(0), - m_binaryMessage(), - m_textMessage(), - m_payloadLength(0), - m_pConverterState(0), - m_pTextCodec(QTextCodec::codecForName("UTF-8")) -{ - clear(); -} - -/*! - \internal - */ -DataProcessor::~DataProcessor() -{ - clear(); - if (m_pConverterState) - { - delete m_pConverterState; - m_pConverterState = 0; - } -} - -/*! - \internal - */ -quint64 DataProcessor::maxMessageSize() -{ - return MAX_MESSAGE_SIZE_IN_BYTES; -} - -/*! - \internal - */ -quint64 DataProcessor::maxFrameSize() -{ - return MAX_FRAME_SIZE_IN_BYTES; -} - -/*! - \internal - */ -void DataProcessor::process(QIODevice *pIoDevice) -{ - bool isDone = false; - - while (!isDone) - { - Frame frame = Frame::readFrame(pIoDevice); - if (frame.isValid()) - { - if (frame.isControlFrame()) - { - isDone = processControlFrame(frame); - } - else //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY - { - if (!m_isFragmented && frame.isContinuationFrame()) - { - clear(); - Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, tr("Received Continuation frame, while there is nothing to continue.")); - return; - } - if (m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame()) - { - clear(); - Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, tr("All data frames after the initial data frame must have opcode 0 (continuation).")); - return; - } - if (!frame.isContinuationFrame()) - { - m_opCode = frame.getOpCode(); - m_isFragmented = !frame.isFinalFrame(); - } - 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(QWebSocketProtocol::CC_TOO_MUCH_DATA, tr("Received message is too big.")); - return; - } - - 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(QWebSocketProtocol::CC_WRONG_DATATYPE, tr("Invalid UTF-8 code encountered.")); - return; - } - else - { - m_textMessage.append(frameTxt); - Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); - } - } - else - { - m_binaryMessage.append(frame.getPayload()); - Q_EMIT binaryFrameReceived(frame.getPayload(), frame.isFinalFrame()); - } - - if (frame.isFinalFrame()) - { - if (m_opCode == QWebSocketProtocol::OC_TEXT) - { - Q_EMIT textMessageReceived(m_textMessage); - } - else - { - Q_EMIT binaryMessageReceived(m_binaryMessage); - } - clear(); - isDone = true; - } - } - } - else - { - Q_EMIT errorEncountered(frame.getCloseCode(), frame.getCloseReason()); - clear(); - isDone = true; - } - } -} - -/*! - \internal - */ -void DataProcessor::clear() -{ - m_processingState = PS_READ_HEADER; - m_isFinalFrame = false; - m_isFragmented = false; - m_opCode = QWebSocketProtocol::OC_CLOSE; - m_hasMask = false; - m_mask = 0; - m_binaryMessage.clear(); - m_textMessage.clear(); - m_payloadLength = 0; - if (m_pConverterState) - { - if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) - { - delete m_pConverterState; - m_pConverterState = 0; - } - } - if (!m_pConverterState) - { - m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull | QTextCodec::IgnoreHeader); - } -} - -/*! - \internal - */ -bool DataProcessor::processControlFrame(const Frame &frame) -{ - bool mustStopProcessing = false; - switch (frame.getOpCode()) - { - case QWebSocketProtocol::OC_PING: - { - Q_EMIT pingReceived(frame.getPayload()); - break; - } - case QWebSocketProtocol::OC_PONG: - { - Q_EMIT pongReceived(frame.getPayload()); - break; - } - case QWebSocketProtocol::OC_CLOSE: - { - quint16 closeCode = QWebSocketProtocol::CC_NORMAL; - QString closeReason; - QByteArray payload = frame.getPayload(); - if (payload.size() == 1) - { - closeCode = QWebSocketProtocol::CC_PROTOCOL_ERROR; - closeReason = tr("Payload of close frame is too small."); - } - else if (payload.size() > 1) //close frame can have a close code and reason - { - closeCode = qFromBigEndian(reinterpret_cast(payload.constData())); - if (!QWebSocketProtocol::isCloseCodeValid(closeCode)) - { - closeCode = QWebSocketProtocol::CC_PROTOCOL_ERROR; - closeReason = tr("Invalid close code %1 detected.").arg(closeCode); - } - else - { - if (payload.size() > 2) - { - QTextCodec *tc = QTextCodec::codecForName("UTF-8"); - QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); - closeReason = tc->toUnicode(payload.constData() + 2, payload.size() - 2, &state); - bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); - if (failed) - { - closeCode = QWebSocketProtocol::CC_WRONG_DATATYPE; - closeReason = tr("Invalid UTF-8 code encountered."); - } - } - } - } - mustStopProcessing = true; - Q_EMIT closeReceived(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_C: - case QWebSocketProtocol::OC_RESERVED_B: - case QWebSocketProtocol::OC_RESERVED_D: - case QWebSocketProtocol::OC_RESERVED_E: - case QWebSocketProtocol::OC_RESERVED_F: - { - //do nothing - //case added to make C++ compiler happy - break; - } - default: - { - qDebug() << "DataProcessor::processControlFrame: Invalid opcode detected:" << static_cast(frame.getOpCode()); - //Do nothing - break; - } - } - return mustStopProcessing; -} - -QT_END_NAMESPACE diff --git a/src/dataprocessor_p.h b/src/dataprocessor_p.h deleted file mode 100644 index d2d1dcb..0000000 --- a/src/dataprocessor_p.h +++ /dev/null @@ -1,102 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef DATAPROCESSOR_P_H -#define DATAPROCESSOR_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#include -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -class QIODevice; -class Frame; - -/*! - \internal - The DataProcessor class reads and interprets incoming websocket messages, and emits appropriate signals. - */ -class DataProcessor: public QObject -{ - Q_OBJECT -public: - explicit DataProcessor(QObject *parent = 0); - virtual ~DataProcessor(); - - static quint64 maxMessageSize(); - static quint64 maxFrameSize(); - -Q_SIGNALS: - void pingReceived(QByteArray data); - void pongReceived(QByteArray data); - void closeReceived(QWebSocketProtocol::CloseCode closeCode, QString closeReason); - void textFrameReceived(QString frame, bool lastFrame); - void binaryFrameReceived(QByteArray frame, bool lastFrame); - void textMessageReceived(QString message); - void binaryMessageReceived(QByteArray message); - void errorEncountered(QWebSocketProtocol::CloseCode code, QString description); - -public Q_SLOTS: - void process(QIODevice *pIoDevice); - void clear(); - -private: - Q_DISABLE_COPY(DataProcessor) - enum - { - PS_READ_HEADER, - PS_READ_PAYLOAD_LENGTH, - PS_READ_BIG_PAYLOAD_LENGTH, - PS_READ_MASK, - PS_READ_PAYLOAD, - PS_DISPATCH_RESULT - } m_processingState; - - bool m_isFinalFrame; - bool m_isFragmented; - QWebSocketProtocol::OpCode m_opCode; - bool m_isControlFrame; - bool m_hasMask; - quint32 m_mask; - QByteArray m_binaryMessage; - QString m_textMessage; - quint64 m_payloadLength; - QTextCodec::ConverterState *m_pConverterState; - QTextCodec *m_pTextCodec; - - bool processControlFrame(const Frame &frame); -}; - -QT_END_NAMESPACE - -#endif // DATAPROCESSOR_P_H diff --git a/src/doc/qwebsockets.qdoc b/src/doc/qwebsockets.qdoc deleted file mode 100644 index 49bc0f0..0000000 --- a/src/doc/qwebsockets.qdoc +++ /dev/null @@ -1,8 +0,0 @@ -/*! - \module QWebSockets - \title QWebSockets module - - The QWebSockets module implements the WebSocket protocol as specified in \l {http://tools.ietf.org/html/rfc6455} {RFC 6455}. - It solely depends on Qt (no external depencies). - The module contains 2 main classes: QWebSocket and QWebSocketServer, with which one can create client- and server applications. - */ diff --git a/src/doc/qwebsockets.qdocconfig b/src/doc/qwebsockets.qdocconfig deleted file mode 100644 index c707458..0000000 --- a/src/doc/qwebsockets.qdocconfig +++ /dev/null @@ -1,23 +0,0 @@ -include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) - -project = QWebSockets -description = QWebSockets Reference Documentation - -outputdir = html - -headerdirs += .. -headers += ../qwebsocket.h \ - ../qwebsocketserver.h - -sourcedirs += .. -sources += ../qwebsocket.cpp \ - ../qwebsocketserver.cpp \ - qwebsockets.qdoc - -exampledirs += ../../examples/ \ - ../../examples/echoclient \ - ../../examples/echoserver - -#imagedirs += ./images - -depends += qtdoc qtnetwork qtcore diff --git a/src/handshakerequest_p.cpp b/src/handshakerequest_p.cpp deleted file mode 100644 index 5243a6e..0000000 --- a/src/handshakerequest_p.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "handshakerequest_p.h" -#include -#include -#include -#include -#include -#include -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -/*! - \internal - */ -HandshakeRequest::HandshakeRequest(int port, bool isSecure) : - m_port(port), - m_isSecure(isSecure), - m_isValid(false), - m_headers(), - m_versions(), - m_key(), - m_origin(), - m_protocols(), - m_extensions(), - m_requestUrl() -{ -} - -/*! - \internal - */ -HandshakeRequest::~HandshakeRequest() -{ -} - -/*! - \internal - */ -void HandshakeRequest::clear() -{ - m_port = -1; - m_isSecure = false; - m_isValid = false; - m_headers.clear(); - m_versions.clear(); - m_key.clear(); - m_origin.clear(); - m_protocols.clear(); - m_extensions.clear(); - m_requestUrl.clear(); -} - -/*! - \internal - */ -int HandshakeRequest::getPort() const -{ - return m_requestUrl.port(m_port); -} - -/*! - \internal - */ -bool HandshakeRequest::isSecure() const -{ - return m_isSecure; -} - -/*! - \internal - */ -bool HandshakeRequest::isValid() const -{ - return m_isValid; -} - -/*! - \internal - */ -QMap HandshakeRequest::getHeaders() const -{ - return m_headers; -} - -/*! - \internal - */ -QList HandshakeRequest::getVersions() const -{ - return m_versions; -} - -/*! - \internal - */ -QString HandshakeRequest::getResourceName() const -{ - return m_requestUrl.path(); -} - -/*! - \internal - */ -QString HandshakeRequest::getKey() const -{ - return m_key; -} - -/*! - \internal - */ -QString HandshakeRequest::getHost() const -{ - return m_requestUrl.host(); -} - -/*! - \internal - */ -QString HandshakeRequest::getOrigin() const -{ - return m_origin; -} - -/*! - \internal - */ -QList HandshakeRequest::getProtocols() const -{ - return m_protocols; -} - -/*! - \internal - */ -QList HandshakeRequest::getExtensions() const -{ - return m_extensions; -} - -/*! - \internal - */ -QUrl HandshakeRequest::getRequestUrl() const -{ - return m_requestUrl; -} - -/*! - \internal - */ -QTextStream &HandshakeRequest::readFromStream(QTextStream &textStream) -{ - m_isValid = false; - clear(); - if (textStream.status() == QTextStream::Ok) - { - QString requestLine = textStream.readLine(); - QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts); - QString verb = tokens[0]; - QString resourceName = tokens[1]; - QString httpProtocol = tokens[2]; - bool conversionOk = false; - float httpVersion = httpProtocol.midRef(5).toFloat(&conversionOk); - - QString headerLine = textStream.readLine(); - m_headers.clear(); - while (!headerLine.isEmpty()) - { - QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); - m_headers.insertMulti(headerField[0], headerField[1]); - headerLine = textStream.readLine(); - } - - QString host = m_headers.value("Host", ""); - m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1()); - if (m_requestUrl.isRelative()) - { - m_requestUrl.setHost(host); - } - if (m_requestUrl.scheme().isEmpty()) - { - QString scheme = isSecure() ? "wss://" : "ws://"; - m_requestUrl.setScheme(scheme); - } - - QStringList versionLines = m_headers.values("Sec-WebSocket-Version"); - Q_FOREACH(QString versionLine, versionLines) - { - QStringList versions = versionLine.split(",", QString::SkipEmptyParts); - Q_FOREACH(QString version, versions) - { - QWebSocketProtocol::Version ver = QWebSocketProtocol::versionFromString(version.trimmed()); - m_versions << ver; - } - } - 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 - QStringList connectionLine = connection.split(",", QString::SkipEmptyParts); - QStringList connectionValues; - Q_FOREACH(QString connection, connectionLine) - { - connectionValues << connection.trimmed(); - } - - //optional headers - m_origin = m_headers.value("Sec-WebSocket-Origin", ""); - QStringList protocolLines = m_headers.values("Sec-WebSocket-Protocol"); - Q_FOREACH(QString protocolLine, protocolLines) - { - QStringList protocols = protocolLine.split(",", QString::SkipEmptyParts); - Q_FOREACH(QString protocol, protocols) - { - m_protocols << protocol.trimmed(); - } - } - QStringList extensionLines = m_headers.values("Sec-WebSocket-Extensions"); - Q_FOREACH(QString extensionLine, extensionLines) - { - QStringList extensions = extensionLine.split(",", QString::SkipEmptyParts); - Q_FOREACH(QString extension, extensions) - { - m_extensions << extension.trimmed(); - } - } - //TODO: authentication field - - m_isValid = !(host.isEmpty() || - resourceName.isEmpty() || - m_versions.isEmpty() || - m_key.isEmpty() || - (verb != "GET") || - (!conversionOk || (httpVersion < 1.1f)) || - (upgrade.toLower() != "websocket") || - (!connectionValues.contains("upgrade", Qt::CaseInsensitive))); - } - return textStream; -} - -/*! - \internal - */ -QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request) -{ - return request.readFromStream(stream); -} - -QT_END_NAMESPACE diff --git a/src/handshakerequest_p.h b/src/handshakerequest_p.h deleted file mode 100644 index c2cc154..0000000 --- a/src/handshakerequest_p.h +++ /dev/null @@ -1,87 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef HANDSHAKEREQUEST_P_H -#define HANDSHAKEREQUEST_P_H -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#include -#include - -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -class QTextStream; - -class HandshakeRequest -{ -public: - HandshakeRequest(int port, bool isSecure); - virtual ~HandshakeRequest(); - - void clear(); - - int getPort() const; - bool isSecure() const; - bool isValid() const; - QMap getHeaders() const; - QList getVersions() const; - QString getKey() const; - QString getOrigin() const; - QList getProtocols() const; - QList getExtensions() const; - QUrl getRequestUrl() const; - QString getResourceName() const; - QString getHost() const; - -private: - Q_DISABLE_COPY(HandshakeRequest) - QTextStream &readFromStream(QTextStream &textStream); - friend QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); - - int m_port; - bool m_isSecure; - bool m_isValid; - QMap m_headers; - QList m_versions; - QString m_key; - QString m_origin; - QList m_protocols; - QList m_extensions; - QUrl m_requestUrl; -}; - -QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); - -QT_END_NAMESPACE - -#endif // HANDSHAKEREQUEST_P_H diff --git a/src/handshakeresponse_p.cpp b/src/handshakeresponse_p.cpp deleted file mode 100644 index c59b875..0000000 --- a/src/handshakeresponse_p.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "handshakeresponse_p.h" -#include "handshakerequest_p.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include //for more efficient string concatenation - -QT_BEGIN_NAMESPACE - -/*! - \internal - */ -HandshakeResponse::HandshakeResponse(const HandshakeRequest &request, - const QString &serverName, - bool isOriginAllowed, - const QList &supportedVersions, - const QList &supportedProtocols, - const QList &supportedExtensions) : - m_isValid(false), - m_canUpgrade(false), - m_response(), - m_acceptedProtocol(), - m_acceptedExtension(), - m_acceptedVersion(QWebSocketProtocol::V_Unknow) -{ - m_response = getHandshakeResponse(request, serverName, isOriginAllowed, supportedVersions, supportedProtocols, supportedExtensions); - m_isValid = true; -} - -/*! - \internal - */ -HandshakeResponse::~HandshakeResponse() -{ -} - -/*! - \internal - */ -bool HandshakeResponse::isValid() const -{ - return m_isValid; -} - -/*! - \internal - */ -bool HandshakeResponse::canUpgrade() const -{ - return m_isValid && m_canUpgrade; -} - -/*! - \internal - */ -QString HandshakeResponse::getAcceptedProtocol() const -{ - return m_acceptedProtocol; -} - -/*! - \internal - */ -QString HandshakeResponse::calculateAcceptKey(const QString &key) const -{ - QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; //the UID comes from RFC6455 - QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); - return QString(hash.toBase64()); -} - -/*! - \internal - */ -QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, - const QString &serverName, - bool isOriginAllowed, - const QList &supportedVersions, - const QList &supportedProtocols, - const QList &supportedExtensions) -{ - QStringList response; - m_canUpgrade = false; - - if (!isOriginAllowed) - { - if (!m_canUpgrade) - { - response << "HTTP/1.1 403 Access Forbidden"; - } - } - else - { - if (request.isValid()) - { - 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 - - if (matchingVersions.isEmpty()) - { - m_canUpgrade = false; - } - else - { - response << "HTTP/1.1 101 Switching Protocols" << - "Upgrade: websocket" << - "Connection: Upgrade" << - "Sec-WebSocket-Accept: " % acceptKey; - if (!matchingProtocols.isEmpty()) - { - m_acceptedProtocol = matchingProtocols.first(); - response << "Sec-WebSocket-Protocol: " % m_acceptedProtocol; - } - if (!matchingExtensions.isEmpty()) - { - m_acceptedExtension = matchingExtensions.first(); - response << "Sec-WebSocket-Extensions: " % m_acceptedExtension; - } - QString origin = request.getOrigin().trimmed(); - if (origin.isEmpty()) - { - origin = "*"; - } - response << "Server: " + serverName << - "Access-Control-Allow-Credentials: false" << //do not allow credentialed request (containing cookies) - "Access-Control-Allow-Methods: GET" << //only GET is allowed during handshaking - "Access-Control-Allow-Headers: content-type" << //this is OK; only the content-type header is allowed, no other headers are accepted - "Access-Control-Allow-Origin: " % origin << - "Date: " % QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'"); - - m_acceptedVersion = QWebSocketProtocol::currentVersion(); - m_canUpgrade = true; - } - } - else - { - m_canUpgrade = false; - } - if (!m_canUpgrade) - { - response << "HTTP/1.1 400 Bad Request"; - QStringList versions; - Q_FOREACH(QWebSocketProtocol::Version version, supportedVersions) - { - versions << QString::number(static_cast(version)); - } - response << "Sec-WebSocket-Version: " % versions.join(", "); - } - } - response << "\r\n"; //append empty line at end of header - return response.join("\r\n"); -} - -/*! - \internal - */ -QTextStream &HandshakeResponse::writeToStream(QTextStream &textStream) const -{ - if (!m_response.isEmpty()) - { - textStream << m_response.toLatin1().constData(); - } - else - { - textStream.setStatus(QTextStream::WriteFailed); - } - return textStream; -} - -/*! - \internal - */ -QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response) -{ - return response.writeToStream(stream); -} - -/*! - \internal - */ -QWebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const -{ - return m_acceptedVersion; -} - -/*! - \internal - */ -QString HandshakeResponse::getAcceptedExtension() const -{ - return m_acceptedExtension; -} - -QT_END_NAMESPACE diff --git a/src/handshakeresponse_p.h b/src/handshakeresponse_p.h deleted file mode 100644 index 7b1179b..0000000 --- a/src/handshakeresponse_p.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef HANDSHAKERESPONSE_P_H -#define HANDSHAKERESPONSE_P_H -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -class HandshakeRequest; -class QString; -class QTextStream; - -class HandshakeResponse:public QObject -{ - Q_OBJECT -public: - HandshakeResponse(const HandshakeRequest &request, - const QString &serverName, - bool isOriginAllowed, - const QList &supportedVersions, - const QList &supportedProtocols, - const QList &supportedExtensions); - - virtual ~HandshakeResponse(); - - bool isValid() const; - bool canUpgrade() const; - QString getAcceptedProtocol() const; - QString getAcceptedExtension() const; - QWebSocketProtocol::Version getAcceptedVersion() const; - -public Q_SLOTS: - -Q_SIGNALS: - -private: - Q_DISABLE_COPY(HandshakeResponse) - bool m_isValid; - bool m_canUpgrade; - QString m_response; - QString m_acceptedProtocol; - QString m_acceptedExtension; - 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 &supportedProtocols, - const QList &supportedExtensions); - - QTextStream &writeToStream(QTextStream &textStream) const; - friend QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response); -}; - -QT_END_NAMESPACE - -#endif // HANDSHAKERESPONSE_P_H diff --git a/src/imports/imports.pro b/src/imports/imports.pro new file mode 100644 index 0000000..6bf8069 --- /dev/null +++ b/src/imports/imports.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += qmlwebsockets diff --git a/src/imports/qmlwebsockets/qmldir b/src/imports/qmlwebsockets/qmldir new file mode 100644 index 0000000..549c286 --- /dev/null +++ b/src/imports/qmlwebsockets/qmldir @@ -0,0 +1,3 @@ +module Qt.Playground.WebSockets + +plugin declarative_qmlwebsockets diff --git a/src/imports/qmlwebsockets/qmlwebsockets.pro b/src/imports/qmlwebsockets/qmlwebsockets.pro new file mode 100644 index 0000000..fc25714 --- /dev/null +++ b/src/imports/qmlwebsockets/qmlwebsockets.pro @@ -0,0 +1,11 @@ +QT += websockets qml + +TARGETPATH = Qt/Playground/WebSockets + +HEADERS += qmlwebsockets_plugin.h \ + qqmlwebsocket.h + +SOURCES += qmlwebsockets_plugin.cpp \ + qqmlwebsocket.cpp + +load(qml_plugin) diff --git a/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp b/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp new file mode 100644 index 0000000..d757348 --- /dev/null +++ b/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp @@ -0,0 +1,34 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qmlwebsockets_plugin.h" + +#include + +void QmlWebsocket_plugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("Qt.Playground.WebSockets")); + + int major = 1; + int minor = 0; + + // @uri Qt.Playground.WebSockets + + qmlRegisterType(uri, major, minor, "WebSocket"); +} diff --git a/src/imports/qmlwebsockets/qmlwebsockets_plugin.h b/src/imports/qmlwebsockets/qmlwebsockets_plugin.h new file mode 100644 index 0000000..cdb805f --- /dev/null +++ b/src/imports/qmlwebsockets/qmlwebsockets_plugin.h @@ -0,0 +1,40 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QMLWEBSOCKET_PLUGIN_H +#define QMLWEBSOCKET_PLUGIN_H + +#include + +#include "qqmlwebsocket.h" + +QT_BEGIN_NAMESPACE + +class QmlWebsocket_plugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri); +}; + +QT_END_NAMESPACE + +#endif // QMLWEBSOCKET_PLUGIN_H diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.cpp b/src/imports/qmlwebsockets/qqmlwebsocket.cpp new file mode 100644 index 0000000..72b1810 --- /dev/null +++ b/src/imports/qmlwebsockets/qqmlwebsocket.cpp @@ -0,0 +1,35 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qqmlwebsocket.h" + +QQmlWebSocket::QQmlWebSocket(QObject *parent) : + QObject(parent) +{ +} + +void QQmlWebSocket::classBegin() +{ + +} + +void QQmlWebSocket::componentComplete() +{ + +} diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.h b/src/imports/qmlwebsockets/qqmlwebsocket.h new file mode 100644 index 0000000..9b95c28 --- /dev/null +++ b/src/imports/qmlwebsockets/qqmlwebsocket.h @@ -0,0 +1,40 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QQMLWEBSOCKET_H +#define QQMLWEBSOCKET_H + +#include +#include + +class QQmlWebSocket : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_DISABLE_COPY(QQmlWebSocket) + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QQmlWebSocket(QObject *parent = 0); + +public: + void classBegin() Q_DECL_OVERRIDE; + void componentComplete() Q_DECL_OVERRIDE; +}; + +#endif // QQMLWEBSOCKET_H diff --git a/src/qcorsauthenticator.cpp b/src/qcorsauthenticator.cpp deleted file mode 100644 index 5c7e581..0000000 --- a/src/qcorsauthenticator.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -/*! - \class QCorsAuthenticator - - \inmodule QWebSockets - \brief The QCorsAuthenticator class provides an authenticator object for Cross Origin Requests (CORS). - - The QCorsAuthenticator class is used in the \l{QWebSocketServer::}{originAuthenticationRequired()} signal. - The class provides a way to pass back the required information to the QWebSocketServer. - It provides applications with fine-grained control over which origin URLs are allowed and which aren't. - By default, every origin is accepted. - To get fine grained control, an application connects the \l{QWebSocketServer::}{originAuthenticationRequired()} signal to - a slot. When the origin (QCorsAuthenticator::origin()) is accepted, it calls QCorsAuthenticator::setAllowed(true) - - \note 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. - \sa http://tools.ietf.org/html/rfc6455#section-10 - - \sa QWebSocketServer -*/ - -#include "qcorsauthenticator.h" -#include "qcorsauthenticator_p.h" - -QT_BEGIN_NAMESPACE - -/*! - \internal - */ -QCorsAuthenticatorPrivate::QCorsAuthenticatorPrivate(const QString &origin, bool allowed) : - m_origin(origin), - m_isAllowed(allowed) -{} - -/*! - \internal - */ -QCorsAuthenticatorPrivate::~QCorsAuthenticatorPrivate() -{} - -/*! - Constructs a new QCorsAuthencator object with the given \a origin. - \note By default, allowed() returns true. This means that per default every origin is accepted. - */ -QCorsAuthenticator::QCorsAuthenticator(const QString &origin) : - d_ptr(new QCorsAuthenticatorPrivate(origin, true)) //all origins are per default allowed -{ -} - -/*! - Destructs the object - */ -QCorsAuthenticator::~QCorsAuthenticator() -{ - if (d_ptr) - { - delete d_ptr; - } -} - -/*! - Constructs a coy of \a other - */ -QCorsAuthenticator::QCorsAuthenticator(const QCorsAuthenticator &other) : - d_ptr(new QCorsAuthenticatorPrivate(other.d_ptr->m_origin, other.d_ptr->m_isAllowed)) -{ -} - -/*! - Assigns \a other to this authenticator object - */ -QCorsAuthenticator &QCorsAuthenticator::operator =(const QCorsAuthenticator &other) -{ - Q_D(QCorsAuthenticator); - if (this != &other) - { - d->m_origin = other.d_ptr->m_origin; - d->m_isAllowed = other.d_ptr->m_isAllowed; - } - return *this; -} - -/*! - Returns the origin this autenticator is handling about. - */ -QString QCorsAuthenticator::origin() const -{ - Q_D(const QCorsAuthenticator); - return d->m_origin; -} - -/*! - Allows or disallows the origin. Setting \a allowed to true, will accept the connection request for the given origin. - Setting \a allowed to false, will reject the connection request. - - \note By default, all origins are accepted. - */ -void QCorsAuthenticator::setAllowed(bool allowed) -{ - Q_D(QCorsAuthenticator); - d->m_isAllowed = allowed; -} - -/*! - Returns true if the origin is allowed, otherwise returns false. - - \note By default, all origins are accepted. - */ -bool QCorsAuthenticator::allowed() const -{ - Q_D(const QCorsAuthenticator); - return d->m_isAllowed; -} diff --git a/src/qcorsauthenticator.h b/src/qcorsauthenticator.h deleted file mode 100644 index acf997a..0000000 --- a/src/qcorsauthenticator.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef QCORSAUTHENTICATOR_H -#define QCORSAUTHENTICATOR_H - -#include "qwebsocketsglobal.h" - -QT_BEGIN_NAMESPACE - -class QCorsAuthenticatorPrivate; - -class Q_WEBSOCKETS_EXPORT QCorsAuthenticator -{ -public: - QCorsAuthenticator(const QString &origin); - ~QCorsAuthenticator(); - QCorsAuthenticator(const QCorsAuthenticator &other); - - QCorsAuthenticator &operator =(const QCorsAuthenticator &other); - - QString origin() const; - - void setAllowed(bool allowed); - bool allowed() const; - -private: - Q_DECLARE_PRIVATE(QCorsAuthenticator) - - QCorsAuthenticatorPrivate * const d_ptr; -}; - -#endif // QCORSAUTHENTICATOR_H diff --git a/src/qcorsauthenticator_p.h b/src/qcorsauthenticator_p.h deleted file mode 100644 index ee218dc..0000000 --- a/src/qcorsauthenticator_p.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef QCORSAUTHENTICATOR_P_H -#define QCORSAUTHENTICATOR_P_H - -#include //for QT_BEGIN_NAMESPACE -#include - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// -QT_BEGIN_NAMESPACE - -class QCorsAuthenticatorPrivate -{ -public: - QCorsAuthenticatorPrivate(const QString &origin, bool allowed); - ~QCorsAuthenticatorPrivate(); - - QString m_origin; - bool m_isAllowed; -}; - -#endif // QCORSAUTHENTICATOR_P_H diff --git a/src/qwebsocket.cpp b/src/qwebsocket.cpp deleted file mode 100644 index b92b061..0000000 --- a/src/qwebsocket.cpp +++ /dev/null @@ -1,592 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -/*! - \class QWebSocket - - \inmodule QWebSockets - \brief 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. - - \sa QAbstractSocket, QTcpSocket - - \sa echoclient.html -*/ - -/*! - \page echoclient.html example - \title QWebSocket client example - \brief A sample websocket client that sends a message and displays the message that it receives back. - - \section1 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. - \section1 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 state 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() -*/ -/*! - \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() -*/ -/*! - \fn void QWebSocket::textMessageReceived(QString message); - - This signal is emitted whenever a text message is received. The \a message contains the received text. - - \sa binaryMessageReceived() -*/ -/*! - \fn void QWebSocket::binaryMessageReceived(QByteArray message); - - This signal is emitted whenever a binary message is received. The \a message contains the received bytes. - - \sa textMessageReceived() -*/ -/*! - \fn void QWebSocket::error(QAbstractSocket::SocketError error); - - This signal is emitted after an error occurred. The \a error - 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, QByteArray payload) - - Emitted when a pong message is received in reply to a previous ping. - \a elapsedTime contains the roundtrip time in milliseconds and \a payload contains an optional payload that was sent with the ping. - - \sa ping() - */ -#include "qwebsocket.h" -#include "qwebsocket_p.h" -#include -#include -#include -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -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(const QString &origin, QWebSocketProtocol::Version version, QObject *parent) : - QObject(parent), - d_ptr(new QWebSocketPrivate(origin, version, this, this)) -{ -} - -/*! - * \brief Destroys the QWebSocket. Closes the socket if it is still open, and releases any used resources. - */ -QWebSocket::~QWebSocket() -{ - delete d_ptr; - //d_ptr = 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() -{ - Q_D(QWebSocket); - d->abort(); -} - -/*! - * Returns the type of error that last occurred - * \sa errorString() - */ -QAbstractSocket::SocketError QWebSocket::error() const -{ - Q_D(const QWebSocket); - return d->error(); -} - -//only called by QWebSocketPrivate::upgradeFrom -/*! - \internal - */ -QWebSocket::QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent) : - QObject(parent), - d_ptr(new QWebSocketPrivate(pTcpSocket, version, this, this)) -{ -} - -/*! - * Returns a human-readable description of the last error that occurred - * - * \sa error() - */ -QString QWebSocket::errorString() const -{ - Q_D(const QWebSocket); - return d->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 QWebSocket 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 QWebSocket will start sending data automatically once control goes back to the event loop. - In the absence of an event loop, call waitForBytesWritten() instead. -*/ -bool QWebSocket::flush() -{ - Q_D(QWebSocket); - return d->flush(); -} - -/*! - Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. - \a message must be '\\0' terminated. - */ -qint64 QWebSocket::write(const char *message) -{ - Q_D(QWebSocket); - return d->write(message); -} - -/*! - Sends the most \a maxSize bytes of the given \a message over the socket as a text message and returns the number of bytes actually sent. - */ -qint64 QWebSocket::write(const char *message, qint64 maxSize) -{ - Q_D(QWebSocket); - return d->write(message, maxSize); -} - -/*! - \brief Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. - */ -qint64 QWebSocket::write(const QString &message) -{ - Q_D(QWebSocket); - return d->write(message); -} - -/*! - \brief Sends the given \a data over the socket as a binary message and returns the number of bytes actually sent. - */ -qint64 QWebSocket::write(const QByteArray &data) -{ - Q_D(QWebSocket); - return d->write(data); -} - -/*! - \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. - The \a closeCode is a QWebSocketProtocol::CloseCode indicating the reason to close, and - \a reason describes the reason of the closure more in detail - */ -void QWebSocket::close(QWebSocketProtocol::CloseCode closeCode, const QString &reason) -{ - Q_D(QWebSocket); - d->close(closeCode, reason); -} - -/*! - \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 - \note A client socket must *always* mask its frames; servers may *never* mask its frames - */ -void QWebSocket::open(const QUrl &url, bool mask) -{ - Q_D(QWebSocket); - d->open(url, mask); -} - -/*! - \brief Pings the server to indicate that the connection is still alive. - Additional \a payload can be sent along the ping message. - - The size of the \a payload cannot be bigger than 125. If it is larger, the \a payload is clipped to 125 bytes. - - \sa pong() - */ -void QWebSocket::ping(const QByteArray &payload) -{ - Q_D(QWebSocket); - if (payload.length() > 125) - { - payload.left(125); - } - d->ping(payload); -} - -/*! - \brief Returns the version the socket is currently using - */ -QWebSocketProtocol::Version QWebSocket::version() const -{ - Q_D(const QWebSocket); - return d->version(); -} - -/*! - \brief Returns the name of the resource currently accessed. - */ -QString QWebSocket::resourceName() const -{ - Q_D(const QWebSocket); - return d->resourceName(); -} - -/*! - \brief Returns the url the socket is connected to or will connect to. - */ -QUrl QWebSocket::requestUrl() const -{ - Q_D(const QWebSocket); - return d->requestUrl(); -} - -/*! - \brief Returns the current origin - */ -QString QWebSocket::origin() const -{ - Q_D(const QWebSocket); - return d->origin(); -} - -/*! - \brief Returns the currently used protocol. - */ -QString QWebSocket::protocol() const -{ - Q_D(const QWebSocket); - return d->protocol(); -} - -/*! - \brief Returns the currently used extension. - */ -QString QWebSocket::extension() const -{ - Q_D(const QWebSocket); - return d->extension(); -} - -/*! - \brief Returns the current state of the socket - */ -QAbstractSocket::SocketState QWebSocket::state() const -{ - Q_D(const QWebSocket); - return d->state(); -} - -/*! - \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. - - \sa connected(), open(), state() - */ -bool QWebSocket::waitForConnected(int msecs) -{ - Q_D(QWebSocket); - return d->waitForConnected(msecs); -} - -/*! - 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. - When \a msecs is -1, this function will block until the socket is disconnected. - - \sa close(), state() -*/ -bool QWebSocket::waitForDisconnected(int msecs) -{ - Q_D(QWebSocket); - return d->waitForDisconnected(msecs); -} - -/*! - Returns the local address - */ -QHostAddress QWebSocket::localAddress() const -{ - Q_D(const QWebSocket); - return d->localAddress(); -} - -/*! - Returns the local port - */ -quint16 QWebSocket::localPort() const -{ - Q_D(const QWebSocket); - return d->localPort(); -} - -/*! - Returns the pause mode of this socket - */ -QAbstractSocket::PauseModes QWebSocket::pauseMode() const -{ - Q_D(const QWebSocket); - return d->pauseMode(); -} - -/*! - Returns the peer address - */ -QHostAddress QWebSocket::peerAddress() const -{ - Q_D(const QWebSocket); - return d->peerAddress(); -} - -/*! - Returns the peerName - */ -QString QWebSocket::peerName() const -{ - Q_D(const QWebSocket); - return d->peerName(); -} - -/*! - Returns the peerport - */ -quint16 QWebSocket::peerPort() const -{ - Q_D(const QWebSocket); - return d->peerPort(); -} - -#ifndef QT_NO_NETWORKPROXY -/*! - Returns the currently configured proxy - */ -QNetworkProxy QWebSocket::proxy() const -{ - Q_D(const QWebSocket); - return d->proxy(); -} - -/*! - Sets the proxy to \a networkProxy - */ -void QWebSocket::setProxy(const QNetworkProxy &networkProxy) -{ - Q_D(QWebSocket); - d->setProxy(networkProxy); -} -#endif - -/*! - Returns the size in bytes of the readbuffer that is used by the socket. - */ -qint64 QWebSocket::readBufferSize() const -{ - Q_D(const QWebSocket); - return d->readBufferSize(); -} - -/*! - Continues data transfer on the socket. This method should only be used after the socket - has been set to pause upon notifications and a notification has been received. - The only notification currently supported is sslErrors(). - Calling this method if the socket is not paused results in undefined behavior. - - \sa pauseMode(), setPauseMode() - */ -void QWebSocket::resume() -{ - Q_D(QWebSocket); - d->resume(); -} - -/*! - Controls whether to pause upon receiving a notification. The \a pauseMode parameter specifies - the conditions in which the socket should be paused. - The only notification currently supported is sslErrors(). - If set to PauseOnSslErrors, data transfer on the socket will be paused - and needs to be enabled explicitly again by calling resume(). - By default, this option is set to PauseNever. This option must be called - before connecting to the server, otherwise it will result in undefined behavior. - - \sa pauseMode(), resume() - */ -void QWebSocket::setPauseMode(QAbstractSocket::PauseModes pauseMode) -{ - Q_D(QWebSocket); - d->setPauseMode(pauseMode); -} - -/*! - Sets the size of QWebSocket's internal read buffer to be \a size bytes. - If the buffer size is limited to a certain size, QWebSocket 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() -*/ -void QWebSocket::setReadBufferSize(qint64 size) -{ - Q_D(QWebSocket); - d->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) -{ - Q_D(QWebSocket); - d->setSocketOption(option, value); -} - -/*! - Returns the value of the option \a option. - \sa setSocketOption() -*/ -QVariant QWebSocket::socketOption(QAbstractSocket::SocketOption option) -{ - Q_D(QWebSocket); - return d->socketOption(option); -} - -/*! - Returns true if the QWebSocket is valid. - */ -bool QWebSocket::isValid() const -{ - Q_D(const QWebSocket); - return d->isValid(); -} - -QT_END_NAMESPACE diff --git a/src/qwebsocket.h b/src/qwebsocket.h deleted file mode 100644 index cffc87d..0000000 --- a/src/qwebsocket.h +++ /dev/null @@ -1,116 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKET_H -#define QWEBSOCKET_H - -#include -#include -#include -#ifndef QT_NO_NETWORKPROXY -#include -#endif -#include -#include "qwebsocketsglobal.h" -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -class QTcpSocket; -class QWebSocketPrivate; - -class Q_WEBSOCKETS_EXPORT QWebSocket:public QObject -{ - Q_OBJECT - -public: - explicit QWebSocket(const 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() const; - QHostAddress localAddress() const; - quint16 localPort() const; - QAbstractSocket::PauseModes pauseMode() const; - QHostAddress peerAddress() const; - QString peerName() const; - quint16 peerPort() const; -#ifndef QT_NO_NETWORKPROXY - QNetworkProxy proxy() const; - void setProxy(const QNetworkProxy &networkProxy); -#endif - qint64 readBufferSize() const; - void setReadBufferSize(qint64 size); - - void resume(); - void setPauseMode(QAbstractSocket::PauseModes pauseMode); - - 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() const; - QString resourceName() const; - QUrl requestUrl() const; - QString origin() const; - QString protocol() const; - QString extension() const; - - qint64 write(const char *message); //send data as text - qint64 write(const char *message, qint64 maxSize); //send data as text - qint64 write(const QString &message); //send data as text - qint64 write(const QByteArray &data); //send data as binary - -public Q_SLOTS: - void close(QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CC_NORMAL, const QString &reason = QString()); - void open(const QUrl &url, bool mask = true); - void ping(const QByteArray &payload = QByteArray()); - -Q_SIGNALS: - void aboutToClose(); - void connected(); - void disconnected(); - void stateChanged(QAbstractSocket::SocketState state); -#ifndef QT_NO_NETWORKPROXY - void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *pAuthenticator); -#endif - 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, QByteArray payload); - -private: - Q_DISABLE_COPY(QWebSocket) - Q_DECLARE_PRIVATE(QWebSocket) - QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent = 0); - QWebSocketPrivate * const d_ptr; -}; - -QT_END_NAMESPACE - -#endif // QWEBSOCKET_H diff --git a/src/qwebsocket_p.cpp b/src/qwebsocket_p.cpp deleted file mode 100644 index 054034c..0000000 --- a/src/qwebsocket_p.cpp +++ /dev/null @@ -1,1108 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "qwebsocket.h" -#include "qwebsocket_p.h" -#include "handshakerequest_p.h" -#include "handshakeresponse_p.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include //for more efficient string concatenation -#ifndef QT_NONETWORKPROXY -#include -#endif - -#include - -#include - -QT_BEGIN_NAMESPACE - -const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message - -/*! - \internal -*/ -QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : - QObject(parent), - q_ptr(pWebSocket), - m_pSocket(new QTcpSocket(this)), - m_errorString(), - m_version(version), - m_resourceName(), - m_requestUrl(), - m_origin(origin), - m_protocol(""), - m_extension(""), - m_socketState(QAbstractSocket::UnconnectedState), - m_key(), - m_mustMask(true), - m_isClosingHandshakeSent(false), - m_isClosingHandshakeReceived(false), - m_pingTimer(), - m_dataProcessor() -{ - Q_ASSERT(pWebSocket != 0); - makeConnections(m_pSocket); - qsrand(static_cast(QDateTime::currentMSecsSinceEpoch())); -} - -/*! - \internal -*/ -QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : - QObject(parent), - q_ptr(pWebSocket), - m_pSocket(pTcpSocket), - m_errorString(pTcpSocket->errorString()), - m_version(version), - m_resourceName(), - m_requestUrl(), - m_origin(), - m_protocol(), - m_extension(), - m_socketState(pTcpSocket->state()), - m_key(), - m_mustMask(true), - m_isClosingHandshakeSent(false), - m_isClosingHandshakeReceived(false), - m_pingTimer(), - m_dataProcessor() -{ - Q_ASSERT(pWebSocket != 0); - makeConnections(m_pSocket); -} - -/*! - \internal -*/ -QWebSocketPrivate::~QWebSocketPrivate() -{ - if (state() == QAbstractSocket::ConnectedState) - { - close(QWebSocketProtocol::CC_GOING_AWAY, tr("Connection closed")); - } - releaseConnections(m_pSocket); - m_pSocket->deleteLater(); - m_pSocket = 0; -} - -/*! - \internal - */ -void QWebSocketPrivate::abort() -{ - m_pSocket->abort(); -} - -/*! - \internal - */ -QAbstractSocket::SocketError QWebSocketPrivate::error() const -{ - return m_pSocket->error(); -} - -/*! - \internal - */ -QString QWebSocketPrivate::errorString() const -{ - if (!m_errorString.isEmpty()) - { - return m_errorString; - } - else - { - return m_pSocket->errorString(); - } -} - -/*! - \internal - */ -bool QWebSocketPrivate::flush() -{ - return m_pSocket->flush(); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::write(const char *message) -{ - return write(QString::fromUtf8(message)); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::write(const char *message, qint64 maxSize) -{ - return write(QString::fromUtf8(message, static_cast(maxSize))); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::write(const QString &message) -{ - return doWriteData(message.toUtf8(), false); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::write(const QByteArray &data) -{ - return doWriteData(data, true); -} - -/*! - \internal - */ -QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket, - const HandshakeRequest &request, - const HandshakeResponse &response, - QObject *parent) -{ - QWebSocket *pWebSocket = new QWebSocket(pTcpSocket, response.getAcceptedVersion(), parent); - pWebSocket->d_func()->setExtension(response.getAcceptedExtension()); - pWebSocket->d_func()->setOrigin(request.getOrigin()); - pWebSocket->d_func()->setRequestUrl(request.getRequestUrl()); - pWebSocket->d_func()->setProtocol(response.getAcceptedProtocol()); - pWebSocket->d_func()->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); - pWebSocket->d_func()->enableMasking(false); //a server should not send masked frames - - return pWebSocket; -} - -/*! - \internal - */ -void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) -{ - Q_Q(QWebSocket); - if (!m_isClosingHandshakeSent) - { - quint32 maskingKey = 0; - if (m_mustMask) - { - maskingKey = generateMaskingKey(); - } - quint16 code = qToBigEndian(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 q->aboutToClose(); - } - m_pSocket->close(); -} - -/*! - \internal - */ -void QWebSocketPrivate::open(const QUrl &url, bool mask) -{ - m_dataProcessor.clear(); - m_isClosingHandshakeReceived = false; - m_isClosingHandshakeSent = false; - - setRequestUrl(url); - QString resourceName = url.path(); - if (!url.query().isEmpty()) - { - resourceName.append("?" + url.query()); - } - if (resourceName.isEmpty()) - { - resourceName = "/"; - } - setResourceName(resourceName); - enableMasking(mask); - - setSocketState(QAbstractSocket::ConnectingState); - - m_pSocket->connectToHost(url.host(), url.port(80)); -} - -/*! - \internal - */ -void QWebSocketPrivate::ping(const QByteArray &payload) -{ - Q_ASSERT(payload.length() < 126); - m_pingTimer.restart(); - QByteArray pingFrame = getFrameHeader(QWebSocketProtocol::OC_PING, payload.size(), 0 /*do not mask*/, true); - pingFrame.append(payload); - writeFrame(pingFrame); -} - -/*! - \internal - Sets the version to use for the websocket protocol; this must be set before the socket is opened. -*/ -void QWebSocketPrivate::setVersion(QWebSocketProtocol::Version version) -{ - m_version = version; -} - -/*! - \internal - Sets the resource name of the connection; must be set before the socket is openend -*/ -void QWebSocketPrivate::setResourceName(const QString &resourceName) -{ - m_resourceName = resourceName; -} - -/*! - \internal - */ -void QWebSocketPrivate::setRequestUrl(const QUrl &requestUrl) -{ - m_requestUrl = requestUrl; -} - -/*! - \internal - */ -void QWebSocketPrivate::setOrigin(const QString &origin) -{ - m_origin = origin; -} - -/*! - \internal - */ -void QWebSocketPrivate::setProtocol(const QString &protocol) -{ - m_protocol = protocol; -} - -/*! - \internal - */ -void QWebSocketPrivate::setExtension(const QString &extension) -{ - m_extension = extension; -} - -/*! - \internal - */ -void QWebSocketPrivate::enableMasking(bool enable) -{ - m_mustMask = enable; -} - -/*! - * \internal - */ -qint64 QWebSocketPrivate::doWriteData(const QByteArray &data, bool isBinary) -{ - return doWriteFrames(data, isBinary); -} - -/*! - * \internal - */ -void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) -{ - Q_Q(QWebSocket); - //pass through signals - connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); - connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); - connect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); - connect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); - //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); - - //catch signals - connect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); - connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); - - connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); - connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); - connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); - connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); - connect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); - connect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); - connect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); - connect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); -} - -/*! - * \internal - */ -void QWebSocketPrivate::releaseConnections(const QTcpSocket *pTcpSocket) -{ - Q_Q(QWebSocket); - if (pTcpSocket) - { - //pass through signals - disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); - disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); - disconnect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); - disconnect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); - //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); - - //catched signals - disconnect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); - disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); - } - disconnect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); - disconnect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); - disconnect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); - disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); - disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); - disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); - disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); - disconnect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); -} - -/*! - \internal - */ -QWebSocketProtocol::Version QWebSocketPrivate::version() const -{ - return m_version; -} - -/*! - \internal - */ -QString QWebSocketPrivate::resourceName() const -{ - return m_resourceName; -} - -/*! - \internal - */ -QUrl QWebSocketPrivate::requestUrl() const -{ - return m_requestUrl; -} - -/*! - \internal - */ -QString QWebSocketPrivate::origin() const -{ - return m_origin; -} - -/*! - \internal - */ -QString QWebSocketPrivate::protocol() const -{ - return m_protocol; -} - -/*! - \internal - */ -QString QWebSocketPrivate::extension() const -{ - return m_extension; -} - -/*! - * \internal - */ -QByteArray QWebSocketPrivate::getFrameHeader(QWebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const -{ - QByteArray header; - quint8 byte = 0x00; - bool ok = payloadLength <= 0x7FFFFFFFFFFFFFFFULL; - - if (ok) - { - //FIN, RSV1-3, opcode - byte = static_cast((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 q_ptr->error(QAbstractSocket::DatagramTooLargeError); - qDebug() << "WebSocket::getHeader: payload too big!"; - } - - return header; -} - -/*! - * \internal - */ -qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) -{ - Q_Q(QWebSocket); - const QWebSocketProtocol::OpCode firstOpCode = isBinary ? QWebSocketProtocol::OC_BINARY : QWebSocketProtocol::OC_TEXT; - - int numFrames = data.size() / FRAME_SIZE_IN_BYTES; - QByteArray tmpData(data); - tmpData.detach(); - char *payload = tmpData.data(); - quint64 sizeLeft = static_cast(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(tr("Error writing bytes to socket: %1.").arg(m_pSocket->errorString())); - qDebug() << errorString(); - m_pSocket->flush(); - Q_EMIT q->error(QAbstractSocket::NetworkError); - break; - } - } - currentPosition += size; - bytesLeft -= size; - } - if (payloadWritten != data.size()) - { - setErrorString(tr("Bytes written %1 != %2.").arg(payloadWritten).arg(data.size())); - qDebug() << errorString(); - Q_EMIT q->error(QAbstractSocket::NetworkError); - } - return payloadWritten; -} - -/*! - * \internal - */ -quint32 QWebSocketPrivate::generateRandomNumber() const -{ - return static_cast((static_cast(qrand()) / RAND_MAX) * std::numeric_limits::max()); -} - -/*! - \internal - */ -quint32 QWebSocketPrivate::generateMaskingKey() const -{ - return generateRandomNumber(); -} - -/*! - \internal - */ -QByteArray QWebSocketPrivate::generateKey() const -{ - QByteArray key; - - for (int i = 0; i < 4; ++i) - { - quint32 tmp = generateRandomNumber(); - key.append(static_cast(static_cast(&tmp)), sizeof(quint32)); - } - - return key.toBase64(); -} - - -/*! - \internal - */ -QString QWebSocketPrivate::calculateAcceptKey(const QString &key) const -{ - QString tmpKey = key % "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); - return QString(hash.toBase64()); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::writeFrames(const QList &frames) -{ - qint64 written = 0; - for (int i = 0; i < frames.size(); ++i) - { - written += writeFrame(frames[i]); - } - return written; -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::writeFrame(const QByteArray &frame) -{ - return m_pSocket->write(frame); -} - -/*! - \internal - */ -QString readLine(QTcpSocket *pSocket) -{ - QString line; - char c; - while (pSocket->getChar(&c)) - { - if (c == '\r') - { - pSocket->getChar(&c); - break; - } - else - { - line.append(QChar(c)); - } - } - return line; -} - -//called on the client for a server handshake response -/*! - \internal - */ -void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) -{ - Q_Q(QWebSocket); - if (pSocket == 0) - { - return; - } - - bool ok = false; - QString errorDescription; - - const QString regExpStatusLine("^(HTTP/[0-9]+\\.[0-9]+)\\s([0-9]+)\\s(.*)"); - const QRegularExpression regExp(regExpStatusLine); - QString statusLine = readLine(pSocket); - QString httpProtocol; - int httpStatusCode; - QString httpStatusMessage; - QRegularExpressionMatch match = regExp.match(statusLine); - if (match.hasMatch()) - { - QStringList tokens = match.capturedTexts(); - tokens.removeFirst(); //remove the search string - if (tokens.length() == 3) - { - httpProtocol = tokens[0]; - httpStatusCode = tokens[1].toInt(); - httpStatusMessage = tokens[2].trimmed(); - ok = true; - } - } - if (!ok) - { - errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); - } - else - { - QString headerLine = readLine(pSocket); - QMap headers; - while (!headerLine.isEmpty()) - { - QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); - headers.insertMulti(headerField[0], headerField[1]); - headerLine = readLine(pSocket); - } - - QString acceptKey = headers.value("Sec-WebSocket-Accept", ""); - QString upgrade = headers.value("Upgrade", ""); - QString connection = headers.value("Connection", ""); - //unused for the moment - //QString extensions = headers.value("Sec-WebSocket-Extensions", ""); - //QString protocol = headers.value("Sec-WebSocket-Protocol", ""); - QString version = headers.value("Sec-WebSocket-Version", ""); - - if (httpStatusCode == 101) //HTTP/x.y 101 Switching Protocols - { - bool conversionOk = false; - float version = httpProtocol.midRef(5).toFloat(&conversionOk); - //TODO: do not check the httpStatusText right now - ok = !(acceptKey.isEmpty() || - (!conversionOk || (version < 1.1f)) || - (upgrade.toLower() != "websocket") || - (connection.toLower() != "upgrade")); - if (ok) - { - QString accept = calculateAcceptKey(m_key); - ok = (accept == acceptKey); - if (!ok) - { - errorDescription = tr("Accept-Key received from server %1 does not match the client key %2.").arg(acceptKey).arg(accept); - } - } - else - { - errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); - } - } - else if (httpStatusCode == 400) //HTTP/1.1 400 Bad Request - { - if (!version.isEmpty()) - { - QStringList versions = version.split(", ", QString::SkipEmptyParts); - if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) - { - //if needed to switch protocol version, then we are finished here - //because we cannot handle other protocols than the RFC one (v13) - errorDescription = tr("Handshake: Server requests a version that we don't support: %1.").arg(versions.join(", ")); - ok = false; - } - else - { - //we tried v13, but something different went wrong - errorDescription = tr("Unknown error condition encountered. Aborting connection."); - ok = false; - } - } - } - else - { - errorDescription = tr("Unhandled http status code: %1.").arg(httpStatusCode); - ok = false; - } - - if (!ok) - { - qDebug() << errorDescription; - setErrorString(errorDescription); - Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); - } - else - { - //handshake succeeded - setSocketState(QAbstractSocket::ConnectedState); - Q_EMIT q->connected(); - } - } -} - -/*! - \internal - */ -void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketState) -{ - Q_Q(QWebSocket); - QAbstractSocket::SocketState webSocketState = this->state(); - switch (socketState) - { - case QAbstractSocket::ConnectedState: - { - if (webSocketState == QAbstractSocket::ConnectingState) - { - m_key = generateKey(); - QString handshake = createHandShakeRequest(m_resourceName, m_requestUrl.host() % ":" % QString::number(m_requestUrl.port(80)), origin(), "", "", m_key); - m_pSocket->write(handshake.toLatin1()); - } - break; - } - case QAbstractSocket::ClosingState: - { - if (webSocketState == QAbstractSocket::ConnectedState) - { - setSocketState(QAbstractSocket::ClosingState); - } - break; - } - case QAbstractSocket::UnconnectedState: - { - if (webSocketState != QAbstractSocket::UnconnectedState) - { - setSocketState(QAbstractSocket::UnconnectedState); - Q_EMIT q->disconnected(); - } - break; - } - case QAbstractSocket::HostLookupState: - case QAbstractSocket::ConnectingState: - case QAbstractSocket::BoundState: - case QAbstractSocket::ListeningState: - { - //do nothing - //to make C++ compiler happy; - break; - } - default: - { - break; - } - } -} - -//order of events: -//connectToHost is called -//our socket state is set to "connecting", and tcpSocket->connectToHost is called -//the tcpsocket is opened, a handshake message is sent; a readyRead signal is thrown -//this signal is catched by processData -//when OUR socket state is in the "connecting state", this means that -//we have received data from the server (response to handshake), and that we -//should "upgrade" our socket to a websocket (connected state) -//if our socket was already upgraded, then we need to process websocket data -/*! - \internal - */ -void QWebSocketPrivate::processData() -{ - while (m_pSocket->bytesAvailable()) - { - if (state() == QAbstractSocket::ConnectingState) - { - processHandshake(m_pSocket); - } - else - { - m_dataProcessor.process(m_pSocket); - } - } -} - -/*! - \internal - */ -void QWebSocketPrivate::processPing(QByteArray data) -{ - quint32 maskingKey = 0; - if (m_mustMask) - { - maskingKey = generateMaskingKey(); - } - m_pSocket->write(getFrameHeader(QWebSocketProtocol::OC_PONG, data.size(), maskingKey, true)); - if (data.size() > 0) - { - if (m_mustMask) - { - QWebSocketProtocol::mask(&data, maskingKey); - } - m_pSocket->write(data); - } -} - -/*! - \internal - */ -void QWebSocketPrivate::processPong(QByteArray data) -{ - Q_Q(QWebSocket); - Q_EMIT q->pong(static_cast(m_pingTimer.elapsed()), data); -} - -/*! - \internal - */ -void QWebSocketPrivate::processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason) -{ - m_isClosingHandshakeReceived = true; - close(closeCode, closeReason); -} - -/*! - \internal - */ -QString QWebSocketPrivate::createHandShakeRequest(QString resourceName, - QString host, - QString origin, - QString extensions, - QString protocols, - QByteArray key) -{ - QStringList handshakeRequest; - - handshakeRequest << "GET " % resourceName % " HTTP/1.1" << - "Host: " % host << - "Upgrade: websocket" << - "Connection: Upgrade" << - "Sec-WebSocket-Key: " % QString(key); - if (!origin.isEmpty()) - { - handshakeRequest << "Origin: " % origin; - } - handshakeRequest << "Sec-WebSocket-Version: " % QString::number(QWebSocketProtocol::currentVersion()); - if (extensions.length() > 0) - { - handshakeRequest << "Sec-WebSocket-Extensions: " % extensions; - } - if (protocols.length() > 0) - { - handshakeRequest << "Sec-WebSocket-Protocol: " % protocols; - } - handshakeRequest << "\r\n"; - - return handshakeRequest.join("\r\n"); -} - -/*! - \internal - */ -QAbstractSocket::SocketState QWebSocketPrivate::state() const -{ - return m_socketState; -} - -/*! - \internal - */ -bool QWebSocketPrivate::waitForConnected(int msecs) -{ - return m_pSocket->waitForConnected(msecs); -} - -/*! - \internal - */ -bool QWebSocketPrivate::waitForDisconnected(int msecs) -{ - return m_pSocket->waitForDisconnected(msecs); -} - -/*! - \internal - */ -void QWebSocketPrivate::setSocketState(QAbstractSocket::SocketState state) -{ - Q_Q(QWebSocket); - if (m_socketState != state) - { - m_socketState = state; - Q_EMIT q->stateChanged(m_socketState); - } -} - -/*! - \internal - */ -void QWebSocketPrivate::setErrorString(const QString &errorString) -{ - m_errorString = errorString; -} - -/*! - \internal - */ -QHostAddress QWebSocketPrivate::localAddress() const -{ - return m_pSocket->localAddress(); -} - -/*! - \internal - */ -quint16 QWebSocketPrivate::localPort() const -{ - return m_pSocket->localPort(); -} - -/*! - \internal - */ -QAbstractSocket::PauseModes QWebSocketPrivate::pauseMode() const -{ - return m_pSocket->pauseMode(); -} - -/*! - \internal - */ -QHostAddress QWebSocketPrivate::peerAddress() const -{ - return m_pSocket->peerAddress(); -} - -/*! - \internal - */ -QString QWebSocketPrivate::peerName() const -{ - return m_pSocket->peerName(); -} - -/*! - \internal - */ -quint16 QWebSocketPrivate::peerPort() const -{ - return m_pSocket->peerPort(); -} - -/*! - \internal - */ -QNetworkProxy QWebSocketPrivate::proxy() const -{ - return m_pSocket->proxy(); -} - -/*! - \internal - */ -qint64 QWebSocketPrivate::readBufferSize() const -{ - return m_pSocket->readBufferSize(); -} - -/*! - \internal - */ -void QWebSocketPrivate::resume() -{ - m_pSocket->resume(); -} - -/*! - \internal - */ -void QWebSocketPrivate::setPauseMode(QAbstractSocket::PauseModes pauseMode) -{ - m_pSocket->setPauseMode(pauseMode); -} - -/*! - \internal - */ -void QWebSocketPrivate::setProxy(const QNetworkProxy &networkProxy) -{ - m_pSocket->setProxy(networkProxy); -} - -/*! - \internal - */ -void QWebSocketPrivate::setReadBufferSize(qint64 size) -{ - m_pSocket->setReadBufferSize(size); -} - -/*! - \internal - */ -void QWebSocketPrivate::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) -{ - m_pSocket->setSocketOption(option, value); -} - -/*! - \internal - */ -QVariant QWebSocketPrivate::socketOption(QAbstractSocket::SocketOption option) -{ - return m_pSocket->socketOption(option); -} - -/*! - \internal - */ -bool QWebSocketPrivate::isValid() const -{ - return m_pSocket->isValid(); -} - -QT_END_NAMESPACE diff --git a/src/qwebsocket_p.h b/src/qwebsocket_p.h deleted file mode 100644 index e4cd657..0000000 --- a/src/qwebsocket_p.h +++ /dev/null @@ -1,183 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKET_P_H -#define QWEBSOCKET_P_H -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#ifndef QT_NO_NETWORKPROXY -#include -#endif -#include -#include "qwebsocketsglobal.h" -#include "qwebsocketprotocol.h" -#include "dataprocessor_p.h" - -QT_BEGIN_NAMESPACE - -class HandshakeRequest; -class HandshakeResponse; -class QTcpSocket; -class QWebSocket; - -class QWebSocketPrivate:public QObject -{ - Q_OBJECT - -public: - explicit QWebSocketPrivate(const QString &origin, - QWebSocketProtocol::Version version, - QWebSocket * const pWebSocket, - QObject *parent = 0); - virtual ~QWebSocketPrivate(); - - void abort(); - QAbstractSocket::SocketError error() const; - QString errorString() const; - bool flush(); - bool isValid() const; - QHostAddress localAddress() const; - quint16 localPort() const; - QAbstractSocket::PauseModes pauseMode() const; - QHostAddress peerAddress() const; - QString peerName() const; - quint16 peerPort() const; -#ifndef QT_NO_NETWORKPROXY - QNetworkProxy proxy() const; - void setProxy(const QNetworkProxy &networkProxy); -#endif - qint64 readBufferSize() const; - void resume(); - void setPauseMode(QAbstractSocket::PauseModes pauseMode); - 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); - bool waitForDisconnected(int msecs); - - QWebSocketProtocol::Version version() const; - QString resourceName() const; - QUrl requestUrl() const; - QString origin() const; - QString protocol() const; - QString extension() const; - - qint64 write(const char *message); //send data as text - qint64 write(const char *message, qint64 maxSize); //send data as text - qint64 write(const QString &message); //send data as text - qint64 write(const QByteArray &data); //send data as binary - -public Q_SLOTS: - void close(QWebSocketProtocol::CloseCode closeCode, QString reason); - void open(const QUrl &url, bool mask); - void ping(const QByteArray &payload); - -private Q_SLOTS: - void processData(); - void processPing(QByteArray data); - void processPong(QByteArray data); - void processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason); - void processHandshake(QTcpSocket *pSocket); - void processStateChanged(QAbstractSocket::SocketState socketState); - -private: - Q_DISABLE_COPY(QWebSocketPrivate) - Q_DECLARE_PUBLIC(QWebSocket) - - QWebSocket * const q_ptr; - - QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent = 0); - void setVersion(QWebSocketProtocol::Version version); - void setResourceName(const QString &resourceName); - void setRequestUrl(const QUrl &requestUrl); - void setOrigin(const QString &origin); - void setProtocol(const QString &protocol); - void setExtension(const QString &extension); - void enableMasking(bool enable); - void setSocketState(QAbstractSocket::SocketState state); - void setErrorString(const 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); - - static QWebSocket *upgradeFrom(QTcpSocket *tcpSocket, - const HandshakeRequest &request, - const HandshakeResponse &response, - QObject *parent = 0); - - quint32 generateMaskingKey() const; - QByteArray generateKey() const; - quint32 generateRandomNumber() const; - qint64 writeFrames(const QList &frames); - qint64 writeFrame(const QByteArray &frame); - - 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; - - friend class QWebSocketServerPrivate; -}; - -QT_END_NAMESPACE - -#endif // QWEBSOCKET_H diff --git a/src/qwebsocketprotocol.cpp b/src/qwebsocketprotocol.cpp deleted file mode 100644 index 4e02cd5..0000000 --- a/src/qwebsocketprotocol.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "qwebsocketprotocol.h" -#include -#include -#include - -/*! - \enum WebSocketProtocol::CloseCode - - The close codes supported by WebSockets V13 - - \value CC_NORMAL Normal closure - \value CC_GOING_AWAY Going away - \value CC_PROTOCOL_ERROR Protocol error - \value CC_DATATYPE_NOT_SUPPORTED Unsupported data - \value CC_RESERVED_1004 Reserved - \value CC_MISSING_STATUS_CODE No status received - \value CC_ABNORMAL_DISCONNECTION Abnormal closure - \value CC_WRONG_DATATYPE Invalid frame payload data - \value CC_POLICY_VIOLATED Policy violation - \value CC_TOO_MUCH_DATA Message too big - \value CC_MISSING_EXTENSION Mandatory extension missing - \value CC_BAD_OPERATION Internal server error - \value CC_TLS_HANDSHAKE_FAILED TLS handshake failed - - \sa \l{QWebSocket::} {close()} -*/ -/*! - \enum WebSocketProtocol::Version - - \brief The different defined versions of the Websocket protocol. - - For an overview of the differences between the different protocols, see - - - \value V_Unknow - \value 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. - Attribute: Sec-WebSocket-Draft value 0. - \value 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 - Sec-WebSocket-Draft renamed to Sec-WebSocket-Version - Sec-WebSocket-Version = 4 - \value V_5 hybi-05: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-05.txt. - Sec-WebSocket-Version = 5 - Removed Sec-WebSocket-Nonce - Added Sec-WebSocket-Accept - \value V_6 Sec-WebSocket-Version = 6. - \value V_7 hybi-07: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07. - Sec-WebSocket-Version = 7 - \value 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 - Internal error results in 1006 - \value V_13 hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and RFC 6455. - Sec-WebSocket-Version = 13 - Status code 1004 is now reserved - Added 1008, 1009 and 1010 - Must support TLS - Clarify multiple version support - \value 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 -*/ - -QT_BEGIN_NAMESPACE - -/** - * @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 - -QT_END_NAMESPACE diff --git a/src/qwebsocketprotocol.h b/src/qwebsocketprotocol.h deleted file mode 100644 index 2d1bea3..0000000 --- a/src/qwebsocketprotocol.h +++ /dev/null @@ -1,108 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKETPROTOCOL_H -#define QWEBSOCKETPROTOCOL_H - -#include - -QT_BEGIN_NAMESPACE - -class QString; -class QByteArray; - -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_C = 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 - -QT_END_NAMESPACE - -#endif // QWEBSOCKETPROTOCOL_H diff --git a/src/qwebsockets.pri b/src/qwebsockets.pri deleted file mode 100644 index c701c93..0000000 --- a/src/qwebsockets.pri +++ /dev/null @@ -1,28 +0,0 @@ -QT *= network - -SOURCES += \ - $$PWD/qwebsocket.cpp \ - $$PWD/qwebsocket_p.cpp \ - $$PWD/qwebsocketserver.cpp \ - $$PWD/qwebsocketserver_p.cpp \ - $$PWD/qwebsocketprotocol.cpp \ - $$PWD/handshakerequest_p.cpp \ - $$PWD/handshakeresponse_p.cpp \ - $$PWD/dataprocessor_p.cpp \ - $$PWD/qcorsauthenticator.cpp - -HEADERS += \ - $$PWD/qwebsocket.h \ - $$PWD/qwebsocket_p.h \ - $$PWD/qwebsocketserver.h \ - $$PWD/qwebsocketserver_p.h \ - $$PWD/qwebsocketprotocol.h \ - $$PWD/handshakerequest_p.h \ - $$PWD/handshakeresponse_p.h \ - $$PWD/dataprocessor_p.h \ - $$PWD/qwebsocketsglobal.h \ - $$PWD/qcorsauthenticator.h \ - qcorsauthenticator_p.h - -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD diff --git a/src/qwebsockets.pro b/src/qwebsockets.pro deleted file mode 100644 index 0480ec3..0000000 --- a/src/qwebsockets.pro +++ /dev/null @@ -1,10 +0,0 @@ -QMAKE_DOCS = $$PWD/doc/qwebsockets.qdocconfig - -include($$PWD/qwebsockets.pri) - -TEMPLATE = lib -VERSION = 0.9 -TARGET = QWebSockets - -mac:QMAKE_FRAMEWORK_BUNDLE_NAME = $$TARGET -mac:QMAKE_CXXFLAGS += -Wall -Werror -Wextra diff --git a/src/qwebsocketserver.cpp b/src/qwebsocketserver.cpp deleted file mode 100644 index 03c0ec2..0000000 --- a/src/qwebsocketserver.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -/*! - \class QWebSocketServer - - \inmodule QWebSockets - - \brief Implements 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 QWebSocketServer. - This class makes it possible to accept incoming websocket connections. - You can specify the port or have QWebSocketServer pick one automatically. - You can listen on a specific address or on all the machine's addresses. - Call listen() to have the server listen for incoming connections. - - The newConnection() signal is then emitted each time a client connects to the server. - Call nextPendingConnection() to accept the pending connection as a connected QWebSocket. - The function returns a pointer to a QWebSocket in QAbstractSocket::ConnectedState that you can use for communicating with the client. - If an error occurs, serverError() returns the type of error, and errorString() can be called to get a human readable description of what happened. - When listening for connections, the address and port on which the server is listening are available as serverAddress() and serverPort(). - Calling close() makes QWebSocketServer stop listening for incoming connections. - Although QWebSocketServer 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. - - \sa echoserver.html - - \sa QWebSocket -*/ - -/*! - \page echoserver.html example - \title WebSocket server example - \brief A sample websocket server echoing back messages sent to it. - - \section1 Description - The echoserver example implements a web socket server that echoes back everything that is sent to it. - \section1 Code - We start by creating a QWebSocketServer (`new QWebSocketServer()`). 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 QWebSocket 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 QWebSocketServer::acceptError(QAbstractSocket::SocketError socketError) - This signal is emitted when accepting a new connection results in an error. - The \a socketError parameter describes the type of error that occurred - - \sa pauseAccepting(), resumeAccepting() -*/ - -/*! - \fn void QWebSocketServer::newConnection() - This signal is emitted every time a new connection is available. - - \sa hasPendingConnections(), nextPendingConnection() -*/ - -/*! - \fn void QWebSocketServer::originAuthenticationRequired(QCorsAuthenticator *authenticator) - This signal is emitted when a new connection is requested. - The slot connected to this signal should indicate whether the origin (which can be determined by the origin() call) - is allowed in the \a authenticator object (by issuing \l{QCorsAuthenticator::}{setAllowed()}) - - If no slot is connected to this signal, all origins will be accepted by default. - - \note It is not possible to use a QueuedConnection to connect to - this signal, as the connection will always succeed. -*/ - -#include -#include -#include -#include "qwebsocketprotocol.h" -#include "qwebsocket.h" -#include "qwebsocketserver.h" -#include "qwebsocketserver_p.h" - -//TODO: CorsCheck: give list in constructor or use CorsAuthenticator object -//in QNetworkAccessManager the signal cannot be connected to a queued signal, because it waits for the signal to return - -QT_BEGIN_NAMESPACE - -/*! - Constructs a new WebSocketServer with the given \a serverName. - The \a serverName will be used in the http handshake phase to identify the server. - - \a parent is passed to the QObject constructor. - */ -QWebSocketServer::QWebSocketServer(const QString &serverName, QObject *parent) : - QObject(parent), - d_ptr(new QWebSocketServerPrivate(serverName, this, this)) -{ -} - -/*! - 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() -{ - delete d_ptr; -} - -/*! - Closes the server. The server will no longer listen for incoming connections. - */ -void QWebSocketServer::close() -{ - Q_D(QWebSocketServer); - d->close(); -} - -/*! - Returns a human readable description of the last error that occurred. - - \sa serverError() -*/ -QString QWebSocketServer::errorString() const -{ - Q_D(const QWebSocketServer); - return d->errorString(); -} - -/*! - Returns true if the server has pending connections; otherwise returns false. - - \sa nextPendingConnection(), setMaxPendingConnections() - */ -bool QWebSocketServer::hasPendingConnections() const -{ - Q_D(const QWebSocketServer); - return d->hasPendingConnections(); -} - -/*! - Returns true if the server is currently listening for incoming connections; otherwise returns false. - - \sa listen() - */ -bool QWebSocketServer::isListening() const -{ - Q_D(const QWebSocketServer); - return d->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. - 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) -{ - Q_D(QWebSocketServer); - return d->listen(address, port); -} - -/*! - Returns the maximum number of pending accepted connections. The default is 30. - - \sa setMaxPendingConnections(), hasPendingConnections() - */ -int QWebSocketServer::maxPendingConnections() const -{ - Q_D(const QWebSocketServer); - return d->maxPendingConnections(); -} - -/*! - 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() -{ - Q_D(QWebSocketServer); - return d->nextPendingConnection(); -} - -/*! - Pauses incoming new connections. Queued connections will remain in queue. - \sa resumeAccepting() - */ -void QWebSocketServer::pauseAccepting() -{ - Q_D(QWebSocketServer); - d->pauseAccepting(); -} - -#ifndef QT_NO_NETWORKPROXY -/*! - Returns the network proxy for this socket. By default QNetworkProxy::DefaultProxy is used. - - \sa setProxy() -*/ -QNetworkProxy QWebSocketServer::proxy() const -{ - Q_D(const QWebSocketServer); - return d->proxy(); -} - -/*! - \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) -{ - Q_D(QWebSocketServer); - d->setProxy(networkProxy); -} -#endif -/*! - Resumes accepting new connections. - \sa pauseAccepting() - */ -void QWebSocketServer::resumeAccepting() -{ - Q_D(QWebSocketServer); - d->resumeAccepting(); -} - -/*! - Sets the server name that will be used during the http handshake phase to the given \a serverName. - Existing connected clients will not be notified of this change, only newly connecting clients - will see this new name. - */ -void QWebSocketServer::setServerName(const QString &serverName) -{ - Q_D(QWebSocketServer); - d->setServerName(serverName); -} - -/*! - Returns the server name that is used during the http handshake phase. - */ -QString QWebSocketServer::serverName() const -{ - Q_D(const QWebSocketServer); - return d->serverName(); -} - -/*! - Returns the server's address if the server is listening for connections; otherwise returns QHostAddress::Null. - - \sa serverPort(), listen() - */ -QHostAddress QWebSocketServer::serverAddress() const -{ - Q_D(const QWebSocketServer); - return d->serverAddress(); -} - -/*! - Returns an error code for the last error that occurred. - \sa errorString() - */ -QAbstractSocket::SocketError QWebSocketServer::serverError() const -{ - Q_D(const QWebSocketServer); - return d->serverError(); -} - -/*! - Returns the server's port if the server is listening for connections; otherwise returns 0. - \sa serverAddress(), listen() - */ -quint16 QWebSocketServer::serverPort() const -{ - Q_D(const QWebSocketServer); - return d->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. - 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(), hasPendingConnections() - */ -void QWebSocketServer::setMaxPendingConnections(int numConnections) -{ - Q_D(QWebSocketServer); - d->setMaxPendingConnections(numConnections); -} - -/*! - 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. - The socket is assumed to be in listening state. - - \sa socketDescriptor(), isListening() - */ -bool QWebSocketServer::setSocketDescriptor(int socketDescriptor) -{ - Q_D(QWebSocketServer); - return d->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(), isListening() - */ -int QWebSocketServer::socketDescriptor() const -{ - Q_D(const QWebSocketServer); - return d->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(), nextPendingConnection() -*/ -bool QWebSocketServer::waitForNewConnection(int msec, bool *timedOut) -{ - Q_D(QWebSocketServer); - return d->waitForNewConnection(msec, timedOut); -} - -/*! - Returns a list of websocket versions that this server is supporting. - */ -QList QWebSocketServer::supportedVersions() const -{ - Q_D(const QWebSocketServer); - return d->supportedVersions(); -} - -/*! - Returns a list of websocket subprotocols that this server supports. - */ -QList QWebSocketServer::supportedProtocols() const -{ - Q_D(const QWebSocketServer); - return d->supportedProtocols(); -} - -/*! - Returns a list of websocket extensions that this server supports. - */ -QList QWebSocketServer::supportedExtensions() const -{ - Q_D(const QWebSocketServer); - return d->supportedExtensions(); -} - -QT_END_NAMESPACE diff --git a/src/qwebsocketserver.h b/src/qwebsocketserver.h deleted file mode 100644 index 6579b53..0000000 --- a/src/qwebsocketserver.h +++ /dev/null @@ -1,92 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKETSERVER_H -#define QWEBSOCKETSERVER_H - -#include -#include -#include -#include "qwebsocketsglobal.h" -#include "qwebsocketprotocol.h" - -QT_BEGIN_NAMESPACE - -class QWebSocketServerPrivate; -class QWebSocket; -class QCorsAuthenticator; - -class Q_WEBSOCKETS_EXPORT QWebSocketServer : public QObject -{ - Q_OBJECT - -public: - explicit QWebSocketServer(const QString &serverName, QObject *parent = 0); - virtual ~QWebSocketServer(); - - bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); - void close(); - - bool isListening() const; - - void setMaxPendingConnections(int numConnections); - int maxPendingConnections() const; - - quint16 serverPort() const; - QHostAddress serverAddress() const; - - bool setSocketDescriptor(int socketDescriptor); - int socketDescriptor() const; - - bool waitForNewConnection(int msec = 0, bool *timedOut = 0); - bool hasPendingConnections() const; - virtual QWebSocket *nextPendingConnection(); - - QAbstractSocket::SocketError serverError() const; - QString errorString() const; - - void pauseAccepting(); - void resumeAccepting(); - - void setServerName(const QString &serverName); - QString serverName() const; - -#ifndef QT_NO_NETWORKPROXY - void setProxy(const QNetworkProxy &networkProxy); - QNetworkProxy proxy() const; -#endif - - QList supportedVersions() const; - QList supportedProtocols() const; - QList supportedExtensions() const; - -Q_SIGNALS: - void acceptError(QAbstractSocket::SocketError socketError); - void originAuthenticationRequired(QCorsAuthenticator *pAuthenticator); - void newConnection(); - -private: - Q_DISABLE_COPY(QWebSocketServer) - Q_DECLARE_PRIVATE(QWebSocketServer) - QWebSocketServerPrivate * const d_ptr; -}; - -QT_END_NAMESPACE - -#endif // QWEBSOCKETSERVER_H diff --git a/src/qwebsocketserver_p.cpp b/src/qwebsocketserver_p.cpp deleted file mode 100644 index 8e4bc7b..0000000 --- a/src/qwebsocketserver_p.cpp +++ /dev/null @@ -1,362 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include -#include -#include -#include "qwebsocketserver.h" -#include "qwebsocketserver_p.h" -#include "qwebsocketprotocol.h" -#include "handshakerequest_p.h" -#include "handshakeresponse_p.h" -#include "qwebsocket.h" -#include "qwebsocket_p.h" -#include "qcorsauthenticator.h" - -QT_BEGIN_NAMESPACE - -/*! - \internal - */ -QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName, QWebSocketServer * const pWebSocketServer, QObject *parent) : - QObject(parent), - q_ptr(pWebSocketServer), - m_pTcpServer(0), - m_serverName(serverName), - m_pendingConnections() -{ - Q_ASSERT(pWebSocketServer != 0); - m_pTcpServer = new QTcpServer(this); - connect(m_pTcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), q_ptr, SIGNAL(acceptError(QAbstractSocket::SocketError))); - connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); -} - -/*! - \internal - */ -QWebSocketServerPrivate::~QWebSocketServerPrivate() -{ - while (!m_pendingConnections.isEmpty()) - { - QWebSocket *pWebSocket = m_pendingConnections.dequeue(); - pWebSocket->close(QWebSocketProtocol::CC_GOING_AWAY, tr("Server closed.")); - pWebSocket->deleteLater(); - } - m_pTcpServer->deleteLater(); -} - -/*! - \internal - */ -void QWebSocketServerPrivate::close() -{ - m_pTcpServer->close(); -} - -/*! - \internal - */ -QString QWebSocketServerPrivate::errorString() const -{ - return m_pTcpServer->errorString(); -} - -/*! - \internal - */ -bool QWebSocketServerPrivate::hasPendingConnections() const -{ - return !m_pendingConnections.isEmpty(); -} - -/*! - \internal - */ -bool QWebSocketServerPrivate::isListening() const -{ - return m_pTcpServer->isListening(); -} - -/*! - \internal - */ -bool QWebSocketServerPrivate::listen(const QHostAddress &address, quint16 port) -{ - return m_pTcpServer->listen(address, port); -} - -/*! - \internal - */ -int QWebSocketServerPrivate::maxPendingConnections() const -{ - return m_pTcpServer->maxPendingConnections(); -} - -/*! - \internal - */ -void QWebSocketServerPrivate::addPendingConnection(QWebSocket *pWebSocket) -{ - if (m_pendingConnections.size() < maxPendingConnections()) - { - m_pendingConnections.enqueue(pWebSocket); - } -} - -/*! - \internal - */ -QWebSocket *QWebSocketServerPrivate::nextPendingConnection() -{ - QWebSocket *pWebSocket = 0; - if (!m_pendingConnections.isEmpty()) - { - pWebSocket = m_pendingConnections.dequeue(); - } - return pWebSocket; -} - -/*! - \internal - */ -void QWebSocketServerPrivate::pauseAccepting() -{ - m_pTcpServer->pauseAccepting(); -} - -#ifndef QT_NO_NETWORKPROXY -/*! - \internal - */ -QNetworkProxy QWebSocketServerPrivate::proxy() const -{ - return m_pTcpServer->proxy(); -} - -/*! - \internal - */ -void QWebSocketServerPrivate::setProxy(const QNetworkProxy &networkProxy) -{ - m_pTcpServer->setProxy(networkProxy); -} -#endif -/*! - \internal - */ -void QWebSocketServerPrivate::resumeAccepting() -{ - m_pTcpServer->resumeAccepting(); -} - -/*! - \internal - */ -QHostAddress QWebSocketServerPrivate::serverAddress() const -{ - return m_pTcpServer->serverAddress(); -} - -/*! - \internal - */ -QAbstractSocket::SocketError QWebSocketServerPrivate::serverError() const -{ - return m_pTcpServer->serverError(); -} - -/*! - \internal - */ -quint16 QWebSocketServerPrivate::serverPort() const -{ - return m_pTcpServer->serverPort(); -} - -/*! - \internal - */ -void QWebSocketServerPrivate::setMaxPendingConnections(int numConnections) -{ - m_pTcpServer->setMaxPendingConnections(numConnections); -} - -/*! - \internal - */ -bool QWebSocketServerPrivate::setSocketDescriptor(int socketDescriptor) -{ - return m_pTcpServer->setSocketDescriptor(socketDescriptor); -} - -/*! - \internal - */ -int QWebSocketServerPrivate::socketDescriptor() const -{ - return m_pTcpServer->socketDescriptor(); -} - -/*! - \internal - */ -bool QWebSocketServerPrivate::waitForNewConnection(int msec, bool *timedOut) -{ - return m_pTcpServer->waitForNewConnection(msec, timedOut); -} - -/*! - \internal - */ -QList QWebSocketServerPrivate::supportedVersions() const -{ - QList supportedVersions; - supportedVersions << QWebSocketProtocol::currentVersion(); //we only support V13 - return supportedVersions; -} - -/*! - \internal - */ -QList QWebSocketServerPrivate::supportedProtocols() const -{ - QList supportedProtocols; - return supportedProtocols; //no protocols are currently supported -} - -/*! - \internal - */ -QList QWebSocketServerPrivate::supportedExtensions() const -{ - QList supportedExtensions; - return supportedExtensions; //no extensions are currently supported -} - -/*! - \internal - */ -void QWebSocketServerPrivate::setServerName(const QString &serverName) -{ - m_serverName = serverName; -} - -/*! - \internal - */ -QString QWebSocketServerPrivate::serverName() const -{ - return m_serverName; -} - -/*! - \internal - */ -void QWebSocketServerPrivate::onNewConnection() -{ - QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection(); - connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); -} - -/*! - \internal - */ -void QWebSocketServerPrivate::onCloseConnection() -{ - QTcpSocket *pTcpSocket = qobject_cast(sender()); - if (pTcpSocket != 0) - { - pTcpSocket->close(); - } -} - -/*! - \internal - */ -void QWebSocketServerPrivate::handshakeReceived() -{ - Q_Q(QWebSocketServer); - QTcpSocket *pTcpSocket = qobject_cast(sender()); - if (pTcpSocket != 0) - { - bool success = false; - bool isSecure = false; - HandshakeRequest request(pTcpSocket->peerPort(), isSecure); - QTextStream textStream(pTcpSocket); - textStream >> request; - - QCorsAuthenticator corsAuthenticator(request.getOrigin()); - Q_EMIT q->originAuthenticationRequired(&corsAuthenticator); - - HandshakeResponse response(request, - m_serverName, - corsAuthenticator.allowed(), - 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 = QWebSocketPrivate::upgradeFrom(pTcpSocket, request, response); - if (pWebSocket) - { - pWebSocket->setParent(this); - addPendingConnection(pWebSocket); - Q_EMIT q->newConnection(); - success = true; - } - else - { - //TODO: should set or emit error - qDebug() << tr("Upgrading to websocket failed."); - } - } - else - { - //TODO: should set or emit error - qDebug() << tr("Cannot upgrade to websocket."); - } - } - else - { - //TODO: should set or emit error - qDebug() << tr("Invalid response received."); - } - if (!success) - { - //TODO: should set or emit error - qDebug() << tr("Closing socket because of invalid or unsupported request."); - pTcpSocket->close(); - } - } - else - { - qWarning() << "Sender socket is NULL. This should not happen, otherwise it is a Qt bug!!!"; - } -} - -QT_END_NAMESPACE diff --git a/src/qwebsocketserver_p.h b/src/qwebsocketserver_p.h deleted file mode 100644 index 081ecec..0000000 --- a/src/qwebsocketserver_p.h +++ /dev/null @@ -1,101 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKETSERVER_P_H -#define QWEBSOCKETSERVER_P_H -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#include -#include "qwebsocket.h" - -QT_BEGIN_NAMESPACE - -class QTcpServer; -class QWebSocketServer; - -class QWebSocketServerPrivate : public QObject -{ - Q_OBJECT - -public: - explicit QWebSocketServerPrivate(const QString &serverName, QWebSocketServer * const pWebSocketServer, QObject *parent = 0); - virtual ~QWebSocketServerPrivate(); - - 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(); - void pauseAccepting(); -#ifndef QT_NO_NETWORKPROXY - QNetworkProxy proxy() const; - void setProxy(const QNetworkProxy &networkProxy); -#endif - void resumeAccepting(); - QHostAddress serverAddress() const; - QAbstractSocket::SocketError serverError() const; - quint16 serverPort() const; - void setMaxPendingConnections(int numConnections); - bool setSocketDescriptor(int socketDescriptor); - int socketDescriptor() const; - bool waitForNewConnection(int msec = 0, bool *timedOut = 0); - - QList supportedVersions() const; - QList supportedProtocols() const; - QList supportedExtensions() const; - - void setServerName(const QString &serverName); - QString serverName() const; - -Q_SIGNALS: - void newConnection(); - -private Q_SLOTS: - void onNewConnection(); - void onCloseConnection(); - void handshakeReceived(); - -private: - Q_DECLARE_PUBLIC(QWebSocketServer) - QWebSocketServer * const q_ptr; - - QTcpServer *m_pTcpServer; - QString m_serverName; - QQueue m_pendingConnections; - - void addPendingConnection(QWebSocket *pWebSocket); -}; - -QT_END_NAMESPACE - -#endif // QWEBSOCKETSERVER_P_H diff --git a/src/qwebsocketsglobal.h b/src/qwebsocketsglobal.h deleted file mode 100644 index e7eb583..0000000 --- a/src/qwebsocketsglobal.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -QWebSockets implements the WebSocket protocol as defined in RFC 6455. -Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef QWEBSOCKETSGLOBAL_H -#define QWEBSOCKETSGLOBAL_H - -#include - -QT_BEGIN_NAMESPACE - -#ifndef QT_STATIC -# if defined(QT_BUILD_WEBSOCKETS_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/src/src.pro b/src/src.pro index e975a6b..759b5ba 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1 +1,3 @@ -include ($$PWD/qwebsockets.pro) +TEMPLATE = subdirs + +SUBDIRS += websockets imports diff --git a/src/websockets/dataprocessor_p.cpp b/src/websockets/dataprocessor_p.cpp new file mode 100644 index 0000000..f666ab3 --- /dev/null +++ b/src/websockets/dataprocessor_p.cpp @@ -0,0 +1,843 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/*! + \class DataProcessor + The class DataProcessor is responsible for reading, validating and interpreting data from a websocket. + It reads data from a QIODevice, validates it against RFC 6455, and parses it into frames (data, control). + It emits signals that correspond to the type of the frame: textFrameReceived(), binaryFrameReceived(), + textMessageReceived(), binaryMessageReceived(), pingReceived(), pongReceived() and closeReceived(). + Whenever an error is detected, the errorEncountered() signal is emitted. + DataProcessor also checks if a frame is allowed in a sequence of frames (e.g. a continuation frame cannot follow a final frame). + This class is an internal class used by QWebSocketInternal for data processing and validation. + + \sa Frame() + + \internal +*/ +#include "dataprocessor_p.h" +#include "qwebsocketprotocol.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1; +const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1; + +/*! + \class Frame + The class Frame is responsible for reading, validating and interpreting frames from a websocket. + It reads data from a QIODevice, validates it against RFC 6455, and parses it into a frame (data, control). + Whenever an error is detected, the isValid() returns false. + + \note The Frame class does not look at valid sequences of frames. It processes frames one at a time. + \note It is the DataProcessor that takes the sequence into account. + + \sa DataProcessor() + \internal + */ +class Frame +{ +public: + Frame(); + Frame(const Frame &other); + + const Frame &operator =(const Frame &other); + + QWebSocketProtocol::CloseCode getCloseCode() const; + QString getCloseReason() const; + bool isFinalFrame() const; + bool isControlFrame() const; + bool isDataFrame() const; + bool isContinuationFrame() const; + bool hasMask() const; + quint32 getMask() const; //returns 0 if no mask + int getRsv1() const; + int getRsv2() const; + int getRsv3() const; + QWebSocketProtocol::OpCode getOpCode() const; + QByteArray getPayload() const; + + void clear(); //resets all member variables, and invalidates the object + + bool isValid() const; + + static Frame readFrame(QIODevice *pIoDevice); + +private: + 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 + 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; + + bool m_isValid; + + enum ProcessingState + { + PS_READ_HEADER, + PS_READ_PAYLOAD_LENGTH, + PS_READ_BIG_PAYLOAD_LENGTH, + PS_READ_MASK, + PS_READ_PAYLOAD, + PS_DISPATCH_RESULT, + PS_WAIT_FOR_MORE_DATA + }; + + void setError(QWebSocketProtocol::CloseCode code, QString closeReason); + bool checkValidity(); +}; + +/*! + \internal + */ +Frame::Frame() : + m_closeCode(QWebSocketProtocol::CC_NORMAL), + m_closeReason(), + m_isFinalFrame(true), + m_mask(0), + m_rsv1(0), + m_rsv2(0), + m_rsv3(0), + m_opCode(QWebSocketProtocol::OC_RESERVED_C), + m_length(0), + m_payload(), + m_isValid(false) +{ +} + +/*! + \internal + */ +Frame::Frame(const Frame &other) : + m_closeCode(other.m_closeCode), + m_closeReason(other.m_closeReason), + m_isFinalFrame(other.m_isFinalFrame), + m_mask(other.m_mask), + m_rsv1(other.m_rsv1), + m_rsv2(other.m_rsv2), + m_rsv3(other.m_rsv3), + m_opCode(other.m_opCode), + m_length(other.m_length), + m_payload(other.m_payload), + m_isValid(other.m_isValid) +{ +} + +/*! + \internal + */ +const Frame &Frame::operator =(const Frame &other) +{ + m_closeCode = other.m_closeCode; + m_closeReason = other.m_closeReason; + m_isFinalFrame = other.m_isFinalFrame; + m_mask = other.m_mask; + m_rsv1 = other.m_rsv1; + m_rsv2 = other.m_rsv2; + m_rsv3 = other.m_rsv2; + m_opCode = other.m_opCode; + m_length = other.m_length; + m_payload = other.m_payload; + m_isValid = other.m_isValid; + + return *this; +} + +/*! + \internal + */ +QWebSocketProtocol::CloseCode Frame::getCloseCode() const +{ + return m_closeCode; +} + +/*! + \internal + */ +QString Frame::getCloseReason() const +{ + return m_closeReason; +} + +/*! + \internal + */ +bool Frame::isFinalFrame() const +{ + return m_isFinalFrame; +} + +/*! + \internal + */ +bool Frame::isControlFrame() const +{ + return (m_opCode & 0x08) == 0x08; +} + +/*! + \internal + */ +bool Frame::isDataFrame() const +{ + return !isControlFrame(); +} + +/*! + \internal + */ +bool Frame::isContinuationFrame() const +{ + return isDataFrame() && (m_opCode == QWebSocketProtocol::OC_CONTINUE); +} + +/*! + \internal + */ +bool Frame::hasMask() const +{ + return m_mask != 0; +} + +/*! + \internal + */ +quint32 Frame::getMask() const +{ + return m_mask; +} + +/*! + \internal + */ +int Frame::getRsv1() const +{ + return m_rsv1; +} + +/*! + \internal + */ +int Frame::getRsv2() const +{ + return m_rsv2; +} + +/*! + \internal + */ +int Frame::getRsv3() const +{ + return m_rsv3; +} + +/*! + \internal + */ +QWebSocketProtocol::OpCode Frame::getOpCode() const +{ + return m_opCode; +} + +/*! + \internal + */ +QByteArray Frame::getPayload() const +{ + return m_payload; +} + +/*! + \internal + */ +void Frame::clear() +{ + 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 = QWebSocketProtocol::OC_RESERVED_C; + m_length = 0; + m_payload.clear(); + m_isValid = false; +} + +/*! + \internal + */ +bool Frame::isValid() const +{ + return m_isValid; +} + +#define WAIT_FOR_MORE_DATA(dataSizeInBytes) { returnState = processingState; processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; } + +/*! + \internal + */ +Frame Frame::readFrame(QIODevice *pIoDevice) +{ + bool isDone = false; + qint64 bytesRead = 0; + Frame frame; + quint64 dataWaitSize = 0; + ProcessingState processingState = PS_READ_HEADER; + ProcessingState returnState = PS_READ_HEADER; + bool hasMask = false; + quint64 payloadLength = 0; + + while (!isDone) + { + switch (processingState) + { + case PS_WAIT_FOR_MORE_DATA: + { + bool ok = pIoDevice->waitForReadyRead(5000); + if (!ok) + { + frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Timeout when reading data from socket.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + processingState = returnState; + } + break; + } + case PS_READ_HEADER: + { + if (pIoDevice->bytesAvailable() >= 2) + { + //FIN, RSV1-3, Opcode + char header[2] = {0}; + bytesRead = pIoDevice->read(header, 2); + frame.m_isFinalFrame = (header[0] & 0x80) != 0; + 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); + + //Mask, PayloadLength + hasMask = (header[1] & 0x80) != 0; + frame.m_length = (header[1] & 0x7F); + + switch (frame.m_length) + { + case 126: + { + processingState = PS_READ_PAYLOAD_LENGTH; + break; + } + case 127: + { + processingState = PS_READ_BIG_PAYLOAD_LENGTH; + break; + } + default: + { + payloadLength = frame.m_length; + processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; + break; + } + } + if (!frame.checkValidity()) + { + processingState = PS_DISPATCH_RESULT; + } + } + else + { + WAIT_FOR_MORE_DATA(2); + } + break; + } + + case PS_READ_PAYLOAD_LENGTH: + { + if (pIoDevice->bytesAvailable() >= 2) + { + uchar length[2] = {0}; + bytesRead = pIoDevice->read(reinterpret_cast(length), 2); + if (bytesRead == -1) + { + frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error occurred while reading from the network: %1").arg(pIoDevice->errorString())); + processingState = PS_DISPATCH_RESULT; + } + else + { + payloadLength = qFromBigEndian(reinterpret_cast(length)); + if (payloadLength < 126) + { + //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 + //"in all cases, the minimal number of bytes MUST be used to encode + //the length, for example, the length of a 124-byte-long string + //can't be encoded as the sequence 126, 0, 124" + frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 126 must be expressed as one byte.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; + } + } + } + else + { + WAIT_FOR_MORE_DATA(2); + } + break; + } + + case PS_READ_BIG_PAYLOAD_LENGTH: + { + if (pIoDevice->bytesAvailable() >= 8) + { + uchar length[8] = {0}; + bytesRead = pIoDevice->read(reinterpret_cast(length), 8); + if (bytesRead < 8) + { + frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Something went wrong during reading from the network.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + //Most significant bit must be set to 0 as per http://tools.ietf.org/html/rfc6455#section-5.2 + //TODO: Do we check for that? Now we just strip off the highest bit + payloadLength = qFromBigEndian(length) & ~(1ULL << 63); + if (payloadLength <= 0xFFFFu) + { + //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 + //"in all cases, the minimal number of bytes MUST be used to encode + //the length, for example, the length of a 124-byte-long string + //can't be encoded as the sequence 126, 0, 124" + frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; + } + } + } + else + { + WAIT_FOR_MORE_DATA(8); + } + + break; + } + + case PS_READ_MASK: + { + if (pIoDevice->bytesAvailable() >= 4) + { + bytesRead = pIoDevice->read(reinterpret_cast(&frame.m_mask), sizeof(frame.m_mask)); + if (bytesRead == -1) + { + frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error while reading from the network: %1.").arg(pIoDevice->errorString())); + processingState = PS_DISPATCH_RESULT; + } + else + { + processingState = PS_READ_PAYLOAD; + } + } + else + { + WAIT_FOR_MORE_DATA(4); + } + break; + } + + case PS_READ_PAYLOAD: + { + if (!payloadLength) + { + processingState = PS_DISPATCH_RESULT; + } + else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES) + { + frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, QObject::tr("Maximum framesize exceeded.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + quint64 bytesAvailable = static_cast(pIoDevice->bytesAvailable()); + if (bytesAvailable >= payloadLength) + { + frame.m_payload = pIoDevice->read(payloadLength); + //payloadLength can be safely cast to an integer, as the MAX_FRAME_SIZE_IN_BYTES = MAX_INT + if (frame.m_payload.length() != static_cast(payloadLength)) //some error occurred; refer to the Qt documentation + { + frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Some serious error occurred while reading from the network.")); + processingState = PS_DISPATCH_RESULT; + } + else + { + if (hasMask) + { + QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask); + } + processingState = PS_DISPATCH_RESULT; + } + } + else + { + WAIT_FOR_MORE_DATA(payloadLength); //if payload is too big, then this will timeout + } + } + break; + } + + case PS_DISPATCH_RESULT: + { + processingState = PS_READ_HEADER; + isDone = true; + break; + } + + default: + { + //should not come here + qWarning() << "DataProcessor::process: Found invalid state. This should not happen!"; + frame.clear(); + isDone = true; + break; + } + } //end switch + } + + return frame; +} + +/*! + \internal + */ +void Frame::setError(QWebSocketProtocol::CloseCode code, QString closeReason) +{ + clear(); + m_closeCode = code; + m_closeReason = closeReason; + m_isValid = false; +} + +/*! + \internal + */ +bool Frame::checkValidity() +{ + if (!isValid()) + { + if (m_rsv1 || m_rsv2 || m_rsv3) + { + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Rsv field is non-zero")); + } + else if (QWebSocketProtocol::isOpCodeReserved(m_opCode)) + { + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Used reserved opcode")); + } + else if (isControlFrame()) + { + if (m_length > 125) + { + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frame is larger than 125 bytes")); + } + else if (!m_isFinalFrame) + { + setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frames cannot be fragmented")); + } + else + { + m_isValid = true; + } + } + else + { + m_isValid = true; + } + } + return m_isValid; +} + +/*! + \internal + */ +DataProcessor::DataProcessor(QObject *parent) : + QObject(parent), + m_processingState(PS_READ_HEADER), + m_isFinalFrame(false), + m_isFragmented(false), + m_opCode(QWebSocketProtocol::OC_CLOSE), + m_isControlFrame(false), + m_hasMask(false), + m_mask(0), + m_binaryMessage(), + m_textMessage(), + m_payloadLength(0), + m_pConverterState(0), + m_pTextCodec(QTextCodec::codecForName("UTF-8")) +{ + clear(); +} + +/*! + \internal + */ +DataProcessor::~DataProcessor() +{ + clear(); + if (m_pConverterState) + { + delete m_pConverterState; + m_pConverterState = 0; + } +} + +/*! + \internal + */ +quint64 DataProcessor::maxMessageSize() +{ + return MAX_MESSAGE_SIZE_IN_BYTES; +} + +/*! + \internal + */ +quint64 DataProcessor::maxFrameSize() +{ + return MAX_FRAME_SIZE_IN_BYTES; +} + +/*! + \internal + */ +void DataProcessor::process(QIODevice *pIoDevice) +{ + bool isDone = false; + + while (!isDone) + { + Frame frame = Frame::readFrame(pIoDevice); + if (frame.isValid()) + { + if (frame.isControlFrame()) + { + isDone = processControlFrame(frame); + } + else //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY + { + if (!m_isFragmented && frame.isContinuationFrame()) + { + clear(); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, tr("Received Continuation frame, while there is nothing to continue.")); + return; + } + if (m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame()) + { + clear(); + Q_EMIT errorEncountered(QWebSocketProtocol::CC_PROTOCOL_ERROR, tr("All data frames after the initial data frame must have opcode 0 (continuation).")); + return; + } + if (!frame.isContinuationFrame()) + { + m_opCode = frame.getOpCode(); + m_isFragmented = !frame.isFinalFrame(); + } + 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(QWebSocketProtocol::CC_TOO_MUCH_DATA, tr("Received message is too big.")); + return; + } + + 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(QWebSocketProtocol::CC_WRONG_DATATYPE, tr("Invalid UTF-8 code encountered.")); + return; + } + else + { + m_textMessage.append(frameTxt); + Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); + } + } + else + { + m_binaryMessage.append(frame.getPayload()); + Q_EMIT binaryFrameReceived(frame.getPayload(), frame.isFinalFrame()); + } + + if (frame.isFinalFrame()) + { + if (m_opCode == QWebSocketProtocol::OC_TEXT) + { + Q_EMIT textMessageReceived(m_textMessage); + } + else + { + Q_EMIT binaryMessageReceived(m_binaryMessage); + } + clear(); + isDone = true; + } + } + } + else + { + Q_EMIT errorEncountered(frame.getCloseCode(), frame.getCloseReason()); + clear(); + isDone = true; + } + } +} + +/*! + \internal + */ +void DataProcessor::clear() +{ + m_processingState = PS_READ_HEADER; + m_isFinalFrame = false; + m_isFragmented = false; + m_opCode = QWebSocketProtocol::OC_CLOSE; + m_hasMask = false; + m_mask = 0; + m_binaryMessage.clear(); + m_textMessage.clear(); + m_payloadLength = 0; + if (m_pConverterState) + { + if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) + { + delete m_pConverterState; + m_pConverterState = 0; + } + } + if (!m_pConverterState) + { + m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull | QTextCodec::IgnoreHeader); + } +} + +/*! + \internal + */ +bool DataProcessor::processControlFrame(const Frame &frame) +{ + bool mustStopProcessing = false; + switch (frame.getOpCode()) + { + case QWebSocketProtocol::OC_PING: + { + Q_EMIT pingReceived(frame.getPayload()); + break; + } + case QWebSocketProtocol::OC_PONG: + { + Q_EMIT pongReceived(frame.getPayload()); + break; + } + case QWebSocketProtocol::OC_CLOSE: + { + quint16 closeCode = QWebSocketProtocol::CC_NORMAL; + QString closeReason; + QByteArray payload = frame.getPayload(); + if (payload.size() == 1) + { + closeCode = QWebSocketProtocol::CC_PROTOCOL_ERROR; + closeReason = tr("Payload of close frame is too small."); + } + else if (payload.size() > 1) //close frame can have a close code and reason + { + closeCode = qFromBigEndian(reinterpret_cast(payload.constData())); + if (!QWebSocketProtocol::isCloseCodeValid(closeCode)) + { + closeCode = QWebSocketProtocol::CC_PROTOCOL_ERROR; + closeReason = tr("Invalid close code %1 detected.").arg(closeCode); + } + else + { + if (payload.size() > 2) + { + QTextCodec *tc = QTextCodec::codecForName("UTF-8"); + QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); + closeReason = tc->toUnicode(payload.constData() + 2, payload.size() - 2, &state); + bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); + if (failed) + { + closeCode = QWebSocketProtocol::CC_WRONG_DATATYPE; + closeReason = tr("Invalid UTF-8 code encountered."); + } + } + } + } + mustStopProcessing = true; + Q_EMIT closeReceived(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_C: + case QWebSocketProtocol::OC_RESERVED_B: + case QWebSocketProtocol::OC_RESERVED_D: + case QWebSocketProtocol::OC_RESERVED_E: + case QWebSocketProtocol::OC_RESERVED_F: + { + //do nothing + //case added to make C++ compiler happy + break; + } + default: + { + qDebug() << "DataProcessor::processControlFrame: Invalid opcode detected:" << static_cast(frame.getOpCode()); + //Do nothing + break; + } + } + return mustStopProcessing; +} + +QT_END_NAMESPACE diff --git a/src/websockets/dataprocessor_p.h b/src/websockets/dataprocessor_p.h new file mode 100644 index 0000000..d2d1dcb --- /dev/null +++ b/src/websockets/dataprocessor_p.h @@ -0,0 +1,102 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef DATAPROCESSOR_P_H +#define DATAPROCESSOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class QIODevice; +class Frame; + +/*! + \internal + The DataProcessor class reads and interprets incoming websocket messages, and emits appropriate signals. + */ +class DataProcessor: public QObject +{ + Q_OBJECT +public: + explicit DataProcessor(QObject *parent = 0); + virtual ~DataProcessor(); + + static quint64 maxMessageSize(); + static quint64 maxFrameSize(); + +Q_SIGNALS: + void pingReceived(QByteArray data); + void pongReceived(QByteArray data); + void closeReceived(QWebSocketProtocol::CloseCode closeCode, QString closeReason); + void textFrameReceived(QString frame, bool lastFrame); + void binaryFrameReceived(QByteArray frame, bool lastFrame); + void textMessageReceived(QString message); + void binaryMessageReceived(QByteArray message); + void errorEncountered(QWebSocketProtocol::CloseCode code, QString description); + +public Q_SLOTS: + void process(QIODevice *pIoDevice); + void clear(); + +private: + Q_DISABLE_COPY(DataProcessor) + enum + { + PS_READ_HEADER, + PS_READ_PAYLOAD_LENGTH, + PS_READ_BIG_PAYLOAD_LENGTH, + PS_READ_MASK, + PS_READ_PAYLOAD, + PS_DISPATCH_RESULT + } m_processingState; + + bool m_isFinalFrame; + bool m_isFragmented; + QWebSocketProtocol::OpCode m_opCode; + bool m_isControlFrame; + bool m_hasMask; + quint32 m_mask; + QByteArray m_binaryMessage; + QString m_textMessage; + quint64 m_payloadLength; + QTextCodec::ConverterState *m_pConverterState; + QTextCodec *m_pTextCodec; + + bool processControlFrame(const Frame &frame); +}; + +QT_END_NAMESPACE + +#endif // DATAPROCESSOR_P_H diff --git a/src/websockets/doc/qwebsockets.qdoc b/src/websockets/doc/qwebsockets.qdoc new file mode 100644 index 0000000..49bc0f0 --- /dev/null +++ b/src/websockets/doc/qwebsockets.qdoc @@ -0,0 +1,8 @@ +/*! + \module QWebSockets + \title QWebSockets module + + The QWebSockets module implements the WebSocket protocol as specified in \l {http://tools.ietf.org/html/rfc6455} {RFC 6455}. + It solely depends on Qt (no external depencies). + The module contains 2 main classes: QWebSocket and QWebSocketServer, with which one can create client- and server applications. + */ diff --git a/src/websockets/doc/qwebsockets.qdocconfig b/src/websockets/doc/qwebsockets.qdocconfig new file mode 100644 index 0000000..c707458 --- /dev/null +++ b/src/websockets/doc/qwebsockets.qdocconfig @@ -0,0 +1,23 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QWebSockets +description = QWebSockets Reference Documentation + +outputdir = html + +headerdirs += .. +headers += ../qwebsocket.h \ + ../qwebsocketserver.h + +sourcedirs += .. +sources += ../qwebsocket.cpp \ + ../qwebsocketserver.cpp \ + qwebsockets.qdoc + +exampledirs += ../../examples/ \ + ../../examples/echoclient \ + ../../examples/echoserver + +#imagedirs += ./images + +depends += qtdoc qtnetwork qtcore diff --git a/src/websockets/handshakerequest_p.cpp b/src/websockets/handshakerequest_p.cpp new file mode 100644 index 0000000..5243a6e --- /dev/null +++ b/src/websockets/handshakerequest_p.cpp @@ -0,0 +1,269 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "handshakerequest_p.h" +#include +#include +#include +#include +#include +#include +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + */ +HandshakeRequest::HandshakeRequest(int port, bool isSecure) : + m_port(port), + m_isSecure(isSecure), + m_isValid(false), + m_headers(), + m_versions(), + m_key(), + m_origin(), + m_protocols(), + m_extensions(), + m_requestUrl() +{ +} + +/*! + \internal + */ +HandshakeRequest::~HandshakeRequest() +{ +} + +/*! + \internal + */ +void HandshakeRequest::clear() +{ + m_port = -1; + m_isSecure = false; + m_isValid = false; + m_headers.clear(); + m_versions.clear(); + m_key.clear(); + m_origin.clear(); + m_protocols.clear(); + m_extensions.clear(); + m_requestUrl.clear(); +} + +/*! + \internal + */ +int HandshakeRequest::getPort() const +{ + return m_requestUrl.port(m_port); +} + +/*! + \internal + */ +bool HandshakeRequest::isSecure() const +{ + return m_isSecure; +} + +/*! + \internal + */ +bool HandshakeRequest::isValid() const +{ + return m_isValid; +} + +/*! + \internal + */ +QMap HandshakeRequest::getHeaders() const +{ + return m_headers; +} + +/*! + \internal + */ +QList HandshakeRequest::getVersions() const +{ + return m_versions; +} + +/*! + \internal + */ +QString HandshakeRequest::getResourceName() const +{ + return m_requestUrl.path(); +} + +/*! + \internal + */ +QString HandshakeRequest::getKey() const +{ + return m_key; +} + +/*! + \internal + */ +QString HandshakeRequest::getHost() const +{ + return m_requestUrl.host(); +} + +/*! + \internal + */ +QString HandshakeRequest::getOrigin() const +{ + return m_origin; +} + +/*! + \internal + */ +QList HandshakeRequest::getProtocols() const +{ + return m_protocols; +} + +/*! + \internal + */ +QList HandshakeRequest::getExtensions() const +{ + return m_extensions; +} + +/*! + \internal + */ +QUrl HandshakeRequest::getRequestUrl() const +{ + return m_requestUrl; +} + +/*! + \internal + */ +QTextStream &HandshakeRequest::readFromStream(QTextStream &textStream) +{ + m_isValid = false; + clear(); + if (textStream.status() == QTextStream::Ok) + { + QString requestLine = textStream.readLine(); + QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts); + QString verb = tokens[0]; + QString resourceName = tokens[1]; + QString httpProtocol = tokens[2]; + bool conversionOk = false; + float httpVersion = httpProtocol.midRef(5).toFloat(&conversionOk); + + QString headerLine = textStream.readLine(); + m_headers.clear(); + while (!headerLine.isEmpty()) + { + QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); + m_headers.insertMulti(headerField[0], headerField[1]); + headerLine = textStream.readLine(); + } + + QString host = m_headers.value("Host", ""); + m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1()); + if (m_requestUrl.isRelative()) + { + m_requestUrl.setHost(host); + } + if (m_requestUrl.scheme().isEmpty()) + { + QString scheme = isSecure() ? "wss://" : "ws://"; + m_requestUrl.setScheme(scheme); + } + + QStringList versionLines = m_headers.values("Sec-WebSocket-Version"); + Q_FOREACH(QString versionLine, versionLines) + { + QStringList versions = versionLine.split(",", QString::SkipEmptyParts); + Q_FOREACH(QString version, versions) + { + QWebSocketProtocol::Version ver = QWebSocketProtocol::versionFromString(version.trimmed()); + m_versions << ver; + } + } + 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 + QStringList connectionLine = connection.split(",", QString::SkipEmptyParts); + QStringList connectionValues; + Q_FOREACH(QString connection, connectionLine) + { + connectionValues << connection.trimmed(); + } + + //optional headers + m_origin = m_headers.value("Sec-WebSocket-Origin", ""); + QStringList protocolLines = m_headers.values("Sec-WebSocket-Protocol"); + Q_FOREACH(QString protocolLine, protocolLines) + { + QStringList protocols = protocolLine.split(",", QString::SkipEmptyParts); + Q_FOREACH(QString protocol, protocols) + { + m_protocols << protocol.trimmed(); + } + } + QStringList extensionLines = m_headers.values("Sec-WebSocket-Extensions"); + Q_FOREACH(QString extensionLine, extensionLines) + { + QStringList extensions = extensionLine.split(",", QString::SkipEmptyParts); + Q_FOREACH(QString extension, extensions) + { + m_extensions << extension.trimmed(); + } + } + //TODO: authentication field + + m_isValid = !(host.isEmpty() || + resourceName.isEmpty() || + m_versions.isEmpty() || + m_key.isEmpty() || + (verb != "GET") || + (!conversionOk || (httpVersion < 1.1f)) || + (upgrade.toLower() != "websocket") || + (!connectionValues.contains("upgrade", Qt::CaseInsensitive))); + } + return textStream; +} + +/*! + \internal + */ +QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request) +{ + return request.readFromStream(stream); +} + +QT_END_NAMESPACE diff --git a/src/websockets/handshakerequest_p.h b/src/websockets/handshakerequest_p.h new file mode 100644 index 0000000..c2cc154 --- /dev/null +++ b/src/websockets/handshakerequest_p.h @@ -0,0 +1,87 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef HANDSHAKEREQUEST_P_H +#define HANDSHAKEREQUEST_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class QTextStream; + +class HandshakeRequest +{ +public: + HandshakeRequest(int port, bool isSecure); + virtual ~HandshakeRequest(); + + void clear(); + + int getPort() const; + bool isSecure() const; + bool isValid() const; + QMap getHeaders() const; + QList getVersions() const; + QString getKey() const; + QString getOrigin() const; + QList getProtocols() const; + QList getExtensions() const; + QUrl getRequestUrl() const; + QString getResourceName() const; + QString getHost() const; + +private: + Q_DISABLE_COPY(HandshakeRequest) + QTextStream &readFromStream(QTextStream &textStream); + friend QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); + + int m_port; + bool m_isSecure; + bool m_isValid; + QMap m_headers; + QList m_versions; + QString m_key; + QString m_origin; + QList m_protocols; + QList m_extensions; + QUrl m_requestUrl; +}; + +QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); + +QT_END_NAMESPACE + +#endif // HANDSHAKEREQUEST_P_H diff --git a/src/websockets/handshakeresponse_p.cpp b/src/websockets/handshakeresponse_p.cpp new file mode 100644 index 0000000..c59b875 --- /dev/null +++ b/src/websockets/handshakeresponse_p.cpp @@ -0,0 +1,220 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "handshakeresponse_p.h" +#include "handshakerequest_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include //for more efficient string concatenation + +QT_BEGIN_NAMESPACE + +/*! + \internal + */ +HandshakeResponse::HandshakeResponse(const HandshakeRequest &request, + const QString &serverName, + bool isOriginAllowed, + const QList &supportedVersions, + const QList &supportedProtocols, + const QList &supportedExtensions) : + m_isValid(false), + m_canUpgrade(false), + m_response(), + m_acceptedProtocol(), + m_acceptedExtension(), + m_acceptedVersion(QWebSocketProtocol::V_Unknow) +{ + m_response = getHandshakeResponse(request, serverName, isOriginAllowed, supportedVersions, supportedProtocols, supportedExtensions); + m_isValid = true; +} + +/*! + \internal + */ +HandshakeResponse::~HandshakeResponse() +{ +} + +/*! + \internal + */ +bool HandshakeResponse::isValid() const +{ + return m_isValid; +} + +/*! + \internal + */ +bool HandshakeResponse::canUpgrade() const +{ + return m_isValid && m_canUpgrade; +} + +/*! + \internal + */ +QString HandshakeResponse::getAcceptedProtocol() const +{ + return m_acceptedProtocol; +} + +/*! + \internal + */ +QString HandshakeResponse::calculateAcceptKey(const QString &key) const +{ + QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; //the UID comes from RFC6455 + QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); + return QString(hash.toBase64()); +} + +/*! + \internal + */ +QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, + const QString &serverName, + bool isOriginAllowed, + const QList &supportedVersions, + const QList &supportedProtocols, + const QList &supportedExtensions) +{ + QStringList response; + m_canUpgrade = false; + + if (!isOriginAllowed) + { + if (!m_canUpgrade) + { + response << "HTTP/1.1 403 Access Forbidden"; + } + } + else + { + if (request.isValid()) + { + 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 + + if (matchingVersions.isEmpty()) + { + m_canUpgrade = false; + } + else + { + response << "HTTP/1.1 101 Switching Protocols" << + "Upgrade: websocket" << + "Connection: Upgrade" << + "Sec-WebSocket-Accept: " % acceptKey; + if (!matchingProtocols.isEmpty()) + { + m_acceptedProtocol = matchingProtocols.first(); + response << "Sec-WebSocket-Protocol: " % m_acceptedProtocol; + } + if (!matchingExtensions.isEmpty()) + { + m_acceptedExtension = matchingExtensions.first(); + response << "Sec-WebSocket-Extensions: " % m_acceptedExtension; + } + QString origin = request.getOrigin().trimmed(); + if (origin.isEmpty()) + { + origin = "*"; + } + response << "Server: " + serverName << + "Access-Control-Allow-Credentials: false" << //do not allow credentialed request (containing cookies) + "Access-Control-Allow-Methods: GET" << //only GET is allowed during handshaking + "Access-Control-Allow-Headers: content-type" << //this is OK; only the content-type header is allowed, no other headers are accepted + "Access-Control-Allow-Origin: " % origin << + "Date: " % QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'"); + + m_acceptedVersion = QWebSocketProtocol::currentVersion(); + m_canUpgrade = true; + } + } + else + { + m_canUpgrade = false; + } + if (!m_canUpgrade) + { + response << "HTTP/1.1 400 Bad Request"; + QStringList versions; + Q_FOREACH(QWebSocketProtocol::Version version, supportedVersions) + { + versions << QString::number(static_cast(version)); + } + response << "Sec-WebSocket-Version: " % versions.join(", "); + } + } + response << "\r\n"; //append empty line at end of header + return response.join("\r\n"); +} + +/*! + \internal + */ +QTextStream &HandshakeResponse::writeToStream(QTextStream &textStream) const +{ + if (!m_response.isEmpty()) + { + textStream << m_response.toLatin1().constData(); + } + else + { + textStream.setStatus(QTextStream::WriteFailed); + } + return textStream; +} + +/*! + \internal + */ +QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response) +{ + return response.writeToStream(stream); +} + +/*! + \internal + */ +QWebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const +{ + return m_acceptedVersion; +} + +/*! + \internal + */ +QString HandshakeResponse::getAcceptedExtension() const +{ + return m_acceptedExtension; +} + +QT_END_NAMESPACE diff --git a/src/websockets/handshakeresponse_p.h b/src/websockets/handshakeresponse_p.h new file mode 100644 index 0000000..7b1179b --- /dev/null +++ b/src/websockets/handshakeresponse_p.h @@ -0,0 +1,88 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef HANDSHAKERESPONSE_P_H +#define HANDSHAKERESPONSE_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class HandshakeRequest; +class QString; +class QTextStream; + +class HandshakeResponse:public QObject +{ + Q_OBJECT +public: + HandshakeResponse(const HandshakeRequest &request, + const QString &serverName, + bool isOriginAllowed, + const QList &supportedVersions, + const QList &supportedProtocols, + const QList &supportedExtensions); + + virtual ~HandshakeResponse(); + + bool isValid() const; + bool canUpgrade() const; + QString getAcceptedProtocol() const; + QString getAcceptedExtension() const; + QWebSocketProtocol::Version getAcceptedVersion() const; + +public Q_SLOTS: + +Q_SIGNALS: + +private: + Q_DISABLE_COPY(HandshakeResponse) + bool m_isValid; + bool m_canUpgrade; + QString m_response; + QString m_acceptedProtocol; + QString m_acceptedExtension; + 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 &supportedProtocols, + const QList &supportedExtensions); + + QTextStream &writeToStream(QTextStream &textStream) const; + friend QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response); +}; + +QT_END_NAMESPACE + +#endif // HANDSHAKERESPONSE_P_H diff --git a/src/websockets/qcorsauthenticator.cpp b/src/websockets/qcorsauthenticator.cpp new file mode 100644 index 0000000..5c7e581 --- /dev/null +++ b/src/websockets/qcorsauthenticator.cpp @@ -0,0 +1,132 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/*! + \class QCorsAuthenticator + + \inmodule QWebSockets + \brief The QCorsAuthenticator class provides an authenticator object for Cross Origin Requests (CORS). + + The QCorsAuthenticator class is used in the \l{QWebSocketServer::}{originAuthenticationRequired()} signal. + The class provides a way to pass back the required information to the QWebSocketServer. + It provides applications with fine-grained control over which origin URLs are allowed and which aren't. + By default, every origin is accepted. + To get fine grained control, an application connects the \l{QWebSocketServer::}{originAuthenticationRequired()} signal to + a slot. When the origin (QCorsAuthenticator::origin()) is accepted, it calls QCorsAuthenticator::setAllowed(true) + + \note 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. + \sa http://tools.ietf.org/html/rfc6455#section-10 + + \sa QWebSocketServer +*/ + +#include "qcorsauthenticator.h" +#include "qcorsauthenticator_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + */ +QCorsAuthenticatorPrivate::QCorsAuthenticatorPrivate(const QString &origin, bool allowed) : + m_origin(origin), + m_isAllowed(allowed) +{} + +/*! + \internal + */ +QCorsAuthenticatorPrivate::~QCorsAuthenticatorPrivate() +{} + +/*! + Constructs a new QCorsAuthencator object with the given \a origin. + \note By default, allowed() returns true. This means that per default every origin is accepted. + */ +QCorsAuthenticator::QCorsAuthenticator(const QString &origin) : + d_ptr(new QCorsAuthenticatorPrivate(origin, true)) //all origins are per default allowed +{ +} + +/*! + Destructs the object + */ +QCorsAuthenticator::~QCorsAuthenticator() +{ + if (d_ptr) + { + delete d_ptr; + } +} + +/*! + Constructs a coy of \a other + */ +QCorsAuthenticator::QCorsAuthenticator(const QCorsAuthenticator &other) : + d_ptr(new QCorsAuthenticatorPrivate(other.d_ptr->m_origin, other.d_ptr->m_isAllowed)) +{ +} + +/*! + Assigns \a other to this authenticator object + */ +QCorsAuthenticator &QCorsAuthenticator::operator =(const QCorsAuthenticator &other) +{ + Q_D(QCorsAuthenticator); + if (this != &other) + { + d->m_origin = other.d_ptr->m_origin; + d->m_isAllowed = other.d_ptr->m_isAllowed; + } + return *this; +} + +/*! + Returns the origin this autenticator is handling about. + */ +QString QCorsAuthenticator::origin() const +{ + Q_D(const QCorsAuthenticator); + return d->m_origin; +} + +/*! + Allows or disallows the origin. Setting \a allowed to true, will accept the connection request for the given origin. + Setting \a allowed to false, will reject the connection request. + + \note By default, all origins are accepted. + */ +void QCorsAuthenticator::setAllowed(bool allowed) +{ + Q_D(QCorsAuthenticator); + d->m_isAllowed = allowed; +} + +/*! + Returns true if the origin is allowed, otherwise returns false. + + \note By default, all origins are accepted. + */ +bool QCorsAuthenticator::allowed() const +{ + Q_D(const QCorsAuthenticator); + return d->m_isAllowed; +} diff --git a/src/websockets/qcorsauthenticator.h b/src/websockets/qcorsauthenticator.h new file mode 100644 index 0000000..acf997a --- /dev/null +++ b/src/websockets/qcorsauthenticator.h @@ -0,0 +1,48 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef QCORSAUTHENTICATOR_H +#define QCORSAUTHENTICATOR_H + +#include "qwebsocketsglobal.h" + +QT_BEGIN_NAMESPACE + +class QCorsAuthenticatorPrivate; + +class Q_WEBSOCKETS_EXPORT QCorsAuthenticator +{ +public: + QCorsAuthenticator(const QString &origin); + ~QCorsAuthenticator(); + QCorsAuthenticator(const QCorsAuthenticator &other); + + QCorsAuthenticator &operator =(const QCorsAuthenticator &other); + + QString origin() const; + + void setAllowed(bool allowed); + bool allowed() const; + +private: + Q_DECLARE_PRIVATE(QCorsAuthenticator) + + QCorsAuthenticatorPrivate * const d_ptr; +}; + +#endif // QCORSAUTHENTICATOR_H diff --git a/src/websockets/qcorsauthenticator_p.h b/src/websockets/qcorsauthenticator_p.h new file mode 100644 index 0000000..ee218dc --- /dev/null +++ b/src/websockets/qcorsauthenticator_p.h @@ -0,0 +1,29 @@ +#ifndef QCORSAUTHENTICATOR_P_H +#define QCORSAUTHENTICATOR_P_H + +#include //for QT_BEGIN_NAMESPACE +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +QT_BEGIN_NAMESPACE + +class QCorsAuthenticatorPrivate +{ +public: + QCorsAuthenticatorPrivate(const QString &origin, bool allowed); + ~QCorsAuthenticatorPrivate(); + + QString m_origin; + bool m_isAllowed; +}; + +#endif // QCORSAUTHENTICATOR_P_H diff --git a/src/websockets/qwebsocket.cpp b/src/websockets/qwebsocket.cpp new file mode 100644 index 0000000..b92b061 --- /dev/null +++ b/src/websockets/qwebsocket.cpp @@ -0,0 +1,592 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/*! + \class QWebSocket + + \inmodule QWebSockets + \brief 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. + + \sa QAbstractSocket, QTcpSocket + + \sa echoclient.html +*/ + +/*! + \page echoclient.html example + \title QWebSocket client example + \brief A sample websocket client that sends a message and displays the message that it receives back. + + \section1 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. + \section1 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 state 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() +*/ +/*! + \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() +*/ +/*! + \fn void QWebSocket::textMessageReceived(QString message); + + This signal is emitted whenever a text message is received. The \a message contains the received text. + + \sa binaryMessageReceived() +*/ +/*! + \fn void QWebSocket::binaryMessageReceived(QByteArray message); + + This signal is emitted whenever a binary message is received. The \a message contains the received bytes. + + \sa textMessageReceived() +*/ +/*! + \fn void QWebSocket::error(QAbstractSocket::SocketError error); + + This signal is emitted after an error occurred. The \a error + 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, QByteArray payload) + + Emitted when a pong message is received in reply to a previous ping. + \a elapsedTime contains the roundtrip time in milliseconds and \a payload contains an optional payload that was sent with the ping. + + \sa ping() + */ +#include "qwebsocket.h" +#include "qwebsocket_p.h" +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +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(const QString &origin, QWebSocketProtocol::Version version, QObject *parent) : + QObject(parent), + d_ptr(new QWebSocketPrivate(origin, version, this, this)) +{ +} + +/*! + * \brief Destroys the QWebSocket. Closes the socket if it is still open, and releases any used resources. + */ +QWebSocket::~QWebSocket() +{ + delete d_ptr; + //d_ptr = 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() +{ + Q_D(QWebSocket); + d->abort(); +} + +/*! + * Returns the type of error that last occurred + * \sa errorString() + */ +QAbstractSocket::SocketError QWebSocket::error() const +{ + Q_D(const QWebSocket); + return d->error(); +} + +//only called by QWebSocketPrivate::upgradeFrom +/*! + \internal + */ +QWebSocket::QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent) : + QObject(parent), + d_ptr(new QWebSocketPrivate(pTcpSocket, version, this, this)) +{ +} + +/*! + * Returns a human-readable description of the last error that occurred + * + * \sa error() + */ +QString QWebSocket::errorString() const +{ + Q_D(const QWebSocket); + return d->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 QWebSocket 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 QWebSocket will start sending data automatically once control goes back to the event loop. + In the absence of an event loop, call waitForBytesWritten() instead. +*/ +bool QWebSocket::flush() +{ + Q_D(QWebSocket); + return d->flush(); +} + +/*! + Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. + \a message must be '\\0' terminated. + */ +qint64 QWebSocket::write(const char *message) +{ + Q_D(QWebSocket); + return d->write(message); +} + +/*! + Sends the most \a maxSize bytes of the given \a message over the socket as a text message and returns the number of bytes actually sent. + */ +qint64 QWebSocket::write(const char *message, qint64 maxSize) +{ + Q_D(QWebSocket); + return d->write(message, maxSize); +} + +/*! + \brief Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. + */ +qint64 QWebSocket::write(const QString &message) +{ + Q_D(QWebSocket); + return d->write(message); +} + +/*! + \brief Sends the given \a data over the socket as a binary message and returns the number of bytes actually sent. + */ +qint64 QWebSocket::write(const QByteArray &data) +{ + Q_D(QWebSocket); + return d->write(data); +} + +/*! + \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. + The \a closeCode is a QWebSocketProtocol::CloseCode indicating the reason to close, and + \a reason describes the reason of the closure more in detail + */ +void QWebSocket::close(QWebSocketProtocol::CloseCode closeCode, const QString &reason) +{ + Q_D(QWebSocket); + d->close(closeCode, reason); +} + +/*! + \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 + \note A client socket must *always* mask its frames; servers may *never* mask its frames + */ +void QWebSocket::open(const QUrl &url, bool mask) +{ + Q_D(QWebSocket); + d->open(url, mask); +} + +/*! + \brief Pings the server to indicate that the connection is still alive. + Additional \a payload can be sent along the ping message. + + The size of the \a payload cannot be bigger than 125. If it is larger, the \a payload is clipped to 125 bytes. + + \sa pong() + */ +void QWebSocket::ping(const QByteArray &payload) +{ + Q_D(QWebSocket); + if (payload.length() > 125) + { + payload.left(125); + } + d->ping(payload); +} + +/*! + \brief Returns the version the socket is currently using + */ +QWebSocketProtocol::Version QWebSocket::version() const +{ + Q_D(const QWebSocket); + return d->version(); +} + +/*! + \brief Returns the name of the resource currently accessed. + */ +QString QWebSocket::resourceName() const +{ + Q_D(const QWebSocket); + return d->resourceName(); +} + +/*! + \brief Returns the url the socket is connected to or will connect to. + */ +QUrl QWebSocket::requestUrl() const +{ + Q_D(const QWebSocket); + return d->requestUrl(); +} + +/*! + \brief Returns the current origin + */ +QString QWebSocket::origin() const +{ + Q_D(const QWebSocket); + return d->origin(); +} + +/*! + \brief Returns the currently used protocol. + */ +QString QWebSocket::protocol() const +{ + Q_D(const QWebSocket); + return d->protocol(); +} + +/*! + \brief Returns the currently used extension. + */ +QString QWebSocket::extension() const +{ + Q_D(const QWebSocket); + return d->extension(); +} + +/*! + \brief Returns the current state of the socket + */ +QAbstractSocket::SocketState QWebSocket::state() const +{ + Q_D(const QWebSocket); + return d->state(); +} + +/*! + \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. + + \sa connected(), open(), state() + */ +bool QWebSocket::waitForConnected(int msecs) +{ + Q_D(QWebSocket); + return d->waitForConnected(msecs); +} + +/*! + 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. + When \a msecs is -1, this function will block until the socket is disconnected. + + \sa close(), state() +*/ +bool QWebSocket::waitForDisconnected(int msecs) +{ + Q_D(QWebSocket); + return d->waitForDisconnected(msecs); +} + +/*! + Returns the local address + */ +QHostAddress QWebSocket::localAddress() const +{ + Q_D(const QWebSocket); + return d->localAddress(); +} + +/*! + Returns the local port + */ +quint16 QWebSocket::localPort() const +{ + Q_D(const QWebSocket); + return d->localPort(); +} + +/*! + Returns the pause mode of this socket + */ +QAbstractSocket::PauseModes QWebSocket::pauseMode() const +{ + Q_D(const QWebSocket); + return d->pauseMode(); +} + +/*! + Returns the peer address + */ +QHostAddress QWebSocket::peerAddress() const +{ + Q_D(const QWebSocket); + return d->peerAddress(); +} + +/*! + Returns the peerName + */ +QString QWebSocket::peerName() const +{ + Q_D(const QWebSocket); + return d->peerName(); +} + +/*! + Returns the peerport + */ +quint16 QWebSocket::peerPort() const +{ + Q_D(const QWebSocket); + return d->peerPort(); +} + +#ifndef QT_NO_NETWORKPROXY +/*! + Returns the currently configured proxy + */ +QNetworkProxy QWebSocket::proxy() const +{ + Q_D(const QWebSocket); + return d->proxy(); +} + +/*! + Sets the proxy to \a networkProxy + */ +void QWebSocket::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QWebSocket); + d->setProxy(networkProxy); +} +#endif + +/*! + Returns the size in bytes of the readbuffer that is used by the socket. + */ +qint64 QWebSocket::readBufferSize() const +{ + Q_D(const QWebSocket); + return d->readBufferSize(); +} + +/*! + Continues data transfer on the socket. This method should only be used after the socket + has been set to pause upon notifications and a notification has been received. + The only notification currently supported is sslErrors(). + Calling this method if the socket is not paused results in undefined behavior. + + \sa pauseMode(), setPauseMode() + */ +void QWebSocket::resume() +{ + Q_D(QWebSocket); + d->resume(); +} + +/*! + Controls whether to pause upon receiving a notification. The \a pauseMode parameter specifies + the conditions in which the socket should be paused. + The only notification currently supported is sslErrors(). + If set to PauseOnSslErrors, data transfer on the socket will be paused + and needs to be enabled explicitly again by calling resume(). + By default, this option is set to PauseNever. This option must be called + before connecting to the server, otherwise it will result in undefined behavior. + + \sa pauseMode(), resume() + */ +void QWebSocket::setPauseMode(QAbstractSocket::PauseModes pauseMode) +{ + Q_D(QWebSocket); + d->setPauseMode(pauseMode); +} + +/*! + Sets the size of QWebSocket's internal read buffer to be \a size bytes. + If the buffer size is limited to a certain size, QWebSocket 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() +*/ +void QWebSocket::setReadBufferSize(qint64 size) +{ + Q_D(QWebSocket); + d->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) +{ + Q_D(QWebSocket); + d->setSocketOption(option, value); +} + +/*! + Returns the value of the option \a option. + \sa setSocketOption() +*/ +QVariant QWebSocket::socketOption(QAbstractSocket::SocketOption option) +{ + Q_D(QWebSocket); + return d->socketOption(option); +} + +/*! + Returns true if the QWebSocket is valid. + */ +bool QWebSocket::isValid() const +{ + Q_D(const QWebSocket); + return d->isValid(); +} + +QT_END_NAMESPACE diff --git a/src/websockets/qwebsocket.h b/src/websockets/qwebsocket.h new file mode 100644 index 0000000..cffc87d --- /dev/null +++ b/src/websockets/qwebsocket.h @@ -0,0 +1,116 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKET_H +#define QWEBSOCKET_H + +#include +#include +#include +#ifndef QT_NO_NETWORKPROXY +#include +#endif +#include +#include "qwebsocketsglobal.h" +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class QTcpSocket; +class QWebSocketPrivate; + +class Q_WEBSOCKETS_EXPORT QWebSocket:public QObject +{ + Q_OBJECT + +public: + explicit QWebSocket(const 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() const; + QHostAddress localAddress() const; + quint16 localPort() const; + QAbstractSocket::PauseModes pauseMode() const; + QHostAddress peerAddress() const; + QString peerName() const; + quint16 peerPort() const; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy() const; + void setProxy(const QNetworkProxy &networkProxy); +#endif + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + void resume(); + void setPauseMode(QAbstractSocket::PauseModes pauseMode); + + 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() const; + QString resourceName() const; + QUrl requestUrl() const; + QString origin() const; + QString protocol() const; + QString extension() const; + + qint64 write(const char *message); //send data as text + qint64 write(const char *message, qint64 maxSize); //send data as text + qint64 write(const QString &message); //send data as text + qint64 write(const QByteArray &data); //send data as binary + +public Q_SLOTS: + void close(QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CC_NORMAL, const QString &reason = QString()); + void open(const QUrl &url, bool mask = true); + void ping(const QByteArray &payload = QByteArray()); + +Q_SIGNALS: + void aboutToClose(); + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState state); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *pAuthenticator); +#endif + 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, QByteArray payload); + +private: + Q_DISABLE_COPY(QWebSocket) + Q_DECLARE_PRIVATE(QWebSocket) + QWebSocket(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QObject *parent = 0); + QWebSocketPrivate * const d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKET_H diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp new file mode 100644 index 0000000..054034c --- /dev/null +++ b/src/websockets/qwebsocket_p.cpp @@ -0,0 +1,1108 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qwebsocket.h" +#include "qwebsocket_p.h" +#include "handshakerequest_p.h" +#include "handshakeresponse_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include //for more efficient string concatenation +#ifndef QT_NONETWORKPROXY +#include +#endif + +#include + +#include + +QT_BEGIN_NAMESPACE + +const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message + +/*! + \internal +*/ +QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : + QObject(parent), + q_ptr(pWebSocket), + m_pSocket(new QTcpSocket(this)), + m_errorString(), + m_version(version), + m_resourceName(), + m_requestUrl(), + m_origin(origin), + m_protocol(""), + m_extension(""), + m_socketState(QAbstractSocket::UnconnectedState), + m_key(), + m_mustMask(true), + m_isClosingHandshakeSent(false), + m_isClosingHandshakeReceived(false), + m_pingTimer(), + m_dataProcessor() +{ + Q_ASSERT(pWebSocket != 0); + makeConnections(m_pSocket); + qsrand(static_cast(QDateTime::currentMSecsSinceEpoch())); +} + +/*! + \internal +*/ +QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent) : + QObject(parent), + q_ptr(pWebSocket), + m_pSocket(pTcpSocket), + m_errorString(pTcpSocket->errorString()), + m_version(version), + m_resourceName(), + m_requestUrl(), + m_origin(), + m_protocol(), + m_extension(), + m_socketState(pTcpSocket->state()), + m_key(), + m_mustMask(true), + m_isClosingHandshakeSent(false), + m_isClosingHandshakeReceived(false), + m_pingTimer(), + m_dataProcessor() +{ + Q_ASSERT(pWebSocket != 0); + makeConnections(m_pSocket); +} + +/*! + \internal +*/ +QWebSocketPrivate::~QWebSocketPrivate() +{ + if (state() == QAbstractSocket::ConnectedState) + { + close(QWebSocketProtocol::CC_GOING_AWAY, tr("Connection closed")); + } + releaseConnections(m_pSocket); + m_pSocket->deleteLater(); + m_pSocket = 0; +} + +/*! + \internal + */ +void QWebSocketPrivate::abort() +{ + m_pSocket->abort(); +} + +/*! + \internal + */ +QAbstractSocket::SocketError QWebSocketPrivate::error() const +{ + return m_pSocket->error(); +} + +/*! + \internal + */ +QString QWebSocketPrivate::errorString() const +{ + if (!m_errorString.isEmpty()) + { + return m_errorString; + } + else + { + return m_pSocket->errorString(); + } +} + +/*! + \internal + */ +bool QWebSocketPrivate::flush() +{ + return m_pSocket->flush(); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const char *message) +{ + return write(QString::fromUtf8(message)); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const char *message, qint64 maxSize) +{ + return write(QString::fromUtf8(message, static_cast(maxSize))); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const QString &message) +{ + return doWriteData(message.toUtf8(), false); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::write(const QByteArray &data) +{ + return doWriteData(data, true); +} + +/*! + \internal + */ +QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket, + const HandshakeRequest &request, + const HandshakeResponse &response, + QObject *parent) +{ + QWebSocket *pWebSocket = new QWebSocket(pTcpSocket, response.getAcceptedVersion(), parent); + pWebSocket->d_func()->setExtension(response.getAcceptedExtension()); + pWebSocket->d_func()->setOrigin(request.getOrigin()); + pWebSocket->d_func()->setRequestUrl(request.getRequestUrl()); + pWebSocket->d_func()->setProtocol(response.getAcceptedProtocol()); + pWebSocket->d_func()->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); + pWebSocket->d_func()->enableMasking(false); //a server should not send masked frames + + return pWebSocket; +} + +/*! + \internal + */ +void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) +{ + Q_Q(QWebSocket); + if (!m_isClosingHandshakeSent) + { + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + quint16 code = qToBigEndian(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 q->aboutToClose(); + } + m_pSocket->close(); +} + +/*! + \internal + */ +void QWebSocketPrivate::open(const QUrl &url, bool mask) +{ + m_dataProcessor.clear(); + m_isClosingHandshakeReceived = false; + m_isClosingHandshakeSent = false; + + setRequestUrl(url); + QString resourceName = url.path(); + if (!url.query().isEmpty()) + { + resourceName.append("?" + url.query()); + } + if (resourceName.isEmpty()) + { + resourceName = "/"; + } + setResourceName(resourceName); + enableMasking(mask); + + setSocketState(QAbstractSocket::ConnectingState); + + m_pSocket->connectToHost(url.host(), url.port(80)); +} + +/*! + \internal + */ +void QWebSocketPrivate::ping(const QByteArray &payload) +{ + Q_ASSERT(payload.length() < 126); + m_pingTimer.restart(); + QByteArray pingFrame = getFrameHeader(QWebSocketProtocol::OC_PING, payload.size(), 0 /*do not mask*/, true); + pingFrame.append(payload); + writeFrame(pingFrame); +} + +/*! + \internal + Sets the version to use for the websocket protocol; this must be set before the socket is opened. +*/ +void QWebSocketPrivate::setVersion(QWebSocketProtocol::Version version) +{ + m_version = version; +} + +/*! + \internal + Sets the resource name of the connection; must be set before the socket is openend +*/ +void QWebSocketPrivate::setResourceName(const QString &resourceName) +{ + m_resourceName = resourceName; +} + +/*! + \internal + */ +void QWebSocketPrivate::setRequestUrl(const QUrl &requestUrl) +{ + m_requestUrl = requestUrl; +} + +/*! + \internal + */ +void QWebSocketPrivate::setOrigin(const QString &origin) +{ + m_origin = origin; +} + +/*! + \internal + */ +void QWebSocketPrivate::setProtocol(const QString &protocol) +{ + m_protocol = protocol; +} + +/*! + \internal + */ +void QWebSocketPrivate::setExtension(const QString &extension) +{ + m_extension = extension; +} + +/*! + \internal + */ +void QWebSocketPrivate::enableMasking(bool enable) +{ + m_mustMask = enable; +} + +/*! + * \internal + */ +qint64 QWebSocketPrivate::doWriteData(const QByteArray &data, bool isBinary) +{ + return doWriteFrames(data, isBinary); +} + +/*! + * \internal + */ +void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) +{ + Q_Q(QWebSocket); + //pass through signals + connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); + connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + connect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + connect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + + //catch signals + connect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); + connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); + + connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); + connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); + connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); + connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); + connect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); + connect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); + connect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); + connect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + * \internal + */ +void QWebSocketPrivate::releaseConnections(const QTcpSocket *pTcpSocket) +{ + Q_Q(QWebSocket); + if (pTcpSocket) + { + //pass through signals + disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), q, SIGNAL(error(QAbstractSocket::SocketError))); + disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); + disconnect(pTcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + disconnect(pTcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + + //catched signals + disconnect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); + disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); + } + disconnect(&m_dataProcessor, SIGNAL(pingReceived(QByteArray)), this, SLOT(processPing(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(pongReceived(QByteArray)), this, SLOT(processPong(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)), this, SLOT(processClose(QWebSocketProtocol::CloseCode,QString))); + disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), q, SIGNAL(textFrameReceived(QString,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), q, SIGNAL(binaryFrameReceived(QByteArray,bool))); + disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), q, SIGNAL(binaryMessageReceived(QByteArray))); + disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), q, SIGNAL(textMessageReceived(QString))); + disconnect(&m_dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)), this, SLOT(close(QWebSocketProtocol::CloseCode,QString))); +} + +/*! + \internal + */ +QWebSocketProtocol::Version QWebSocketPrivate::version() const +{ + return m_version; +} + +/*! + \internal + */ +QString QWebSocketPrivate::resourceName() const +{ + return m_resourceName; +} + +/*! + \internal + */ +QUrl QWebSocketPrivate::requestUrl() const +{ + return m_requestUrl; +} + +/*! + \internal + */ +QString QWebSocketPrivate::origin() const +{ + return m_origin; +} + +/*! + \internal + */ +QString QWebSocketPrivate::protocol() const +{ + return m_protocol; +} + +/*! + \internal + */ +QString QWebSocketPrivate::extension() const +{ + return m_extension; +} + +/*! + * \internal + */ +QByteArray QWebSocketPrivate::getFrameHeader(QWebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const +{ + QByteArray header; + quint8 byte = 0x00; + bool ok = payloadLength <= 0x7FFFFFFFFFFFFFFFULL; + + if (ok) + { + //FIN, RSV1-3, opcode + byte = static_cast((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 q_ptr->error(QAbstractSocket::DatagramTooLargeError); + qDebug() << "WebSocket::getHeader: payload too big!"; + } + + return header; +} + +/*! + * \internal + */ +qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) +{ + Q_Q(QWebSocket); + const QWebSocketProtocol::OpCode firstOpCode = isBinary ? QWebSocketProtocol::OC_BINARY : QWebSocketProtocol::OC_TEXT; + + int numFrames = data.size() / FRAME_SIZE_IN_BYTES; + QByteArray tmpData(data); + tmpData.detach(); + char *payload = tmpData.data(); + quint64 sizeLeft = static_cast(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(tr("Error writing bytes to socket: %1.").arg(m_pSocket->errorString())); + qDebug() << errorString(); + m_pSocket->flush(); + Q_EMIT q->error(QAbstractSocket::NetworkError); + break; + } + } + currentPosition += size; + bytesLeft -= size; + } + if (payloadWritten != data.size()) + { + setErrorString(tr("Bytes written %1 != %2.").arg(payloadWritten).arg(data.size())); + qDebug() << errorString(); + Q_EMIT q->error(QAbstractSocket::NetworkError); + } + return payloadWritten; +} + +/*! + * \internal + */ +quint32 QWebSocketPrivate::generateRandomNumber() const +{ + return static_cast((static_cast(qrand()) / RAND_MAX) * std::numeric_limits::max()); +} + +/*! + \internal + */ +quint32 QWebSocketPrivate::generateMaskingKey() const +{ + return generateRandomNumber(); +} + +/*! + \internal + */ +QByteArray QWebSocketPrivate::generateKey() const +{ + QByteArray key; + + for (int i = 0; i < 4; ++i) + { + quint32 tmp = generateRandomNumber(); + key.append(static_cast(static_cast(&tmp)), sizeof(quint32)); + } + + return key.toBase64(); +} + + +/*! + \internal + */ +QString QWebSocketPrivate::calculateAcceptKey(const QString &key) const +{ + QString tmpKey = key % "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); + return QString(hash.toBase64()); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::writeFrames(const QList &frames) +{ + qint64 written = 0; + for (int i = 0; i < frames.size(); ++i) + { + written += writeFrame(frames[i]); + } + return written; +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::writeFrame(const QByteArray &frame) +{ + return m_pSocket->write(frame); +} + +/*! + \internal + */ +QString readLine(QTcpSocket *pSocket) +{ + QString line; + char c; + while (pSocket->getChar(&c)) + { + if (c == '\r') + { + pSocket->getChar(&c); + break; + } + else + { + line.append(QChar(c)); + } + } + return line; +} + +//called on the client for a server handshake response +/*! + \internal + */ +void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) +{ + Q_Q(QWebSocket); + if (pSocket == 0) + { + return; + } + + bool ok = false; + QString errorDescription; + + const QString regExpStatusLine("^(HTTP/[0-9]+\\.[0-9]+)\\s([0-9]+)\\s(.*)"); + const QRegularExpression regExp(regExpStatusLine); + QString statusLine = readLine(pSocket); + QString httpProtocol; + int httpStatusCode; + QString httpStatusMessage; + QRegularExpressionMatch match = regExp.match(statusLine); + if (match.hasMatch()) + { + QStringList tokens = match.capturedTexts(); + tokens.removeFirst(); //remove the search string + if (tokens.length() == 3) + { + httpProtocol = tokens[0]; + httpStatusCode = tokens[1].toInt(); + httpStatusMessage = tokens[2].trimmed(); + ok = true; + } + } + if (!ok) + { + errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); + } + else + { + QString headerLine = readLine(pSocket); + QMap headers; + while (!headerLine.isEmpty()) + { + QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); + headers.insertMulti(headerField[0], headerField[1]); + headerLine = readLine(pSocket); + } + + QString acceptKey = headers.value("Sec-WebSocket-Accept", ""); + QString upgrade = headers.value("Upgrade", ""); + QString connection = headers.value("Connection", ""); + //unused for the moment + //QString extensions = headers.value("Sec-WebSocket-Extensions", ""); + //QString protocol = headers.value("Sec-WebSocket-Protocol", ""); + QString version = headers.value("Sec-WebSocket-Version", ""); + + if (httpStatusCode == 101) //HTTP/x.y 101 Switching Protocols + { + bool conversionOk = false; + float version = httpProtocol.midRef(5).toFloat(&conversionOk); + //TODO: do not check the httpStatusText right now + ok = !(acceptKey.isEmpty() || + (!conversionOk || (version < 1.1f)) || + (upgrade.toLower() != "websocket") || + (connection.toLower() != "upgrade")); + if (ok) + { + QString accept = calculateAcceptKey(m_key); + ok = (accept == acceptKey); + if (!ok) + { + errorDescription = tr("Accept-Key received from server %1 does not match the client key %2.").arg(acceptKey).arg(accept); + } + } + else + { + errorDescription = tr("Invalid statusline in response: %1.").arg(statusLine); + } + } + else if (httpStatusCode == 400) //HTTP/1.1 400 Bad Request + { + if (!version.isEmpty()) + { + QStringList versions = version.split(", ", QString::SkipEmptyParts); + if (!versions.contains(QString::number(QWebSocketProtocol::currentVersion()))) + { + //if needed to switch protocol version, then we are finished here + //because we cannot handle other protocols than the RFC one (v13) + errorDescription = tr("Handshake: Server requests a version that we don't support: %1.").arg(versions.join(", ")); + ok = false; + } + else + { + //we tried v13, but something different went wrong + errorDescription = tr("Unknown error condition encountered. Aborting connection."); + ok = false; + } + } + } + else + { + errorDescription = tr("Unhandled http status code: %1.").arg(httpStatusCode); + ok = false; + } + + if (!ok) + { + qDebug() << errorDescription; + setErrorString(errorDescription); + Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); + } + else + { + //handshake succeeded + setSocketState(QAbstractSocket::ConnectedState); + Q_EMIT q->connected(); + } + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketState) +{ + Q_Q(QWebSocket); + QAbstractSocket::SocketState webSocketState = this->state(); + switch (socketState) + { + case QAbstractSocket::ConnectedState: + { + if (webSocketState == QAbstractSocket::ConnectingState) + { + m_key = generateKey(); + QString handshake = createHandShakeRequest(m_resourceName, m_requestUrl.host() % ":" % QString::number(m_requestUrl.port(80)), origin(), "", "", m_key); + m_pSocket->write(handshake.toLatin1()); + } + break; + } + case QAbstractSocket::ClosingState: + { + if (webSocketState == QAbstractSocket::ConnectedState) + { + setSocketState(QAbstractSocket::ClosingState); + } + break; + } + case QAbstractSocket::UnconnectedState: + { + if (webSocketState != QAbstractSocket::UnconnectedState) + { + setSocketState(QAbstractSocket::UnconnectedState); + Q_EMIT q->disconnected(); + } + break; + } + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + { + //do nothing + //to make C++ compiler happy; + break; + } + default: + { + break; + } + } +} + +//order of events: +//connectToHost is called +//our socket state is set to "connecting", and tcpSocket->connectToHost is called +//the tcpsocket is opened, a handshake message is sent; a readyRead signal is thrown +//this signal is catched by processData +//when OUR socket state is in the "connecting state", this means that +//we have received data from the server (response to handshake), and that we +//should "upgrade" our socket to a websocket (connected state) +//if our socket was already upgraded, then we need to process websocket data +/*! + \internal + */ +void QWebSocketPrivate::processData() +{ + while (m_pSocket->bytesAvailable()) + { + if (state() == QAbstractSocket::ConnectingState) + { + processHandshake(m_pSocket); + } + else + { + m_dataProcessor.process(m_pSocket); + } + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processPing(QByteArray data) +{ + quint32 maskingKey = 0; + if (m_mustMask) + { + maskingKey = generateMaskingKey(); + } + m_pSocket->write(getFrameHeader(QWebSocketProtocol::OC_PONG, data.size(), maskingKey, true)); + if (data.size() > 0) + { + if (m_mustMask) + { + QWebSocketProtocol::mask(&data, maskingKey); + } + m_pSocket->write(data); + } +} + +/*! + \internal + */ +void QWebSocketPrivate::processPong(QByteArray data) +{ + Q_Q(QWebSocket); + Q_EMIT q->pong(static_cast(m_pingTimer.elapsed()), data); +} + +/*! + \internal + */ +void QWebSocketPrivate::processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason) +{ + m_isClosingHandshakeReceived = true; + close(closeCode, closeReason); +} + +/*! + \internal + */ +QString QWebSocketPrivate::createHandShakeRequest(QString resourceName, + QString host, + QString origin, + QString extensions, + QString protocols, + QByteArray key) +{ + QStringList handshakeRequest; + + handshakeRequest << "GET " % resourceName % " HTTP/1.1" << + "Host: " % host << + "Upgrade: websocket" << + "Connection: Upgrade" << + "Sec-WebSocket-Key: " % QString(key); + if (!origin.isEmpty()) + { + handshakeRequest << "Origin: " % origin; + } + handshakeRequest << "Sec-WebSocket-Version: " % QString::number(QWebSocketProtocol::currentVersion()); + if (extensions.length() > 0) + { + handshakeRequest << "Sec-WebSocket-Extensions: " % extensions; + } + if (protocols.length() > 0) + { + handshakeRequest << "Sec-WebSocket-Protocol: " % protocols; + } + handshakeRequest << "\r\n"; + + return handshakeRequest.join("\r\n"); +} + +/*! + \internal + */ +QAbstractSocket::SocketState QWebSocketPrivate::state() const +{ + return m_socketState; +} + +/*! + \internal + */ +bool QWebSocketPrivate::waitForConnected(int msecs) +{ + return m_pSocket->waitForConnected(msecs); +} + +/*! + \internal + */ +bool QWebSocketPrivate::waitForDisconnected(int msecs) +{ + return m_pSocket->waitForDisconnected(msecs); +} + +/*! + \internal + */ +void QWebSocketPrivate::setSocketState(QAbstractSocket::SocketState state) +{ + Q_Q(QWebSocket); + if (m_socketState != state) + { + m_socketState = state; + Q_EMIT q->stateChanged(m_socketState); + } +} + +/*! + \internal + */ +void QWebSocketPrivate::setErrorString(const QString &errorString) +{ + m_errorString = errorString; +} + +/*! + \internal + */ +QHostAddress QWebSocketPrivate::localAddress() const +{ + return m_pSocket->localAddress(); +} + +/*! + \internal + */ +quint16 QWebSocketPrivate::localPort() const +{ + return m_pSocket->localPort(); +} + +/*! + \internal + */ +QAbstractSocket::PauseModes QWebSocketPrivate::pauseMode() const +{ + return m_pSocket->pauseMode(); +} + +/*! + \internal + */ +QHostAddress QWebSocketPrivate::peerAddress() const +{ + return m_pSocket->peerAddress(); +} + +/*! + \internal + */ +QString QWebSocketPrivate::peerName() const +{ + return m_pSocket->peerName(); +} + +/*! + \internal + */ +quint16 QWebSocketPrivate::peerPort() const +{ + return m_pSocket->peerPort(); +} + +/*! + \internal + */ +QNetworkProxy QWebSocketPrivate::proxy() const +{ + return m_pSocket->proxy(); +} + +/*! + \internal + */ +qint64 QWebSocketPrivate::readBufferSize() const +{ + return m_pSocket->readBufferSize(); +} + +/*! + \internal + */ +void QWebSocketPrivate::resume() +{ + m_pSocket->resume(); +} + +/*! + \internal + */ +void QWebSocketPrivate::setPauseMode(QAbstractSocket::PauseModes pauseMode) +{ + m_pSocket->setPauseMode(pauseMode); +} + +/*! + \internal + */ +void QWebSocketPrivate::setProxy(const QNetworkProxy &networkProxy) +{ + m_pSocket->setProxy(networkProxy); +} + +/*! + \internal + */ +void QWebSocketPrivate::setReadBufferSize(qint64 size) +{ + m_pSocket->setReadBufferSize(size); +} + +/*! + \internal + */ +void QWebSocketPrivate::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ + m_pSocket->setSocketOption(option, value); +} + +/*! + \internal + */ +QVariant QWebSocketPrivate::socketOption(QAbstractSocket::SocketOption option) +{ + return m_pSocket->socketOption(option); +} + +/*! + \internal + */ +bool QWebSocketPrivate::isValid() const +{ + return m_pSocket->isValid(); +} + +QT_END_NAMESPACE diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h new file mode 100644 index 0000000..e4cd657 --- /dev/null +++ b/src/websockets/qwebsocket_p.h @@ -0,0 +1,183 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKET_P_H +#define QWEBSOCKET_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#ifndef QT_NO_NETWORKPROXY +#include +#endif +#include +#include "qwebsocketsglobal.h" +#include "qwebsocketprotocol.h" +#include "dataprocessor_p.h" + +QT_BEGIN_NAMESPACE + +class HandshakeRequest; +class HandshakeResponse; +class QTcpSocket; +class QWebSocket; + +class QWebSocketPrivate:public QObject +{ + Q_OBJECT + +public: + explicit QWebSocketPrivate(const QString &origin, + QWebSocketProtocol::Version version, + QWebSocket * const pWebSocket, + QObject *parent = 0); + virtual ~QWebSocketPrivate(); + + void abort(); + QAbstractSocket::SocketError error() const; + QString errorString() const; + bool flush(); + bool isValid() const; + QHostAddress localAddress() const; + quint16 localPort() const; + QAbstractSocket::PauseModes pauseMode() const; + QHostAddress peerAddress() const; + QString peerName() const; + quint16 peerPort() const; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy() const; + void setProxy(const QNetworkProxy &networkProxy); +#endif + qint64 readBufferSize() const; + void resume(); + void setPauseMode(QAbstractSocket::PauseModes pauseMode); + 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); + bool waitForDisconnected(int msecs); + + QWebSocketProtocol::Version version() const; + QString resourceName() const; + QUrl requestUrl() const; + QString origin() const; + QString protocol() const; + QString extension() const; + + qint64 write(const char *message); //send data as text + qint64 write(const char *message, qint64 maxSize); //send data as text + qint64 write(const QString &message); //send data as text + qint64 write(const QByteArray &data); //send data as binary + +public Q_SLOTS: + void close(QWebSocketProtocol::CloseCode closeCode, QString reason); + void open(const QUrl &url, bool mask); + void ping(const QByteArray &payload); + +private Q_SLOTS: + void processData(); + void processPing(QByteArray data); + void processPong(QByteArray data); + void processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason); + void processHandshake(QTcpSocket *pSocket); + void processStateChanged(QAbstractSocket::SocketState socketState); + +private: + Q_DISABLE_COPY(QWebSocketPrivate) + Q_DECLARE_PUBLIC(QWebSocket) + + QWebSocket * const q_ptr; + + QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version, QWebSocket *pWebSocket, QObject *parent = 0); + void setVersion(QWebSocketProtocol::Version version); + void setResourceName(const QString &resourceName); + void setRequestUrl(const QUrl &requestUrl); + void setOrigin(const QString &origin); + void setProtocol(const QString &protocol); + void setExtension(const QString &extension); + void enableMasking(bool enable); + void setSocketState(QAbstractSocket::SocketState state); + void setErrorString(const 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); + + static QWebSocket *upgradeFrom(QTcpSocket *tcpSocket, + const HandshakeRequest &request, + const HandshakeResponse &response, + QObject *parent = 0); + + quint32 generateMaskingKey() const; + QByteArray generateKey() const; + quint32 generateRandomNumber() const; + qint64 writeFrames(const QList &frames); + qint64 writeFrame(const QByteArray &frame); + + 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; + + friend class QWebSocketServerPrivate; +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKET_H diff --git a/src/websockets/qwebsocketprotocol.cpp b/src/websockets/qwebsocketprotocol.cpp new file mode 100644 index 0000000..4e02cd5 --- /dev/null +++ b/src/websockets/qwebsocketprotocol.cpp @@ -0,0 +1,179 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qwebsocketprotocol.h" +#include +#include +#include + +/*! + \enum WebSocketProtocol::CloseCode + + The close codes supported by WebSockets V13 + + \value CC_NORMAL Normal closure + \value CC_GOING_AWAY Going away + \value CC_PROTOCOL_ERROR Protocol error + \value CC_DATATYPE_NOT_SUPPORTED Unsupported data + \value CC_RESERVED_1004 Reserved + \value CC_MISSING_STATUS_CODE No status received + \value CC_ABNORMAL_DISCONNECTION Abnormal closure + \value CC_WRONG_DATATYPE Invalid frame payload data + \value CC_POLICY_VIOLATED Policy violation + \value CC_TOO_MUCH_DATA Message too big + \value CC_MISSING_EXTENSION Mandatory extension missing + \value CC_BAD_OPERATION Internal server error + \value CC_TLS_HANDSHAKE_FAILED TLS handshake failed + + \sa \l{QWebSocket::} {close()} +*/ +/*! + \enum WebSocketProtocol::Version + + \brief The different defined versions of the Websocket protocol. + + For an overview of the differences between the different protocols, see + + + \value V_Unknow + \value 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. + Attribute: Sec-WebSocket-Draft value 0. + \value 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 + Sec-WebSocket-Draft renamed to Sec-WebSocket-Version + Sec-WebSocket-Version = 4 + \value V_5 hybi-05: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-05.txt. + Sec-WebSocket-Version = 5 + Removed Sec-WebSocket-Nonce + Added Sec-WebSocket-Accept + \value V_6 Sec-WebSocket-Version = 6. + \value V_7 hybi-07: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07. + Sec-WebSocket-Version = 7 + \value 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 + Internal error results in 1006 + \value V_13 hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and RFC 6455. + Sec-WebSocket-Version = 13 + Status code 1004 is now reserved + Added 1008, 1009 and 1010 + Must support TLS + Clarify multiple version support + \value 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 +*/ + +QT_BEGIN_NAMESPACE + +/** + * @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 + +QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketprotocol.h b/src/websockets/qwebsocketprotocol.h new file mode 100644 index 0000000..2d1bea3 --- /dev/null +++ b/src/websockets/qwebsocketprotocol.h @@ -0,0 +1,108 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKETPROTOCOL_H +#define QWEBSOCKETPROTOCOL_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QByteArray; + +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_C = 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 + +QT_END_NAMESPACE + +#endif // QWEBSOCKETPROTOCOL_H diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp new file mode 100644 index 0000000..03c0ec2 --- /dev/null +++ b/src/websockets/qwebsocketserver.cpp @@ -0,0 +1,407 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/*! + \class QWebSocketServer + + \inmodule QWebSockets + + \brief Implements 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 QWebSocketServer. + This class makes it possible to accept incoming websocket connections. + You can specify the port or have QWebSocketServer pick one automatically. + You can listen on a specific address or on all the machine's addresses. + Call listen() to have the server listen for incoming connections. + + The newConnection() signal is then emitted each time a client connects to the server. + Call nextPendingConnection() to accept the pending connection as a connected QWebSocket. + The function returns a pointer to a QWebSocket in QAbstractSocket::ConnectedState that you can use for communicating with the client. + If an error occurs, serverError() returns the type of error, and errorString() can be called to get a human readable description of what happened. + When listening for connections, the address and port on which the server is listening are available as serverAddress() and serverPort(). + Calling close() makes QWebSocketServer stop listening for incoming connections. + Although QWebSocketServer 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. + + \sa echoserver.html + + \sa QWebSocket +*/ + +/*! + \page echoserver.html example + \title WebSocket server example + \brief A sample websocket server echoing back messages sent to it. + + \section1 Description + The echoserver example implements a web socket server that echoes back everything that is sent to it. + \section1 Code + We start by creating a QWebSocketServer (`new QWebSocketServer()`). 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 QWebSocket 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 QWebSocketServer::acceptError(QAbstractSocket::SocketError socketError) + This signal is emitted when accepting a new connection results in an error. + The \a socketError parameter describes the type of error that occurred + + \sa pauseAccepting(), resumeAccepting() +*/ + +/*! + \fn void QWebSocketServer::newConnection() + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +/*! + \fn void QWebSocketServer::originAuthenticationRequired(QCorsAuthenticator *authenticator) + This signal is emitted when a new connection is requested. + The slot connected to this signal should indicate whether the origin (which can be determined by the origin() call) + is allowed in the \a authenticator object (by issuing \l{QCorsAuthenticator::}{setAllowed()}) + + If no slot is connected to this signal, all origins will be accepted by default. + + \note It is not possible to use a QueuedConnection to connect to + this signal, as the connection will always succeed. +*/ + +#include +#include +#include +#include "qwebsocketprotocol.h" +#include "qwebsocket.h" +#include "qwebsocketserver.h" +#include "qwebsocketserver_p.h" + +//TODO: CorsCheck: give list in constructor or use CorsAuthenticator object +//in QNetworkAccessManager the signal cannot be connected to a queued signal, because it waits for the signal to return + +QT_BEGIN_NAMESPACE + +/*! + Constructs a new WebSocketServer with the given \a serverName. + The \a serverName will be used in the http handshake phase to identify the server. + + \a parent is passed to the QObject constructor. + */ +QWebSocketServer::QWebSocketServer(const QString &serverName, QObject *parent) : + QObject(parent), + d_ptr(new QWebSocketServerPrivate(serverName, this, this)) +{ +} + +/*! + 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() +{ + delete d_ptr; +} + +/*! + Closes the server. The server will no longer listen for incoming connections. + */ +void QWebSocketServer::close() +{ + Q_D(QWebSocketServer); + d->close(); +} + +/*! + Returns a human readable description of the last error that occurred. + + \sa serverError() +*/ +QString QWebSocketServer::errorString() const +{ + Q_D(const QWebSocketServer); + return d->errorString(); +} + +/*! + Returns true if the server has pending connections; otherwise returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() + */ +bool QWebSocketServer::hasPendingConnections() const +{ + Q_D(const QWebSocketServer); + return d->hasPendingConnections(); +} + +/*! + Returns true if the server is currently listening for incoming connections; otherwise returns false. + + \sa listen() + */ +bool QWebSocketServer::isListening() const +{ + Q_D(const QWebSocketServer); + return d->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. + 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) +{ + Q_D(QWebSocketServer); + return d->listen(address, port); +} + +/*! + Returns the maximum number of pending accepted connections. The default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() + */ +int QWebSocketServer::maxPendingConnections() const +{ + Q_D(const QWebSocketServer); + return d->maxPendingConnections(); +} + +/*! + 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() +{ + Q_D(QWebSocketServer); + return d->nextPendingConnection(); +} + +/*! + Pauses incoming new connections. Queued connections will remain in queue. + \sa resumeAccepting() + */ +void QWebSocketServer::pauseAccepting() +{ + Q_D(QWebSocketServer); + d->pauseAccepting(); +} + +#ifndef QT_NO_NETWORKPROXY +/*! + Returns the network proxy for this socket. By default QNetworkProxy::DefaultProxy is used. + + \sa setProxy() +*/ +QNetworkProxy QWebSocketServer::proxy() const +{ + Q_D(const QWebSocketServer); + return d->proxy(); +} + +/*! + \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) +{ + Q_D(QWebSocketServer); + d->setProxy(networkProxy); +} +#endif +/*! + Resumes accepting new connections. + \sa pauseAccepting() + */ +void QWebSocketServer::resumeAccepting() +{ + Q_D(QWebSocketServer); + d->resumeAccepting(); +} + +/*! + Sets the server name that will be used during the http handshake phase to the given \a serverName. + Existing connected clients will not be notified of this change, only newly connecting clients + will see this new name. + */ +void QWebSocketServer::setServerName(const QString &serverName) +{ + Q_D(QWebSocketServer); + d->setServerName(serverName); +} + +/*! + Returns the server name that is used during the http handshake phase. + */ +QString QWebSocketServer::serverName() const +{ + Q_D(const QWebSocketServer); + return d->serverName(); +} + +/*! + Returns the server's address if the server is listening for connections; otherwise returns QHostAddress::Null. + + \sa serverPort(), listen() + */ +QHostAddress QWebSocketServer::serverAddress() const +{ + Q_D(const QWebSocketServer); + return d->serverAddress(); +} + +/*! + Returns an error code for the last error that occurred. + \sa errorString() + */ +QAbstractSocket::SocketError QWebSocketServer::serverError() const +{ + Q_D(const QWebSocketServer); + return d->serverError(); +} + +/*! + Returns the server's port if the server is listening for connections; otherwise returns 0. + \sa serverAddress(), listen() + */ +quint16 QWebSocketServer::serverPort() const +{ + Q_D(const QWebSocketServer); + return d->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. + 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(), hasPendingConnections() + */ +void QWebSocketServer::setMaxPendingConnections(int numConnections) +{ + Q_D(QWebSocketServer); + d->setMaxPendingConnections(numConnections); +} + +/*! + 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. + The socket is assumed to be in listening state. + + \sa socketDescriptor(), isListening() + */ +bool QWebSocketServer::setSocketDescriptor(int socketDescriptor) +{ + Q_D(QWebSocketServer); + return d->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(), isListening() + */ +int QWebSocketServer::socketDescriptor() const +{ + Q_D(const QWebSocketServer); + return d->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(), nextPendingConnection() +*/ +bool QWebSocketServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QWebSocketServer); + return d->waitForNewConnection(msec, timedOut); +} + +/*! + Returns a list of websocket versions that this server is supporting. + */ +QList QWebSocketServer::supportedVersions() const +{ + Q_D(const QWebSocketServer); + return d->supportedVersions(); +} + +/*! + Returns a list of websocket subprotocols that this server supports. + */ +QList QWebSocketServer::supportedProtocols() const +{ + Q_D(const QWebSocketServer); + return d->supportedProtocols(); +} + +/*! + Returns a list of websocket extensions that this server supports. + */ +QList QWebSocketServer::supportedExtensions() const +{ + Q_D(const QWebSocketServer); + return d->supportedExtensions(); +} + +QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketserver.h b/src/websockets/qwebsocketserver.h new file mode 100644 index 0000000..6579b53 --- /dev/null +++ b/src/websockets/qwebsocketserver.h @@ -0,0 +1,92 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKETSERVER_H +#define QWEBSOCKETSERVER_H + +#include +#include +#include +#include "qwebsocketsglobal.h" +#include "qwebsocketprotocol.h" + +QT_BEGIN_NAMESPACE + +class QWebSocketServerPrivate; +class QWebSocket; +class QCorsAuthenticator; + +class Q_WEBSOCKETS_EXPORT QWebSocketServer : public QObject +{ + Q_OBJECT + +public: + explicit QWebSocketServer(const QString &serverName, QObject *parent = 0); + virtual ~QWebSocketServer(); + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + bool setSocketDescriptor(int socketDescriptor); + int socketDescriptor() const; + + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + bool hasPendingConnections() const; + virtual QWebSocket *nextPendingConnection(); + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + + void pauseAccepting(); + void resumeAccepting(); + + void setServerName(const QString &serverName); + QString serverName() const; + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + + QList supportedVersions() const; + QList supportedProtocols() const; + QList supportedExtensions() const; + +Q_SIGNALS: + void acceptError(QAbstractSocket::SocketError socketError); + void originAuthenticationRequired(QCorsAuthenticator *pAuthenticator); + void newConnection(); + +private: + Q_DISABLE_COPY(QWebSocketServer) + Q_DECLARE_PRIVATE(QWebSocketServer) + QWebSocketServerPrivate * const d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKETSERVER_H diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp new file mode 100644 index 0000000..8e4bc7b --- /dev/null +++ b/src/websockets/qwebsocketserver_p.cpp @@ -0,0 +1,362 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include "qwebsocketserver.h" +#include "qwebsocketserver_p.h" +#include "qwebsocketprotocol.h" +#include "handshakerequest_p.h" +#include "handshakeresponse_p.h" +#include "qwebsocket.h" +#include "qwebsocket_p.h" +#include "qcorsauthenticator.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + */ +QWebSocketServerPrivate::QWebSocketServerPrivate(const QString &serverName, QWebSocketServer * const pWebSocketServer, QObject *parent) : + QObject(parent), + q_ptr(pWebSocketServer), + m_pTcpServer(0), + m_serverName(serverName), + m_pendingConnections() +{ + Q_ASSERT(pWebSocketServer != 0); + m_pTcpServer = new QTcpServer(this); + connect(m_pTcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), q_ptr, SIGNAL(acceptError(QAbstractSocket::SocketError))); + connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); +} + +/*! + \internal + */ +QWebSocketServerPrivate::~QWebSocketServerPrivate() +{ + while (!m_pendingConnections.isEmpty()) + { + QWebSocket *pWebSocket = m_pendingConnections.dequeue(); + pWebSocket->close(QWebSocketProtocol::CC_GOING_AWAY, tr("Server closed.")); + pWebSocket->deleteLater(); + } + m_pTcpServer->deleteLater(); +} + +/*! + \internal + */ +void QWebSocketServerPrivate::close() +{ + m_pTcpServer->close(); +} + +/*! + \internal + */ +QString QWebSocketServerPrivate::errorString() const +{ + return m_pTcpServer->errorString(); +} + +/*! + \internal + */ +bool QWebSocketServerPrivate::hasPendingConnections() const +{ + return !m_pendingConnections.isEmpty(); +} + +/*! + \internal + */ +bool QWebSocketServerPrivate::isListening() const +{ + return m_pTcpServer->isListening(); +} + +/*! + \internal + */ +bool QWebSocketServerPrivate::listen(const QHostAddress &address, quint16 port) +{ + return m_pTcpServer->listen(address, port); +} + +/*! + \internal + */ +int QWebSocketServerPrivate::maxPendingConnections() const +{ + return m_pTcpServer->maxPendingConnections(); +} + +/*! + \internal + */ +void QWebSocketServerPrivate::addPendingConnection(QWebSocket *pWebSocket) +{ + if (m_pendingConnections.size() < maxPendingConnections()) + { + m_pendingConnections.enqueue(pWebSocket); + } +} + +/*! + \internal + */ +QWebSocket *QWebSocketServerPrivate::nextPendingConnection() +{ + QWebSocket *pWebSocket = 0; + if (!m_pendingConnections.isEmpty()) + { + pWebSocket = m_pendingConnections.dequeue(); + } + return pWebSocket; +} + +/*! + \internal + */ +void QWebSocketServerPrivate::pauseAccepting() +{ + m_pTcpServer->pauseAccepting(); +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \internal + */ +QNetworkProxy QWebSocketServerPrivate::proxy() const +{ + return m_pTcpServer->proxy(); +} + +/*! + \internal + */ +void QWebSocketServerPrivate::setProxy(const QNetworkProxy &networkProxy) +{ + m_pTcpServer->setProxy(networkProxy); +} +#endif +/*! + \internal + */ +void QWebSocketServerPrivate::resumeAccepting() +{ + m_pTcpServer->resumeAccepting(); +} + +/*! + \internal + */ +QHostAddress QWebSocketServerPrivate::serverAddress() const +{ + return m_pTcpServer->serverAddress(); +} + +/*! + \internal + */ +QAbstractSocket::SocketError QWebSocketServerPrivate::serverError() const +{ + return m_pTcpServer->serverError(); +} + +/*! + \internal + */ +quint16 QWebSocketServerPrivate::serverPort() const +{ + return m_pTcpServer->serverPort(); +} + +/*! + \internal + */ +void QWebSocketServerPrivate::setMaxPendingConnections(int numConnections) +{ + m_pTcpServer->setMaxPendingConnections(numConnections); +} + +/*! + \internal + */ +bool QWebSocketServerPrivate::setSocketDescriptor(int socketDescriptor) +{ + return m_pTcpServer->setSocketDescriptor(socketDescriptor); +} + +/*! + \internal + */ +int QWebSocketServerPrivate::socketDescriptor() const +{ + return m_pTcpServer->socketDescriptor(); +} + +/*! + \internal + */ +bool QWebSocketServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + return m_pTcpServer->waitForNewConnection(msec, timedOut); +} + +/*! + \internal + */ +QList QWebSocketServerPrivate::supportedVersions() const +{ + QList supportedVersions; + supportedVersions << QWebSocketProtocol::currentVersion(); //we only support V13 + return supportedVersions; +} + +/*! + \internal + */ +QList QWebSocketServerPrivate::supportedProtocols() const +{ + QList supportedProtocols; + return supportedProtocols; //no protocols are currently supported +} + +/*! + \internal + */ +QList QWebSocketServerPrivate::supportedExtensions() const +{ + QList supportedExtensions; + return supportedExtensions; //no extensions are currently supported +} + +/*! + \internal + */ +void QWebSocketServerPrivate::setServerName(const QString &serverName) +{ + m_serverName = serverName; +} + +/*! + \internal + */ +QString QWebSocketServerPrivate::serverName() const +{ + return m_serverName; +} + +/*! + \internal + */ +void QWebSocketServerPrivate::onNewConnection() +{ + QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection(); + connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); +} + +/*! + \internal + */ +void QWebSocketServerPrivate::onCloseConnection() +{ + QTcpSocket *pTcpSocket = qobject_cast(sender()); + if (pTcpSocket != 0) + { + pTcpSocket->close(); + } +} + +/*! + \internal + */ +void QWebSocketServerPrivate::handshakeReceived() +{ + Q_Q(QWebSocketServer); + QTcpSocket *pTcpSocket = qobject_cast(sender()); + if (pTcpSocket != 0) + { + bool success = false; + bool isSecure = false; + HandshakeRequest request(pTcpSocket->peerPort(), isSecure); + QTextStream textStream(pTcpSocket); + textStream >> request; + + QCorsAuthenticator corsAuthenticator(request.getOrigin()); + Q_EMIT q->originAuthenticationRequired(&corsAuthenticator); + + HandshakeResponse response(request, + m_serverName, + corsAuthenticator.allowed(), + 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 = QWebSocketPrivate::upgradeFrom(pTcpSocket, request, response); + if (pWebSocket) + { + pWebSocket->setParent(this); + addPendingConnection(pWebSocket); + Q_EMIT q->newConnection(); + success = true; + } + else + { + //TODO: should set or emit error + qDebug() << tr("Upgrading to websocket failed."); + } + } + else + { + //TODO: should set or emit error + qDebug() << tr("Cannot upgrade to websocket."); + } + } + else + { + //TODO: should set or emit error + qDebug() << tr("Invalid response received."); + } + if (!success) + { + //TODO: should set or emit error + qDebug() << tr("Closing socket because of invalid or unsupported request."); + pTcpSocket->close(); + } + } + else + { + qWarning() << "Sender socket is NULL. This should not happen, otherwise it is a Qt bug!!!"; + } +} + +QT_END_NAMESPACE diff --git a/src/websockets/qwebsocketserver_p.h b/src/websockets/qwebsocketserver_p.h new file mode 100644 index 0000000..081ecec --- /dev/null +++ b/src/websockets/qwebsocketserver_p.h @@ -0,0 +1,101 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKETSERVER_P_H +#define QWEBSOCKETSERVER_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include "qwebsocket.h" + +QT_BEGIN_NAMESPACE + +class QTcpServer; +class QWebSocketServer; + +class QWebSocketServerPrivate : public QObject +{ + Q_OBJECT + +public: + explicit QWebSocketServerPrivate(const QString &serverName, QWebSocketServer * const pWebSocketServer, QObject *parent = 0); + virtual ~QWebSocketServerPrivate(); + + 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(); + void pauseAccepting(); +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy() const; + void setProxy(const QNetworkProxy &networkProxy); +#endif + void resumeAccepting(); + QHostAddress serverAddress() const; + QAbstractSocket::SocketError serverError() const; + quint16 serverPort() const; + void setMaxPendingConnections(int numConnections); + bool setSocketDescriptor(int socketDescriptor); + int socketDescriptor() const; + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + + QList supportedVersions() const; + QList supportedProtocols() const; + QList supportedExtensions() const; + + void setServerName(const QString &serverName); + QString serverName() const; + +Q_SIGNALS: + void newConnection(); + +private Q_SLOTS: + void onNewConnection(); + void onCloseConnection(); + void handshakeReceived(); + +private: + Q_DECLARE_PUBLIC(QWebSocketServer) + QWebSocketServer * const q_ptr; + + QTcpServer *m_pTcpServer; + QString m_serverName; + QQueue m_pendingConnections; + + void addPendingConnection(QWebSocket *pWebSocket); +}; + +QT_END_NAMESPACE + +#endif // QWEBSOCKETSERVER_P_H diff --git a/src/websockets/qwebsocketsglobal.h b/src/websockets/qwebsocketsglobal.h new file mode 100644 index 0000000..e7eb583 --- /dev/null +++ b/src/websockets/qwebsocketsglobal.h @@ -0,0 +1,43 @@ +/* +QWebSockets implements the WebSocket protocol as defined in RFC 6455. +Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QWEBSOCKETSGLOBAL_H +#define QWEBSOCKETSGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_WEBSOCKETS_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/src/websockets/websockets.pro b/src/websockets/websockets.pro new file mode 100644 index 0000000..69ddc9c --- /dev/null +++ b/src/websockets/websockets.pro @@ -0,0 +1,45 @@ +QT += network + +TARGET = QtWebSockets + +TEMPLATE += lib + +DEFINES += QTWEBSOCKETS_LIBRARY + +load(qt_module) + +QMAKE_DOCS = $$PWD/doc/qwebsockets.qdocconfig + +PUBLIC_HEADERS += \ + $$PWD/qwebsocket.h \ + $$PWD/qwebsocketserver.h \ + $$PWD/qwebsocketprotocol.h \ + $$PWD/qwebsocketsglobal.h \ + $$PWD/qcorsauthenticator.h + +PRIVATE_HEADERS += \ + $$PWD/qwebsocket_p.h \ + $$PWD/qwebsocketserver_p.h \ + $$PWD/handshakerequest_p.h \ + $$PWD/handshakeresponse_p.h \ + $$PWD/dataprocessor_p.h \ + $$PWD/qcorsauthenticator_p.h + +SOURCES += \ + $$PWD/qwebsocket.cpp \ + $$PWD/qwebsocket_p.cpp \ + $$PWD/qwebsocketserver.cpp \ + $$PWD/qwebsocketserver_p.cpp \ + $$PWD/qwebsocketprotocol.cpp \ + $$PWD/handshakerequest_p.cpp \ + $$PWD/handshakeresponse_p.cpp \ + $$PWD/dataprocessor_p.cpp \ + $$PWD/qcorsauthenticator.cpp + +HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS + +#mac:QMAKE_FRAMEWORK_BUNDLE_NAME = $$TARGET +#mac:QMAKE_CXXFLAGS += -Wall -Werror -Wextra + + + diff --git a/sync.profile b/sync.profile index 7933ae7..b26d76c 100644 --- a/sync.profile +++ b/sync.profile @@ -1,5 +1,12 @@ %modules = ( - "QWebSockets" => "$basedir/src", + "QtWebSockets" => "$basedir/src/websockets", +); + +%moduleheaders = ( +); + +%classnames = ( + "qtwebsockets.h" => "QtWebSockets", ); %dependencies = ( diff --git a/tests/tests.pro b/tests/tests.pro index dc855d1..9cd3320 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -1,6 +1,6 @@ cache() -QT += core network testlib +QT += core network websockets testlib TARGET = unittests CONFIG += testcase @@ -10,19 +10,6 @@ CONFIG -= app_bundle TEMPLATE = app -mac:QMAKE_CXXFLAGS += -Wall -Werror -Wextra - -include(../src/qwebsockets.pri) - -# Remove the main.cpp file from the sources. -#S = $$SOURCES -#for(F, S) { -# M = $$find(F, main.cpp) -# count(M, 0) { -# SOURCES += $$F -# } -#} - SOURCES += \ main.cpp \ tst_websockets.cpp \ @@ -32,7 +19,4 @@ SOURCES += \ HEADERS += \ unittests.h -INCLUDEPATH += -DEPENDPATH += - DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/websockets.pro b/websockets.pro new file mode 100644 index 0000000..58c33f2 --- /dev/null +++ b/websockets.pro @@ -0,0 +1 @@ +load(qt_parts) -- cgit v1.2.1