[arch-commits] Commit in qt5/trunk (PKGBUILD qnam-corruption.patch)

Felix Yan fyan at archlinux.org
Wed May 6 10:13:45 UTC 2015


    Date: Wednesday, May 6, 2015 @ 12:13:45
  Author: fyan
Revision: 238538

upgpkg: qt5 5.4.1-5

Added:
  qt5/trunk/qnam-corruption.patch
Modified:
  qt5/trunk/PKGBUILD

-----------------------+
 PKGBUILD              |   10 
 qnam-corruption.patch |  691 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 698 insertions(+), 3 deletions(-)

Modified: PKGBUILD
===================================================================
--- PKGBUILD	2015-05-06 08:21:39 UTC (rev 238537)
+++ PKGBUILD	2015-05-06 10:13:45 UTC (rev 238538)
@@ -28,7 +28,7 @@
          'qt5-x11extras'
          'qt5-xmlpatterns')
 pkgver=5.4.1
-pkgrel=4
+pkgrel=5
 arch=('i686' 'x86_64')
 url='http://qt-project.org/'
 license=('GPL3' 'LGPL' 'FDL' 'custom')
@@ -42,7 +42,7 @@
 groups=('qt' 'qt5')
 _pkgfqn="qt-everywhere-opensource-src-${pkgver}"
 source=("http://download.qt-project.org/official_releases/qt/${pkgver%.*}/${pkgver}/single/${_pkgfqn}.tar.xz"
-        'assistant.desktop' 'designer.desktop' 'linguist.desktop' 'qdbusviewer.desktop' 'glib-2.43.patch' 'qlockfile-deadlock.patch')
+        'assistant.desktop' 'designer.desktop' 'linguist.desktop' 'qdbusviewer.desktop' 'glib-2.43.patch' 'qlockfile-deadlock.patch' 'qnam-corruption.patch')
 md5sums=('7afb5f9235d8d42b5b6e832442a32a5d'
          'b2897dd6a2967bccf8f10e397aafee55'
          '76e150b20178d2d43801f7367232e5f7'
@@ -49,7 +49,8 @@
          '188da8f4c87316e730ebf1c6217bf5a0'
          '322b419b16c75d4de0ee7ad0a246caa1'
          'bf756a3061e1b30b28df85dcf0c90df3'
-         '30d219401f77e536d215addc420b634c')
+         '30d219401f77e536d215addc420b634c'
+         'd0b070d6f211948ef4842b46542b9e4f')
 
 prepare() {
   cd ${_pkgfqn}
@@ -60,6 +61,9 @@
   # https://bugs.kde.org/show_bug.cgi?id=345901
   (cd qtbase; patch -p1 -i "$srcdir/qlockfile-deadlock.patch")
 
+  # https://codereview.qt-project.org/#/c/110150/
+  (cd qtbase; patch -p1 -i "$srcdir/qnam-corruption.patch")
+
   # Build qmake using Arch {C,LD}FLAGS
   # This also sets default {C,CXX,LD}FLAGS for projects built using qmake
   sed -i -e "s|^\(QMAKE_CFLAGS_RELEASE.*\)|\1 ${CFLAGS}|" \

Added: qnam-corruption.patch
===================================================================
--- qnam-corruption.patch	                        (rev 0)
+++ qnam-corruption.patch	2015-05-06 10:13:45 UTC (rev 238538)
@@ -0,0 +1,691 @@
+From cff39fba10ffc10ee4dcfdc66ff6528eb26462d3 Mon Sep 17 00:00:00 2001
+From: Markus Goetz <markus at woboq.com>
+Date: Fri, 10 Apr 2015 14:09:53 +0200
+Subject: 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 at 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()
+-- 
+cgit v0.11.0
+



More information about the arch-commits mailing list