diff options
Diffstat (limited to 'tests/auto/qnetworkreply/tst_qnetworkreply.cpp')
-rw-r--r-- | tests/auto/qnetworkreply/tst_qnetworkreply.cpp | 6172 |
1 files changed, 6172 insertions, 0 deletions
diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp new file mode 100644 index 0000000000..f509ceaad6 --- /dev/null +++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp @@ -0,0 +1,6172 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> +#include <QtCore/QCryptographicHash> +#include <QtCore/QDataStream> +#include <QtCore/QUrl> +#include <QtCore/QEventLoop> +#include <QtCore/QFile> +#include <QtCore/QSharedPointer> +#include <QtCore/QScopedPointer> +#include <QtCore/QTemporaryFile> +#include <QtNetwork/QTcpServer> +#include <QtNetwork/QTcpSocket> +#include <QtNetwork/QLocalSocket> +#include <QtNetwork/QLocalServer> +#include <QtNetwork/QHostInfo> +#include <QtNetwork/QFtp> +#include <QtNetwork/QAbstractNetworkCache> +#include <QtNetwork/qauthenticator.h> +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtNetwork/qnetworkrequest.h> +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qnetworkcookie.h> +#include <QtNetwork/QHttpPart> +#include <QtNetwork/QHttpMultiPart> +#ifndef QT_NO_OPENSSL +#include <QtNetwork/qsslerror.h> +#include <QtNetwork/qsslconfiguration.h> +#endif +#ifndef QT_NO_BEARERMANAGEMENT +#include <QtNetwork/qnetworkconfigmanager.h> +#include <QtNetwork/qnetworkconfiguration.h> +#include <QtNetwork/qnetworksession.h> +#endif + +#include <time.h> + +#include "private/qnetworkaccessmanager_p.h" + +#ifdef Q_OS_SYMBIAN +#define SRCDIR "." +#endif + +#include "../network-settings.h" + +Q_DECLARE_METATYPE(QSharedPointer<char>) +Q_DECLARE_METATYPE(QNetworkReply*) +Q_DECLARE_METATYPE(QAuthenticator*) +Q_DECLARE_METATYPE(QNetworkProxy) +Q_DECLARE_METATYPE(QNetworkProxyQuery) +Q_DECLARE_METATYPE(QList<QNetworkProxy>) +Q_DECLARE_METATYPE(QNetworkReply::NetworkError) +Q_DECLARE_METATYPE(QBuffer*) +Q_DECLARE_METATYPE(QHttpMultiPart *) +Q_DECLARE_METATYPE(QList<QFile*>) // for multiparts +#ifndef QT_NO_OPENSSL +Q_DECLARE_METATYPE(QSslConfiguration) +#endif + +class QNetworkReplyPtr: public QSharedPointer<QNetworkReply> +{ +public: + inline QNetworkReplyPtr(QNetworkReply *ptr = 0) + : QSharedPointer<QNetworkReply>(ptr) + { } + + inline operator QNetworkReply *() const { return data(); } +}; + +class MyCookieJar; +class tst_QNetworkReply: public QObject +{ + Q_OBJECT + + struct ProxyData { + ProxyData(const QNetworkProxy &p, const QByteArray &t, bool auth) + : tag(t), proxy(p), requiresAuthentication(auth) + { } + QByteArray tag; + QNetworkProxy proxy; + 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; + QString testFileName; +#if !defined Q_OS_WIN + QString wronlyFileName; +#endif + QString uniqueExtension; + QList<ProxyData> proxies; + QNetworkAccessManager manager; + MyCookieJar *cookieJar; +#ifndef QT_NO_OPENSSL + QSslConfiguration storedSslConfiguration; + QList<QSslError> storedExpectedSslErrors; +#endif +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkConfigurationManager *netConfMan; + QNetworkConfiguration networkConfiguration; + QScopedPointer<QNetworkSession> networkSession; +#endif + +public: + tst_QNetworkReply(); + ~tst_QNetworkReply(); + QString runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QNetworkReplyPtr &reply, const QByteArray &data = QByteArray()); + QString runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, + QHttpMultiPart *multiPart, const QByteArray &verb); + + QString runCustomRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, + const QByteArray &verb, QIODevice *data); + +public Q_SLOTS: + void finished(); + void gotError(); + void authenticationRequired(QNetworkReply*,QAuthenticator*); + void proxyAuthenticationRequired(const QNetworkProxy &,QAuthenticator*); + +#ifndef QT_NO_OPENSSL + void sslErrors(QNetworkReply*,const QList<QSslError> &); + void storeSslConfiguration(); + void ignoreSslErrorListSlot(QNetworkReply *reply, const QList<QSslError> &); +#endif + +protected Q_SLOTS: + void nestedEventLoops_slot(); + +private Q_SLOTS: + void init(); + void cleanup(); + void initTestCase(); + void cleanupTestCase(); + + void stateChecking(); + void invalidProtocol(); + void getFromData_data(); + void getFromData(); + void getFromFile(); + void getFromFileSpecial_data(); + void getFromFileSpecial(); + void getFromFtp_data(); + void getFromFtp(); + void getFromHttp_data(); + void getFromHttp(); + void getErrors_data(); + void getErrors(); + void putToFile_data(); + void putToFile(); + void putToFtp_data(); + void putToFtp(); + void putToHttp_data(); + void putToHttp(); + void putToHttpSynchronous_data(); + void putToHttpSynchronous(); + void putToHttpMultipart_data(); + void putToHttpMultipart(); + void postToHttp_data(); + void postToHttp(); + void postToHttpSynchronous_data(); + void postToHttpSynchronous(); + void postToHttpMultipart_data(); + void postToHttpMultipart(); + void deleteFromHttp_data(); + void deleteFromHttp(); + void putGetDeleteGetFromHttp_data(); + void putGetDeleteGetFromHttp(); + void sendCustomRequestToHttp_data(); + void sendCustomRequestToHttp(); + + void ioGetFromData_data(); + void ioGetFromData(); + void ioGetFromFileSpecial_data(); + void ioGetFromFileSpecial(); + void ioGetFromFile_data(); + void ioGetFromFile(); + void ioGetFromFtp_data(); + void ioGetFromFtp(); + void ioGetFromFtpWithReuse(); + void ioGetFromHttp(); + + void ioGetFromBuiltinHttp_data(); + void ioGetFromBuiltinHttp(); + void ioGetFromHttpWithReuseParallel(); + void ioGetFromHttpWithReuseSequential(); + void ioGetFromHttpWithAuth_data(); + void ioGetFromHttpWithAuth(); + void ioGetFromHttpWithAuthSynchronous(); + void ioGetFromHttpWithProxyAuth(); + void ioGetFromHttpWithProxyAuthSynchronous(); + void ioGetFromHttpWithSocksProxy(); +#ifndef QT_NO_OPENSSL + void ioGetFromHttpsWithSslErrors(); + void ioGetFromHttpsWithIgnoreSslErrors(); + void ioGetFromHttpsWithSslHandshakeError(); +#endif + void ioGetFromHttpBrokenServer_data(); + void ioGetFromHttpBrokenServer(); + void ioGetFromHttpStatus100_data(); + void ioGetFromHttpStatus100(); + void ioGetFromHttpNoHeaders_data(); + void ioGetFromHttpNoHeaders(); + void ioGetFromHttpWithCache_data(); + void ioGetFromHttpWithCache(); + + void ioGetWithManyProxies_data(); + void ioGetWithManyProxies(); + + void ioPutToFileFromFile_data(); + void ioPutToFileFromFile(); + void ioPutToFileFromSocket_data(); + void ioPutToFileFromSocket(); + void ioPutToFileFromLocalSocket_data(); + void ioPutToFileFromLocalSocket(); + void ioPutToFileFromProcess_data(); + void ioPutToFileFromProcess(); + void ioPutToFtpFromFile_data(); + void ioPutToFtpFromFile(); + void ioPutToHttpFromFile_data(); + void ioPutToHttpFromFile(); + void ioPostToHttpFromFile_data(); + void ioPostToHttpFromFile(); + void ioPostToHttpFromSocket_data(); + void ioPostToHttpFromSocket(); + void ioPostToHttpFromSocketSynchronous(); + void ioPostToHttpFromSocketSynchronous_data(); + void ioPostToHttpFromMiddleOfFileToEnd(); + void ioPostToHttpFromMiddleOfFileFiveBytes(); + void ioPostToHttpFromMiddleOfQBufferFiveBytes(); + void ioPostToHttpNoBufferFlag(); + void ioPostToHttpUploadProgress(); + void ioPostToHttpEmptyUploadProgress(); + + void lastModifiedHeaderForFile(); + void lastModifiedHeaderForHttp(); + + void httpCanReadLine(); + + void rateControl_data(); + void rateControl(); + + void downloadProgress_data(); + void downloadProgress(); + void uploadProgress_data(); + void uploadProgress(); + + void chaining_data(); + void chaining(); + + 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(); + + void httpConnectionCount(); + + void httpReUsingConnectionSequential_data(); + void httpReUsingConnectionSequential(); + void httpReUsingConnectionFromFinishedSlot_data(); + void httpReUsingConnectionFromFinishedSlot(); + + void httpRecursiveCreation(); + +#ifndef QT_NO_OPENSSL + void ioPostToHttpsUploadProgress(); + void ignoreSslErrorsList_data(); + void ignoreSslErrorsList(); + void ignoreSslErrorsListWithSlot_data(); + void ignoreSslErrorsListWithSlot(); + void sslConfiguration_data(); + void sslConfiguration(); +#endif + + void getAndThenDeleteObject_data(); + void getAndThenDeleteObject(); + + void symbianOpenCDataUrlCrash(); + + void getFromHttpIntoBuffer_data(); + void getFromHttpIntoBuffer(); + void getFromHttpIntoBuffer2_data(); + void getFromHttpIntoBuffer2(); + + void ioGetFromHttpWithoutContentLength(); + + void ioGetFromHttpBrokenChunkedEncoding(); + void qtbug12908compressedHttpReply(); + void compressedHttpReplyBrokenGzip(); + + void getFromUnreachableIp(); + + void qtbug4121unknownAuthentication(); + + void qtbug13431replyThrottling(); + + void httpWithNoCredentialUsage(); + + void qtbug15311doubleContentLength(); + + void qtbug18232gzipContentLengthZero(); + + void synchronousRequest_data(); + void synchronousRequest(); +#ifndef QT_NO_OPENSSL + void synchronousRequestSslFailure(); +#endif + + void httpAbort(); + + void dontInsertPartialContentIntoTheCache(); + + // NOTE: This test must be last! + void parentingRepliesToTheApp(); +}; + +bool tst_QNetworkReply::seedCreated = false; + +QT_BEGIN_NAMESPACE + +namespace QTest { + template<> + char *toString(const QNetworkReply::NetworkError& code) + { + const QMetaObject *mo = &QNetworkReply::staticMetaObject; + int index = mo->indexOfEnumerator("NetworkError"); + if (index == -1) + return qstrdup(""); + + QMetaEnum qme = mo->enumerator(index); + return qstrdup(qme.valueToKey(code)); + } + + template<> + char *toString(const QNetworkCookie &cookie) + { + return qstrdup(cookie.toRawForm()); + } + + template<> + char *toString(const QList<QNetworkCookie> &list) + { + QString result = "QList("; + bool first = true; + foreach (QNetworkCookie cookie, list) { + if (!first) + result += ", "; + first = false; + result += QString::fromLatin1("QNetworkCookie(%1)").arg(QLatin1String(cookie.toRawForm())); + } + + return qstrdup(result.append(')').toLocal8Bit()); + } +} + +QT_END_NAMESPACE + +#define RUN_REQUEST(call) \ + do { \ + QString errorMsg = call; \ + if (!errorMsg.isEmpty()) \ + QFAIL(qPrintable(errorMsg)); \ + } while (0); + +#ifndef QT_NO_OPENSSL +static void setupSslServer(QSslSocket* serverSocket) +{ + serverSocket->setProtocol(QSsl::AnyProtocol); + serverSocket->setLocalCertificate(SRCDIR "/certs/server.pem"); + serverSocket->setPrivateKey(SRCDIR "/certs/server.key"); +} +#endif + +// Does not work for POST/PUT! +class MiniHttpServer: public QTcpServer +{ + Q_OBJECT +public: + QTcpSocket *client; // always the last one that was received + QByteArray dataToTransmit; + QByteArray receivedData; + QSemaphore ready; + bool doClose; + bool doSsl; + bool multiple; + int totalConnections; + + MiniHttpServer(const QByteArray &data, bool ssl = false, QThread *thread = 0) + : client(0), dataToTransmit(data), doClose(true), doSsl(ssl), + multiple(false), totalConnections(0) + { + listen(); + if (thread) { + connect(thread, SIGNAL(started()), this, SLOT(threadStartedSlot())); + moveToThread(thread); + thread->start(); + ready.acquire(); + } + } + +protected: + void incomingConnection(int socketDescriptor) + { + //qDebug() << "incomingConnection" << socketDescriptor; + if (!doSsl) { + client = new QTcpSocket; + client->setSocketDescriptor(socketDescriptor); + connectSocketSignals(); + } else { +#ifndef QT_NO_OPENSSL + QSslSocket *serverSocket = new QSslSocket; + serverSocket->setParent(this); + if (serverSocket->setSocketDescriptor(socketDescriptor)) { + connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(slotSslErrors(QList<QSslError>))); + setupSslServer(serverSocket); + serverSocket->startServerEncryption(); + client = serverSocket; + connectSocketSignals(); + } else { + delete serverSocket; + return; + } +#endif + } + client->setParent(this); + ++totalConnections; + } +private: + void connectSocketSignals() + { + //qDebug() << "connectSocketSignals" << client; + connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); + connect(client, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWrittenSlot())); + connect(client, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(slotError(QAbstractSocket::SocketError))); + } + +private slots: +#ifndef QT_NO_OPENSSL + void slotSslErrors(const QList<QSslError>& errors) + { + qDebug() << "slotSslErrors" << client->errorString() << errors; + } +#endif + void slotError(QAbstractSocket::SocketError err) + { + qDebug() << "slotError" << err << client->errorString(); + } + +public slots: + void readyReadSlot() + { + receivedData += client->readAll(); + int doubleEndlPos = receivedData.indexOf("\r\n\r\n"); + + if (doubleEndlPos != -1) { + // multiple requests incoming. remove the bytes of the current one + if (multiple) + receivedData.remove(0, doubleEndlPos+4); + + // we need to emulate the bytesWrittenSlot call if the data is empty. + if (dataToTransmit.size() == 0) + QMetaObject::invokeMethod(this, "bytesWrittenSlot", Qt::QueuedConnection); + else + client->write(dataToTransmit); + } + } + + void bytesWrittenSlot() { + if (doClose && client->bytesToWrite() == 0) { + client->disconnectFromHost(); + disconnect(client, 0, this, 0); + } + } + + void threadStartedSlot() + { + ready.release(); + } +}; + +class MyCookieJar: public QNetworkCookieJar +{ +public: + inline QList<QNetworkCookie> allCookies() const + { return QNetworkCookieJar::allCookies(); } + inline void setAllCookies(const QList<QNetworkCookie> &cookieList) + { QNetworkCookieJar::setAllCookies(cookieList); } +}; + +class MyProxyFactory: public QNetworkProxyFactory +{ +public: + int callCount; + QList<QNetworkProxy> toReturn; + QNetworkProxyQuery lastQuery; + inline MyProxyFactory() { clear(); } + + inline void clear() + { + callCount = 0; + toReturn = QList<QNetworkProxy>() << QNetworkProxy::DefaultProxy; + lastQuery = QNetworkProxyQuery(); + } + + virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query) + { + lastQuery = query; + ++callCount; + return toReturn; + } +}; + +class MyMemoryCache: public QAbstractNetworkCache +{ +public: + typedef QPair<QNetworkCacheMetaData, QByteArray> CachedContent; + typedef QHash<QByteArray, CachedContent> CacheData; + CacheData cache; + + MyMemoryCache(QObject *parent) : QAbstractNetworkCache(parent) {} + + QNetworkCacheMetaData metaData(const QUrl &url) + { + return cache.value(url.toEncoded()).first; + } + + void updateMetaData(const QNetworkCacheMetaData &metaData) + { + cache[metaData.url().toEncoded()].first = metaData; + } + + QIODevice *data(const QUrl &url) + { + CacheData::ConstIterator it = cache.find(url.toEncoded()); + if (it == cache.constEnd()) + return 0; + QBuffer *io = new QBuffer(this); + io->setData(it->second); + io->open(QIODevice::ReadOnly); + io->seek(0); + return io; + } + + bool remove(const QUrl &url) + { + cache.remove(url.toEncoded()); + return true; + } + + qint64 cacheSize() const + { + qint64 total = 0; + foreach (const CachedContent &entry, cache) + total += entry.second.size(); + return total; + } + + QIODevice *prepare(const QNetworkCacheMetaData &) + { Q_ASSERT(0 && "Should not have tried to add to the cache"); return 0; } + void insert(QIODevice *) + { Q_ASSERT(0 && "Should not have tried to add to the cache"); } + + void clear() { cache.clear(); } +}; +Q_DECLARE_METATYPE(MyMemoryCache::CachedContent) +Q_DECLARE_METATYPE(MyMemoryCache::CacheData) + +class MySpyMemoryCache: public QAbstractNetworkCache +{ +public: + MySpyMemoryCache(QObject *parent) : QAbstractNetworkCache(parent) {} + ~MySpyMemoryCache() + { + qDeleteAll(m_buffers); + m_buffers.clear(); + } + + QHash<QUrl, QIODevice*> m_buffers; + QList<QUrl> m_insertedUrls; + + QNetworkCacheMetaData metaData(const QUrl &) + { + return QNetworkCacheMetaData(); + } + + void updateMetaData(const QNetworkCacheMetaData &) + { + } + + QIODevice *data(const QUrl &) + { + return 0; + } + + bool remove(const QUrl &url) + { + delete m_buffers.take(url); + return m_insertedUrls.removeAll(url) > 0; + } + + qint64 cacheSize() const + { + return 0; + } + + QIODevice *prepare(const QNetworkCacheMetaData &metaData) + { + QBuffer* buffer = new QBuffer; + buffer->open(QIODevice::ReadWrite); + buffer->setProperty("url", metaData.url()); + m_buffers.insert(metaData.url(), buffer); + return buffer; + } + + void insert(QIODevice *buffer) + { + QUrl url = buffer->property("url").toUrl(); + m_insertedUrls << url; + delete m_buffers.take(url); + } + + void clear() { m_insertedUrls.clear(); } +}; + +class DataReader: public QObject +{ + Q_OBJECT +public: + qint64 totalBytes; + QByteArray data; + QIODevice *device; + bool accumulate; + DataReader(QIODevice *dev, bool acc = true) : totalBytes(0), device(dev), accumulate(acc) + { + connect(device, SIGNAL(readyRead()), SLOT(doRead())); + } + +public slots: + void doRead() + { + QByteArray buffer; + buffer.resize(device->bytesAvailable()); + qint64 bytesRead = device->read(buffer.data(), device->bytesAvailable()); + if (bytesRead == -1) { + QTestEventLoop::instance().exitLoop(); + return; + } + buffer.truncate(bytesRead); + totalBytes += bytesRead; + + if (accumulate) + data += buffer; + } +}; + + +class SocketPair: public QObject +{ + Q_OBJECT +public: + QIODevice *endPoints[2]; + + SocketPair(QObject *parent = 0) + : QObject(parent) + { + endPoints[0] = endPoints[1] = 0; + } + + bool create() + { + QTcpServer server; + server.listen(); + + QTcpSocket *active = new QTcpSocket(this); + active->connectToHost("127.0.0.1", server.serverPort()); +#ifndef Q_OS_SYMBIAN + // need more time as working with embedded + // device and testing from emualtor + // things tend to get slower + if (!active->waitForConnected(1000)) + return false; + + if (!server.waitForNewConnection(1000)) + return false; +#else + if (!active->waitForConnected(100)) + return false; + + if (!server.waitForNewConnection(100)) + return false; +#endif + QTcpSocket *passive = server.nextPendingConnection(); + passive->setParent(this); + + endPoints[0] = active; + endPoints[1] = passive; + return true; + } +}; + +// A blocking tcp server (must be used in a thread) which supports SSL. +class BlockingTcpServer : public QTcpServer +{ + Q_OBJECT +public: + BlockingTcpServer(bool ssl) : doSsl(ssl), sslSocket(0) {} + + QTcpSocket* waitForNextConnectionSocket() { + waitForNewConnection(-1); + if (doSsl) { + Q_ASSERT(sslSocket); + return sslSocket; + } else { + //qDebug() << "returning nextPendingConnection"; + return nextPendingConnection(); + } + } + virtual void incomingConnection(int socketDescriptor) + { +#ifndef QT_NO_OPENSSL + if (doSsl) { + QSslSocket *serverSocket = new QSslSocket; + serverSocket->setParent(this); + serverSocket->setSocketDescriptor(socketDescriptor); + connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(slotSslErrors(QList<QSslError>))); + setupSslServer(serverSocket); + serverSocket->startServerEncryption(); + sslSocket = serverSocket; + } else +#endif + { + QTcpServer::incomingConnection(socketDescriptor); + } + } +private slots: + +#ifndef QT_NO_OPENSSL + void slotSslErrors(const QList<QSslError>& errors) + { + qDebug() << "slotSslErrors" << sslSocket->errorString() << errors; + } +#endif + +private: + const bool doSsl; + QTcpSocket* sslSocket; +}; + +// This server tries to send data as fast as possible (like most servers) +// but it measures how fast it was able to send it, which shows at which +// rate the reader is processing the data. +class FastSender: public QThread +{ + Q_OBJECT + QSemaphore ready; + qint64 wantedSize; + int port; + enum Protocol { DebugPipe, ProvidedData }; + const Protocol protocol; + const bool doSsl; + const bool fillKernelBuffer; +public: + int transferRate; + QWaitCondition cond; + + QByteArray dataToTransmit; + int dataIndex; + + // a server that sends debugpipe data + FastSender(qint64 size) + : wantedSize(size), port(-1), protocol(DebugPipe), + doSsl(false), fillKernelBuffer(true), transferRate(-1), + dataIndex(0) + { + start(); + ready.acquire(); + } + + // a server that sends the data provided at construction time, useful for HTTP + FastSender(const QByteArray& data, bool https, bool fillBuffer) + : wantedSize(data.size()), port(-1), protocol(ProvidedData), + doSsl(https), fillKernelBuffer(fillBuffer), transferRate(-1), + dataToTransmit(data), dataIndex(0) + { + start(); + ready.acquire(); + } + + inline int serverPort() const { return port; } + + int writeNextData(QTcpSocket* socket, qint32 size) + { + if (protocol == DebugPipe) { + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << QVariantMap() << QByteArray(size, 'a'); + socket->write((char*)&size, sizeof size); + socket->write(data); + dataIndex += size; + return size; + } else { + const QByteArray data = dataToTransmit.mid(dataIndex, size); + socket->write(data); + dataIndex += data.size(); + //qDebug() << "wrote" << dataIndex << "/" << dataToTransmit.size(); + return data.size(); + } + } + void writeLastData(QTcpSocket* socket) + { + if (protocol == DebugPipe) { + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << QVariantMap() << QByteArray(); + const qint32 size = data.size(); + socket->write((char*)&size, sizeof size); + socket->write(data); + } + } + +protected: + void run() + { + BlockingTcpServer server(doSsl); + server.listen(); + port = server.serverPort(); + ready.release(); + + QTcpSocket *client = server.waitForNextConnectionSocket(); + + // get the "request" packet + if (!client->waitForReadyRead(2000)) { + qDebug() << "FastSender:" << client->error() << "waiting for \"request\" packet"; + return; + } + client->readAll(); // we're not interested in the actual contents (e.g. HTTP request) + + enum { BlockSize = 1024 }; + + if (fillKernelBuffer) { + + // write a bunch of bytes to fill up the buffers + bool done = false; + do { + if (writeNextData(client, BlockSize) < BlockSize) { + qDebug() << "ERROR: FastSender: not enough data to write in order to fill buffers; or client is reading too fast"; + return; + } + while (client->bytesToWrite() > 0) { + if (!client->waitForBytesWritten(0)) { + done = true; + break; + } + } + //qDebug() << "Filling kernel buffer: wrote" << dataIndex << "bytes"; + } while (!done); + + qDebug() << "FastSender: ok, kernel buffer is full after writing" << dataIndex << "bytes"; + } + + // Tell the client to start reading + emit dataReady(); + + // the kernel buffer is full + // clean up QAbstractSocket's residue: + while (client->bytesToWrite() > 0) { + qDebug() << "Still having" << client->bytesToWrite() << "bytes to write, doing that now"; + if (!client->waitForBytesWritten(2000)) { + qDebug() << "ERROR: FastSender:" << client->error() << "cleaning up residue"; + return; + } + } + + // now write in "blocking mode", this is where the rate measuring starts + QTime timer; + timer.start(); + //const qint64 writtenBefore = dataIndex; + //qint64 measuredTotalBytes = wantedSize - writtenBefore; + qint64 measuredSentBytes = 0; + while (dataIndex < wantedSize) { + const int remainingBytes = wantedSize - measuredSentBytes; + const int bytesToWrite = qMin(remainingBytes, static_cast<int>(BlockSize)); + Q_ASSERT(bytesToWrite); + measuredSentBytes += writeNextData(client, bytesToWrite); + + while (client->bytesToWrite() > 0) { + if (!client->waitForBytesWritten(2000)) { + qDebug() << "ERROR: FastSender:" << client->error() << "during blocking write"; + return; + } + } + /*qDebug() << "FastSender:" << bytesToWrite << "bytes written now;" + << measuredSentBytes << "measured bytes" << measuredSentBytes + writtenBefore << "total (" + << measuredSentBytes*100/measuredTotalBytes << "% complete);" + << timer.elapsed() << "ms elapsed";*/ + } + + transferRate = measuredSentBytes * 1000 / timer.elapsed(); + qDebug() << "FastSender: flushed" << measuredSentBytes << "bytes in" << timer.elapsed() << "ms: rate =" << transferRate << "B/s"; + + // write a "close connection" packet, if the protocol needs it + writeLastData(client); + } +signals: + void dataReady(); +}; + +class RateControlledReader: public QObject +{ + Q_OBJECT + QIODevice *device; + int bytesToRead; + int interval; + int readBufferSize; +public: + QByteArray data; + qint64 totalBytesRead; + RateControlledReader(QObject& senderObj, QIODevice *dev, int kbPerSec, int maxBufferSize = 0) + : device(dev), readBufferSize(maxBufferSize), totalBytesRead(0) + { + // determine how often we have to wake up + int timesPerSecond; + if (readBufferSize == 0) { + // The requirement is simply "N KB per seconds" + timesPerSecond = 20; + bytesToRead = kbPerSec * 1024 / timesPerSecond; + } else { + // The requirement also includes "<readBufferSize> bytes at a time" + bytesToRead = readBufferSize; + timesPerSecond = kbPerSec * 1024 / readBufferSize; + } + interval = 1000 / timesPerSecond; // in ms + + qDebug() << "RateControlledReader: going to read" << bytesToRead + << "bytes every" << interval << "ms"; + qDebug() << "actual read rate will be" + << (bytesToRead * 1000 / interval) << "bytes/sec (wanted" + << kbPerSec * 1024 << "bytes/sec)"; + + // Wait for data to be readyRead + bool ok = connect(&senderObj, SIGNAL(dataReady()), this, SLOT(slotDataReady())); + Q_ASSERT(ok); + } + + void wrapUp() + { + QByteArray someData = device->read(device->bytesAvailable()); + data += someData; + totalBytesRead += someData.size(); + qDebug() << "wrapUp: found" << someData.size() << "bytes left. progress" << data.size(); + //qDebug() << "wrapUp: now bytesAvailable=" << device->bytesAvailable(); + } + +private slots: + void slotDataReady() + { + //qDebug() << "RateControlledReader: ready to go"; + startTimer(interval); + } + +protected: + void timerEvent(QTimerEvent *) + { + //qDebug() << "RateControlledReader: timerEvent bytesAvailable=" << device->bytesAvailable(); + if (readBufferSize > 0) { + // This asserts passes all the time, except in the final flush. + //Q_ASSERT(device->bytesAvailable() <= readBufferSize); + } + + qint64 bytesRead = 0; + QTime stopWatch; + stopWatch.start(); + do { + if (device->bytesAvailable() == 0) { + if (stopWatch.elapsed() > 20) { + qDebug() << "RateControlledReader: Not enough data available for reading, waited too much, timing out"; + break; + } + if (!device->waitForReadyRead(5)) { + qDebug() << "RateControlledReader: Not enough data available for reading, even after waiting 5ms, bailing out"; + break; + } + } + QByteArray someData = device->read(bytesToRead - bytesRead); + data += someData; + bytesRead += someData.size(); + //qDebug() << "RateControlledReader: successfully read" << someData.size() << "progress:" << data.size(); + } while (bytesRead < bytesToRead); + totalBytesRead += bytesRead; + + if (bytesRead < bytesToRead) + qWarning() << "RateControlledReader: WARNING:" << bytesToRead - bytesRead << "bytes not read"; + } +}; + + +tst_QNetworkReply::tst_QNetworkReply() +{ + qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy + qRegisterMetaType<QAuthenticator *>(); + qRegisterMetaType<QNetworkProxy>(); +#ifndef QT_NO_OPENSSL + qRegisterMetaType<QList<QSslError> >(); +#endif + qRegisterMetaType<QNetworkReply::NetworkError>(); + + Q_SET_DEFAULT_IAP + + testFileName = QDir::currentPath() + "/testfile"; + uniqueExtension = createUniqueExtension(); + cookieJar = new MyCookieJar; + manager.setCookieJar(cookieJar); + + QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName()); + + proxies << ProxyData(QNetworkProxy::NoProxy, "", false); + + if (hostInfo.error() == QHostInfo::NoError && !hostInfo.addresses().isEmpty()) { + QString proxyserver = hostInfo.addresses().first().toString(); + proxies << ProxyData(QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128), "+proxy", false) + << ProxyData(QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3129), "+proxyauth", true) + // currently unsupported + // << ProxyData(QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3130), "+proxyauth-ntlm", true); + << ProxyData(QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080), "+socks", false) + << ProxyData(QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1081), "+socksauth", true); + } else { + printf("==================================================================\n"); + printf("Proxy could not be looked up. No proxy will be used while testing!\n"); + printf("==================================================================\n"); + } +} + +tst_QNetworkReply::~tst_QNetworkReply() +{ +} + + +void tst_QNetworkReply::authenticationRequired(QNetworkReply*, QAuthenticator* auth) +{ + auth->setUser("httptest"); + auth->setPassword("httptest"); +} + +void tst_QNetworkReply::proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator* auth) +{ + auth->setUser("qsockstest"); + auth->setPassword("password"); +} + +#ifndef QT_NO_OPENSSL +void tst_QNetworkReply::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors) +{ + reply->ignoreSslErrors(); + QVERIFY(!errors.isEmpty()); + QVERIFY(!reply->sslConfiguration().isNull()); +} + +void tst_QNetworkReply::storeSslConfiguration() +{ + storedSslConfiguration = QSslConfiguration(); + QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); + if (reply) + storedSslConfiguration = reply->sslConfiguration(); +} +#endif + +QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request, + QNetworkReplyPtr &reply, + QHttpMultiPart *multiPart, + const QByteArray &verb) +{ + if (verb == "POST") + reply = manager.post(request, multiPart); + else + reply = manager.put(request, multiPart); + + // the code below is copied from tst_QNetworkReply::runSimpleRequest, see below + reply->setParent(this); + connect(reply, SIGNAL(finished()), SLOT(finished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError())); + multiPart->setParent(reply); + + returnCode = Timeout; + loop = new QEventLoop; + QTimer::singleShot(25000, loop, SLOT(quit())); + int code = returnCode == Timeout ? loop->exec() : returnCode; + delete loop; + loop = 0; + + switch (code) { + case Failure: + return "Request failed: " + reply->errorString(); + case Timeout: + return "Network timeout"; + } + return QString(); +} + +QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, + const QNetworkRequest &request, + QNetworkReplyPtr &reply, + const QByteArray &data) +{ + switch (op) { + case QNetworkAccessManager::HeadOperation: + reply = manager.head(request); + break; + + case QNetworkAccessManager::GetOperation: + reply = manager.get(request); + break; + + case QNetworkAccessManager::PutOperation: + reply = manager.put(request, data); + break; + + case QNetworkAccessManager::PostOperation: + reply = manager.post(request, data); + break; + + case QNetworkAccessManager::DeleteOperation: + reply = manager.deleteResource(request); + break; + + default: + Q_ASSERT_X(false, "tst_QNetworkReply", "Invalid/unknown operation requested"); + } + reply->setParent(this); + + returnCode = Timeout; + int code = Success; + + if (request.attribute(QNetworkRequest::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: + return "Request failed: " + reply->errorString(); + case Timeout: + return "Network timeout"; + } + return QString(); +} + +QString tst_QNetworkReply::runCustomRequest(const QNetworkRequest &request, + QNetworkReplyPtr &reply, + const QByteArray &verb, + QIODevice *data) +{ + reply = manager.sendCustomRequest(request, verb, data); + 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; + + switch (code) { + case Failure: + return "Request failed: " + reply->errorString(); + case Timeout: + return "Network timeout"; + } + return QString(); +} + +void tst_QNetworkReply::finished() +{ + loop->exit(returnCode = Success); +} + +void tst_QNetworkReply::gotError() +{ + loop->exit(returnCode = Failure); + disconnect(QObject::sender(), SIGNAL(finished()), this, 0); +} + +void tst_QNetworkReply::initTestCase() +{ +#if !defined Q_OS_WIN + wronlyFileName = QDir::currentPath() + "/write-only"; + QFile wr(wronlyFileName); + QVERIFY(wr.open(QIODevice::WriteOnly | QIODevice::Truncate)); + wr.setPermissions(QFile::WriteOwner | QFile::WriteUser); + wr.close(); +#endif + + QDir::setSearchPaths("srcdir", QStringList() << SRCDIR); +#ifndef QT_NO_OPENSSL + QSslSocket::defaultCaCertificates(); //preload certificates +#endif +#ifndef QT_NO_BEARERMANAGEMENT + netConfMan = new QNetworkConfigurationManager(this); + networkConfiguration = netConfMan->defaultConfiguration(); + networkSession.reset(new QNetworkSession(networkConfiguration)); + if (!networkSession->isOpen()) { + networkSession->open(); + QVERIFY(networkSession->waitForOpened(30000)); + } +#endif +} + +void tst_QNetworkReply::cleanupTestCase() +{ +#if !defined Q_OS_WIN + QFile::remove(wronlyFileName); +#endif + if (networkSession && networkSession->isOpen()) { + networkSession->close(); + } +} + +void tst_QNetworkReply::init() +{ + cleanup(); +} + +void tst_QNetworkReply::cleanup() +{ + QFile file(testFileName); + QVERIFY(!file.exists() || file.remove()); + + // clear the internal cache + QNetworkAccessManagerPrivate::clearCache(&manager); + manager.setProxy(QNetworkProxy()); + manager.setCache(0); + + // clear cookies + cookieJar->setAllCookies(QList<QNetworkCookie>()); +} + +void tst_QNetworkReply::stateChecking() +{ + QUrl url = QUrl("file:///"); + QNetworkRequest req(url); // you can't open this file, I know + QNetworkReplyPtr reply = manager.get(req); + + QVERIFY(reply.data()); + QVERIFY(reply->isOpen()); + QVERIFY(reply->isReadable()); + QVERIFY(!reply->isWritable()); + + // both behaviours are OK since we might change underlying behaviour again + if (!reply->isFinished()) + QCOMPARE(reply->errorString(), QString("Unknown error")); + else + QVERIFY(!reply->errorString().isEmpty()); + + + QCOMPARE(reply->manager(), &manager); + QCOMPARE(reply->request(), req); + QCOMPARE(int(reply->operation()), int(QNetworkAccessManager::GetOperation)); + // error and not error are OK since we might change underlying behaviour again + if (!reply->isFinished()) + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->url(), url); + + reply->abort(); +} + +void tst_QNetworkReply::invalidProtocol() +{ + QUrl url = QUrl::fromEncoded("not-a-known-protocol://foo/bar"); + QNetworkRequest req(url); + QNetworkReplyPtr reply; + + QString errorMsg = "Request failed: Protocol \"not-a-known-protocol\" is unknown"; + QString result = runSimpleRequest(QNetworkAccessManager::GetOperation, req, reply); + QCOMPARE(result, errorMsg); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); +} + +void tst_QNetworkReply::getFromData_data() +{ + QTest::addColumn<QString>("request"); + QTest::addColumn<QByteArray>("expected"); + QTest::addColumn<QString>("mimeType"); + + const QString defaultMimeType("text/plain;charset=US-ASCII"); + + //QTest::newRow("empty") << "data:" << QByteArray() << defaultMimeType; + QTest::newRow("empty2") << "data:," << QByteArray() << defaultMimeType; + QTest::newRow("just-charset_1") << "data:charset=iso-8859-1," + << QByteArray() << "text/plain;charset=iso-8859-1"; + QTest::newRow("just-charset_2") << "data:charset = iso-8859-1 ," + << QByteArray() << "text/plain;charset = iso-8859-1"; + //QTest::newRow("just-media") << "data:text/xml" << QByteArray() << "text/xml"; + QTest::newRow("just-media2") << "data:text/xml," << QByteArray() << "text/xml"; + + QTest::newRow("plain_1") << "data:,foo" << QByteArray("foo") << defaultMimeType; + QTest::newRow("plain_2") << "data:text/html,Hello World" << QByteArray("Hello World") + << "text/html"; + QTest::newRow("plain_3") << "data:text/html;charset=utf-8,Hello World" + << QByteArray("Hello World") << "text/html;charset=utf-8"; + + QTest::newRow("pct_1") << "data:,%3Cbody%20contentEditable%3Dtrue%3E%0D%0A" + << QByteArray("<body contentEditable=true>\r\n") << defaultMimeType; + QTest::newRow("pct_2") << "data:text/html;charset=utf-8,%3Cbody%20contentEditable%3Dtrue%3E%0D%0A" + << QByteArray("<body contentEditable=true>\r\n") + << "text/html;charset=utf-8"; + + QTest::newRow("base64-empty_1") << "data:;base64," << QByteArray() << defaultMimeType; + QTest::newRow("base64-empty_2") << "data:charset=utf-8;base64," << QByteArray() + << "text/plain;charset=utf-8"; + QTest::newRow("base64-empty_3") << "data:text/html;charset=utf-8;base64," + << QByteArray() << "text/html;charset=utf-8"; + + QTest::newRow("base64_1") << "data:;base64,UXQgaXMgZ3JlYXQh" << QByteArray("Qt is great!") + << defaultMimeType; + QTest::newRow("base64_2") << "data:charset=utf-8;base64,UXQgaXMgZ3JlYXQh" + << QByteArray("Qt is great!") << "text/plain;charset=utf-8"; + QTest::newRow("base64_3") << "data:text/html;charset=utf-8;base64,UXQgaXMgZ3JlYXQh" + << QByteArray("Qt is great!") << "text/html;charset=utf-8"; + + QTest::newRow("pct-nul") << "data:,a%00g" << QByteArray("a\0g", 3) << defaultMimeType; + QTest::newRow("base64-nul") << "data:;base64,YQBn" << QByteArray("a\0g", 3) << defaultMimeType; + QTest::newRow("pct-nonutf8") << "data:,a%E1g" << QByteArray("a\xE1g", 3) << defaultMimeType; + + QTest::newRow("base64") + << QString::fromLatin1("data:application/xml;base64,PGUvPg==") + << QByteArray("<e/>") + << "application/xml"; + + QTest::newRow("base64, no media type") + << QString::fromLatin1("data:;base64,PGUvPg==") + << QByteArray("<e/>") + << defaultMimeType; + + QTest::newRow("Percent encoding") + << QString::fromLatin1("data:application/xml,%3Ce%2F%3E") + << QByteArray("<e/>") + << "application/xml"; + + QTest::newRow("Percent encoding, no media type") + << QString::fromLatin1("data:,%3Ce%2F%3E") + << QByteArray("<e/>") + << defaultMimeType; + + QTest::newRow("querychars") + << QString::fromLatin1("data:,foo?x=0&y=0") + << QByteArray("foo?x=0&y=0") + << defaultMimeType; + + QTest::newRow("css") << "data:text/css,div%20{%20border-right:%20solid;%20}" + << QByteArray("div { border-right: solid; }") + << "text/css"; +} + +void tst_QNetworkReply::getFromData() +{ + QFETCH(QString, request); + QFETCH(QByteArray, expected); + QFETCH(QString, mimeType); + + QUrl url = QUrl::fromEncoded(request.toLatin1()); + QNetworkRequest req(url); + QNetworkReplyPtr reply; + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, req, reply)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toString(), mimeType); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(expected.size())); + QCOMPARE(reply->readAll(), expected); +} + +void tst_QNetworkReply::getFromFile() +{ + // create the file: + QTemporaryFile file(QDir::currentPath() + "/temp-XXXXXX"); + file.setAutoRemove(true); + QVERIFY(file.open()); + + QNetworkRequest request(QUrl::fromLocalFile(file.fileName())); + QNetworkReplyPtr reply; + + static const char fileData[] = "This is some data that is in the file.\r\n"; + QByteArray data = QByteArray::fromRawData(fileData, sizeof fileData - 1); + QVERIFY(file.write(data) == data.size()); + file.flush(); + QCOMPARE(file.size(), qint64(data.size())); + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), file.size()); + QCOMPARE(reply->readAll(), data); + + // make the file bigger + file.resize(0); + const int multiply = (128 * 1024) / (sizeof fileData - 1); + for (int i = 0; i < multiply; ++i) + file.write(fileData, sizeof fileData - 1); + file.flush(); + + // run again + reply = 0; + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), file.size()); + QCOMPARE(qint64(reply->readAll().size()), file.size()); +} + +void tst_QNetworkReply::getFromFileSpecial_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QString>("url"); + + QTest::newRow("resource") << ":/resource" << "qrc:/resource"; + QTest::newRow("search-path") << "srcdir:/rfc3252.txt" << "srcdir:/rfc3252.txt"; + QTest::newRow("bigfile-path") << "srcdir:/bigfile" << "srcdir:/bigfile"; +#ifdef Q_OS_WIN + QTest::newRow("smb-path") << "srcdir:/smb-file.txt" << "file://" + QtNetworkSettings::winServerName() + "/testshare/test.pri"; +#endif +} + +void tst_QNetworkReply::getFromFileSpecial() +{ + QFETCH(QString, fileName); + QFETCH(QString, url); + + // open the resource so we can find out its size + QFile resource(fileName); + QVERIFY(resource.open(QIODevice::ReadOnly)); + + QNetworkRequest request; + QNetworkReplyPtr reply; + request.setUrl(url); + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), resource.size()); + QCOMPARE(reply->readAll(), resource.readAll()); +} + +void tst_QNetworkReply::getFromFtp_data() +{ + QTest::addColumn<QString>("referenceName"); + QTest::addColumn<QString>("url"); + + QTest::newRow("rfc3252.txt") << SRCDIR "/rfc3252.txt" << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"; + QTest::newRow("bigfile") << SRCDIR "/bigfile" << "ftp://" + QtNetworkSettings::serverName() + "/qtest/bigfile"; +} + +void tst_QNetworkReply::getFromFtp() +{ + QFETCH(QString, referenceName); + QFETCH(QString, url); + + QFile reference(referenceName); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(reply->readAll(), reference.readAll()); +} + +void tst_QNetworkReply::getFromHttp_data() +{ + QTest::addColumn<QString>("referenceName"); + QTest::addColumn<QString>("url"); + + QTest::newRow("success-internal") << SRCDIR "/rfc3252.txt" << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"; + QTest::newRow("success-external") << SRCDIR "/rfc3252.txt" << "http://www.ietf.org/rfc/rfc3252.txt"; + QTest::newRow("bigfile-internal") << SRCDIR "/bigfile" << "http://" + QtNetworkSettings::serverName() + "/qtest/bigfile"; +} + +void tst_QNetworkReply::getFromHttp() +{ + QFETCH(QString, referenceName); + QFETCH(QString, url); + + QFile reference(referenceName); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reply->size(), reference.size()); + // only compare when the header is set. + if (reply->header(QNetworkRequest::ContentLengthHeader).isValid()) + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(reply->readAll(), reference.readAll()); +} + +void tst_QNetworkReply::getErrors_data() +{ + QTest::addColumn<QString>("url"); + QTest::addColumn<int>("error"); + QTest::addColumn<int>("httpStatusCode"); + QTest::addColumn<bool>("dataIsEmpty"); + + // empties + QTest::newRow("empty-url") << QString() << int(QNetworkReply::ProtocolUnknownError) << 0 << true; + QTest::newRow("empty-scheme-host") << SRCDIR "/rfc3252.txt" << int(QNetworkReply::ProtocolUnknownError) << 0 << true; + QTest::newRow("empty-scheme") << "//" + QtNetworkSettings::winServerName() + "/testshare/test.pri" + << int(QNetworkReply::ProtocolUnknownError) << 0 << true; + + // file: errors + QTest::newRow("file-host") << "file://this-host-doesnt-exist.troll.no/foo.txt" +#if !defined Q_OS_WIN + << int(QNetworkReply::ProtocolInvalidOperationError) << 0 << true; +#else + << int(QNetworkReply::ContentNotFoundError) << 0 << true; +#endif + QTest::newRow("file-no-path") << "file://localhost" + << int(QNetworkReply::ContentOperationNotPermittedError) << 0 << true; + QTest::newRow("file-is-dir") << QUrl::fromLocalFile(QDir::currentPath()).toString() + << int(QNetworkReply::ContentOperationNotPermittedError) << 0 << true; + QTest::newRow("file-exist") << QUrl::fromLocalFile(QDir::currentPath() + "/this-file-doesnt-exist.txt").toString() + << int(QNetworkReply::ContentNotFoundError) << 0 << true; +#if !defined Q_OS_WIN && !defined(Q_OS_SYMBIAN) + QTest::newRow("file-is-wronly") << QUrl::fromLocalFile(wronlyFileName).toString() + << int(QNetworkReply::ContentAccessDenied) << 0 << true; +#endif + if (QFile::exists("/etc/shadow")) + QTest::newRow("file-permissions") << "file:/etc/shadow" + << int(QNetworkReply::ContentAccessDenied) << 0 << true; + + // ftp: errors + QTest::newRow("ftp-host") << "ftp://this-host-doesnt-exist.troll.no/foo.txt" + << int(QNetworkReply::HostNotFoundError) << 0 << true; + QTest::newRow("ftp-no-path") << "ftp://" + QtNetworkSettings::serverName() + << int(QNetworkReply::ContentOperationNotPermittedError) << 0 << true; + QTest::newRow("ftp-is-dir") << "ftp://" + QtNetworkSettings::serverName() + "/qtest" + << int(QNetworkReply::ContentOperationNotPermittedError) << 0 << true; + QTest::newRow("ftp-dir-not-readable") << "ftp://" + QtNetworkSettings::serverName() + "/pub/dir-not-readable/foo.txt" + << int(QNetworkReply::ContentAccessDenied) << 0 << true; + QTest::newRow("ftp-file-not-readable") << "ftp://" + QtNetworkSettings::serverName() + "/pub/file-not-readable.txt" + << int(QNetworkReply::ContentAccessDenied) << 0 << true; + QTest::newRow("ftp-exist") << "ftp://" + QtNetworkSettings::serverName() + "/pub/this-file-doesnt-exist.txt" + << int(QNetworkReply::ContentNotFoundError) << 0 << true; + + // http: errors + QTest::newRow("http-host") << "http://this-host-will-never-exist.troll.no/" + << int(QNetworkReply::HostNotFoundError) << 0 << true; + QTest::newRow("http-exist") << "http://" + QtNetworkSettings::serverName() + "/this-file-doesnt-exist.txt" + << int(QNetworkReply::ContentNotFoundError) << 404 << false; + QTest::newRow("http-authentication") << "http://" + QtNetworkSettings::serverName() + "/qtest/rfcs-auth" + << int(QNetworkReply::AuthenticationRequiredError) << 401 << false; +} + +void tst_QNetworkReply::getErrors() +{ + QFETCH(QString, url); + QNetworkRequest request(url); + +#if defined(Q_OS_WIN) || defined (Q_OS_SYMBIAN) + if (qstrcmp(QTest::currentDataTag(), "empty-scheme-host") == 0 && QFileInfo(url).isAbsolute()) + QTest::ignoreMessage(QtWarningMsg, "QNetworkAccessFileBackendFactory: URL has no schema set, use file:// for files"); +#endif + + QNetworkReplyPtr reply = manager.get(request); + reply->setParent(this); // we have expect-fails + + if (!reply->isFinished()) + QCOMPARE(reply->error(), QNetworkReply::NoError); + + // now run the request: + connect(reply, SIGNAL(finished()), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + //qDebug() << reply->errorString(); + + QFETCH(int, error); +#if defined(Q_OS_WIN) || defined (Q_OS_SYMBIAN) + if (QFileInfo(url).isAbsolute()) + QEXPECT_FAIL("empty-scheme-host", "this is expected to fail on Windows and Symbian, QTBUG-17731", Abort); +#endif + QEXPECT_FAIL("ftp-is-dir", "QFtp cannot provide enough detail", Abort); + // the line below is not necessary + QEXPECT_FAIL("ftp-dir-not-readable", "QFtp cannot provide enough detail", Abort); + QCOMPARE(reply->error(), QNetworkReply::NetworkError(error)); + + QTEST(reply->readAll().isEmpty(), "dataIsEmpty"); + + QVERIFY(reply->isFinished()); + QVERIFY(!reply->isRunning()); + + QFETCH(int, httpStatusCode); + if (httpStatusCode != 0) { + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), httpStatusCode); + } +} + +static inline QByteArray md5sum(const QByteArray &data) +{ + return QCryptographicHash::hash(data, QCryptographicHash::Md5); +} + +void tst_QNetworkReply::putToFile_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QByteArray>("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::putToFile() +{ + QFile file(testFileName); + + QUrl url = QUrl::fromLocalFile(file.fileName()); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + + QFETCH(QByteArray, data); + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PutOperation, request, reply, data)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.size(), qint64(data.size())); + QByteArray contents = file.readAll(); + QCOMPARE(contents, data); +} + +void tst_QNetworkReply::putToFtp_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::putToFtp() +{ + QUrl url("ftp://" + QtNetworkSettings::serverName()); + url.setPath(QString("/qtest/upload/qnetworkaccess-putToFtp-%1-%2") + .arg(QTest::currentDataTag()) + .arg(uniqueExtension)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + + QFETCH(QByteArray, data); + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PutOperation, request, reply, data)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + // download the file again from FTP to make sure it was uploaded + // correctly + QFtp ftp; + ftp.connectToHost(url.host()); + ftp.login(); + ftp.get(url.path()); + + QObject::connect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QObject::disconnect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QByteArray uploaded = ftp.readAll(); + QCOMPARE(uploaded.size(), data.size()); + QCOMPARE(uploaded, data); + + ftp.close(); + QObject::connect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QObject::disconnect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); +} + +void tst_QNetworkReply::putToHttp_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::putToHttp() +{ + 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); + + 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::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( + QNetworkRequest::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(); +} + +void tst_QNetworkReply::postToHttp() +{ + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"); + + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + 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::postToHttpSynchronous_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::postToHttpSynchronous() +{ + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"); + + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + + request.setAttribute( + QNetworkRequest::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::postToHttpMultipart_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QHttpMultiPart *>("multiPart"); + QTest::addColumn<QByteArray>("expectedReplyData"); + QTest::addColumn<QByteArray>("contentType"); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/multipart.cgi"); + QByteArray expectedData; + + + // empty parts + + QHttpMultiPart *emptyMultiPart = new QHttpMultiPart; + QTest::newRow("empty") << url << emptyMultiPart << expectedData << QByteArray("mixed"); + + QHttpMultiPart *emptyRelatedMultiPart = new QHttpMultiPart; + emptyRelatedMultiPart->setContentType(QHttpMultiPart::RelatedType); + QTest::newRow("empty-related") << url << emptyRelatedMultiPart << expectedData << QByteArray("related"); + + QHttpMultiPart *emptyAlternativeMultiPart = new QHttpMultiPart; + emptyAlternativeMultiPart->setContentType(QHttpMultiPart::AlternativeType); + QTest::newRow("empty-alternative") << url << emptyAlternativeMultiPart << expectedData << QByteArray("alternative"); + + + // text-only parts + + QHttpPart textPart; + textPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\"")); + textPart.setBody("7 bytes"); + QHttpMultiPart *multiPart1 = new QHttpMultiPart; + multiPart1->setContentType(QHttpMultiPart::FormDataType); + multiPart1->append(textPart); + expectedData = "key: text, value: 7 bytes\n"; + QTest::newRow("text") << url << multiPart1 << expectedData << QByteArray("form-data"); + + QHttpMultiPart *customMultiPart = new QHttpMultiPart; + customMultiPart->append(textPart); + expectedData = "header: Content-Type, value: 'text/plain'\n" + "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + "content: 7 bytes\n" + "\n"; + QTest::newRow("text-custom") << url << customMultiPart << expectedData << QByteArray("custom"); + + QHttpPart textPart2; + textPart2.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + textPart2.setRawHeader("myRawHeader", "myValue"); + textPart2.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text2\"")); + textPart2.setBody("some more bytes"); + textPart2.setBodyDevice((QIODevice *) 1); // test whether setting and unsetting of the device works + textPart2.setBodyDevice(0); + QHttpMultiPart *multiPart2 = new QHttpMultiPart; + multiPart2->setContentType(QHttpMultiPart::FormDataType); + multiPart2->append(textPart); + multiPart2->append(textPart2); + expectedData = "key: text2, value: some more bytes\n" + "key: text, value: 7 bytes\n"; + QTest::newRow("text-text") << url << multiPart2 << expectedData << QByteArray("form-data"); + + + QHttpPart textPart3; + textPart3.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + textPart3.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text3\"")); + textPart3.setRawHeader("Content-Location", "http://my.test.location.tld"); + textPart3.setBody("even more bytes"); + QHttpMultiPart *multiPart3 = new QHttpMultiPart; + multiPart3->setContentType(QHttpMultiPart::AlternativeType); + multiPart3->append(textPart); + multiPart3->append(textPart2); + multiPart3->append(textPart3); + expectedData = "header: Content-Type, value: 'text/plain'\n" + "header: Content-Disposition, value: 'form-data; name=\"text\"'\n" + "content: 7 bytes\n" + "\n" + "header: Content-Type, value: 'text/plain'\n" + "header: myRawHeader, value: 'myValue'\n" + "header: Content-Disposition, value: 'form-data; name=\"text2\"'\n" + "content: some more bytes\n" + "\n" + "header: Content-Type, value: 'text/plain'\n" + "header: Content-Disposition, value: 'form-data; name=\"text3\"'\n" + "header: Content-Location, value: 'http://my.test.location.tld'\n" + "content: even more bytes\n\n"; + QTest::newRow("text-text-text") << url << multiPart3 << expectedData << QByteArray("alternative"); + + + + // text and image parts + + QHttpPart imagePart11; + imagePart11.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart11.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage\"")); + imagePart11.setRawHeader("Content-Location", "http://my.test.location.tld"); + imagePart11.setRawHeader("Content-ID", "my@id.tld"); + QFile *file11 = new QFile(SRCDIR "/image1.jpg"); + file11->open(QIODevice::ReadOnly); + imagePart11.setBodyDevice(file11); + QHttpMultiPart *imageMultiPart1 = new QHttpMultiPart(QHttpMultiPart::FormDataType); + imageMultiPart1->append(imagePart11); + file11->setParent(imageMultiPart1); + expectedData = "key: testImage, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"; // md5 sum of file + QTest::newRow("image") << url << imageMultiPart1 << expectedData << QByteArray("form-data"); + + QHttpPart imagePart21; + imagePart21.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart21.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\"")); + imagePart21.setRawHeader("Content-Location", "http://my.test.location.tld"); + imagePart21.setRawHeader("Content-ID", "my@id.tld"); + QFile *file21 = new QFile(SRCDIR "/image1.jpg"); + file21->open(QIODevice::ReadOnly); + imagePart21.setBodyDevice(file21); + QHttpMultiPart *imageMultiPart2 = new QHttpMultiPart(); + imageMultiPart2->setContentType(QHttpMultiPart::FormDataType); + imageMultiPart2->append(textPart); + imageMultiPart2->append(imagePart21); + file21->setParent(imageMultiPart2); + QHttpPart imagePart22; + imagePart22.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart22.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\"")); + QFile *file22 = new QFile(SRCDIR "/image2.jpg"); + file22->open(QIODevice::ReadOnly); + imagePart22.setBodyDevice(file22); + imageMultiPart2->append(imagePart22); + file22->setParent(imageMultiPart2); + expectedData = "key: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n" + "key: text, value: 7 bytes\n" + "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n"; + QTest::newRow("text-image-image") << url << imageMultiPart2 << expectedData << QByteArray("form-data"); + + + QHttpPart imagePart31; + imagePart31.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart31.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\"")); + imagePart31.setRawHeader("Content-Location", "http://my.test.location.tld"); + imagePart31.setRawHeader("Content-ID", "my@id.tld"); + QFile *file31 = new QFile(SRCDIR "/image1.jpg"); + file31->open(QIODevice::ReadOnly); + imagePart31.setBodyDevice(file31); + QHttpMultiPart *imageMultiPart3 = new QHttpMultiPart(QHttpMultiPart::FormDataType); + imageMultiPart3->append(imagePart31); + file31->setParent(imageMultiPart3); + QHttpPart imagePart32; + imagePart32.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart32.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\"")); + QFile *file32 = new QFile(SRCDIR "/image2.jpg"); + file32->open(QIODevice::ReadOnly); + imagePart32.setBodyDevice(file31); // check that resetting works + imagePart32.setBodyDevice(file32); + imageMultiPart3->append(imagePart32); + file32->setParent(imageMultiPart3); + QHttpPart imagePart33; + imagePart33.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart33.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage3\"")); + QFile *file33 = new QFile(SRCDIR "/image3.jpg"); + file33->open(QIODevice::ReadOnly); + imagePart33.setBodyDevice(file33); + imageMultiPart3->append(imagePart33); + file33->setParent(imageMultiPart3); + expectedData = "key: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n" + "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n" + "key: testImage3, value: ab0eb6fd4fcf8b4436254870b4513033\n"; + QTest::newRow("3-images") << url << imageMultiPart3 << expectedData << QByteArray("form-data"); + + + // note: nesting multiparts is not working currently; for that, the outputDevice would need to be public + +// QHttpPart imagePart41; +// imagePart41.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); +// QFile *file41 = new QFile(SRCDIR "/image1.jpg"); +// file41->open(QIODevice::ReadOnly); +// imagePart41.setBodyDevice(file41); +// +// QHttpMultiPart *innerMultiPart = new QHttpMultiPart(); +// innerMultiPart->setContentType(QHttpMultiPart::FormDataType); +// textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant()); +// innerMultiPart->append(textPart); +// innerMultiPart->append(imagePart41); +// textPart2.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant()); +// innerMultiPart->append(textPart2); +// +// QHttpPart nestedPart; +// nestedPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"nestedMessage")); +// nestedPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("multipart/alternative; boundary=\"" + innerMultiPart->boundary() + "\"")); +// innerMultiPart->outputDevice()->open(QIODevice::ReadOnly); +// nestedPart.setBodyDevice(innerMultiPart->outputDevice()); +// +// QHttpMultiPart *outerMultiPart = new QHttpMultiPart; +// outerMultiPart->setContentType(QHttpMultiPart::FormDataType); +// outerMultiPart->append(textPart); +// outerMultiPart->append(nestedPart); +// outerMultiPart->append(textPart2); +// expectedData = "nothing"; // the CGI.pm module running on the test server does not understand nested multiparts +// openFiles.clear(); +// openFiles << file41; +// QTest::newRow("nested") << url << outerMultiPart << expectedData << openFiles; + + + // test setting large chunks of content with a byte array instead of a device (DISCOURAGED because of high memory consumption, + // but we need to test that the behavior is correct) + QHttpPart imagePart51; + imagePart51.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); + imagePart51.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage\"")); + QFile *file51 = new QFile(SRCDIR "/image1.jpg"); + file51->open(QIODevice::ReadOnly); + QByteArray imageData = file51->readAll(); + file51->close(); + delete file51; + imagePart51.setBody("7 bytes"); // check that resetting works + imagePart51.setBody(imageData); + QHttpMultiPart *imageMultiPart5 = new QHttpMultiPart; + imageMultiPart5->setContentType(QHttpMultiPart::FormDataType); + imageMultiPart5->append(imagePart51); + expectedData = "key: testImage, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"; // md5 sum of file + QTest::newRow("image-as-content") << url << imageMultiPart5 << expectedData << QByteArray("form-data"); +} + +void tst_QNetworkReply::postToHttpMultipart() +{ + QFETCH(QUrl, url); + + static QSet<QByteArray> boundaries; + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + + QFETCH(QHttpMultiPart *, multiPart); + QFETCH(QByteArray, expectedReplyData); + QFETCH(QByteArray, contentType); + + // hack for testing the setting of the content-type header by hand: + if (contentType == "custom") { + QByteArray contentType("multipart/custom; boundary=\"" + multiPart->boundary() + "\""); + request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); + } + + QVERIFY2(! boundaries.contains(multiPart->boundary()), "boundary '" + multiPart->boundary() + "' has been created twice"); + boundaries.insert(multiPart->boundary()); + + RUN_REQUEST(runMultipartRequest(request, reply, multiPart, "POST")); + multiPart->deleteLater(); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().count() < 70); + QByteArray replyData = reply->readAll(); + + expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); +// QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above + QCOMPARE(replyData, expectedReplyData); +} + +void tst_QNetworkReply::putToHttpMultipart_data() +{ + postToHttpMultipart_data(); +} + +void tst_QNetworkReply::putToHttpMultipart() +{ + QSKIP("test server script cannot handle PUT data yet", SkipAll); + QFETCH(QUrl, url); + + static QSet<QByteArray> boundaries; + + QNetworkRequest request(url); + QNetworkReplyPtr reply; + + QFETCH(QHttpMultiPart *, multiPart); + QFETCH(QByteArray, expectedReplyData); + QFETCH(QByteArray, contentType); + + // hack for testing the setting of the content-type header by hand: + if (contentType == "custom") { + QByteArray contentType("multipart/custom; boundary=\"" + multiPart->boundary() + "\""); + request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); + } + + QVERIFY2(! boundaries.contains(multiPart->boundary()), "boundary '" + multiPart->boundary() + "' has been created twice"); + boundaries.insert(multiPart->boundary()); + + RUN_REQUEST(runMultipartRequest(request, reply, multiPart, "PUT")); + multiPart->deleteLater(); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string + QVERIFY(multiPart->boundary().count() < 70); + QByteArray replyData = reply->readAll(); + + expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n"); +// QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above + QCOMPARE(replyData, expectedReplyData); +} + +void tst_QNetworkReply::deleteFromHttp_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<int>("resultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("error"); + + // for status codes to expect, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html + + QTest::newRow("405-method-not-allowed") << QUrl("http://" + QtNetworkSettings::serverName() + "/index.html") << 405 << QNetworkReply::ContentOperationNotPermittedError; + QTest::newRow("200-ok") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/http-delete.cgi?200-ok") << 200 << QNetworkReply::NoError; + QTest::newRow("202-accepted") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/http-delete.cgi?202-accepted") << 202 << QNetworkReply::NoError; + QTest::newRow("204-no-content") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/http-delete.cgi?204-no-content") << 204 << QNetworkReply::NoError; + QTest::newRow("404-not-found") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/http-delete.cgi?404-not-found") << 404 << QNetworkReply::ContentNotFoundError; +} + +void tst_QNetworkReply::deleteFromHttp() +{ + QFETCH(QUrl, url); + QFETCH(int, resultCode); + QFETCH(QNetworkReply::NetworkError, error); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + runSimpleRequest(QNetworkAccessManager::DeleteOperation, request, reply, 0); + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), error); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), resultCode); +} + +void tst_QNetworkReply::putGetDeleteGetFromHttp_data() +{ + QTest::addColumn<QUrl>("putUrl"); + QTest::addColumn<int>("putResultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("putError"); + QTest::addColumn<QUrl>("deleteUrl"); + QTest::addColumn<int>("deleteResultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("deleteError"); + QTest::addColumn<QUrl>("get2Url"); + QTest::addColumn<int>("get2ResultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("get2Error"); + + QUrl url("http://" + QtNetworkSettings::serverName()); + url.setPath(QString("/dav/qnetworkaccess-putToHttp-%1-%2") + .arg(QTest::currentDataTag()) + .arg(uniqueExtension)); + + // first use case: put, get (to check it is there), delete, get (to check it is not there anymore) + QTest::newRow("success") << url << 201 << QNetworkReply::NoError << url << 204 << QNetworkReply::NoError << url << 404 << QNetworkReply::ContentNotFoundError; + + QUrl wrongUrl("http://" + QtNetworkSettings::serverName()); + wrongUrl.setPath(QString("/dav/qnetworkaccess-thisURLisNotAvailable")); + + // second use case: put, get (to check it is there), delete wrong URL, get (to check it is still there) + QTest::newRow("delete-error") << url << 201 << QNetworkReply::NoError << wrongUrl << 404 << QNetworkReply::ContentNotFoundError << url << 200 << QNetworkReply::NoError; + +} + +void tst_QNetworkReply::putGetDeleteGetFromHttp() +{ + QFETCH(QUrl, putUrl); + QFETCH(int, putResultCode); + QFETCH(QNetworkReply::NetworkError, putError); + QFETCH(QUrl, deleteUrl); + QFETCH(int, deleteResultCode); + QFETCH(QNetworkReply::NetworkError, deleteError); + QFETCH(QUrl, get2Url); + QFETCH(int, get2ResultCode); + QFETCH(QNetworkReply::NetworkError, get2Error); + + QNetworkRequest putRequest(putUrl); + QNetworkRequest deleteRequest(deleteUrl); + QNetworkRequest get2Request(get2Url); + QNetworkReplyPtr reply; + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::PutOperation, putRequest, reply, 0)); + QCOMPARE(reply->error(), putError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), putResultCode); + + runSimpleRequest(QNetworkAccessManager::GetOperation, putRequest, reply, 0); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + runSimpleRequest(QNetworkAccessManager::DeleteOperation, deleteRequest, reply, 0); + QCOMPARE(reply->error(), deleteError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), deleteResultCode); + + runSimpleRequest(QNetworkAccessManager::GetOperation, get2Request, reply, 0); + QCOMPARE(reply->error(), get2Error); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), get2ResultCode); + +} + +void tst_QNetworkReply::sendCustomRequestToHttp_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QByteArray>("verb"); + QTest::addColumn<QBuffer *>("device"); + QTest::addColumn<int>("resultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("error"); + QTest::addColumn<QByteArray>("expectedContent"); + + QTest::newRow("options") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("OPTIONS") << (QBuffer *) 0 << 200 << QNetworkReply::NoError << QByteArray(); + QTest::newRow("trace") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("TRACE") << (QBuffer *) 0 << 200 << QNetworkReply::NoError << QByteArray(); + QTest::newRow("connect") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("CONNECT") << (QBuffer *) 0 << 400 << QNetworkReply::UnknownContentError << QByteArray(); // 400 = Bad Request + QTest::newRow("nonsense") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("NONSENSE") << (QBuffer *) 0 << 501 << QNetworkReply::ProtocolUnknownError << QByteArray(); // 501 = Method Not Implemented + + QByteArray ba("test"); + QBuffer *buffer = new QBuffer; + buffer->setData(ba); + buffer->open(QIODevice::ReadOnly); + QTest::newRow("post") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi") << QByteArray("POST") + << buffer << 200 << QNetworkReply::NoError << QByteArray("098f6bcd4621d373cade4e832627b4f6\n"); + + QByteArray ba2("test"); + QBuffer *buffer2 = new QBuffer; + buffer2->setData(ba2); + buffer2->open(QIODevice::ReadOnly); + QTest::newRow("put") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi") << QByteArray("PUT") + << buffer2 << 200 << QNetworkReply::NoError << QByteArray("098f6bcd4621d373cade4e832627b4f6\n"); +} + +void tst_QNetworkReply::sendCustomRequestToHttp() +{ + QFETCH(QUrl, url); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + QFETCH(QByteArray, verb); + QFETCH(QBuffer *, device); + runCustomRequest(request, reply, verb, device); + QCOMPARE(reply->url(), url); + QFETCH(QNetworkReply::NetworkError, error); + QCOMPARE(reply->error(), error); + QFETCH(int, resultCode); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), resultCode); + QFETCH(QByteArray, expectedContent); + if (! expectedContent.isEmpty()) + QCOMPARE(reply->readAll(), expectedContent); +} + +void tst_QNetworkReply::ioGetFromData_data() +{ + QTest::addColumn<QString>("urlStr"); + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("data-empty") << "data:," << QByteArray(); + QTest::newRow("data-literal") << "data:,foo" << QByteArray("foo"); + QTest::newRow("data-pct") << "data:,%3Cbody%20contentEditable%3Dtrue%3E%0D%0A" + << QByteArray("<body contentEditable=true>\r\n"); + QTest::newRow("data-base64") << "data:;base64,UXQgaXMgZ3JlYXQh" << QByteArray("Qt is great!"); +} + +void tst_QNetworkReply::ioGetFromData() +{ + QFETCH(QString, urlStr); + + QUrl url = QUrl::fromEncoded(urlStr.toLatin1()); + QNetworkRequest request(url); + + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QFETCH(QByteArray, data); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toInt(), data.size()); + QCOMPARE(reader.data.size(), data.size()); + QCOMPARE(reader.data, data); +} + +void tst_QNetworkReply::ioGetFromFileSpecial_data() +{ + getFromFileSpecial_data(); +} + +void tst_QNetworkReply::ioGetFromFileSpecial() +{ + QFETCH(QString, fileName); + QFETCH(QString, url); + + QFile resource(fileName); + QVERIFY(resource.open(QIODevice::ReadOnly)); + + QNetworkRequest request; + request.setUrl(url); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), resource.size()); + QCOMPARE(qint64(reader.data.size()), resource.size()); + QCOMPARE(reader.data, resource.readAll()); +} + +void tst_QNetworkReply::ioGetFromFile_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::ioGetFromFile() +{ + QTemporaryFile file(QDir::currentPath() + "/temp-XXXXXX"); + file.setAutoRemove(true); + QVERIFY(file.open()); + + QFETCH(QByteArray, data); + QVERIFY(file.write(data) == data.size()); + file.flush(); + QCOMPARE(file.size(), qint64(data.size())); + + QNetworkRequest request(QUrl::fromLocalFile(file.fileName())); + QNetworkReplyPtr reply = manager.get(request); + QVERIFY(reply->isFinished()); // a file should immediatly be done + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), file.size()); + QCOMPARE(qint64(reader.data.size()), file.size()); + QCOMPARE(reader.data, data); +} + +void tst_QNetworkReply::ioGetFromFtp_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<qint64>("expectedSize"); + + QTest::newRow("bigfile") << "bigfile" << Q_INT64_C(519240); + + QFile file(SRCDIR "/rfc3252.txt"); + QTest::newRow("rfc3252.txt") << "rfc3252.txt" << file.size(); +} + +void tst_QNetworkReply::ioGetFromFtp() +{ + QFETCH(QString, fileName); + QFile reference(fileName); + reference.open(QIODevice::ReadOnly); // will fail for bigfile + + QNetworkRequest request("ftp://" + QtNetworkSettings::serverName() + "/qtest/" + fileName); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QFETCH(qint64, expectedSize); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), expectedSize); + QCOMPARE(qint64(reader.data.size()), expectedSize); + + if (reference.isOpen()) + QCOMPARE(reader.data, reference.readAll()); +} + +void tst_QNetworkReply::ioGetFromFtpWithReuse() +{ + QString fileName = SRCDIR "/rfc3252.txt"; + QFile reference(fileName); + reference.open(QIODevice::ReadOnly); + + QNetworkRequest request(QUrl("ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + + // two concurrent (actually, consecutive) gets: + QNetworkReplyPtr reply1 = manager.get(request); + DataReader reader1(reply1); + QNetworkReplyPtr reply2 = manager.get(request); + DataReader reader2(reply2); + QSignalSpy spy(reply1, SIGNAL(finished())); + + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + if (spy.count() == 0) { + connect(reply1, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + + QCOMPARE(reply1->url(), request.url()); + QCOMPARE(reply2->url(), request.url()); + QCOMPARE(reply1->error(), QNetworkReply::NoError); + QCOMPARE(reply2->error(), QNetworkReply::NoError); + + QCOMPARE(qint64(reader1.data.size()), reference.size()); + QCOMPARE(qint64(reader2.data.size()), reference.size()); + QCOMPARE(reply1->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(reply2->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + + QByteArray referenceData = reference.readAll(); + QCOMPARE(reader1.data, referenceData); + QCOMPARE(reader2.data, referenceData); +} + +void tst_QNetworkReply::ioGetFromHttp() +{ + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(qint64(reader.data.size()), reference.size()); + + QCOMPARE(reader.data, reference.readAll()); +} + +void tst_QNetworkReply::ioGetFromHttpWithReuseParallel() +{ + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + QNetworkReplyPtr reply1 = manager.get(request); + QNetworkReplyPtr reply2 = manager.get(request); + DataReader reader1(reply1); + DataReader reader2(reply2); + QSignalSpy spy(reply1, SIGNAL(finished())); + + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + if (spy.count() == 0) { + connect(reply1, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + + QCOMPARE(reply1->url(), request.url()); + QCOMPARE(reply2->url(), request.url()); + QCOMPARE(reply1->error(), QNetworkReply::NoError); + QCOMPARE(reply2->error(), QNetworkReply::NoError); + QCOMPARE(reply1->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply1->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(reply2->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(qint64(reader1.data.size()), reference.size()); + QCOMPARE(qint64(reader2.data.size()), reference.size()); + + QByteArray referenceData = reference.readAll(); + QCOMPARE(reader1.data, referenceData); + QCOMPARE(reader2.data, referenceData); +} + +void tst_QNetworkReply::ioGetFromHttpWithReuseSequential() +{ + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + { + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(qint64(reader.data.size()), reference.size()); + + QCOMPARE(reader.data, reference.readAll()); + } + + reference.seek(0); + // rinse and repeat: + { + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size()); + QCOMPARE(qint64(reader.data.size()), reference.size()); + + QCOMPARE(reader.data, reference.readAll()); + } +} + +void tst_QNetworkReply::ioGetFromHttpWithAuth_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QByteArray>("expectedData"); + + QFile reference(SRCDIR "/rfc3252.txt"); + reference.open(QIODevice::ReadOnly); + QByteArray referenceData = reference.readAll(); + QTest::newRow("basic") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfcs-auth/rfc3252.txt") << referenceData; + QTest::newRow("digest") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/auth-digest/") << QByteArray("digest authentication successful\n"); +} + +void tst_QNetworkReply::ioGetFromHttpWithAuth() +{ + // This test sends three requests + // The first two in parallel + // The third after the first two finished + + QFETCH(QUrl, url); + QFETCH(QByteArray, expectedData); + QNetworkRequest request(url); + { + QNetworkReplyPtr reply1 = manager.get(request); + QNetworkReplyPtr reply2 = manager.get(request); + DataReader reader1(reply1); + DataReader reader2(reply2); + QSignalSpy finishedspy(reply1, SIGNAL(finished())); + + QSignalSpy authspy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + if (finishedspy.count() == 0) { + connect(reply1, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + manager.disconnect(SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QCOMPARE(reply1->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader1.data, expectedData); + QCOMPARE(reader2.data, expectedData); + + QCOMPARE(authspy.count(), 1); + } + + // rinse and repeat: + { + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + QSignalSpy authspy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + manager.disconnect(SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader.data, expectedData); + + QCOMPARE(authspy.count(), 0); + } + + // now check with synchronous calls: + { + request.setAttribute( + QNetworkRequest::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(), expectedData); + } +} + +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( + QNetworkRequest::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() +{ + qRegisterMetaType<QNetworkProxy>(); // for QSignalSpy + qRegisterMetaType<QAuthenticator *>(); + + // This test sends three requests + // The first two in parallel + // The third after the first two finished + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkProxy proxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + { + manager.setProxy(proxy); + QNetworkReplyPtr reply1 = manager.get(request); + QNetworkReplyPtr reply2 = manager.get(request); + manager.setProxy(QNetworkProxy()); + + DataReader reader1(reply1); + DataReader reader2(reply2); + QSignalSpy finishedspy(reply1, SIGNAL(finished())); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + if (finishedspy.count() == 0) { + connect(reply1, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + QCOMPARE(reply1->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QByteArray referenceData = reference.readAll(); + QCOMPARE(reader1.data, referenceData); + QCOMPARE(reader2.data, referenceData); + + QCOMPARE(authspy.count(), 1); + } + + reference.seek(0); + // rinse and repeat: + { + manager.setProxy(proxy); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + manager.setProxy(QNetworkProxy()); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader.data, reference.readAll()); + + QCOMPARE(authspy.count(), 0); + } + + // now check with synchronous calls: + reference.seek(0); + { + request.setAttribute( + QNetworkRequest::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( + QNetworkRequest::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() +{ + // HTTP caching proxies are tested by the above function + // test SOCKSv5 proxies too + + qRegisterMetaType<QNetworkProxy>(); // for QSignalSpy + qRegisterMetaType<QAuthenticator *>(); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkProxy proxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1080); + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + { + manager.setProxy(proxy); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + manager.setProxy(QNetworkProxy()); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader.data, reference.readAll()); + + QCOMPARE(authspy.count(), 0); + } + + // set an invalid proxy just to make sure that we can't load + proxy = QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1079); + { + manager.setProxy(proxy); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + manager.setProxy(QNetworkProxy()); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + QVERIFY(!reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()); + QVERIFY(reader.data.isEmpty()); + + QVERIFY(int(reply->error()) > 0); + QEXPECT_FAIL("", "QTcpSocket doesn't return enough information yet", Continue); + QCOMPARE(int(reply->error()), int(QNetworkReply::ProxyConnectionRefusedError)); + + QCOMPARE(authspy.count(), 0); + } +} + +#ifndef QT_NO_OPENSSL +void tst_QNetworkReply::ioGetFromHttpsWithSslErrors() +{ + qRegisterMetaType<QNetworkReply*>(); // for QSignalSpy + qRegisterMetaType<QList<QSslError> >(); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + QSignalSpy sslspy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), + SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); + connect(reply, SIGNAL(metaDataChanged()), SLOT(storeSslConfiguration())); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), + this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader.data, reference.readAll()); + + QCOMPARE(sslspy.count(), 1); + + QVERIFY(!storedSslConfiguration.isNull()); + QVERIFY(!reply->sslConfiguration().isNull()); +} + +void tst_QNetworkReply::ioGetFromHttpsWithIgnoreSslErrors() +{ + // same as above, except that we call ignoreSslErrors and don't connect + // to the sslErrors() signal (which is *still* emitted) + + qRegisterMetaType<QNetworkReply*>(); // for QSignalSpy + qRegisterMetaType<QList<QSslError> >(); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + + QNetworkReplyPtr reply = manager.get(request); + reply->ignoreSslErrors(); + DataReader reader(reply); + + QSignalSpy sslspy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + connect(reply, SIGNAL(metaDataChanged()), SLOT(storeSslConfiguration())); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reader.data, reference.readAll()); + + QCOMPARE(sslspy.count(), 1); + + QVERIFY(!storedSslConfiguration.isNull()); + QVERIFY(!reply->sslConfiguration().isNull()); +} + +void tst_QNetworkReply::ioGetFromHttpsWithSslHandshakeError() +{ + qRegisterMetaType<QNetworkReply*>(); // for QSignalSpy + qRegisterMetaType<QList<QSslError> >(); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + ":80")); + + QNetworkReplyPtr reply = manager.get(request); + reply->ignoreSslErrors(); + DataReader reader(reply); + + QSignalSpy sslspy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + connect(reply, SIGNAL(metaDataChanged()), SLOT(storeSslConfiguration())); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); + QCOMPARE(sslspy.count(), 0); +} +#endif + +void tst_QNetworkReply::ioGetFromHttpBrokenServer_data() +{ + QTest::addColumn<QByteArray>("dataToSend"); + QTest::addColumn<bool>("doDisconnect"); + + QTest::newRow("no-newline") << QByteArray("Hello World") << false; + + // these are OK now, we just eat the lonely newlines + //QTest::newRow("just-newline") << QByteArray("\r\n") << false; + //QTest::newRow("just-2newline") << QByteArray("\r\n\r\n") << false; + + QTest::newRow("with-newlines") << QByteArray("Long first line\r\nLong second line") << false; + QTest::newRow("with-newlines2") << QByteArray("\r\nSecond line") << false; + QTest::newRow("with-newlines3") << QByteArray("ICY\r\nSecond line") << false; + QTest::newRow("invalid-version") << QByteArray("HTTP/123 200 \r\n") << false; + QTest::newRow("invalid-version2") << QByteArray("HTTP/a.\033 200 \r\n") << false; + QTest::newRow("invalid-reply-code") << QByteArray("HTTP/1.0 fuu \r\n") << false; + + QTest::newRow("empty+disconnect") << QByteArray() << true; + + QTest::newRow("no-newline+disconnect") << QByteArray("Hello World") << true; + QTest::newRow("just-newline+disconnect") << QByteArray("\r\n") << true; + QTest::newRow("just-2newline+disconnect") << QByteArray("\r\n\r\n") << true; + QTest::newRow("with-newlines+disconnect") << QByteArray("Long first line\r\nLong second line") << true; + QTest::newRow("with-newlines2+disconnect") << QByteArray("\r\nSecond line") << true; + QTest::newRow("with-newlines3+disconnect") << QByteArray("ICY\r\nSecond line") << true; + + QTest::newRow("invalid-version+disconnect") << QByteArray("HTTP/123 200 ") << true; + QTest::newRow("invalid-version2+disconnect") << QByteArray("HTTP/a.\033 200 ") << true; + QTest::newRow("invalid-reply-code+disconnect") << QByteArray("HTTP/1.0 fuu ") << true; + + QTest::newRow("immediate disconnect") << QByteArray("") << true; + QTest::newRow("justHalfStatus+disconnect") << QByteArray("HTTP/1.1") << true; + QTest::newRow("justStatus+disconnect") << QByteArray("HTTP/1.1 200 OK\r\n") << true; + QTest::newRow("justStatusAndHalfHeaders+disconnect") << QByteArray("HTTP/1.1 200 OK\r\nContent-L") << true; + + QTest::newRow("halfContent+disconnect") << QByteArray("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nAB") << true; + +} + +void tst_QNetworkReply::ioGetFromHttpBrokenServer() +{ + QFETCH(QByteArray, dataToSend); + QFETCH(bool, doDisconnect); + MiniHttpServer server(dataToSend); + server.doClose = doDisconnect; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + QSignalSpy spy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(spy.count(), 1); + QVERIFY(reply->error() != QNetworkReply::NoError); +} + +void tst_QNetworkReply::ioGetFromHttpStatus100_data() +{ + QTest::addColumn<QByteArray>("dataToSend"); + QTest::addColumn<int>("statusCode"); + QTest::newRow("normal") << QByteArray("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("minimal2") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.0 200 OK\r\n\r\n") << 200; + QTest::newRow("minimal3") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.0 200 OK\n\n") << 200; + QTest::newRow("minimal+404") << QByteArray("HTTP/1.1 100 Continue\n\nHTTP/1.0 204 No Content\r\n\r\n") << 204; + QTest::newRow("with_headers") << QByteArray("HTTP/1.1 100 Continue\r\nBla: x\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; + QTest::newRow("with_headers2") << QByteArray("HTTP/1.1 100 Continue\nBla: x\n\nHTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << 200; +} + +void tst_QNetworkReply::ioGetFromHttpStatus100() +{ + QFETCH(QByteArray, dataToSend); + QFETCH(int, statusCode); + MiniHttpServer server(dataToSend); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); + QVERIFY(reply->rawHeader("bla").isNull()); +} + +void tst_QNetworkReply::ioGetFromHttpNoHeaders_data() +{ + QTest::addColumn<QByteArray>("dataToSend"); + QTest::newRow("justStatus+noheaders+disconnect") << QByteArray("HTTP/1.0 200 OK\r\n\r\n"); +} + +void tst_QNetworkReply::ioGetFromHttpNoHeaders() +{ + QFETCH(QByteArray, dataToSend); + MiniHttpServer server(dataToSend); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); +} + +void tst_QNetworkReply::ioGetFromHttpWithCache_data() +{ + qRegisterMetaType<MyMemoryCache::CachedContent>(); + QTest::addColumn<QByteArray>("dataToSend"); + QTest::addColumn<QString>("body"); + QTest::addColumn<MyMemoryCache::CachedContent>("cachedReply"); + QTest::addColumn<int>("cacheMode"); + QTest::addColumn<QStringList>("extraHttpHeaders"); + QTest::addColumn<bool>("loadedFromCache"); + QTest::addColumn<bool>("networkUsed"); + + QByteArray reply200 = + "HTTP/1.0 200\r\n" + "Connection: keep-alive\r\n" + "Content-Type: text/plain\r\n" + "Cache-control: no-cache\r\n" + "Content-length: 8\r\n" + "\r\n" + "Reloaded"; + QByteArray reply304 = + "HTTP/1.0 304 Use Cache\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + + QTest::newRow("not-cached,always-network") + << reply200 << "Reloaded" << MyMemoryCache::CachedContent() << int(QNetworkRequest::AlwaysNetwork) << QStringList() << false << true; + QTest::newRow("not-cached,prefer-network") + << reply200 << "Reloaded" << MyMemoryCache::CachedContent() << int(QNetworkRequest::PreferNetwork) << QStringList() << false << true; + QTest::newRow("not-cached,prefer-cache") + << reply200 << "Reloaded" << MyMemoryCache::CachedContent() << int(QNetworkRequest::PreferCache) << QStringList() << false << true; + + QDateTime present = QDateTime::currentDateTime().toUTC(); + QDateTime past = present.addSecs(-3600); + QDateTime future = present.addSecs(3600); + static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'"; + + QNetworkCacheMetaData::RawHeaderList rawHeaders; + MyMemoryCache::CachedContent content; + content.second = "Not-reloaded"; + content.first.setLastModified(past); + + // + // Set to expired + // + rawHeaders.clear(); + rawHeaders << QNetworkCacheMetaData::RawHeader("Date", QLocale::c().toString(past, dateFormat).toLatin1()) + << QNetworkCacheMetaData::RawHeader("Cache-control", "max-age=0"); // isn't used in cache loading + content.first.setRawHeaders(rawHeaders); + content.first.setLastModified(past); + + QTest::newRow("expired,200,prefer-network") + << reply200 << "Reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << false << true; + QTest::newRow("expired,200,prefer-cache") + << reply200 << "Reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << false << true; + + QTest::newRow("expired,304,prefer-network") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << true << true; + QTest::newRow("expired,304,prefer-cache") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << true << true; + + // + // Set to not-expired + // + rawHeaders.clear(); + rawHeaders << QNetworkCacheMetaData::RawHeader("Date", QLocale::c().toString(past, dateFormat).toLatin1()) + << QNetworkCacheMetaData::RawHeader("Cache-control", "max-age=7200"); // isn't used in cache loading + content.first.setRawHeaders(rawHeaders); + content.first.setExpirationDate(future); + + QTest::newRow("not-expired,200,always-network") + << reply200 << "Reloaded" << content << int(QNetworkRequest::AlwaysNetwork) << QStringList() << false << true; + QTest::newRow("not-expired,200,prefer-network") + << reply200 << "Not-reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << true << false; + QTest::newRow("not-expired,200,prefer-cache") + << reply200 << "Not-reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << true << false; + QTest::newRow("not-expired,200,always-cache") + << reply200 << "Not-reloaded" << content << int(QNetworkRequest::AlwaysCache) << QStringList() << true << false; + + QTest::newRow("not-expired,304,prefer-network") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << true << false; + QTest::newRow("not-expired,304,prefer-cache") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << true << false; + QTest::newRow("not-expired,304,always-cache") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::AlwaysCache) << QStringList() << true << false; + + // + // Set must-revalidate now + // + rawHeaders.clear(); + rawHeaders << QNetworkCacheMetaData::RawHeader("Date", QLocale::c().toString(past, dateFormat).toLatin1()) + << QNetworkCacheMetaData::RawHeader("Cache-control", "max-age=7200, must-revalidate"); // must-revalidate is used + content.first.setRawHeaders(rawHeaders); + + QTest::newRow("must-revalidate,200,always-network") + << reply200 << "Reloaded" << content << int(QNetworkRequest::AlwaysNetwork) << QStringList() << false << true; + QTest::newRow("must-revalidate,200,prefer-network") + << reply200 << "Reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << false << true; + QTest::newRow("must-revalidate,200,prefer-cache") + << reply200 << "Not-reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << true << false; + QTest::newRow("must-revalidate,200,always-cache") + << reply200 << "Not-reloaded" << content << int(QNetworkRequest::AlwaysCache) << QStringList() << true << false; + + QTest::newRow("must-revalidate,304,prefer-network") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferNetwork) << QStringList() << true << true; + QTest::newRow("must-revalidate,304,prefer-cache") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::PreferCache) << QStringList() << true << false; + QTest::newRow("must-revalidate,304,always-cache") + << reply304 << "Not-reloaded" << content << int(QNetworkRequest::AlwaysCache) << QStringList() << true << false; + + // + // Partial content + // + rawHeaders.clear(); + rawHeaders << QNetworkCacheMetaData::RawHeader("Date", QLocale::c().toString(past, dateFormat).toLatin1()) + << QNetworkCacheMetaData::RawHeader("Cache-control", "max-age=7200"); // isn't used in cache loading + content.first.setRawHeaders(rawHeaders); + content.first.setExpirationDate(future); + + QByteArray reply206 = + "HTTP/1.0 206\r\n" + "Connection: keep-alive\r\n" + "Content-Type: text/plain\r\n" + "Cache-control: no-cache\r\n" + "Content-Range: bytes 2-6/8\r\n" + "Content-length: 4\r\n" + "\r\n" + "load"; + + QTest::newRow("partial,dontuse-cache") + << reply206 << "load" << content << int(QNetworkRequest::PreferCache) << (QStringList() << "Range" << "bytes=2-6") << false << true; +} + +void tst_QNetworkReply::ioGetFromHttpWithCache() +{ + QFETCH(QByteArray, dataToSend); + MiniHttpServer server(dataToSend); + server.doClose = false; + + MyMemoryCache *memoryCache = new MyMemoryCache(&manager); + manager.setCache(memoryCache); + + QFETCH(MyMemoryCache::CachedContent, cachedReply); + QUrl url = "http://localhost:" + QString::number(server.serverPort()); + cachedReply.first.setUrl(url); + if (!cachedReply.second.isNull()) + memoryCache->cache.insert(url.toEncoded(), cachedReply); + + QFETCH(int, cacheMode); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, cacheMode); + request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); + + QFETCH(QStringList, extraHttpHeaders); + QStringListIterator it(extraHttpHeaders); + while (it.hasNext()) { + QString header = it.next(); + QString value = it.next(); + request.setRawHeader(header.toLatin1(), value.toLatin1()); // To latin1? Deal with it! + } + + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QTEST(reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), "loadedFromCache"); + QTEST(server.totalConnections > 0, "networkUsed"); + QFETCH(QString, body); + QCOMPARE(reply->readAll().constData(), qPrintable(body)); +} + +void tst_QNetworkReply::ioGetWithManyProxies_data() +{ + QTest::addColumn<QList<QNetworkProxy> >("proxyList"); + QTest::addColumn<QNetworkProxy>("proxyUsed"); + QTest::addColumn<QString>("url"); + QTest::addColumn<QNetworkReply::NetworkError>("expectedError"); + + QList<QNetworkProxy> proxyList; + + // All of the other functions test DefaultProxy + // So let's test something else + + // Simple tests that work: + + // HTTP request with HTTP caching proxy + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("http-on-http") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with HTTP transparent proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("http-on-http2") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with SOCKS transparent proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1081); + QTest::newRow("http-on-socks") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // FTP request with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121); + QTest::newRow("ftp-on-ftp") + << proxyList << proxyList.at(0) + << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // The following test doesn't work because QFtp is too limited + // It can only talk to its own kind of proxies + + // FTP request with SOCKSv5 transparent proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1081); + QTest::newRow("ftp-on-socks") + << proxyList << proxyList.at(0) + << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + +#ifndef QT_NO_OPENSSL + // HTTPS with HTTP transparent proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("https-on-http") + << proxyList << proxyList.at(0) + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTPS request with SOCKS transparent proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1081); + QTest::newRow("https-on-socks") + << proxyList << proxyList.at(0) + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; +#endif + + // Tests that fail: + + // HTTP request with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121); + QTest::newRow("http-on-ftp") + << proxyList << QNetworkProxy() + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + + // FTP request with HTTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("ftp-on-http") + << proxyList << QNetworkProxy() + << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + + // FTP request with HTTP caching proxies + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3130); + QTest::newRow("ftp-on-multiple-http") + << proxyList << QNetworkProxy() + << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + +#ifndef QT_NO_OPENSSL + // HTTPS with HTTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("https-on-httptransparent") + << proxyList << QNetworkProxy() + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; + + // HTTPS with FTP caching proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121); + QTest::newRow("https-on-ftp") + << proxyList << QNetworkProxy() + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::ProxyNotFoundError; +#endif + + // Complex requests: + + // HTTP request with more than one HTTP proxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3130); + QTest::newRow("http-on-multiple-http") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with HTTP + SOCKS + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1081); + QTest::newRow("http-on-http+socks") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with FTP + HTTP + SOCKS + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1081); + QTest::newRow("http-on-ftp+http+socks") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with NoProxy + HTTP + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::NoProxy) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("http-on-noproxy+http") + << proxyList << proxyList.at(0) + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTP request with FTP + NoProxy + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121) + << QNetworkProxy(QNetworkProxy::NoProxy); + QTest::newRow("http-on-ftp+noproxy") + << proxyList << proxyList.at(1) // second proxy should be used + << "http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // FTP request with HTTP Caching + FTP + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121); + QTest::newRow("ftp-on-http+ftp") + << proxyList << proxyList.at(1) // second proxy should be used + << "ftp://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + +#ifndef QT_NO_OPENSSL + // HTTPS request with HTTP Caching + HTTP transparent + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("https-on-httpcaching+http") + << proxyList << proxyList.at(1) // second proxy should be used + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; + + // HTTPS request with FTP + HTTP C + HTTP T + proxyList.clear(); + proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::serverName(), 2121) + << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129) + << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3129); + QTest::newRow("https-on-ftp+httpcaching+http") + << proxyList << proxyList.at(2) // skip the first two + << "https://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt" + << QNetworkReply::NoError; +#endif +} + +void tst_QNetworkReply::ioGetWithManyProxies() +{ + // Test proxy factories + + qRegisterMetaType<QNetworkProxy>(); // for QSignalSpy + qRegisterMetaType<QAuthenticator *>(); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + // set the proxy factory: + QFETCH(QList<QNetworkProxy>, proxyList); + MyProxyFactory *proxyFactory = new MyProxyFactory; + proxyFactory->toReturn = proxyList; + manager.setProxyFactory(proxyFactory); + + QFETCH(QString, url); + QUrl theUrl(url); + QNetworkRequest request(theUrl); + QNetworkReplyPtr reply = manager.get(request); + DataReader reader(reply); + + QSignalSpy authspy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); +#ifndef QT_NO_OPENSSL + connect(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), + SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); +#endif + QTestEventLoop::instance().enterLoop(15); + QVERIFY(!QTestEventLoop::instance().timeout()); + + manager.disconnect(SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); +#ifndef QT_NO_OPENSSL + manager.disconnect(SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), + this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>))); +#endif + + QFETCH(QNetworkReply::NetworkError, expectedError); + QEXPECT_FAIL("ftp-on-socks", "QFtp is too limited and won't accept non-FTP proxies", Abort); + QCOMPARE(reply->error(), expectedError); + + // Verify that the factory was called properly + QCOMPARE(proxyFactory->callCount, 1); + QCOMPARE(proxyFactory->lastQuery, QNetworkProxyQuery(theUrl)); + + if (expectedError == QNetworkReply::NoError) { + // request succeeded + QCOMPARE(reader.data, reference.readAll()); + + // now verify that the proxies worked: + QFETCH(QNetworkProxy, proxyUsed); + if (proxyUsed.type() == QNetworkProxy::NoProxy) { + QCOMPARE(authspy.count(), 0); + } else { + if (QByteArray(QTest::currentDataTag()).startsWith("ftp-")) + return; // No authentication with current FTP or with FTP proxies + QCOMPARE(authspy.count(), 1); + QCOMPARE(qvariant_cast<QNetworkProxy>(authspy.at(0).at(0)), proxyUsed); + } + } else { + // request failed + QCOMPARE(authspy.count(), 0); + } +} + +void tst_QNetworkReply::ioPutToFileFromFile_data() +{ + QTest::addColumn<QString>("fileName"); + + QTest::newRow("empty") << SRCDIR "/empty"; + QTest::newRow("real-file") << SRCDIR "/rfc3252.txt"; + QTest::newRow("resource") << ":/resource"; + QTest::newRow("search-path") << "srcdir:/rfc3252.txt"; +} + +void tst_QNetworkReply::ioPutToFileFromFile() +{ + QFETCH(QString, fileName); + QFile sourceFile(fileName); + QFile targetFile(testFileName); + + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + QUrl url = QUrl::fromLocalFile(targetFile.fileName()); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.put(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(sourceFile.atEnd()); + sourceFile.seek(0); // reset it to the beginning + + QVERIFY(targetFile.open(QIODevice::ReadOnly)); + QCOMPARE(targetFile.size(), sourceFile.size()); + QCOMPARE(targetFile.readAll(), sourceFile.readAll()); +} + +void tst_QNetworkReply::ioPutToFileFromSocket_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::ioPutToFileFromSocket() +{ + QFile file(testFileName); + + QUrl url = QUrl::fromLocalFile(file.fileName()); + QNetworkRequest request(url); + + QFETCH(QByteArray, data); + SocketPair socketpair; + socketpair.create(); + QVERIFY(socketpair.endPoints[0] && socketpair.endPoints[1]); + + socketpair.endPoints[0]->write(data); + QNetworkReplyPtr reply = manager.put(QNetworkRequest(url), socketpair.endPoints[1]); + socketpair.endPoints[0]->close(); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.size(), qint64(data.size())); + QByteArray contents = file.readAll(); + QCOMPARE(contents, data); +} + +void tst_QNetworkReply::ioPutToFileFromLocalSocket_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::ioPutToFileFromLocalSocket() +{ + QString socketname = "networkreplytest"; + QLocalServer server; + if (!server.listen(socketname)) { + QLocalServer::removeServer(socketname); + QVERIFY(server.listen(socketname)); + } + QLocalSocket active; + active.connectToServer(socketname); + QVERIFY2(server.waitForNewConnection(10), server.errorString().toLatin1().constData()); + QVERIFY2(active.waitForConnected(10), active.errorString().toLatin1().constData()); + QVERIFY2(server.hasPendingConnections(), server.errorString().toLatin1().constData()); + QLocalSocket *passive = server.nextPendingConnection(); + + QFile file(testFileName); + QUrl url = QUrl::fromLocalFile(file.fileName()); + QNetworkRequest request(url); + + QFETCH(QByteArray, data); + active.write(data); + active.close(); + QNetworkReplyPtr reply = manager.put(QNetworkRequest(url), passive); + passive->setParent(reply); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.size(), qint64(data.size())); + QByteArray contents = file.readAll(); + QCOMPARE(contents, data); +} + +void tst_QNetworkReply::ioPutToFileFromProcess_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::ioPutToFileFromProcess() +{ +#if defined(Q_OS_WINCE) || defined (Q_OS_SYMBIAN) + QSKIP("Currently no stdin/out supported for Windows CE / Symbian OS", SkipAll); +#else + +#ifdef Q_OS_WIN + if (qstrcmp(QTest::currentDataTag(), "small") == 0) + QSKIP("When passing a CR-LF-LF sequence through Windows stdio, it gets converted, " + "so this test fails. Disabled on Windows", SkipSingle); +#endif + +#if defined(QT_NO_PROCESS) + QSKIP("Qt was compiled with QT_NO_PROCESS", SkipAll); +#else + QFile file(testFileName); + + QUrl url = QUrl::fromLocalFile(file.fileName()); + QNetworkRequest request(url); + + QFETCH(QByteArray, data); + QProcess process; + process.start("echo/echo all"); + process.write(data); + process.closeWriteChannel(); + + QNetworkReplyPtr reply = manager.put(QNetworkRequest(url), &process); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.size(), qint64(data.size())); + QByteArray contents = file.readAll(); + QCOMPARE(contents, data); +#endif +#endif +} + +void tst_QNetworkReply::ioPutToFtpFromFile_data() +{ + ioPutToFileFromFile_data(); +} + +void tst_QNetworkReply::ioPutToFtpFromFile() +{ + QFETCH(QString, fileName); + QFile sourceFile(fileName); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + QUrl url("ftp://" + QtNetworkSettings::serverName()); + url.setPath(QString("/qtest/upload/qnetworkaccess-ioPutToFtpFromFile-%1-%2") + .arg(QTest::currentDataTag()) + .arg(uniqueExtension)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.put(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(reply->readAll().isEmpty()); + + QVERIFY(sourceFile.atEnd()); + sourceFile.seek(0); // reset it to the beginning + + // download the file again from FTP to make sure it was uploaded + // correctly + QFtp ftp; + ftp.connectToHost(url.host()); + ftp.login(); + ftp.get(url.path()); + + QObject::connect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(3); + QObject::disconnect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QByteArray uploaded = ftp.readAll(); + QCOMPARE(qint64(uploaded.size()), sourceFile.size()); + QCOMPARE(uploaded, sourceFile.readAll()); + + ftp.close(); + QObject::connect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QObject::disconnect(&ftp, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); +} + +void tst_QNetworkReply::ioPutToHttpFromFile_data() +{ + ioPutToFileFromFile_data(); +} + +void tst_QNetworkReply::ioPutToHttpFromFile() +{ + QFETCH(QString, fileName); + QFile sourceFile(fileName); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + QUrl url("http://" + QtNetworkSettings::serverName()); + url.setPath(QString("/dav/qnetworkaccess-ioPutToHttpFromFile-%1-%2") + .arg(QTest::currentDataTag()) + .arg(uniqueExtension)); + + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.put(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + // verify that the HTTP status code is 201 Created + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 201); + + QVERIFY(sourceFile.atEnd()); + sourceFile.seek(0); // reset it to the beginning + + // download the file again from HTTP to make sure it was uploaded + // correctly + reply = manager.get(request); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok + + QCOMPARE(reply->readAll(), sourceFile.readAll()); +} + +void tst_QNetworkReply::ioPostToHttpFromFile_data() +{ + ioPutToFileFromFile_data(); +} + +void tst_QNetworkReply::ioPostToHttpFromFile() +{ + QFETCH(QString, fileName); + QFile sourceFile(fileName); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + + QNetworkReplyPtr reply = manager.post(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + 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); + + QVERIFY(sourceFile.atEnd()); + sourceFile.seek(0); // reset it to the beginning + + QCOMPARE(reply->readAll().trimmed(), md5sum(sourceFile.readAll()).toHex()); +} + +void tst_QNetworkReply::ioPostToHttpFromSocket_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QByteArray>("md5sum"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QNetworkProxy>("proxy"); + QTest::addColumn<int>("authenticationRequiredCount"); + QTest::addColumn<int>("proxyAuthenticationRequiredCount"); + + for (int i = 0; i < proxies.count(); ++i) + for (int auth = 0; auth < 2; ++auth) { + QUrl url; + if (auth) + url = "http://" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi"; + else + url = "http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"; + + QNetworkProxy proxy = proxies.at(i).proxy; + QByteArray testsuffix = QByteArray(auth ? "+auth" : "") + proxies.at(i).tag; + int proxyauthcount = proxies.at(i).requiresAuthentication; + + QByteArray data; + data = ""; + QTest::newRow("empty" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + + data = "This is a normal message."; + QTest::newRow("generic" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + + data = "This is a message to show that Qt rocks!\r\n\n"; + QTest::newRow("small" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + + data = QByteArray("abcd\0\1\2\abcd",12); + QTest::newRow("with-nul" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + + data = QByteArray(4097, '\4'); + QTest::newRow("4k+1" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + + data = QByteArray(128*1024+1, '\177'); + QTest::newRow("128k+1" + testsuffix) << data << md5sum(data) << url << proxy << auth << proxyauthcount; + } +} + +void tst_QNetworkReply::ioPostToHttpFromSocket() +{ + qRegisterMetaType<QNetworkProxy>(); // for QSignalSpy + qRegisterMetaType<QAuthenticator *>(); + qRegisterMetaType<QNetworkReply *>(); + + QFETCH(QByteArray, data); + QFETCH(QUrl, url); + QFETCH(QNetworkProxy, proxy); + SocketPair socketpair; + socketpair.create(); + QVERIFY(socketpair.endPoints[0] && socketpair.endPoints[1]); + + socketpair.endPoints[0]->write(data); + + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + + manager.setProxy(proxy); + QNetworkReplyPtr reply = manager.post(request, socketpair.endPoints[1]); + socketpair.endPoints[0]->close(); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QSignalSpy authenticationRequiredSpy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QSignalSpy proxyAuthenticationRequiredSpy(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + + QTestEventLoop::instance().enterLoop(12); + disconnect(&manager, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); + disconnect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); + + 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()); + + QTEST(authenticationRequiredSpy.count(), "authenticationRequiredCount"); + QTEST(proxyAuthenticationRequiredSpy.count(), "proxyAuthenticationRequiredCount"); +} + +void tst_QNetworkReply::ioPostToHttpFromSocketSynchronous_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QByteArray>("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.setRawHeader("Content-Type", "application/octet-stream"); + request.setAttribute( + QNetworkRequest::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. +void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileToEnd() +{ + QFile sourceFile(SRCDIR "/rfc3252.txt"); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + // seeking to the middle + sourceFile.seek(sourceFile.size() / 2); + + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi"; + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + QNetworkReplyPtr reply = manager.post(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QTestEventLoop::instance().enterLoop(2); + disconnect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // compare half data + sourceFile.seek(sourceFile.size() / 2); + QByteArray data = sourceFile.readAll(); + QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); +} + +void tst_QNetworkReply::ioPostToHttpFromMiddleOfFileFiveBytes() +{ + QFile sourceFile(SRCDIR "/rfc3252.txt"); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + // seeking to the middle + sourceFile.seek(sourceFile.size() / 2); + + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi"; + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + // only send 5 bytes + request.setHeader(QNetworkRequest::ContentLengthHeader, 5); + QVERIFY(request.header(QNetworkRequest::ContentLengthHeader).isValid()); + QNetworkReplyPtr reply = manager.post(request, &sourceFile); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QTestEventLoop::instance().enterLoop(2); + disconnect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // compare half data + sourceFile.seek(sourceFile.size() / 2); + QByteArray data = sourceFile.read(5); + QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); +} + +void tst_QNetworkReply::ioPostToHttpFromMiddleOfQBufferFiveBytes() +{ + // test needed since a QBuffer goes with a different codepath than the QFile + // tested in ioPostToHttpFromMiddleOfFileFiveBytes + QBuffer uploadBuffer; + uploadBuffer.open(QIODevice::ReadWrite); + uploadBuffer.write("1234567890"); + uploadBuffer.seek(5); + + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi"; + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.post(request, &uploadBuffer); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QTestEventLoop::instance().enterLoop(2); + disconnect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // compare half data + uploadBuffer.seek(5); + QByteArray data = uploadBuffer.read(5); + QCOMPARE(reply->readAll().trimmed(), md5sum(data).toHex()); +} + + +void tst_QNetworkReply::ioPostToHttpNoBufferFlag() +{ + QByteArray data = QByteArray("daaaaaaataaaaaaa"); + // create a sequential QIODevice by feeding the data into a local TCP server + SocketPair socketpair; + socketpair.create(); + QVERIFY(socketpair.endPoints[0] && socketpair.endPoints[1]); + socketpair.endPoints[0]->write(data); + + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi"; + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + // disallow buffering + request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); + request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); + QNetworkReplyPtr reply = manager.post(request, socketpair.endPoints[1]); + socketpair.endPoints[0]->close(); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + QTestEventLoop::instance().enterLoop(2); + disconnect(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), + this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*))); + + // verify: error code is QNetworkReply::ContentReSendError + QCOMPARE(reply->error(), QNetworkReply::ContentReSendError); +} + +#ifndef QT_NO_OPENSSL +class SslServer : public QTcpServer { + Q_OBJECT +public: + SslServer() : socket(0) {}; + void incomingConnection(int socketDescriptor) { + QSslSocket *serverSocket = new QSslSocket; + serverSocket->setParent(this); + + if (serverSocket->setSocketDescriptor(socketDescriptor)) { + connect(serverSocket, SIGNAL(encrypted()), this, SLOT(encryptedSlot())); + serverSocket->setProtocol(QSsl::AnyProtocol); + connect(serverSocket, SIGNAL(sslErrors(const QList<QSslError>&)), serverSocket, SLOT(ignoreSslErrors())); + serverSocket->setLocalCertificate(SRCDIR "/certs/server.pem"); + serverSocket->setPrivateKey(SRCDIR "/certs/server.key"); + serverSocket->startServerEncryption(); + } else { + delete serverSocket; + } + } +signals: + void newEncryptedConnection(); +public slots: + void encryptedSlot() { + socket = (QSslSocket*) sender(); + emit newEncryptedConnection(); + } +public: + QSslSocket *socket; +}; + +// very similar to ioPostToHttpUploadProgress but for SSL +void tst_QNetworkReply::ioPostToHttpsUploadProgress() +{ + QFile sourceFile(SRCDIR "/bigfile"); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + // emulate a minimal https server + SslServer server; + server.listen(QHostAddress(QHostAddress::LocalHost), 0); + + // create the request + QUrl url = QUrl(QString("https://127.0.0.1:%1/").arg(server.serverPort())); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + QNetworkReplyPtr reply = manager.post(request, &sourceFile); + QSignalSpy spy(reply, SIGNAL(uploadProgress(qint64,qint64))); + connect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); + connect(reply, SIGNAL(sslErrors(const QList<QSslError>&)), reply, SLOT(ignoreSslErrors())); + + // get the request started and the incoming socket connected + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QTcpSocket *incomingSocket = server.socket; + QVERIFY(incomingSocket); + disconnect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + + incomingSocket->setReadBufferSize(1*1024); + QTestEventLoop::instance().enterLoop(2); + // some progress should have been made + QVERIFY(!spy.isEmpty()); + QList<QVariant> args = spy.last(); + QVERIFY(args.at(0).toLongLong() > 0); + + QVERIFY(args.at(0).toLongLong() != sourceFile.size()); + + incomingSocket->setReadBufferSize(32*1024); + incomingSocket->read(16*1024); + QTestEventLoop::instance().enterLoop(2); + // some more progress than before + QVERIFY(!spy.isEmpty()); + QList<QVariant> args2 = spy.last(); + QVERIFY(args2.at(0).toLongLong() > args.at(0).toLongLong()); + + // set the read buffer to unlimited + incomingSocket->setReadBufferSize(0); + QTestEventLoop::instance().enterLoop(10); + // progress should be finished + QVERIFY(!spy.isEmpty()); + QList<QVariant> args3 = spy.last(); + QVERIFY(args3.at(0).toLongLong() > args2.at(0).toLongLong()); + QCOMPARE(args3.at(0).toLongLong(), args3.at(1).toLongLong()); + QCOMPARE(args3.at(0).toLongLong(), sourceFile.size()); + + // after sending this, the QNAM should emit finished() + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + incomingSocket->write("HTTP/1.0 200 OK\r\n"); + incomingSocket->write("Content-Length: 0\r\n"); + incomingSocket->write("\r\n"); + QTestEventLoop::instance().enterLoop(10); + // not timeouted -> finished() was emitted + QVERIFY(!QTestEventLoop::instance().timeout()); + + incomingSocket->close(); + server.close(); +} +#endif + +void tst_QNetworkReply::ioGetFromBuiltinHttp_data() +{ + QTest::addColumn<bool>("https"); + QTest::addColumn<int>("bufferSize"); + QTest::newRow("http+unlimited") << false << 0; + QTest::newRow("http+limited") << false << 4096; +#ifndef QT_NO_OPENSSL + QTest::newRow("https+unlimited") << true << 0; + QTest::newRow("https+limited") << true << 4096; +#endif +} + +void tst_QNetworkReply::ioGetFromBuiltinHttp() +{ + QSKIP("Limiting is broken right now, check QTBUG-15065", SkipAll); + QFETCH(bool, https); + QFETCH(int, bufferSize); + + QByteArray testData; + // Make the data big enough so that it can fill the kernel buffer + // (which seems to hold 202 KB here) + const int wantedSize = 1200 * 1000; + testData.reserve(wantedSize); + // And in the case of SSL, the compression can fool us and let the + // server send the data much faster than expected. + // So better provide random data that cannot be compressed. + for (int i = 0; i < wantedSize; ++i) + testData += (char)qrand(); + + QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: "); + httpResponse += QByteArray::number(testData.size()); + httpResponse += "\r\n\r\n"; + httpResponse += testData; + + qDebug() << "Server will send" << (httpResponse.size()-testData.size()) << "bytes of header and" + << testData.size() << "bytes of data"; + + const bool fillKernelBuffer = bufferSize > 0; + FastSender server(httpResponse, https, fillKernelBuffer); + + QUrl url(QString("%1://127.0.0.1:%2/qtest/rfc3252.txt") + .arg(https?"https":"http") + .arg(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.get(request); + reply->setReadBufferSize(bufferSize); + reply->ignoreSslErrors(); + const int rate = 200; // in kB per sec + RateControlledReader reader(server, reply, rate, bufferSize); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTime loopTime; + loopTime.start(); + QTestEventLoop::instance().enterLoop(30); + const int elapsedTime = loopTime.elapsed(); + server.wait(); + reader.wrapUp(); + + qDebug() << "send rate:" << server.transferRate << "B/s"; + qDebug() << "receive rate:" << reader.totalBytesRead * 1000 / elapsedTime + << "(it received" << reader.totalBytesRead << "bytes in" << elapsedTime << "ms)"; + + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), (qint64)testData.size()); + if (reader.data.size() < testData.size()) { // oops? + QCOMPARE(reader.data, testData.mid(0, reader.data.size())); + qDebug() << "The data is incomplete, the last" << testData.size() - reader.data.size() << "bytes are missing"; + QEXPECT_FAIL("http+limited", "Limiting is broken right now, check QTBUG-15065", Abort); + QEXPECT_FAIL("https+limited", "Limiting is broken right now, check QTBUG-15065", Abort); + } + QCOMPARE(reader.data.size(), testData.size()); + QCOMPARE(reader.data, testData); + + // OK we got the file alright, but did setReadBufferSize work? + QVERIFY(server.transferRate != -1); + if (bufferSize > 0) { + const int allowedDeviation = 16; // TODO find out why the send rate is 13% faster currently + const int minRate = rate * 1024 * (100-allowedDeviation) / 100; + const int maxRate = rate * 1024 * (100+allowedDeviation) / 100; + qDebug() << minRate << "<="<< server.transferRate << "<=" << maxRate << "?"; + QEXPECT_FAIL("http+limited", "Limiting is broken right now, check QTBUG-15065", Continue); + QEXPECT_FAIL("https+limited", "Limiting is broken right now, check QTBUG-15065", Continue); + QVERIFY(server.transferRate >= minRate && server.transferRate <= maxRate); + } +} + +void tst_QNetworkReply::ioPostToHttpUploadProgress() +{ + QFile sourceFile(SRCDIR "/bigfile"); + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + + // emulate a minimal http server + QTcpServer server; + server.listen(QHostAddress(QHostAddress::LocalHost), 0); + + // create the request + QUrl url = QUrl(QString("http://127.0.0.1:%1/").arg(server.serverPort())); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.post(request, &sourceFile); + QSignalSpy spy(reply, SIGNAL(uploadProgress(qint64,qint64))); + connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + // get the request started and the incoming socket connected + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QTcpSocket *incomingSocket = server.nextPendingConnection(); + QVERIFY(incomingSocket); + disconnect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + incomingSocket->setReadBufferSize(1*1024); + QTestEventLoop::instance().enterLoop(5); + // some progress should have been made + QList<QVariant> args = spy.last(); + QVERIFY(!args.isEmpty()); + QVERIFY(args.at(0).toLongLong() > 0); + // but not everything! + QVERIFY(args.at(0).toLongLong() != sourceFile.size()); + + // set the read buffer to unlimited + incomingSocket->setReadBufferSize(0); + QTestEventLoop::instance().enterLoop(10); + // progress should be finished + QList<QVariant> args3 = spy.last(); + QVERIFY(!args3.isEmpty()); + // More progress than before + QVERIFY(args3.at(0).toLongLong() > args.at(0).toLongLong()); + QCOMPARE(args3.at(0).toLongLong(), args3.at(1).toLongLong()); + // And actually finished.. + QCOMPARE(args3.at(0).toLongLong(), sourceFile.size()); + + // after sending this, the QNAM should emit finished() + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + incomingSocket->write("HTTP/1.0 200 OK\r\n"); + incomingSocket->write("Content-Length: 0\r\n"); + incomingSocket->write("\r\n"); + QTestEventLoop::instance().enterLoop(10); + // not timeouted -> finished() was emitted + QVERIFY(!QTestEventLoop::instance().timeout()); + + incomingSocket->close(); + server.close(); +} + +void tst_QNetworkReply::ioPostToHttpEmptyUploadProgress() +{ + QByteArray ba; + ba.resize(0); + QBuffer buffer(&ba,0); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + // emulate a minimal http server + QTcpServer server; + server.listen(QHostAddress(QHostAddress::LocalHost), 0); + + // create the request + QUrl url = QUrl(QString("http://127.0.0.1:%1/").arg(server.serverPort())); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + QNetworkReplyPtr reply = manager.post(request, &buffer); + QSignalSpy spy(reply, SIGNAL(uploadProgress(qint64,qint64))); + connect(&server, SIGNAL(newConnection()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + + // get the request started and the incoming socket connected + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QTcpSocket *incomingSocket = server.nextPendingConnection(); + QVERIFY(incomingSocket); + + // after sending this, the QNAM should emit finished() + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + incomingSocket->write("HTTP/1.0 200 OK\r\n"); + incomingSocket->write("Content-Length: 0\r\n"); + incomingSocket->write("\r\n"); + incomingSocket->flush(); + QTestEventLoop::instance().enterLoop(10); + // not timeouted -> finished() was emitted + QVERIFY(!QTestEventLoop::instance().timeout()); + + // final check: only 1 uploadProgress has been emitted + QVERIFY(spy.length() == 1); + QList<QVariant> args = spy.last(); + QVERIFY(!args.isEmpty()); + QCOMPARE(args.at(0).toLongLong(), buffer.size()); + QCOMPARE(args.at(0).toLongLong(), buffer.size()); + + incomingSocket->close(); + server.close(); +} + +void tst_QNetworkReply::lastModifiedHeaderForFile() +{ + QFileInfo fileInfo(SRCDIR "/bigfile"); + QVERIFY(fileInfo.exists()); + + QUrl url = QUrl::fromLocalFile(fileInfo.filePath()); + + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.head(request); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); + QCOMPARE(header, fileInfo.lastModified()); +} + +void tst_QNetworkReply::lastModifiedHeaderForHttp() +{ + // Tue, 22 May 2007 12:04:57 GMT according to webserver + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/fluke.gif"; + + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.head(request); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QDateTime header = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); + QDateTime realDate = QDateTime::fromString("2007-05-22T12:04:57", Qt::ISODate); + realDate.setTimeSpec(Qt::UTC); + + QCOMPARE(header, realDate); +} + +void tst_QNetworkReply::httpCanReadLine() +{ + QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt")); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QVERIFY(reply->canReadLine()); + QVERIFY(!reply->readAll().isEmpty()); + QVERIFY(!reply->canReadLine()); +} + +void tst_QNetworkReply::rateControl_data() +{ + QTest::addColumn<int>("rate"); + + QTest::newRow("15") << 15; + QTest::newRow("40") << 40; + QTest::newRow("73") << 73; + QTest::newRow("80") << 80; + QTest::newRow("125") << 125; + QTest::newRow("250") << 250; + QTest::newRow("1024") << 1024; +} + +void tst_QNetworkReply::rateControl() +{ + QSKIP("Test disabled -- only for manual purposes", SkipAll); + // this function tests that we aren't reading from the network + // faster than the data is being consumed. + QFETCH(int, rate); + + // ask for 20 seconds worth of data + FastSender sender(20 * rate * 1024); + + QNetworkRequest request("debugpipe://localhost:" + QString::number(sender.serverPort())); + QNetworkReplyPtr reply = manager.get(request); + reply->setReadBufferSize(32768); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); + QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + + RateControlledReader reader(sender, reply, rate, 20); + + // this test is designed to run for 25 seconds at most + QTime loopTime; + loopTime.start(); + QTestEventLoop::instance().enterLoop(40); + int elapsedTime = loopTime.elapsed(); + + if (!errorSpy.isEmpty()) { + qDebug() << "ERROR!" << errorSpy[0][0] << reply->errorString(); + } + + qDebug() << "tst_QNetworkReply::rateControl" << "send rate:" << sender.transferRate; + qDebug() << "tst_QNetworkReply::rateControl" << "receive rate:" << reader.totalBytesRead * 1000 / elapsedTime + << "(it received" << reader.totalBytesRead << "bytes in" << elapsedTime << "ms)"; + + sender.wait(); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QVERIFY(sender.transferRate != -1); + int minRate = rate * 1024 * 9 / 10; + int maxRate = rate * 1024 * 11 / 10; + QVERIFY(sender.transferRate >= minRate); + QVERIFY(sender.transferRate <= maxRate); +} + +void tst_QNetworkReply::downloadProgress_data() +{ + QTest::addColumn<int>("loopCount"); + + QTest::newRow("empty") << 0; + QTest::newRow("small") << 4; +#ifndef Q_OS_SYMBIAN + QTest::newRow("big") << 4096; +#else + // it can run even with 4096 + // but it takes lot time + //especially on emulator + QTest::newRow("big") << 1024; +#endif +} + +void tst_QNetworkReply::downloadProgress() +{ + QTcpServer server; + QVERIFY(server.listen()); + + QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1"); + QNetworkReplyPtr reply = manager.get(request); + QSignalSpy spy(reply, SIGNAL(downloadProgress(qint64,qint64))); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QVERIFY(spy.isValid()); + QVERIFY(!reply->isFinished()); + QVERIFY(reply->isRunning()); + + QCoreApplication::instance()->processEvents(); + if (!server.hasPendingConnections()) + server.waitForNewConnection(1000); + QVERIFY(server.hasPendingConnections()); + QCOMPARE(spy.count(), 0); + + QByteArray data(128, 'a'); + QTcpSocket *sender = server.nextPendingConnection(); + QVERIFY(sender); + + QFETCH(int, loopCount); + for (int i = 1; i <= loopCount; ++i) { + sender->write(data); + QVERIFY2(sender->waitForBytesWritten(2000), "Network timeout"); + + spy.clear(); + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(spy.count() > 0); + QVERIFY(!reply->isFinished()); + QVERIFY(reply->isRunning()); + + QList<QVariant> args = spy.last(); + QCOMPARE(args.at(0).toInt(), i*data.size()); + QCOMPARE(args.at(1).toInt(), -1); + } + + // close the connection: + delete sender; + + spy.clear(); + QTestEventLoop::instance().enterLoop(2); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(spy.count() > 0); + QVERIFY(!reply->isRunning()); + QVERIFY(reply->isFinished()); + + QList<QVariant> args = spy.last(); + QCOMPARE(args.at(0).toInt(), loopCount * data.size()); + QCOMPARE(args.at(1).toInt(), loopCount * data.size()); +} + +void tst_QNetworkReply::uploadProgress_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::uploadProgress() +{ + QFETCH(QByteArray, data); + QTcpServer server; + QVERIFY(server.listen()); + + QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1"); + QNetworkReplyPtr reply = manager.put(request, data); + QSignalSpy spy(reply, SIGNAL(uploadProgress(qint64,qint64))); + QSignalSpy finished(reply, SIGNAL(finished())); + QVERIFY(spy.isValid()); + QVERIFY(finished.isValid()); + + QCoreApplication::instance()->processEvents(); + if (!server.hasPendingConnections()) + server.waitForNewConnection(1000); + QVERIFY(server.hasPendingConnections()); + + QTcpSocket *receiver = server.nextPendingConnection(); + if (finished.count() == 0) { + // it's not finished yet, so wait for it to be + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + delete receiver; + + QVERIFY(finished.count() > 0); + QVERIFY(spy.count() > 0); + + QList<QVariant> args = spy.last(); + QCOMPARE(args.at(0).toInt(), data.size()); + QCOMPARE(args.at(1).toInt(), data.size()); +} + +void tst_QNetworkReply::chaining_data() +{ + putToFile_data(); +} + +void tst_QNetworkReply::chaining() +{ + QTemporaryFile sourceFile(QDir::currentPath() + "/temp-XXXXXX"); + sourceFile.setAutoRemove(true); + QVERIFY(sourceFile.open()); + + QFETCH(QByteArray, data); + QVERIFY(sourceFile.write(data) == data.size()); + sourceFile.flush(); + QCOMPARE(sourceFile.size(), qint64(data.size())); + + QNetworkRequest request(QUrl::fromLocalFile(sourceFile.fileName())); + QNetworkReplyPtr getReply = manager.get(request); + + QFile targetFile(testFileName); + QUrl url = QUrl::fromLocalFile(targetFile.fileName()); + request.setUrl(url); + QNetworkReplyPtr putReply = manager.put(request, getReply); + + connect(putReply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(getReply->url(), QUrl::fromLocalFile(sourceFile.fileName())); + QCOMPARE(getReply->error(), QNetworkReply::NoError); + QCOMPARE(getReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), sourceFile.size()); + + QCOMPARE(putReply->url(), url); + QCOMPARE(putReply->error(), QNetworkReply::NoError); + QCOMPARE(putReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), Q_INT64_C(0)); + QVERIFY(putReply->readAll().isEmpty()); + + QVERIFY(sourceFile.atEnd()); + sourceFile.seek(0); // reset it to the beginning + + QVERIFY(targetFile.open(QIODevice::ReadOnly)); + QCOMPARE(targetFile.size(), sourceFile.size()); + QCOMPARE(targetFile.readAll(), sourceFile.readAll()); +} + +void tst_QNetworkReply::receiveCookiesFromHttp_data() +{ + QTest::addColumn<QString>("cookieString"); + QTest::addColumn<QList<QNetworkCookie> >("expectedCookiesFromHttp"); + QTest::addColumn<QList<QNetworkCookie> >("expectedCookiesInJar"); + + QTest::newRow("empty") << "" << QList<QNetworkCookie>() << QList<QNetworkCookie>(); + + QList<QNetworkCookie> header, jar; + QNetworkCookie cookie("a", "b"); + header << cookie; + cookie.setDomain(QtNetworkSettings::serverName()); + cookie.setPath("/qtest/cgi-bin/"); + jar << cookie; + QTest::newRow("simple-cookie") << "a=b" << header << jar; + + header << QNetworkCookie("c", "d"); + cookie.setName("c"); + cookie.setValue("d"); + jar << cookie; + QTest::newRow("two-cookies") << "a=b, c=d" << header << jar; + QTest::newRow("two-cookies-2") << "a=b\nc=d" << header << jar; + + header.clear(); + jar.clear(); + cookie = QNetworkCookie("a", "b"); + cookie.setPath("/not/part-of-path"); + header << cookie; + cookie.setDomain(QtNetworkSettings::serverName()); + jar << cookie; + QTest::newRow("invalid-cookie-path") << "a=b; path=/not/part-of-path" << header << jar; + + jar.clear(); + cookie = QNetworkCookie("a", "b"); + cookie.setDomain(".example.com"); + header.clear(); + header << cookie; + QTest::newRow("invalid-cookie-domain") << "a=b; domain=.example.com" << header << jar; +} + +void tst_QNetworkReply::receiveCookiesFromHttp() +{ + QFETCH(QString, cookieString); + + QByteArray data = cookieString.toLatin1() + '\n'; + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/set-cookie.cgi"); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", "application/octet-stream"); + 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<QNetworkCookie> setCookies = + qvariant_cast<QList<QNetworkCookie> >(reply->header(QNetworkRequest::SetCookieHeader)); + QTEST(setCookies, "expectedCookiesFromHttp"); + 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.setRawHeader("Content-Type", "application/octet-stream"); + request.setAttribute( + QNetworkRequest::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<QNetworkCookie> setCookies = + qvariant_cast<QList<QNetworkCookie> >(reply->header(QNetworkRequest::SetCookieHeader)); + QTEST(setCookies, "expectedCookiesFromHttp"); + QTEST(cookieJar->allCookies(), "expectedCookiesInJar"); +} + +void tst_QNetworkReply::sendCookies_data() +{ + QTest::addColumn<QList<QNetworkCookie> >("cookiesToSet"); + QTest::addColumn<QString>("expectedCookieString"); + + QList<QNetworkCookie> list; + QTest::newRow("empty") << list << ""; + + QNetworkCookie cookie("a", "b"); + cookie.setPath("/"); + cookie.setDomain("example.com"); + list << cookie; + QTest::newRow("no-match-domain") << list << ""; + + cookie.setDomain(QtNetworkSettings::serverName()); + cookie.setPath("/something/else"); + list << cookie; + QTest::newRow("no-match-path") << list << ""; + + cookie.setPath("/"); + list << cookie; + QTest::newRow("simple-cookie") << list << "a=b"; + + cookie.setPath("/qtest"); + cookie.setValue("longer"); + list << cookie; + QTest::newRow("two-cookies") << list << "a=longer; a=b"; + + list.clear(); + cookie = QNetworkCookie("a", "b"); + cookie.setPath("/"); + cookie.setDomain("." + QtNetworkSettings::serverDomainName()); + list << cookie; + QTest::newRow("domain-match") << list << "a=b"; + + // but it shouldn't match this: + cookie.setDomain(QtNetworkSettings::serverDomainName()); + list << cookie; + QTest::newRow("domain-match-2") << list << "a=b"; +} + +void tst_QNetworkReply::sendCookies() +{ + QFETCH(QString, expectedCookieString); + QFETCH(QList<QNetworkCookie>, cookiesToSet); + cookieJar->setAllCookies(cookiesToSet); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/get-cookie.cgi"); + QNetworkRequest request(url); + 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::sendCookiesSynchronous_data() +{ + tst_QNetworkReply::sendCookies_data(); +} + +void tst_QNetworkReply::sendCookiesSynchronous() +{ + QFETCH(QString, expectedCookieString); + QFETCH(QList<QNetworkCookie>, cookiesToSet); + cookieJar->setAllCookies(cookiesToSet); + + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/get-cookie.cgi"); + QNetworkRequest request(url); + + request.setAttribute( + QNetworkRequest::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; + + // 16 seconds: fluke times out in 15 seconds, which triggers a QTcpSocket error + QTimer::singleShot(16000, &subloop, SLOT(quit())); + subloop.exec(); + + QTestEventLoop::instance().exitLoop(); +} + +void tst_QNetworkReply::nestedEventLoops() +{ + // Slightly fragile test, it may not be testing anything + // This is certifying that we're not running into the same issue + // that QHttp had (task 200432): the QTcpSocket connection is + // closed by the remote end because of the kept-alive HTTP + // connection timed out. + // + // The exact time required for this to happen is not exactly + // defined. Our server (Apache httpd) times out after 15 + // seconds. (see above) + + qDebug("Takes 16 seconds to run, please wait"); + qRegisterMetaType<QNetworkReply::NetworkError>(); + + QUrl url("http://" + QtNetworkSettings::serverName()); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.get(request); + + QSignalSpy finishedspy(reply, SIGNAL(finished())); + QSignalSpy errorspy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + + connect(reply, SIGNAL(finished()), SLOT(nestedEventLoops_slot())); + QTestEventLoop::instance().enterLoop(20); + QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); + + QCOMPARE(finishedspy.count(), 1); + QCOMPARE(errorspy.count(), 0); +} + +void tst_QNetworkReply::httpProxyCommands_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QByteArray>("responseToSend"); + QTest::addColumn<QString>("expectedCommand"); + + QTest::newRow("http") + << QUrl("http://0.0.0.0:4443/http-request") + << QByteArray("HTTP/1.0 200 OK\r\nProxy-Connection: close\r\nContent-Length: 1\r\n\r\n1") + << "GET http://0.0.0.0:4443/http-request HTTP/1."; +#ifndef QT_NO_OPENSSL + QTest::newRow("https") + << QUrl("https://0.0.0.0:4443/https-request") + << QByteArray("HTTP/1.0 200 Connection Established\r\n\r\n") + << "CONNECT 0.0.0.0:4443 HTTP/1."; +#endif +} + +void tst_QNetworkReply::httpProxyCommands() +{ + QFETCH(QUrl, url); + QFETCH(QByteArray, responseToSend); + QFETCH(QString, expectedCommand); + + MiniHttpServer proxyServer(responseToSend); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer.serverPort()); + + manager.setProxy(proxy); + QNetworkReplyPtr reply = manager.get(QNetworkRequest(url)); + manager.setProxy(QNetworkProxy()); + + // wait for the finished signal + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QTestEventLoop::instance().enterLoop(1); + + QVERIFY(!QTestEventLoop::instance().timeout()); + + //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); +} + +class ProxyChangeHelper : public QObject { + Q_OBJECT +public: + ProxyChangeHelper() : QObject(), signalCount(0) {}; +public slots: + void finishedSlot() { + signalCount++; + if (signalCount == 2) + QMetaObject::invokeMethod(&QTestEventLoop::instance(), "exitLoop", Qt::QueuedConnection); + } +private: + int signalCount; +}; + +void tst_QNetworkReply::httpProxyCommandsSynchronous_data() +{ + httpProxyCommands_data(); +} + +struct QThreadCleanup +{ + static inline void cleanup(QThread *thread) + { + thread->quit(); + if (thread->wait(3000)) + delete thread; + else + qWarning("thread hung, leaking memory so test can finish"); + } +}; + +struct QDeleteLaterCleanup +{ + static inline void cleanup(QObject *o) + { + o->deleteLater(); + } +}; + +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 + QScopedPointer<QThread, QThreadCleanup> serverThread(new QThread); + QScopedPointer<MiniHttpServer, QDeleteLaterCleanup> proxyServer(new MiniHttpServer(responseToSend, false, serverThread.data())); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer->serverPort()); + + manager.setProxy(proxy); + QNetworkRequest request(url); + + // send synchronous request + request.setAttribute( + QNetworkRequest::SynchronousRequestAttribute, + true); + + QNetworkReplyPtr reply = manager.get(request); + QVERIFY(reply->isFinished()); // synchronous + manager.setProxy(QNetworkProxy()); + + //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; + MiniHttpServer proxyServer( + "HTTP/1.0 200 OK\r\nProxy-Connection: keep-alive\r\n" + "Content-Length: 1\r\n\r\n1"); + QNetworkProxy dummyProxy(QNetworkProxy::HttpProxy, "127.0.0.1", proxyServer.serverPort()); + QNetworkRequest req(QUrl("http://" + QtNetworkSettings::serverName())); + proxyServer.doClose = false; + + manager.setProxy(dummyProxy); + QNetworkReplyPtr reply1 = manager.get(req); + connect(reply1, SIGNAL(finished()), &helper, SLOT(finishedSlot())); + + manager.setProxy(QNetworkProxy()); + QNetworkReplyPtr reply2 = manager.get(req); + connect(reply2, SIGNAL(finished()), &helper, SLOT(finishedSlot())); + + QTestEventLoop::instance().enterLoop(20); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // verify that the replies succeeded + QCOMPARE(reply1->error(), QNetworkReply::NoError); + QCOMPARE(reply1->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(reply1->size() == 1); + + QCOMPARE(reply2->error(), QNetworkReply::NoError); + QCOMPARE(reply2->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(reply2->size() > 1); + + // now try again and get an error + // this verifies that we reuse the already-open connection + + proxyServer.doClose = true; + proxyServer.dataToTransmit = + "HTTP/1.0 403 Forbidden\r\nProxy-Connection: close\r\n" + "Content-Length: 1\r\n\r\n1"; + + manager.setProxy(dummyProxy); + QNetworkReplyPtr reply3 = manager.get(req); + connect(reply3, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(5); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(int(reply3->error()) > 0); +} + +void tst_QNetworkReply::authorizationError_data() +{ + + QTest::addColumn<QString>("url"); + QTest::addColumn<int>("errorSignalCount"); + QTest::addColumn<int>("finishedSignalCount"); + QTest::addColumn<int>("error"); + QTest::addColumn<int>("httpStatusCode"); + QTest::addColumn<QString>("httpBody"); + + QTest::newRow("unknown-authorization-method") << "http://" + QtNetworkSettings::serverName() + + "/qtest/cgi-bin/http-unknown-authentication-method.cgi?401-authorization-required" << 1 << 1 + << int(QNetworkReply::AuthenticationRequiredError) << 401 << "authorization required"; + QTest::newRow("unknown-proxy-authorization-method") << "http://" + QtNetworkSettings::serverName() + + "/qtest/cgi-bin/http-unknown-authentication-method.cgi?407-proxy-authorization-required" << 1 << 1 + << int(QNetworkReply::ProxyAuthenticationRequiredError) << 407 + << "authorization required"; +} + +void tst_QNetworkReply::authorizationError() +{ + QFETCH(QString, url); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.get(request); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + + qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); + QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + // now run the request: + connect(reply, SIGNAL(finished()), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QFETCH(int, errorSignalCount); + QCOMPARE(errorSpy.count(), errorSignalCount); + QFETCH(int, finishedSignalCount); + QCOMPARE(finishedSpy.count(), finishedSignalCount); + QFETCH(int, error); + QCOMPARE(reply->error(), QNetworkReply::NetworkError(error)); + + QFETCH(int, httpStatusCode); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), httpStatusCode); + + QFETCH(QString, httpBody); + QCOMPARE(qint64(reply->size()), qint64(httpBody.size())); + QCOMPARE(QString(reply->readAll()), httpBody); +} + +void tst_QNetworkReply::httpConnectionCount() +{ + QTcpServer server; + QVERIFY(server.listen()); + QCoreApplication::instance()->processEvents(); + + for (int i = 0; i < 10; i++) { + QNetworkRequest request (QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/" + QString::number(i))); + QNetworkReply* reply = manager.get(request); + reply->setParent(&server); + } + + int pendingConnectionCount = 0; + QTime time; + time.start(); + + while(pendingConnectionCount <= 20) { + QTestEventLoop::instance().enterLoop(1); + QTcpSocket *socket = server.nextPendingConnection(); + while (socket != 0) { + pendingConnectionCount++; + socket->setParent(&server); + socket = server.nextPendingConnection(); + } + + // at max. wait 10 sec + if (time.elapsed() > 10000) + break; + } + +#ifdef Q_OS_SYMBIAN + // see in qhttpnetworkconnection.cpp + // hardcoded defaultChannelCount = 3 + QCOMPARE(pendingConnectionCount, 3); +#else + QCOMPARE(pendingConnectionCount, 6); +#endif +} + +void tst_QNetworkReply::httpReUsingConnectionSequential_data() +{ + QTest::addColumn<bool>("doDeleteLater"); + QTest::newRow("deleteLater") << true; + QTest::newRow("noDeleteLater") << false; +} + +void tst_QNetworkReply::httpReUsingConnectionSequential() +{ + QFETCH(bool, doDeleteLater); + + QByteArray response("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + MiniHttpServer server(response); + server.multiple = true; + server.doClose = false; + + QUrl url; + url.setScheme("http"); + url.setPort(server.serverPort()); + url.setHost("127.0.0.1"); + // first request + QNetworkReply* reply1 = manager.get(QNetworkRequest(url)); + connect(reply1, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(!reply1->error()); + int reply1port = server.client->peerPort(); + + if (doDeleteLater) + reply1->deleteLater(); + + // finished received, send the next one + QNetworkReply*reply2 = manager.get(QNetworkRequest(url)); + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(!reply2->error()); + int reply2port = server.client->peerPort(); // should still be the same object + + QVERIFY(reply1port > 0); + QCOMPARE(server.totalConnections, 1); + QCOMPARE(reply2port, reply1port); + + if (!doDeleteLater) + reply1->deleteLater(); // only do it if it was not done earlier + reply2->deleteLater(); +} + +class HttpReUsingConnectionFromFinishedSlot : public QObject { + Q_OBJECT +public: + QNetworkReply* reply1; + QNetworkReply* reply2; + QUrl url; + QNetworkAccessManager manager; +public slots: + void finishedSlot() { + QVERIFY(!reply1->error()); + + QFETCH(bool, doDeleteLater); + if (doDeleteLater) { + reply1->deleteLater(); + reply1 = 0; + } + + // kick off 2nd request and exit the loop when it is done + reply2 = manager.get(QNetworkRequest(url)); + reply2->setParent(this); + connect(reply2, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + } +}; + +void tst_QNetworkReply::httpReUsingConnectionFromFinishedSlot_data() +{ + httpReUsingConnectionSequential_data(); +} + +void tst_QNetworkReply::httpReUsingConnectionFromFinishedSlot() +{ + QByteArray response("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + MiniHttpServer server(response); + server.multiple = true; + server.doClose = false; + + HttpReUsingConnectionFromFinishedSlot helper; + helper.reply1 = 0; + helper.reply2 = 0; + helper.url.setScheme("http"); + helper.url.setPort(server.serverPort()); + helper.url.setHost("127.0.0.1"); + + // first request + helper.reply1 = helper.manager.get(QNetworkRequest(helper.url)); + helper.reply1->setParent(&helper); + connect(helper.reply1, SIGNAL(finished()), &helper, SLOT(finishedSlot())); + QTestEventLoop::instance().enterLoop(4); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(helper.reply2); + QVERIFY(!helper.reply2->error()); + + QCOMPARE(server.totalConnections, 1); +} + +class HttpRecursiveCreationHelper : public QObject { + Q_OBJECT +public: + + HttpRecursiveCreationHelper(): + QObject(0), + requestsStartedCount_finished(0), + requestsStartedCount_readyRead(0), + requestsFinishedCount(0) + { + } + QNetworkAccessManager manager; + int requestsStartedCount_finished; + int requestsStartedCount_readyRead; + int requestsFinishedCount; +public slots: + void finishedSlot() { + requestsFinishedCount++; + + QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); + QVERIFY(!reply->error()); + QVERIFY(reply->bytesAvailable() == 27906); + + if (requestsFinishedCount == 60) { + QTestEventLoop::instance().exitLoop(); + return; + } + + if (requestsStartedCount_finished < 30) { + startOne(); + requestsStartedCount_finished++; + } + + reply->deleteLater(); + } + void readyReadSlot() { + QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); + QVERIFY(!reply->error()); + + if (requestsStartedCount_readyRead < 30 && reply->bytesAvailable() > 27906/2) { + startOne(); + requestsStartedCount_readyRead++; + } + } + void startOne() { + QUrl url = "http://" + QtNetworkSettings::serverName() + "/qtest/fluke.gif"; + QNetworkRequest request(url); + QNetworkReply *reply = manager.get(request); + reply->setParent(this); + connect(reply, SIGNAL(finished()), this, SLOT(finishedSlot())); + connect(reply, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); + } +}; + +void tst_QNetworkReply::httpRecursiveCreation() +{ + // this test checks if creation of new requests to the same host properly works + // from readyRead() and finished() signals + HttpRecursiveCreationHelper helper; + helper.startOne(); + QTestEventLoop::instance().enterLoop(30); + QVERIFY(!QTestEventLoop::instance().timeout()); +} + +#ifndef QT_NO_OPENSSL +void tst_QNetworkReply::ignoreSslErrorsList_data() +{ + QTest::addColumn<QString>("url"); + QTest::addColumn<QList<QSslError> >("expectedSslErrors"); + QTest::addColumn<QNetworkReply::NetworkError>("expectedNetworkError"); + + QList<QSslError> expectedSslErrors; + // apparently, because of some weird behaviour of SRCDIR, the file name below needs to start with a slash + QList<QSslCertificate> certs = QSslCertificate::fromPath(QLatin1String(SRCDIR "/certs/qt-test-server-cacert.pem")); + QSslError rightError(QSslError::SelfSignedCertificate, certs.at(0)); + QSslError wrongError(QSslError::SelfSignedCertificate); + + QTest::newRow("SSL-failure-empty-list") << "https://" + QtNetworkSettings::serverName() + "/index.html" << expectedSslErrors << QNetworkReply::SslHandshakeFailedError; + expectedSslErrors.append(wrongError); + QTest::newRow("SSL-failure-wrong-error") << "https://" + QtNetworkSettings::serverName() + "/index.html" << expectedSslErrors << QNetworkReply::SslHandshakeFailedError; + expectedSslErrors.append(rightError); + QTest::newRow("allErrorsInExpectedList1") << "https://" + QtNetworkSettings::serverName() + "/index.html" << expectedSslErrors << QNetworkReply::NoError; + expectedSslErrors.removeAll(wrongError); + QTest::newRow("allErrorsInExpectedList2") << "https://" + QtNetworkSettings::serverName() + "/index.html" << expectedSslErrors << QNetworkReply::NoError; + expectedSslErrors.removeAll(rightError); + QTest::newRow("SSL-failure-empty-list-again") << "https://" + QtNetworkSettings::serverName() + "/index.html" << expectedSslErrors << QNetworkReply::SslHandshakeFailedError; +} + +void tst_QNetworkReply::ignoreSslErrorsList() +{ + QFETCH(QString, url); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.get(request); + + QFETCH(QList<QSslError>, expectedSslErrors); + reply->ignoreSslErrors(expectedSslErrors); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QFETCH(QNetworkReply::NetworkError, expectedNetworkError); + QCOMPARE(reply->error(), expectedNetworkError); +} + +void tst_QNetworkReply::ignoreSslErrorsListWithSlot_data() +{ + ignoreSslErrorsList_data(); +} + +// this is not a test, just a slot called in the test below +void tst_QNetworkReply::ignoreSslErrorListSlot(QNetworkReply *reply, const QList<QSslError> &) +{ + reply->ignoreSslErrors(storedExpectedSslErrors); +} + +// do the same as in ignoreSslErrorsList, but ignore the errors in the slot +void tst_QNetworkReply::ignoreSslErrorsListWithSlot() +{ + QFETCH(QString, url); + QNetworkRequest request(url); + QNetworkReplyPtr reply = manager.get(request); + + QFETCH(QList<QSslError>, expectedSslErrors); + // store the errors to ignore them later in the slot connected below + storedExpectedSslErrors = expectedSslErrors; + connect(&manager, SIGNAL(sslErrors(QNetworkReply *, const QList<QSslError> &)), + this, SLOT(ignoreSslErrorListSlot(QNetworkReply *, const QList<QSslError> &))); + + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QFETCH(QNetworkReply::NetworkError, expectedNetworkError); + QCOMPARE(reply->error(), expectedNetworkError); +} + +void tst_QNetworkReply::sslConfiguration_data() +{ + QTest::addColumn<QSslConfiguration>("configuration"); + QTest::addColumn<bool>("works"); + + QTest::newRow("empty") << QSslConfiguration() << false; + QSslConfiguration conf = QSslConfiguration::defaultConfiguration(); + QTest::newRow("default") << conf << false; // does not contain test server cert + QList<QSslCertificate> testServerCert = QSslCertificate::fromPath(SRCDIR "/certs/qt-test-server-cacert.pem"); + conf.setCaCertificates(testServerCert); + QTest::newRow("set-root-cert") << conf << true; + conf.setProtocol(QSsl::SecureProtocols); + QTest::newRow("secure") << conf << true; +} + +void tst_QNetworkReply::sslConfiguration() +{ + QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/index.html")); + QFETCH(QSslConfiguration, configuration); + request.setSslConfiguration(configuration); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QFETCH(bool, works); + QNetworkReply::NetworkError expectedError = works ? QNetworkReply::NoError : QNetworkReply::SslHandshakeFailedError; + QCOMPARE(reply->error(), expectedError); +} + +#endif // QT_NO_OPENSSL + +void tst_QNetworkReply::getAndThenDeleteObject_data() +{ + QTest::addColumn<bool>("replyFirst"); + + QTest::newRow("delete-reply-first") << true; + QTest::newRow("delete-qnam-first") << false; +} + +void tst_QNetworkReply::getAndThenDeleteObject() +{ + // yes, this will leak if the testcase fails. I don't care. It must not fail then :P + QNetworkAccessManager *manager = new QNetworkAccessManager(); + QNetworkRequest request("http://" + QtNetworkSettings::serverName() + "/qtest/bigfile"); + QNetworkReply *reply = manager->get(request); + reply->setReadBufferSize(1); + reply->setParent((QObject*)0); // must be 0 because else it is the manager + + QTime stopWatch; + stopWatch.start(); + forever { + QCoreApplication::instance()->processEvents(); + if (reply->bytesAvailable()) + break; + if (stopWatch.elapsed() >= 30000) + break; + } + + QVERIFY(reply->bytesAvailable()); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QVERIFY(!reply->isFinished()); // must not be finished + + QFETCH(bool, replyFirst); + + if (replyFirst) { + delete reply; + delete manager; + } else { + delete manager; + delete reply; + } +} + +// see https://bugs.webkit.org/show_bug.cgi?id=38935 +void tst_QNetworkReply::symbianOpenCDataUrlCrash() +{ + QString requestUrl("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAAB3RJTUUH2AUSEgolrgBvVQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAAHlSURBVHja5VbNShxBEK6ZaXtnHTebQPA1gngNmfaeq+QNPIlIXkC9iQdJxJNvEHLN3VkxhxxE8gTmEhAVddXZ6Z3f9Ndriz89/sHmkBQUVVT1fB9d9c3uOERUKTunIdn3HzstxGpYBDS4wZk7TAJj/wlJ90J+jnuygqs8svSj+/rGHBos3rE18XBvfU3no7NzlJfUaY/5whAwl8Lr/WDUv4ODxTMb+P5xLExe5LmO559WqTX/MQR4WZYEAtSePS4pE0qSnuhnRUcBU5Gm2k9XljU4Z26I3NRxBrd80rj2fh+KNE0FY4xevRgTjREvPFpasAK8Xli6MUbbuKw3afAGgSBXozo5u4hkmncAlkl5wx8iMGbdyQjnCFEiEwGiosj1UQA/x2rVddiVoi+l4IxE0PTDnx+mrQBvvnx9cFz3krhVvuhzFn579/aq/n5rW8fbtTqiWhIQZEo17YBvbkxOXNVndnYpTvod7AtiuN2re0+siwcB9oH8VxxrNwQQAhzyRs30n7wTI2HIN2g2QtQwjjhJIQatOq7E8bIVCLwzpl83Lvtvl+NohWWlE8UZTWEMAGCcR77fHKhPnZF5tYie6dfdxCphACmLPM+j8bYfmTryg64kV9Vh3mV8jP0b/4wO/YUPiT/8i0MLf55lSQAAAABJRU5ErkJggg=="); + QUrl url = QUrl::fromEncoded(requestUrl.toLatin1()); + QNetworkRequest req(url); + QNetworkReplyPtr reply; + + RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, req, reply)); + + QCOMPARE(reply->url(), url); + QCOMPARE(reply->error(), QNetworkReply::NoError); + + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(598)); +} + +void tst_QNetworkReply::getFromHttpIntoBuffer_data() +{ + QTest::addColumn<QUrl>("url"); + + QTest::newRow("rfc-internal") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"); +} + +// Please note that the whole "zero copy" download buffer API is private right now. Do not use it. +void tst_QNetworkReply::getFromHttpIntoBuffer() +{ + QFETCH(QUrl, url); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 1024*128); // 128 kB + + QNetworkAccessManager manager; + QNetworkReply *reply = manager.get(request); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(reply->isFinished()); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QCOMPARE(reference.bytesAvailable(), reply->bytesAvailable()); + QCOMPARE(reference.size(), reply->size()); + + // Compare the memory buffer + QVariant downloadBufferAttribute = reply->attribute(QNetworkRequest::DownloadBufferAttribute); + QVERIFY(downloadBufferAttribute.isValid()); + QSharedPointer<char> sharedPointer = downloadBufferAttribute.value<QSharedPointer<char> >(); + bool memoryComparison = + (0 == memcmp(static_cast<void*>(reference.readAll().data()), + sharedPointer.data(), reference.size())); + QVERIFY(memoryComparison); + + // Make sure the normal reading works + reference.seek(0); + QCOMPARE(reply->read(42), reference.read(42)); + QCOMPARE(reply->getChar(0), reference.getChar(0)); + QCOMPARE(reply->peek(23), reference.peek(23)); + QCOMPARE(reply->readLine(), reference.readLine()); + QCOMPARE(reference.bytesAvailable(), reply->bytesAvailable()); + QCOMPARE(reply->readAll(), reference.readAll()); + QVERIFY(reply->atEnd()); +} + +// FIXME we really need to consolidate all those server implementations +class GetFromHttpIntoBuffer2Server : QObject { + Q_OBJECT + qint64 dataSize; + qint64 dataSent; + QTcpServer server; + QTcpSocket *client; + bool serverSendsContentLength; + bool chunkedEncoding; + +public: + GetFromHttpIntoBuffer2Server (qint64 ds, bool sscl, bool ce) : dataSize(ds), dataSent(0), + client(0), serverSendsContentLength(sscl), chunkedEncoding(ce) { + server.listen(); + connect(&server, SIGNAL(newConnection()), this, SLOT(newConnectionSlot())); + } + + int serverPort() { + return server.serverPort(); + } + +public slots: + + void newConnectionSlot() { + client = server.nextPendingConnection(); + client->setParent(this); + connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); + connect(client, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWrittenSlot(qint64))); + } + + void readyReadSlot() { + client->readAll(); + client->write("HTTP/1.0 200 OK\n"); + if (serverSendsContentLength) + client->write(QString("Content-Length: " + QString::number(dataSize) + "\n").toAscii()); + if (chunkedEncoding) + client->write(QString("Transfer-Encoding: chunked\n").toAscii()); + client->write("Connection: close\n\n"); + } + + void bytesWrittenSlot(qint64 amount) { + Q_UNUSED(amount); + if (dataSent == dataSize && client) { + // close eventually + + // chunked encoding: we have to send a last "empty" chunk + if (chunkedEncoding) + client->write(QString("0\r\n\r\n").toAscii()); + + client->disconnectFromHost(); + server.close(); + client = 0; + return; + } + + // send data + if (client && client->bytesToWrite() < 100*1024 && dataSent < dataSize) { + qint64 amount = qMin(qint64(16*1024), dataSize - dataSent); + QByteArray data(amount, '@'); + + if (chunkedEncoding) { + client->write(QString(QString("%1").arg(amount,0,16).toUpper() + "\r\n").toAscii()); + client->write(data.constData(), amount); + client->write(QString("\r\n").toAscii()); + } else { + client->write(data.constData(), amount); + } + + dataSent += amount; + } + } +}; + +class GetFromHttpIntoBuffer2Client : QObject { + Q_OBJECT +private: + bool useDownloadBuffer; + QNetworkReply *reply; + qint64 uploadSize; + QList<qint64> bytesAvailableList; +public: + GetFromHttpIntoBuffer2Client (QNetworkReply *reply, bool useDownloadBuffer, qint64 uploadSize) + : useDownloadBuffer(useDownloadBuffer), reply(reply), uploadSize(uploadSize) + { + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChangedSlot())); + connect(reply, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); + connect(reply, SIGNAL(finished()), this, SLOT(finishedSlot())); + } + + public slots: + void metaDataChangedSlot() { + if (useDownloadBuffer) { + QSharedPointer<char> sharedPointer = qvariant_cast<QSharedPointer<char> >(reply->attribute(QNetworkRequest::DownloadBufferAttribute)); + QVERIFY(!sharedPointer.isNull()); // It will be 0 if it failed + } + + // metaDataChanged needs to come before everything else + QVERIFY(bytesAvailableList.isEmpty()); + } + + void readyReadSlot() { + QVERIFY(!reply->isFinished()); + + qint64 bytesAvailable = reply->bytesAvailable(); + + // bytesAvailable must never be 0 + QVERIFY(bytesAvailable != 0); + + if (bytesAvailableList.length() < 5) { + // We assume that the first few times the bytes available must be less than the complete size, e.g. + // the bytesAvailable() function works correctly in case of a downloadBuffer. + QVERIFY(bytesAvailable < uploadSize); + } + if (!bytesAvailableList.isEmpty()) { + // Also check that the same bytesAvailable is not coming twice in a row + QVERIFY(bytesAvailableList.last() != bytesAvailable); + } + + bytesAvailableList.append(bytesAvailable); + // Add bytesAvailable to a list an parse + } + + void finishedSlot() { + // We should have already received all readyRead + QVERIFY(!bytesAvailableList.isEmpty()); + QVERIFY(bytesAvailableList.last() == uploadSize); + } +}; + +void tst_QNetworkReply::getFromHttpIntoBuffer2_data() +{ + QTest::addColumn<bool>("useDownloadBuffer"); + + QTest::newRow("use-download-buffer") << true; + QTest::newRow("do-not-use-download-buffer") << false; +} + +// This test checks mostly that signal emissions are in correct order +// Please note that the whole "zero copy" download buffer API is private right now. Do not use it. +void tst_QNetworkReply::getFromHttpIntoBuffer2() +{ + QFETCH(bool, useDownloadBuffer); + + // On my Linux Desktop the results are already visible with 128 kB, however we use this to have good results. +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_WINCE_WM) + // Show some mercy to non-desktop platform/s + enum {UploadSize = 4*1024*1024}; // 4 MB +#else + enum {UploadSize = 32*1024*1024}; // 32 MB +#endif + + GetFromHttpIntoBuffer2Server server(UploadSize, true, false); + + QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1")); + if (useDownloadBuffer) + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 1024*1024*128); // 128 MB is max allowed + + QNetworkAccessManager manager; + QNetworkReplyPtr reply = manager.get(request); + + GetFromHttpIntoBuffer2Client client(reply, useDownloadBuffer, UploadSize); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); + QTestEventLoop::instance().enterLoop(40); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); +} + + +// Is handled somewhere else too, introduced this special test to have it more accessible +void tst_QNetworkReply::ioGetFromHttpWithoutContentLength() +{ + QByteArray dataToSend("HTTP/1.0 200 OK\r\n\r\nHALLO! 123!"); + MiniHttpServer server(dataToSend); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QVERIFY(reply->isFinished()); + QVERIFY(reply->error() == QNetworkReply::NoError); +} + +// Is handled somewhere else too, introduced this special test to have it more accessible +void tst_QNetworkReply::ioGetFromHttpBrokenChunkedEncoding() +{ + // This is wrong chunked encoding because of the X. What actually has to follow is \r\n + // and then the declaration of the final 0 chunk + QByteArray dataToSend("HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nABCX"); + MiniHttpServer server(dataToSend); + server.doClose = false; // FIXME + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + + QEXPECT_FAIL(0, "We should close the socket and not just do nothing", Continue); + QVERIFY(!QTestEventLoop::instance().timeout()); + QEXPECT_FAIL(0, "We should close the socket and not just do nothing", Continue); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::NoError); +} + +// TODO: +// Prepare a gzip that has one chunk that expands to the size mentioned in the bugreport. +// Then have a custom HTTP server that waits after this chunk so the returning gets +// triggered. +void tst_QNetworkReply::qtbug12908compressedHttpReply() +{ + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: 63\r\n\r\n"); + + // dd if=/dev/zero of=qtbug-12908 bs=16384 count=1 && gzip qtbug-12908 && base64 -w 0 qtbug-12908.gz + QString encodedFile("H4sICDdDaUwAA3F0YnVnLTEyOTA4AO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"); + QByteArray decodedFile = QByteArray::fromBase64(encodedFile.toAscii()); + QCOMPARE(decodedFile.size(), 63); + + MiniHttpServer server(header.toAscii() + decodedFile); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->size(), qint64(16384)); + QCOMPARE(reply->readAll(), QByteArray(16384, '\0')); +} + +void tst_QNetworkReply::compressedHttpReplyBrokenGzip() +{ + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: 63\r\n\r\n"); + + // dd if=/dev/zero of=qtbug-12908 bs=16384 count=1 && gzip qtbug-12908 && base64 -w 0 qtbug-12908.gz + // Then change "BMQ" to "BMX" + QString encodedFile("H4sICDdDaUwAA3F0YnVnLTEyOTA4AO3BMXEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"); + QByteArray decodedFile = QByteArray::fromBase64(encodedFile.toAscii()); + QCOMPARE(decodedFile.size(), 63); + + MiniHttpServer server(header.toAscii() + decodedFile); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->error(), QNetworkReply::ProtocolFailure); +} + +// TODO add similar test for FTP +void tst_QNetworkReply::getFromUnreachableIp() +{ + QNetworkAccessManager manager; + + QNetworkRequest request(QUrl("http://255.255.255.255/42/23/narf/narf/narf")); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(reply->error() != QNetworkReply::NoError); +} + +void tst_QNetworkReply::qtbug4121unknownAuthentication() +{ + MiniHttpServer server(QByteArray("HTTP/1.1 401 bla\r\nWWW-Authenticate: crap\r\nContent-Length: 0\r\n\r\n")); + server.doClose = false; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkAccessManager manager; + QNetworkReplyPtr reply = manager.get(request); + + qRegisterMetaType<QNetworkReply*>("QNetworkReply*"); + qRegisterMetaType<QAuthenticator*>("QAuthenticator*"); + QSignalSpy authSpy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QSignalSpy finishedSpy(&manager, SIGNAL(finished(QNetworkReply*))); + qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); + QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(authSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(errorSpy.count(), 1); + + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); +} + +class QtBug13431Helper : public QObject { + Q_OBJECT +public: + QNetworkReply* m_reply; + QTimer m_dlTimer; +public slots: + void replyFinished(QNetworkReply*) { + QTestEventLoop::instance().exitLoop(); + } + + void onReadAndReschedule() { + const qint64 bytesReceived = m_reply->bytesAvailable(); + if (bytesReceived) { + QByteArray data = m_reply->read(bytesReceived); + // reschedule read + const int millisecDelay = static_cast<int>(bytesReceived * 1000 / m_reply->readBufferSize()); + m_dlTimer.start(millisecDelay); + } + else { + // reschedule read + m_dlTimer.start(200); + } + } +}; + +void tst_QNetworkReply::qtbug13431replyThrottling() +{ + QtBug13431Helper helper; + + QNetworkAccessManager nam; + connect(&nam, SIGNAL(finished(QNetworkReply*)), &helper, SLOT(replyFinished(QNetworkReply*))); + + // Download a bigger file + QNetworkRequest netRequest(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/bigfile")); + helper.m_reply = nam.get(netRequest); + // Set the throttle + helper.m_reply->setReadBufferSize(36000); + + // Schedule a timer that tries to read + + connect(&helper.m_dlTimer, SIGNAL(timeout()), &helper, SLOT(onReadAndReschedule())); + helper.m_dlTimer.setSingleShot(true); + helper.m_dlTimer.start(0); + + QTestEventLoop::instance().enterLoop(30); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(helper.m_reply->isFinished()); + QCOMPARE(helper.m_reply->error(), QNetworkReply::NoError); +} + +void tst_QNetworkReply::httpWithNoCredentialUsage() +{ + QNetworkRequest request(QUrl("http://httptest:httptest@" + QtNetworkSettings::serverName() + "/qtest/protected/cgi-bin/md5sum.cgi")); + // Do not use credentials + request.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + QNetworkAccessManager manager; + QNetworkReplyPtr reply = manager.get(request); + + qRegisterMetaType<QNetworkReply*>("QNetworkReply*"); + qRegisterMetaType<QAuthenticator*>("QAuthenticator*"); + QSignalSpy authSpy(&manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*))); + QSignalSpy finishedSpy(&manager, SIGNAL(finished(QNetworkReply*))); + qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); + QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError))); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + // We check if authenticationRequired was emitted, however we do not anything in it so it should be 401 + QCOMPARE(authSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(errorSpy.count(), 1); + + QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError); +} + +void tst_QNetworkReply::qtbug15311doubleContentLength() +{ + QByteArray response("HTTP/1.0 200 OK\r\nContent-Length: 3\r\nServer: bogus\r\nContent-Length: 3\r\n\r\nABC"); + MiniHttpServer server(response); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->size(), qint64(3)); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(3)); + QCOMPARE(reply->rawHeader("Content-length"), QByteArray("3, 3")); + QCOMPARE(reply->readAll(), QByteArray("ABC")); +} + +void tst_QNetworkReply::qtbug18232gzipContentLengthZero() +{ + QByteArray response("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: 0\r\n\r\n"); + MiniHttpServer server(response); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QCOMPARE(reply->size(), qint64(0)); + QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(0)); + QCOMPARE(reply->readAll(), QByteArray()); +} + +void tst_QNetworkReply::synchronousRequest_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QString>("expected"); + QTest::addColumn<bool>("checkContentLength"); + QTest::addColumn<QString>("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::fromLocalFile(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<QSslCertificate> certs = QSslCertificate::fromPath(SRCDIR "/certs/qt-test-server-cacert.pem"); + sslConf.setCaCertificates(certs); + request.setSslConfiguration(sslConf); + } +#endif + + request.setAttribute( + QNetworkRequest::SynchronousRequestAttribute, + true); + + QNetworkReplyPtr reply; + QSignalSpy finishedSpy(&manager, SIGNAL(finished(QNetworkReply*))); + QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + 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(); +} + +#ifndef QT_NO_OPENSSL +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( + QNetworkRequest::SynchronousRequestAttribute, + true); + QNetworkReplyPtr reply; + QSignalSpy sslErrorsSpy(&manager, SIGNAL(sslErrors(QNetworkReply *, const QList<QSslError> &))); + runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply, 0); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 0); +} +#endif + +void tst_QNetworkReply::httpAbort() +{ + // FIXME: Implement a test that aborts a big HTTP reply + // a) after the first readyRead() + // b) immediatly after the get() + // c) after the finished() + // The goal is no crash and no irrelevant signals after the abort + + // FIXME Also implement one where we do a big upload and then abort(). + // It must not crash either. +} + +void tst_QNetworkReply::dontInsertPartialContentIntoTheCache() +{ + QByteArray reply206 = + "HTTP/1.0 206\r\n" + "Connection: keep-alive\r\n" + "Content-Type: text/plain\r\n" + "Cache-control: no-cache\r\n" + "Content-Range: bytes 2-6/8\r\n" + "Content-length: 4\r\n" + "\r\n" + "load"; + + MiniHttpServer server(reply206); + server.doClose = false; + + MySpyMemoryCache *memoryCache = new MySpyMemoryCache(&manager); + manager.setCache(memoryCache); + + QUrl url = "http://localhost:" + QString::number(server.serverPort()); + QNetworkRequest request(url); + request.setRawHeader("Range", "bytes=2-6"); + + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(server.totalConnections > 0); + QCOMPARE(reply->readAll().constData(), "load"); + QCOMPARE(memoryCache->m_insertedUrls.count(), 0); +} + +// NOTE: This test must be last testcase in tst_qnetworkreply! +void tst_QNetworkReply::parentingRepliesToTheApp() +{ + QNetworkRequest request (QUrl("http://" + QtNetworkSettings::serverName())); + manager.get(request)->setParent(this); // parent to this object + manager.get(request)->setParent(qApp); // parent to the app +} + +QTEST_MAIN(tst_QNetworkReply) + +#include "tst_qnetworkreply.moc" |