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#include <QtTest/QtTest>
30#include <QtNetwork/QtNetwork>
31#include <QtCore/QDateTime>
32#include <QtCore/QElapsedTimer>
33#include <QtCore/QTextStream>
34#include <QtCore/QRandomGenerator>
35#include <QtCore/QStandardPaths>
36#include <QtCore/private/qiodevice_p.h>
37
38#include "../../network-settings.h"
39
40class tst_NetworkSelfTest: public QObject
41{
42 Q_OBJECT
43 // This is either old server's address, or the new http
44 // server's address (different from ftp, for example):
45 QHostAddress cachedIpAddress;
46 // This is only for the new docker test server:
47 QHostAddress ftpServerIpAddress;
48public:
49 tst_NetworkSelfTest();
50 virtual ~tst_NetworkSelfTest();
51
52 QHostAddress serverIpAddress();
53
54private slots:
55 void initTestCase();
56 void hostTest();
57 void dnsResolution_data();
58 void dnsResolution();
59 void serverReachability();
60 void remotePortsOpen_data();
61 void remotePortsOpen();
62
63 // specific protocol tests
64 void ftpServer();
65 void ftpProxyServer();
66 void imapServer();
67 void httpServer();
68 void httpServerFiles_data();
69 void httpServerFiles();
70 void httpServerCGI_data();
71 void httpServerCGI();
72#ifndef QT_NO_SSL
73 void httpsServer();
74#endif
75 void httpProxy();
76 void httpProxyBasicAuth();
77 void httpProxyNtlmAuth();
78 void socks5Proxy();
79 void socks5ProxyAuth();
80 void smbServer();
81
82 // ssl supported test
83 void supportsSsl();
84};
85
86class Chat
87{
88public:
89 enum Type {
90 Reconnect,
91 Send,
92 Expect,
93 SkipBytes,
94 DiscardUntil,
95 DiscardUntilDisconnect,
96 Disconnect,
97 RemoteDisconnect,
98 StartEncryption
99 };
100 Chat(Type t, const QByteArray &d)
101 : data(d), type(t)
102 {
103 }
104 Chat(Type t, int val = 0)
105 : value(val), type(t)
106 {
107 }
108
109 static inline Chat send(const QByteArray &data)
110 { return Chat(Send, data); }
111 static inline Chat expect(const QByteArray &data)
112 { return Chat(Expect, data); }
113 static inline Chat discardUntil(const QByteArray &data)
114 { return Chat(DiscardUntil, data); }
115 static inline Chat skipBytes(int count)
116 { return Chat(SkipBytes, count); }
117
118 QByteArray data;
119 int value;
120 Type type;
121};
122
123static QString prettyByteArray(const QByteArray &array)
124{
125 // any control chars?
126 QString result;
127 result.reserve(asize: array.length() + array.length() / 3);
128 for (int i = 0; i < array.length(); ++i) {
129 char c = array.at(i);
130 switch (c) {
131 case '\n':
132 result += "\\n";
133 continue;
134 case '\r':
135 result += "\\r";
136 continue;
137 case '\t':
138 result += "\\t";
139 continue;
140 case '"':
141 result += "\\\"";
142 continue;
143 default:
144 break;
145 }
146
147 if (c < 0x20 || uchar(c) >= 0x7f) {
148 result += '\\';
149 result += QString::number(uchar(c), base: 8);
150 } else {
151 result += c;
152 }
153 }
154 return result;
155}
156
157enum { defaultReadTimeoutMS = 4000 };
158
159static bool doSocketRead(QTcpSocket *socket, int minBytesAvailable, int timeout = defaultReadTimeoutMS)
160{
161 QElapsedTimer timer;
162 timer.start();
163 int t = timeout;
164 forever {
165 if (socket->bytesAvailable() >= minBytesAvailable)
166 return true;
167 if (socket->state() == QAbstractSocket::UnconnectedState)
168 return false;
169 if (!socket->waitForReadyRead(msecs: t))
170 return false;
171 t = qt_subtract_from_timeout(timeout, elapsed: timer.elapsed());
172 if (t == 0)
173 return false;
174 }
175}
176
177static QByteArray msgDoSocketReadFailed(const QString &host, quint16 port,
178 int step, int minBytesAvailable)
179{
180 return "Failed to receive "
181 + QByteArray::number(minBytesAvailable) + " bytes from "
182 + host.toLatin1() + ':' + QByteArray::number(port)
183 + " in step " + QByteArray::number(step) + ": timeout";
184}
185
186static bool doSocketFlush(QTcpSocket *socket, int timeout = 4000)
187{
188#ifndef QT_NO_SSL
189 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(object: socket);
190#endif
191 QElapsedTimer timer;
192 timer.start();
193 int t = timeout;
194 forever {
195 if (socket->bytesToWrite() == 0
196#ifndef QT_NO_SSL
197 && sslSocket->encryptedBytesToWrite() == 0
198#endif
199 )
200 return true;
201 if (socket->state() == QAbstractSocket::UnconnectedState)
202 return false;
203 if (!socket->waitForBytesWritten(msecs: t))
204 return false;
205 t = qt_subtract_from_timeout(timeout, elapsed: timer.elapsed());
206 if (t == 0)
207 return false;
208 }
209}
210
211static void netChat(int port, const QList<Chat> &chat)
212{
213#ifndef QT_NO_SSL
214 QSslSocket socket;
215#else
216 QTcpSocket socket;
217#endif
218 const auto serverName = QtNetworkSettings::hostWithServiceOnPort(port);
219 socket.connectToHost(hostName: serverName, port);
220 qDebug() << 0 << "Connecting to server on port" << port;
221 QVERIFY2(socket.waitForConnected(10000),
222 QString("Failed to connect to server in step 0: %1").arg(socket.errorString()).toLocal8Bit());
223
224 // now start the chat
225 QList<Chat>::ConstIterator it = chat.constBegin();
226 for (int i = 1; it != chat.constEnd(); ++it, ++i) {
227 switch (it->type) {
228 case Chat::Expect: {
229 qDebug() << i << "Expecting" << prettyByteArray(array: it->data);
230 if (!doSocketRead(socket: &socket, minBytesAvailable: it->data.length(), timeout: 3 * defaultReadTimeoutMS))
231 QFAIL(msgDoSocketReadFailed(serverName, port, i, it->data.length()));
232
233 // pop that many bytes off the socket
234 QByteArray received = socket.read(maxlen: it->data.length());
235
236 // is it what we expected?
237 QVERIFY2(received == it->data,
238 QString("Did not receive expected data in step %1: data received was:\n%2")
239 .arg(i).arg(prettyByteArray(received)).toLocal8Bit());
240
241 break;
242 }
243
244 case Chat::DiscardUntil:
245 qDebug() << i << "Discarding until" << prettyByteArray(array: it->data);
246 while (true) {
247 // scan the buffer until we have our string
248 if (!doSocketRead(socket: &socket, minBytesAvailable: it->data.length()))
249 QFAIL(msgDoSocketReadFailed(serverName, port, i, it->data.length()));
250
251 QByteArray buffer;
252 buffer.resize(size: socket.bytesAvailable());
253 socket.peek(data: buffer.data(), maxlen: socket.bytesAvailable());
254
255 int pos = buffer.indexOf(a: it->data);
256 if (pos == -1) {
257 // data not found, keep trying
258 continue;
259 }
260
261 buffer = socket.read(maxlen: pos + it->data.length());
262 qDebug() << i << "Discarded" << prettyByteArray(array: buffer);
263 break;
264 }
265 break;
266
267 case Chat::SkipBytes: {
268 qDebug() << i << "Skipping" << it->value << "bytes";
269 if (!doSocketRead(socket: &socket, minBytesAvailable: it->value))
270 QFAIL(msgDoSocketReadFailed(serverName, port, i, it->value));
271
272 // now discard the bytes
273 QByteArray buffer = socket.read(maxlen: it->value);
274 qDebug() << i << "Skipped" << prettyByteArray(array: buffer);
275 break;
276 }
277
278 case Chat::Send: {
279 qDebug() << i << "Sending" << prettyByteArray(array: it->data);
280 socket.write(data: it->data);
281 if (!doSocketFlush(socket: &socket)) {
282 QVERIFY2(socket.state() == QAbstractSocket::ConnectedState,
283 QString("Socket disconnected while sending data in step %1").arg(i).toLocal8Bit());
284 QFAIL(QString("Failed to send data in step %1: timeout").arg(i).toLocal8Bit());
285 }
286 break;
287 }
288
289 case Chat::Disconnect:
290 qDebug() << i << "Disconnecting from host";
291 socket.disconnectFromHost();
292
293 // is this the last command?
294 if (it + 1 != chat.constEnd())
295 break;
296
297 Q_FALLTHROUGH();
298 case Chat::RemoteDisconnect:
299 case Chat::DiscardUntilDisconnect:
300 qDebug() << i << "Waiting for remote disconnect";
301 if (socket.state() != QAbstractSocket::UnconnectedState)
302 socket.waitForDisconnected(msecs: 10000);
303 QVERIFY2(socket.state() == QAbstractSocket::UnconnectedState,
304 QString("Socket did not disconnect as expected in step %1").arg(i).toLocal8Bit());
305
306 // any data left?
307 if (it->type == Chat::DiscardUntilDisconnect) {
308 QByteArray buffer = socket.readAll();
309 qDebug() << i << "Discarded in the process:" << prettyByteArray(array: buffer);
310 }
311
312 if (socket.bytesAvailable() != 0)
313 QFAIL(QString("Unexpected bytes still on buffer when disconnecting in step %1:\n%2")
314 .arg(i).arg(prettyByteArray(socket.readAll())).toLocal8Bit());
315 break;
316
317 case Chat::Reconnect:
318 qDebug() << i << "Reconnecting to server on port" << port;
319 socket.connectToHost(hostName: serverName, port);
320 QVERIFY2(socket.waitForConnected(10000),
321 QString("Failed to reconnect to server in step %1: %2").arg(i).arg(socket.errorString()).toLocal8Bit());
322 break;
323
324 case Chat::StartEncryption:
325#ifdef QT_NO_SSL
326 QFAIL("Internal error: SSL required for this test");
327#else
328 qDebug() << i << "Starting client encryption";
329 socket.ignoreSslErrors();
330 socket.startClientEncryption();
331 QVERIFY2(socket.waitForEncrypted(5000),
332 QString("Failed to start client encryption in step %1: %2").arg(i)
333 .arg(socket.errorString()).toLocal8Bit());
334 break;
335#endif
336 }
337 }
338}
339
340tst_NetworkSelfTest::tst_NetworkSelfTest()
341{
342}
343
344tst_NetworkSelfTest::~tst_NetworkSelfTest()
345{
346}
347
348QHostAddress tst_NetworkSelfTest::serverIpAddress()
349{
350 if (cachedIpAddress.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) {
351 // need resolving
352 QHostInfo resolved = QHostInfo::fromName(name: QtNetworkSettings::httpServerName());
353 if(resolved.error() != QHostInfo::NoError ||
354 resolved.addresses().isEmpty()) {
355 qWarning(msg: "QHostInfo::fromName failed (%d).", resolved.error());
356 return QHostAddress(QHostAddress::Null);
357 }
358 cachedIpAddress = resolved.addresses().first();
359 }
360 return cachedIpAddress;
361}
362
363void tst_NetworkSelfTest::initTestCase()
364{
365#if defined(QT_TEST_SERVER)
366 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::echoServerName(), 7));
367 // TODO: 'daytime' , port 13.
368 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpServerName(), 21));
369 const QHostInfo resolved = QHostInfo::fromName(QtNetworkSettings::ftpServerName());
370 if (resolved.error() == QHostInfo::NoError && !resolved.addresses().isEmpty())
371 ftpServerIpAddress = resolved.addresses().first();
372 // TODO: 'ssh', port 22.
373 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121));
374 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 80));
375 // TODO: 'smb', port 139.
376 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::imapServerName(), 143));
377 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpServerName(), 443));
378 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3128));
379 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3129));
380 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3130));
381 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::socksProxyServerName(), 1080));
382 QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::socksProxyServerName(), 1081));
383#else
384 if (!QtNetworkSettings::verifyTestNetworkSettings())
385 QSKIP("No network test server available");
386#endif
387}
388
389void tst_NetworkSelfTest::hostTest()
390{
391 // this is a localhost self-test
392 QHostInfo localhost = QHostInfo::fromName(name: "localhost");
393 QCOMPARE(localhost.error(), QHostInfo::NoError);
394 QVERIFY(!localhost.addresses().isEmpty());
395
396 QTcpServer server;
397 QVERIFY(server.listen());
398
399 QTcpSocket socket;
400 socket.connectToHost(hostName: "127.0.0.1", port: server.serverPort());
401 QVERIFY(socket.waitForConnected(10000));
402}
403
404void tst_NetworkSelfTest::dnsResolution_data()
405{
406 QTest::addColumn<QString>(name: "hostName");
407 QTest::newRow(dataTag: "local-name") << QtNetworkSettings::serverLocalName();
408 QTest::newRow(dataTag: "fqdn") << QtNetworkSettings::httpServerName();
409}
410
411void tst_NetworkSelfTest::dnsResolution()
412{
413 QFETCH(QString, hostName);
414 QHostInfo resolved = QHostInfo::fromName(name: hostName);
415 QVERIFY2(resolved.error() == QHostInfo::NoError,
416 QString("Failed to resolve hostname %1: %2").arg(hostName, resolved.errorString()).toLocal8Bit());
417 QVERIFY2(resolved.addresses().size() > 0, "Got 0 addresses for server IP");
418
419 cachedIpAddress = resolved.addresses().first();
420}
421
422void tst_NetworkSelfTest::serverReachability()
423{
424 // check that we get a proper error connecting to port 12346
425 QTcpSocket socket;
426 socket.connectToHost(hostName: QtNetworkSettings::httpServerName(), port: 12346);
427
428 QElapsedTimer timer;
429 timer.start();
430 socket.waitForConnected(msecs: 10000);
431 QVERIFY2(timer.elapsed() < 9900, "Connection to closed port timed out instead of refusing, something is wrong");
432
433 QVERIFY2(socket.state() == QAbstractSocket::UnconnectedState, "Socket connected unexpectedly!");
434 QVERIFY2(socket.error() == QAbstractSocket::ConnectionRefusedError,
435 QString("Could not reach server: %1").arg(socket.errorString()).toLocal8Bit());
436}
437
438void tst_NetworkSelfTest::remotePortsOpen_data()
439{
440#if defined(QT_TEST_SERVER)
441 QSKIP("Skipping, for the docker test server already tested by initTestCase()");
442#endif
443 QTest::addColumn<int>(name: "portNumber");
444
445 QTest::newRow(dataTag: "echo") << 7;
446 QTest::newRow(dataTag: "daytime") << 13;
447 QTest::newRow(dataTag: "ftp") << 21;
448 QTest::newRow(dataTag: "ssh") << 22;
449 QTest::newRow(dataTag: "imap") << 143;
450 QTest::newRow(dataTag: "http") << 80;
451 QTest::newRow(dataTag: "https") << 443;
452 QTest::newRow(dataTag: "http-proxy") << 3128;
453 QTest::newRow(dataTag: "http-proxy-auth-basic") << 3129;
454 QTest::newRow(dataTag: "http-proxy-auth-ntlm") << 3130;
455 QTest::newRow(dataTag: "socks5-proxy") << 1080;
456 QTest::newRow(dataTag: "socks5-proxy-auth") << 1081;
457 QTest::newRow(dataTag: "ftp-proxy") << 2121;
458 QTest::newRow(dataTag: "smb") << 139;
459}
460
461void tst_NetworkSelfTest::remotePortsOpen()
462{
463 QFETCH(const int, portNumber);
464
465 QTcpSocket socket;
466 socket.connectToHost(hostName: QtNetworkSettings::hostWithServiceOnPort(port: portNumber), port: quint16(portNumber));
467
468 if (!socket.waitForConnected(msecs: 10000)) {
469 if (socket.error() == QAbstractSocket::SocketTimeoutError)
470 QFAIL(QString("Network timeout connecting to the server on port %1").arg(portNumber).toLocal8Bit());
471 else
472 QFAIL(QString("Error connecting to server on port %1: %2").arg(portNumber).arg(socket.errorString()).toLocal8Bit());
473 }
474 QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
475}
476
477static QList<Chat> ftpChat(const QByteArray &userSuffix = QByteArray())
478{
479 QList<Chat> rv;
480 rv << Chat::expect(data: "220")
481 << Chat::discardUntil(data: "\r\n")
482 << Chat::send(data: "USER anonymous" + userSuffix + "\r\n")
483 << Chat::expect(data: "331")
484 << Chat::discardUntil(data: "\r\n")
485 << Chat::send(data: "PASS user@hostname\r\n")
486 << Chat::expect(data: "230")
487 << Chat::discardUntil(data: "\r\n")
488 << Chat::send(data: "CWD pub\r\n")
489 << Chat::expect(data: "250")
490 << Chat::discardUntil(data: "\r\n")
491 << Chat::send(data: "CWD dir-not-readable\r\n")
492 << Chat::expect(data: "550")
493 << Chat::discardUntil(data: "\r\n")
494 << Chat::send(data: "PWD\r\n")
495#if defined(QT_TEST_SERVER)
496 << Chat::expect("257 \"/pub\" is the current directory\r\n")
497#else
498 << Chat::expect(data: "257 \"/pub\"\r\n")
499#endif
500 << Chat::send(data: "SIZE file-not-readable.txt\r\n")
501 << Chat::expect(data: "213 41\r\n")
502 << Chat::send(data: "CWD qxmlquery\r\n")
503 << Chat::expect(data: "250")
504 << Chat::discardUntil(data: "\r\n")
505 << Chat::send(data: "CWD /qtest\r\n")
506 << Chat::expect(data: "250")
507 << Chat::discardUntil(data: "\r\n")
508 << Chat::send(data: "SIZE bigfile\r\n")
509 << Chat::expect(data: "213 519240\r\n")
510 << Chat::send(data: "SIZE rfc3252\r\n")
511 << Chat::expect(data: "213 25962\r\n")
512 << Chat::send(data: "SIZE rfc3252.txt\r\n")
513 << Chat::expect(data: "213 25962\r\n")
514 << Chat::send(data: "QUIT\r\n");
515
516 rv << Chat::expect(data: "221")
517 << Chat::discardUntil(data: "\r\n");
518
519 rv << Chat::RemoteDisconnect;
520 return rv;
521}
522
523void tst_NetworkSelfTest::ftpServer()
524{
525 netChat(port: 21, chat: ftpChat());
526}
527
528void tst_NetworkSelfTest::ftpProxyServer()
529{
530 netChat(port: 2121, chat: ftpChat(userSuffix: "@" + QtNetworkSettings::ftpServerName().toLatin1()));
531}
532
533void tst_NetworkSelfTest::imapServer()
534{
535 netChat(port: 143, chat: QList<Chat>()
536 << Chat::expect(data: "* OK ")
537 << Chat::discardUntil(data: "\r\n")
538 << Chat::send(data: "1 CAPABILITY\r\n")
539 << Chat::expect(data: "* CAPABILITY ")
540 << Chat::discardUntil(data: "1 OK")
541 << Chat::discardUntil(data: "\r\n")
542 << Chat::send(data: "2 LOGOUT\r\n")
543 << Chat::discardUntil(data: "2 OK")
544 << Chat::discardUntil(data: "\r\n")
545 << Chat::RemoteDisconnect);
546}
547
548void tst_NetworkSelfTest::httpServer()
549{
550 QByteArray uniqueExtension = QByteArray::number((qulonglong)this) +
551 QByteArray::number((qulonglong)QRandomGenerator::global()->generate()) +
552 QByteArray::number(QDateTime::currentSecsSinceEpoch());
553
554 netChat(port: 80, chat: QList<Chat>()
555 // HTTP/0.9 chat:
556 << Chat::send(data: "GET /\r\n")
557 << Chat::DiscardUntilDisconnect
558
559 // HTTP/1.0 chat:
560 << Chat::Reconnect
561 << Chat::send(data: "GET / HTTP/1.0\r\n"
562 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
563 "Connection: close\r\n"
564 "\r\n")
565 << Chat::expect(data: "HTTP/1.")
566 << Chat::discardUntil(data: " ")
567 << Chat::expect(data: "200 ")
568 << Chat::DiscardUntilDisconnect
569
570 // HTTP/1.0 POST:
571 << Chat::Reconnect
572 << Chat::send(data: "POST / HTTP/1.0\r\n"
573 "Content-Length: 5\r\n"
574 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
575 "Connection: close\r\n"
576 "\r\n"
577 "Hello")
578 << Chat::expect(data: "HTTP/1.")
579 << Chat::discardUntil(data: " ")
580 << Chat::expect(data: "200 ")
581 << Chat::DiscardUntilDisconnect
582
583 // HTTP protected area
584 << Chat::Reconnect
585 << Chat::send(data: "GET /qtest/protected/rfc3252.txt HTTP/1.0\r\n"
586 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
587 "Connection: close\r\n"
588 "\r\n")
589 << Chat::expect(data: "HTTP/1.")
590 << Chat::discardUntil(data: " ")
591 << Chat::expect(data: "401 ")
592 << Chat::DiscardUntilDisconnect
593
594 << Chat::Reconnect
595 << Chat::send(data: "HEAD /qtest/protected/rfc3252.txt HTTP/1.0\r\n"
596 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
597 "Connection: close\r\n"
598 "Authorization: Basic cXNvY2tzdGVzdDpwYXNzd29yZA==\r\n"
599 "\r\n")
600 << Chat::expect(data: "HTTP/1.")
601 << Chat::discardUntil(data: " ")
602 << Chat::expect(data: "200 ")
603 << Chat::DiscardUntilDisconnect
604
605 // DAV area
606 << Chat::Reconnect
607 << Chat::send(data: "HEAD /dav/ HTTP/1.0\r\n"
608 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
609 "Connection: close\r\n"
610 "\r\n")
611 << Chat::expect(data: "HTTP/1.")
612 << Chat::discardUntil(data: " ")
613 << Chat::expect(data: "200 ")
614 << Chat::DiscardUntilDisconnect
615
616 // HTTP/1.0 PUT
617 << Chat::Reconnect
618 << Chat::send(data: "PUT /dav/networkselftest-" + uniqueExtension + ".txt HTTP/1.0\r\n"
619 "Content-Length: 5\r\n"
620 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
621 "Connection: close\r\n"
622 "\r\n"
623 "Hello")
624 << Chat::expect(data: "HTTP/1.")
625 << Chat::discardUntil(data: " ")
626 << Chat::expect(data: "201 ")
627 << Chat::DiscardUntilDisconnect
628
629 // check that the file did get uploaded
630 << Chat::Reconnect
631 << Chat::send(data: "HEAD /dav/networkselftest-" + uniqueExtension + ".txt HTTP/1.0\r\n"
632 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
633 "Connection: close\r\n"
634 "\r\n")
635 << Chat::expect(data: "HTTP/1.")
636 << Chat::discardUntil(data: " ")
637 << Chat::expect(data: "200 ")
638 << Chat::discardUntil(data: "\r\nContent-Length: 5\r\n")
639 << Chat::DiscardUntilDisconnect
640
641 // HTTP/1.0 DELETE
642 << Chat::Reconnect
643 << Chat::send(data: "DELETE /dav/networkselftest-" + uniqueExtension + ".txt HTTP/1.0\r\n"
644 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
645 "Connection: close\r\n"
646 "\r\n")
647 << Chat::expect(data: "HTTP/1.")
648 << Chat::discardUntil(data: " ")
649 << Chat::expect(data: "204 ")
650 << Chat::DiscardUntilDisconnect
651 );
652}
653
654void tst_NetworkSelfTest::httpServerFiles_data()
655{
656 QTest::addColumn<QString>(name: "uri");
657 QTest::addColumn<int>(name: "size");
658
659 QTest::newRow(dataTag: "fluke.gif") << "/qtest/fluke.gif" << -1;
660 QTest::newRow(dataTag: "bigfile") << "/qtest/bigfile" << 519240;
661 QTest::newRow(dataTag: "rfc3252.txt") << "/qtest/rfc3252.txt" << 25962;
662 QTest::newRow(dataTag: "protected/rfc3252.txt") << "/qtest/protected/rfc3252.txt" << 25962;
663 QTest::newRow(dataTag: "completelyEmptyQuery.xq") << "/qtest/qxmlquery/completelyEmptyQuery.xq" << -1;
664 QTest::newRow(dataTag: "notWellformedViaHttps.xml") << "/qtest/qxmlquery/notWellformedViaHttps.xml" << -1;
665 QTest::newRow(dataTag: "notWellformed.xml") << "/qtest/qxmlquery/notWellformed.xml" << -1;
666 QTest::newRow(dataTag: "viaHttp.xq") << "/qtest/qxmlquery/viaHttp.xq" << -1;
667 QTest::newRow(dataTag: "wellFormedViaHttps.xml") << "/qtest/qxmlquery/wellFormedViaHttps.xml" << -1;
668 QTest::newRow(dataTag: "wellFormed.xml") << "/qtest/qxmlquery/wellFormed.xml" << -1;
669}
670
671void tst_NetworkSelfTest::httpServerFiles()
672{
673 QFETCH(QString, uri);
674 QFETCH(int, size);
675 QUrl url(uri);
676
677 QList<Chat> chat;
678 chat << Chat::send(data: "HEAD " + url.toEncoded() + " HTTP/1.0\r\n"
679 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
680 "Connection: close\r\n"
681 "Authorization: Basic cXNvY2tzdGVzdDpwYXNzd29yZA==\r\n"
682 "\r\n")
683 << Chat::expect(data: "HTTP/1.")
684 << Chat::skipBytes(count: 1) // HTTP/1.0 or 1.1 reply
685 << Chat::expect(data: " 200 ");
686 if (size != -1)
687 chat << Chat::discardUntil(data: "\r\nContent-Length: " + QByteArray::number(size) + "\r\n");
688 chat << Chat::DiscardUntilDisconnect;
689 netChat(port: 80, chat);
690}
691
692void tst_NetworkSelfTest::httpServerCGI_data()
693{
694 QTest::addColumn<QByteArray>(name: "request");
695 QTest::addColumn<QByteArray>(name: "result");
696 QTest::addColumn<QByteArray>(name: "additionalHeader");
697
698 QTest::newRow(dataTag: "echo.cgi")
699 << QByteArray("GET /qtest/cgi-bin/echo.cgi?Hello+World HTTP/1.0\r\n"
700 "Connection: close\r\n"
701 "\r\n")
702 << QByteArray("Hello+World")
703 << QByteArray();
704
705 QTest::newRow(dataTag: "echo.cgi(POST)")
706 << QByteArray("POST /qtest/cgi-bin/echo.cgi?Hello+World HTTP/1.0\r\n"
707 "Connection: close\r\n"
708 "Content-Length: 15\r\n"
709 "\r\n"
710 "Hello, World!\r\n")
711 << QByteArray("Hello, World!\r\n")
712 << QByteArray();
713
714 QTest::newRow(dataTag: "md5sum.cgi")
715 << QByteArray("POST /qtest/cgi-bin/md5sum.cgi HTTP/1.0\r\n"
716 "Connection: close\r\n"
717 "Content-Length: 15\r\n"
718 "\r\n"
719 "Hello, World!\r\n")
720 << QByteArray("29b933a8d9a0fcef0af75f1713f4940e\n")
721 << QByteArray();
722
723 QTest::newRow(dataTag: "protected/md5sum.cgi")
724 << QByteArray("POST /qtest/protected/cgi-bin/md5sum.cgi HTTP/1.0\r\n"
725 "Connection: close\r\n"
726 "Authorization: Basic cXNvY2tzdGVzdDpwYXNzd29yZA==\r\n"
727 "Content-Length: 15\r\n"
728 "\r\n"
729 "Hello, World!\r\n")
730 << QByteArray("29b933a8d9a0fcef0af75f1713f4940e\n")
731 << QByteArray();
732
733 QTest::newRow(dataTag: "set-cookie.cgi")
734 << QByteArray("POST /qtest/cgi-bin/set-cookie.cgi HTTP/1.0\r\n"
735 "Connection: close\r\n"
736 "Content-Length: 8\r\n"
737 "\r\n"
738 "foo=bar\n")
739 << QByteArray("Success\n")
740 << QByteArray("\r\nSet-Cookie: foo=bar\r\n");
741}
742
743void tst_NetworkSelfTest::httpServerCGI()
744{
745 QFETCH(QByteArray, request);
746 QFETCH(QByteArray, result);
747 QFETCH(QByteArray, additionalHeader);
748 QList<Chat> chat;
749 chat << Chat::send(data: request)
750 << Chat::expect(data: "HTTP/1.") << Chat::skipBytes(count: 1)
751 << Chat::expect(data: " 200 ");
752
753 if (!additionalHeader.isEmpty())
754 chat << Chat::discardUntil(data: additionalHeader);
755
756 chat << Chat::discardUntil(data: "\r\n\r\n")
757 << Chat::expect(data: result)
758 << Chat::RemoteDisconnect;
759 netChat(port: 80, chat);
760}
761
762#ifndef QT_NO_SSL
763void tst_NetworkSelfTest::httpsServer()
764{
765 netChat(port: 443, chat: QList<Chat>()
766 << Chat::StartEncryption
767 << Chat::send(data: "GET / HTTP/1.0\r\n"
768 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
769 "Connection: close\r\n"
770 "\r\n")
771 << Chat::expect(data: "HTTP/1.")
772 << Chat::discardUntil(data: " ")
773 << Chat::expect(data: "200 ")
774 << Chat::DiscardUntilDisconnect);
775}
776#endif
777
778void tst_NetworkSelfTest::httpProxy()
779{
780 netChat(port: 3128, chat: QList<Chat>()
781 // proxy GET by IP
782 << Chat::send(data: "GET http://" + serverIpAddress().toString().toLatin1() + "/ HTTP/1.0\r\n"
783 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
784 "Proxy-connection: close\r\n"
785 "\r\n")
786 << Chat::expect(data: "HTTP/1.")
787 << Chat::discardUntil(data: " ")
788 << Chat::expect(data: "200 ")
789 << Chat::DiscardUntilDisconnect
790
791 // proxy GET by hostname
792 << Chat::Reconnect
793 << Chat::send(data: "GET http://" + QtNetworkSettings::httpServerName().toLatin1() + "/ HTTP/1.0\r\n"
794 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
795 "Proxy-connection: close\r\n"
796 "\r\n")
797 << Chat::expect(data: "HTTP/1.")
798 << Chat::discardUntil(data: " ")
799 << Chat::expect(data: "200 ")
800 << Chat::DiscardUntilDisconnect
801
802 // proxy CONNECT by IP
803 << Chat::Reconnect
804#if !defined(QT_TEST_SERVER)
805 << Chat::send(data: "CONNECT " + serverIpAddress().toString().toLatin1() + ":21 HTTP/1.0\r\n"
806 "\r\n")
807#else
808 << Chat::send("CONNECT " + ftpServerIpAddress.toString().toLatin1() + ":21 HTTP/1.0\r\n"
809 "\r\n")
810#endif
811 << Chat::expect(data: "HTTP/1.")
812 << Chat::discardUntil(data: " ")
813 << Chat::expect(data: "200 ")
814 << Chat::discardUntil(data: "\r\n\r\n")
815 << ftpChat()
816
817 // proxy CONNECT by hostname
818 << Chat::Reconnect
819 << Chat::send(data: "CONNECT " + QtNetworkSettings::ftpServerName().toLatin1() + ":21 HTTP/1.0\r\n"
820 "\r\n")
821 << Chat::expect(data: "HTTP/1.")
822 << Chat::discardUntil(data: " ")
823 << Chat::expect(data: "200 ")
824 << Chat::discardUntil(data: "\r\n\r\n")
825 << ftpChat()
826 );
827}
828
829void tst_NetworkSelfTest::httpProxyBasicAuth()
830{
831 netChat(port: 3129, chat: QList<Chat>()
832 // test auth required response
833 << Chat::send(data: "GET http://" + QtNetworkSettings::httpServerName().toLatin1() + "/ HTTP/1.0\r\n"
834 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
835 "Proxy-connection: close\r\n"
836 "\r\n")
837 << Chat::expect(data: "HTTP/1.")
838 << Chat::discardUntil(data: " ")
839 << Chat::expect(data: "407 ")
840 << Chat::discardUntil(data: "\r\nProxy-Authenticate: Basic realm=\"")
841 << Chat::DiscardUntilDisconnect
842
843 // now try sending our credentials
844 << Chat::Reconnect
845 << Chat::send(data: "GET http://" + QtNetworkSettings::httpServerName().toLatin1() + "/ HTTP/1.0\r\n"
846 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
847 "Proxy-connection: close\r\n"
848 "Proxy-Authorization: Basic cXNvY2tzdGVzdDpwYXNzd29yZA==\r\n"
849 "\r\n")
850 << Chat::expect(data: "HTTP/1.")
851 << Chat::discardUntil(data: " ")
852 << Chat::expect(data: "200 ")
853 << Chat::DiscardUntilDisconnect);
854}
855
856void tst_NetworkSelfTest::httpProxyNtlmAuth()
857{
858 netChat(port: 3130, chat: QList<Chat>()
859 // test auth required response
860 << Chat::send(data: "GET http://" + QtNetworkSettings::httpServerName().toLatin1() + "/ HTTP/1.0\r\n"
861 "Host: " + QtNetworkSettings::httpServerName().toLatin1() + "\r\n"
862#if !defined(QT_TEST_SERVER)
863 "Proxy-connection: keep-alive\r\n" // NTLM auth will disconnect
864#else
865 "Proxy-connection: close\r\n" // Well, what do you know? It keeps it alive!
866#endif
867 "\r\n")
868 << Chat::expect(data: "HTTP/1.")
869 << Chat::discardUntil(data: " ")
870 << Chat::expect(data: "407 ")
871 << Chat::discardUntil(data: "\r\nProxy-Authenticate: NTLM\r\n")
872 << Chat::DiscardUntilDisconnect
873 );
874}
875
876// SOCKSv5 is a binary protocol
877static const char handshakeNoAuth[] = "\5\1\0";
878static const char handshakeOkNoAuth[] = "\5\0";
879static const char handshakeAuthPassword[] = "\5\1\2\1\12qsockstest\10password";
880static const char handshakeOkPasswdAuth[] = "\5\2\1\0";
881static const char handshakeAuthNotOk[] = "\5\377";
882static const char connect1[] = "\5\1\0\1\177\0\0\1\0\25"; // Connect IPv4 127.0.0.1 port 21
883static const char connect1a[] = "\5\1\0\1"; // just "Connect to IPv4"
884static const char connect1b[] = "\0\25"; // just "port 21"
885static const char connect2[] = "\5\1\0\3\11localhost\0\25"; // Connect hostname localhost 21
886static const char connect2a[] = "\5\1\0\3"; // just "Connect to hostname"
887static const char connected[] = "\5\0\0";
888
889#define QBA(x) (QByteArray::fromRawData(x, int(sizeof(x)) - 1))
890
891void tst_NetworkSelfTest::socks5Proxy()
892{
893 union {
894 char buf[4];
895 quint32 data;
896 } ip4Address;
897 ip4Address.data =
898#if !defined(QT_TEST_SERVER)
899 qToBigEndian(source: serverIpAddress().toIPv4Address());
900#else
901 qToBigEndian(ftpServerIpAddress.toIPv4Address());
902#endif
903 const QByteArray handshakeNoAuthData = QByteArray(handshakeNoAuth, int(sizeof handshakeNoAuth) - 1);
904 const QByteArray handshakeOkNoAuthData = QByteArray(handshakeOkNoAuth, int(sizeof handshakeOkNoAuth) - 1);
905 const QByteArray connect1Data = QByteArray(connect1, int(sizeof connect1) - 1);
906 const QByteArray connectedData = QByteArray(connected, int(sizeof connected) - 1);
907 const QByteArray connect2Data = QByteArray(connect2, int(sizeof connect2) - 1);
908
909 netChat(port: 1080, chat: QList<Chat>()
910 // IP address connection
911#if !defined(QT_TEST_SERVER)
912 // This test relies on the proxy and ftp servers
913 // running on the same machine, which is not the
914 // case for the docker test server.
915 << Chat::send(data: handshakeNoAuthData)
916 << Chat::expect(data: handshakeOkNoAuthData)
917 << Chat::send(data: connect1Data)
918 << Chat::expect(data: connectedData)
919 << Chat::expect(data: "\1") // IPv4 address following
920 << Chat::skipBytes(count: 6) // the server's local address and port
921 << ftpChat()
922
923 // connect by IP
924 << Chat::Reconnect
925#endif
926 << Chat::send(data: handshakeNoAuthData)
927 << Chat::expect(data: handshakeOkNoAuthData)
928 << Chat::send(QBA(connect1a) + QByteArray::fromRawData(ip4Address.buf, size: 4) + QBA(connect1b))
929 << Chat::expect(data: connectedData)
930 << Chat::expect(data: "\1") // IPv4 address following
931 << Chat::skipBytes(count: 6) // the server's local address and port
932 << ftpChat()
933#if !defined(QT_TEST_SERVER)
934 // connect to "localhost" by hostname, the same as above:
935 // makes no sense with the docker test server.
936 << Chat::Reconnect
937 << Chat::send(data: handshakeNoAuthData)
938 << Chat::expect(data: handshakeOkNoAuthData)
939 << Chat::send(data: connect2Data)
940 << Chat::expect(data: connectedData)
941 << Chat::expect(data: "\1") // IPv4 address following
942 << Chat::skipBytes(count: 6) // the server's local address and port
943 << ftpChat()
944#endif
945 // connect to server by its official name
946 << Chat::Reconnect
947 << Chat::send(data: handshakeNoAuthData)
948 << Chat::expect(data: handshakeOkNoAuthData)
949 << Chat::send(QBA(connect2a) + char(QtNetworkSettings::ftpServerName().size()) + QtNetworkSettings::ftpServerName().toLatin1() + QBA(connect1b))
950 << Chat::expect(data: connectedData)
951 << Chat::expect(data: "\1") // IPv4 address following
952 << Chat::skipBytes(count: 6) // the server's local address and port
953 << ftpChat()
954 );
955}
956
957void tst_NetworkSelfTest::socks5ProxyAuth()
958{
959 const QByteArray handshakeNoAuthData = QByteArray(handshakeNoAuth, int(sizeof handshakeNoAuth) - 1);
960 const QByteArray connect1Data = QByteArray(connect1, int(sizeof connect1) - 1);
961 const QByteArray connectedData = QByteArray(connected, int(sizeof connected) - 1);
962 const QByteArray handshakeAuthNotOkData = QByteArray(handshakeAuthNotOk, int(sizeof(handshakeAuthNotOk)) - 1);
963 const QByteArray handshakeAuthPasswordData = QByteArray(handshakeAuthPassword, int(sizeof(handshakeAuthPassword)) - 1);
964 const QByteArray handshakeOkPasswdAuthData = QByteArray(handshakeOkPasswdAuth, int(sizeof(handshakeOkPasswdAuth)) - 1);
965
966 netChat(port: 1081, chat: QList<Chat>()
967 // unauthenticated connect -- will get error
968 << Chat::send(data: handshakeNoAuthData)
969 << Chat::expect(data: handshakeAuthNotOkData)
970 << Chat::RemoteDisconnect
971#if !defined(QT_TEST_SERVER)
972 // now try to connect with authentication,
973 // danted is just that, socks 5 proxy and
974 // does not have ftp running, skip!
975 << Chat::Reconnect
976 << Chat::send(data: handshakeAuthPasswordData)
977 << Chat::expect(data: handshakeOkPasswdAuthData)
978 << Chat::send(data: connect1Data)
979 << Chat::expect(data: connectedData)
980 << Chat::expect(data: "\1") // IPv4 address following
981 << Chat::skipBytes(count: 6) // the server's local address and port
982 << ftpChat()
983#else
984 << Chat::Reconnect
985 << Chat::send(handshakeAuthPasswordData)
986 << Chat::expect(handshakeOkPasswdAuthData)
987 << Chat::send(QBA(connect2a) + char(QtNetworkSettings::ftpServerName().size()) + QtNetworkSettings::ftpServerName().toLatin1() + QBA(connect1b))
988 << Chat::expect(connectedData)
989 << Chat::expect("\1") // IPv4 address following
990 << Chat::skipBytes(6) // the server's local address and port
991 << ftpChat()
992#endif
993 );
994}
995
996void tst_NetworkSelfTest::supportsSsl()
997{
998#ifdef QT_NO_SSL
999 QSKIP("SSL not compiled in");
1000#else
1001 QVERIFY2(QSslSocket::supportsSsl(), "Could not load SSL libraries");
1002#endif
1003}
1004
1005#if QT_CONFIG(process)
1006static const QByteArray msgProcessError(const QProcess &process, const char *what)
1007{
1008 QString result;
1009 QTextStream(&result) << what << ": \"" << process.program() << ' '
1010 << process.arguments().join(sep: QLatin1Char(' ')) << "\": " << process.errorString();
1011 return result.toLocal8Bit();
1012}
1013
1014static void ensureTermination(QProcess &process)
1015{
1016 if (process.state() == QProcess::Running) {
1017 process.terminate();
1018 if (!process.waitForFinished(msecs: 300))
1019 process.kill();
1020 }
1021}
1022#endif // QT_CONFIG(process)
1023
1024void tst_NetworkSelfTest::smbServer()
1025{
1026#if defined(QT_TEST_SERVER)
1027 QSKIP("Not supported by the docker test server");
1028#endif // QT_TEST_SERVER
1029 static const char contents[] = "This is 34 bytes. Do not change...";
1030#ifdef Q_OS_WIN
1031 // use Windows's native UNC support to try and open a file on the server
1032 QByteArray filepath = "\\\\" + QtNetworkSettings::winServerName().toLatin1() + "\\testshare\\test.pri";
1033 FILE *f = fopen(filepath.constData(), "rb");
1034 QVERIFY2(f, qt_error_string().toLocal8Bit());
1035
1036 char buf[128];
1037 size_t ret = fread(buf, 1, sizeof buf, f);
1038 fclose(f);
1039
1040 QCOMPARE(ret, strlen(contents));
1041 QVERIFY(memcmp(buf, contents, strlen(contents)) == 0);
1042#else
1043#if QT_CONFIG(process)
1044 enum { sambaTimeOutSecs = 5 };
1045 // try to use Samba
1046 const QString progname = "smbclient";
1047 const QString binary = QStandardPaths::findExecutable(executableName: progname);
1048 if (binary.isEmpty())
1049 QSKIP("Could not find smbclient (from Samba), cannot continue testing");
1050
1051 // try listing the server
1052 const QStringList timeOutArguments = QStringList()
1053 << "--timeout" << QString::number(sambaTimeOutSecs);
1054 QStringList arguments = timeOutArguments;
1055 arguments << "-g" << "-N" << "-L" << QtNetworkSettings::winServerName();
1056 QProcess smbclient;
1057 smbclient.start(program: binary, arguments, mode: QIODevice::ReadOnly);
1058 QVERIFY2(smbclient.waitForStarted(), msgProcessError(smbclient, "Unable to start"));
1059 const bool listFinished = smbclient.waitForFinished(msecs: (1 + sambaTimeOutSecs) * 1000);
1060 ensureTermination(process&: smbclient);
1061 QVERIFY2(listFinished, msgProcessError(smbclient, "Listing servers timed out"));
1062 if (smbclient.exitStatus() != QProcess::NormalExit)
1063 QSKIP("smbclient crashed");
1064 QVERIFY2(smbclient.exitCode() == 0, "Test server not found");
1065
1066 QByteArray output = smbclient.readAll();
1067 QVERIFY(output.contains("Disk|testshare|"));
1068 QVERIFY(output.contains("Disk|testsharewritable|"));
1069 QVERIFY(output.contains("Disk|testsharelargefile|"));
1070 qDebug() << "Test server found and shares are correct";
1071
1072 // try getting a file
1073 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
1074 env.insert(name: "PAGER", value: "/bin/cat"); // just in case
1075 smbclient.setProcessEnvironment(env);
1076 arguments = timeOutArguments;
1077 arguments << "-N" << "-c" << "more test.pri"
1078 << ("\\\\" + QtNetworkSettings::winServerName() + "\\testshare");
1079 smbclient.start(program: binary, arguments, mode: QIODevice::ReadOnly);
1080 const bool fileFinished = smbclient.waitForFinished(msecs: (1 + sambaTimeOutSecs) * 1000);
1081 ensureTermination(process&: smbclient);
1082 QVERIFY2(fileFinished, msgProcessError(smbclient, "Timed out"));
1083 if (smbclient.exitStatus() != QProcess::NormalExit)
1084 QSKIP("smbclient crashed");
1085 QVERIFY2(smbclient.exitCode() == 0, "File //qt-test-server/testshare/test.pri not found");
1086
1087 output = smbclient.readAll();
1088 QCOMPARE(output.constData(), contents);
1089 qDebug() << "Test file is correct";
1090#else
1091 QSKIP( "No QProcess support", SkipAll);
1092#endif
1093#endif
1094}
1095
1096QTEST_MAIN(tst_NetworkSelfTest)
1097#include "tst_networkselftest.moc"
1098

source code of qtbase/tests/auto/other/networkselftest/tst_networkselftest.cpp