From ad1e82323225e996720136e8b2d669166b8d8441 Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Wed, 17 Nov 2010 18:33:22 +0100 Subject: QNetworkAccessManager: enable synchronous HTTP calls To enable synchronous calls, an attribute in the QNetworkRequest has to be set. If set, when QNetworkAccessManager::get() (and post(), put()) returns, the reply is finished and all data has been read in case of success. This feature is semi-public for now (usable, but not documented). To enable this, an attribute in the QNetworkRequest must be set. If this attribute is set, we open a new connection to the server with only one channel and call the channels' sockets' waitFor* methods. Reviewed-by: Markus Goetz --- src/network/access/qhttpnetworkconnection.cpp | 9 +- src/network/access/qhttpnetworkconnection_p.h | 4 +- .../access/qhttpnetworkconnectionchannel.cpp | 2 +- .../access/qhttpnetworkconnectionchannel_p.h | 2 + src/network/access/qhttpnetworkreply.cpp | 6 + src/network/access/qhttpnetworkreply_p.h | 1 + src/network/access/qnetworkaccessbackend.cpp | 1 + src/network/access/qnetworkaccessbackend_p.h | 6 + src/network/access/qnetworkaccessdatabackend.cpp | 6 + src/network/access/qnetworkaccessdatabackend_p.h | 2 + src/network/access/qnetworkaccesshttpbackend.cpp | 140 +++++- src/network/access/qnetworkaccesshttpbackend_p.h | 4 +- src/network/access/qnetworkaccessmanager.cpp | 16 +- src/network/access/qnetworkreplyimpl.cpp | 47 +- src/network/access/qnetworkrequest.h | 3 + .../tst_qabstractnetworkcache.cpp | 111 ++++- tests/auto/qnetworkreply/tst_qnetworkreply.cpp | 506 ++++++++++++++++++++- 17 files changed, 799 insertions(+), 67 deletions(-) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 4d27531..0531595 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -327,8 +327,6 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket Q_ASSERT(socket); Q_ASSERT(reply); - Q_Q(QHttpNetworkConnection); - resend = false; //create the response header to be used with QAuthenticatorPrivate. QList > fields = reply->header(); @@ -854,12 +852,17 @@ QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest return d->queueRequest(request); } -bool QHttpNetworkConnection::isEncrypted() const +bool QHttpNetworkConnection::isSsl() const { Q_D(const QHttpNetworkConnection); return d->encrypt; } +QHttpNetworkConnectionChannel *QHttpNetworkConnection::channels() const +{ + return d_func()->channels; +} + #ifndef QT_NO_NETWORKPROXY void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy) { diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 8461426c..9f23cbf 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -108,7 +108,9 @@ public: QNetworkProxy transparentProxy() const; #endif - bool isEncrypted() const; + bool isSsl() const; + + QHttpNetworkConnectionChannel *channels() const; #ifndef QT_NO_OPENSSL void setSslConfiguration(const QSslConfiguration &config); diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 02daa50..c8caad4 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -180,7 +180,6 @@ bool QHttpNetworkConnectionChannel::sendRequest() reply->d_func()->autoDecompress = request.d->autoDecompress; reply->d_func()->pipeliningUsed = false; - pendingEncrypt = false; // if the url contains authentication parameters, use the new ones // both channels will use the new authentication parameters if (!request.url().userInfo().isEmpty() && request.withCredentials()) { @@ -1019,6 +1018,7 @@ void QHttpNetworkConnectionChannel::_q_encrypted() if (!socket) return; // ### error state = QHttpNetworkConnectionChannel::IdleState; + pendingEncrypt = false; sendRequest(); } diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index 442086a..fd18042 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -158,6 +158,8 @@ public: bool isSocketWaiting() const; bool isSocketReading() const; + friend class QNetworkAccessHttpBackend; + protected slots: void _q_receiveReply(); void _q_bytesWritten(qint64 bytes); // proceed sending diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index e4eb7c4..21bc427 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -188,6 +188,12 @@ QByteArray QHttpNetworkReply::readAny() return d->responseData.read(); } +QByteArray QHttpNetworkReply::readAll() +{ + Q_D(QHttpNetworkReply); + return d->responseData.readAll(); +} + void QHttpNetworkReply::setDownstreamLimited(bool dsl) { Q_D(QHttpNetworkReply); diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h index 3f79d81..9cf805c 100644 --- a/src/network/access/qhttpnetworkreply_p.h +++ b/src/network/access/qhttpnetworkreply_p.h @@ -126,6 +126,7 @@ public: qint64 bytesAvailable() const; qint64 bytesAvailableNextBlock() const; QByteArray readAny(); + QByteArray readAll(); void setDownstreamLimited(bool t); bool isFinished() const; diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp index 05eb6cb..12b6400 100644 --- a/src/network/access/qnetworkaccessbackend.cpp +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -142,6 +142,7 @@ void QNetworkAccessBackend::emitReplyUploadProgress(qint64 bytesSent, qint64 byt QNetworkAccessBackend::QNetworkAccessBackend() : manager(0) , reply(0) + , synchronous(false) { } diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h index 7faa5cb..c9ec37e 100644 --- a/src/network/access/qnetworkaccessbackend_p.h +++ b/src/network/access/qnetworkaccessbackend_p.h @@ -157,6 +157,9 @@ public: QVariant attribute(QNetworkRequest::Attribute code) const; void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); + bool isSynchronous() { return synchronous; } + void setSynchronous(bool sync) { synchronous = sync; } + // return true if the QNonContiguousByteDevice of the upload // data needs to support reset(). Currently needed for HTTP. // This will possibly enable buffering of the upload data. @@ -166,6 +169,8 @@ public: virtual bool canResume() const { return false; } virtual void setResumeOffset(quint64 offset) { Q_UNUSED(offset); } + virtual bool processRequestSynchronously() { return false; } + protected: // Create the device used for reading the upload data QNonContiguousByteDevice* createUploadByteDevice(); @@ -200,6 +205,7 @@ private: friend class QNetworkReplyImplPrivate; QNetworkAccessManagerPrivate *manager; QNetworkReplyImplPrivate *reply; + bool synchronous; }; class QNetworkAccessBackendFactory diff --git a/src/network/access/qnetworkaccessdatabackend.cpp b/src/network/access/qnetworkaccessdatabackend.cpp index efb6e3e..74aebdb 100644 --- a/src/network/access/qnetworkaccessdatabackend.cpp +++ b/src/network/access/qnetworkaccessdatabackend.cpp @@ -122,4 +122,10 @@ bool QNetworkAccessDataBackend::waitForUpstreamBytesWritten(int) return false; } +bool QNetworkAccessDataBackend::processRequestSynchronously() +{ + start(); + return true; +} + QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessdatabackend_p.h b/src/network/access/qnetworkaccessdatabackend_p.h index a7c63d5..0e5a494 100644 --- a/src/network/access/qnetworkaccessdatabackend_p.h +++ b/src/network/access/qnetworkaccessdatabackend_p.h @@ -68,6 +68,8 @@ public: virtual void closeUpstreamChannel(); virtual bool waitForDownstreamReadyRead(int msecs); virtual bool waitForUpstreamBytesWritten(int msecs); + + virtual bool processRequestSynchronously(); }; class QNetworkAccessDataBackendFactory: public QNetworkAccessBackendFactory diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index 80b05a4..070111d 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -50,6 +50,7 @@ #include "qnetworkrequest_p.h" #include "qnetworkcookie_p.h" #include "QtCore/qdatetime.h" +#include "QtCore/qelapsedtimer.h" #include "QtNetwork/qsslconfiguration.h" #ifndef QT_NO_HTTP @@ -318,7 +319,10 @@ void QNetworkAccessHttpBackend::disconnectFromHttp() // Get the object cache that stores our QHttpNetworkConnection objects QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this); - cache->releaseEntry(cacheKey); + + // synchronous calls are not put into the cache, so for them the key is empty + if (!cacheKey.isEmpty()) + cache->releaseEntry(cacheKey); } // This is abut disconnecting signals, not about disconnecting TCP connections @@ -639,34 +643,49 @@ void QNetworkAccessHttpBackend::open() if (transparentProxy.type() == QNetworkProxy::DefaultProxy && cacheProxy.type() == QNetworkProxy::DefaultProxy) { // unsuitable proxies - QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError), - Q_ARG(QString, tr("No suitable proxy found"))); - QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); - return; + if (isSynchronous()) { + error(QNetworkReply::ProxyNotFoundError, tr("No suitable proxy found")); + finished(); + } else { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError), + Q_ARG(QString, tr("No suitable proxy found"))); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + } + return; } #endif - // check if we have an open connection to this host - cacheKey = makeCacheKey(this, theProxy); - QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this); - // the http object is actually a QHttpNetworkConnection - http = static_cast(cache->requestEntryNow(cacheKey)); - if (http == 0) { - // no entry in cache; create an object - // the http object is actually a QHttpNetworkConnection - http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt); - + if (isSynchronous()) { + // for synchronous requests, we just create a new connection + http = new QHttpNetworkConnection(1, url.host(), url.port(), encrypt, this); #ifndef QT_NO_NETWORKPROXY http->setTransparentProxy(transparentProxy); http->setCacheProxy(cacheProxy); #endif + postRequest(); + processRequestSynchronously(); + } else { + // check if we have an open connection to this host + cacheKey = makeCacheKey(this, theProxy); + QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this); + // the http object is actually a QHttpNetworkConnection + http = static_cast(cache->requestEntryNow(cacheKey)); + if (http == 0) { + // no entry in cache; create an object + // the http object is actually a QHttpNetworkConnection + http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt); - // cache the QHttpNetworkConnection corresponding to this cache key - cache->addEntry(cacheKey, http); - } +#ifndef QT_NO_NETWORKPROXY + http->setTransparentProxy(transparentProxy); + http->setCacheProxy(cacheProxy); +#endif - postRequest(); + // cache the QHttpNetworkConnection corresponding to this cache key + cache->addEntry(cacheKey, static_cast(http.data())); + } + postRequest(); + } } void QNetworkAccessHttpBackend::closeDownstreamChannel() @@ -1125,6 +1144,87 @@ void QNetworkAccessHttpBackend::setResumeOffset(quint64 offset) resumeOffset = offset; } +bool QNetworkAccessHttpBackend::processRequestSynchronously() +{ + QHttpNetworkConnectionChannel *channel = &http->channels()[0]; + + // Disconnect all socket signals. They will only confuse us when using waitFor* + QObject::disconnect(channel->socket, 0, 0, 0); + + qint64 timeout = 20*1000; // 20 sec + QElapsedTimer timeoutTimer; + + bool waitResult = channel->socket->waitForConnected(timeout); + timeoutTimer.start(); + + if (!waitResult || channel->socket->state() != QAbstractSocket::Connected) { + error(QNetworkReply::UnknownNetworkError, QLatin1String("could not connect")); + return false; + } + channel->_q_connected(); // this will send the request (via sendRequest()) + +#ifndef QT_NO_OPENSSL + if (http->isSsl()) { + qint64 remainingTimeEncrypted = timeout - timeoutTimer.elapsed(); + if (!static_cast(channel->socket)->waitForEncrypted(remainingTimeEncrypted)) { + error(QNetworkReply::SslHandshakeFailedError, + QLatin1String("could not encrypt or timeout while encrypting")); + return false; + } + channel->_q_encrypted(); + } +#endif + + // if we get a 401 or 407, we might need to send the request twice, see below + bool authenticating = false; + + do { + channel->sendRequest(); + + qint64 remainingTimeBytesWritten; + while(channel->socket->bytesToWrite() > 0 || + channel->state == QHttpNetworkConnectionChannel::WritingState) { + remainingTimeBytesWritten = timeout - timeoutTimer.elapsed(); + channel->sendRequest(); // triggers channel->socket->write() + if (!channel->socket->waitForBytesWritten(remainingTimeBytesWritten)) { + error(QNetworkReply::TimeoutError, + QLatin1String("could not write bytes to socket or timeout while writing")); + return false; + } + } + + qint64 remainingTimeBytesRead = timeout - timeoutTimer.elapsed(); + // Loop for at most remainingTime until either the socket disconnects + // or the reply is finished + do { + waitResult = channel->socket->waitForReadyRead(remainingTimeBytesRead); + remainingTimeBytesRead = timeout - timeoutTimer.elapsed(); + if (!waitResult || remainingTimeBytesRead <= 0 + || channel->socket->state() != QAbstractSocket::ConnectedState) { + error(QNetworkReply::TimeoutError, + QLatin1String("could not read from socket or timeout while reading")); + return false; + } + + if (channel->socket->bytesAvailable()) + channel->_q_readyRead(); + + if (!httpReply) + return false; // we got a 401 or 407 and cannot handle it (it might happen that + // disconnectFromHttp() was called, in that case the reply is zero) + // ### I am quite sure this does not work for NTLM + // ### how about uploading to an auth / proxyAuth site? + + authenticating = (httpReply->statusCode() == 401 || httpReply->statusCode() == 407); + + if (httpReply->isFinished()) + break; + } while (remainingTimeBytesRead > 0); + } while (authenticating); + + return true; +} + QT_END_NAMESPACE #endif // QT_NO_HTTP diff --git a/src/network/access/qnetworkaccesshttpbackend_p.h b/src/network/access/qnetworkaccesshttpbackend_p.h index 5de3429..cc2f9ac 100644 --- a/src/network/access/qnetworkaccesshttpbackend_p.h +++ b/src/network/access/qnetworkaccesshttpbackend_p.h @@ -99,6 +99,8 @@ public: bool canResume() const; void setResumeOffset(quint64 offset); + virtual bool processRequestSynchronously(); + private slots: void replyReadyRead(); void replyFinished(); @@ -111,7 +113,7 @@ private slots: private: QHttpNetworkReply *httpReply; - QPointer http; + QPointer http; QByteArray cacheKey; QNetworkAccessBackendUploadIODevice *uploadDevice; diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index effd79e..4bc036e 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -1060,12 +1060,14 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera priv->backend->setParent(reply); priv->backend->reply = priv; } - // fourth step: setup the reply - priv->setup(op, request, outgoingData); #ifndef QT_NO_OPENSSL reply->setSslConfiguration(request.sslConfiguration()); #endif + + // fourth step: setup the reply + priv->setup(op, request, outgoingData); + return reply; } @@ -1141,6 +1143,11 @@ void QNetworkAccessManagerPrivate::authenticationRequired(QNetworkAccessBackend } } + // if we emit a signal here in synchronous mode, the user might spin + // an event loop, which might recurse and lead to problems + if (backend->isSynchronous()) + return; + backend->reply->urlForLastAuthentication = url; emit q->authenticationRequired(backend->reply->q_func(), authenticator); cacheCredentials(url, authenticator); @@ -1168,6 +1175,11 @@ void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBac } } + // if we emit a signal here in synchronous mode, the user might spin + // an event loop, which might recurse and lead to problems + if (backend->isSynchronous()) + return; + backend->reply->lastProxyAuthentication = proxy; emit q->proxyAuthenticationRequired(proxy, authenticator); cacheProxyCredentials(proxy, authenticator); diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index 5850494..cf6e674 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -85,7 +85,7 @@ void QNetworkReplyImplPrivate::_q_startOperation() } #ifndef QT_NO_BEARERMANAGEMENT - if (!backend->start()) { + if (!backend->start()) { // ### we should call that method even if bearer is not used // backend failed to start because the session state is not Connected. // QNetworkAccessManager will call reply->backend->start() again for us when the session // state changes. @@ -109,11 +109,15 @@ void QNetworkReplyImplPrivate::_q_startOperation() } #endif - if (state != Finished) { - if (operation == QNetworkAccessManager::GetOperation) - pendingNotifications.append(NotifyDownstreamReadyWrite); + if (backend->isSynchronous()) { + state = Finished; + } else { + if (state != Finished) { + if (operation == QNetworkAccessManager::GetOperation) + pendingNotifications.append(NotifyDownstreamReadyWrite); - handleNotifications(); + handleNotifications(); + } } } @@ -287,7 +291,25 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const url = request.url(); operation = op; - if (outgoingData && backend) { + q->QIODevice::open(QIODevice::ReadOnly); + // Internal code that does a HTTP reply for the synchronous Ajax + // in QtWebKit. + QVariant synchronousHttpAttribute = req.attribute( + static_cast(QNetworkRequest::DownloadBufferAttribute + 1)); + if (synchronousHttpAttribute.toBool()) { + backend->setSynchronous(true); + if (outgoingData && outgoingData->isSequential()) { + outgoingDataBuffer = new QRingBuffer(); + QByteArray data; + do { + data = outgoingData->readAll(); + if (data.isEmpty()) + break; + outgoingDataBuffer->append(data); + } while (1); + } + } + if (outgoingData && backend && !backend->isSynchronous()) { // there is data to be uploaded, e.g. HTTP POST. if (!backend->needsResetableUploadData() || !outgoingData->isSequential()) { @@ -298,7 +320,7 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const } else { bool bufferingDisallowed = req.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, - false).toBool(); + false).toBool(); if (bufferingDisallowed) { // if a valid content-length header for the request was supplied, we can disable buffering @@ -323,17 +345,18 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const // for HTTP, we want to send out the request as fast as possible to the network, without // invoking methods in a QueuedConnection #ifndef QT_NO_HTTP - if (qobject_cast(backend)) { + if (qobject_cast(backend) || (backend && backend->isSynchronous())) { _q_startOperation(); } else { QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); } #else - QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + if (backend->isSynchronous()) + _q_startOperation(); + else + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); #endif // QT_NO_HTTP - } - - q->QIODevice::open(QIODevice::ReadOnly); + } } void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification) diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index cdadf0f..9bcc900 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -85,6 +85,9 @@ public: MaximumDownloadBufferSizeAttribute, // internal DownloadBufferAttribute, // internal + // (DownloadBufferAttribute + 1) is reserved internal for QSynchronousHttpNetworkReply + // add the enum in 4.8 + User = 1000, UserMax = 32767 }; diff --git a/tests/auto/qabstractnetworkcache/tst_qabstractnetworkcache.cpp b/tests/auto/qabstractnetworkcache/tst_qabstractnetworkcache.cpp index 04bd432..fc8a126 100644 --- a/tests/auto/qabstractnetworkcache/tst_qabstractnetworkcache.cpp +++ b/tests/auto/qabstractnetworkcache/tst_qabstractnetworkcache.cpp @@ -58,20 +58,29 @@ public: private slots: void expires_data(); void expires(); + void expiresSynchronous_data(); + void expiresSynchronous(); void lastModified_data(); void lastModified(); + void lastModifiedSynchronous_data(); + void lastModifiedSynchronous(); void etag_data(); void etag(); + void etagSynchronous_data(); + void etagSynchronous(); void cacheControl_data(); void cacheControl(); + void cacheControlSynchronous_data(); + void cacheControlSynchronous(); void deleteCache(); private: void check(); + void checkSynchronous(); }; class NetworkDiskCache : public QNetworkDiskCache @@ -142,6 +151,16 @@ void tst_QAbstractNetworkCache::expires() check(); } +void tst_QAbstractNetworkCache::expiresSynchronous_data() +{ + expires_data(); +} + +void tst_QAbstractNetworkCache::expiresSynchronous() +{ + checkSynchronous(); +} + void tst_QAbstractNetworkCache::lastModified_data() { QTest::addColumn("cacheLoadControl"); @@ -164,6 +183,16 @@ void tst_QAbstractNetworkCache::lastModified() check(); } +void tst_QAbstractNetworkCache::lastModifiedSynchronous_data() +{ + tst_QAbstractNetworkCache::lastModified_data(); +} + +void tst_QAbstractNetworkCache::lastModifiedSynchronous() +{ + checkSynchronous(); +} + void tst_QAbstractNetworkCache::etag_data() { QTest::addColumn("cacheLoadControl"); @@ -186,6 +215,16 @@ void tst_QAbstractNetworkCache::etag() check(); } +void tst_QAbstractNetworkCache::etagSynchronous_data() +{ + tst_QAbstractNetworkCache::etag_data(); +} + +void tst_QAbstractNetworkCache::etagSynchronous() +{ + checkSynchronous(); +} + void tst_QAbstractNetworkCache::cacheControl_data() { QTest::addColumn("cacheLoadControl"); @@ -217,6 +256,16 @@ void tst_QAbstractNetworkCache::cacheControl() check(); } +void tst_QAbstractNetworkCache::cacheControlSynchronous_data() +{ + tst_QAbstractNetworkCache::cacheControl_data(); +} + +void tst_QAbstractNetworkCache::cacheControlSynchronous() +{ + checkSynchronous(); +} + void tst_QAbstractNetworkCache::check() { QFETCH(QNetworkRequest::CacheLoadControl, cacheLoadControl); @@ -250,8 +299,6 @@ void tst_QAbstractNetworkCache::check() QCOMPARE(reply2->error(), QNetworkReply::ContentNotFoundError); QCOMPARE(secondData, QByteArray()); } else { - if (reply2->error() != QNetworkReply::NoError) - qDebug() << reply2->errorString(); QCOMPARE(reply2->error(), QNetworkReply::NoError); QCOMPARE(QString(secondData), QString(goodData)); QCOMPARE(secondData, goodData); @@ -263,16 +310,60 @@ void tst_QAbstractNetworkCache::check() QList rawHeaderList2 = reply2->rawHeaderList(); qSort(rawHeaderList); qSort(rawHeaderList2); + } + QCOMPARE(diskCache->gotData, fetchFromCache); +} - // headers can change - for (int i = 0; i < rawHeaderList.count(); ++i) { - //qDebug() << i << rawHeaderList.value(i) << reply->rawHeader(rawHeaderList.value(i)); - //qDebug() << i << rawHeaderList2.value(i) << reply2->rawHeader(rawHeaderList2.value(i)); - //QCOMPARE(QString(rawHeaderList.value(i)), QString(rawHeaderList2.value(i))); - //QCOMPARE(QString(reply->rawHeader(rawHeaderList.value(i))), QString(reply2->rawHeader(rawHeaderList2.value(i)))); - } - //QCOMPARE(rawHeaderList.count(), rawHeaderList2.count()); +void tst_QAbstractNetworkCache::checkSynchronous() +{ + QSKIP("not working yet, see QTBUG-15221", SkipAll); + QFETCH(QNetworkRequest::CacheLoadControl, cacheLoadControl); + QFETCH(QString, url); + QFETCH(bool, fetchFromCache); + + QNetworkAccessManager manager; + NetworkDiskCache *diskCache = new NetworkDiskCache(&manager); + manager.setCache(diskCache); + QCOMPARE(diskCache->gotData, false); + + QUrl realUrl = url.contains("://") ? url : TESTFILE + url; + QNetworkRequest request(realUrl); + + request.setAttribute( + static_cast(QNetworkRequest::DownloadBufferAttribute + 1), + true); + + // prime the cache + QNetworkReply *reply = manager.get(request); + QVERIFY(reply->isFinished()); // synchronous + QCOMPARE(diskCache->gotData, false); + QByteArray goodData = reply->readAll(); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheLoadControl); + + // should be in the cache now + QNetworkReply *reply2 = manager.get(request); + QVERIFY(reply2->isFinished()); // synchronous + + QByteArray secondData = reply2->readAll(); + if (!fetchFromCache && cacheLoadControl == QNetworkRequest::AlwaysCache) { + QCOMPARE(reply2->error(), QNetworkReply::ContentNotFoundError); + QCOMPARE(secondData, QByteArray()); + } else { + if (reply2->error() != QNetworkReply::NoError) + qDebug() << reply2->errorString(); + QCOMPARE(reply2->error(), QNetworkReply::NoError); + QCOMPARE(QString(secondData), QString(goodData)); + QCOMPARE(secondData, goodData); + QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + } + + if (fetchFromCache) { + QList rawHeaderList = reply->rawHeaderList(); + QList rawHeaderList2 = reply2->rawHeaderList(); + qSort(rawHeaderList); + qSort(rawHeaderList2); } QCOMPARE(diskCache->gotData, fetchFromCache); } diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp index ddb7687..90416f2 100644 --- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp @@ -75,7 +75,6 @@ #include "../network-settings.h" - Q_DECLARE_METATYPE(QNetworkReply*) Q_DECLARE_METATYPE(QAuthenticator*) Q_DECLARE_METATYPE(QNetworkProxy) @@ -84,6 +83,8 @@ Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QNetworkReply::NetworkError) Q_DECLARE_METATYPE(QBuffer*) +const int SynchronousRequestAttribute = QNetworkRequest::DownloadBufferAttribute + 1; + class QNetworkReplyPtr: public QSharedPointer { public: @@ -108,6 +109,16 @@ class tst_QNetworkReply: public QObject bool requiresAuthentication; }; + static bool seedCreated; + static QString createUniqueExtension() { + if (!seedCreated) { + qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) + QCoreApplication::applicationPid()); + seedCreated = true; // not thread-safe, but who cares + } + QString s = QString("%1-%2-%3").arg(QTime(0,0,0).msecsTo(QTime::currentTime())).arg(QCoreApplication::applicationPid()).arg(qrand()); + return s; + }; + QEventLoop *loop; enum RunSimpleRequestReturn { Timeout = 0, Success, Failure }; int returnCode; @@ -173,8 +184,12 @@ private Q_SLOTS: void putToFtp(); void putToHttp_data(); void putToHttp(); + void putToHttpSynchronous_data(); + void putToHttpSynchronous(); void postToHttp_data(); void postToHttp(); + void postToHttpSynchronous_data(); + void postToHttpSynchronous(); void deleteFromHttp_data(); void deleteFromHttp(); void putGetDeleteGetFromHttp_data(); @@ -198,7 +213,9 @@ private Q_SLOTS: void ioGetFromHttpWithReuseParallel(); void ioGetFromHttpWithReuseSequential(); void ioGetFromHttpWithAuth(); + void ioGetFromHttpWithAuthSynchronous(); void ioGetFromHttpWithProxyAuth(); + void ioGetFromHttpWithProxyAuthSynchronous(); void ioGetFromHttpWithSocksProxy(); #ifndef QT_NO_OPENSSL void ioGetFromHttpsWithSslErrors(); @@ -233,6 +250,8 @@ private Q_SLOTS: void ioPostToHttpFromFile(); void ioPostToHttpFromSocket_data(); void ioPostToHttpFromSocket(); + void ioPostToHttpFromSocketSynchronous(); + void ioPostToHttpFromSocketSynchronous_data(); void ioPostToHttpFromMiddleOfFileToEnd(); void ioPostToHttpFromMiddleOfFileFiveBytes(); void ioPostToHttpFromMiddleOfQBufferFiveBytes(); @@ -258,13 +277,19 @@ private Q_SLOTS: void receiveCookiesFromHttp_data(); void receiveCookiesFromHttp(); + void receiveCookiesFromHttpSynchronous_data(); + void receiveCookiesFromHttpSynchronous(); void sendCookies_data(); void sendCookies(); + void sendCookiesSynchronous_data(); + void sendCookiesSynchronous(); void nestedEventLoops(); void httpProxyCommands_data(); void httpProxyCommands(); + void httpProxyCommandsSynchronous_data(); + void httpProxyCommandsSynchronous(); void proxyChange(); void authorizationError_data(); void authorizationError(); @@ -303,12 +328,18 @@ private Q_SLOTS: void qtbug15311doubleContentLength(); + void synchronousRequest_data(); + void synchronousRequest(); + void synchronousRequestSslFailure(); + // NOTE: This test must be last! void parentingRepliesToTheApp(); }; QT_BEGIN_NAMESPACE +bool tst_QNetworkReply::seedCreated = false; + namespace QTest { template<> char *toString(const QNetworkReply::NetworkError& code) @@ -915,14 +946,15 @@ protected: tst_QNetworkReply::tst_QNetworkReply() { + qRegisterMetaType(); // for QSignalSpy + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType >(); + Q_SET_DEFAULT_IAP testFileName = QDir::currentPath() + "/testfile"; -#ifndef Q_OS_WINCE - uniqueExtension = QString("%1%2%3").arg((qulonglong)this).arg(rand()).arg((qulonglong)time(0)); -#else - uniqueExtension = QString("%1%2").arg((qulonglong)this).arg(rand()); -#endif + uniqueExtension = createUniqueExtension(); cookieJar = new MyCookieJar; manager.setCookieJar(cookieJar); @@ -1009,15 +1041,25 @@ QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, Q_ASSERT_X(false, "tst_QNetworkReply", "Invalid/unknown operation requested"); } reply->setParent(this); - connect(reply, SIGNAL(finished()), SLOT(finished())); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError())); returnCode = Timeout; - loop = new QEventLoop; - QTimer::singleShot(20000, loop, SLOT(quit())); - int code = returnCode == Timeout ? loop->exec() : returnCode; - delete loop; - loop = 0; + int code = Success; + + if (request.attribute(static_cast(SynchronousRequestAttribute)).toBool()) { + if (reply->isFinished()) + code = reply->error() != QNetworkReply::NoError ? Failure : Success; + else + code = Failure; + } else { + connect(reply, SIGNAL(finished()), SLOT(finished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError())); + + loop = new QEventLoop; + QTimer::singleShot(20000, loop, SLOT(quit())); + code = returnCode == Timeout ? loop->exec() : returnCode; + delete loop; + loop = 0; + } switch (code) { case Failure: @@ -1492,6 +1534,9 @@ void tst_QNetworkReply::putToFile_data() data = QByteArray(128*1024+1, '\177'); QTest::newRow("128k+1") << data << md5sum(data); + + data = QByteArray(2*1024*1024+1, '\177'); + QTest::newRow("2MB+1") << data << md5sum(data); } void tst_QNetworkReply::putToFile() @@ -1598,6 +1643,47 @@ void tst_QNetworkReply::putToHttp() QCOMPARE(uploadedData, data); } +void tst_QNetworkReply::putToHttpSynchronous_data() +{ + uniqueExtension = createUniqueExtension(); + putToFile_data(); +} + +void tst_QNetworkReply::putToHttpSynchronous() +{ + QUrl url("http://" + QtNetworkSettings::serverName()); + url.setPath(QString("/dav/qnetworkaccess-putToHttp-%1-%2") + .arg(QTest::currentDataTag()) + .arg(uniqueExtension)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + + QFETCH(QByteArray, data); + + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PutOperation, request, reply, data)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 201); // 201 Created + + // download the file again from HTTP to make sure it was uploaded + // correctly. HTTP/0.9 is enough + QTcpSocket socket; + socket.connectToHost(QtNetworkSettings::serverName(), 80); + socket.write("GET " + url.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority) + "\r\n"); + if (!socket.waitForDisconnected(10000)) + QFAIL("Network timeout"); + + QByteArray uploadedData = socket.readAll(); + QCOMPARE(uploadedData, data); +} + void tst_QNetworkReply::postToHttp_data() { putToFile_data(); @@ -1624,6 +1710,37 @@ void tst_QNetworkReply::postToHttp() QCOMPARE(uploadedData, md5sum.toHex()); } +void tst_QNetworkReply::postToHttpSynchronous_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::postToHttpSynchronous() +{ + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"); + + QNetworkRequest request(url); + + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply; + + QFETCH(QByteArray, data); + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PostOperation, request, reply, data)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QFETCH(QByteArray, md5sum); + QByteArray uploadedData = reply->readAll().trimmed(); + QCOMPARE(uploadedData, md5sum.toHex()); +} + void tst_QNetworkReply::deleteFromHttp_data() { QTest::addColumn("url"); @@ -2048,9 +2165,6 @@ void tst_QNetworkReply::ioGetFromHttpWithReuseSequential() void tst_QNetworkReply::ioGetFromHttpWithAuth() { - qRegisterMetaType(); // for QSignalSpy - qRegisterMetaType(); - // This test sends three requests // The first two in parallel // The third after the first two finished @@ -2109,6 +2223,44 @@ void tst_QNetworkReply::ioGetFromHttpWithAuth() QCOMPARE(authspy.count(), 0); } + + // now check with synchronous calls: + reference.seek(0); + { + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QSignalSpy authspy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QNetworkReplyPtr replySync = manager.get(request); + QVERIFY(replySync->isFinished()); // synchronous + QCOMPARE(authspy.count(), 0); + + // we cannot use a data reader here, since that connects to the readyRead signal, + // just use readAll() + + // the only thing we check here is that the auth cache was used when using synchronous requests + QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(replySync->readAll(), reference.readAll()); + } +} + +void tst_QNetworkReply::ioGetFromHttpWithAuthSynchronous() +{ + // verify that we do not enter an endless loop with synchronous calls and wrong credentials + // the case when we succed with the login is tested in ioGetFromHttpWithAuth() + + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfcs-auth/rfc3252.txt")); + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QSignalSpy authspy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QNetworkReplyPtr replySync = manager.get(request); + QVERIFY(replySync->isFinished()); // synchronous + QCOMPARE(replySync->error(), QNetworkReply::AuthenticationRequiredError); + QCOMPARE(authspy.count(), 0); + QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 401); } void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() @@ -2180,6 +2332,47 @@ void tst_QNetworkReply::ioGetFromHttpWithProxyAuth() QCOMPARE(authspy.count(), 0); } + + // now check with synchronous calls: + reference.seek(0); + { + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + QNetworkReplyPtr replySync = manager.get(request); + QVERIFY(replySync->isFinished()); // synchronous + QCOMPARE(authspy.count(), 0); + + // we cannot use a data reader here, since that connects to the readyRead signal, + // just use readAll() + + // the only thing we check here is that the proxy auth cache was used when using synchronous requests + QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(replySync->readAll(), reference.readAll()); + } +} + +void tst_QNetworkReply::ioGetFromHttpWithProxyAuthSynchronous() +{ + // verify that we do not enter an endless loop with synchronous calls and wrong credentials + // the case when we succed with the login is tested in ioGetFromHttpWithAuth() + + QNetworkProxy proxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + manager.setProxy(proxy); + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + QNetworkReplyPtr replySync = manager.get(request); + manager.setProxy(QNetworkProxy()); // reset + QVERIFY(replySync->isFinished()); // synchronous + QCOMPARE(replySync->error(), QNetworkReply::ProxyAuthenticationRequiredError); + QCOMPARE(authspy.count(), 0); + QCOMPARE(replySync->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 407); } void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() @@ -3238,7 +3431,67 @@ void tst_QNetworkReply::ioPostToHttpFromSocket() QTEST(authenticationRequiredSpy.count(), "authenticationRequiredCount"); QTEST(proxyAuthenticationRequiredSpy.count(), "proxyAuthenticationRequiredCount"); - } +} + +void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous_data() +{ + QTest::addColumn("data"); + QTest::addColumn("md5sum"); + + QByteArray data; + data = ""; + QTest::newRow("empty") << data << md5sum(data); + + data = "This is a normal message."; + QTest::newRow("generic") << data << md5sum(data); + + data = "This is a message to show that Qt rocks!\r\n\n"; + QTest::newRow("small") << data << md5sum(data); + + data = QByteArray("abcd\0\1\2\abcd",12); + QTest::newRow("with-nul") << data << md5sum(data); + + data = QByteArray(4097, '\4'); + QTest::newRow("4k+1") << data << md5sum(data); + + data = QByteArray(128*1024+1, '\177'); + QTest::newRow("128k+1") << data << md5sum(data); + + data = QByteArray(2*1024*1024+1, '\177'); + QTest::newRow("2MB+1") << data << md5sum(data); +} + +void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous() +{ + QFETCH(QByteArray, data); + + SocketPair socketpair; + QVERIFY(socketpair.create()); + QVERIFY(socketpair.endPoints[0] && socketpair.endPoints[1]); + socketpair.endPoints[0]->write(data); + socketpair.endPoints[0]->waitForBytesWritten(5000); + // ### for 4.8: make the socket pair unbuffered, to not read everything in one go in QNetworkReplyImplPrivate::setup() + QTestEventLoop::instance().enterLoop(3); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"); + QNetworkRequest request(url); + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply = manager.post(request, socketpair.endPoints[1]); + QVERIFY(reply->isFinished()); + socketpair.endPoints[0]->close(); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + // verify that the HTTP status code is 200 Ok + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); +} // this tests checks if rewinding the POST-data to some place in the middle // worked. @@ -3981,6 +4234,38 @@ void tst_QNetworkReply::receiveCookiesFromHttp() QTEST(cookieJar->allCookies(), "expectedCookiesInJar"); } +void tst_QNetworkReply::receiveCookiesFromHttpSynchronous_data() +{ + tst_QNetworkReply::receiveCookiesFromHttp_data(); +} + +void tst_QNetworkReply::receiveCookiesFromHttpSynchronous() +{ + QFETCH(QString, cookieString); + + QByteArray data = cookieString.toLatin1() + '\n'; + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/set-cookie.cgi"); + + QNetworkRequest request(url); + + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply; + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PostOperation, request, reply, data)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QList setCookies = + qvariant_cast >(reply->header(QNetworkRequest::SetCookieHeader)); + QTEST(setCookies, "expectedCookiesFromHttp"); + QTEST(cookieJar->allCookies(), "expectedCookiesInJar"); +} + void tst_QNetworkReply::sendCookies_data() { QTest::addColumn >("cookiesToSet"); @@ -4041,6 +4326,35 @@ void tst_QNetworkReply::sendCookies() QCOMPARE(QString::fromLatin1(reply->readAll()).trimmed(), expectedCookieString); } +void tst_QNetworkReply::sendCookiesSynchronous_data() +{ + tst_QNetworkReply::sendCookies_data(); +} + +void tst_QNetworkReply::sendCookiesSynchronous() +{ + QFETCH(QString, expectedCookieString); + QFETCH(QList, cookiesToSet); + cookieJar->setAllCookies(cookiesToSet); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/get-cookie.cgi"); + QNetworkRequest request(url); + + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply; + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QCOMPARE(QString::fromLatin1(reply->readAll()).trimmed(), expectedCookieString); +} + void tst_QNetworkReply::nestedEventLoops_slot() { QEventLoop subloop; @@ -4144,6 +4458,49 @@ private: int signalCount; }; +void tst_QNetworkReply::httpProxyCommandsSynchronous_data() +{ + httpProxyCommands_data(); +} + +void tst_QNetworkReply::httpProxyCommandsSynchronous() +{ + QFETCH(QUrl, url); + QFETCH(QByteArray, responseToSend); + QFETCH(QString, expectedCommand); + + // when using synchronous commands, we need a different event loop for + // the server thread, because the client is never returning to the + // event loop + MiniHttpServer proxyServer(responseToSend); + QThread serverThread; + proxyServer.moveToThread(&serverThread); + serverThread.start(); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer.serverPort()); + + manager.setProxy(proxy); + QNetworkRequest request(url); + + // send synchronous request + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply = manager.get(request); + QVERIFY(reply->isFinished()); // synchronous + manager.setProxy(QNetworkProxy()); + serverThread.quit(); + + //qDebug() << reply->error() << reply->errorString(); + + // we don't really care if the request succeeded + // especially since it won't succeed in the HTTPS case + // so just check that the command was correct + + QString receivedHeader = proxyServer.receivedData.left(expectedCommand.length()); + QCOMPARE(receivedHeader, expectedCommand); +} + void tst_QNetworkReply::proxyChange() { ProxyChangeHelper helper; @@ -4746,7 +5103,122 @@ void tst_QNetworkReply::qtbug15311doubleContentLength() QCOMPARE(reply->readAll(), QByteArray("ABC")); } +void tst_QNetworkReply::synchronousRequest_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expected"); + QTest::addColumn("checkContentLength"); + QTest::addColumn("mimeType"); + + // ### cache, auth, proxies + + QTest::newRow("http") + << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt") + << QString("file:" SRCDIR "/rfc3252.txt") + << true + << QString("text/plain"); + + QTest::newRow("http-gzip") + << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/deflate/rfc3252.txt") + << QString("file:" SRCDIR "/rfc3252.txt") + << false // don't check content length, because it's gzip encoded + // ### we would need to enflate (un-deflate) the file content and compare the sizes + << QString("text/plain"); + +#ifndef QT_NO_OPENSSL + QTest::newRow("https") + << QUrl("https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt") + << QString("file:" SRCDIR "/rfc3252.txt") + << true + << QString("text/plain"); +#endif + + QTest::newRow("data") + << QUrl(QString::fromLatin1("data:text/plain,hello world")) + << QString("data:hello world") + << true // check content length + << QString("text/plain"); + + QTest::newRow("simple-file") + << QUrl(QString::fromLatin1("file:///" SRCDIR "/rfc3252.txt")) + << QString("file:" SRCDIR "/rfc3252.txt") + << true + << QString(); +} + +// FIXME add testcase for failing network etc +void tst_QNetworkReply::synchronousRequest() +{ + QFETCH(QUrl, url); + QFETCH(QString, expected); + QFETCH(bool, checkContentLength); + QFETCH(QString, mimeType); + + QNetworkRequest request(url); + +#ifndef QT_NO_OPENSSL + // workaround for HTTPS requests: add self-signed server cert to list of CA certs, + // since we cannot react to the sslErrors() signal + // to fix this properly we would need to have an ignoreSslErrors() method in the + // QNetworkRequest, see http://bugreports.qt.nokia.com/browse/QTBUG-14774 + if (url.scheme() == "https") { + QSslConfiguration sslConf; + QList certs = QSslCertificate::fromPath(SRCDIR "/certs/qt-test-server-cacert.pem"); + sslConf.setCaCertificates(certs); + request.setSslConfiguration(sslConf); + } +#endif + + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + + QNetworkReplyPtr reply; + QSignalSpy finishedSpy(&manager, SIGNAL(finished(QNetworkReply*))); + QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList))); + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0)); + QVERIFY(reply->isFinished()); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); + + QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), mimeType); + + QByteArray expectedContent; + + if (expected.startsWith("file:")) { + QString path = expected.mid(5); + QFile file(path); + file.open(QIODevice::ReadOnly); + expectedContent = file.readAll(); + } else if (expected.startsWith("data:")) { + expectedContent = expected.mid(5).toUtf8(); + } + + if (checkContentLength) + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(expectedContent.size())); + QCOMPARE(reply->readAll(), expectedContent); + + reply->deleteLater(); +} + +void tst_QNetworkReply::synchronousRequestSslFailure() +{ + // test that SSL won't be accepted with self-signed certificate, + // and that we do not emit the sslError signal (in the manager that is, + // in the reply we don't care) + QUrl url("https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"); + QNetworkRequest request(url); + request.setAttribute( + static_cast(SynchronousRequestAttribute), + true); + QNetworkReplyPtr reply; + QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply *, const QList &))); + runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 0); +} // NOTE: This test must be last testcase in tst_qnetworkreply! void tst_QNetworkReply::parentingRepliesToTheApp() -- cgit v0.12 From b91221ebe4acc5f59793b1070dd134ed56eb91fb Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Tue, 23 Nov 2010 17:53:25 +0100 Subject: HTTP backend: fix build without Qt3 support Reviewed-by: Markus Goetz --- src/network/access/qnetworkaccesshttpbackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index 070111d..9df5d7b 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -1157,7 +1157,7 @@ bool QNetworkAccessHttpBackend::processRequestSynchronously() bool waitResult = channel->socket->waitForConnected(timeout); timeoutTimer.start(); - if (!waitResult || channel->socket->state() != QAbstractSocket::Connected) { + if (!waitResult || channel->socket->state() != QAbstractSocket::ConnectedState) { error(QNetworkReply::UnknownNetworkError, QLatin1String("could not connect")); return false; } -- cgit v0.12 From ba9816d1160998abe75b8b45f4b1c14985119553 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Wed, 24 Nov 2010 11:57:49 +0100 Subject: Ensure that if this is does not have a valid filter when on XP or less The renderer is only supported on Windows Vista or later so if we are on an earlier version of Windows then this should not be used. In addition this patch also ensures that it resets the filter if any of the needed functions fail. Task-number: QTBUG-13062 Reviewed-by: Thierry Bastian --- src/3rdparty/phonon/ds9/videorenderer_evr.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/3rdparty/phonon/ds9/videorenderer_evr.cpp b/src/3rdparty/phonon/ds9/videorenderer_evr.cpp index de3f46f..ff39eccc4 100644 --- a/src/3rdparty/phonon/ds9/videorenderer_evr.cpp +++ b/src/3rdparty/phonon/ds9/videorenderer_evr.cpp @@ -62,19 +62,21 @@ namespace Phonon VideoRendererEVR::VideoRendererEVR(QWidget *target) : m_target(target) { + if (QSysInfo::WindowsVersion < QSysInfo::WV_VISTA) + return; m_filter = Filter(CLSID_EnhancedVideoRenderer, IID_IBaseFilter); if (!m_filter) { return; } ComPointer filterControl = getService(m_filter, MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl); - if (!filterControl) { + if (!filterControl || + FAILED(filterControl->SetVideoWindow(reinterpret_cast(target->winId()))) || + FAILED(filterControl->SetAspectRatioMode(MFVideoARMode_None)) || // We're in control of the size + !getService(m_filter, MR_VIDEO_MIXER_SERVICE, IID_IMFVideoMixerControl) || + !getService(m_filter, MR_VIDEO_MIXER_SERVICE, IID_IMFVideoProcessor)) { m_filter = Filter(); //will release the interface - return; } - - filterControl->SetVideoWindow(reinterpret_cast(target->winId())); - filterControl->SetAspectRatioMode(MFVideoARMode_None); // We're in control of the size } QImage VideoRendererEVR::snapshot() const -- cgit v0.12 From cbf7a7782f465846455a5fd5df339ebde31a5521 Mon Sep 17 00:00:00 2001 From: Ville Pernu Date: Wed, 24 Nov 2010 12:59:36 +0200 Subject: Fix a missing error-signal when a server is shut down while downloading QT-3494: During download, if a QAbstractSocket::RemoteHostClosedError occurs, the error handling is deferred to _q_disconnected() slot. However, the error message is not saved for the function, and thus the _q_disconnected only emits a finished-signal. Solution: Store an unhandled error into a private member. It is handled and reset by the _q_disconnected (or more specifically, allDone-function). --- src/network/access/qhttpnetworkconnectionchannel.cpp | 18 +++++++++++++++++- src/network/access/qhttpnetworkconnectionchannel_p.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 02daa50..39f4811 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -66,6 +66,7 @@ QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel() , bytesTotal(0) , resendCurrent(false) , lastStatus(0) + , unhandledError(QNetworkReply::NoError) , pendingEncrypt(false) , reconnectAttempts(2) , authMethod(QAuthenticatorPrivate::None) @@ -643,7 +644,21 @@ void QHttpNetworkConnectionChannel::allDone() // slot connected to it. The socket will not fire readyRead signal, if we are already // in the slot connected to readyRead if (emitFinished) - QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection); + { + // Check whether _q_error was invoked previously and if it left a socket + // error unhandled. + if(unhandledError != QNetworkReply::NoError) { + QString errorString = connection->d_func()->errorDetail(unhandledError, socket, socket->errorString()); + qRegisterMetaType("QNetworkReply::NetworkError"); + QMetaObject::invokeMethod(reply, "finishedWithError", + Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, unhandledError), + Q_ARG(QString, errorString)); + unhandledError = QNetworkReply::NoError; // Reset the value + } else { + QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection); + } + } // reset the reconnection attempts after we receive a complete reply. // in case of failures, each channel will attempt two reconnects before emitting error. reconnectAttempts = 2; @@ -965,6 +980,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket errorCode = QNetworkReply::RemoteHostClosedError; } } else { + unhandledError = QNetworkReply::RemoteHostClosedError; return; } break; diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index 442086a..33cef5a 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -105,6 +105,7 @@ public: qint64 bytesTotal; bool resendCurrent; int lastStatus; // last status received on this channel + QNetworkReply::NetworkError unhandledError; // Stored code of an unhandled error. bool pendingEncrypt; // for https (send after encrypted) int reconnectAttempts; // maximum 2 reconnection attempts QAuthenticatorPrivate::Method authMethod; -- cgit v0.12