1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> |
4 | ** Copyright (C) 2016 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | |
31 | #include <QtTest/QtTest> |
32 | #include <QtNetwork/QDnsLookup> |
33 | #include <QtNetwork/QHostAddress> |
34 | |
35 | static const int Timeout = 15000; // 15s |
36 | |
37 | class tst_QDnsLookup: public QObject |
38 | { |
39 | Q_OBJECT |
40 | |
41 | QString domainName(const QString &input); |
42 | QString domainNameList(const QString &input); |
43 | QStringList domainNameListAlternatives(const QString &input); |
44 | public slots: |
45 | void initTestCase(); |
46 | |
47 | private slots: |
48 | void lookup_data(); |
49 | void lookup(); |
50 | void lookupReuse(); |
51 | void lookupAbortRetry(); |
52 | }; |
53 | |
54 | void tst_QDnsLookup::initTestCase() |
55 | { |
56 | QTest::addColumn<QString>(name: "tld" ); |
57 | QTest::newRow(dataTag: "normal" ) << ".test.qt-project.org" ; |
58 | QTest::newRow(dataTag: "idn" ) << ".alqualond\xc3\xab.test.qt-project.org" ; |
59 | } |
60 | |
61 | QString tst_QDnsLookup::domainName(const QString &input) |
62 | { |
63 | if (input.isEmpty()) |
64 | return input; |
65 | |
66 | if (input.endsWith(c: QLatin1Char('.'))) { |
67 | QString nodot = input; |
68 | nodot.chop(n: 1); |
69 | return nodot; |
70 | } |
71 | |
72 | QFETCH_GLOBAL(QString, tld); |
73 | return input + tld; |
74 | } |
75 | |
76 | QString tst_QDnsLookup::domainNameList(const QString &input) |
77 | { |
78 | QStringList list = input.split(sep: QLatin1Char(';')); |
79 | QString result; |
80 | foreach (const QString &s, list) { |
81 | if (!result.isEmpty()) |
82 | result += ';'; |
83 | result += domainName(input: s); |
84 | } |
85 | return result; |
86 | } |
87 | |
88 | QStringList tst_QDnsLookup::domainNameListAlternatives(const QString &input) |
89 | { |
90 | QStringList alternatives = input.split(sep: '|'); |
91 | for (int i = 0; i < alternatives.length(); ++i) |
92 | alternatives[i] = domainNameList(input: alternatives[i]); |
93 | return alternatives; |
94 | } |
95 | |
96 | void tst_QDnsLookup::lookup_data() |
97 | { |
98 | QTest::addColumn<int>(name: "type" ); |
99 | QTest::addColumn<QString>(name: "domain" ); |
100 | QTest::addColumn<int>(name: "error" ); |
101 | QTest::addColumn<QString>(name: "cname" ); |
102 | QTest::addColumn<QString>(name: "host" ); |
103 | QTest::addColumn<QString>(name: "mx" ); |
104 | QTest::addColumn<QString>(name: "ns" ); |
105 | QTest::addColumn<QString>(name: "ptr" ); |
106 | QTest::addColumn<QString>(name: "srv" ); |
107 | QTest::addColumn<QString>(name: "txt" ); |
108 | |
109 | QTest::newRow(dataTag: "a-empty" ) << int(QDnsLookup::A) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
110 | QTest::newRow(dataTag: "a-notfound" ) << int(QDnsLookup::A) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
111 | QTest::newRow(dataTag: "a-single" ) << int(QDnsLookup::A) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "" ; |
112 | QTest::newRow(dataTag: "a-multi" ) << int(QDnsLookup::A) << "a-multi" << int(QDnsLookup::NoError) << "" << "192.0.2.1;192.0.2.2;192.0.2.3" << "" << "" << "" << "" << "" ; |
113 | QTest::newRow(dataTag: "aaaa-empty" ) << int(QDnsLookup::AAAA) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
114 | QTest::newRow(dataTag: "aaaa-notfound" ) << int(QDnsLookup::AAAA) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
115 | QTest::newRow(dataTag: "aaaa-single" ) << int(QDnsLookup::AAAA) << "aaaa-single" << int(QDnsLookup::NoError) << "" << "2001:db8::1" << "" << "" << "" << "" << "" ; |
116 | QTest::newRow(dataTag: "aaaa-multi" ) << int(QDnsLookup::AAAA) << "aaaa-multi" << int(QDnsLookup::NoError) << "" << "2001:db8::1;2001:db8::2;2001:db8::3" << "" << "" << "" << "" << "" ; |
117 | |
118 | QTest::newRow(dataTag: "any-empty" ) << int(QDnsLookup::ANY) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
119 | QTest::newRow(dataTag: "any-notfound" ) << int(QDnsLookup::ANY) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
120 | QTest::newRow(dataTag: "any-a-single" ) << int(QDnsLookup::ANY) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "" ; |
121 | QTest::newRow(dataTag: "any-a-plus-aaaa" ) << int(QDnsLookup::ANY) << "a-plus-aaaa" << int(QDnsLookup::NoError) << "" << "198.51.100.1;2001:db8::1:1" << "" << "" << "" << "" << "" ; |
122 | QTest::newRow(dataTag: "any-multi" ) << int(QDnsLookup::ANY) << "multi" << int(QDnsLookup::NoError) << "" << "198.51.100.1;198.51.100.2;198.51.100.3;2001:db8::1:1;2001:db8::1:2" << "" << "" << "" << "" << "" ; |
123 | |
124 | QTest::newRow(dataTag: "mx-empty" ) << int(QDnsLookup::MX) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
125 | QTest::newRow(dataTag: "mx-notfound" ) << int(QDnsLookup::MX) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
126 | QTest::newRow(dataTag: "mx-single" ) << int(QDnsLookup::MX) << "mx-single" << int(QDnsLookup::NoError) << "" << "" << "10 multi" << "" << "" << "" << "" ; |
127 | QTest::newRow(dataTag: "mx-single-cname" ) << int(QDnsLookup::MX) << "mx-single-cname" << int(QDnsLookup::NoError) << "" << "" << "10 cname" << "" << "" << "" << "" ; |
128 | QTest::newRow(dataTag: "mx-multi" ) << int(QDnsLookup::MX) << "mx-multi" << int(QDnsLookup::NoError) << "" << "" << "10 multi;20 a-single" << "" << "" << "" << "" ; |
129 | QTest::newRow(dataTag: "mx-multi-sameprio" ) << int(QDnsLookup::MX) << "mx-multi-sameprio" << int(QDnsLookup::NoError) << "" << "" |
130 | << "10 multi;10 a-single|" |
131 | "10 a-single;10 multi" << "" << "" << "" << "" ; |
132 | |
133 | QTest::newRow(dataTag: "ns-empty" ) << int(QDnsLookup::NS) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
134 | QTest::newRow(dataTag: "ns-notfound" ) << int(QDnsLookup::NS) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
135 | QTest::newRow(dataTag: "ns-single" ) << int(QDnsLookup::NS) << "ns-single" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net." << "" << "" << "" ; |
136 | QTest::newRow(dataTag: "ns-multi" ) << int(QDnsLookup::NS) << "ns-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net.;ns12.cloudns.net." << "" << "" << "" ; |
137 | |
138 | QTest::newRow(dataTag: "ptr-empty" ) << int(QDnsLookup::PTR) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
139 | QTest::newRow(dataTag: "ptr-notfound" ) << int(QDnsLookup::PTR) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
140 | #if 0 |
141 | // temporarily disabled since the new hosting provider can't insert |
142 | // PTR records outside of the in-addr.arpa zone |
143 | QTest::newRow("ptr-single" ) << int(QDnsLookup::PTR) << "ptr-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "a-single" << "" << "" ; |
144 | #endif |
145 | |
146 | QTest::newRow(dataTag: "srv-empty" ) << int(QDnsLookup::SRV) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
147 | QTest::newRow(dataTag: "srv-notfound" ) << int(QDnsLookup::SRV) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
148 | QTest::newRow(dataTag: "srv-single" ) << int(QDnsLookup::SRV) << "_echo._tcp.srv-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 7 multi" << "" ; |
149 | QTest::newRow(dataTag: "srv-prio" ) << int(QDnsLookup::SRV) << "_echo._tcp.srv-prio" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "1 0 7 multi;2 0 7 a-plus-aaaa" << "" ; |
150 | QTest::newRow(dataTag: "srv-weighted" ) << int(QDnsLookup::SRV) << "_echo._tcp.srv-weighted" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" |
151 | << "5 75 7 multi;5 25 7 a-plus-aaaa|" |
152 | "5 25 7 a-plus-aaaa;5 75 7 multi" << "" ; |
153 | QTest::newRow(dataTag: "srv-multi" ) << int(QDnsLookup::SRV) << "_echo._tcp.srv-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" |
154 | << "1 50 7 multi;2 50 7 a-single;2 50 7 aaaa-single;3 50 7 a-multi|" |
155 | "1 50 7 multi;2 50 7 aaaa-single;2 50 7 a-single;3 50 7 a-multi" << "" ; |
156 | |
157 | QTest::newRow(dataTag: "txt-empty" ) << int(QDnsLookup::TXT) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "" ; |
158 | QTest::newRow(dataTag: "txt-notfound" ) << int(QDnsLookup::TXT) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "" ; |
159 | QTest::newRow(dataTag: "txt-single" ) << int(QDnsLookup::TXT) << "txt-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello" ; |
160 | QTest::newRow(dataTag: "txt-multi-onerr" ) << int(QDnsLookup::TXT) << "txt-multi-onerr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" |
161 | << QString::fromLatin1(str: "Hello\0World" , size: sizeof("Hello\0World" ) - 1); |
162 | QTest::newRow(dataTag: "txt-multi-multirr" ) << int(QDnsLookup::TXT) << "txt-multi-multirr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello;World" ; |
163 | } |
164 | |
165 | static QByteArray msgDnsLookup(QDnsLookup::Error actualError, |
166 | int expectedError, |
167 | const QString &domain, |
168 | const QString &cname, |
169 | const QString &host, |
170 | const QString &srv, |
171 | const QString &mx, |
172 | const QString &ns, |
173 | const QString &ptr, |
174 | const QString &errorString) |
175 | { |
176 | QString result; |
177 | QTextStream str(&result); |
178 | str << "Actual error: " << actualError; |
179 | if (!errorString.isEmpty()) |
180 | str << " (" << errorString << ')'; |
181 | str << ", expected: " << expectedError; |
182 | str << ", domain: " << domain; |
183 | if (!cname.isEmpty()) |
184 | str << ", cname: " << cname; |
185 | str << ", host: " << host; |
186 | if (!srv.isEmpty()) |
187 | str << " server: " << srv; |
188 | if (!mx.isEmpty()) |
189 | str << " mx: " << mx; |
190 | if (!ns.isEmpty()) |
191 | str << " ns: " << ns; |
192 | if (!ptr.isEmpty()) |
193 | str << " ptr: " << ptr; |
194 | return result.toLocal8Bit(); |
195 | } |
196 | |
197 | void tst_QDnsLookup::lookup() |
198 | { |
199 | QFETCH(int, type); |
200 | QFETCH(QString, domain); |
201 | QFETCH(int, error); |
202 | QFETCH(QString, cname); |
203 | QFETCH(QString, host); |
204 | QFETCH(QString, mx); |
205 | QFETCH(QString, ns); |
206 | QFETCH(QString, ptr); |
207 | QFETCH(QString, srv); |
208 | QFETCH(QString, txt); |
209 | |
210 | // transform the inputs |
211 | domain = domainName(input: domain); |
212 | cname = domainName(input: cname); |
213 | ns = domainNameList(input: ns); |
214 | ptr = domainNameList(input: ptr); |
215 | |
216 | // SRV and MX have reply entries that can change order |
217 | // and we can't sort |
218 | QStringList mx_alternatives = domainNameListAlternatives(input: mx); |
219 | QStringList srv_alternatives = domainNameListAlternatives(input: srv); |
220 | |
221 | QDnsLookup lookup; |
222 | lookup.setType(static_cast<QDnsLookup::Type>(type)); |
223 | lookup.setName(domain); |
224 | lookup.lookup(); |
225 | QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); |
226 | |
227 | #if defined(Q_OS_ANDROID) |
228 | if (lookup.errorString() == QStringLiteral("Not yet supported on Android" )) |
229 | QEXPECT_FAIL("" , "Not yet supported on Android" , Abort); |
230 | #endif |
231 | |
232 | QVERIFY2(int(lookup.error()) == error, |
233 | msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString())); |
234 | if (error == QDnsLookup::NoError) |
235 | QVERIFY(lookup.errorString().isEmpty()); |
236 | QCOMPARE(int(lookup.type()), type); |
237 | QCOMPARE(lookup.name(), domain); |
238 | |
239 | // canonical names |
240 | if (!cname.isEmpty()) { |
241 | QVERIFY(!lookup.canonicalNameRecords().isEmpty()); |
242 | const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first(); |
243 | QCOMPARE(cnameRecord.name(), domain); |
244 | QCOMPARE(cnameRecord.value(), cname); |
245 | } else { |
246 | QVERIFY(lookup.canonicalNameRecords().isEmpty()); |
247 | } |
248 | |
249 | // host addresses |
250 | const QString hostName = cname.isEmpty() ? domain : cname; |
251 | QStringList addresses; |
252 | foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) { |
253 | //reply may include A & AAAA records for nameservers, ignore them and only look at records matching the query |
254 | if (record.name() == hostName) |
255 | addresses << record.value().toString().toLower(); |
256 | } |
257 | addresses.sort(); |
258 | QCOMPARE(addresses.join(';'), host); |
259 | |
260 | // mail exchanges |
261 | QStringList mailExchanges; |
262 | foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) { |
263 | QCOMPARE(record.name(), domain); |
264 | mailExchanges << QString::number(record.preference()) + QLatin1Char(' ') + record.exchange(); |
265 | } |
266 | QVERIFY2(mx_alternatives.contains(mailExchanges.join(';')), |
267 | qPrintable("Actual: " + mailExchanges.join(';') + "\nExpected one of:\n" + mx_alternatives.join('\n'))); |
268 | |
269 | // name servers |
270 | QStringList nameServers; |
271 | foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) { |
272 | //reply may include NS records for authoritative nameservers, ignore them and only look at records matching the query |
273 | if (record.name() == domain) |
274 | nameServers << record.value(); |
275 | } |
276 | nameServers.sort(); |
277 | QCOMPARE(nameServers.join(';'), ns); |
278 | |
279 | // pointers |
280 | if (!ptr.isEmpty()) { |
281 | QVERIFY(!lookup.pointerRecords().isEmpty()); |
282 | const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first(); |
283 | QCOMPARE(ptrRecord.name(), domain); |
284 | QCOMPARE(ptrRecord.value(), ptr); |
285 | } else { |
286 | QVERIFY(lookup.pointerRecords().isEmpty()); |
287 | } |
288 | |
289 | // services |
290 | QStringList services; |
291 | foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) { |
292 | QCOMPARE(record.name(), domain); |
293 | services << (QString::number(record.priority()) + QLatin1Char(' ') |
294 | + QString::number(record.weight()) + QLatin1Char(' ') |
295 | + QString::number(record.port()) + QLatin1Char(' ') + record.target()); |
296 | } |
297 | QVERIFY2(srv_alternatives.contains(services.join(';')), |
298 | qPrintable("Actual: " + services.join(';') + "\nExpected one of:\n" + srv_alternatives.join('\n'))); |
299 | |
300 | // text |
301 | QStringList texts; |
302 | foreach (const QDnsTextRecord &record, lookup.textRecords()) { |
303 | QCOMPARE(record.name(), domain); |
304 | QString text; |
305 | foreach (const QByteArray &ba, record.values()) { |
306 | if (!text.isEmpty()) |
307 | text += '\0'; |
308 | text += QString::fromLatin1(str: ba); |
309 | } |
310 | texts << text; |
311 | } |
312 | texts.sort(); |
313 | QCOMPARE(texts.join(';'), txt); |
314 | } |
315 | |
316 | void tst_QDnsLookup::lookupReuse() |
317 | { |
318 | QDnsLookup lookup; |
319 | |
320 | // first lookup |
321 | lookup.setType(QDnsLookup::A); |
322 | lookup.setName(domainName(input: "a-single" )); |
323 | lookup.lookup(); |
324 | QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); |
325 | |
326 | #if defined(Q_OS_ANDROID) |
327 | if (lookup.errorString() == QStringLiteral("Not yet supported on Android" )) |
328 | QEXPECT_FAIL("" , "Not yet supported on Android" , Abort); |
329 | #endif |
330 | |
331 | QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); |
332 | QVERIFY(!lookup.hostAddressRecords().isEmpty()); |
333 | QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single" )); |
334 | QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1" )); |
335 | |
336 | // second lookup |
337 | lookup.setType(QDnsLookup::AAAA); |
338 | lookup.setName(domainName(input: "aaaa-single" )); |
339 | lookup.lookup(); |
340 | QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); |
341 | QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); |
342 | QVERIFY(!lookup.hostAddressRecords().isEmpty()); |
343 | QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single" )); |
344 | QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1" )); |
345 | } |
346 | |
347 | |
348 | void tst_QDnsLookup::lookupAbortRetry() |
349 | { |
350 | QDnsLookup lookup; |
351 | |
352 | // try and abort the lookup |
353 | lookup.setType(QDnsLookup::A); |
354 | lookup.setName(domainName(input: "a-single" )); |
355 | lookup.lookup(); |
356 | lookup.abort(); |
357 | QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); |
358 | QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError)); |
359 | QVERIFY(lookup.hostAddressRecords().isEmpty()); |
360 | |
361 | // retry a different lookup |
362 | lookup.setType(QDnsLookup::AAAA); |
363 | lookup.setName(domainName(input: "aaaa-single" )); |
364 | lookup.lookup(); |
365 | QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); |
366 | |
367 | #if defined(Q_OS_ANDROID) |
368 | if (lookup.errorString() == QStringLiteral("Not yet supported on Android" )) |
369 | QEXPECT_FAIL("" , "Not yet supported on Android" , Abort); |
370 | #endif |
371 | |
372 | QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); |
373 | QVERIFY(!lookup.hostAddressRecords().isEmpty()); |
374 | QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single" )); |
375 | QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1" )); |
376 | } |
377 | |
378 | QTEST_MAIN(tst_QDnsLookup) |
379 | #include "tst_qdnslookup.moc" |
380 | |