From 8796c69480a6e5e331d19edf24d4dabb180bc4d2 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 10 Oct 2016 03:55:35 +0200 Subject: QNetworkSession: make sure that "interface" isn't #defined Depending on #include order isn't a good idea. Change-Id: Ief935e1fcc5d40ecb510fffd147c08dffe6cba2d Reviewed-by: Edward Welbourne --- src/network/bearer/qnetworksession.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/network') diff --git a/src/network/bearer/qnetworksession.cpp b/src/network/bearer/qnetworksession.cpp index 6f83fd25ca..1b939bab01 100644 --- a/src/network/bearer/qnetworksession.cpp +++ b/src/network/bearer/qnetworksession.cpp @@ -42,6 +42,11 @@ #include "qnetworkconfigmanager_p.h" +// for QNetworkSession::interface +#ifdef interface +# undef interface +#endif + #ifndef QT_NO_BEARERMANAGEMENT QT_BEGIN_NAMESPACE -- cgit v1.2.3 From dcf7da7c93c0d974bfb72ffa677a1d26fb9be5e0 Mon Sep 17 00:00:00 2001 From: Oliver Wolff Date: Mon, 10 Oct 2016 14:35:19 +0200 Subject: winrt: Do not lose initial data for TCP connections When a client connects and sends data immediately it was possible that initial data was lost as the state was set too late. If the callback was called before the state was set the socket engine just discarded the data. So the state has to be set before the callback is registered. The new implementation needs a list of pending read operations. It can happen that the "readyRead" callback is triggered directly while "put_Completed" is called. The callback reassigns readOp which causes a "function not implemented" exception when it jumps back to the "put_Completed" call in "initialize" Task-number: QTBUG-55889 Change-Id: I5f52e3377b6176f1f90f227ac0bf52b60ee2d95a Reviewed-by: Maurice Kalinowski --- src/network/socket/qnativesocketengine_winrt.cpp | 34 ++++++++++++++++++------ src/network/socket/qnativesocketengine_winrt_p.h | 2 +- 2 files changed, 27 insertions(+), 9 deletions(-) (limited to 'src/network') diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp index bd9b443602..2ff028d2c5 100644 --- a/src/network/socket/qnativesocketengine_winrt.cpp +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -317,26 +317,31 @@ bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket:: // Start processing incoming data if (d->socketType == QAbstractSocket::TcpSocket) { HRESULT hr; - QEventDispatcherWinRT::runOnXamlThread([d, &hr, socket, this]() { + QEventDispatcherWinRT::runOnXamlThread([&hr, socket, socketState, this]() { + Q_D(QNativeSocketEngine); ComPtr buffer; HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer); RETURN_OK_IF_FAILED("initialize(): Could not create buffer"); ComPtr stream; hr = socket->get_InputStream(&stream); RETURN_OK_IF_FAILED("initialize(): Could not obtain input stream"); - hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, d->readOp.GetAddressOf()); + ComPtr readOp; + hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, readOp.GetAddressOf()); RETURN_OK_IF_FAILED_WITH_ARGS("initialize(): Failed to read from the socket buffer (%s).", socketDescription(this).constData()); - hr = d->readOp->put_Completed(Callback(d, &QNativeSocketEnginePrivate::handleReadyRead).Get()); + d->pendingReadOps.append(readOp); + d->socketState = socketState; + hr = readOp->put_Completed(Callback(d, &QNativeSocketEnginePrivate::handleReadyRead).Get()); RETURN_OK_IF_FAILED_WITH_ARGS("initialize(): Failed to set socket read callback (%s).", socketDescription(this).constData()); return S_OK; }); if (FAILED(hr)) return false; + } else { + d->socketState = socketState; } - d->socketState = socketState; return true; } @@ -567,9 +572,9 @@ void QNativeSocketEngine::close() } #endif // _MSC_VER >= 1900 - if (d->readOp) { + for (ComPtr readOp : d->pendingReadOps) { ComPtr info; - hr = d->readOp.As(&info); + hr = readOp.As(&info); Q_ASSERT_SUCCEEDED(hr); if (info) { hr = info->Cancel(); @@ -933,9 +938,11 @@ void QNativeSocketEngine::establishRead() hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer); RETURN_HR_IF_FAILED("establishRead(): Failed to create buffer"); - hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &d->readOp); + ComPtr readOp; + hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, readOp.GetAddressOf()); RETURN_HR_IF_FAILED("establishRead(): Failed to initiate socket read"); - hr = d->readOp->put_Completed(Callback(d, &QNativeSocketEnginePrivate::handleReadyRead).Get()); + d->pendingReadOps.append(readOp); + hr = readOp->put_Completed(Callback(d, &QNativeSocketEnginePrivate::handleReadyRead).Get()); RETURN_HR_IF_FAILED("establishRead(): Failed to register read callback"); return S_OK; }); @@ -1410,7 +1417,15 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async } Q_Q(QNativeSocketEngine); + for (int i = 0; i < pendingReadOps.count(); ++i) { + if (pendingReadOps.at(i).Get() == asyncInfo) { + pendingReadOps.takeAt(i); + break; + } + } + static QMutex mutex; + mutex.lock(); // A read in UnconnectedState will close the socket and return -1 and thus tell the caller, // that the connection was closed. The socket cannot be closed here, as the subsequent read // might fail then. @@ -1463,6 +1478,7 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async if (notifyOnRead) emit q->readReady(); + mutex.unlock(); hr = QEventDispatcherWinRT::runOnXamlThread([buffer, q, this]() { UINT32 readBufferLength; @@ -1476,12 +1492,14 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async hr = buffer->put_Length(0); RETURN_HR_IF_FAILED("handleReadyRead(): Could not set buffer length"); + ComPtr readOp; hr = stream->ReadAsync(buffer.Get(), readBufferLength, InputStreamOptions_Partial, &readOp); if (FAILED(hr)) { qErrnoWarning(hr, "handleReadyRead(): Could not read into socket stream buffer (%s).", socketDescription(q).constData()); return S_OK; } + pendingReadOps.append(readOp); hr = readOp->put_Completed(Callback(this, &QNativeSocketEnginePrivate::handleReadyRead).Get()); if (FAILED(hr)) { qErrnoWarning(hr, "handleReadyRead(): Failed to set socket read callback (%s).", diff --git a/src/network/socket/qnativesocketengine_winrt_p.h b/src/network/socket/qnativesocketengine_winrt_p.h index 605f3631b9..79530d57f1 100644 --- a/src/network/socket/qnativesocketengine_winrt_p.h +++ b/src/network/socket/qnativesocketengine_winrt_p.h @@ -214,7 +214,7 @@ private: { return reinterpret_cast(socketDescriptor); } Microsoft::WRL::ComPtr tcpListener; Microsoft::WRL::ComPtr connectOp; - Microsoft::WRL::ComPtr> readOp; + QVector>> pendingReadOps; QBuffer readBytes; QMutex readMutex; bool emitOnNewDatagram; -- cgit v1.2.3 From f71aa48138e939ccb687ffab6afca734b1b13973 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Wed, 12 Oct 2016 09:19:11 +0200 Subject: Fix resolution of OPENSSL_LIBS in ssl.pri Task-number: QTBUG-55530 Change-Id: Icc5ae9849e41479732eb44d01d9ea37aa3da16f8 Reviewed-by: Oswald Buddenhagen --- src/network/ssl/ssl.pri | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/network') diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri index 8139af50af..79351017a6 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -79,6 +79,8 @@ qtConfig(ssl) { # - libs in \lib\VC\static # - configure: -openssl -openssl-linked -I \include -L \lib\VC\static OPENSSL_LIBS="-lUser32 -lAdvapi32 -lGdi32" OPENSSL_LIBS_DEBUG="-lssleay32MDd -llibeay32MDd" OPENSSL_LIBS_RELEASE="-lssleay32MD -llibeay32MD" + include($$OUT_PWD/qtnetwork-config.pri) + CONFIG(debug, debug|release) { LIBS_PRIVATE += $$OPENSSL_LIBS_DEBUG } else { -- cgit v1.2.3 From aec9cebf8c1ef4b9d497f76337d7af0b8b5f8d46 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 12 Oct 2016 16:36:50 +0200 Subject: make setting OPENSSL_LIBS_{DEBUG,RELEASE} work with dynamic builds while it's probably not really necessary (which is why it wasn't implemented before), just ignoring the options is somewhat inconsistent and a deviation from historical behavior. Task-number: QTBUG-55530 Change-Id: I9441bf7be50ab5c997bb745e2525048ca23e4cd5 Reviewed-by: Jake Petroules Reviewed-by: Kai Koehne --- src/network/configure.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/network') diff --git a/src/network/configure.json b/src/network/configure.json index 97bf92167d..124fa1718f 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -60,7 +60,14 @@ }, "condition": "config.win32 && !features.shared" }, - { "libs": "-lssleay32 -llibeay32", "condition": "config.win32 && features.shared" }, + { + "libs": "-lssleay32 -llibeay32", + "builds": { + "debug": "", + "release": "" + }, + "condition": "config.win32 && features.shared" + }, { "libs": "-lssl -lcrypto", "condition": "!config.win32" } ] } -- cgit v1.2.3 From 0e61323c87490ea3991f7b6211034285ce5a932f Mon Sep 17 00:00:00 2001 From: Oliver Wolff Date: Thu, 13 Oct 2016 13:59:44 +0200 Subject: winrt: Added timeout for cancellation of socket read operation As the function runs on the XAML thread it can make the app unresponsive/wait forever on a socket close. Thus we should not wait forever but have a timeout. If the timeout is hit the socket is not closed properly but hard reset. Change-Id: I82e9425c0f8195e3465027fdc2417a93f1c1ad91 Reviewed-by: Maurice Kalinowski --- src/network/socket/qnativesocketengine_winrt.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/network') diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp index 58f0668854..b6a739d1b8 100644 --- a/src/network/socket/qnativesocketengine_winrt.cpp +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -492,10 +492,12 @@ void QNativeSocketEngine::close() ComPtr action; hr = socket3->CancelIOAsync(&action); Q_ASSERT_SUCCEEDED(hr); - hr = QWinRTFunctions::await(action); + hr = QWinRTFunctions::await(action, QWinRTFunctions::YieldThread, 5000); // If there is no pending IO (no read established before) the function will fail with // "function was called at an unexpected time" which is fine. - if (hr != E_ILLEGAL_METHOD_CALL) + // Timeout is fine as well. The result will be the socket being hard reset instead of + // being closed gracefully + if (hr != E_ILLEGAL_METHOD_CALL && hr != ERROR_TIMEOUT) Q_ASSERT_SUCCEEDED(hr); return S_OK; }); -- cgit v1.2.3 From 0cccc23478432240f44cabfd853e60ff8c84c692 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Sun, 9 Oct 2016 18:08:14 +0200 Subject: QNetworkReplyHttpImpl: Fix UB (member call) in destruction sequence Found by UBSan: qnetworkreplyhttpimpl.cpp:457:29: runtime error: member call on address 0x602000009cf0 which does not point to an object of type 'QNetworkReplyHttpImpl' 0x602000009cf0: note: object is of type 'QObject' 1e 00 80 18 20 e0 bb 12 54 7f 00 00 00 f2 00 00 70 61 00 00 02 00 00 00 ff ff ff 06 08 00 00 00 ^~~~~~~~~~~~~~~~~~~~~~~ vptr for 'QObject' #0 0x7f541461b71b in QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate() qnetworkreplyhttpimpl.cpp:457 #1 0x7f541461b7f0 in QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate() qnetworkreplyhttpimpl.cpp:458 #2 0x7f540f26df1a in QScopedPointerDeleter::cleanup(QObjectData*) qscopedpointer.h:54 #3 0x7f540f26df1a in QScopedPointer >::~QScopedPointer() qscopedpointer.h:101 #4 0x7f540f26df1a in QObject::~QObject() qobject.cpp:940 #5 0x7f540e915f6e in QIODevice::~QIODevice() qiodevice.cpp:416 #6 0x7f5414599bae in QNetworkReply::~QNetworkReply() qnetworkreply.cpp:444 #7 0x7f54145e6f5e in QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl() qnetworkreplyhttpimpl.cpp:239 #8 0x7f54145e6f5e in QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl() qnetworkreplyhttpimpl.cpp:242 #9 0x7f54144b3539 in void qDeleteAll::const_iterator>(QList::const_iterator, QList::const_iterator) qalgorithms.h:317 #10 0x7f54144b3539 in void qDeleteAll >(QList const&) qalgorithms.h:325 #11 0x7f54144b3539 in QNetworkAccessManager::~QNetworkAccessManager() qnetworkaccessmanager.cpp:496 Fix by moving the emission of the QNetworkReplyHttpImpl::abortHttpRequest() signal from ~Private, when the public object is merely a QObject anymore, to ~QNetworkReplyHttpImpl(), when the public class is still itself. Change-Id: Ifb3b19f6d180452bdf3fc26f54629ef780a5d9d9 Reviewed-by: Timur Pocheptsov --- src/network/access/qnetworkreplyhttpimpl.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/network') diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 94235a48dd..fe8277ba92 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -238,7 +238,8 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl() { - // Most work is done in private destructor + // This will do nothing if the request was already finished or aborted + emit abortHttpRequest(); } void QNetworkReplyHttpImpl::close() @@ -452,9 +453,6 @@ QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate() QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate() { - Q_Q(QNetworkReplyHttpImpl); - // This will do nothing if the request was already finished or aborted - emit q->abortHttpRequest(); } /* -- cgit v1.2.3 From 512934f7e70592ed06a790fcb46dde1e435b488e Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 10 Oct 2016 15:29:26 +0200 Subject: HTTP/2 - fix the handling of PUSH_PROMISE HTTP/2 allows a server to pre-emptively send (or "push") responses (along with corresponding "promised" requests) to a client in association with a previous client-initiated request. This can be useful when the server knows the client will need to have those responses available in order to fully process the response to the original request. Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame. The PUSH_PROMISE frame includes a header block that contains a complete set of request header fields that the server attributes to the request. After sending the PUSH_PROMISE frame, the server can begin delivering the pushed response as a response on a server-initiated stream that uses the promised stream identifier. This patch: - fixes the HPACK decompression of PUSH_PROMISE frames; - allows a user to enable PUSH_PROMISE; - processes and caches pushed data for promised streams; - updates auto-test - emulates a simple PUSH_PROMISE scenario. Change-Id: Ic4850863a5e3895320baac3871a723fc091b4aca Reviewed-by: Edward Welbourne --- src/network/access/http2/http2frames.cpp | 33 +++ src/network/access/http2/http2frames_p.h | 35 ++- src/network/access/http2/http2protocol_p.h | 4 + src/network/access/http2/http2streams.cpp | 11 +- src/network/access/http2/http2streams_p.h | 25 +- src/network/access/qhttp2protocolhandler.cpp | 372 +++++++++++++++++++++------ src/network/access/qhttp2protocolhandler_p.h | 19 +- 7 files changed, 400 insertions(+), 99 deletions(-) (limited to 'src/network') diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp index 978bee09b1..5a684c2f41 100644 --- a/src/network/access/http2/http2frames.cpp +++ b/src/network/access/http2/http2frames.cpp @@ -244,6 +244,24 @@ quint32 Frame::dataSize() const return size; } +quint32 Frame::hpackBlockSize() const +{ + Q_ASSERT(validatePayload() == FrameStatus::goodFrame); + + const auto frameType = type(); + Q_ASSERT(frameType == FrameType::HEADERS || + frameType == FrameType::PUSH_PROMISE || + frameType == FrameType::CONTINUATION); + + quint32 size = dataSize(); + if (frameType == FrameType::PUSH_PROMISE) { + Q_ASSERT(size >= 4); + size -= 4; + } + + return size; +} + const uchar *Frame::dataBegin() const { Q_ASSERT(validatePayload() == FrameStatus::goodFrame); @@ -260,6 +278,21 @@ const uchar *Frame::dataBegin() const return src; } +const uchar *Frame::hpackBlockBegin() const +{ + Q_ASSERT(validatePayload() == FrameStatus::goodFrame); + + const auto frameType = type(); + Q_ASSERT(frameType == FrameType::HEADERS || + frameType == FrameType::PUSH_PROMISE || + frameType == FrameType::CONTINUATION); + + const uchar *begin = dataBegin(); + if (frameType == FrameType::PUSH_PROMISE) + begin += 4; // That's a promised stream, skip it. + return begin; +} + FrameStatus FrameReader::read(QAbstractSocket &socket) { if (offset < frameHeaderSize) { diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h index 84ba9c3662..e5f6d46c67 100644 --- a/src/network/access/http2/http2frames_p.h +++ b/src/network/access/http2/http2frames_p.h @@ -71,27 +71,29 @@ namespace Http2 struct Q_AUTOTEST_EXPORT Frame { Frame(); - // Reading these values without first forming a valid frame - // (either reading it from a socket or building it) will result - // in undefined behavior: + // Reading these values without first forming a valid frame (either reading + // it from a socket or building it) will result in undefined behavior: FrameType type() const; quint32 streamID() const; FrameFlags flags() const; quint32 payloadSize() const; uchar padding() const; - // In HTTP/2 a stream's priority is specified by its weight - // and a stream (id) it depends on: + // In HTTP/2 a stream's priority is specified by its weight and a stream + // (id) it depends on: bool priority(quint32 *streamID = nullptr, uchar *weight = nullptr) const; FrameStatus validateHeader() const; FrameStatus validatePayload() const; - // Number of payload bytes without padding and/or priority + // Number of payload bytes without padding and/or priority. quint32 dataSize() const; - // Beginning of payload without priority/padding - // bytes. + // HEADERS data size for HEADERS, PUSH_PROMISE and CONTINUATION streams: + quint32 hpackBlockSize() const; + // Beginning of payload without priority/padding bytes. const uchar *dataBegin() const; + // HEADERS data beginning for HEADERS, PUSH_PROMISE and CONTINUATION streams: + const uchar *hpackBlockBegin() const; std::vector buffer; }; @@ -134,8 +136,7 @@ public: void setFlags(FrameFlags flags); void addFlag(FrameFlag flag); - // All append functions also update frame's payload - // length. + // All append functions also update frame's payload length. template void append(ValueType val) { @@ -161,16 +162,14 @@ public: // Write as a single frame: bool write(QAbstractSocket &socket) const; - // Two types of frames we are sending are affected by - // frame size limits: HEADERS and DATA. HEADERS' payload - // (hpacked HTTP headers, following a frame header) - // is always in our 'buffer', we send the initial HEADERS + // Two types of frames we are sending are affected by frame size limits: + // HEADERS and DATA. HEADERS' payload (hpacked HTTP headers, following a + // frame header) is always in our 'buffer', we send the initial HEADERS // frame first and then CONTINUTATION frame(s) if needed: bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit); - // With DATA frames the actual payload is never in our 'buffer', - // it's a 'readPointer' from QNonContiguousData. We split - // this payload as needed into DATA frames with correct - // payload size fitting into frame size limit: + // With DATA frames the actual payload is never in our 'buffer', it's a + // 'readPointer' from QNonContiguousData. We split this payload as needed + // into DATA frames with correct payload size fitting into frame size limit: bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit, const uchar *src, quint32 size); private: diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h index 5c46949e23..5d730404bb 100644 --- a/src/network/access/http2/http2protocol_p.h +++ b/src/network/access/http2/http2protocol_p.h @@ -127,6 +127,10 @@ enum Http2PredefinedParameters maxConcurrentStreams = 100 // HTTP/2, 6.5.2 }; +// It's int, it has internal linkage, it's ok to have it in headers - +// no ODR violation is possible. +const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1 + extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength]; enum class FrameStatus diff --git a/src/network/access/http2/http2streams.cpp b/src/network/access/http2/http2streams.cpp index 660100f5e4..fa39c1d57b 100644 --- a/src/network/access/http2/http2streams.cpp +++ b/src/network/access/http2/http2streams.cpp @@ -61,6 +61,15 @@ Stream::Stream(const HttpMessagePair &message, quint32 id, qint32 sendSize, qint { } +Stream::Stream(const QString &cacheKey, quint32 id, qint32 recvSize) + : streamID(id), + // sendWindow is 0, this stream only receives data + recvWindow(recvSize), + state(remoteReserved), + key(cacheKey) +{ +} + QHttpNetworkReply *Stream::reply() const { return httpPair.second; @@ -99,6 +108,6 @@ QNonContiguousByteDevice *Stream::data() const return httpPair.first.uploadByteDevice(); } -} +} // namespace Http2 QT_END_NAMESPACE diff --git a/src/network/access/http2/http2streams_p.h b/src/network/access/http2/http2streams_p.h index 8a825a5457..8465486ae8 100644 --- a/src/network/access/http2/http2streams_p.h +++ b/src/network/access/http2/http2streams_p.h @@ -51,10 +51,16 @@ // We mean it. // +#include "http2frames_p.h" +#include "hpack_p.h" + #include #include #include +#include + +#include QT_BEGIN_NAMESPACE @@ -70,12 +76,16 @@ struct Q_AUTOTEST_EXPORT Stream open, halfClosedLocal, halfClosedRemote, + remoteReserved, closed }; Stream(); + // That's a ctor for a client-initiated stream: Stream(const HttpMessagePair &message, quint32 streamID, qint32 sendSize, qint32 recvSize); + // That's a reserved stream, created by PUSH_PROMISE from a server: + Stream(const QString &key, quint32 streamID, qint32 recvSize); QHttpNetworkReply *reply() const; const QHttpNetworkRequest &request() const; @@ -92,9 +102,22 @@ struct Q_AUTOTEST_EXPORT Stream qint32 recvWindow = 65535; StreamState state = idle; + QString key; // for PUSH_PROMISE +}; + +struct PushPromise +{ + quint32 reservedID = 0; + // PUSH_PROMISE has its own HEADERS, + // usually similar to what request has: + HPack::HttpHeader pushHeader; + // Response has its own (normal) HEADERS: + HPack::HttpHeader responseHeader; + // DATA frames on a promised stream: + std::vector dataFrames; }; -} +} // namespace Http2 QT_END_NAMESPACE diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 68a00c6837..3fa0c18dc0 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -108,6 +108,41 @@ HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxH return header; } +std::vector assemble_hpack_block(const std::vector &frames) +{ + std::vector hpackBlock; + + quint32 total = 0; + for (const auto &frame : frames) + total += frame.hpackBlockSize(); + + if (!total) + return hpackBlock; + + hpackBlock.resize(total); + auto dst = hpackBlock.begin(); + for (const auto &frame : frames) { + if (const auto hpackBlockSize = frame.hpackBlockSize()) { + const uchar *src = frame.hpackBlockBegin(); + std::copy(src, src + hpackBlockSize, dst); + dst += hpackBlockSize; + } + } + + return hpackBlock; +} + +QUrl urlkey_from_request(const QHttpNetworkRequest &request) +{ + QUrl url; + + url.setScheme(request.url().scheme()); + url.setAuthority(request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo)); + url.setPath(QLatin1String(request.uri(false))); + + return url; +} + bool sum_will_overflow(qint32 windowSize, qint32 delta) { if (windowSize > 0) @@ -131,6 +166,9 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan encoder(HPack::FieldLookupTable::DefaultSize, true) { continuedFrames.reserve(20); + bool ok = false; + const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok); + pushPromiseEnabled = ok && env; } void QHttp2ProtocolHandler::_q_uploadDataReadyRead() @@ -241,10 +279,25 @@ bool QHttp2ProtocolHandler::sendRequest() if (!requests.size()) return true; + m_channel->state = QHttpNetworkConnectionChannel::WritingState; + // Check what was promised/pushed, maybe we do not have to send a request + // and have a response already? + + for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) { + const auto key = urlkey_from_request(it->first).toString(); + if (!promisedData.contains(key)) { + ++it; + continue; + } + // Woo-hoo, we do not have to ask, the answer is ready for us: + HttpMessagePair message = *it; + it = requests.erase(it); + initReplyFromPushPromise(message, key); + } + const auto streamsToUse = std::min(maxConcurrentStreams - activeStreams.size(), requests.size()); auto it = requests.begin(); - m_channel->state = QHttpNetworkConnectionChannel::WritingState; for (quint32 i = 0; i < streamsToUse; ++i) { const qint32 newStreamID = createNewStream(*it); if (!newStreamID) { @@ -293,11 +346,11 @@ bool QHttp2ProtocolHandler::sendClientPreface() // 6.5 SETTINGS frameWriter.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID); - // MAX frame size (16 kb), disable PUSH + // MAX frame size (16 kb), enable/disable PUSH frameWriter.append(Settings::MAX_FRAME_SIZE_ID); frameWriter.append(quint32(Http2::maxFrameSize)); frameWriter.append(Settings::ENABLE_PUSH_ID); - frameWriter.append(quint32(0)); + frameWriter.append(quint32(pushPromiseEnabled)); if (!frameWriter.write(*m_socket)) return false; @@ -621,7 +674,7 @@ void QHttp2ProtocolHandler::handlePUSH_PROMISE() // 6.6 PUSH_PROMISE. Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE); - if (prefaceSent && !waitingForSettingsACK) { + if (!pushPromiseEnabled && prefaceSent && !waitingForSettingsACK) { // This means, server ACKed our 'NO PUSH', // but sent us PUSH_PROMISE anyway. return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame"); @@ -639,15 +692,19 @@ void QHttp2ProtocolHandler::handlePUSH_PROMISE() } const auto reservedID = qFromBigEndian(inboundFrame.dataBegin()); - if (!reservedID || (reservedID & 0x1)) { + if ((reservedID & 1) || reservedID <= lastPromisedID || + reservedID > Http2::lastValidStreamID) { return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid promised stream ID"); } - // "ignoring a PUSH_PROMISE frame causes the stream - // state to become indeterminate" - let's RST_STREAM it then ... - sendRST_STREAM(reservedID, REFUSE_STREAM); - markAsReset(reservedID); + lastPromisedID = reservedID; + + if (!pushPromiseEnabled) { + // "ignoring a PUSH_PROMISE frame causes the stream state to become + // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code. + resetPromisedStream(inboundFrame, Http2::REFUSE_STREAM); + } const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS); continuedFrames.clear(); @@ -710,7 +767,7 @@ void QHttp2ProtocolHandler::handleGOAWAY() // "A server that is attempting to gracefully shut down a connection SHOULD // send an initial GOAWAY frame with the last stream identifier set to 2^31-1 // and a NO_ERROR code." - if (lastStreamID != (quint32(1) << 31) - 1 || errorCode != HTTP2_NO_ERROR) + if (lastStreamID != Http2::lastValidStreamID || errorCode != HTTP2_NO_ERROR) return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code"); lastStreamID = 1; } else { @@ -795,16 +852,24 @@ void QHttp2ProtocolHandler::handleCONTINUATION() void QHttp2ProtocolHandler::handleContinuedHEADERS() { + // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame + // with/without END_HEADERS flag set plus, if no END_HEADERS flag, + // a sequence of one or more CONTINUATION frames. Q_ASSERT(continuedFrames.size()); + const auto firstFrameType = continuedFrames[0].type(); + Q_ASSERT(firstFrameType == FrameType::HEADERS || + firstFrameType == FrameType::PUSH_PROMISE); const auto streamID = continuedFrames[0].streamID(); - if (continuedFrames[0].type() == FrameType::HEADERS) { + if (firstFrameType == FrameType::HEADERS) { if (activeStreams.contains(streamID)) { Stream &stream = activeStreams[streamID]; - if (stream.state != Stream::halfClosedLocal) { - // If we're receiving headers, they're a response to a request we sent; - // and we closed our end when we finished sending that. + if (stream.state != Stream::halfClosedLocal + && stream.state != Stream::remoteReserved) { + // We can receive HEADERS on streams initiated by our requests + // (these streams are in halfClosedLocal state) or remote-reserved + // streams from a server's PUSH_PROMISE. finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError, QLatin1String("HEADERS on invalid stream")); sendRST_STREAM(streamID, CANCEL); @@ -815,42 +880,49 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS() } else if (!streamWasReset(streamID)) { return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream"); } + // Else: we cannot just ignore our peer's HEADERS frames - they change + // HPACK context - even though the stream was reset; apparently the peer + // has yet to see the reset. } - quint32 total = 0; - for (const auto &frame : continuedFrames) - total += frame.dataSize(); + std::vector hpackBlock(assemble_hpack_block(continuedFrames)); + if (!hpackBlock.size()) { + // It could be a PRIORITY sent in HEADERS - already handled by this + // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1): + // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION + // frames MUST be a valid and complete set of request header fields + // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does + // not include a complete and valid set of header fields or the :method + // pseudo-header field identifies a method that is not safe, it MUST + // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR." + if (firstFrameType == FrameType::PUSH_PROMISE) + resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR); - if (!total) { - // It could be a PRIORITY sent in HEADERS - handled by this point. return; } - std::vector hpackBlock(total); - auto dst = hpackBlock.begin(); - for (const auto &frame : continuedFrames) { - if (!frame.dataSize()) - continue; - const uchar *src = frame.dataBegin(); - std::copy(src, src + frame.dataSize(), dst); - dst += frame.dataSize(); - } - - HPack::BitIStream inputStream{&hpackBlock[0], - &hpackBlock[0] + hpackBlock.size()}; - + HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()}; if (!decoder.decodeHeaderFields(inputStream)) return connectionError(COMPRESSION_ERROR, "HPACK decompression failed"); - if (continuedFrames[0].type() == FrameType::HEADERS) { + switch (firstFrameType) { + case FrameType::HEADERS: if (activeStreams.contains(streamID)) { Stream &stream = activeStreams[streamID]; updateStream(stream, decoder.decodedHeader()); + // No DATA frames. if (continuedFrames[0].flags() & FrameFlag::END_STREAM) { finishStream(stream); deleteActiveStream(stream.streamID); } } + break; + case FrameType::PUSH_PROMISE: + if (!tryReserveStream(continuedFrames[0], decoder.decodedHeader())) + resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR); + break; + default: + break; } } @@ -923,10 +995,26 @@ bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 ne return true; } -void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader &headers) +void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader &headers, + Qt::ConnectionType connectionType) { const auto httpReply = stream.reply(); - Q_ASSERT(httpReply); + Q_ASSERT(httpReply || stream.state == Stream::remoteReserved); + + if (!httpReply) { + // It's a PUSH_PROMISEd HEADERS, no actual request/reply + // exists yet, we have to cache this data for a future + // (potential) request. + + // TODO: the part with assignment is not especially cool + // or beautiful, good that at least QByteArray is implicitly + // sharing data. To be refactored (std::move). + Q_ASSERT(promisedData.contains(stream.key)); + PushPromise &promise = promisedData[stream.key]; + promise.responseHeader = headers; + return; + } + const auto httpReplyPrivate = httpReply->d_func(); for (const auto &pair : headers) { const auto &name = pair.name; @@ -951,18 +1039,30 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader } } - emit httpReply->headerChanged(); + if (connectionType == Qt::DirectConnection) + emit httpReply->headerChanged(); + else + QMetaObject::invokeMethod(httpReply, "headerChanged", connectionType); } -void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame) +void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame, + Qt::ConnectionType connectionType) { Q_ASSERT(frame.type() == FrameType::DATA); + auto httpReply = stream.reply(); + Q_ASSERT(httpReply || stream.state == Stream::remoteReserved); + + if (!httpReply) { + Q_ASSERT(promisedData.contains(stream.key)); + PushPromise &promise = promisedData[stream.key]; + // TODO: refactor this to use std::move. + promise.dataFrames.push_back(frame); + return; + } if (const auto length = frame.dataSize()) { const char *data = reinterpret_cast(frame.dataBegin()); auto &httpRequest = stream.request(); - auto httpReply = stream.reply(); - Q_ASSERT(httpReply); auto replyPrivate = httpReply->d_func(); replyPrivate->compressedData.append(data, length); @@ -978,24 +1078,38 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame) } if (replyPrivate->shouldEmitSignals()) { - emit httpReply->readyRead(); - emit httpReply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + if (connectionType == Qt::DirectConnection) { + emit httpReply->readyRead(); + emit httpReply->dataReadProgress(replyPrivate->totalProgress, + replyPrivate->bodyLength); + } else { + QMetaObject::invokeMethod(httpReply, "readyRead", connectionType); + QMetaObject::invokeMethod(httpReply, "dataReadProgress", connectionType, + Q_ARG(qint64, replyPrivate->totalProgress), + Q_ARG(qint64, replyPrivate->bodyLength)); + } } } } -void QHttp2ProtocolHandler::finishStream(Stream &stream) +void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType connectionType) { + Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply()); + stream.state = Stream::closed; auto httpReply = stream.reply(); - Q_ASSERT(httpReply); - httpReply->disconnect(this); - if (stream.data()) - stream.data()->disconnect(this); + if (httpReply) { + httpReply->disconnect(this); + if (stream.data()) + stream.data()->disconnect(this); - qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed"; + if (connectionType == Qt::DirectConnection) + emit httpReply->finished(); + else + QMetaObject::invokeMethod(httpReply, "finished", connectionType); + } - emit httpReply->finished(); + qCDebug(QT_HTTP2) << "stream" << stream.streamID << "closed"; } void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorCode) @@ -1009,18 +1123,20 @@ void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, quint32 errorC void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply::NetworkError error, const QString &message) { + Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply()); + stream.state = Stream::closed; - auto httpReply = stream.reply(); - Q_ASSERT(httpReply); - httpReply->disconnect(this); - if (stream.data()) - stream.data()->disconnect(this); + if (auto httpReply = stream.reply()) { + httpReply->disconnect(this); + if (stream.data()) + stream.data()->disconnect(this); + + // TODO: error message must be translated!!! (tr) + emit httpReply->finishedWithError(error, message); + } qCWarning(QT_HTTP2) << "stream" << stream.streamID << "finished with error:" << message; - - // TODO: error message must be translated!!! (tr) - emit httpReply->finishedWithError(error, message); } quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message) @@ -1066,26 +1182,24 @@ void QHttp2ProtocolHandler::addToSuspended(Stream &stream) void QHttp2ProtocolHandler::markAsReset(quint32 streamID) { - // For now, we trace only client's streams (created by us, - // odd integer numbers). - if (streamID & 0x1) { - qCDebug(QT_HTTP2) << "stream" << streamID << "was reset"; - // This part is quite tricky: I have to clear this set - // so that it does not become tOOO big. - if (recycledStreams.size() > maxRecycledStreams) { - // At least, I'm erasing the oldest first ... - recycledStreams.erase(recycledStreams.begin(), - recycledStreams.begin() + - recycledStreams.size() / 2); - } + Q_ASSERT(streamID); + + qCDebug(QT_HTTP2) << "stream" << streamID << "was reset"; + // This part is quite tricky: I have to clear this set + // so that it does not become tOOO big. + if (recycledStreams.size() > maxRecycledStreams) { + // At least, I'm erasing the oldest first ... + recycledStreams.erase(recycledStreams.begin(), + recycledStreams.begin() + + recycledStreams.size() / 2); + } - const auto it = std::lower_bound(recycledStreams.begin(), recycledStreams.end(), - streamID); - if (it != recycledStreams.end() && *it == streamID) - return; + const auto it = std::lower_bound(recycledStreams.begin(), recycledStreams.end(), + streamID); + if (it != recycledStreams.end() && *it == streamID) + return; - recycledStreams.insert(it, streamID); - } + recycledStreams.insert(it, streamID); } quint32 QHttp2ProtocolHandler::popStreamToResume() @@ -1175,7 +1289,7 @@ quint32 QHttp2ProtocolHandler::allocateStreamID() { // With protocol upgrade streamID == 1 will become // invalid. The logic must be updated. - if (nextID > quint32(std::numeric_limits::max())) + if (nextID > Http2::lastValidStreamID) return 0; const quint32 streamID = nextID; @@ -1184,6 +1298,114 @@ quint32 QHttp2ProtocolHandler::allocateStreamID() return streamID; } +bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFrame, + const HPack::HttpHeader &requestHeader) +{ + Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE); + + QMap pseudoHeaders; + for (const auto &field : requestHeader) { + if (field.name == ":scheme" || field.name == ":path" + || field.name == ":authority" || field.name == ":method") { + if (field.value.isEmpty() || pseudoHeaders.contains(field.name)) + return false; + pseudoHeaders[field.name] = field.value; + } + } + + if (pseudoHeaders.size() != 4) { + // All four required, HTTP/2 8.1.2.3. + return false; + } + + const auto method = pseudoHeaders[":method"].toLower(); + if (method != "get" && method != "head") + return false; + + QUrl url; + url.setScheme(QLatin1String(pseudoHeaders[":scheme"])); + url.setAuthority(QLatin1String(pseudoHeaders[":authority"])); + url.setPath(QLatin1String(pseudoHeaders[":path"])); + + if (!url.isValid()) + return false; + + Q_ASSERT(activeStreams.contains(pushPromiseFrame.streamID())); + const Stream &associatedStream = activeStreams[pushPromiseFrame.streamID()]; + + const auto associatedUrl = urlkey_from_request(associatedStream.request()); + if (url.adjusted(QUrl::RemovePath) != associatedUrl.adjusted(QUrl::RemovePath)) + return false; + + const auto urlKey = url.toString(); + if (promisedData.contains(urlKey)) // duplicate push promise + return false; + + const auto reservedID = qFromBigEndian(pushPromiseFrame.dataBegin()); + // By this time all sanity checks on reservedID were done already + // in handlePUSH_PROMISE. We do not repeat them, only those below: + Q_ASSERT(!activeStreams.contains(reservedID)); + Q_ASSERT(!streamWasReset(reservedID)); + + auto &promise = promisedData[urlKey]; + promise.reservedID = reservedID; + promise.pushHeader = requestHeader; + + activeStreams.insert(reservedID, Stream(urlKey, reservedID, streamInitialRecvWindowSize)); + return true; +} + +void QHttp2ProtocolHandler::resetPromisedStream(const Frame &pushPromiseFrame, + Http2::Http2Error reason) +{ + Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE); + const auto reservedID = qFromBigEndian(pushPromiseFrame.dataBegin()); + sendRST_STREAM(reservedID, reason); + markAsReset(reservedID); +} + +void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &message, + const QString &cacheKey) +{ + Q_ASSERT(promisedData.contains(cacheKey)); + auto promise = promisedData.take(cacheKey); + + qCDebug(QT_HTTP2) << "found cached/promised response on stream" << promise.reservedID; + + bool replyFinished = false; + Stream *promisedStream = nullptr; + if (activeStreams.contains(promise.reservedID)) { + promisedStream = &activeStreams[promise.reservedID]; + // Ok, we have an active (not closed yet) stream waiting for more frames, + // let's pretend we requested it: + promisedStream->httpPair = message; + } else { + // Let's pretent we're sending a request now: + Stream closedStream(message, promise.reservedID, + streamInitialSendWindowSize, + streamInitialRecvWindowSize); + closedStream.state = Stream::halfClosedLocal; + activeStreams.insert(promise.reservedID, closedStream); + promisedStream = &activeStreams[promise.reservedID]; + replyFinished = true; + } + + Q_ASSERT(promisedStream); + + if (!promise.responseHeader.empty()) + updateStream(*promisedStream, promise.responseHeader, Qt::QueuedConnection); + + for (const auto &frame : promise.dataFrames) + updateStream(*promisedStream, frame, Qt::QueuedConnection); + + if (replyFinished) { + // Good, we already have received ALL the frames of that PUSH_PROMISE, + // nothing more to do. + finishStream(*promisedStream, Qt::QueuedConnection); + deleteActiveStream(promisedStream->streamID); + } +} + void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode, const char *message) { diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h index 92c6851078..df0cf6a288 100644 --- a/src/network/access/qhttp2protocolhandler_p.h +++ b/src/network/access/qhttp2protocolhandler_p.h @@ -63,6 +63,7 @@ #include "http2/hpacktable_p.h" #include "http2/hpack_p.h" +#include #include #include #include @@ -123,9 +124,11 @@ private: bool acceptSetting(Http2::Settings identifier, quint32 newValue); - void updateStream(Stream &stream, const HPack::HttpHeader &headers); - void updateStream(Stream &stream, const Http2::Frame &dataFrame); - void finishStream(Stream &stream); + void updateStream(Stream &stream, const HPack::HttpHeader &headers, + Qt::ConnectionType connectionType = Qt::DirectConnection); + void updateStream(Stream &stream, const Http2::Frame &dataFrame, + Qt::ConnectionType connectionType = Qt::DirectConnection); + void finishStream(Stream &stream, Qt::ConnectionType connectionType = Qt::DirectConnection); // Error code send by a peer (GOAWAY/RST_STREAM): void finishStreamWithError(Stream &stream, quint32 errorCode); // Locally encountered error: @@ -194,7 +197,15 @@ private: quint32 allocateStreamID(); bool validPeerStreamID() const; bool goingAway = false; - + bool pushPromiseEnabled = false; + quint32 lastPromisedID = Http2::connectionStreamID; + QHash promisedData; + bool tryReserveStream(const Http2::Frame &pushPromiseFrame, + const HPack::HttpHeader &requestHeader); + void resetPromisedStream(const Http2::Frame &pushPromiseFrame, + Http2::Http2Error reason); + void initReplyFromPushPromise(const HttpMessagePair &message, + const QString &cacheKey); // Errors: void connectionError(Http2::Http2Error errorCode, const char *message); -- cgit v1.2.3 From 398d67198c054ff5fa24103bda62cdaffdc194e2 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Thu, 20 Oct 2016 13:26:36 +0200 Subject: Document Qt Network licenses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also explicitly mention the GPL exception for OpenSSL. Change-Id: I460189ee4d2dd79f8eca320ac82460e186b0f84c Reviewed-by: Topi Reiniƶ --- src/network/doc/src/external-resources.qdoc | 36 +++++++++++++++++++++++++++++ src/network/doc/src/qtnetwork.qdoc | 30 ++++++++++++++++++++++++ src/network/doc/src/ssl.qdoc | 9 +++----- 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/network/doc/src/external-resources.qdoc (limited to 'src/network') diff --git a/src/network/doc/src/external-resources.qdoc b/src/network/doc/src/external-resources.qdoc new file mode 100644 index 0000000000..f033ddc729 --- /dev/null +++ b/src/network/doc/src/external-resources.qdoc @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \externalpage https://www.openssl.org/ + \title OpenSSL Toolkit +*/ + +/*! + \externalpage https://www.openssl.org/source/license.html + \title OpenSSL License +*/ diff --git a/src/network/doc/src/qtnetwork.qdoc b/src/network/doc/src/qtnetwork.qdoc index f15b180625..7a95195da2 100644 --- a/src/network/doc/src/qtnetwork.qdoc +++ b/src/network/doc/src/qtnetwork.qdoc @@ -60,7 +60,37 @@ \list \li \l{Qt Network C++ Classes}{C++ Classes} \endlist + + \section1 Licenses and Attributions + + Qt Network is available under commercial licenses from \l{The Qt Company}. + In addition, it is available under the + \l{GNU Lesser General Public License, version 3}, or + the \l{GNU General Public License, version 2}. + See \l{Qt Licensing} for further details. + + Qt Network can use the \l{OpenSSL Toolkit} as a backend. The library is then + linked against OpenSSL in a way that requires compliance with the \l{OpenSSL + License}. To allow linking OpenSSL with Qt Network under the GPL, following + exceptions to the GPL do apply: + + \badcode + In addition, as a special exception, the copyright holders listed above give + permission to link the code of its release of Qt with the OpenSSL project's + "OpenSSL" library (or modified versions of the "OpenSSL" library that use the + same license as the original version), and distribute the linked executables. + + You must comply with the GNU General Public License version 2 in all + respects for all of the code used other than the "OpenSSL" code. If you + modify this file, you may extend this exception to your version of the file, + but you are not obligated to do so. If you do not wish to do so, delete + this exception statement from your version of this file. + \endcode + + Also note shipping OpenSSL might cause \l{Import and Export Restrictions} + to apply. */ + /*! \module QtNetwork \title Qt Network C++ Classes diff --git a/src/network/doc/src/ssl.qdoc b/src/network/doc/src/ssl.qdoc index 5ad2cfafc6..e4948c393c 100644 --- a/src/network/doc/src/ssl.qdoc +++ b/src/network/doc/src/ssl.qdoc @@ -33,7 +33,7 @@ \keyword SSL The classes below provide support for secure network communication using - the Secure Sockets Layer (SSL) protocol, using the OpenSSL Toolkit (\l{http://www.openssl.org/}) + the Secure Sockets Layer (SSL) protocol, using the \l{OpenSSL Toolkit} to perform encryption and protocol handling. From Qt version 5.2 onwards, the officially supported version for OpenSSL @@ -67,16 +67,13 @@ To disable SSL support in a Qt build, configure Qt with the \c{-no-openssl} option. - \section1 Licensing Information + \section1 Import and Export Restrictions - \note Due to import and export restrictions in some parts of the world, we + Due to import and export restrictions in some parts of the world, we are unable to supply the OpenSSL Toolkit with Qt packages. Developers wishing to use SSL communication in their deployed applications should either ensure that their users have the appropriate libraries installed, or they should consult a suitably qualified legal professional to ensure that applications using code from the OpenSSL project are correctly certified for import and export in relevant regions of the world. - - When the Qt Network module is built with SSL support, the library is linked - against OpenSSL in a way that requires OpenSSL license compliance. */ -- cgit v1.2.3 From 0e2d38b4477ffec6941d0f88cfa7bf92a8b79843 Mon Sep 17 00:00:00 2001 From: Alex Trotsenko Date: Mon, 24 Oct 2016 19:16:13 +0300 Subject: QAbstractSocket: avoid unspecified behavior in writing on TCP Passing zero as size parameter to QAbstractSocketEngine::write() has unspecified behavior, at least for TCP sockets. This could happen on flush() when writeBuffer is empty or on writeData() with size 0. Avoid by explicitly checking against zero size. Change-Id: I070630d244ce6c3de3da94f84c2cded2c7a4b081 Reviewed-by: Edward Welbourne --- src/network/socket/qabstractsocket.cpp | 4 ++-- src/network/socket/qnativesocketengine.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/network') diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index 3d665270fd..f28dc6698a 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -881,7 +881,7 @@ bool QAbstractSocketPrivate::flush() const char *ptr = writeBuffer.readPointer(); // Attempt to write it all in one chunk. - qint64 written = socketEngine->write(ptr, nextSize); + qint64 written = nextSize ? socketEngine->write(ptr, nextSize) : Q_INT64_C(0); if (written < 0) { #if defined (QABSTRACTSOCKET_DEBUG) qDebug() << "QAbstractSocketPrivate::flush() write error, aborting." << socketEngine->errorString(); @@ -2477,7 +2477,7 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size) if (!d->isBuffered && d->socketType == TcpSocket && d->socketEngine && d->writeBuffer.isEmpty()) { // This code is for the new Unbuffered QTcpSocket use case - qint64 written = d->socketEngine->write(data, size); + qint64 written = size ? d->socketEngine->write(data, size) : Q_INT64_C(0); if (written < 0) { d->setError(d->socketEngine->error(), d->socketEngine->errorString()); return written; diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp index 805acde860..86d13d82a2 100644 --- a/src/network/socket/qnativesocketengine.cpp +++ b/src/network/socket/qnativesocketengine.cpp @@ -846,6 +846,10 @@ qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 size, const Q /*! Writes a block of \a size bytes from \a data to the socket. Returns the number of bytes written, or -1 if an error occurred. + + Passing zero as the \a size parameter on a connected UDP socket + will send an empty datagram. For other socket types results are + unspecified. */ qint64 QNativeSocketEngine::write(const char *data, qint64 size) { -- cgit v1.2.3