summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorLiang Qi <liang.qi@qt.io>2016-11-01 10:27:41 +0100
committerLiang Qi <liang.qi@qt.io>2016-11-02 09:24:11 +0100
commitd7e4980132057aa10e54137114bf65e06c455030 (patch)
tree9d6ae36efa0cf84a612bfec6cf3dd2ea7f7e3446 /src/network
parent44c402b4bfba44480382244b8409fb3cf34d7ac1 (diff)
parenta732576a66ff2bbd9c0b41cd5f3505a4d2fbf043 (diff)
Merge remote-tracking branch 'origin/5.8' into dev
Blacklist tst_QMenuBar::taskQTBUG46812_doNotLeaveMenubarHighlighted() on macOS. Conflicts: mkspecs/features/mac/default_post.prf mkspecs/features/mac/sdk.prf mkspecs/features/uikit/default_post.prf mkspecs/features/uikit/sdk.prf src/angle/src/libEGL/libEGL.pro src/platformsupport/fontdatabases/fontdatabases.pro src/platformsupport/platformsupport.pro src/plugins/platforms/cocoa/qnswindowdelegate.mm src/plugins/platforms/direct2d/qwindowsdirect2dintegration.cpp src/plugins/platforms/ios/ios.pro src/plugins/platforms/ios/kernel.pro tests/auto/widgets/widgets/qmenubar/BLACKLIST tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp Task-number: QTBUG-56853 Change-Id: If58785210feee3550892fc7768cce90e75a2416c
Diffstat (limited to 'src/network')
-rw-r--r--src/network/access/http2/http2frames.cpp33
-rw-r--r--src/network/access/http2/http2frames_p.h35
-rw-r--r--src/network/access/http2/http2protocol_p.h4
-rw-r--r--src/network/access/http2/http2streams.cpp11
-rw-r--r--src/network/access/http2/http2streams_p.h25
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp372
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h19
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp6
-rw-r--r--src/network/bearer/qnetworksession.cpp5
-rw-r--r--src/network/configure.json9
-rw-r--r--src/network/doc/src/external-resources.qdoc36
-rw-r--r--src/network/doc/src/qtnetwork.qdoc30
-rw-r--r--src/network/doc/src/ssl.qdoc9
-rw-r--r--src/network/socket/qabstractsocket.cpp4
-rw-r--r--src/network/socket/qnativesocketengine.cpp4
-rw-r--r--src/network/socket/qnativesocketengine_winrt.cpp40
-rw-r--r--src/network/socket/qnativesocketengine_winrt_p.h2
-rw-r--r--src/network/ssl/ssl.pri2
18 files changed, 523 insertions, 123 deletions
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<uchar> 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<typename ValueType>
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 <private/qhttpnetworkconnectionchannel_p.h>
#include <private/qhttpnetworkrequest_p.h>
#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <vector>
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<Frame> 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<uchar> assemble_hpack_block(const std::vector<Http2::Frame> &frames)
+{
+ std::vector<uchar> 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<quint32>(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<quint32>(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<uchar> 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<uchar> 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<const char *>(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<qint32>::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<QByteArray, QByteArray> 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<quint32>(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<quint32>(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 <QtCore/qnamespace.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qglobal.h>
#include <QtCore/qobject.h>
@@ -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<QString, Http2::PushPromise> 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);
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index f70f11d384..00f3468ebd 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -244,7 +244,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()
@@ -442,9 +443,6 @@ QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate()
{
- Q_Q(QNetworkReplyHttpImpl);
- // This will do nothing if the request was already finished or aborted
- emit q->abortHttpRequest();
}
/*
diff --git a/src/network/bearer/qnetworksession.cpp b/src/network/bearer/qnetworksession.cpp
index 6d864a7d3e..2ad46918b2 100644
--- a/src/network/bearer/qnetworksession.cpp
+++ b/src/network/bearer/qnetworksession.cpp
@@ -48,6 +48,11 @@
#include "qnetworkconfigmanager_p.h"
+// for QNetworkSession::interface
+#ifdef interface
+# undef interface
+#endif
+
#ifndef QT_NO_BEARERMANAGEMENT
QT_BEGIN_NAMESPACE
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" }
]
}
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.
*/
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp
index 34a8025cdd..02bba2d293 100644
--- a/src/network/socket/qabstractsocket.cpp
+++ b/src/network/socket/qabstractsocket.cpp
@@ -850,7 +850,7 @@ bool QAbstractSocketPrivate::writeToSocket()
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::writeToSocket() write error, aborting."
@@ -2504,7 +2504,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());
} else if (written < size) {
diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp
index f2bc3cec94..11d19682d8 100644
--- a/src/network/socket/qnativesocketengine.cpp
+++ b/src/network/socket/qnativesocketengine.cpp
@@ -873,6 +873,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)
{
diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp
index bd9b443602..f8e92d3f50 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<IBuffer> buffer;
HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
RETURN_OK_IF_FAILED("initialize(): Could not create buffer");
ComPtr<IInputStream> 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<IAsyncBufferOperation> 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<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
+ d->pendingReadOps.append(readOp);
+ d->socketState = socketState;
+ hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(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;
}
@@ -556,10 +561,12 @@ void QNativeSocketEngine::close()
ComPtr<IAsyncAction> 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;
});
@@ -567,9 +574,9 @@ void QNativeSocketEngine::close()
}
#endif // _MSC_VER >= 1900
- if (d->readOp) {
+ for (ComPtr<IAsyncBufferOperation> readOp : d->pendingReadOps) {
ComPtr<IAsyncInfo> info;
- hr = d->readOp.As(&info);
+ hr = readOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
@@ -933,9 +940,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<IAsyncBufferOperation> 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<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
+ d->pendingReadOps.append(readOp);
+ hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
RETURN_HR_IF_FAILED("establishRead(): Failed to register read callback");
return S_OK;
});
@@ -1410,7 +1419,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 +1480,7 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
if (notifyOnRead)
emit q->readReady();
+ mutex.unlock();
hr = QEventDispatcherWinRT::runOnXamlThread([buffer, q, this]() {
UINT32 readBufferLength;
@@ -1476,12 +1494,14 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
hr = buffer->put_Length(0);
RETURN_HR_IF_FAILED("handleReadyRead(): Could not set buffer length");
+ ComPtr<IAsyncBufferOperation> 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<SocketReadCompletedHandler>(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<ABI::Windows::Networking::Sockets::IDatagramSocket *>(socketDescriptor); }
Microsoft::WRL::ComPtr<ABI::Windows::Networking::Sockets::IStreamSocketListener> tcpListener;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> connectOp;
- Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer *, UINT32>> readOp;
+ QVector<Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer *, UINT32>>> pendingReadOps;
QBuffer readBytes;
QMutex readMutex;
bool emitOnNewDatagram;
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 <OPENSSL_DIR>\lib\VC\static
# - configure: -openssl -openssl-linked -I <OPENSSL_DIR>\include -L <OPENSSL_DIR>\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 {