--- /dev/null
+From 22f3d359350fd65e4bbe2e9420fcc4460e8a590a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@digia.com>
+Date: Tue, 10 Mar 2015 22:37:39 +0100
+Subject: [PATCH] Cocoa: Fix systray SVG icons.
+
+Regression caused by f3699510.
+
+Task-number: QTBUG-44686
+Change-Id: I546422a67d4da29fac196025b09bddcb45c1b641
+Reviewed-by: Timur Pocheptsov <Timur.Pocheptsov@digia.com>
+---
+ src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+index e449fd3..8a35705 100755
+--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
++++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+@@ -234,6 +234,10 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
+ }
+ }
+
++ // Handle SVG icons, which do not return anything for availableSizes().
++ if (!selectedSize.isValid())
++ selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode);
++
+ QPixmap pixmap = icon.pixmap(selectedSize, mode);
+
+ // Draw a low-resolution icon if there is not enough pixels for a retina
+--
+1.9.1
+
+++ /dev/null
-From ee7fea33383726f0bb72e8082a357820e3ee3675 Mon Sep 17 00:00:00 2001
-From: Jocelyn Turcotte <jturcotte@woboq.com>
-Date: Tue, 24 Feb 2015 17:02:02 +0100
-Subject: [PATCH] OSX Fix disapearing tray icon
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-It would happen together with an error:
- QPainter::begin: Paint device returned engine == 0
-and would be caused by the size provided to QIcon::pixmap being empty,
-itself caused by the availableSizes list being empty for the Selected
-mode.
-
-This bug was most often hidden by the fact that the Selected icon mode
-was not triggered properly since we usually only set menuVisible after
-calling updateIcon, and most of the time when it did, we would overwrite
-it right after with a Normal mode icon.
-
-Fix the issue by disabling the broken feature completely since the
-default Selected icon is grayed out while tray icons are now usually
-black (or white when selected). To support the dark menu bar mode on
-10.10 we'll need to use NSImage's setTemplate anyway and that
-knowing in advance if we can invert the colors ourselves would also
-better solve the menuVisible usecase.
-
-Task-number: QTBUG-42910
-Change-Id: If9ec9659af28ecceb841bfc2f11721e6029fe891
-Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
----
- src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 17 +++--------------
- 1 file changed, 3 insertions(+), 14 deletions(-)
-
-diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
-index 8a35705..d366a3c 100755
---- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
-+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
-@@ -102,7 +102,6 @@ QT_USE_NAMESPACE
- QCocoaSystemTrayIcon *systray;
- NSStatusItem *item;
- QCocoaMenu *menu;
-- bool menuVisible;
- QIcon icon;
- QT_MANGLE_NAMESPACE(QNSImageView) *imageCell;
- }
-@@ -202,8 +201,6 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
-
- m_sys->item->icon = icon;
-
-- const bool menuVisible = m_sys->item->menu && m_sys->item->menuVisible;
--
- // The reccomended maximum title bar icon height is 18 points
- // (device independent pixels). The menu height on past and
- // current OS X versions is 22 points. Provide some future-proofing
-@@ -218,9 +215,8 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
- // devicePixelRatio for the "best" screen on the system.
- qreal devicePixelRatio = qApp->devicePixelRatio();
- const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
-- const QIcon::Mode mode = menuVisible ? QIcon::Selected : QIcon::Normal;
- QSize selectedSize;
-- Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes(mode))) {
-+ Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) {
- // Select a pixmap based on the height. We want the largest pixmap
- // with a height smaller or equal to maxPixmapHeight. The pixmap
- // may rectangular; assume it has a reasonable size. If there is
-@@ -236,9 +232,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
-
- // Handle SVG icons, which do not return anything for availableSizes().
- if (!selectedSize.isValid())
-- selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode);
-+ selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
-
-- QPixmap pixmap = icon.pixmap(selectedSize, mode);
-+ QPixmap pixmap = icon.pixmap(selectedSize);
-
- // Draw a low-resolution icon if there is not enough pixels for a retina
- // icon. This prevents showing a small icon on retina displays.
-@@ -385,9 +381,6 @@ QT_END_NAMESPACE
- Q_UNUSED(notification);
- down = NO;
-
-- parent->systray->updateIcon(parent->icon);
-- parent->menuVisible = false;
--
- [self setNeedsDisplay:YES];
- }
-
-@@ -397,8 +390,6 @@ QT_END_NAMESPACE
- int clickCount = [mouseEvent clickCount];
- [self setNeedsDisplay:YES];
-
-- parent->systray->updateIcon(parent->icon);
--
- if (clickCount == 2) {
- [self menuTrackingDone:nil];
- [parent doubleClickSelector:self];
-@@ -454,7 +445,6 @@ QT_END_NAMESPACE
- if (self) {
- item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
- menu = 0;
-- menuVisible = false;
- systray = sys;
- imageCell = [[QNSImageView alloc] initWithParent:self];
- [item setView: imageCell];
-@@ -498,7 +488,6 @@ QT_END_NAMESPACE
- selector:@selector(menuTrackingDone:)
- name:NSMenuDidEndTrackingNotification
- object:m];
-- menuVisible = true;
- [item popUpStatusItemMenu: m];
- }
- }
---
-1.9.1
-
+++ /dev/null
-From f3cd07c11e0b7327ffc629f48a89c8c457cdba75 Mon Sep 17 00:00:00 2001
-From: Jocelyn Turcotte <jturcotte@woboq.com>
-Date: Fri, 6 Mar 2015 16:12:37 +0100
-Subject: [PATCH] Fix -force-debug-info with macx-clang
-
----
- mkspecs/common/clang.conf | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/mkspecs/common/clang.conf b/mkspecs/common/clang.conf
-index 2c29bb8..110d380 100644
---- a/mkspecs/common/clang.conf
-+++ b/mkspecs/common/clang.conf
-@@ -20,11 +20,13 @@ QMAKE_CFLAGS_ISYSTEM = -isystem
- QMAKE_CFLAGS_PRECOMPILE = -x c-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
- QMAKE_CFLAGS_USE_PRECOMPILE = -Xclang -include-pch -Xclang ${QMAKE_PCH_OUTPUT}
- QMAKE_CFLAGS_LTCG = -flto
-+QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_OPTIMIZE -g
-
- QMAKE_CXXFLAGS_PRECOMPILE = -x c++-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
- QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
- QMAKE_CXXFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
- QMAKE_CXXFLAGS_CXX11 = -std=c++11
-+QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
-
- QMAKE_LFLAGS_CXX11 =
- QMAKE_LFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
---
-2.2.0
-
--- /dev/null
+From ee7fea33383726f0bb72e8082a357820e3ee3675 Mon Sep 17 00:00:00 2001
+From: Jocelyn Turcotte <jturcotte@woboq.com>
+Date: Tue, 24 Feb 2015 17:02:02 +0100
+Subject: [PATCH] OSX Fix disapearing tray icon
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It would happen together with an error:
+ QPainter::begin: Paint device returned engine == 0
+and would be caused by the size provided to QIcon::pixmap being empty,
+itself caused by the availableSizes list being empty for the Selected
+mode.
+
+This bug was most often hidden by the fact that the Selected icon mode
+was not triggered properly since we usually only set menuVisible after
+calling updateIcon, and most of the time when it did, we would overwrite
+it right after with a Normal mode icon.
+
+Fix the issue by disabling the broken feature completely since the
+default Selected icon is grayed out while tray icons are now usually
+black (or white when selected). To support the dark menu bar mode on
+10.10 we'll need to use NSImage's setTemplate anyway and that
+knowing in advance if we can invert the colors ourselves would also
+better solve the menuVisible usecase.
+
+Task-number: QTBUG-42910
+Change-Id: If9ec9659af28ecceb841bfc2f11721e6029fe891
+Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
+---
+ src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 17 +++--------------
+ 1 file changed, 3 insertions(+), 14 deletions(-)
+
+diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+index 8a35705..d366a3c 100755
+--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
++++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+@@ -102,7 +102,6 @@ QT_USE_NAMESPACE
+ QCocoaSystemTrayIcon *systray;
+ NSStatusItem *item;
+ QCocoaMenu *menu;
+- bool menuVisible;
+ QIcon icon;
+ QT_MANGLE_NAMESPACE(QNSImageView) *imageCell;
+ }
+@@ -202,8 +201,6 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
+
+ m_sys->item->icon = icon;
+
+- const bool menuVisible = m_sys->item->menu && m_sys->item->menuVisible;
+-
+ // The reccomended maximum title bar icon height is 18 points
+ // (device independent pixels). The menu height on past and
+ // current OS X versions is 22 points. Provide some future-proofing
+@@ -218,9 +215,8 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
+ // devicePixelRatio for the "best" screen on the system.
+ qreal devicePixelRatio = qApp->devicePixelRatio();
+ const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
+- const QIcon::Mode mode = menuVisible ? QIcon::Selected : QIcon::Normal;
+ QSize selectedSize;
+- Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes(mode))) {
++ Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) {
+ // Select a pixmap based on the height. We want the largest pixmap
+ // with a height smaller or equal to maxPixmapHeight. The pixmap
+ // may rectangular; assume it has a reasonable size. If there is
+@@ -236,9 +232,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
+
+ // Handle SVG icons, which do not return anything for availableSizes().
+ if (!selectedSize.isValid())
+- selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode);
++ selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
+
+- QPixmap pixmap = icon.pixmap(selectedSize, mode);
++ QPixmap pixmap = icon.pixmap(selectedSize);
+
+ // Draw a low-resolution icon if there is not enough pixels for a retina
+ // icon. This prevents showing a small icon on retina displays.
+@@ -385,9 +381,6 @@ QT_END_NAMESPACE
+ Q_UNUSED(notification);
+ down = NO;
+
+- parent->systray->updateIcon(parent->icon);
+- parent->menuVisible = false;
+-
+ [self setNeedsDisplay:YES];
+ }
+
+@@ -397,8 +390,6 @@ QT_END_NAMESPACE
+ int clickCount = [mouseEvent clickCount];
+ [self setNeedsDisplay:YES];
+
+- parent->systray->updateIcon(parent->icon);
+-
+ if (clickCount == 2) {
+ [self menuTrackingDone:nil];
+ [parent doubleClickSelector:self];
+@@ -454,7 +445,6 @@ QT_END_NAMESPACE
+ if (self) {
+ item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ menu = 0;
+- menuVisible = false;
+ systray = sys;
+ imageCell = [[QNSImageView alloc] initWithParent:self];
+ [item setView: imageCell];
+@@ -498,7 +488,6 @@ QT_END_NAMESPACE
+ selector:@selector(menuTrackingDone:)
+ name:NSMenuDidEndTrackingNotification
+ object:m];
+- menuVisible = true;
+ [item popUpStatusItemMenu: m];
+ }
+ }
+--
+1.9.1
+
--- /dev/null
+From f3cd07c11e0b7327ffc629f48a89c8c457cdba75 Mon Sep 17 00:00:00 2001
+From: Jocelyn Turcotte <jturcotte@woboq.com>
+Date: Fri, 6 Mar 2015 16:12:37 +0100
+Subject: [PATCH] Fix -force-debug-info with macx-clang
+
+---
+ mkspecs/common/clang.conf | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mkspecs/common/clang.conf b/mkspecs/common/clang.conf
+index 2c29bb8..110d380 100644
+--- a/mkspecs/common/clang.conf
++++ b/mkspecs/common/clang.conf
+@@ -20,11 +20,13 @@ QMAKE_CFLAGS_ISYSTEM = -isystem
+ QMAKE_CFLAGS_PRECOMPILE = -x c-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
+ QMAKE_CFLAGS_USE_PRECOMPILE = -Xclang -include-pch -Xclang ${QMAKE_PCH_OUTPUT}
+ QMAKE_CFLAGS_LTCG = -flto
++QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_OPTIMIZE -g
+
+ QMAKE_CXXFLAGS_PRECOMPILE = -x c++-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
+ QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
+ QMAKE_CXXFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
+ QMAKE_CXXFLAGS_CXX11 = -std=c++11
++QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
+
+ QMAKE_LFLAGS_CXX11 =
+ QMAKE_LFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
+--
+2.2.0
+
+++ /dev/null
-From cff39fba10ffc10ee4dcfdc66ff6528eb26462d3 Mon Sep 17 00:00:00 2001
-From: Markus Goetz <markus@woboq.com>
-Date: Fri, 10 Apr 2015 14:09:53 +0200
-Subject: [PATCH] QNAM: Fix upload corruptions when server closes connection
-
-This patch fixes several upload corruptions if the server closes the connection
-while/before we send data into it. They happen inside multiple places in the HTTP
-layer and are explained in the comments.
-Corruptions are:
-* The upload byte device has an in-flight signal with pending upload data, if
-it gets reset (because server closes the connection) then the re-send of the
-request was sometimes taking this stale in-flight pending upload data.
-* Because some signals were DirectConnection and some were QueuedConnection, there
-was a chance that a direct signal overtakes a queued signal. The state machine
-then sent data down the socket which was buffered there (and sent later) although
-it did not match the current state of the state machine when it was actually sent.
-* A socket was seen as being able to have requests sent even though it was not
-encrypted yet. This relates to the previous corruption where data is stored inside
-the socket's buffer and then sent later.
-
-The included auto test produces all fixed corruptions, I detected no regressions
-via the other tests.
-This code also adds a bit of sanity checking to protect from possible further
-problems.
-
-[ChangeLog][QtNetwork] Fix HTTP(s) upload corruption when server closes connection
-
-Change-Id: I54c883925ec897050941498f139c4b523030432e
-Reviewed-by: Peter Hartmann <peter-qt@hartmann.tk>
----
- src/corelib/io/qnoncontiguousbytedevice.cpp | 18 +++
- src/corelib/io/qnoncontiguousbytedevice_p.h | 4 +
- .../access/qhttpnetworkconnectionchannel.cpp | 35 ++++-
- .../access/qhttpnetworkconnectionchannel_p.h | 2 +
- src/network/access/qhttpprotocolhandler.cpp | 7 +
- src/network/access/qhttpthreaddelegate_p.h | 36 ++++-
- src/network/access/qnetworkreplyhttpimpl.cpp | 25 ++-
- src/network/access/qnetworkreplyhttpimpl_p.h | 7 +-
- .../access/qnetworkreply/tst_qnetworkreply.cpp | 175 ++++++++++++++++++++-
- 9 files changed, 279 insertions(+), 30 deletions(-)
-
-diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp
-index 11510a8..760ca3d 100644
---- a/src/corelib/io/qnoncontiguousbytedevice.cpp
-+++ b/src/corelib/io/qnoncontiguousbytedevice.cpp
-@@ -236,6 +236,11 @@ qint64 QNonContiguousByteDeviceByteArrayImpl::size()
- return byteArray->size();
- }
-
-+qint64 QNonContiguousByteDeviceByteArrayImpl::pos()
-+{
-+ return currentPosition;
-+}
-+
- QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb)
- : QNonContiguousByteDevice(), currentPosition(0)
- {
-@@ -273,6 +278,11 @@ bool QNonContiguousByteDeviceRingBufferImpl::atEnd()
- return currentPosition >= size();
- }
-
-+qint64 QNonContiguousByteDeviceRingBufferImpl::pos()
-+{
-+ return currentPosition;
-+}
-+
- bool QNonContiguousByteDeviceRingBufferImpl::reset()
- {
- if (resetDisabled)
-@@ -406,6 +416,14 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::size()
- return device->size() - initialPosition;
- }
-
-+qint64 QNonContiguousByteDeviceIoDeviceImpl::pos()
-+{
-+ if (device->isSequential())
-+ return -1;
-+
-+ return device->pos();
-+}
-+
- QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0)
- {
- byteDevice = bd;
-diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h
-index c05ae11..4d7b7b0 100644
---- a/src/corelib/io/qnoncontiguousbytedevice_p.h
-+++ b/src/corelib/io/qnoncontiguousbytedevice_p.h
-@@ -61,6 +61,7 @@ public:
- virtual const char* readPointer(qint64 maximumLength, qint64 &len) = 0;
- virtual bool advanceReadPointer(qint64 amount) = 0;
- virtual bool atEnd() = 0;
-+ virtual qint64 pos() { return -1; }
- virtual bool reset() = 0;
- void disableReset();
- bool isResetDisabled() { return resetDisabled; }
-@@ -106,6 +107,7 @@ public:
- bool atEnd();
- bool reset();
- qint64 size();
-+ qint64 pos() Q_DECL_OVERRIDE;
- protected:
- QByteArray* byteArray;
- qint64 currentPosition;
-@@ -121,6 +123,7 @@ public:
- bool atEnd();
- bool reset();
- qint64 size();
-+ qint64 pos() Q_DECL_OVERRIDE;
- protected:
- QSharedPointer<QRingBuffer> ringBuffer;
- qint64 currentPosition;
-@@ -138,6 +141,7 @@ public:
- bool atEnd();
- bool reset();
- qint64 size();
-+ qint64 pos() Q_DECL_OVERRIDE;
- protected:
- QIODevice* device;
- QByteArray* currentReadBuffer;
-diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
-index 9f63280..49c6793 100644
---- a/src/network/access/qhttpnetworkconnectionchannel.cpp
-+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
-@@ -106,15 +106,19 @@ void QHttpNetworkConnectionChannel::init()
- socket->setProxy(QNetworkProxy::NoProxy);
- #endif
-
-+ // We want all signals (except the interactive ones) be connected as QueuedConnection
-+ // because else we're falling into cases where we recurse back into the socket code
-+ // and mess up the state. Always going to the event loop (and expecting that when reading/writing)
-+ // is safer.
- QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
- this, SLOT(_q_bytesWritten(qint64)),
-- Qt::DirectConnection);
-+ Qt::QueuedConnection);
- QObject::connect(socket, SIGNAL(connected()),
- this, SLOT(_q_connected()),
-- Qt::DirectConnection);
-+ Qt::QueuedConnection);
- QObject::connect(socket, SIGNAL(readyRead()),
- this, SLOT(_q_readyRead()),
-- Qt::DirectConnection);
-+ Qt::QueuedConnection);
-
- // The disconnected() and error() signals may already come
- // while calling connectToHost().
-@@ -143,13 +147,13 @@ void QHttpNetworkConnectionChannel::init()
- // won't be a sslSocket if encrypt is false
- QObject::connect(sslSocket, SIGNAL(encrypted()),
- this, SLOT(_q_encrypted()),
-- Qt::DirectConnection);
-+ Qt::QueuedConnection);
- QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
- this, SLOT(_q_sslErrors(QList<QSslError>)),
- Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
- this, SLOT(_q_encryptedBytesWritten(qint64)),
-- Qt::DirectConnection);
-+ Qt::QueuedConnection);
-
- if (ignoreAllSslErrors)
- sslSocket->ignoreSslErrors();
-@@ -186,8 +190,11 @@ void QHttpNetworkConnectionChannel::close()
- // pendingEncrypt must only be true in between connected and encrypted states
- pendingEncrypt = false;
-
-- if (socket)
-+ if (socket) {
-+ // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
-+ // there is no socket yet.
- socket->close();
-+ }
- }
-
-
-@@ -353,6 +360,14 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
- }
- return false;
- }
-+
-+ // This code path for ConnectedState
-+ if (pendingEncrypt) {
-+ // Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
-+ // and corrupt the things sent to the server.
-+ return false;
-+ }
-+
- return true;
- }
-
-@@ -659,6 +674,12 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const
- void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
- {
- Q_UNUSED(bytes);
-+ if (ssl) {
-+ // In the SSL case we want to send data from encryptedBytesWritten signal since that one
-+ // is the one going down to the actual network, not only into some SSL buffer.
-+ return;
-+ }
-+
- // bytes have been written to the socket. write even more of them :)
- if (isSocketWriting())
- sendRequest();
-@@ -734,7 +755,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
-
- // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
- //channels[i].reconnectAttempts = 2;
-- if (pendingEncrypt) {
-+ if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
- #ifndef QT_NO_SSL
- if (connection->sslContext().isNull()) {
- // this socket is making the 1st handshake for this connection,
-diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
-index 692c0e6..231fe11 100644
---- a/src/network/access/qhttpnetworkconnectionchannel_p.h
-+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
-@@ -83,6 +83,8 @@ typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
- class QHttpNetworkConnectionChannel : public QObject {
- Q_OBJECT
- public:
-+ // TODO: Refactor this to add an EncryptingState (and remove pendingEncrypt).
-+ // Also add an Unconnected state so IdleState does not have double meaning.
- enum ChannelState {
- IdleState = 0, // ready to send request
- ConnectingState = 1, // connecting to host
-diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
-index 28e10f7..3357948 100644
---- a/src/network/access/qhttpprotocolhandler.cpp
-+++ b/src/network/access/qhttpprotocolhandler.cpp
-@@ -368,6 +368,13 @@ bool QHttpProtocolHandler::sendRequest()
- // nothing to read currently, break the loop
- break;
- } else {
-+ if (m_channel->written != uploadByteDevice->pos()) {
-+ // Sanity check. This was useful in tracking down an upload corruption.
-+ qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << m_channel->written << "but read device is at" << uploadByteDevice->pos();
-+ Q_ASSERT(m_channel->written == uploadByteDevice->pos());
-+ m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
-+ return false;
-+ }
- qint64 currentWriteSize = m_socket->write(readPointer, currentReadSize);
- if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
- // socket broke down
-diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
-index 1661082..b553409 100644
---- a/src/network/access/qhttpthreaddelegate_p.h
-+++ b/src/network/access/qhttpthreaddelegate_p.h
-@@ -187,6 +187,7 @@ protected:
- QByteArray m_dataArray;
- bool m_atEnd;
- qint64 m_size;
-+ qint64 m_pos; // to match calls of haveDataSlot with the expected position
- public:
- QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
- : QNonContiguousByteDevice(),
-@@ -194,7 +195,8 @@ public:
- m_amount(0),
- m_data(0),
- m_atEnd(aE),
-- m_size(s)
-+ m_size(s),
-+ m_pos(0)
- {
- }
-
-@@ -202,6 +204,11 @@ public:
- {
- }
-
-+ qint64 pos() Q_DECL_OVERRIDE
-+ {
-+ return m_pos;
-+ }
-+
- const char* readPointer(qint64 maximumLength, qint64 &len)
- {
- if (m_amount > 0) {
-@@ -229,11 +236,10 @@ public:
-
- m_amount -= a;
- m_data += a;
-+ m_pos += a;
-
-- // To main thread to inform about our state
-- emit processedData(a);
--
-- // FIXME possible optimization, already ask user thread for some data
-+ // To main thread to inform about our state. The m_pos will be sent as a sanity check.
-+ emit processedData(m_pos, a);
-
- return true;
- }
-@@ -250,10 +256,21 @@ public:
- {
- m_amount = 0;
- m_data = 0;
-+ m_dataArray.clear();
-+
-+ if (wantDataPending) {
-+ // had requested the user thread to send some data (only 1 in-flight at any moment)
-+ wantDataPending = false;
-+ }
-
- // Communicate as BlockingQueuedConnection
- bool b = false;
- emit resetData(&b);
-+ if (b) {
-+ // the reset succeeded, we're at pos 0 again
-+ m_pos = 0;
-+ // the HTTP code will anyway abort the request if !b.
-+ }
- return b;
- }
-
-@@ -264,8 +281,13 @@ public:
-
- public slots:
- // From user thread:
-- void haveDataSlot(QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
-+ void haveDataSlot(qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
- {
-+ if (pos != m_pos) {
-+ // Sometimes when re-sending a request in the qhttpnetwork* layer there is a pending haveData from the
-+ // user thread on the way to us. We need to ignore it since it is the data for the wrong(later) chunk.
-+ return;
-+ }
- wantDataPending = false;
-
- m_dataArray = dataArray;
-@@ -285,7 +307,7 @@ signals:
-
- // to main thread:
- void wantData(qint64);
-- void processedData(qint64);
-+ void processedData(qint64 pos, qint64 amount);
- void resetData(bool *b);
- };
-
-diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
-index 4ce7303..974a101 100644
---- a/src/network/access/qnetworkreplyhttpimpl.cpp
-+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
-@@ -424,6 +424,7 @@ QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
- , synchronous(false)
- , state(Idle)
- , statusCode(0)
-+ , uploadByteDevicePosition(false)
- , uploadDeviceChoking(false)
- , outgoingData(0)
- , bytesUploaded(-1)
-@@ -863,9 +864,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
- q, SLOT(uploadByteDeviceReadyReadSlot()),
- Qt::QueuedConnection);
-
-- // From main thread to user thread:
-- QObject::connect(q, SIGNAL(haveUploadData(QByteArray,bool,qint64)),
-- forwardUploadDevice, SLOT(haveDataSlot(QByteArray,bool,qint64)), Qt::QueuedConnection);
-+ // From user thread to http thread:
-+ QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
-+ forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
- QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
- forwardUploadDevice, SIGNAL(readyRead()),
- Qt::QueuedConnection);
-@@ -873,8 +874,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
- // From http thread to user thread:
- QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
- q, SLOT(wantUploadDataSlot(qint64)));
-- QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
-- q, SLOT(sentUploadDataSlot(qint64)));
-+ QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64, qint64)),
-+ q, SLOT(sentUploadDataSlot(qint64,qint64)));
- QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
- q, SLOT(resetUploadDataSlot(bool*)),
- Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
-@@ -1268,12 +1269,22 @@ void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfig
- void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
- {
- *r = uploadByteDevice->reset();
-+ if (*r) {
-+ // reset our own position which is used for the inter-thread communication
-+ uploadByteDevicePosition = 0;
-+ }
- }
-
- // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
--void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 amount)
-+void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
- {
-+ if (uploadByteDevicePosition + amount != pos) {
-+ // Sanity check, should not happen.
-+ error(QNetworkReply::UnknownNetworkError, "");
-+ return;
-+ }
- uploadByteDevice->advanceReadPointer(amount);
-+ uploadByteDevicePosition += amount;
- }
-
- // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
-@@ -1298,7 +1309,7 @@ void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
- QByteArray dataArray(data, currentUploadDataLength);
-
- // Communicate back to HTTP thread
-- emit q->haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
-+ emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
- }
-
- void QNetworkReplyHttpImplPrivate::uploadByteDeviceReadyReadSlot()
-diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
-index 77d9c5a..1940922 100644
---- a/src/network/access/qnetworkreplyhttpimpl_p.h
-+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
-@@ -120,7 +120,7 @@ public:
-
- Q_PRIVATE_SLOT(d_func(), void resetUploadDataSlot(bool *r))
- Q_PRIVATE_SLOT(d_func(), void wantUploadDataSlot(qint64))
-- Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64))
-+ Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64,qint64))
- Q_PRIVATE_SLOT(d_func(), void uploadByteDeviceReadyReadSlot())
- Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64))
- Q_PRIVATE_SLOT(d_func(), void _q_cacheSaveDeviceAboutToClose())
-@@ -144,7 +144,7 @@ signals:
-
- void startHttpRequestSynchronously();
-
-- void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
-+ void haveUploadData(const qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
- };
-
- class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
-@@ -195,6 +195,7 @@ public:
- // upload
- QNonContiguousByteDevice* createUploadByteDevice();
- QSharedPointer<QNonContiguousByteDevice> uploadByteDevice;
-+ qint64 uploadByteDevicePosition;
- bool uploadDeviceChoking; // if we couldn't readPointer() any data at the moment
- QIODevice *outgoingData;
- QSharedPointer<QRingBuffer> outgoingDataBuffer;
-@@ -283,7 +284,7 @@ public:
- // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
- void resetUploadDataSlot(bool *r);
- void wantUploadDataSlot(qint64);
-- void sentUploadDataSlot(qint64);
-+ void sentUploadDataSlot(qint64, qint64);
-
- // From user's QNonContiguousByteDevice
- void uploadByteDeviceReadyReadSlot();
-diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
-index 3ccedf6..d2edf67 100644
---- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
-+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
-@@ -457,6 +457,10 @@ private Q_SLOTS:
-
- void putWithRateLimiting();
-
-+#ifndef QT_NO_SSL
-+ void putWithServerClosingConnectionImmediately();
-+#endif
-+
- // NOTE: This test must be last!
- void parentingRepliesToTheApp();
- private:
-@@ -4718,18 +4722,22 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag()
- class SslServer : public QTcpServer {
- Q_OBJECT
- public:
-- SslServer() : socket(0) {};
-+ SslServer() : socket(0), m_ssl(true) {}
- void incomingConnection(qintptr socketDescriptor) {
- QSslSocket *serverSocket = new QSslSocket;
- serverSocket->setParent(this);
-
- if (serverSocket->setSocketDescriptor(socketDescriptor)) {
-+ connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
-+ if (!m_ssl) {
-+ emit newPlainConnection(serverSocket);
-+ return;
-+ }
- QString testDataDir = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absolutePath();
- if (testDataDir.isEmpty())
- testDataDir = QCoreApplication::applicationDirPath();
-
- connect(serverSocket, SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
-- connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
- serverSocket->setProtocol(QSsl::AnyProtocol);
- connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), serverSocket, SLOT(ignoreSslErrors()));
- serverSocket->setLocalCertificate(testDataDir + "/certs/server.pem");
-@@ -4740,11 +4748,12 @@ public:
- }
- }
- signals:
-- void newEncryptedConnection();
-+ void newEncryptedConnection(QSslSocket *s);
-+ void newPlainConnection(QSslSocket *s);
- public slots:
- void encryptedSlot() {
- socket = (QSslSocket*) sender();
-- emit newEncryptedConnection();
-+ emit newEncryptedConnection(socket);
- }
- void readyReadSlot() {
- // for the incoming sockets, not the server socket
-@@ -4753,6 +4762,7 @@ public slots:
-
- public:
- QSslSocket *socket;
-+ bool m_ssl;
- };
-
- // very similar to ioPostToHttpUploadProgress but for SSL
-@@ -4780,7 +4790,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
- QNetworkReplyPtr reply(manager.post(request, sourceFile));
-
- QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64)));
-- connect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
-+ connect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
- connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply.data(), SLOT(ignoreSslErrors()));
-
- // get the request started and the incoming socket connected
-@@ -4788,7 +4798,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
- QVERIFY(!QTestEventLoop::instance().timeout());
- QTcpSocket *incomingSocket = server.socket;
- QVERIFY(incomingSocket);
-- disconnect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
-+ disconnect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
-
-
- incomingSocket->setReadBufferSize(1*1024);
-@@ -7905,6 +7915,159 @@ void tst_QNetworkReply::putWithRateLimiting()
- }
-
-
-+#ifndef QT_NO_SSL
-+
-+class PutWithServerClosingConnectionImmediatelyHandler: public QObject
-+{
-+ Q_OBJECT
-+public:
-+ bool m_parsedHeaders;
-+ QByteArray m_receivedData;
-+ QByteArray m_expectedData;
-+ QSslSocket *m_socket;
-+ PutWithServerClosingConnectionImmediatelyHandler(QSslSocket *s, QByteArray expected) :m_parsedHeaders(false), m_expectedData(expected), m_socket(s)
-+ {
-+ m_socket->setParent(this);
-+ connect(m_socket, SIGNAL(readyRead()), SLOT(readyReadSlot()));
-+ connect(m_socket, SIGNAL(disconnected()), SLOT(disconnectedSlot()));
-+ }
-+signals:
-+ void correctFileUploadReceived();
-+ void corruptFileUploadReceived();
-+
-+public slots:
-+ void closeDelayed() {
-+ m_socket->close();
-+ }
-+
-+ void readyReadSlot()
-+ {
-+ QByteArray data = m_socket->readAll();
-+ m_receivedData += data;
-+ if (!m_parsedHeaders && m_receivedData.contains("\r\n\r\n")) {
-+ m_parsedHeaders = true;
-+ QTimer::singleShot(qrand()%10, this, SLOT(closeDelayed())); // simulate random network latency
-+ // This server simulates a web server connection closing, e.g. because of Apaches MaxKeepAliveRequests or KeepAliveTimeout
-+ // In this case QNAM needs to re-send the upload data but it had a bug which then corrupts the upload
-+ // This test catches that.
-+ }
-+
-+ }
-+ void disconnectedSlot()
-+ {
-+ if (m_parsedHeaders) {
-+ //qDebug() << m_receivedData.left(m_receivedData.indexOf("\r\n\r\n"));
-+ m_receivedData = m_receivedData.mid(m_receivedData.indexOf("\r\n\r\n")+4); // check only actual data
-+ }
-+ if (m_receivedData.length() > 0 && !m_expectedData.startsWith(m_receivedData)) {
-+ // We had received some data but it is corrupt!
-+ qDebug() << "CORRUPT" << m_receivedData.count();
-+
-+ // Use this to track down the pattern of the corruption and conclude the source
-+// QFile a("/tmp/corrupt");
-+// a.open(QIODevice::WriteOnly);
-+// a.write(m_receivedData);
-+// a.close();
-+
-+// QFile b("/tmp/correct");
-+// b.open(QIODevice::WriteOnly);
-+// b.write(m_expectedData);
-+// b.close();
-+ //exit(1);
-+ emit corruptFileUploadReceived();
-+ } else {
-+ emit correctFileUploadReceived();
-+ }
-+ }
-+};
-+
-+class PutWithServerClosingConnectionImmediatelyServer: public SslServer
-+{
-+ Q_OBJECT
-+public:
-+ int m_correctUploads;
-+ int m_corruptUploads;
-+ int m_repliesFinished;
-+ int m_expectedReplies;
-+ QByteArray m_expectedData;
-+ PutWithServerClosingConnectionImmediatelyServer() : SslServer(), m_correctUploads(0), m_corruptUploads(0), m_repliesFinished(0), m_expectedReplies(0)
-+ {
-+ QObject::connect(this, SIGNAL(newEncryptedConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
-+ QObject::connect(this, SIGNAL(newPlainConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
-+ }
-+
-+public slots:
-+ void createHandlerForConnection(QSslSocket* s) {
-+ PutWithServerClosingConnectionImmediatelyHandler *handler = new PutWithServerClosingConnectionImmediatelyHandler(s, m_expectedData);
-+ handler->setParent(this);
-+ QObject::connect(handler, SIGNAL(correctFileUploadReceived()), this, SLOT(increaseCorrect()));
-+ QObject::connect(handler, SIGNAL(corruptFileUploadReceived()), this, SLOT(increaseCorrupt()));
-+ }
-+ void increaseCorrect() {
-+ m_correctUploads++;
-+ }
-+ void increaseCorrupt() {
-+ m_corruptUploads++;
-+ }
-+ void replyFinished() {
-+ m_repliesFinished++;
-+ if (m_repliesFinished == m_expectedReplies) {
-+ QTestEventLoop::instance().exitLoop();
-+ }
-+ }
-+};
-+
-+
-+
-+void tst_QNetworkReply::putWithServerClosingConnectionImmediately()
-+{
-+ const int numUploads = 40;
-+ qint64 wantedSize = 512*1024; // 512 kB
-+ QByteArray sourceFile;
-+ for (int i = 0; i < wantedSize; ++i) {
-+ sourceFile += (char)'a' +(i%26);
-+ }
-+ bool withSsl = false;
-+
-+ for (int s = 0; s <= 1; s++) {
-+ withSsl = (s == 1);
-+ // Test also needs to run several times because of 9c2ecf89
-+ for (int j = 0; j < 20; j++) {
-+ // emulate a minimal https server
-+ PutWithServerClosingConnectionImmediatelyServer server;
-+ server.m_ssl = withSsl;
-+ server.m_expectedData = sourceFile;
-+ server.m_expectedReplies = numUploads;
-+ server.listen(QHostAddress(QHostAddress::LocalHost), 0);
-+
-+ for (int i = 0; i < numUploads; i++) {
-+ // create the request
-+ QUrl url = QUrl(QString("http%1://127.0.0.1:%2/file=%3").arg(withSsl ? "s" : "").arg(server.serverPort()).arg(i));
-+ QNetworkRequest request(url);
-+ QNetworkReply *reply = manager.put(request, sourceFile);
-+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
-+ connect(reply, SIGNAL(finished()), &server, SLOT(replyFinished()));
-+ reply->setParent(&server);
-+ }
-+
-+ // get the request started and the incoming socket connected
-+ QTestEventLoop::instance().enterLoop(10);
-+
-+ //qDebug() << "correct=" << server.m_correctUploads << "corrupt=" << server.m_corruptUploads << "expected=" <<numUploads;
-+
-+ // Sanity check because ecause of 9c2ecf89 most replies will error out but we want to make sure at least some of them worked
-+ QVERIFY(server.m_correctUploads > 5);
-+ // Because actually important is that we don't get any corruption:
-+ QCOMPARE(server.m_corruptUploads, 0);
-+
-+ server.close();
-+ }
-+ }
-+
-+
-+}
-+
-+#endif
-
- // NOTE: This test must be last testcase in tst_qnetworkreply!
- void tst_QNetworkReply::parentingRepliesToTheApp()
---
-1.9.1
-
--- /dev/null
+From cff39fba10ffc10ee4dcfdc66ff6528eb26462d3 Mon Sep 17 00:00:00 2001
+From: Markus Goetz <markus@woboq.com>
+Date: Fri, 10 Apr 2015 14:09:53 +0200
+Subject: [PATCH] QNAM: Fix upload corruptions when server closes connection
+
+This patch fixes several upload corruptions if the server closes the connection
+while/before we send data into it. They happen inside multiple places in the HTTP
+layer and are explained in the comments.
+Corruptions are:
+* The upload byte device has an in-flight signal with pending upload data, if
+it gets reset (because server closes the connection) then the re-send of the
+request was sometimes taking this stale in-flight pending upload data.
+* Because some signals were DirectConnection and some were QueuedConnection, there
+was a chance that a direct signal overtakes a queued signal. The state machine
+then sent data down the socket which was buffered there (and sent later) although
+it did not match the current state of the state machine when it was actually sent.
+* A socket was seen as being able to have requests sent even though it was not
+encrypted yet. This relates to the previous corruption where data is stored inside
+the socket's buffer and then sent later.
+
+The included auto test produces all fixed corruptions, I detected no regressions
+via the other tests.
+This code also adds a bit of sanity checking to protect from possible further
+problems.
+
+[ChangeLog][QtNetwork] Fix HTTP(s) upload corruption when server closes connection
+
+Change-Id: I54c883925ec897050941498f139c4b523030432e
+Reviewed-by: Peter Hartmann <peter-qt@hartmann.tk>
+---
+ src/corelib/io/qnoncontiguousbytedevice.cpp | 18 +++
+ src/corelib/io/qnoncontiguousbytedevice_p.h | 4 +
+ .../access/qhttpnetworkconnectionchannel.cpp | 35 ++++-
+ .../access/qhttpnetworkconnectionchannel_p.h | 2 +
+ src/network/access/qhttpprotocolhandler.cpp | 7 +
+ src/network/access/qhttpthreaddelegate_p.h | 36 ++++-
+ src/network/access/qnetworkreplyhttpimpl.cpp | 25 ++-
+ src/network/access/qnetworkreplyhttpimpl_p.h | 7 +-
+ .../access/qnetworkreply/tst_qnetworkreply.cpp | 175 ++++++++++++++++++++-
+ 9 files changed, 279 insertions(+), 30 deletions(-)
+
+diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp
+index 11510a8..760ca3d 100644
+--- a/src/corelib/io/qnoncontiguousbytedevice.cpp
++++ b/src/corelib/io/qnoncontiguousbytedevice.cpp
+@@ -236,6 +236,11 @@ qint64 QNonContiguousByteDeviceByteArrayImpl::size()
+ return byteArray->size();
+ }
+
++qint64 QNonContiguousByteDeviceByteArrayImpl::pos()
++{
++ return currentPosition;
++}
++
+ QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb)
+ : QNonContiguousByteDevice(), currentPosition(0)
+ {
+@@ -273,6 +278,11 @@ bool QNonContiguousByteDeviceRingBufferImpl::atEnd()
+ return currentPosition >= size();
+ }
+
++qint64 QNonContiguousByteDeviceRingBufferImpl::pos()
++{
++ return currentPosition;
++}
++
+ bool QNonContiguousByteDeviceRingBufferImpl::reset()
+ {
+ if (resetDisabled)
+@@ -406,6 +416,14 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::size()
+ return device->size() - initialPosition;
+ }
+
++qint64 QNonContiguousByteDeviceIoDeviceImpl::pos()
++{
++ if (device->isSequential())
++ return -1;
++
++ return device->pos();
++}
++
+ QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0)
+ {
+ byteDevice = bd;
+diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h
+index c05ae11..4d7b7b0 100644
+--- a/src/corelib/io/qnoncontiguousbytedevice_p.h
++++ b/src/corelib/io/qnoncontiguousbytedevice_p.h
+@@ -61,6 +61,7 @@ public:
+ virtual const char* readPointer(qint64 maximumLength, qint64 &len) = 0;
+ virtual bool advanceReadPointer(qint64 amount) = 0;
+ virtual bool atEnd() = 0;
++ virtual qint64 pos() { return -1; }
+ virtual bool reset() = 0;
+ void disableReset();
+ bool isResetDisabled() { return resetDisabled; }
+@@ -106,6 +107,7 @@ public:
+ bool atEnd();
+ bool reset();
+ qint64 size();
++ qint64 pos() Q_DECL_OVERRIDE;
+ protected:
+ QByteArray* byteArray;
+ qint64 currentPosition;
+@@ -121,6 +123,7 @@ public:
+ bool atEnd();
+ bool reset();
+ qint64 size();
++ qint64 pos() Q_DECL_OVERRIDE;
+ protected:
+ QSharedPointer<QRingBuffer> ringBuffer;
+ qint64 currentPosition;
+@@ -138,6 +141,7 @@ public:
+ bool atEnd();
+ bool reset();
+ qint64 size();
++ qint64 pos() Q_DECL_OVERRIDE;
+ protected:
+ QIODevice* device;
+ QByteArray* currentReadBuffer;
+diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
+index 9f63280..49c6793 100644
+--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
++++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
+@@ -106,15 +106,19 @@ void QHttpNetworkConnectionChannel::init()
+ socket->setProxy(QNetworkProxy::NoProxy);
+ #endif
+
++ // We want all signals (except the interactive ones) be connected as QueuedConnection
++ // because else we're falling into cases where we recurse back into the socket code
++ // and mess up the state. Always going to the event loop (and expecting that when reading/writing)
++ // is safer.
+ QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(_q_bytesWritten(qint64)),
+- Qt::DirectConnection);
++ Qt::QueuedConnection);
+ QObject::connect(socket, SIGNAL(connected()),
+ this, SLOT(_q_connected()),
+- Qt::DirectConnection);
++ Qt::QueuedConnection);
+ QObject::connect(socket, SIGNAL(readyRead()),
+ this, SLOT(_q_readyRead()),
+- Qt::DirectConnection);
++ Qt::QueuedConnection);
+
+ // The disconnected() and error() signals may already come
+ // while calling connectToHost().
+@@ -143,13 +147,13 @@ void QHttpNetworkConnectionChannel::init()
+ // won't be a sslSocket if encrypt is false
+ QObject::connect(sslSocket, SIGNAL(encrypted()),
+ this, SLOT(_q_encrypted()),
+- Qt::DirectConnection);
++ Qt::QueuedConnection);
+ QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
+ this, SLOT(_q_sslErrors(QList<QSslError>)),
+ Qt::DirectConnection);
+ QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
+ this, SLOT(_q_encryptedBytesWritten(qint64)),
+- Qt::DirectConnection);
++ Qt::QueuedConnection);
+
+ if (ignoreAllSslErrors)
+ sslSocket->ignoreSslErrors();
+@@ -186,8 +190,11 @@ void QHttpNetworkConnectionChannel::close()
+ // pendingEncrypt must only be true in between connected and encrypted states
+ pendingEncrypt = false;
+
+- if (socket)
++ if (socket) {
++ // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
++ // there is no socket yet.
+ socket->close();
++ }
+ }
+
+
+@@ -353,6 +360,14 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
+ }
+ return false;
+ }
++
++ // This code path for ConnectedState
++ if (pendingEncrypt) {
++ // Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
++ // and corrupt the things sent to the server.
++ return false;
++ }
++
+ return true;
+ }
+
+@@ -659,6 +674,12 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const
+ void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
+ {
+ Q_UNUSED(bytes);
++ if (ssl) {
++ // In the SSL case we want to send data from encryptedBytesWritten signal since that one
++ // is the one going down to the actual network, not only into some SSL buffer.
++ return;
++ }
++
+ // bytes have been written to the socket. write even more of them :)
+ if (isSocketWriting())
+ sendRequest();
+@@ -734,7 +755,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
+
+ // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
+ //channels[i].reconnectAttempts = 2;
+- if (pendingEncrypt) {
++ if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
+ #ifndef QT_NO_SSL
+ if (connection->sslContext().isNull()) {
+ // this socket is making the 1st handshake for this connection,
+diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
+index 692c0e6..231fe11 100644
+--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
++++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
+@@ -83,6 +83,8 @@ typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
+ class QHttpNetworkConnectionChannel : public QObject {
+ Q_OBJECT
+ public:
++ // TODO: Refactor this to add an EncryptingState (and remove pendingEncrypt).
++ // Also add an Unconnected state so IdleState does not have double meaning.
+ enum ChannelState {
+ IdleState = 0, // ready to send request
+ ConnectingState = 1, // connecting to host
+diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
+index 28e10f7..3357948 100644
+--- a/src/network/access/qhttpprotocolhandler.cpp
++++ b/src/network/access/qhttpprotocolhandler.cpp
+@@ -368,6 +368,13 @@ bool QHttpProtocolHandler::sendRequest()
+ // nothing to read currently, break the loop
+ break;
+ } else {
++ if (m_channel->written != uploadByteDevice->pos()) {
++ // Sanity check. This was useful in tracking down an upload corruption.
++ qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << m_channel->written << "but read device is at" << uploadByteDevice->pos();
++ Q_ASSERT(m_channel->written == uploadByteDevice->pos());
++ m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
++ return false;
++ }
+ qint64 currentWriteSize = m_socket->write(readPointer, currentReadSize);
+ if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
+ // socket broke down
+diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
+index 1661082..b553409 100644
+--- a/src/network/access/qhttpthreaddelegate_p.h
++++ b/src/network/access/qhttpthreaddelegate_p.h
+@@ -187,6 +187,7 @@ protected:
+ QByteArray m_dataArray;
+ bool m_atEnd;
+ qint64 m_size;
++ qint64 m_pos; // to match calls of haveDataSlot with the expected position
+ public:
+ QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
+ : QNonContiguousByteDevice(),
+@@ -194,7 +195,8 @@ public:
+ m_amount(0),
+ m_data(0),
+ m_atEnd(aE),
+- m_size(s)
++ m_size(s),
++ m_pos(0)
+ {
+ }
+
+@@ -202,6 +204,11 @@ public:
+ {
+ }
+
++ qint64 pos() Q_DECL_OVERRIDE
++ {
++ return m_pos;
++ }
++
+ const char* readPointer(qint64 maximumLength, qint64 &len)
+ {
+ if (m_amount > 0) {
+@@ -229,11 +236,10 @@ public:
+
+ m_amount -= a;
+ m_data += a;
++ m_pos += a;
+
+- // To main thread to inform about our state
+- emit processedData(a);
+-
+- // FIXME possible optimization, already ask user thread for some data
++ // To main thread to inform about our state. The m_pos will be sent as a sanity check.
++ emit processedData(m_pos, a);
+
+ return true;
+ }
+@@ -250,10 +256,21 @@ public:
+ {
+ m_amount = 0;
+ m_data = 0;
++ m_dataArray.clear();
++
++ if (wantDataPending) {
++ // had requested the user thread to send some data (only 1 in-flight at any moment)
++ wantDataPending = false;
++ }
+
+ // Communicate as BlockingQueuedConnection
+ bool b = false;
+ emit resetData(&b);
++ if (b) {
++ // the reset succeeded, we're at pos 0 again
++ m_pos = 0;
++ // the HTTP code will anyway abort the request if !b.
++ }
+ return b;
+ }
+
+@@ -264,8 +281,13 @@ public:
+
+ public slots:
+ // From user thread:
+- void haveDataSlot(QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
++ void haveDataSlot(qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
+ {
++ if (pos != m_pos) {
++ // Sometimes when re-sending a request in the qhttpnetwork* layer there is a pending haveData from the
++ // user thread on the way to us. We need to ignore it since it is the data for the wrong(later) chunk.
++ return;
++ }
+ wantDataPending = false;
+
+ m_dataArray = dataArray;
+@@ -285,7 +307,7 @@ signals:
+
+ // to main thread:
+ void wantData(qint64);
+- void processedData(qint64);
++ void processedData(qint64 pos, qint64 amount);
+ void resetData(bool *b);
+ };
+
+diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
+index 4ce7303..974a101 100644
+--- a/src/network/access/qnetworkreplyhttpimpl.cpp
++++ b/src/network/access/qnetworkreplyhttpimpl.cpp
+@@ -424,6 +424,7 @@ QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
+ , synchronous(false)
+ , state(Idle)
+ , statusCode(0)
++ , uploadByteDevicePosition(false)
+ , uploadDeviceChoking(false)
+ , outgoingData(0)
+ , bytesUploaded(-1)
+@@ -863,9 +864,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
+ q, SLOT(uploadByteDeviceReadyReadSlot()),
+ Qt::QueuedConnection);
+
+- // From main thread to user thread:
+- QObject::connect(q, SIGNAL(haveUploadData(QByteArray,bool,qint64)),
+- forwardUploadDevice, SLOT(haveDataSlot(QByteArray,bool,qint64)), Qt::QueuedConnection);
++ // From user thread to http thread:
++ QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
++ forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
+ QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
+ forwardUploadDevice, SIGNAL(readyRead()),
+ Qt::QueuedConnection);
+@@ -873,8 +874,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
+ // From http thread to user thread:
+ QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
+ q, SLOT(wantUploadDataSlot(qint64)));
+- QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
+- q, SLOT(sentUploadDataSlot(qint64)));
++ QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64, qint64)),
++ q, SLOT(sentUploadDataSlot(qint64,qint64)));
+ QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
+ q, SLOT(resetUploadDataSlot(bool*)),
+ Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
+@@ -1268,12 +1269,22 @@ void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfig
+ void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
+ {
+ *r = uploadByteDevice->reset();
++ if (*r) {
++ // reset our own position which is used for the inter-thread communication
++ uploadByteDevicePosition = 0;
++ }
+ }
+
+ // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+-void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 amount)
++void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
+ {
++ if (uploadByteDevicePosition + amount != pos) {
++ // Sanity check, should not happen.
++ error(QNetworkReply::UnknownNetworkError, "");
++ return;
++ }
+ uploadByteDevice->advanceReadPointer(amount);
++ uploadByteDevicePosition += amount;
+ }
+
+ // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+@@ -1298,7 +1309,7 @@ void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
+ QByteArray dataArray(data, currentUploadDataLength);
+
+ // Communicate back to HTTP thread
+- emit q->haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
++ emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
+ }
+
+ void QNetworkReplyHttpImplPrivate::uploadByteDeviceReadyReadSlot()
+diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
+index 77d9c5a..1940922 100644
+--- a/src/network/access/qnetworkreplyhttpimpl_p.h
++++ b/src/network/access/qnetworkreplyhttpimpl_p.h
+@@ -120,7 +120,7 @@ public:
+
+ Q_PRIVATE_SLOT(d_func(), void resetUploadDataSlot(bool *r))
+ Q_PRIVATE_SLOT(d_func(), void wantUploadDataSlot(qint64))
+- Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64))
++ Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64,qint64))
+ Q_PRIVATE_SLOT(d_func(), void uploadByteDeviceReadyReadSlot())
+ Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64))
+ Q_PRIVATE_SLOT(d_func(), void _q_cacheSaveDeviceAboutToClose())
+@@ -144,7 +144,7 @@ signals:
+
+ void startHttpRequestSynchronously();
+
+- void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
++ void haveUploadData(const qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
+ };
+
+ class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
+@@ -195,6 +195,7 @@ public:
+ // upload
+ QNonContiguousByteDevice* createUploadByteDevice();
+ QSharedPointer<QNonContiguousByteDevice> uploadByteDevice;
++ qint64 uploadByteDevicePosition;
+ bool uploadDeviceChoking; // if we couldn't readPointer() any data at the moment
+ QIODevice *outgoingData;
+ QSharedPointer<QRingBuffer> outgoingDataBuffer;
+@@ -283,7 +284,7 @@ public:
+ // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
+ void resetUploadDataSlot(bool *r);
+ void wantUploadDataSlot(qint64);
+- void sentUploadDataSlot(qint64);
++ void sentUploadDataSlot(qint64, qint64);
+
+ // From user's QNonContiguousByteDevice
+ void uploadByteDeviceReadyReadSlot();
+diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+index 3ccedf6..d2edf67 100644
+--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
++++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+@@ -457,6 +457,10 @@ private Q_SLOTS:
+
+ void putWithRateLimiting();
+
++#ifndef QT_NO_SSL
++ void putWithServerClosingConnectionImmediately();
++#endif
++
+ // NOTE: This test must be last!
+ void parentingRepliesToTheApp();
+ private:
+@@ -4718,18 +4722,22 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag()
+ class SslServer : public QTcpServer {
+ Q_OBJECT
+ public:
+- SslServer() : socket(0) {};
++ SslServer() : socket(0), m_ssl(true) {}
+ void incomingConnection(qintptr socketDescriptor) {
+ QSslSocket *serverSocket = new QSslSocket;
+ serverSocket->setParent(this);
+
+ if (serverSocket->setSocketDescriptor(socketDescriptor)) {
++ connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
++ if (!m_ssl) {
++ emit newPlainConnection(serverSocket);
++ return;
++ }
+ QString testDataDir = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absolutePath();
+ if (testDataDir.isEmpty())
+ testDataDir = QCoreApplication::applicationDirPath();
+
+ connect(serverSocket, SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
+- connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
+ serverSocket->setProtocol(QSsl::AnyProtocol);
+ connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), serverSocket, SLOT(ignoreSslErrors()));
+ serverSocket->setLocalCertificate(testDataDir + "/certs/server.pem");
+@@ -4740,11 +4748,12 @@ public:
+ }
+ }
+ signals:
+- void newEncryptedConnection();
++ void newEncryptedConnection(QSslSocket *s);
++ void newPlainConnection(QSslSocket *s);
+ public slots:
+ void encryptedSlot() {
+ socket = (QSslSocket*) sender();
+- emit newEncryptedConnection();
++ emit newEncryptedConnection(socket);
+ }
+ void readyReadSlot() {
+ // for the incoming sockets, not the server socket
+@@ -4753,6 +4762,7 @@ public slots:
+
+ public:
+ QSslSocket *socket;
++ bool m_ssl;
+ };
+
+ // very similar to ioPostToHttpUploadProgress but for SSL
+@@ -4780,7 +4790,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
+ QNetworkReplyPtr reply(manager.post(request, sourceFile));
+
+ QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64)));
+- connect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
++ connect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply.data(), SLOT(ignoreSslErrors()));
+
+ // get the request started and the incoming socket connected
+@@ -4788,7 +4798,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QTcpSocket *incomingSocket = server.socket;
+ QVERIFY(incomingSocket);
+- disconnect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
++ disconnect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
+
+
+ incomingSocket->setReadBufferSize(1*1024);
+@@ -7905,6 +7915,159 @@ void tst_QNetworkReply::putWithRateLimiting()
+ }
+
+
++#ifndef QT_NO_SSL
++
++class PutWithServerClosingConnectionImmediatelyHandler: public QObject
++{
++ Q_OBJECT
++public:
++ bool m_parsedHeaders;
++ QByteArray m_receivedData;
++ QByteArray m_expectedData;
++ QSslSocket *m_socket;
++ PutWithServerClosingConnectionImmediatelyHandler(QSslSocket *s, QByteArray expected) :m_parsedHeaders(false), m_expectedData(expected), m_socket(s)
++ {
++ m_socket->setParent(this);
++ connect(m_socket, SIGNAL(readyRead()), SLOT(readyReadSlot()));
++ connect(m_socket, SIGNAL(disconnected()), SLOT(disconnectedSlot()));
++ }
++signals:
++ void correctFileUploadReceived();
++ void corruptFileUploadReceived();
++
++public slots:
++ void closeDelayed() {
++ m_socket->close();
++ }
++
++ void readyReadSlot()
++ {
++ QByteArray data = m_socket->readAll();
++ m_receivedData += data;
++ if (!m_parsedHeaders && m_receivedData.contains("\r\n\r\n")) {
++ m_parsedHeaders = true;
++ QTimer::singleShot(qrand()%10, this, SLOT(closeDelayed())); // simulate random network latency
++ // This server simulates a web server connection closing, e.g. because of Apaches MaxKeepAliveRequests or KeepAliveTimeout
++ // In this case QNAM needs to re-send the upload data but it had a bug which then corrupts the upload
++ // This test catches that.
++ }
++
++ }
++ void disconnectedSlot()
++ {
++ if (m_parsedHeaders) {
++ //qDebug() << m_receivedData.left(m_receivedData.indexOf("\r\n\r\n"));
++ m_receivedData = m_receivedData.mid(m_receivedData.indexOf("\r\n\r\n")+4); // check only actual data
++ }
++ if (m_receivedData.length() > 0 && !m_expectedData.startsWith(m_receivedData)) {
++ // We had received some data but it is corrupt!
++ qDebug() << "CORRUPT" << m_receivedData.count();
++
++ // Use this to track down the pattern of the corruption and conclude the source
++// QFile a("/tmp/corrupt");
++// a.open(QIODevice::WriteOnly);
++// a.write(m_receivedData);
++// a.close();
++
++// QFile b("/tmp/correct");
++// b.open(QIODevice::WriteOnly);
++// b.write(m_expectedData);
++// b.close();
++ //exit(1);
++ emit corruptFileUploadReceived();
++ } else {
++ emit correctFileUploadReceived();
++ }
++ }
++};
++
++class PutWithServerClosingConnectionImmediatelyServer: public SslServer
++{
++ Q_OBJECT
++public:
++ int m_correctUploads;
++ int m_corruptUploads;
++ int m_repliesFinished;
++ int m_expectedReplies;
++ QByteArray m_expectedData;
++ PutWithServerClosingConnectionImmediatelyServer() : SslServer(), m_correctUploads(0), m_corruptUploads(0), m_repliesFinished(0), m_expectedReplies(0)
++ {
++ QObject::connect(this, SIGNAL(newEncryptedConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
++ QObject::connect(this, SIGNAL(newPlainConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
++ }
++
++public slots:
++ void createHandlerForConnection(QSslSocket* s) {
++ PutWithServerClosingConnectionImmediatelyHandler *handler = new PutWithServerClosingConnectionImmediatelyHandler(s, m_expectedData);
++ handler->setParent(this);
++ QObject::connect(handler, SIGNAL(correctFileUploadReceived()), this, SLOT(increaseCorrect()));
++ QObject::connect(handler, SIGNAL(corruptFileUploadReceived()), this, SLOT(increaseCorrupt()));
++ }
++ void increaseCorrect() {
++ m_correctUploads++;
++ }
++ void increaseCorrupt() {
++ m_corruptUploads++;
++ }
++ void replyFinished() {
++ m_repliesFinished++;
++ if (m_repliesFinished == m_expectedReplies) {
++ QTestEventLoop::instance().exitLoop();
++ }
++ }
++};
++
++
++
++void tst_QNetworkReply::putWithServerClosingConnectionImmediately()
++{
++ const int numUploads = 40;
++ qint64 wantedSize = 512*1024; // 512 kB
++ QByteArray sourceFile;
++ for (int i = 0; i < wantedSize; ++i) {
++ sourceFile += (char)'a' +(i%26);
++ }
++ bool withSsl = false;
++
++ for (int s = 0; s <= 1; s++) {
++ withSsl = (s == 1);
++ // Test also needs to run several times because of 9c2ecf89
++ for (int j = 0; j < 20; j++) {
++ // emulate a minimal https server
++ PutWithServerClosingConnectionImmediatelyServer server;
++ server.m_ssl = withSsl;
++ server.m_expectedData = sourceFile;
++ server.m_expectedReplies = numUploads;
++ server.listen(QHostAddress(QHostAddress::LocalHost), 0);
++
++ for (int i = 0; i < numUploads; i++) {
++ // create the request
++ QUrl url = QUrl(QString("http%1://127.0.0.1:%2/file=%3").arg(withSsl ? "s" : "").arg(server.serverPort()).arg(i));
++ QNetworkRequest request(url);
++ QNetworkReply *reply = manager.put(request, sourceFile);
++ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
++ connect(reply, SIGNAL(finished()), &server, SLOT(replyFinished()));
++ reply->setParent(&server);
++ }
++
++ // get the request started and the incoming socket connected
++ QTestEventLoop::instance().enterLoop(10);
++
++ //qDebug() << "correct=" << server.m_correctUploads << "corrupt=" << server.m_corruptUploads << "expected=" <<numUploads;
++
++ // Sanity check because ecause of 9c2ecf89 most replies will error out but we want to make sure at least some of them worked
++ QVERIFY(server.m_correctUploads > 5);
++ // Because actually important is that we don't get any corruption:
++ QCOMPARE(server.m_corruptUploads, 0);
++
++ server.close();
++ }
++ }
++
++
++}
++
++#endif
+
+ // NOTE: This test must be last testcase in tst_qnetworkreply!
+ void tst_QNetworkReply::parentingRepliesToTheApp()
+--
+1.9.1
+
+++ /dev/null
-From 0d3c8feeeb3c49d01bcfbca63b672e20ab29aa9f Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Andr=C3=A9=20Klitzing?= <aklitzing@gmail.com>
-Date: Thu, 30 Jul 2015 10:08:36 +0200
-Subject: [PATCH] Fix SNI for TlsV1_0OrLater, TlsV1_1OrLater and TlsV1_2OrLater
-
-Since SslV3, SslV2 and UnknownProtocol do not support it we can
-invert the IF clause here.
-
-Change-Id: I42e942337d01f3a8c97885b268bffa568e40d335
-Task-number: QTBUG-47528
-Reviewed-by: Mikkel Krautz <mikkel@krautz.dk>
-Reviewed-by: Richard J. Moore <rich@kde.org>
----
- src/network/ssl/qsslsocket_openssl.cpp | 9 +++------
- 1 file changed, 3 insertions(+), 6 deletions(-)
-
-diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
-index 049666b..bc1df81 100644
---- a/src/network/ssl/qsslsocket_openssl.cpp
-+++ b/src/network/ssl/qsslsocket_openssl.cpp
-@@ -377,12 +377,9 @@ bool QSslSocketBackendPrivate::initSslContext()
- return false;
- }
-
-- if ((configuration.protocol == QSsl::TlsV1SslV3 ||
-- configuration.protocol == QSsl::TlsV1_0 ||
-- configuration.protocol == QSsl::TlsV1_1 ||
-- configuration.protocol == QSsl::TlsV1_2 ||
-- configuration.protocol == QSsl::SecureProtocols ||
-- configuration.protocol == QSsl::AnyProtocol) &&
-+ if (configuration.protocol != QSsl::SslV2 &&
-+ configuration.protocol != QSsl::SslV3 &&
-+ configuration.protocol != QSsl::UnknownProtocol &&
- mode == QSslSocket::SslClientMode && q_SSLeay() >= 0x00090806fL) {
- // Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format.
- QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName;
---
-1.9.1
* 0003-Fix-crash-if-PAC-script-retrieval-returns-a-null-CFD.patch
### Part of Qt v5.4.2 and later
-* 0004-OSX-Fix-disapearing-tray-icon.patch
+* 0004-Cocoa-Fix-systray-SVG-icons.patch
+* 0005-OSX-Fix-disapearing-tray-icon.patch
(TODO: actual patch slighly differs)
-* 0006-QNAM-Fix-upload-corruptions-when-server-closes-conne.patch
+* 0007-QNAM-Fix-upload-corruptions-when-server-closes-conne.patch
(TODO: Actual patch on build machine spans over two commit but is identical)
### Part of Qt v5.5.1 and later
* 0016-Fix-possible-crash-when-passing-an-invalid-PAC-URL.patch
### Not submitted to be part of any release:
-* 0005-Fix-force-debug-info-with-macx-clang_NOUPSTREAM.patch
+* 0006-Fix-force-debug-info-with-macx-clang_NOUPSTREAM.patch
This is only needed if you intent to harvest debugging symbols
for breakpad.