| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2014 BlackBerry Limited. All rights reserved. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | |
| 30 | #include <QtTest/QtTest> |
| 31 | #include <QtNetwork/QNetworkAccessManager> |
| 32 | #include <QtNetwork/QNetworkReply> |
| 33 | #include <QtNetwork/QHttpPart> |
| 34 | #include <QtNetwork/QHttpMultiPart> |
| 35 | #include <QtNetwork/QNetworkProxy> |
| 36 | #include <QtNetwork/QAuthenticator> |
| 37 | #if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_OPENSSL) |
| 38 | #include <QtNetwork/private/qsslsocket_openssl_p.h> |
| 39 | #endif // QT_BUILD_INTERNAL && !QT_NO_OPENSSL |
| 40 | |
| 41 | #include "../../../network-settings.h" |
| 42 | |
| 43 | Q_DECLARE_METATYPE(QAuthenticator*) |
| 44 | |
| 45 | class tst_Spdy: public QObject |
| 46 | { |
| 47 | Q_OBJECT |
| 48 | |
| 49 | public: |
| 50 | tst_Spdy(); |
| 51 | ~tst_Spdy(); |
| 52 | |
| 53 | private Q_SLOTS: |
| 54 | void initTestCase(); |
| 55 | void settingsAndNegotiation_data(); |
| 56 | void settingsAndNegotiation(); |
| 57 | #ifndef QT_NO_NETWORKPROXY |
| 58 | void download_data(); |
| 59 | void download(); |
| 60 | #endif // !QT_NO_NETWORKPROXY |
| 61 | void headerFields(); |
| 62 | #ifndef QT_NO_NETWORKPROXY |
| 63 | void upload_data(); |
| 64 | void upload(); |
| 65 | void errors_data(); |
| 66 | void errors(); |
| 67 | #endif // !QT_NO_NETWORKPROXY |
| 68 | void multipleRequests_data(); |
| 69 | void multipleRequests(); |
| 70 | |
| 71 | private: |
| 72 | QNetworkAccessManager m_manager; |
| 73 | int m_multipleRequestsCount; |
| 74 | int m_multipleRepliesFinishedCount; |
| 75 | const QString m_rfc3252FilePath; |
| 76 | |
| 77 | protected Q_SLOTS: |
| 78 | void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *authenticator); |
| 79 | void multipleRequestsFinishedSlot(); |
| 80 | }; |
| 81 | |
| 82 | tst_Spdy::tst_Spdy() |
| 83 | : m_rfc3252FilePath(QFINDTESTDATA("../qnetworkreply/rfc3252.txt" )) |
| 84 | { |
| 85 | #if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) |
| 86 | qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy |
| 87 | qRegisterMetaType<QAuthenticator *>(); |
| 88 | |
| 89 | connect(sender: &m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), |
| 90 | receiver: this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); |
| 91 | #else |
| 92 | QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old" ); |
| 93 | #endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ... |
| 94 | } |
| 95 | |
| 96 | tst_Spdy::~tst_Spdy() |
| 97 | { |
| 98 | } |
| 99 | |
| 100 | void tst_Spdy::initTestCase() |
| 101 | { |
| 102 | QVERIFY(!m_rfc3252FilePath.isEmpty()); |
| 103 | if (!QtNetworkSettings::verifyTestNetworkSettings()) |
| 104 | QSKIP("No network test server available" ); |
| 105 | } |
| 106 | |
| 107 | void tst_Spdy::settingsAndNegotiation_data() |
| 108 | { |
| 109 | QTest::addColumn<QUrl>(name: "url" ); |
| 110 | QTest::addColumn<bool>(name: "setAttribute" ); |
| 111 | QTest::addColumn<bool>(name: "enabled" ); |
| 112 | QTest::addColumn<QByteArray>(name: "expectedProtocol" ); |
| 113 | QTest::addColumn<QByteArray>(name: "expectedContent" ); |
| 114 | |
| 115 | QTest::newRow(dataTag: "default-settings" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 116 | + "/qtest/cgi-bin/echo.cgi?1" ) |
| 117 | << false << false << QByteArray() |
| 118 | << QByteArray("1" ); |
| 119 | |
| 120 | QTest::newRow(dataTag: "http-url" ) << QUrl("http://" + QtNetworkSettings::serverName() |
| 121 | + "/qtest/cgi-bin/echo.cgi?1" ) |
| 122 | << true << true << QByteArray() |
| 123 | << QByteArray("1" ); |
| 124 | |
| 125 | QTest::newRow(dataTag: "spdy-disabled" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 126 | + "/qtest/cgi-bin/echo.cgi?1" ) |
| 127 | << true << false << QByteArray() |
| 128 | << QByteArray("1" ); |
| 129 | |
| 130 | #ifndef QT_NO_OPENSSL |
| 131 | QTest::newRow(dataTag: "spdy-enabled" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 132 | + "/qtest/cgi-bin/echo.cgi?1" ) |
| 133 | << true << true << QByteArray(QSslConfiguration::NextProtocolSpdy3_0) |
| 134 | << QByteArray("1" ); |
| 135 | #endif // QT_NO_OPENSSL |
| 136 | } |
| 137 | |
| 138 | void tst_Spdy::settingsAndNegotiation() |
| 139 | { |
| 140 | QFETCH(QUrl, url); |
| 141 | QFETCH(bool, setAttribute); |
| 142 | QFETCH(bool, enabled); |
| 143 | |
| 144 | QNetworkRequest request(url); |
| 145 | |
| 146 | if (setAttribute) { |
| 147 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: QVariant(enabled)); |
| 148 | } |
| 149 | |
| 150 | QNetworkReply *reply = m_manager.get(request); |
| 151 | reply->ignoreSslErrors(); |
| 152 | QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged())); |
| 153 | QSignalSpy readyReadSpy(reply, SIGNAL(readyRead())); |
| 154 | QSignalSpy finishedSpy(reply, SIGNAL(finished())); |
| 155 | |
| 156 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 157 | QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); |
| 158 | |
| 159 | QTestEventLoop::instance().enterLoop(secs: 15); |
| 160 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 161 | |
| 162 | QFETCH(QByteArray, expectedProtocol); |
| 163 | |
| 164 | #ifndef QT_NO_OPENSSL |
| 165 | bool expectedSpdyUsed = (expectedProtocol == QSslConfiguration::NextProtocolSpdy3_0); |
| 166 | QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), expectedSpdyUsed); |
| 167 | #endif // QT_NO_OPENSSL |
| 168 | |
| 169 | QCOMPARE(metaDataChangedSpy.count(), 1); |
| 170 | QCOMPARE(finishedSpy.count(), 1); |
| 171 | |
| 172 | int statusCode = reply->attribute(code: QNetworkRequest::HttpStatusCodeAttribute).toInt(); |
| 173 | QCOMPARE(statusCode, 200); |
| 174 | |
| 175 | QByteArray content = reply->readAll(); |
| 176 | |
| 177 | QFETCH(QByteArray, expectedContent); |
| 178 | QCOMPARE(expectedContent, content); |
| 179 | |
| 180 | #ifndef QT_NO_OPENSSL |
| 181 | QSslConfiguration::NextProtocolNegotiationStatus expectedStatus = |
| 182 | (expectedProtocol.isEmpty()) |
| 183 | ? QSslConfiguration::NextProtocolNegotiationNone |
| 184 | : QSslConfiguration::NextProtocolNegotiationNegotiated; |
| 185 | QCOMPARE(reply->sslConfiguration().nextProtocolNegotiationStatus(), |
| 186 | expectedStatus); |
| 187 | |
| 188 | QCOMPARE(reply->sslConfiguration().nextNegotiatedProtocol(), expectedProtocol); |
| 189 | #endif // QT_NO_OPENSSL |
| 190 | } |
| 191 | |
| 192 | void tst_Spdy::proxyAuthenticationRequired(const QNetworkProxy &/*proxy*/, |
| 193 | QAuthenticator *authenticator) |
| 194 | { |
| 195 | authenticator->setUser("qsockstest" ); |
| 196 | authenticator->setPassword("password" ); |
| 197 | } |
| 198 | |
| 199 | #ifndef QT_NO_NETWORKPROXY |
| 200 | void tst_Spdy::download_data() |
| 201 | { |
| 202 | QTest::addColumn<QUrl>(name: "url" ); |
| 203 | QTest::addColumn<QString>(name: "fileName" ); |
| 204 | QTest::addColumn<QNetworkProxy>(name: "proxy" ); |
| 205 | |
| 206 | QTest::newRow(dataTag: "mediumfile" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 207 | + "/qtest/rfc3252.txt" ) |
| 208 | << m_rfc3252FilePath |
| 209 | << QNetworkProxy(); |
| 210 | |
| 211 | QHostInfo hostInfo = QHostInfo::fromName(name: QtNetworkSettings::serverName()); |
| 212 | QString proxyserver = hostInfo.addresses().first().toString(); |
| 213 | |
| 214 | QTest::newRow(dataTag: "mediumfile-http-proxy" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 215 | + "/qtest/rfc3252.txt" ) |
| 216 | << m_rfc3252FilePath |
| 217 | << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128); |
| 218 | |
| 219 | QTest::newRow(dataTag: "mediumfile-http-proxy-auth" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 220 | + "/qtest/rfc3252.txt" ) |
| 221 | << m_rfc3252FilePath |
| 222 | << QNetworkProxy(QNetworkProxy::HttpProxy, |
| 223 | proxyserver, 3129); |
| 224 | |
| 225 | QTest::newRow(dataTag: "mediumfile-socks-proxy" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 226 | + "/qtest/rfc3252.txt" ) |
| 227 | << m_rfc3252FilePath |
| 228 | << QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080); |
| 229 | |
| 230 | QTest::newRow(dataTag: "mediumfile-socks-proxy-auth" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 231 | + "/qtest/rfc3252.txt" ) |
| 232 | << m_rfc3252FilePath |
| 233 | << QNetworkProxy(QNetworkProxy::Socks5Proxy, |
| 234 | proxyserver, 1081); |
| 235 | |
| 236 | QTest::newRow(dataTag: "bigfile" ) << QUrl("https://" + QtNetworkSettings::serverName() |
| 237 | + "/qtest/bigfile" ) |
| 238 | << QFINDTESTDATA("../qnetworkreply/bigfile" ) |
| 239 | << QNetworkProxy(); |
| 240 | } |
| 241 | |
| 242 | void tst_Spdy::download() |
| 243 | { |
| 244 | QFETCH(QUrl, url); |
| 245 | QFETCH(QString, fileName); |
| 246 | QFETCH(QNetworkProxy, proxy); |
| 247 | |
| 248 | QNetworkRequest request(url); |
| 249 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: true); |
| 250 | |
| 251 | if (proxy.type() != QNetworkProxy::DefaultProxy) { |
| 252 | m_manager.setProxy(proxy); |
| 253 | } |
| 254 | QNetworkReply *reply = m_manager.get(request); |
| 255 | reply->ignoreSslErrors(); |
| 256 | QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged())); |
| 257 | QSignalSpy downloadProgressSpy(reply, SIGNAL(downloadProgress(qint64, qint64))); |
| 258 | QSignalSpy readyReadSpy(reply, SIGNAL(readyRead())); |
| 259 | QSignalSpy finishedSpy(reply, SIGNAL(finished())); |
| 260 | |
| 261 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 262 | QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); |
| 263 | QSignalSpy proxyAuthRequiredSpy(&m_manager, SIGNAL( |
| 264 | proxyAuthenticationRequired(const QNetworkProxy &, |
| 265 | QAuthenticator *))); |
| 266 | |
| 267 | QTestEventLoop::instance().enterLoop(secs: 15); |
| 268 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 269 | |
| 270 | QCOMPARE(finishedManagerSpy.count(), 1); |
| 271 | QCOMPARE(metaDataChangedSpy.count(), 1); |
| 272 | QCOMPARE(finishedSpy.count(), 1); |
| 273 | QVERIFY(downloadProgressSpy.count() > 0); |
| 274 | QVERIFY(readyReadSpy.count() > 0); |
| 275 | |
| 276 | QVERIFY(proxyAuthRequiredSpy.count() <= 1); |
| 277 | |
| 278 | QCOMPARE(reply->error(), QNetworkReply::NoError); |
| 279 | QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true); |
| 280 | QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true); |
| 281 | QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); |
| 282 | |
| 283 | QFile file(fileName); |
| 284 | QVERIFY(file.open(QIODevice::ReadOnly)); |
| 285 | |
| 286 | qint64 contentLength = reply->header(header: QNetworkRequest::ContentLengthHeader).toLongLong(); |
| 287 | qint64 expectedContentLength = file.bytesAvailable(); |
| 288 | QCOMPARE(contentLength, expectedContentLength); |
| 289 | |
| 290 | QByteArray expectedContent = file.readAll(); |
| 291 | QByteArray content = reply->readAll(); |
| 292 | QCOMPARE(content, expectedContent); |
| 293 | |
| 294 | reply->deleteLater(); |
| 295 | m_manager.setProxy(QNetworkProxy()); // reset |
| 296 | } |
| 297 | #endif // !QT_NO_NETWORKPROXY |
| 298 | |
| 299 | void tst_Spdy::() |
| 300 | { |
| 301 | QUrl url(QUrl("https://" + QtNetworkSettings::serverName())); |
| 302 | QNetworkRequest request(url); |
| 303 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: true); |
| 304 | |
| 305 | QNetworkReply *reply = m_manager.get(request); |
| 306 | reply->ignoreSslErrors(); |
| 307 | |
| 308 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 309 | |
| 310 | QTestEventLoop::instance().enterLoop(secs: 15); |
| 311 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 312 | |
| 313 | QCOMPARE(reply->rawHeader("Content-Type" ), QByteArray("text/html" )); |
| 314 | QVERIFY(reply->rawHeader("Content-Length" ).toInt() > 0); |
| 315 | QVERIFY(reply->rawHeader("server" ).contains("Apache" )); |
| 316 | |
| 317 | QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), QByteArray("text/html" )); |
| 318 | QVERIFY(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong() > 0); |
| 319 | QVERIFY(reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().isValid()); |
| 320 | QVERIFY(reply->header(QNetworkRequest::ServerHeader).toByteArray().contains("Apache" )); |
| 321 | } |
| 322 | |
| 323 | static inline QByteArray md5sum(const QByteArray &data) |
| 324 | { |
| 325 | return QCryptographicHash::hash(data, method: QCryptographicHash::Md5).toHex().append(c: '\n'); |
| 326 | } |
| 327 | |
| 328 | #ifndef QT_NO_NETWORKPROXY |
| 329 | void tst_Spdy::upload_data() |
| 330 | { |
| 331 | QTest::addColumn<QUrl>(name: "url" ); |
| 332 | QTest::addColumn<QByteArray>(name: "data" ); |
| 333 | QTest::addColumn<QByteArray>(name: "uploadMethod" ); |
| 334 | QTest::addColumn<QObject *>(name: "uploadObject" ); |
| 335 | QTest::addColumn<QByteArray>(name: "md5sum" ); |
| 336 | QTest::addColumn<QNetworkProxy>(name: "proxy" ); |
| 337 | |
| 338 | |
| 339 | // 1. test uploading of byte arrays |
| 340 | |
| 341 | QUrl md5Url("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi" ); |
| 342 | |
| 343 | QByteArray data; |
| 344 | data = "" ; |
| 345 | QObject *dummyObject = 0; |
| 346 | QTest::newRow(dataTag: "empty" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 347 | << md5sum(data) << QNetworkProxy(); |
| 348 | |
| 349 | data = "This is a normal message." ; |
| 350 | QTest::newRow(dataTag: "generic" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 351 | << md5sum(data) << QNetworkProxy(); |
| 352 | |
| 353 | data = "This is a message to show that Qt rocks!\r\n\n" ; |
| 354 | QTest::newRow(dataTag: "small" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 355 | << md5sum(data) << QNetworkProxy(); |
| 356 | |
| 357 | data = QByteArray("abcd\0\1\2\abcd" ,12); |
| 358 | QTest::newRow(dataTag: "with-nul" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 359 | << md5sum(data) << QNetworkProxy(); |
| 360 | |
| 361 | data = QByteArray(4097, '\4'); |
| 362 | QTest::newRow(dataTag: "4k+1" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 363 | << md5sum(data)<< QNetworkProxy(); |
| 364 | |
| 365 | QHostInfo hostInfo = QHostInfo::fromName(name: QtNetworkSettings::serverName()); |
| 366 | QString proxyserver = hostInfo.addresses().first().toString(); |
| 367 | |
| 368 | QTest::newRow(dataTag: "4k+1-with-http-proxy" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 369 | << md5sum(data) |
| 370 | << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128); |
| 371 | |
| 372 | QTest::newRow(dataTag: "4k+1-with-http-proxy-auth" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 373 | << md5sum(data) |
| 374 | << QNetworkProxy(QNetworkProxy::HttpProxy, |
| 375 | proxyserver, 3129); |
| 376 | |
| 377 | QTest::newRow(dataTag: "4k+1-with-socks-proxy" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 378 | << md5sum(data) |
| 379 | << QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080); |
| 380 | |
| 381 | QTest::newRow(dataTag: "4k+1-with-socks-proxy-auth" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 382 | << md5sum(data) |
| 383 | << QNetworkProxy(QNetworkProxy::Socks5Proxy, |
| 384 | proxyserver, 1081); |
| 385 | |
| 386 | data = QByteArray(128*1024+1, '\177'); |
| 387 | QTest::newRow(dataTag: "128k+1" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 388 | << md5sum(data) << QNetworkProxy(); |
| 389 | |
| 390 | data = QByteArray(128*1024+1, '\177'); |
| 391 | QTest::newRow(dataTag: "128k+1-put" ) << md5Url << data << QByteArray("PUT" ) << dummyObject |
| 392 | << md5sum(data) << QNetworkProxy(); |
| 393 | |
| 394 | data = QByteArray(2*1024*1024+1, '\177'); |
| 395 | QTest::newRow(dataTag: "2MB+1" ) << md5Url << data << QByteArray("POST" ) << dummyObject |
| 396 | << md5sum(data) << QNetworkProxy(); |
| 397 | |
| 398 | |
| 399 | // 2. test uploading of files |
| 400 | |
| 401 | QFile *file = new QFile(m_rfc3252FilePath); |
| 402 | file->open(flags: QIODevice::ReadOnly); |
| 403 | QTest::newRow(dataTag: "file-26K" ) << md5Url << QByteArray() << QByteArray("POST" ) |
| 404 | << static_cast<QObject *>(file) |
| 405 | << QByteArray("b3e32ac459b99d3f59318f3ac31e4bee\n" ) << QNetworkProxy(); |
| 406 | |
| 407 | QFile *file2 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg" )); |
| 408 | file2->open(flags: QIODevice::ReadOnly); |
| 409 | QTest::newRow(dataTag: "file-1MB" ) << md5Url << QByteArray() << QByteArray("POST" ) |
| 410 | << static_cast<QObject *>(file2) |
| 411 | << QByteArray("87ef3bb319b004ba9e5e9c9fa713776e\n" ) << QNetworkProxy(); |
| 412 | |
| 413 | |
| 414 | // 3. test uploading of multipart |
| 415 | |
| 416 | QUrl multiPartUrl("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/multipart.cgi" ); |
| 417 | |
| 418 | QHttpPart imagePart31; |
| 419 | imagePart31.setHeader(header: QNetworkRequest::ContentTypeHeader, value: QVariant("image/jpeg" )); |
| 420 | imagePart31.setHeader(header: QNetworkRequest::ContentDispositionHeader, value: QVariant("form-data; name=\"testImage1\"" )); |
| 421 | imagePart31.setRawHeader(headerName: "Content-Location" , headerValue: "http://my.test.location.tld" ); |
| 422 | imagePart31.setRawHeader(headerName: "Content-ID" , headerValue: "my@id.tld" ); |
| 423 | QFile *file31 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg" )); |
| 424 | file31->open(flags: QIODevice::ReadOnly); |
| 425 | imagePart31.setBodyDevice(file31); |
| 426 | QHttpMultiPart *imageMultiPart3 = new QHttpMultiPart(QHttpMultiPart::FormDataType); |
| 427 | imageMultiPart3->append(httpPart: imagePart31); |
| 428 | file31->setParent(imageMultiPart3); |
| 429 | QHttpPart imagePart32; |
| 430 | imagePart32.setHeader(header: QNetworkRequest::ContentTypeHeader, value: QVariant("image/jpeg" )); |
| 431 | imagePart32.setHeader(header: QNetworkRequest::ContentDispositionHeader, value: QVariant("form-data; name=\"testImage2\"" )); |
| 432 | QFile *file32 = new QFile(QFINDTESTDATA("../qnetworkreply/image2.jpg" )); |
| 433 | file32->open(flags: QIODevice::ReadOnly); |
| 434 | imagePart32.setBodyDevice(file31); // check that resetting works |
| 435 | imagePart32.setBodyDevice(file32); |
| 436 | imageMultiPart3->append(httpPart: imagePart32); |
| 437 | file32->setParent(imageMultiPart3); |
| 438 | QHttpPart imagePart33; |
| 439 | imagePart33.setHeader(header: QNetworkRequest::ContentTypeHeader, value: QVariant("image/jpeg" )); |
| 440 | imagePart33.setHeader(header: QNetworkRequest::ContentDispositionHeader, value: QVariant("form-data; name=\"testImage3\"" )); |
| 441 | QFile *file33 = new QFile(QFINDTESTDATA("../qnetworkreply/image3.jpg" )); |
| 442 | file33->open(flags: QIODevice::ReadOnly); |
| 443 | imagePart33.setBodyDevice(file33); |
| 444 | imageMultiPart3->append(httpPart: imagePart33); |
| 445 | file33->setParent(imageMultiPart3); |
| 446 | QByteArray expectedData = "content type: multipart/form-data; boundary=\"" |
| 447 | + imageMultiPart3->boundary(); |
| 448 | expectedData.append(s: "\"\nkey: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n" |
| 449 | "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n" |
| 450 | "key: testImage3, value: ab0eb6fd4fcf8b4436254870b4513033\n" ); |
| 451 | |
| 452 | QTest::newRow(dataTag: "multipart-3images" ) << multiPartUrl << QByteArray() << QByteArray("POST" ) |
| 453 | << static_cast<QObject *>(imageMultiPart3) << expectedData |
| 454 | << QNetworkProxy(); |
| 455 | } |
| 456 | |
| 457 | void tst_Spdy::upload() |
| 458 | { |
| 459 | QFETCH(QUrl, url); |
| 460 | QNetworkRequest request(url); |
| 461 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: true); |
| 462 | |
| 463 | QFETCH(QByteArray, data); |
| 464 | QFETCH(QByteArray, uploadMethod); |
| 465 | QFETCH(QObject *, uploadObject); |
| 466 | QFETCH(QNetworkProxy, proxy); |
| 467 | |
| 468 | if (proxy.type() != QNetworkProxy::DefaultProxy) { |
| 469 | m_manager.setProxy(proxy); |
| 470 | } |
| 471 | |
| 472 | QNetworkReply *reply; |
| 473 | QHttpMultiPart *multiPart = 0; |
| 474 | |
| 475 | if (uploadObject) { |
| 476 | // upload via device |
| 477 | if (QIODevice *device = qobject_cast<QIODevice *>(object: uploadObject)) { |
| 478 | reply = m_manager.post(request, data: device); |
| 479 | } else if ((multiPart = qobject_cast<QHttpMultiPart *>(object: uploadObject))) { |
| 480 | reply = m_manager.post(request, multiPart); |
| 481 | } else { |
| 482 | QFAIL("got unknown upload device" ); |
| 483 | } |
| 484 | } else { |
| 485 | // upload via byte array |
| 486 | if (uploadMethod == "PUT" ) { |
| 487 | reply = m_manager.put(request, data); |
| 488 | } else { |
| 489 | reply = m_manager.post(request, data); |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | reply->ignoreSslErrors(); |
| 494 | QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged())); |
| 495 | QSignalSpy uploadProgressSpy(reply, SIGNAL(uploadProgress(qint64, qint64))); |
| 496 | QSignalSpy readyReadSpy(reply, SIGNAL(readyRead())); |
| 497 | QSignalSpy finishedSpy(reply, SIGNAL(finished())); |
| 498 | |
| 499 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 500 | QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); |
| 501 | |
| 502 | QTestEventLoop::instance().enterLoop(secs: 20); |
| 503 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 504 | |
| 505 | QCOMPARE(finishedManagerSpy.count(), 1); |
| 506 | QCOMPARE(metaDataChangedSpy.count(), 1); |
| 507 | QCOMPARE(finishedSpy.count(), 1); |
| 508 | QVERIFY(uploadProgressSpy.count() > 0); |
| 509 | QVERIFY(readyReadSpy.count() > 0); |
| 510 | |
| 511 | QCOMPARE(reply->error(), QNetworkReply::NoError); |
| 512 | QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true); |
| 513 | QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true); |
| 514 | QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); |
| 515 | |
| 516 | qint64 contentLength = reply->header(header: QNetworkRequest::ContentLengthHeader).toLongLong(); |
| 517 | if (!multiPart) // script to test multiparts does not return a content length |
| 518 | QCOMPARE(contentLength, 33); // 33 bytes for md5 sums (including new line) |
| 519 | |
| 520 | QFETCH(QByteArray, md5sum); |
| 521 | QByteArray content = reply->readAll(); |
| 522 | QCOMPARE(content, md5sum); |
| 523 | |
| 524 | reply->deleteLater(); |
| 525 | if (uploadObject) |
| 526 | uploadObject->deleteLater(); |
| 527 | |
| 528 | m_manager.setProxy(QNetworkProxy()); // reset |
| 529 | } |
| 530 | |
| 531 | void tst_Spdy::errors_data() |
| 532 | { |
| 533 | QTest::addColumn<QUrl>(name: "url" ); |
| 534 | QTest::addColumn<QNetworkProxy>(name: "proxy" ); |
| 535 | QTest::addColumn<bool>(name: "ignoreSslErrors" ); |
| 536 | QTest::addColumn<int>(name: "expectedReplyError" ); |
| 537 | |
| 538 | QTest::newRow(dataTag: "http-404" ) << QUrl("https://" + QtNetworkSettings::serverName() + "/non-existent-url" ) |
| 539 | << QNetworkProxy() << true << int(QNetworkReply::ContentNotFoundError); |
| 540 | |
| 541 | QTest::newRow(dataTag: "ssl-errors" ) << QUrl("https://" + QtNetworkSettings::serverName()) |
| 542 | << QNetworkProxy() << false << int(QNetworkReply::SslHandshakeFailedError); |
| 543 | |
| 544 | QTest::newRow(dataTag: "host-not-found" ) << QUrl("https://this-host-does-not.exist" ) |
| 545 | << QNetworkProxy() |
| 546 | << true << int(QNetworkReply::HostNotFoundError); |
| 547 | |
| 548 | QTest::newRow(dataTag: "proxy-not-found" ) << QUrl("https://" + QtNetworkSettings::serverName()) |
| 549 | << QNetworkProxy(QNetworkProxy::HttpProxy, |
| 550 | "https://this-host-does-not.exist" , 3128) |
| 551 | << true << int(QNetworkReply::HostNotFoundError); |
| 552 | |
| 553 | QHostInfo hostInfo = QHostInfo::fromName(name: QtNetworkSettings::serverName()); |
| 554 | QString proxyserver = hostInfo.addresses().first().toString(); |
| 555 | |
| 556 | QTest::newRow(dataTag: "proxy-unavailable" ) << QUrl("https://" + QtNetworkSettings::serverName()) |
| 557 | << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 10) |
| 558 | << true << int(QNetworkReply::UnknownNetworkError); |
| 559 | |
| 560 | QTest::newRow(dataTag: "no-proxy-credentials" ) << QUrl("https://" + QtNetworkSettings::serverName()) |
| 561 | << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3129) |
| 562 | << true << int(QNetworkReply::ProxyAuthenticationRequiredError); |
| 563 | } |
| 564 | |
| 565 | void tst_Spdy::errors() |
| 566 | { |
| 567 | QFETCH(QUrl, url); |
| 568 | QFETCH(QNetworkProxy, proxy); |
| 569 | QFETCH(bool, ignoreSslErrors); |
| 570 | QFETCH(int, expectedReplyError); |
| 571 | |
| 572 | QNetworkRequest request(url); |
| 573 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: true); |
| 574 | |
| 575 | disconnect(sender: &m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), |
| 576 | receiver: 0, member: 0); |
| 577 | if (proxy.type() != QNetworkProxy::DefaultProxy) { |
| 578 | m_manager.setProxy(proxy); |
| 579 | } |
| 580 | QNetworkReply *reply = m_manager.get(request); |
| 581 | if (ignoreSslErrors) |
| 582 | reply->ignoreSslErrors(); |
| 583 | QSignalSpy finishedSpy(reply, SIGNAL(finished())); |
| 584 | QSignalSpy errorSpy(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError))); |
| 585 | |
| 586 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 587 | |
| 588 | QTestEventLoop::instance().enterLoop(secs: 15); |
| 589 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 590 | |
| 591 | QCOMPARE(finishedSpy.count(), 1); |
| 592 | QCOMPARE(errorSpy.count(), 1); |
| 593 | |
| 594 | QCOMPARE(reply->error(), static_cast<QNetworkReply::NetworkError>(expectedReplyError)); |
| 595 | |
| 596 | m_manager.setProxy(QNetworkProxy()); // reset |
| 597 | m_manager.clearAccessCache(); // e.g. to get an SSL error we need a new connection |
| 598 | connect(sender: &m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), |
| 599 | receiver: this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), |
| 600 | Qt::UniqueConnection); // reset |
| 601 | } |
| 602 | #endif // !QT_NO_NETWORKPROXY |
| 603 | |
| 604 | void tst_Spdy::multipleRequests_data() |
| 605 | { |
| 606 | QTest::addColumn<QList<QUrl> >(name: "urls" ); |
| 607 | |
| 608 | QString baseUrl = "https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/echo.cgi?" ; |
| 609 | QList<QUrl> urls; |
| 610 | for (int a = 1; a <= 50; ++a) |
| 611 | urls.append(t: QUrl(baseUrl + QLatin1String(QByteArray::number(a)))); |
| 612 | |
| 613 | QTest::newRow(dataTag: "one-request" ) << urls.mid(pos: 0, alength: 1); |
| 614 | QTest::newRow(dataTag: "two-requests" ) << urls.mid(pos: 0, alength: 2); |
| 615 | QTest::newRow(dataTag: "ten-requests" ) << urls.mid(pos: 0, alength: 10); |
| 616 | QTest::newRow(dataTag: "twenty-requests" ) << urls.mid(pos: 0, alength: 20); |
| 617 | QTest::newRow(dataTag: "fifty-requests" ) << urls; |
| 618 | } |
| 619 | |
| 620 | void tst_Spdy::multipleRequestsFinishedSlot() |
| 621 | { |
| 622 | m_multipleRepliesFinishedCount++; |
| 623 | if (m_multipleRepliesFinishedCount == m_multipleRequestsCount) |
| 624 | QTestEventLoop::instance().exitLoop(); |
| 625 | } |
| 626 | |
| 627 | void tst_Spdy::multipleRequests() |
| 628 | { |
| 629 | QFETCH(QList<QUrl>, urls); |
| 630 | m_multipleRequestsCount = urls.count(); |
| 631 | m_multipleRepliesFinishedCount = 0; |
| 632 | |
| 633 | QList<QNetworkReply *> replies; |
| 634 | QList<QSignalSpy *> metaDataChangedSpies; |
| 635 | QList<QSignalSpy *> readyReadSpies; |
| 636 | QList<QSignalSpy *> finishedSpies; |
| 637 | |
| 638 | foreach (const QUrl &url, urls) { |
| 639 | QNetworkRequest request(url); |
| 640 | request.setAttribute(code: QNetworkRequest::SpdyAllowedAttribute, value: true); |
| 641 | QNetworkReply *reply = m_manager.get(request); |
| 642 | replies.append(t: reply); |
| 643 | reply->ignoreSslErrors(); |
| 644 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(multipleRequestsFinishedSlot())); |
| 645 | QSignalSpy *metaDataChangedSpy = new QSignalSpy(reply, SIGNAL(metaDataChanged())); |
| 646 | metaDataChangedSpies << metaDataChangedSpy; |
| 647 | QSignalSpy *readyReadSpy = new QSignalSpy(reply, SIGNAL(readyRead())); |
| 648 | readyReadSpies << readyReadSpy; |
| 649 | QSignalSpy *finishedSpy = new QSignalSpy(reply, SIGNAL(finished())); |
| 650 | finishedSpies << finishedSpy; |
| 651 | } |
| 652 | |
| 653 | QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); |
| 654 | |
| 655 | QTestEventLoop::instance().enterLoop(secs: 15); |
| 656 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 657 | |
| 658 | QCOMPARE(finishedManagerSpy.count(), m_multipleRequestsCount); |
| 659 | |
| 660 | for (int a = 0; a < replies.count(); ++a) { |
| 661 | |
| 662 | #ifndef QT_NO_OPENSSL |
| 663 | QCOMPARE(replies.at(a)->sslConfiguration().nextProtocolNegotiationStatus(), |
| 664 | QSslConfiguration::NextProtocolNegotiationNegotiated); |
| 665 | QCOMPARE(replies.at(a)->sslConfiguration().nextNegotiatedProtocol(), |
| 666 | QByteArray(QSslConfiguration::NextProtocolSpdy3_0)); |
| 667 | #endif // QT_NO_OPENSSL |
| 668 | |
| 669 | QCOMPARE(replies.at(a)->error(), QNetworkReply::NoError); |
| 670 | QCOMPARE(replies.at(a)->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true); |
| 671 | QCOMPARE(replies.at(a)->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true); |
| 672 | QCOMPARE(replies.at(a)->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); |
| 673 | |
| 674 | // using the echo script, a request to "echo.cgi?1" will return a body of "1" |
| 675 | QByteArray expectedContent = replies.at(i: a)->url().query().toUtf8(); |
| 676 | QByteArray content = replies.at(i: a)->readAll(); |
| 677 | QCOMPARE(expectedContent, content); |
| 678 | |
| 679 | QCOMPARE(metaDataChangedSpies.at(a)->count(), 1); |
| 680 | metaDataChangedSpies.at(i: a)->deleteLater(); |
| 681 | |
| 682 | QCOMPARE(finishedSpies.at(a)->count(), 1); |
| 683 | finishedSpies.at(i: a)->deleteLater(); |
| 684 | |
| 685 | QVERIFY(readyReadSpies.at(a)->count() > 0); |
| 686 | readyReadSpies.at(i: a)->deleteLater(); |
| 687 | |
| 688 | replies.at(i: a)->deleteLater(); |
| 689 | } |
| 690 | } |
| 691 | |
| 692 | QTEST_MAIN(tst_Spdy) |
| 693 | |
| 694 | #include "tst_spdy.moc" |
| 695 | |