File qt5-fix-upload-corruptions.patch of Package mingw32-libqt5-qtbase

From 05da6418db2d606ddf8fd2bb179b94a7c493501e 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.
The included auto test produces all fixed corruptions.
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
---
 src/corelib/io/qnoncontiguousbytedevice.cpp        |   5 +
 src/corelib/io/qnoncontiguousbytedevice_p.h        |   5 +
 .../access/qhttpnetworkconnectionchannel.cpp       |  36 ++++-
 .../access/qhttpnetworkconnectionchannel_p.h       |   2 +
 src/network/access/qhttpprotocolhandler.cpp        |   7 +
 src/network/access/qhttpthreaddelegate_p.h         |  31 +++-
 src/network/access/qnetworkreplyhttpimpl.cpp       |  25 ++-
 src/network/access/qnetworkreplyhttpimpl_p.h       |   7 +-
 .../access/qnetworkreply/tst_qnetworkreply.cpp     | 174 ++++++++++++++++++++-
 9 files changed, 262 insertions(+), 30 deletions(-)

diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp
index 11510a8..2e564ae 100644
--- a/src/corelib/io/qnoncontiguousbytedevice.cpp
+++ b/src/corelib/io/qnoncontiguousbytedevice.cpp
@@ -273,6 +273,11 @@ bool QNonContiguousByteDeviceRingBufferImpl::atEnd()
     return currentPosition >= size();
 }
 
+qint64 QNonContiguousByteDeviceRingBufferImpl::pos()
+{
+    return currentPosition;
+}
+
 bool QNonContiguousByteDeviceRingBufferImpl::reset()
 {
     if (resetDisabled)
diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h
index c05ae11..bf8d9d8 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; }
@@ -98,6 +99,7 @@ public:
 
 class QNonContiguousByteDeviceByteArrayImpl : public QNonContiguousByteDevice
 {
+    Q_OBJECT
 public:
     QNonContiguousByteDeviceByteArrayImpl(QByteArray *ba);
     ~QNonContiguousByteDeviceByteArrayImpl();
@@ -113,6 +115,7 @@ protected:
 
 class QNonContiguousByteDeviceRingBufferImpl : public QNonContiguousByteDevice
 {
+    Q_OBJECT
 public:
     QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb);
     ~QNonContiguousByteDeviceRingBufferImpl();
@@ -121,6 +124,7 @@ public:
     bool atEnd();
     bool reset();
     qint64 size();
+    qint64 pos();
 protected:
     QSharedPointer<QRingBuffer> ringBuffer;
     qint64 currentPosition;
@@ -169,6 +173,7 @@ protected:
 // ... and the reverse thing
 class QByteDeviceWrappingIoDevice : public QIODevice
 {
+    Q_OBJECT
 public:
     QByteDeviceWrappingIoDevice (QNonContiguousByteDevice *bd);
     ~QByteDeviceWrappingIoDevice ();
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 9f63280..d77f95f 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,15 @@ 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 +675,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 +756,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..c9c871c 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 {
+                Q_ASSERT(m_channel->written == uploadByteDevice->pos());
+                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();
+                    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..386a430 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,10 @@ public:
     {
     }
 
+    qint64 pos() {
+        return m_pos;
+    }
+
     const char* readPointer(qint64 maximumLength, qint64 &len)
     {
         if (m_amount > 0) {
@@ -229,11 +235,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,6 +255,13 @@ public:
     {
         m_amount = 0;
         m_data = 0;
+        m_dataArray.clear();
+        m_pos = 0;
+
+        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;
@@ -264,8 +276,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 +302,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..9a11fe1 100644
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -457,6 +457,8 @@ private Q_SLOTS:
 
     void putWithRateLimiting();
 
+    void putWithServerClosingConnectionImmediately();
+
     // NOTE: This test must be last!
     void parentingRepliesToTheApp();
 private:
@@ -4718,18 +4720,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 +4746,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 +4760,7 @@ public slots:
 
 public:
     QSslSocket *socket;
+    bool m_ssl;
 };
 
 // very similar to ioPostToHttpUploadProgress but for SSL
@@ -4780,7 +4788,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 +4796,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 +7913,160 @@ 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

openSUSE Build Service is sponsored by