From cff39fba10ffc10ee4dcfdc66ff6528eb26462d3 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Fri, 10 Apr 2015 14:09:53 +0200 Subject: [PATCH 1/8] 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 --- 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 +- .../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 11510a8397..760ca3d933 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 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 c05ae11b0f..4d7b7b043e 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 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 9f63280bf8..49c6793b1f 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)), this, SLOT(_q_sslErrors(QList)), 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 692c0e6a94..231fe11135 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -83,6 +83,8 @@ typedef QPair 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 28e10f751e..3357948519 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 16610828cb..b553409391 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 4ce7303dbb..974a101e9c 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 77d9c5a368..1940922f6e 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 uploadByteDevice; + qint64 uploadByteDevicePosition; bool uploadDeviceChoking; // if we couldn't readPointer() any data at the moment QIODevice *outgoingData; QSharedPointer 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 3ccedf6248..d2edf67a82 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)), 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)), 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)), 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=" < 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() From 1b4ce37371252d54ddd08c0e5325d0385217ef26 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 27 Apr 2015 17:09:31 +0200 Subject: [PATCH 2/8] Blacklist task258920_mouseBorder on Mac The test keeps failing, perfectly when run singly btw. However the approach of sending mouse move events is inherently fragile due to the use of QCursor::setPos and the expectation that that produces the correct sequence of mouse move events. Change-Id: I12ba1afcfd3fab965b8f93d5def48b435fd2ff33 Reviewed-by: Andy Shaw --- tests/auto/widgets/widgets/qmenu/BLACKLIST | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/auto/widgets/widgets/qmenu/BLACKLIST diff --git a/tests/auto/widgets/widgets/qmenu/BLACKLIST b/tests/auto/widgets/widgets/qmenu/BLACKLIST new file mode 100644 index 0000000000..de49d5ff45 --- /dev/null +++ b/tests/auto/widgets/widgets/qmenu/BLACKLIST @@ -0,0 +1,2 @@ +[task258920_mouseBorder] +osx From d0eba497c1c34d35ddda2b5d44902ebd636d4f9e Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 29 Apr 2015 09:05:06 +0200 Subject: [PATCH 3/8] configure: Change default of CFG_REDUCE_RELOCATIONS to "no". Suppress QMAKE_LFLAGS_BSYMBOLIC_FUNC (-Bsymbolic-functions for g++) by default since it causes crashes with gcc 5.X. Since applications compiled with gcc 5.X might run against Qt compiled with gcc 4.9.X and CLANG might also be affected, turn it off by default. Task-number: QTBUG-45755 Change-Id: I5704c3156db6b6f74e1c14576e5d02bcbd3082a4 Reviewed-by: Simon Hausmann Reviewed-by: BogDan Vatra --- configure | 2 +- tools/configure/configureapp.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 6ce6753e3f..15a75bbccd 100755 --- a/configure +++ b/configure @@ -674,7 +674,7 @@ CFG_SSE4_1=auto CFG_SSE4_2=auto CFG_AVX=auto CFG_AVX2=auto -CFG_REDUCE_RELOCATIONS=auto +CFG_REDUCE_RELOCATIONS=no CFG_ACCESSIBILITY=auto CFG_ACCESSIBILITY_ATSPI_BRIDGE=no # will be enabled depending on dbus and accessibility being enabled CFG_NEON=auto diff --git a/tools/configure/configureapp.cpp b/tools/configure/configureapp.cpp index 6e9d4aa80b..db0791b75c 100644 --- a/tools/configure/configureapp.cpp +++ b/tools/configure/configureapp.cpp @@ -1696,7 +1696,7 @@ void Configure::applySpecSpecifics() dictionary[ "LARGE_FILE" ] = "no"; dictionary[ "ANGLE" ] = "no"; dictionary[ "DYNAMICGL" ] = "no"; - dictionary[ "REDUCE_RELOCATIONS" ] = "yes"; + dictionary[ "REDUCE_RELOCATIONS" ] = "no"; dictionary[ "QT_GETIFADDRS" ] = "no"; dictionary[ "QT_XKBCOMMON" ] = "no"; dictionary["ANDROID_STYLE_ASSETS"] = "yes"; From 518f886b6128331ce47932edd637471d58d0d877 Mon Sep 17 00:00:00 2001 From: Rainer Keller Date: Fri, 17 Apr 2015 10:43:19 +0200 Subject: [PATCH 4/8] Revert "Rotate images according to Exif orientation" Due to a behavior change. This reverts commit 9157087334186ff3ef811f2ec234a3bf5d4a4889. This reverts commit 16c32c6dfbca03a46d1a2bb87b6c1c365e6179d5. Task-number: QTBUG-37946 Task-number: QTBUG-45552 Task-number: QTBUG-43563 Change-Id: Idf8df7d8f22465e8f6b51acb68993ac97208b184 Reviewed-by: Konstantin Ritt Reviewed-by: Gunnar Sletta --- src/gui/image/qjpeghandler.cpp | 150 +----------------- .../images/jpeg_exif_orientation_value_1.jpg | Bin 910 -> 0 bytes .../images/jpeg_exif_orientation_value_2.jpg | Bin 910 -> 0 bytes .../images/jpeg_exif_orientation_value_3.jpg | Bin 988 -> 0 bytes .../images/jpeg_exif_orientation_value_4.jpg | Bin 995 -> 0 bytes .../images/jpeg_exif_orientation_value_5.jpg | Bin 912 -> 0 bytes .../images/jpeg_exif_orientation_value_6.jpg | Bin 911 -> 0 bytes ...jpeg_exif_orientation_value_6_motorola.jpg | Bin 911 -> 0 bytes .../images/jpeg_exif_orientation_value_7.jpg | Bin 987 -> 0 bytes .../images/jpeg_exif_orientation_value_8.jpg | Bin 991 -> 0 bytes tests/auto/gui/image/qimage/tst_qimage.cpp | 33 ---- 11 files changed, 1 insertion(+), 182 deletions(-) delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6_motorola.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg delete mode 100644 tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg diff --git a/src/gui/image/qjpeghandler.cpp b/src/gui/image/qjpeghandler.cpp index 9cf9947b6c..14c8b4c0f4 100644 --- a/src/gui/image/qjpeghandler.cpp +++ b/src/gui/image/qjpeghandler.cpp @@ -726,7 +726,7 @@ public: }; QJpegHandlerPrivate(QJpegHandler *qq) - : quality(75), exifOrientation(1), iod_src(0), state(Ready), q(qq) + : quality(75), iod_src(0), state(Ready), q(qq) {} ~QJpegHandlerPrivate() @@ -741,10 +741,8 @@ public: bool readJpegHeader(QIODevice*); bool read(QImage *image); - void applyExifOrientation(QImage *image); int quality; - int exifOrientation; QVariant size; QImage::Format format; QSize scaledSize; @@ -762,97 +760,6 @@ public: QJpegHandler *q; }; -static bool readExifHeader(QDataStream &stream) -{ - char prefix[6]; - if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix)) - return false; - if (prefix[0] != 'E' || prefix[1] != 'x' || prefix[2] != 'i' || prefix[3] != 'f' || prefix[4] != 0 || prefix[5] != 0) - return false; - return true; -} - -/* - * Returns -1 on error - * Returns 0 if no Exif orientation was found - * Returns 1 orientation is horizontal (normal) - * Returns 2 mirror horizontal - * Returns 3 rotate 180 - * Returns 4 mirror vertical - * Returns 5 mirror horizontal and rotate 270 CCW - * Returns 6 rotate 90 CW - * Returns 7 mirror horizontal and rotate 90 CW - * Returns 8 rotate 270 CW - */ -static int getExifOrientation(QByteArray &exifData) -{ - QDataStream stream(&exifData, QIODevice::ReadOnly); - - if (!readExifHeader(stream)) - return -1; - - quint16 val; - quint32 offset; - - // read byte order marker - stream >> val; - if (val == 0x4949) // 'II' == Intel - stream.setByteOrder(QDataStream::LittleEndian); - else if (val == 0x4d4d) // 'MM' == Motorola - stream.setByteOrder(QDataStream::BigEndian); - else - return -1; // unknown byte order - - // read size - stream >> val; - if (val != 0x2a) - return -1; - - stream >> offset; - // we have already used 8 bytes of TIFF header - offset -= 8; - - // read IFD - while (!stream.atEnd()) { - quint16 numEntries; - - // skip offset bytes to get the next IFD - if (stream.skipRawData(offset) != (qint32)offset) - return -1; - - stream >> numEntries; - - for (;numEntries > 0; --numEntries) { - quint16 tag; - quint16 type; - quint32 components; - quint16 value; - quint16 dummy; - - stream >> tag >> type >> components >> value >> dummy; - if (tag == 0x0112) { // Tag Exif.Image.Orientation - if (components !=1) - return -1; - if (type != 3) // we are expecting it to be an unsigned short - return -1; - if (value < 1 || value > 8) // check for valid range - return -1; - - // It is possible to include the orientation multiple times. - // Right now the first value is returned. - return value; - } - } - - // read offset to next IFD - stream >> offset; - if (offset == 0) // this is the last IFD - break; - } - - // No Exif orientation was found - return 0; -} /*! \internal */ @@ -872,7 +779,6 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) if (!setjmp(err.setjmp_buffer)) { jpeg_save_markers(&info, JPEG_COM, 0xFFFF); - jpeg_save_markers(&info, JPEG_APP0+1, 0xFFFF); // Exif uses APP1 marker (void) jpeg_read_header(&info, TRUE); @@ -884,8 +790,6 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) format = QImage::Format_Invalid; read_jpeg_format(format, &info); - QByteArray exifData; - for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) { if (marker->marker == JPEG_COM) { QString key, value; @@ -903,18 +807,9 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) description += key + QLatin1String(": ") + value.simplified(); readTexts.append(key); readTexts.append(value); - } else if (marker->marker == JPEG_APP0+1) { - exifData.append((const char*)marker->data, marker->data_length); } } - if (exifData.size()) { - // Exif data present - int orientation = getExifOrientation(exifData); - if (orientation > 0) - exifOrientation = orientation; - } - state = ReadHeader; return true; } @@ -928,48 +823,6 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) return true; } -void QJpegHandlerPrivate::applyExifOrientation(QImage *image) -{ - // This is not an optimized implementation, but easiest to maintain - QTransform transform; - - switch (exifOrientation) { - case 1: // normal - break; - case 2: // mirror horizontal - *image = image->mirrored(true, false); - break; - case 3: // rotate 180 - transform.rotate(180); - *image = image->transformed(transform); - break; - case 4: // mirror vertical - *image = image->mirrored(false, true); - break; - case 5: // mirror horizontal and rotate 270 CCW - *image = image->mirrored(true, false); - transform.rotate(270); - *image = image->transformed(transform); - break; - case 6: // rotate 90 CW - transform.rotate(90); - *image = image->transformed(transform); - break; - case 7: // mirror horizontal and rotate 90 CW - *image = image->mirrored(true, false); - transform.rotate(90); - *image = image->transformed(transform); - break; - case 8: // rotate 270 CW - transform.rotate(-90); - *image = image->transformed(transform); - break; - default: - qWarning("This should never happen"); - } - exifOrientation = 1; -} - bool QJpegHandlerPrivate::read(QImage *image) { if(state == Ready) @@ -981,7 +834,6 @@ bool QJpegHandlerPrivate::read(QImage *image) if (success) { for (int i = 0; i < readTexts.size()-1; i+=2) image->setText(readTexts.at(i), readTexts.at(i+1)); - applyExifOrientation(image); state = Ready; return true; diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg deleted file mode 100644 index aaa4ac4e10f40e6a32560aef43b553e804f0a973..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 910 zcmex=@c%Z0 zGe`$T0AvV727omGKg1x&!NA49#mp$kz$D1XEXer(2!j~dJ0O?B06POGP+XFcfr%O9 z5d>gkVrF6ee~Wz(=SUhmhk>$zLYFZ;^Qy*Xp@!!9$`S)MazOgeKTC9?CS%G6(;wklud z*8?q*2U*0x$bzs5=n+PC239sf0U<@OWlSK;7+4uJ9kip4IA${*U~Ndh;=oeN(b1R2 z@hmMZea)X&em+k+RA)(4vY%7x(!8uvc=GAoUzsV>O1si$WnQk(s=Tm(RaJ{IYMK&* zlJd;YAP)k=0~~Ac5CH`s8>4^_G(_0!nZoV6S4G?jw>(++tL*7>F_y&N`@^?SQH?Px zl-_b%H1pZ#&oLcUcHVbpv`%p-4Y|*15}D6^JUlf&*Yvu-ao4ZRrO$k8MQk?;$9y;G z?9JxrZF5PBo4)V&ccArRpy)sajPSUyR}{TIo!P$icWIex`Wm+NuekhyjsnLlSeHNv zKhOwKL0|--0H_I|$Pw^faBSzpY#Eny2S?MYosLecR)a!Y45$ZH6C(q}T2LSh*vWgX zm{6$Bz$E=-){a-n<}c5K>=8iI!oa8i4I`kI;#YER*`^&=LApid+04N35 AMF0Q* diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg deleted file mode 100644 index a61d2723d7cf43ee9244f4dcb6b26d848574a480..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 910 zcmex=>Y?(AtXBkCs161k%1ZL zPh`N##>Ddf76T76BLkBFvjEVym*4elSJ@emF31klAua&21z7_dGm8LF0|zL)SedF# zo@*8=m&VTrs#O#On$Ez$$_iJ`$i%|Tz{SEB3f?O;B>8U}nWRec5HX`RX%{8Fcm}D@-|a=DWw4GgFtixb9vO zy&D9+-v?PK0JH-oNSIleSlJi_grR}LX3uoocJk)8ewn#!;^x^-oatBB@TSf?)zrl7 zwq{3lG+Wgvv1RM$h+KPmeR-_QEeGED?}TP9+ho}mnX<&1N3Lzfam%xI6P6iztmz1v zGHJ>Q&(oXd{J!bE_kAtUb}>+NAbbmCF*3ko!(NeDxnkYrBt{Ww$9lsm#Z{+bfNlcE zEI5b+82Eukhzp{}C@|$f%mBrW0B0$Sj0ocb1qP?Yea_K6W=6T^>Veusf!a_kW?+P9 z0tK>w+XE(xbM9d$y!aVjFdmu1nGe(->>Kn<)5C;n#;;;xvy%_1)O>qVvO I_5W`I093Zmxc~qF diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg deleted file mode 100644 index 43e56dcef70bbd6cf48fae727ea5f6cb4d63061e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 988 zcmex=>Y?(AtWa|2T)uB=t*Xf zM-YI8jf3<5Ee0N-Fp~hY0MNI=ya{gaK05#Dyz!p_s7G7?WDi0E0|N^iI|EP)2Pni? znJ#WoK4ZOk%hH{Hw{D%81J)uZ2(pEV8KI4liG>-ci&arT5Udj@WzYDhx7%IYQ0J@} z&l&$O(=0VrCV!b!H>qy&^T^Wa(f6DK>kir#p|#jj3gK zy;7L%uzKm~!`{gmWj7*cnVIRWTTvbpA8#_7L+2i!-j)>(W+%^1=el~Uy0!2a-|gOy zcTW47oXjhWO!?6%^2VU()=|SV(Gjhhx=8}lw!Y8Ya_0BC(74^pBU5+PKG54?zSW6= z!D=m!BS+g+H${%pwQN>lnqDqp8wKTn&J+d4CURT?<68h0m-dPi7AGB-R@YSu-L(G7 z!tf8QzyK4hQhfD`AE*SJLclpdpg|v$E(C#Ljbaod15)e?EZZ$GZ@s37h}Po(jj2I>+ML`zJ-dJtOEO@}jAme#}38+Ox0L{e=3UC(-NEPw4 c<;{yOihgMQw`8jpNA32x_o^1S*!{l=0P;Kok^lez diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg deleted file mode 100644 index d5d06f740922c4ecd4e49cd2aaa90a8293c4e108..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 995 zcmex={xcvIrKfhkD{O+;sc9na`y3^XP=J1+^ zhsyBqEt-{4!gl?_x0zedWloklq-><5dw)OBei?8;Az9A=w4RBTjX^{V8dhxfOn>4w zuWr=sOP-uABzfuS=Qi2t+pJq|uXSF&dj5s%jq5FcM4r-YYkQ+}mH7+1*}9u;wI@w3 zcAeJwETNv*Jn5O`xtygO!4gYm3oO;t&7LrQ%f0V2w=PSoy{k68^u~%=bNhWZb?IKt zWlWvMc6kxg>ju9ojts6*Jx;ROj+eGB)fVmOuLn9*Q~(h~kO*QFfW@Z0qTAz1j~bVG zIf)1RADP!twQtI=eA@!k`hsAf5^y460L7|611N2X2_kzJWELX>#3)ew3LHA;UEO0e zS@+P{2ZnKdKv`D2dO-0baAe^FJq8)i1JnGzZrO4=+3W;JivZY8kohnmUZda0FylhIRF3v diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg deleted file mode 100644 index 1886f3775e047d42bb5ffb2af704f84434af336b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 912 zcmex=>Y?(AtXBoCs16Rk%1ZH z4+LOhW?}n(i-89y%p|}p0QBq?f!eYRncJt1cdQ2LkQ88GLe;{;4io|D;Q)mgE7Q8Q zyt`)XR+$|B8K_)Z5M%+e21X_pW&uG(L$C&BMn-$ab-UKDn!RpWciy(%)tgRzyVSLM zRj+iG=CayGz_tXO| zk^@`B#Ka8u3eYEvZ0rJptU`)lyO=aUf)`s*e4lK1C9ersW&(hM; z*Zlc-bCWlF$~PW^vo?XQ=PY9;9#;yt+2n10?%vF2vr0NNg%>(-d4(`-)p22PaW_5= zv{nWZ90*$(m|2)u*%%oFgrFh9X3w;B>G^KeIY)!NO0VYKeEc?k{-F|yc0s8P;rX+< zGp)|Po5&!wZJ%bycU2P!#i%SB{u$vi32%4Yvo<=NyD4(+v`AYmS8E=bO%r{J#ex*Q zJiSupEVVqn>3#KUpaaAN7?3Ond!A7M78&-6O?8vx5+BOv?%nkHI%D{ER(_xqIBvnZ z1RnH*j1UB-8+fpR7(nNPjQ~ZDfcK03osW}J0%reBNVi!M5VC6Ze7u@K;VfV$@3mq= yp*ql(!jjB_8)ts$1NDdspxMK~r~r4WK=ivr$7{z#%~w>Y?(AtXBoCs16Rk%1ZH z4+LOhVP*S&i-89y%p|}p0QBtTXKedox}W7<(R^eN)FL4O)PkyqnT?4BsD}d-VysL@ zU#n}Ln7)6N2~e?|Ajo`1R)l&+CKhIPRyF}eLqV_>pjq~ep&^U`46E!#!!Iz@gvv88 zUtqrQ@#H3Nc9kL?gR?e)uJ``lR4F{!SA1G!^Vip(=4CEF8myy!SjA`Kag~@)XF~;@ z8f@x;wn>3(0|F%9FtaeS3P5aP0@<`~*ZNhn*RAT#-PRtSI(6ri(9n<#b6rg?JTlwY zzUAz?VvifwBqb*b=KVaHxV`^mw$F@X#R|z1hb|i%PjyL4i(ca5x_e3VZV>o>9%!Si z00R@ygGgb>%)-RV#>gNbC=3k|HhZScC-?SU(4DpN7ngL*J@M4^2&tpRKZ>p{vIq!> zT5vvc!`5pnIj_%jeR=(KBip3EvpG*cb6wUG7MX77beKnGvf;VnP%Wn!N7YlDrfi)DJX5To5(TfJO*H%>c!Y0P|iA8HR>O3=T{M_EY?R_qLo228Fg5NE;%kk!%Kq zv_Sq5;TMOML%SxmH|$_2RA-6-Y7r4Y)xf}@0C%c@Xw1nQUHpf8_MBRAbn6F&tM&hH F0sz;@-NOI? diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6_motorola.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6_motorola.jpg deleted file mode 100644 index 0aa164b78b986bec1177bbac2ac5a181c5423a3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 911 zcmex=q=O;=GK3-nK$`y_Vi4qD;9}rnW)x@ci+Wc+`GK@992h+82fI|nCFT%3`C z8R$hMz{JAJ_Wu?G4^Ws%fLQ?O$IH*y_QiBR%e|uc$R4OgLI9`*RSz>86AMre2Pni? znU20z*E}(O|0)xpVmU#O`HZXx^^8m`%={Et7y}qq*^7o>V5kX| zXJEd-eBtBCP2TJ(MLY&)Z312I{k^GDc(Skfw94kMuRqPpTz)iINByvh&&J~_F`v$c z3OY5|)B|mk0@(%x2=6d4voNv>Kx|?H*|cug`c<>nt?JI*)*hZZb?21O(2xytT}>`L zGTYa_$}=|x&fuY zaSKkr0!;ltBg6$!0}W_|Ak+*{>ywSyfxM$C)6-T#zP`FzE G|0V$P?A^-% diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg deleted file mode 100644 index b3dcc466a907a504db72fa83957326bfcc5aac78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmex=>Y?(AtXB|7f@V+k%1ZL zPh`Nt#=-gj76T76BLkBFvjEVyD;s9!tyXc4ynVj56{tsC0AvrU7Ip@p77kE|u`*rQ zlI|mGd9UWm*R5M;=KN;>YLOEJTEoDEWECS53o{!#1FNEdAXq0*%AWD#;@-}*X=!OH zo6RJ}RQ{?=+JDLOm*+44=aHq;qwin)TU&a!_K@zjdxA%`H=X*ieBq*1uNGf8xK-q` z+XcJb7x%6E^>*93TiJ_^cZ;d~1llSKvK7UHOw25dtPE`IP)nIWmVWYI_E(KFtxbNx z?<0Y(Rw;d!w(EDBZ1T1`YJAq@5i^R)_rJ90KM_j6b?i2+^Cvs$hk~b_e?G-1i=`)tpd?Tr{Uo zx|O;?Ip_tt&|u52V|C-?FNFmU_!wk74=@3BhzOu*U{HX&S3v5KAy>Lm Z^@`#H;nhnfX))AppL?%rfs5V$n*d6*1>67t diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg deleted file mode 100644 index 8bc390e2b9a7e8704f824eaedf0248a2181b6650..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 991 zcmex=w%gi1sIrsvPgi1oq?H+6{w2? z6lknWiBi`e^Vg|MgnsNelE88Rs7Fo^Xb}Sw6Ej>FBNGcV13Q}{tAHR_BhVf8j8Epy zylLQVV(?9Tj;l!UW0k^_PwTC1HhJ4+_k8xX3ttxD-{vthxzB6o(^;#OS{Z61Q@dlm zwq9DbC2!U0eG5a~dKehYA23ugR58y7S}F^+6lgHqu}sV?j6e@EK<#7#+4*yhKHp`t z?VXP$jtLs}qzKKNIq#+9%t>21I<}W|mxDn4{&}g-;_~Zf|NMHr^1H{j+g0u%>rQLG zn!{@v9xB7bw`f*I3ETAx-)3$-mpNJLkg}1I?*08B`yl~^6jng%nONBv1cb$)fyHLe zR36>^=hz{Kc+VL}(kI`jPEkJh^lQoCsBQBL=UctW{v!DzamBJSQK=|%r{hO&J=>V( zb2(i_@% diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index 309d3a80c5..33302edfeb 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -173,9 +173,6 @@ private slots: void invertPixelsRGB_data(); void invertPixelsRGB(); - void exifOrientation_data(); - void exifOrientation(); - void cleanupFunctions(); private: @@ -2649,36 +2646,6 @@ void tst_QImage::invertPixelsRGB() QCOMPARE(qBlue(pixel) >> 4, (255 - 96) >> 4); } -void tst_QImage::exifOrientation_data() -{ - QTest::addColumn("fileName"); - QTest::newRow("Orientation 1, Intel format") << m_prefix + "jpeg_exif_orientation_value_1.jpg"; - QTest::newRow("Orientation 2, Intel format") << m_prefix + "jpeg_exif_orientation_value_2.jpg"; - QTest::newRow("Orientation 3, Intel format") << m_prefix + "jpeg_exif_orientation_value_3.jpg"; - QTest::newRow("Orientation 4, Intel format") << m_prefix + "jpeg_exif_orientation_value_4.jpg"; - QTest::newRow("Orientation 5, Intel format") << m_prefix + "jpeg_exif_orientation_value_5.jpg"; - QTest::newRow("Orientation 6, Intel format") << m_prefix + "jpeg_exif_orientation_value_6.jpg"; - QTest::newRow("Orientation 6, Motorola format") << m_prefix + "jpeg_exif_orientation_value_6_motorola.jpg"; - QTest::newRow("Orientation 7, Intel format") << m_prefix + "jpeg_exif_orientation_value_7.jpg"; - QTest::newRow("Orientation 8, Intel format") << m_prefix + "jpeg_exif_orientation_value_8.jpg"; -} - -void tst_QImage::exifOrientation() -{ - QFETCH(QString, fileName); - - QImage img; - QRgb px; - - QVERIFY(img.load(fileName)); - - px = img.pixel(0, 0); - QVERIFY(qRed(px) > 250 && qGreen(px) < 5 && qBlue(px) < 5); - - px = img.pixel(img.width() - 1, 0); - QVERIFY(qRed(px) < 5 && qGreen(px) < 5 && qBlue(px) > 250); -} - static void cleanupFunction(void* info) { bool *called = static_cast(info); From 225270978786a19eccd60fee0097d866ec0a9d78 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 20 Apr 2015 10:35:37 -0700 Subject: [PATCH 5/8] Add the changelog for 5.4.2 Change-Id: I27eaacb532114dd188c4ffff13d6c95e1026b99d Reviewed-by: Gunnar Sletta Reviewed-by: Simon Hausmann --- dist/changes-5.4.2 | 107 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 dist/changes-5.4.2 diff --git a/dist/changes-5.4.2 b/dist/changes-5.4.2 new file mode 100644 index 0000000000..e1ad9b6846 --- /dev/null +++ b/dist/changes-5.4.2 @@ -0,0 +1,107 @@ +Qt 5.4.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.4.1. Compatibility with Qt +5.4.0 is also retained, except on Windows when using MSVC 2012 or MSVC +2013. See note below. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5.4/ + +The Qt version 5.4 series is binary compatible with the 5.3.x series. +Applications compiled for 5.3 will continue to run with 5.4 with the +exception of on Windows when using MSVC 2012 or MSVC 2013. See note +below. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important Behavior Changes * +**************************************************************************** +- Restored binary compatibility with Qt 5.3.2 on Windows when using MSVC + 2012 or MSVC 2013. This means that Qt 5.4.1 and 5.4.2 are no longer + binary compatible with Qt 5.4.0 when using either of those compilers. +- [QTBUG-42594] OS X binary package: fixed incorrect linking to libraries in + /opt/local/lib +- EXIF orientation is no longer applied to JPEG images on read. EXIF + orientation on JPEG was introduced in 5.4.0, but due to a bug the most + common EXIF-format (big-endian) was not working until 5.4.1. 5.4.2 restores the + behavior of 5.4.0 and earlier for most EXIF-tagged JPEGs. + EXIF orientation will be an opt-in starting with Qt 5.5. + +**************************************************************************** +* Library * +**************************************************************************** + +QtCore +------ + - [QTBUG-43893] Fixed memory leak in qSetMessagePattern + - [QTBUG-43513] QXmlStreamReader: Correctly parse XML containing NUL bytes + in the input stream + - [QTBUG-43352] QTemporaryDirectory: Properly clean up in case of a failure + - [QTBUG-43827] Fixed regression in QSortFilterProxyModel which crashed when + sorting a tree model + +QtGui +----- + - [QTBUG-44273] Fixed misplacement of outlined text with native text rendering + - [QTBUG-44147] Fixed VNC not working on some VNC servers + - [QTBUG-43850] Fixed crash with multi-threaded font usage + - [QTBUG-43850] Made the old harfbuzz fallback available at runtime + - Improvements to the experimental high-dpi support + - [QTBUG-43318] Better resolution of GLES 3 functions to avoid issues when + deploying on systems with GLES 2.0 only + +QtWidgets +--------- + - [QTBUG-43830] Fixed crash in stylesheets when styling QProgressBar + - [QTBUG-43663] QColorDialog: Don't lose focus while color picking + +QtNetwork +--------- + - [QTBUG-43793] Fixed disconnects of QSSLSocket after starting encryption + +QtPrintSupport +-------------- + - [QTBUG-43124] Fixed QPrinter::{width,height} return values + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Android +------- + + - [QTBUG-44648] Fixed rendering Chinese text on Android 5 + +Linux/XCB +--------- + + - [QTBUG-45071] Don't crash when resizing windows to bigger than 3840x2160 + +**************************************************************************** +* Tools * +**************************************************************************** + +configure & build system +------------------------ + + - CMake-based projects using Qt will now always be built with -fPIE, + which fixes function pointer based QObject::connect() on ARM. This + is consistent with qmake + - [Android] Fixed compilation on armv5 with 4.9 toolchain + +qmake +----- + + - [VS] Fixed handling of files that are excluded from build + - [QTBUG-44413][VS] Fixed vcxproj generation for CONFIG-=flat, again + - [QTBUG-44595] Restored Qt 4 behavior of qtLibraryTarget() + - [QTBUG-45118][Windows] Fixed parallel build when using TYPELIBS + - [OS X/iOS] Fixed QMAKE_INFO_PLIST path resolution for shadow builds From ef622d55ca990e70f60d11964fa2f3651ccd4522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Fri, 24 Apr 2015 17:13:39 +0200 Subject: [PATCH 6/8] Android: Use Holo theme on Android 5.1 We already fallback to the Holo theme on Android 5.0 devices, see: 7c539579b9e883c87e5f7fb3bbec80847fc83ae2 Task-number: QTBUG-45714 Change-Id: I18b0700321b27ab5bbe3f1642a0bc9de1774864a Reviewed-by: BogDan Vatra --- .../java/src/org/qtproject/qt5/android/bindings/QtActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java index 75f10ad3ba..a790e0ef6f 100644 --- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -187,7 +187,7 @@ public class QtActivity extends Activity QT_ANDROID_THEMES = new String[] {"Theme_Light"}; QT_ANDROID_DEFAULT_THEME = "Theme_Light"; } - else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT == 21){ + else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT >= 21){ QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; } else { From 119c5ec93f3b32cb62456c3a42c54ebb2c42e567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Fri, 24 Apr 2015 15:21:21 +0200 Subject: [PATCH 7/8] Android: Fix wrong field name in ExtractStyle.java In Android 5.1 the field name for the inset state member in InsetDrawable changed from mInsetState to mState. Task-number: QTBUG-45714 Change-Id: I0ebada1ef90954013e5357cbd10df925f8f05295 Reviewed-by: BogDan Vatra --- .../jar/src/org/qtproject/qt5/android/ExtractStyle.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/android/jar/src/org/qtproject/qt5/android/ExtractStyle.java b/src/android/jar/src/org/qtproject/qt5/android/ExtractStyle.java index a8583cde17..79722183bf 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/ExtractStyle.java +++ b/src/android/jar/src/org/qtproject/qt5/android/ExtractStyle.java @@ -1100,7 +1100,9 @@ public class ExtractStyle { { try { InsetDrawable d = (InsetDrawable)drawable; - Object mInsetStateObject = getAccessibleField(InsetDrawable.class, "mInsetState").get(d); + // mInsetState changed to mState in Android 5.1 (22) + Object mInsetStateObject = getAccessibleField(InsetDrawable.class, (Build.VERSION.SDK_INT > 21) ? "mState" + : "mInsetState").get(d); Rect _padding = new Rect(); boolean hasPadding = d.getPadding(_padding); return getDrawable(getAccessibleField(mInsetStateObject.getClass(), "mDrawable").get(mInsetStateObject), filename, hasPadding ? _padding : null); From 1f281bfd63402b50f3db378a027da26ca52ffb27 Mon Sep 17 00:00:00 2001 From: Christoph Schleifenbaum Date: Sat, 18 Apr 2015 14:39:18 +0200 Subject: [PATCH 8/8] ItemViews: Fix acceptance of drag enter events Always accept drag enter events if the mime type and the drop actions matches. Whether the drop can actually happen on the currently hovered index will be decided in the following drag move event. Change-Id: I27e865cb65513dfe3f57ad3f1bc8cebf4c29a692 Task-number: QTBUG-45037 Reviewed-by: David Faure --- src/widgets/itemviews/qabstractitemview_p.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/widgets/itemviews/qabstractitemview_p.h b/src/widgets/itemviews/qabstractitemview_p.h index c3bd1fb504..be5d4b7b71 100644 --- a/src/widgets/itemviews/qabstractitemview_p.h +++ b/src/widgets/itemviews/qabstractitemview_p.h @@ -168,8 +168,20 @@ public: QModelIndex index; int col = -1; int row = -1; + const QMimeData *mime = event->mimeData(); + + // Drag enter event shall always be accepted, if mime type and action match. + // Whether the data can actually be dropped will be checked in drag move. + if (event->type() == QEvent::DragEnter) { + const QStringList modelTypes = model->mimeTypes(); + for (int i = 0; i < modelTypes.count(); ++i) + if (mime->hasFormat(modelTypes.at(i)) + && (event->dropAction() & model->supportedDropActions())) + return true; + } + if (dropOn(event, &row, &col, &index)) { - return model->canDropMimeData(event->mimeData(), + return model->canDropMimeData(mime, dragDropMode == QAbstractItemView::InternalMove ? Qt::MoveAction : event->dropAction(), row, col, index); }