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 | |