1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
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 "private/qhttpnetworkconnection_p.h"
32#include "private/qnoncontiguousbytedevice_p.h"
33#include <QAuthenticator>
34#include <QTcpServer>
35
36#include "../../../network-settings.h"
37
38class tst_QHttpNetworkConnection: public QObject
39{
40 Q_OBJECT
41
42public Q_SLOTS:
43 void finishedReply();
44 void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail);
45 void challenge401(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
46#ifndef QT_NO_SSL
47 void sslErrors(const QList<QSslError> &errors);
48#endif
49private:
50 bool finishedCalled;
51 bool finishedWithErrorCalled;
52 QNetworkReply::NetworkError netErrorCode;
53 QString (*httpServerName)() = QtNetworkSettings::httpServerName;
54
55private Q_SLOTS:
56 void initTestCase();
57 void options_data();
58 void options();
59 void get_data();
60 void get();
61 void head_data();
62 void head();
63 void post_data();
64 void post();
65 void put_data();
66 void put();
67 void _delete_data();
68 void _delete();
69 void trace_data();
70 void trace();
71 void _connect_data();
72 void _connect();
73#ifndef QT_NO_COMPRESS
74 void compression_data();
75 void compression();
76#endif
77#ifndef QT_NO_SSL
78 void ignoresslerror_data();
79 void ignoresslerror();
80#endif
81#ifdef QT_NO_SSL
82 void nossl_data();
83 void nossl();
84#endif
85 void get401_data();
86 void get401();
87
88 void getMultiple_data();
89 void getMultiple();
90 void getMultipleWithPipeliningAndMultiplePriorities();
91 void getMultipleWithPriorities();
92
93 void getEmptyWithPipelining();
94
95 void getAndEverythingShouldBePipelined();
96
97 void getAndThenDeleteObject();
98 void getAndThenDeleteObject_data();
99
100 void overlappingCloseAndWrite();
101};
102
103void tst_QHttpNetworkConnection::initTestCase()
104{
105#if defined(QT_TEST_SERVER)
106 QVERIFY(QtNetworkSettings::verifyConnection(httpServerName(), 80));
107#else
108 if (!QtNetworkSettings::verifyTestNetworkSettings())
109 QSKIP("No network test server available");
110#endif
111}
112
113void tst_QHttpNetworkConnection::options_data()
114{
115 // not tested yet
116}
117
118void tst_QHttpNetworkConnection::options()
119{
120 QEXPECT_FAIL("", "not tested yet", Continue);
121 QVERIFY(false);
122}
123
124void tst_QHttpNetworkConnection::head_data()
125{
126 QTest::addColumn<QString>(name: "protocol");
127 QTest::addColumn<QString>(name: "host");
128 QTest::addColumn<QString>(name: "path");
129 QTest::addColumn<ushort>(name: "port");
130 QTest::addColumn<bool>(name: "encrypt");
131 QTest::addColumn<int>(name: "statusCode");
132 QTest::addColumn<QString>(name: "statusString");
133 QTest::addColumn<int>(name: "contentLength");
134
135 QTest::newRow(dataTag: "success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962;
136 QTest::newRow(dataTag: "failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1;
137 QTest::newRow(dataTag: "failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1;
138}
139
140void tst_QHttpNetworkConnection::head()
141{
142 QFETCH(QString, protocol);
143 QFETCH(QString, host);
144 QFETCH(QString, path);
145 QFETCH(ushort, port);
146 QFETCH(bool, encrypt);
147 QFETCH(int, statusCode);
148 QFETCH(QString, statusString);
149 QFETCH(int, contentLength);
150
151 QHttpNetworkConnection connection(host, port, encrypt);
152 QCOMPARE(connection.port(), port);
153 QCOMPARE(connection.hostName(), host);
154 QCOMPARE(connection.isSsl(), encrypt);
155
156 QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Head);
157 QHttpNetworkReply *reply = connection.sendRequest(request);
158
159 QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
160 QCOMPARE(reply->statusCode(), statusCode);
161 QCOMPARE(reply->reasonPhrase(), statusString);
162 // only check it if it is set and expected
163 if (reply->contentLength() != -1 && contentLength != -1)
164 QCOMPARE(reply->contentLength(), qint64(contentLength));
165
166 QVERIFY(reply->isFinished());
167
168 delete reply;
169}
170
171void tst_QHttpNetworkConnection::get_data()
172{
173 QTest::addColumn<QString>(name: "protocol");
174 QTest::addColumn<QString>(name: "host");
175 QTest::addColumn<QString>(name: "path");
176 QTest::addColumn<ushort>(name: "port");
177 QTest::addColumn<bool>(name: "encrypt");
178 QTest::addColumn<int>(name: "statusCode");
179 QTest::addColumn<QString>(name: "statusString");
180 QTest::addColumn<int>(name: "contentLength");
181 QTest::addColumn<int>(name: "downloadSize");
182
183 QTest::newRow(dataTag: "success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962 << 25962;
184
185 QTest::newRow(dataTag: "failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1 << -1;
186 QTest::newRow(dataTag: "failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1 << -1;
187}
188
189void tst_QHttpNetworkConnection::get()
190{
191 QFETCH(QString, protocol);
192 QFETCH(QString, host);
193 QFETCH(QString, path);
194 QFETCH(ushort, port);
195 QFETCH(bool, encrypt);
196 QFETCH(int, statusCode);
197 QFETCH(QString, statusString);
198 QFETCH(int, contentLength);
199 QFETCH(int, downloadSize);
200
201 QHttpNetworkConnection connection(host, port, encrypt);
202 QCOMPARE(connection.port(), port);
203 QCOMPARE(connection.hostName(), host);
204 QCOMPARE(connection.isSsl(), encrypt);
205
206 QHttpNetworkRequest request(protocol + host + path);
207 QHttpNetworkReply *reply = connection.sendRequest(request);
208
209 QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
210
211 QCOMPARE(reply->statusCode(), statusCode);
212 QCOMPARE(reply->reasonPhrase(), statusString);
213 // only check it if it is set and expected
214 if (reply->contentLength() != -1 && contentLength != -1)
215 QCOMPARE(reply->contentLength(), qint64(contentLength));
216
217 QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
218 QByteArray ba = reply->readAll();
219 //do not require server generated error pages to be a fixed size
220 if (downloadSize != -1)
221 QCOMPARE(ba.size(), downloadSize);
222 //but check against content length if it was sent
223 if (reply->contentLength() != -1)
224 QCOMPARE(ba.size(), (int)reply->contentLength());
225
226 delete reply;
227}
228
229void tst_QHttpNetworkConnection::finishedReply()
230{
231 finishedCalled = true;
232}
233
234void tst_QHttpNetworkConnection::finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail)
235{
236 Q_UNUSED(detail)
237 finishedWithErrorCalled = true;
238 netErrorCode = errorCode;
239}
240
241void tst_QHttpNetworkConnection::put_data()
242{
243
244 QTest::addColumn<QString>(name: "protocol");
245 QTest::addColumn<QString>(name: "host");
246 QTest::addColumn<QString>(name: "path");
247 QTest::addColumn<ushort>(name: "port");
248 QTest::addColumn<bool>(name: "encrypt");
249 QTest::addColumn<QString>(name: "data");
250 QTest::addColumn<bool>(name: "succeed");
251
252 QTest::newRow(dataTag: "success-internal") << "http://" << httpServerName() << "/dav/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<true;
253 QTest::newRow(dataTag: "fail-internal") << "http://" << httpServerName() << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
254 QTest::newRow(dataTag: "fail-host") << "http://" << "invalid.test.qt-project.org" << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
255}
256
257void tst_QHttpNetworkConnection::put()
258{
259 QFETCH(QString, protocol);
260 QFETCH(QString, host);
261 QFETCH(QString, path);
262 QFETCH(ushort, port);
263 QFETCH(bool, encrypt);
264 QFETCH(QString, data);
265 QFETCH(bool, succeed);
266
267 QHttpNetworkConnection connection(host, port, encrypt);
268 QCOMPARE(connection.port(), port);
269 QCOMPARE(connection.hostName(), host);
270 QCOMPARE(connection.isSsl(), encrypt);
271
272 QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Put);
273
274 QByteArray array = data.toLatin1();
275 QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(byteArray: &array);
276 bd->setParent(this);
277 request.setUploadByteDevice(bd);
278
279 finishedCalled = false;
280 finishedWithErrorCalled = false;
281
282 QHttpNetworkReply *reply = connection.sendRequest(request);
283 connect(asender: reply, SIGNAL(finished()), SLOT(finishedReply()));
284 connect(asender: reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
285 SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
286
287 QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished() || finishedCalled || finishedWithErrorCalled, 30000);
288
289 if (reply->isFinished()) {
290 QByteArray ba;
291 while (reply->bytesAvailable())
292 ba += reply->readAny();
293 } else if(finishedWithErrorCalled) {
294 if(!succeed) {
295 delete reply;
296 return;
297 } else {
298 QFAIL("Error in PUT");
299 }
300 } else {
301 QFAIL("PUT timed out");
302 }
303
304 int status = reply->statusCode();
305 if (status != 200 && status != 201 && status != 204) {
306 if (succeed) {
307 qDebug()<<"PUT failed, Status Code:" <<status;
308 QFAIL("Error in PUT");
309 }
310 } else {
311 if (!succeed) {
312 qDebug()<<"PUT Should fail, Status Code:" <<status;
313 QFAIL("Error in PUT");
314 }
315 }
316 delete reply;
317}
318
319void tst_QHttpNetworkConnection::post_data()
320{
321 QTest::addColumn<QString>(name: "protocol");
322 QTest::addColumn<QString>(name: "host");
323 QTest::addColumn<QString>(name: "path");
324 QTest::addColumn<ushort>(name: "port");
325 QTest::addColumn<bool>(name: "encrypt");
326 QTest::addColumn<QString>(name: "data");
327 QTest::addColumn<int>(name: "statusCode");
328 QTest::addColumn<QString>(name: "statusString");
329 QTest::addColumn<int>(name: "contentLength");
330 QTest::addColumn<int>(name: "downloadSize");
331
332 QTest::newRow(dataTag: "success-internal") << "http://" << httpServerName() << "/qtest/cgi-bin/echo.cgi" << ushort(80) << false << "7 bytes" << 200 << "OK" << 7 << 7;
333 QTest::newRow(dataTag: "failure-internal") << "http://" << httpServerName() << "/t" << ushort(80) << false << "Hello World" << 404 << "Not Found" << -1 << -1;
334}
335
336void tst_QHttpNetworkConnection::post()
337{
338 QFETCH(QString, protocol);
339 QFETCH(QString, host);
340 QFETCH(QString, path);
341 QFETCH(ushort, port);
342 QFETCH(bool, encrypt);
343 QFETCH(QString, data);
344 QFETCH(int, statusCode);
345 QFETCH(QString, statusString);
346 QFETCH(int, contentLength);
347 QFETCH(int, downloadSize);
348
349 QHttpNetworkConnection connection(host, port, encrypt);
350 QCOMPARE(connection.port(), port);
351 QCOMPARE(connection.hostName(), host);
352 QCOMPARE(connection.isSsl(), encrypt);
353
354 QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Post);
355
356 QByteArray array = data.toLatin1();
357 QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(byteArray: &array);
358 bd->setParent(this);
359 request.setUploadByteDevice(bd);
360
361 QHttpNetworkReply *reply = connection.sendRequest(request);
362
363 QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
364 QCOMPARE(reply->statusCode(), statusCode);
365 QCOMPARE(reply->reasonPhrase(), statusString);
366
367 qint64 cLen = reply->contentLength();
368 if (contentLength != -1) {
369 // only check the content length if test expected it to be set
370 if (cLen==-1) {
371 // HTTP 1.1 server may respond with chunked encoding and in that
372 // case contentLength is not present in reply -> verify that it is the case
373 QByteArray transferEnc = reply->headerField(name: "Transfer-Encoding");
374 QCOMPARE(transferEnc, QByteArray("chunked"));
375 } else {
376 QCOMPARE(cLen, qint64(contentLength));
377 }
378 }
379
380 QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
381 QByteArray ba = reply->readAll();
382 //don't require fixed size for generated error pages
383 if (downloadSize != -1)
384 QCOMPARE(ba.size(), downloadSize);
385 //but do compare with content length if possible
386 if (cLen != -1)
387 QCOMPARE(ba.size(), (int)cLen);
388
389 delete reply;
390}
391
392void tst_QHttpNetworkConnection::_delete_data()
393{
394 // not tested yet
395}
396
397void tst_QHttpNetworkConnection::_delete()
398{
399 QEXPECT_FAIL("", "not tested yet", Continue);
400 QVERIFY(false);
401}
402
403void tst_QHttpNetworkConnection::trace_data()
404{
405 // not tested yet
406}
407
408void tst_QHttpNetworkConnection::trace()
409{
410 QEXPECT_FAIL("", "not tested yet", Continue);
411 QVERIFY(false);
412}
413
414void tst_QHttpNetworkConnection::_connect_data()
415{
416 // not tested yet
417}
418
419void tst_QHttpNetworkConnection::_connect()
420{
421 QEXPECT_FAIL("", "not tested yet", Continue);
422 QVERIFY(false);
423}
424
425void tst_QHttpNetworkConnection::challenge401(const QHttpNetworkRequest &request,
426 QAuthenticator *authenticator)
427{
428 Q_UNUSED(request)
429
430 QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(object: sender());
431 if (reply) {
432 QHttpNetworkConnection *c = reply->connection();
433
434 QVariant val = c->property(name: "setCredentials");
435 if (val.toBool()) {
436 QVariant user = c->property(name: "username");
437 QVariant password = c->property(name: "password");
438 authenticator->setUser(user.toString());
439 authenticator->setPassword(password.toString());
440 c->setProperty(name: "setCredentials", value: false);
441 }
442 }
443}
444
445void tst_QHttpNetworkConnection::get401_data()
446{
447 QTest::addColumn<QString>(name: "protocol");
448 QTest::addColumn<QString>(name: "host");
449 QTest::addColumn<QString>(name: "path");
450 QTest::addColumn<ushort>(name: "port");
451 QTest::addColumn<bool>(name: "encrypt");
452 QTest::addColumn<bool>(name: "setCredentials");
453 QTest::addColumn<QString>(name: "username");
454 QTest::addColumn<QString>(name: "password");
455 QTest::addColumn<int>(name: "statusCode");
456
457 QTest::newRow(dataTag: "no-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << false << "" << ""<<401;
458 QTest::newRow(dataTag: "invalid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "test" << "test"<<401;
459 QTest::newRow(dataTag: "valid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
460 QTest::newRow(dataTag: "digest-authentication-invalid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "wrong" << "wrong"<<401;
461 QTest::newRow(dataTag: "digest-authentication-valid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
462}
463
464void tst_QHttpNetworkConnection::get401()
465{
466 QFETCH(QString, protocol);
467 QFETCH(QString, host);
468 QFETCH(QString, path);
469 QFETCH(ushort, port);
470 QFETCH(bool, encrypt);
471 QFETCH(bool, setCredentials);
472 QFETCH(QString, username);
473 QFETCH(QString, password);
474 QFETCH(int, statusCode);
475
476 QHttpNetworkConnection connection(host, port, encrypt);
477 QCOMPARE(connection.port(), port);
478 QCOMPARE(connection.hostName(), host);
479 QCOMPARE(connection.isSsl(), encrypt);
480 connection.setProperty(name: "setCredentials", value: setCredentials);
481 connection.setProperty(name: "username", value: username);
482 connection.setProperty(name: "password", value: password);
483
484 QHttpNetworkRequest request(protocol + host + path);
485 QHttpNetworkReply *reply = connection.sendRequest(request);
486 connect(asender: reply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
487 SLOT(challenge401(QHttpNetworkRequest,QAuthenticator*)));
488
489 finishedCalled = false;
490 finishedWithErrorCalled = false;
491
492 connect(asender: reply, SIGNAL(finished()), SLOT(finishedReply()));
493 connect(asender: reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
494 SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
495
496 QTRY_VERIFY_WITH_TIMEOUT(finishedCalled || finishedWithErrorCalled, 30000);
497 QCOMPARE(reply->statusCode(), statusCode);
498 delete reply;
499}
500
501#ifndef QT_NO_COMPRESS
502void tst_QHttpNetworkConnection::compression_data()
503{
504 QTest::addColumn<QString>(name: "protocol");
505 QTest::addColumn<QString>(name: "host");
506 QTest::addColumn<QString>(name: "path");
507 QTest::addColumn<ushort>(name: "port");
508 QTest::addColumn<bool>(name: "encrypt");
509 QTest::addColumn<int>(name: "statusCode");
510 QTest::addColumn<QString>(name: "statusString");
511 QTest::addColumn<int>(name: "contentLength");
512 QTest::addColumn<int>(name: "downloadSize");
513 QTest::addColumn<bool>(name: "autoCompress");
514 QTest::addColumn<QString>(name: "contentCoding");
515
516 QTest::newRow(dataTag: "success-autogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << -1 << 418321 << true << "";
517 QTest::newRow(dataTag: "success-nogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << 418321 << 418321 << false << "identity";
518 QTest::newRow(dataTag: "success-manualgzip-temp") << "http://" << httpServerName() << "/qtest/deflate/rfc2616.html" << ushort(80) << false << 200 << "OK" << 119124 << 119124 << false << "gzip";
519
520}
521
522void tst_QHttpNetworkConnection::compression()
523{
524 QFETCH(QString, protocol);
525 QFETCH(QString, host);
526 QFETCH(QString, path);
527 QFETCH(ushort, port);
528 QFETCH(bool, encrypt);
529 QFETCH(int, statusCode);
530 QFETCH(QString, statusString);
531 QFETCH(int, contentLength);
532 QFETCH(int, downloadSize);
533 QFETCH(bool, autoCompress);
534 QFETCH(QString, contentCoding);
535
536 QHttpNetworkConnection connection(host, port, encrypt);
537 QCOMPARE(connection.port(), port);
538 QCOMPARE(connection.hostName(), host);
539 QCOMPARE(connection.isSsl(), encrypt);
540
541 QHttpNetworkRequest request(protocol + host + path);
542 if (!autoCompress)
543 request.setHeaderField(name: "Accept-Encoding", data: contentCoding.toLatin1());
544 QHttpNetworkReply *reply = connection.sendRequest(request);
545
546 QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
547 QCOMPARE(reply->statusCode(), statusCode);
548 QCOMPARE(reply->reasonPhrase(), statusString);
549 bool isLengthOk = (reply->contentLength() == qint64(contentLength)
550 || reply->contentLength() == qint64(downloadSize)
551 || reply->contentLength() == -1); //apache2 does not send content-length for compressed pages
552
553 QVERIFY(isLengthOk);
554
555 QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
556 QByteArray ba = reply->readAll();
557 QCOMPARE(ba.size(), downloadSize);
558
559 delete reply;
560}
561#endif
562
563#ifndef QT_NO_SSL
564void tst_QHttpNetworkConnection::sslErrors(const QList<QSslError> &errors)
565{
566 Q_UNUSED(errors)
567
568 QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(object: sender());
569 if (reply) {
570 QHttpNetworkConnection *connection = reply->connection();
571
572 QVariant val = connection->property(name: "ignoreFromSignal");
573 if (val.toBool())
574 connection->ignoreSslErrors();
575 finishedWithErrorCalled = true;
576 }
577}
578
579void tst_QHttpNetworkConnection::ignoresslerror_data()
580{
581 QTest::addColumn<QString>(name: "protocol");
582 QTest::addColumn<QString>(name: "host");
583 QTest::addColumn<QString>(name: "path");
584 QTest::addColumn<ushort>(name: "port");
585 QTest::addColumn<bool>(name: "encrypt");
586 QTest::addColumn<bool>(name: "ignoreInit");
587 QTest::addColumn<bool>(name: "ignoreFromSignal");
588 QTest::addColumn<int>(name: "statusCode");
589
590 // This test will work only if the website has ssl errors.
591 // fluke's certificate is signed by a non-standard authority.
592 // Since we don't introduce that CA into the SSL verification chain,
593 // connecting should fail.
594 QTest::newRow(dataTag: "success-init") << "https://" << httpServerName() << "/" << ushort(443) << true << true << false << 200;
595 QTest::newRow(dataTag: "success-fromSignal") << "https://" << httpServerName() << "/" << ushort(443) << true << false << true << 200;
596 QTest::newRow(dataTag: "failure") << "https://" << httpServerName() << "/" << ushort(443) << true << false << false << 100;
597}
598
599void tst_QHttpNetworkConnection::ignoresslerror()
600{
601 QFETCH(QString, protocol);
602 QFETCH(QString, host);
603 QFETCH(QString, path);
604 QFETCH(ushort, port);
605 QFETCH(bool, encrypt);
606 QFETCH(bool, ignoreInit);
607 QFETCH(bool, ignoreFromSignal);
608 QFETCH(int, statusCode);
609
610 QHttpNetworkConnection connection(host, port, encrypt);
611 QCOMPARE(connection.port(), port);
612 QCOMPARE(connection.hostName(), host);
613 if (ignoreInit)
614 connection.ignoreSslErrors();
615 QCOMPARE(connection.isSsl(), encrypt);
616 connection.setProperty(name: "ignoreFromSignal", value: ignoreFromSignal);
617
618 QHttpNetworkRequest request(protocol + host + path);
619 QHttpNetworkReply *reply = connection.sendRequest(request);
620 connect(asender: reply, SIGNAL(sslErrors(QList<QSslError>)),
621 SLOT(sslErrors(QList<QSslError>)));
622
623 finishedWithErrorCalled = false;
624
625 connect(asender: reply, SIGNAL(finished()), SLOT(finishedReply()));
626
627 QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable() || (statusCode == 100 && finishedWithErrorCalled), 30000);
628 QCOMPARE(reply->statusCode(), statusCode);
629 delete reply;
630}
631#endif
632
633#ifdef QT_NO_SSL
634void tst_QHttpNetworkConnection::nossl_data()
635{
636 QTest::addColumn<QString>("protocol");
637 QTest::addColumn<QString>("host");
638 QTest::addColumn<QString>("path");
639 QTest::addColumn<ushort>("port");
640 QTest::addColumn<bool>("encrypt");
641 QTest::addColumn<QNetworkReply::NetworkError>("networkError");
642
643 QTest::newRow("protocol-error") << "https://" << httpServerName() << "/" << ushort(443) << true <<QNetworkReply::ProtocolUnknownError;
644}
645
646void tst_QHttpNetworkConnection::nossl()
647{
648 QFETCH(QString, protocol);
649 QFETCH(QString, host);
650 QFETCH(QString, path);
651 QFETCH(ushort, port);
652 QFETCH(bool, encrypt);
653 QFETCH(QNetworkReply::NetworkError, networkError);
654
655 QHttpNetworkConnection connection(host, port, encrypt);
656 QCOMPARE(connection.port(), port);
657 QCOMPARE(connection.hostName(), host);
658
659 QHttpNetworkRequest request(protocol + host + path);
660 QHttpNetworkReply *reply = connection.sendRequest(request);
661
662 finishedWithErrorCalled = false;
663 netErrorCode = QNetworkReply::NoError;
664
665 connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
666 connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
667 SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
668
669 QTRY_VERIFY_WITH_TIMEOUT(finishedWithErrorCalled, 30000);
670 QCOMPARE(netErrorCode, networkError);
671 delete reply;
672}
673#endif
674
675
676void tst_QHttpNetworkConnection::getMultiple_data()
677{
678 QTest::addColumn<quint16>(name: "connectionCount");
679 QTest::addColumn<bool>(name: "pipeliningAllowed");
680 // send 100 requests. apache will usually force-close after 100 requests in a single tcp connection
681 QTest::addColumn<int>(name: "requestCount");
682
683 QTest::newRow(dataTag: "6 connections, no pipelining, 100 requests") << quint16(6) << false << 100;
684 QTest::newRow(dataTag: "1 connection, no pipelining, 100 requests") << quint16(1) << false << 100;
685 QTest::newRow(dataTag: "6 connections, pipelining allowed, 100 requests") << quint16(6) << true << 100;
686 QTest::newRow(dataTag: "1 connection, pipelining allowed, 100 requests") << quint16(1) << true << 100;
687}
688
689static bool allRepliesFinished(const QList<QHttpNetworkReply*> *_replies)
690{
691 const QList<QHttpNetworkReply*> &replies = *_replies;
692 for (int i = 0; i < replies.length(); i++)
693 if (!replies.at(i)->isFinished())
694 return false;
695 return true;
696}
697
698void tst_QHttpNetworkConnection::getMultiple()
699{
700 QFETCH(quint16, connectionCount);
701 QFETCH(bool, pipeliningAllowed);
702 QFETCH(int, requestCount);
703
704 QHttpNetworkConnection connection(connectionCount, httpServerName());
705
706 QList<QHttpNetworkRequest*> requests;
707 QList<QHttpNetworkReply*> replies;
708
709 for (int i = 0; i < requestCount; i++) {
710 // depending on what you use the results will vary.
711 // for the "real" results, use a URL that has "internet latency" for you. Then (6 connections, pipelining) will win.
712 // for LAN latency, you will possibly get that (1 connection, no pipelining) is the fastest
713 QHttpNetworkRequest *request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt");
714 if (pipeliningAllowed)
715 request->setPipeliningAllowed(true);
716 requests.append(t: request);
717 QHttpNetworkReply *reply = connection.sendRequest(request: *request);
718 replies.append(t: reply);
719 }
720
721 QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
722 qDeleteAll(c: requests);
723 qDeleteAll(c: replies);
724}
725
726void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities()
727{
728 quint16 requestCount = 100;
729
730 // use 2 connections.
731 QHttpNetworkConnection connection(2, httpServerName());
732
733 QList<QHttpNetworkRequest*> requests;
734 QList<QHttpNetworkReply*> replies;
735
736 for (int i = 0; i < requestCount; i++) {
737 QHttpNetworkRequest *request = 0;
738 if (i % 3)
739 request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Get);
740 else
741 request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Head);
742
743 if (i % 2 || i % 3)
744 request->setPipeliningAllowed(true);
745
746 if (i % 3)
747 request->setPriority(QHttpNetworkRequest::HighPriority);
748 else if (i % 5)
749 request->setPriority(QHttpNetworkRequest::NormalPriority);
750 else if (i % 7)
751 request->setPriority(QHttpNetworkRequest::LowPriority);
752
753 requests.append(t: request);
754 QHttpNetworkReply *reply = connection.sendRequest(request: *request);
755 replies.append(t: reply);
756 }
757
758 QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
759
760 int pipelinedCount = 0;
761 for (int i = 0; i < replies.length(); i++) {
762 QVERIFY (!(replies.at(i)->request().isPipeliningAllowed() == false
763 && replies.at(i)->isPipeliningUsed()));
764
765 if (replies.at(i)->isPipeliningUsed())
766 pipelinedCount++;
767 }
768
769 // We allow pipelining for every 2nd,3rd,4th,6th,8th,9th,10th etc request.
770 // Assume that half of the requests had been pipelined.
771 // (this is a very relaxed condition, when last measured 79 of 100
772 // requests had been pipelined)
773 QVERIFY(pipelinedCount >= requestCount / 2);
774
775 qDeleteAll(c: requests);
776 qDeleteAll(c: replies);
777}
778
779class GetMultipleWithPrioritiesReceiver : public QObject
780{
781 Q_OBJECT
782public:
783 int highPrioReceived;
784 int lowPrioReceived;
785 int requestCount;
786 GetMultipleWithPrioritiesReceiver(int rq) : highPrioReceived(0), lowPrioReceived(0), requestCount(rq) { }
787public Q_SLOTS:
788 void finishedSlot() {
789 QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
790 if (reply->request().priority() == QHttpNetworkRequest::HighPriority)
791 highPrioReceived++;
792 else if (reply->request().priority() == QHttpNetworkRequest::LowPriority)
793 lowPrioReceived++;
794 else
795 QFAIL("Wrong priority!?");
796
797 QVERIFY(highPrioReceived + 7 >= lowPrioReceived);
798
799 if (highPrioReceived + lowPrioReceived == requestCount)
800 QTestEventLoop::instance().exitLoop();
801 }
802};
803
804void tst_QHttpNetworkConnection::getMultipleWithPriorities()
805{
806 quint16 requestCount = 100;
807 // use 2 connections.
808 QHttpNetworkConnection connection(2, httpServerName());
809 GetMultipleWithPrioritiesReceiver receiver(requestCount);
810 QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
811 QList<QHttpNetworkRequest*> requests;
812 QList<QHttpNetworkReply*> replies;
813
814 for (int i = 0; i < requestCount; i++) {
815 QHttpNetworkRequest *request = 0;
816 if (i % 3)
817 request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
818 else
819 request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Head);
820
821 if (i % 2)
822 request->setPriority(QHttpNetworkRequest::HighPriority);
823 else
824 request->setPriority(QHttpNetworkRequest::LowPriority);
825
826 requests.append(t: request);
827 QHttpNetworkReply *reply = connection.sendRequest(request: *request);
828 connect(sender: reply, SIGNAL(finished()), receiver: &receiver, SLOT(finishedSlot()));
829 replies.append(t: reply);
830 }
831
832 QTestEventLoop::instance().enterLoop(secs: 40);
833 QVERIFY(!QTestEventLoop::instance().timeout());
834
835 qDeleteAll(c: requests);
836 qDeleteAll(c: replies);
837}
838
839
840class GetEmptyWithPipeliningReceiver : public QObject
841{
842 Q_OBJECT
843public:
844 int receivedCount;
845 int requestCount;
846 GetEmptyWithPipeliningReceiver(int rq) : receivedCount(0),requestCount(rq) { }
847public Q_SLOTS:
848 void finishedSlot() {
849 QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
850 Q_UNUSED(reply);
851 receivedCount++;
852
853 if (receivedCount == requestCount)
854 QTestEventLoop::instance().exitLoop();
855 }
856};
857
858void tst_QHttpNetworkConnection::getEmptyWithPipelining()
859{
860 quint16 requestCount = 50;
861 // use 2 connections.
862 QHttpNetworkConnection connection(2, httpServerName());
863 GetEmptyWithPipeliningReceiver receiver(requestCount);
864
865 QUrl url("http://" + httpServerName() + "/cgi-bin/echo.cgi"); // a get on this = getting an empty file
866 QList<QHttpNetworkRequest*> requests;
867 QList<QHttpNetworkReply*> replies;
868
869 for (int i = 0; i < requestCount; i++) {
870 QHttpNetworkRequest *request = 0;
871 request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
872 request->setPipeliningAllowed(true);
873
874 requests.append(t: request);
875 QHttpNetworkReply *reply = connection.sendRequest(request: *request);
876 connect(sender: reply, SIGNAL(finished()), receiver: &receiver, SLOT(finishedSlot()));
877 replies.append(t: reply);
878 }
879
880 QTestEventLoop::instance().enterLoop(secs: 20);
881 QVERIFY(!QTestEventLoop::instance().timeout());
882
883 qDeleteAll(c: requests);
884 qDeleteAll(c: replies);
885}
886
887class GetAndEverythingShouldBePipelinedReceiver : public QObject
888{
889 Q_OBJECT
890public:
891 int receivedCount;
892 int requestCount;
893 GetAndEverythingShouldBePipelinedReceiver(int rq) : receivedCount(0),requestCount(rq) { }
894public Q_SLOTS:
895 void finishedSlot() {
896 QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
897 Q_UNUSED(reply);
898 receivedCount++;
899
900 if (receivedCount == requestCount)
901 QTestEventLoop::instance().exitLoop();
902 }
903};
904
905void tst_QHttpNetworkConnection::getAndEverythingShouldBePipelined()
906{
907 quint16 requestCount = 100;
908 // use 1 connection.
909 QHttpNetworkConnection connection(1, httpServerName());
910 QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
911 QList<QHttpNetworkRequest*> requests;
912 QList<QHttpNetworkReply*> replies;
913
914 GetAndEverythingShouldBePipelinedReceiver receiver(requestCount);
915
916 for (int i = 0; i < requestCount; i++) {
917 QHttpNetworkRequest *request = 0;
918 request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
919 request->setPipeliningAllowed(true);
920 requests.append(t: request);
921 QHttpNetworkReply *reply = connection.sendRequest(request: *request);
922 connect(sender: reply, SIGNAL(finished()), receiver: &receiver, SLOT(finishedSlot()));
923 replies.append(t: reply);
924 }
925 QTestEventLoop::instance().enterLoop(secs: 40);
926 QVERIFY(!QTestEventLoop::instance().timeout());
927
928 qDeleteAll(c: requests);
929 qDeleteAll(c: replies);
930
931}
932
933
934void tst_QHttpNetworkConnection::getAndThenDeleteObject_data()
935{
936 QTest::addColumn<bool>(name: "replyFirst");
937
938 QTest::newRow(dataTag: "delete-reply-first") << true;
939 QTest::newRow(dataTag: "delete-connection-first") << false;
940}
941
942void tst_QHttpNetworkConnection::getAndThenDeleteObject()
943{
944 // yes, this will leak if the testcase fails. I don't care. It must not fail then :P
945 QHttpNetworkConnection *connection = new QHttpNetworkConnection(httpServerName());
946 QHttpNetworkRequest request("http://" + httpServerName() + "/qtest/bigfile");
947 QHttpNetworkReply *reply = connection->sendRequest(request);
948 reply->setDownstreamLimited(true);
949
950 QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
951 QCOMPARE(reply->statusCode() ,200);
952 QVERIFY(!reply->isFinished()); // must not be finished
953
954 QFETCH(bool, replyFirst);
955
956 if (replyFirst) {
957 delete reply;
958 delete connection;
959 } else {
960 delete connection;
961 delete reply;
962 }
963}
964
965class TestTcpServer : public QTcpServer
966{
967 Q_OBJECT
968public:
969 TestTcpServer() : errorCodeReports(0)
970 {
971 connect(sender: this, signal: &QTcpServer::newConnection, receiver: this, slot: &TestTcpServer::onNewConnection);
972 QVERIFY(listen(QHostAddress::LocalHost));
973 }
974
975 int errorCodeReports;
976
977public slots:
978 void onNewConnection()
979 {
980 QTcpSocket *socket = nextPendingConnection();
981 if (!socket)
982 return;
983 // close socket instantly!
984 connect(sender: socket, signal: &QTcpSocket::readyRead, receiver: socket, slot: &QTcpSocket::close);
985 }
986
987 void onReply(QNetworkReply::NetworkError code)
988 {
989 QCOMPARE(code, QNetworkReply::RemoteHostClosedError);
990 ++errorCodeReports;
991 }
992};
993
994void tst_QHttpNetworkConnection::overlappingCloseAndWrite()
995{
996 // server accepts connections, but closes the socket instantly
997 TestTcpServer server;
998 QNetworkAccessManager accessManager;
999
1000 // ten requests are scheduled. All should result in an RemoteHostClosed...
1001 QUrl url;
1002 url.setScheme(QStringLiteral("http"));
1003 url.setHost(host: server.serverAddress().toString());
1004 url.setPort(server.serverPort());
1005 for (int i = 0; i < 10; ++i) {
1006 QNetworkRequest request(url);
1007 QNetworkReply *reply = accessManager.get(request);
1008 QObject::connect(sender: reply, signal: &QNetworkReply::errorOccurred,
1009 receiver: &server, slot: &TestTcpServer::onReply);
1010 }
1011
1012 QTRY_COMPARE(server.errorCodeReports, 10);
1013}
1014
1015
1016QTEST_MAIN(tst_QHttpNetworkConnection)
1017#include "tst_qhttpnetworkconnection.moc"
1018

source code of qtbase/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp