Skip to content

Commit fa81aa6

Browse files
guruzDaniel Molkentin (ownCloud)
authored andcommitted
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 (cherry picked from commit qtbase/cff39fba10ffc10ee4dcfdc66ff6528eb26462d3) Change-Id: I9793297be6cf3edfb75b65ba03b65f7a133ef194 Reviewed-by: Richard J. Moore <[email protected]>
1 parent ec70186 commit fa81aa6

File tree

7 files changed

+280
-29
lines changed

7 files changed

+280
-29
lines changed

src/corelib/io/qnoncontiguousbytedevice.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ qint64 QNonContiguousByteDeviceByteArrayImpl::size()
245245
return byteArray->size();
246246
}
247247

248+
qint64 QNonContiguousByteDeviceByteArrayImpl::pos()
249+
{
250+
return currentPosition;
251+
}
252+
253+
248254
QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb)
249255
: QNonContiguousByteDevice(), currentPosition(0)
250256
{
@@ -296,6 +302,11 @@ qint64 QNonContiguousByteDeviceRingBufferImpl::size()
296302
return ringBuffer->size();
297303
}
298304

305+
qint64 QNonContiguousByteDeviceRingBufferImpl::pos()
306+
{
307+
return currentPosition;
308+
}
309+
299310
QNonContiguousByteDeviceIoDeviceImpl::QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d)
300311
: QNonContiguousByteDevice(),
301312
currentReadBuffer(0), currentReadBufferSize(16*1024),
@@ -415,6 +426,14 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::size()
415426
return device->size() - initialPosition;
416427
}
417428

429+
qint64 QNonContiguousByteDeviceIoDeviceImpl::pos()
430+
{
431+
if (device->isSequential())
432+
return -1;
433+
434+
return device->pos();
435+
}
436+
418437
QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0)
419438
{
420439
byteDevice = bd;

src/corelib/io/qnoncontiguousbytedevice_p.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class Q_CORE_EXPORT QNonContiguousByteDevice : public QObject
6969
virtual const char* readPointer(qint64 maximumLength, qint64 &len) = 0;
7070
virtual bool advanceReadPointer(qint64 amount) = 0;
7171
virtual bool atEnd() = 0;
72+
virtual qint64 pos() { return -1; }
7273
virtual bool reset() = 0;
7374
void disableReset();
7475
bool isResetDisabled() { return resetDisabled; }
@@ -108,6 +109,7 @@ class QNonContiguousByteDeviceByteArrayImpl : public QNonContiguousByteDevice
108109
bool atEnd();
109110
bool reset();
110111
qint64 size();
112+
qint64 pos();
111113
protected:
112114
QByteArray* byteArray;
113115
qint64 currentPosition;
@@ -123,6 +125,7 @@ class QNonContiguousByteDeviceRingBufferImpl : public QNonContiguousByteDevice
123125
bool atEnd();
124126
bool reset();
125127
qint64 size();
128+
qint64 pos();
126129
protected:
127130
QSharedPointer<QRingBuffer> ringBuffer;
128131
qint64 currentPosition;
@@ -140,6 +143,7 @@ class QNonContiguousByteDeviceIoDeviceImpl : public QNonContiguousByteDevice
140143
bool atEnd();
141144
bool reset();
142145
qint64 size();
146+
qint64 pos();
143147
protected:
144148
QIODevice* device;
145149
QByteArray* currentReadBuffer;

src/network/access/qhttpnetworkconnectionchannel.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,19 @@ void QHttpNetworkConnectionChannel::init()
107107
socket->setProxy(QNetworkProxy::NoProxy);
108108
#endif
109109

110+
// We want all signals (except the interactive ones) be connected as QueuedConnection
111+
// because else we're falling into cases where we recurse back into the socket code
112+
// and mess up the state. Always going to the event loop (and expecting that when reading/writing)
113+
// is safer.
110114
QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
111115
this, SLOT(_q_bytesWritten(qint64)),
112-
Qt::DirectConnection);
116+
Qt::QueuedConnection);
113117
QObject::connect(socket, SIGNAL(connected()),
114118
this, SLOT(_q_connected()),
115-
Qt::DirectConnection);
119+
Qt::QueuedConnection);
116120
QObject::connect(socket, SIGNAL(readyRead()),
117121
this, SLOT(_q_readyRead()),
118-
Qt::DirectConnection);
122+
Qt::QueuedConnection);
119123

120124
// The disconnected() and error() signals may already come
121125
// while calling connectToHost().
@@ -144,13 +148,13 @@ void QHttpNetworkConnectionChannel::init()
144148
// won't be a sslSocket if encrypt is false
145149
QObject::connect(sslSocket, SIGNAL(encrypted()),
146150
this, SLOT(_q_encrypted()),
147-
Qt::DirectConnection);
151+
Qt::QueuedConnection);
148152
QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
149153
this, SLOT(_q_sslErrors(QList<QSslError>)),
150154
Qt::DirectConnection);
151155
QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
152156
this, SLOT(_q_encryptedBytesWritten(qint64)),
153-
Qt::DirectConnection);
157+
Qt::QueuedConnection);
154158
}
155159
#endif
156160
}
@@ -163,7 +167,8 @@ void QHttpNetworkConnectionChannel::close()
163167
else
164168
state = QHttpNetworkConnectionChannel::ClosingState;
165169

166-
socket->close();
170+
if (socket)
171+
socket->close();
167172
}
168173

169174

@@ -280,6 +285,14 @@ bool QHttpNetworkConnectionChannel::sendRequest()
280285
// nothing to read currently, break the loop
281286
break;
282287
} else {
288+
if (written != uploadByteDevice->pos()) {
289+
// Sanity check. This was useful in tracking down an upload corruption.
290+
qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << written << "but read device is at" << uploadByteDevice->pos();
291+
Q_ASSERT(written == uploadByteDevice->pos());
292+
connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
293+
return false;
294+
}
295+
283296
qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
284297
if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
285298
// socket broke down
@@ -639,6 +652,14 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
639652
}
640653
return false;
641654
}
655+
656+
// This code path for ConnectedState
657+
if (pendingEncrypt) {
658+
// Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
659+
// and corrupt the things sent to the server.
660+
return false;
661+
}
662+
642663
return true;
643664
}
644665

@@ -980,6 +1001,13 @@ void QHttpNetworkConnectionChannel::_q_readyRead()
9801001
void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
9811002
{
9821003
Q_UNUSED(bytes);
1004+
1005+
if (ssl) {
1006+
// In the SSL case we want to send data from encryptedBytesWritten signal since that one
1007+
// is the one going down to the actual network, not only into some SSL buffer.
1008+
return;
1009+
}
1010+
9831011
// bytes have been written to the socket. write even more of them :)
9841012
if (isSocketWriting())
9851013
sendRequest();
@@ -1029,7 +1057,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
10291057

10301058
// ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
10311059
//channels[i].reconnectAttempts = 2;
1032-
if (!pendingEncrypt) {
1060+
if (!pendingEncrypt && !ssl) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
10331061
state = QHttpNetworkConnectionChannel::IdleState;
10341062
if (!reply)
10351063
connection->d_func()->dequeueRequest(socket);
@@ -1157,7 +1185,10 @@ void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetwor
11571185

11581186
void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
11591187
{
1160-
sendRequest();
1188+
if (reply && state == QHttpNetworkConnectionChannel::WritingState) {
1189+
// There might be timing issues, make sure to only send upload data if really in that state
1190+
sendRequest();
1191+
}
11611192
}
11621193

11631194
#ifndef QT_NO_OPENSSL

src/network/access/qhttpthreaddelegate_p.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,21 +190,28 @@ class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevic
190190
QByteArray m_dataArray;
191191
bool m_atEnd;
192192
qint64 m_size;
193+
qint64 m_pos; // to match calls of haveDataSlot with the expected position
193194
public:
194195
QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
195196
: QNonContiguousByteDevice(),
196197
wantDataPending(false),
197198
m_amount(0),
198199
m_data(0),
199200
m_atEnd(aE),
200-
m_size(s)
201+
m_size(s),
202+
m_pos(0)
201203
{
202204
}
203205

204206
~QNonContiguousByteDeviceThreadForwardImpl()
205207
{
206208
}
207209

210+
qint64 pos()
211+
{
212+
return m_pos;
213+
}
214+
208215
const char* readPointer(qint64 maximumLength, qint64 &len)
209216
{
210217
if (m_amount > 0) {
@@ -232,11 +239,10 @@ class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevic
232239

233240
m_amount -= a;
234241
m_data += a;
242+
m_pos += a;
235243

236-
// To main thread to inform about our state
237-
emit processedData(a);
238-
239-
// FIXME possible optimization, already ask user thread for some data
244+
// To main thread to inform about our state. The m_pos will be sent as a sanity check.
245+
emit processedData(m_pos, a);
240246

241247
return true;
242248
}
@@ -253,10 +259,21 @@ class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevic
253259
{
254260
m_amount = 0;
255261
m_data = 0;
262+
m_dataArray.clear();
263+
264+
if (wantDataPending) {
265+
// had requested the user thread to send some data (only 1 in-flight at any moment)
266+
wantDataPending = false;
267+
}
256268

257269
// Communicate as BlockingQueuedConnection
258270
bool b = false;
259271
emit resetData(&b);
272+
if (b) {
273+
// the reset succeeded, we're at pos 0 again
274+
m_pos = 0;
275+
// the HTTP code will anyway abort the request if !b.
276+
}
260277
return b;
261278
}
262279

@@ -267,8 +284,13 @@ class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevic
267284

268285
public slots:
269286
// From user thread:
270-
void haveDataSlot(QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
287+
void haveDataSlot(qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
271288
{
289+
if (pos != m_pos) {
290+
// Sometimes when re-sending a request in the qhttpnetwork* layer there is a pending haveData from the
291+
// user thread on the way to us. We need to ignore it since it is the data for the wrong(later) chunk.
292+
return;
293+
}
272294
wantDataPending = false;
273295

274296
m_dataArray = dataArray;
@@ -288,7 +310,7 @@ public slots:
288310

289311
// to main thread:
290312
void wantData(qint64);
291-
void processedData(qint64);
313+
void processedData(qint64 pos, qint64 amount);
292314
void resetData(bool *b);
293315
};
294316

src/network/access/qnetworkaccesshttpbackend.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
193193
QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
194194
: QNetworkAccessBackend()
195195
, statusCode(0)
196+
, uploadByteDevicePosition(false)
196197
, pendingDownloadDataEmissions(new QAtomicInt())
197198
, pendingDownloadProgressEmissions(new QAtomicInt())
198199
, loadingFromCache(false)
@@ -610,18 +611,18 @@ void QNetworkAccessHttpBackend::postRequest()
610611
forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
611612
delegate->httpRequest.setUploadByteDevice(forwardUploadDevice);
612613

613-
// From main thread to user thread:
614-
QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)),
615-
forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection);
614+
// From user thread to http thread:
615+
QObject::connect(this, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
616+
forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
616617
QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
617618
forwardUploadDevice, SIGNAL(readyRead()),
618619
Qt::QueuedConnection);
619620

620621
// From http thread to user thread:
621622
QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
622623
this, SLOT(wantUploadDataSlot(qint64)));
623-
QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
624-
this, SLOT(sentUploadDataSlot(qint64)));
624+
QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64, qint64)),
625+
this, SLOT(sentUploadDataSlot(qint64,qint64)));
625626
connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
626627
this, SLOT(resetUploadDataSlot(bool*)),
627628
Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
@@ -915,12 +916,21 @@ void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfigura
915916
void QNetworkAccessHttpBackend::resetUploadDataSlot(bool *r)
916917
{
917918
*r = uploadByteDevice->reset();
919+
if (*r) {
920+
// reset our own position which is used for the inter-thread communication
921+
uploadByteDevicePosition = 0;
922+
}
918923
}
919924

920925
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
921-
void QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 amount)
926+
void QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 pos, qint64 amount)
922927
{
928+
if (uploadByteDevicePosition + amount != pos) {
929+
// Sanity check, should not happen.
930+
error(QNetworkReply::UnknownNetworkError, "");
931+
}
923932
uploadByteDevice->advanceReadPointer(amount);
933+
uploadByteDevicePosition += amount;
924934
}
925935

926936
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
@@ -933,7 +943,7 @@ void QNetworkAccessHttpBackend::wantUploadDataSlot(qint64 maxSize)
933943
QByteArray dataArray(data, currentUploadDataLength);
934944

935945
// Communicate back to HTTP thread
936-
emit haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
946+
emit haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
937947
}
938948

939949
/*

src/network/access/qnetworkaccesshttpbackend_p.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class QNetworkAccessHttpBackend: public QNetworkAccessBackend
112112

113113
void startHttpRequestSynchronously();
114114

115-
void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
115+
void haveUploadData(const qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
116116
private slots:
117117
// From HTTP thread:
118118
void replyDownloadData(QByteArray);
@@ -129,13 +129,14 @@ private slots:
129129
// From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
130130
void resetUploadDataSlot(bool *r);
131131
void wantUploadDataSlot(qint64);
132-
void sentUploadDataSlot(qint64);
132+
void sentUploadDataSlot(qint64, qint64);
133133

134134
bool sendCacheContents(const QNetworkCacheMetaData &metaData);
135135

136136
private:
137137
QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread
138138
int statusCode;
139+
qint64 uploadByteDevicePosition;
139140
QString reasonPhrase;
140141
// Will be increased by HTTP thread:
141142
QSharedPointer<QAtomicInt> pendingDownloadDataEmissions;

0 commit comments

Comments
 (0)