diff options
-rw-r--r-- | src/corelib/io/qnoncontiguousbytedevice.cpp | 19 | ||||
-rw-r--r-- | src/corelib/io/qnoncontiguousbytedevice_p.h | 4 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 47 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate_p.h | 36 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesshttpbackend.cpp | 24 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesshttpbackend_p.h | 5 | ||||
-rw-r--r-- | tests/auto/qnetworkreply/tst_qnetworkreply.cpp | 174 |
7 files changed, 280 insertions, 29 deletions
diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp index bf58eee..1a0591e 100644 --- a/src/corelib/io/qnoncontiguousbytedevice.cpp +++ b/src/corelib/io/qnoncontiguousbytedevice.cpp @@ -245,6 +245,12 @@ qint64 QNonContiguousByteDeviceByteArrayImpl::size() return byteArray->size(); } +qint64 QNonContiguousByteDeviceByteArrayImpl::pos() +{ + return currentPosition; +} + + QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb) : QNonContiguousByteDevice(), currentPosition(0) { @@ -296,6 +302,11 @@ qint64 QNonContiguousByteDeviceRingBufferImpl::size() return ringBuffer->size(); } +qint64 QNonContiguousByteDeviceRingBufferImpl::pos() +{ + return currentPosition; +} + QNonContiguousByteDeviceIoDeviceImpl::QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d) : QNonContiguousByteDevice(), currentReadBuffer(0), currentReadBufferSize(16*1024), @@ -415,6 +426,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 b6966eb..d1a99a1 100644 --- a/src/corelib/io/qnoncontiguousbytedevice_p.h +++ b/src/corelib/io/qnoncontiguousbytedevice_p.h @@ -69,6 +69,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; } @@ -108,6 +109,7 @@ public: bool atEnd(); bool reset(); qint64 size(); + qint64 pos(); protected: QByteArray* byteArray; qint64 currentPosition; @@ -123,6 +125,7 @@ public: bool atEnd(); bool reset(); qint64 size(); + qint64 pos(); protected: QSharedPointer<QRingBuffer> ringBuffer; qint64 currentPosition; @@ -140,6 +143,7 @@ public: bool atEnd(); bool reset(); qint64 size(); + qint64 pos(); protected: QIODevice* device; QByteArray* currentReadBuffer; diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 550e090..db2f712 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -107,15 +107,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(). @@ -144,13 +148,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); } #endif } @@ -163,7 +167,8 @@ void QHttpNetworkConnectionChannel::close() else state = QHttpNetworkConnectionChannel::ClosingState; - socket->close(); + if (socket) + socket->close(); } @@ -280,6 +285,14 @@ bool QHttpNetworkConnectionChannel::sendRequest() // nothing to read currently, break the loop break; } else { + if (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" << written << "but read device is at" << uploadByteDevice->pos(); + Q_ASSERT(written == uploadByteDevice->pos()); + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); + return false; + } + qint64 currentWriteSize = socket->write(readPointer, currentReadSize); if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { // socket broke down @@ -639,6 +652,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; } @@ -980,6 +1001,13 @@ void QHttpNetworkConnectionChannel::_q_readyRead() 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(); @@ -1029,7 +1057,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 (!pendingEncrypt && !ssl) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState state = QHttpNetworkConnectionChannel::IdleState; if (!reply) connection->d_func()->dequeueRequest(socket); @@ -1157,7 +1185,10 @@ void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetwor void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead() { - sendRequest(); + if (reply && state == QHttpNetworkConnectionChannel::WritingState) { + // There might be timing issues, make sure to only send upload data if really in that state + sendRequest(); + } } #ifndef QT_NO_OPENSSL diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index 7648325..9dd0deb 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -190,6 +190,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(), @@ -197,7 +198,8 @@ public: m_amount(0), m_data(0), m_atEnd(aE), - m_size(s) + m_size(s), + m_pos(0) { } @@ -205,6 +207,11 @@ public: { } + qint64 pos() + { + return m_pos; + } + const char* readPointer(qint64 maximumLength, qint64 &len) { if (m_amount > 0) { @@ -232,11 +239,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; } @@ -253,10 +259,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; } @@ -267,8 +284,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; @@ -288,7 +310,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/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index cc67258..fe2f627 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -193,6 +193,7 @@ QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op, QNetworkAccessHttpBackend::QNetworkAccessHttpBackend() : QNetworkAccessBackend() , statusCode(0) + , uploadByteDevicePosition(false) , pendingDownloadDataEmissions(new QAtomicInt()) , pendingDownloadProgressEmissions(new QAtomicInt()) , loadingFromCache(false) @@ -610,9 +611,9 @@ void QNetworkAccessHttpBackend::postRequest() forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread() delegate->httpRequest.setUploadByteDevice(forwardUploadDevice); - // From main thread to user thread: - QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)), - forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection); + // From user thread to http thread: + QObject::connect(this, 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); @@ -620,8 +621,8 @@ void QNetworkAccessHttpBackend::postRequest() // From http thread to user thread: QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)), this, SLOT(wantUploadDataSlot(qint64))); - QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)), - this, SLOT(sentUploadDataSlot(qint64))); + QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64, qint64)), + this, SLOT(sentUploadDataSlot(qint64,qint64))); connect(forwardUploadDevice, SIGNAL(resetData(bool*)), this, SLOT(resetUploadDataSlot(bool*)), Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued! @@ -915,12 +916,21 @@ void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfigura void QNetworkAccessHttpBackend::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 QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 amount) +void QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 pos, qint64 amount) { + if (uploadByteDevicePosition + amount != pos) { + // Sanity check, should not happen. + error(QNetworkReply::UnknownNetworkError, ""); + } uploadByteDevice->advanceReadPointer(amount); + uploadByteDevicePosition += amount; } // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread @@ -933,7 +943,7 @@ void QNetworkAccessHttpBackend::wantUploadDataSlot(qint64 maxSize) QByteArray dataArray(data, currentUploadDataLength); // Communicate back to HTTP thread - emit haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size()); + emit haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size()); } /* diff --git a/src/network/access/qnetworkaccesshttpbackend_p.h b/src/network/access/qnetworkaccesshttpbackend_p.h index 13519c6..b4ed67c 100644 --- a/src/network/access/qnetworkaccesshttpbackend_p.h +++ b/src/network/access/qnetworkaccesshttpbackend_p.h @@ -112,7 +112,7 @@ signals: void startHttpRequestSynchronously(); - void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize); + void haveUploadData(const qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize); private slots: // From HTTP thread: void replyDownloadData(QByteArray); @@ -129,13 +129,14 @@ private slots: // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread: void resetUploadDataSlot(bool *r); void wantUploadDataSlot(qint64); - void sentUploadDataSlot(qint64); + void sentUploadDataSlot(qint64, qint64); bool sendCacheContents(const QNetworkCacheMetaData &metaData); private: QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread int statusCode; + qint64 uploadByteDevicePosition; QString reasonPhrase; // Will be increased by HTTP thread: QSharedPointer<QAtomicInt> pendingDownloadDataEmissions; diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp index 1362d01..6c4436f 100644 --- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp @@ -394,6 +394,10 @@ private Q_SLOTS: void closeDuringDownload_data(); void closeDuringDownload(); +#ifndef QT_NO_SSL + void putWithServerClosingConnectionImmediately(); +#endif + // NOTE: This test must be last! void parentingRepliesToTheApp(); }; @@ -4411,12 +4415,16 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag() class SslServer : public QTcpServer { Q_OBJECT public: - SslServer() : socket(0) {}; + SslServer() : socket(0), m_ssl(true) {}; void incomingConnection(int socketDescriptor) { QSslSocket *serverSocket = new QSslSocket; serverSocket->setParent(this); if (serverSocket->setSocketDescriptor(socketDescriptor)) { + if (!m_ssl) { + emit newPlainConnection(serverSocket); + return; + } connect(serverSocket, SIGNAL(encrypted()), this, SLOT(encryptedSlot())); serverSocket->setProtocol(QSsl::AnyProtocol); connect(serverSocket, SIGNAL(sslErrors(const QList<QSslError>&)), serverSocket, SLOT(ignoreSslErrors())); @@ -4428,14 +4436,16 @@ public: } } signals: - void newEncryptedConnection(); + void newEncryptedConnection(QSslSocket *s); + void newPlainConnection(QSslSocket *s); public slots: void encryptedSlot() { socket = (QSslSocket*) sender(); - emit newEncryptedConnection(); + emit newEncryptedConnection(socket); } public: QSslSocket *socket; + bool m_ssl; }; // very similar to ioPostToHttpUploadProgress but for SSL @@ -4454,7 +4464,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress() request.setRawHeader("Content-Type", "application/octet-stream"); QNetworkReplyPtr reply = manager.post(request, &sourceFile); QSignalSpy spy(reply, 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(const QList<QSslError>&)), reply, SLOT(ignoreSslErrors())); // get the request started and the incoming socket connected @@ -4462,7 +4472,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); @@ -6917,6 +6927,160 @@ void tst_QNetworkReply::closeDuringDownload() QTest::qWait(1000); //cancelling ftp takes some time, this avoids a warning caused by test's cleanup() destroying the connection cache before the abort is finished } +#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() { |