1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5//#define QHOSTINFO_DEBUG
6
7#include "qhostinfo.h"
8#include "qhostinfo_p.h"
9#include <qplatformdefs.h>
10
11#include "QtCore/qapplicationstatic.h"
12#include "QtCore/qscopedpointer.h"
13#include <qabstracteventdispatcher.h>
14#include <qcoreapplication.h>
15#include <qmetaobject.h>
16#include <qscopeguard.h>
17#include <qstringlist.h>
18#include <qthread.h>
19#include <qurl.h>
20
21#include <algorithm>
22
23#ifdef Q_OS_UNIX
24# include <unistd.h>
25# include <netdb.h>
26# include <netinet/in.h>
27# if defined(AI_ADDRCONFIG) && !defined(Q_OS_WASM)
28# define Q_ADDRCONFIG AI_ADDRCONFIG
29# endif
30#elif defined Q_OS_WIN
31# include <ws2tcpip.h>
32
33# define QT_SOCKLEN_T int
34#endif
35
36QT_BEGIN_NAMESPACE
37
38using namespace Qt::StringLiterals;
39
40//#define QHOSTINFO_DEBUG
41
42QT_IMPL_METATYPE_EXTERN(QHostInfo)
43
44namespace {
45struct ToBeLookedUpEquals {
46 typedef bool result_type;
47 explicit ToBeLookedUpEquals(const QString &toBeLookedUp) noexcept : m_toBeLookedUp(toBeLookedUp) {}
48 result_type operator()(QHostInfoRunnable* lookup) const noexcept
49 {
50 return m_toBeLookedUp == lookup->toBeLookedUp;
51 }
52private:
53 QString m_toBeLookedUp;
54};
55
56template <typename InputIt, typename OutputIt1, typename OutputIt2, typename UnaryPredicate>
57std::pair<OutputIt1, OutputIt2> separate_if(InputIt first, InputIt last, OutputIt1 dest1, OutputIt2 dest2, UnaryPredicate p)
58{
59 while (first != last) {
60 if (p(*first)) {
61 *dest1 = *first;
62 ++dest1;
63 } else {
64 *dest2 = *first;
65 ++dest2;
66 }
67 ++first;
68 }
69 return std::make_pair(dest1, dest2);
70}
71
72Q_APPLICATION_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
73
74}
75
76QHostInfoResult::QHostInfoResult(const QObject *receiver, QtPrivate::SlotObjUniquePtr slot)
77 : receiver{receiver ? receiver : this}, slotObj{std::move(slot)}
78{
79 Q_ASSERT(this->receiver);
80 moveToThread(thread: this->receiver->thread());
81}
82
83QHostInfoResult::~QHostInfoResult()
84 = default;
85
86/*
87 The calling thread is likely the one that executes the lookup via
88 QHostInfoRunnable. Unless we operate with a queued connection already,
89 posts the QHostInfo to a dedicated QHostInfoResult object that lives in
90 the same thread as the user-provided receiver, or (if there is none) in
91 the thread that made the call to lookupHost. That QHostInfoResult object
92 then calls the user code in the correct thread.
93
94 The 'result' object deletes itself (via deleteLater) when
95 finalizePostResultsReady is called.
96*/
97void QHostInfoResult::postResultsReady(const QHostInfo &info)
98{
99 // queued connection will take care of dispatching to right thread
100 if (!slotObj) {
101 emit resultsReady(info);
102 return;
103 }
104 // we used to have a context object, but it's already destroyed
105 if (!receiver)
106 return;
107
108 // a long-living version of this
109 auto result = new QHostInfoResult(this);
110 Q_CHECK_PTR(result);
111
112 QMetaObject::invokeMethod(object: result,
113 function: &QHostInfoResult::finalizePostResultsReady,
114 type: Qt::QueuedConnection,
115 args: info);
116}
117
118/*
119 Receives the info from postResultsReady, and calls the functor.
120*/
121void QHostInfoResult::finalizePostResultsReady(const QHostInfo &info)
122{
123 Q_ASSERT(slotObj);
124
125 // we used to have a context object, but it's already destroyed
126 if (receiver) {
127 void *args[] = { nullptr, const_cast<QHostInfo *>(&info) };
128 slotObj->call(r: const_cast<QObject *>(receiver.data()), a: args);
129 }
130
131 deleteLater();
132}
133
134/*!
135 \class QHostInfo
136 \brief The QHostInfo class provides static functions for host name lookups.
137
138 \reentrant
139 \inmodule QtNetwork
140 \ingroup network
141
142 QHostInfo finds the IP address(es) associated with a host name,
143 or the host name associated with an IP address.
144 The class provides two static convenience functions: one that
145 works asynchronously and emits a signal once the host is found,
146 and one that blocks and returns a QHostInfo object.
147
148 To look up a host's IP addresses asynchronously, call lookupHost(),
149 which takes the host name or IP address, a receiver object, and a slot
150 signature as arguments and returns an ID. You can abort the
151 lookup by calling abortHostLookup() with the lookup ID.
152
153 Example:
154
155 \snippet code/src_network_kernel_qhostinfo.cpp 0
156
157
158 The slot is invoked when the results are ready. The results are
159 stored in a QHostInfo object. Call
160 addresses() to get the list of IP addresses for the host, and
161 hostName() to get the host name that was looked up.
162
163 If the lookup failed, error() returns the type of error that
164 occurred. errorString() gives a human-readable description of the
165 lookup error.
166
167 If you want a blocking lookup, use the QHostInfo::fromName() function:
168
169 \snippet code/src_network_kernel_qhostinfo.cpp 1
170
171 QHostInfo supports Internationalized Domain Names (IDNs) through the
172 IDNA and Punycode standards.
173
174 To retrieve the name of the local host, use the static
175 QHostInfo::localHostName() function.
176
177 QHostInfo uses the mechanisms provided by the operating system
178 to perform the lookup. As per \l {RFC 6724}
179 there is no guarantee that all IP addresses registered for a domain or
180 host will be returned.
181
182 \note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup
183 instead of one dedicated DNS thread. This improves performance,
184 but also changes the order of signal emissions when using lookupHost()
185 compared to previous versions of Qt.
186 \note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache
187 for performance improvements.
188
189 \sa QAbstractSocket, {RFC 3492}, {RFC 6724}
190*/
191
192static int nextId()
193{
194 Q_CONSTINIT static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
195 return 1 + counter.fetchAndAddRelaxed(valueToAdd: 1);
196}
197
198/*!
199 Looks up the IP address(es) associated with host name \a name, and
200 returns an ID for the lookup. When the result of the lookup is
201 ready, the slot or signal \a member in \a receiver is called with
202 a QHostInfo argument. The QHostInfo object can then be inspected
203 to get the results of the lookup.
204
205 The lookup is performed by a single function call, for example:
206
207 \snippet code/src_network_kernel_qhostinfo.cpp 2
208
209 The implementation of the slot prints basic information about the
210 addresses returned by the lookup, or reports an error if it failed:
211
212 \snippet code/src_network_kernel_qhostinfo.cpp 3
213
214 If you pass a literal IP address to \a name instead of a host name,
215 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
216 perform a \e reverse lookup). On success, the resulting QHostInfo will
217 contain both the resolved domain name and IP addresses for the host
218 name. Example:
219
220 \snippet code/src_network_kernel_qhostinfo.cpp 4
221
222 \note There is no guarantee on the order the signals will be emitted
223 if you start multiple requests with lookupHost().
224
225 \note In Qt versions prior to 6.7, this function took \a receiver as
226 (non-const) \c{QObject*}.
227
228 \sa abortHostLookup(), addresses(), error(), fromName()
229*/
230int QHostInfo::lookupHost(const QString &name, const QObject *receiver, const char *member)
231{
232 if (!receiver || !member) {
233 qWarning(msg: "QHostInfo::lookupHost: both the receiver and the member to invoke must be non-null");
234 return -1;
235 }
236 return QHostInfo::lookupHostImpl(name, receiver, slotObj: nullptr, member);
237}
238
239/*!
240 \fn QHostInfo &QHostInfo::operator=(QHostInfo &&other)
241
242 Move-assigns \a other to this QHostInfo instance.
243
244 \note The moved-from object \a other is placed in a
245 partially-formed state, in which the only valid operations are
246 destruction and assignment of a new value.
247
248 \since 5.10
249*/
250
251/*!
252 \fn void QHostInfo::swap(QHostInfo &other)
253 \memberswap{host-info}
254 \since 5.10
255*/
256
257/*!
258 \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, Functor &&functor)
259
260 \since 5.9
261
262 \overload
263
264 Looks up the IP address(es) associated with host name \a name, and
265 returns an ID for the lookup. When the result of the lookup is
266 ready, the \a functor is called with a QHostInfo argument. The
267 QHostInfo object can then be inspected to get the results of the
268 lookup.
269
270 The \a functor will be run in the thread that makes the call to lookupHost;
271 that thread must have a running Qt event loop.
272
273 \note There is no guarantee on the order the signals will be emitted
274 if you start multiple requests with lookupHost().
275
276 \sa abortHostLookup(), addresses(), error(), fromName()
277*/
278
279/*!
280 \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, const QObject *context, Functor functor)
281
282 \since 5.9
283
284 \overload
285
286 Looks up the IP address(es) associated with host name \a name, and
287 returns an ID for the lookup. When the result of the lookup is
288 ready, the \a functor is called with a QHostInfo argument. The
289 QHostInfo object can then be inspected to get the results of the
290 lookup.
291
292 If \a context is destroyed before the lookup completes, the
293 \a functor will not be called. The \a functor will be run in the
294 thread of \a context. The context's thread must have a running Qt
295 event loop.
296
297 Here is an alternative signature for the function:
298 \code
299 lookupHost(const QString &name, const QObject *receiver, PointerToMemberFunction function)
300 \endcode
301
302 In this case, when the result of the lookup is ready, the slot or
303 signal \c{function} in \c{receiver} is called with a QHostInfo
304 argument. The QHostInfo object can then be inspected to get the
305 results of the lookup.
306
307 \note There is no guarantee on the order the signals will be emitted
308 if you start multiple requests with lookupHost().
309
310 \sa abortHostLookup(), addresses(), error(), fromName()
311*/
312
313/*!
314 Aborts the host lookup with the ID \a id, as returned by lookupHost().
315
316 \sa lookupHost(), lookupId()
317*/
318void QHostInfo::abortHostLookup(int id)
319{
320 theHostInfoLookupManager()->abortLookup(id);
321}
322
323/*!
324 Looks up the IP address(es) for the given host \a name. The
325 function blocks during the lookup which means that execution of
326 the program is suspended until the results of the lookup are
327 ready. Returns the result of the lookup in a QHostInfo object.
328
329 If you pass a literal IP address to \a name instead of a host name,
330 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
331 perform a \e reverse lookup). On success, the returned QHostInfo will
332 contain both the resolved domain name and IP addresses for the host name.
333
334 \sa lookupHost()
335*/
336QHostInfo QHostInfo::fromName(const QString &name)
337{
338#if defined QHOSTINFO_DEBUG
339 qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData());
340#endif
341
342#ifdef Q_OS_WASM
343 return QHostInfoAgent::lookup(name);
344#else
345 QHostInfo hostInfo = QHostInfoAgent::fromName(hostName: name);
346 QHostInfoLookupManager* manager = theHostInfoLookupManager();
347 manager->cache.put(name, info: hostInfo);
348 return hostInfo;
349#endif
350}
351
352
353QHostInfo QHostInfoAgent::reverseLookup(const QHostAddress &address)
354{
355 QHostInfo results;
356 // Reverse lookup
357 sockaddr_in sa4;
358 sockaddr_in6 sa6;
359 sockaddr *sa = nullptr;
360 QT_SOCKLEN_T saSize;
361 if (address.protocol() == QAbstractSocket::IPv4Protocol) {
362 sa = reinterpret_cast<sockaddr *>(&sa4);
363 saSize = sizeof(sa4);
364 memset(s: &sa4, c: 0, n: sizeof(sa4));
365 sa4.sin_family = AF_INET;
366 sa4.sin_addr.s_addr = htonl(hostlong: address.toIPv4Address());
367 } else {
368 sa = reinterpret_cast<sockaddr *>(&sa6);
369 saSize = sizeof(sa6);
370 memset(s: &sa6, c: 0, n: sizeof(sa6));
371 sa6.sin6_family = AF_INET6;
372 memcpy(dest: &sa6.sin6_addr, src: address.toIPv6Address().c, n: sizeof(sa6.sin6_addr));
373 }
374
375 char hbuf[NI_MAXHOST];
376 if (sa && getnameinfo(sa: sa, salen: saSize, host: hbuf, hostlen: sizeof(hbuf), serv: nullptr, servlen: 0, flags: 0) == 0)
377 results.setHostName(QString::fromLatin1(ba: hbuf));
378
379 if (results.hostName().isEmpty())
380 results.setHostName(address.toString());
381 results.setAddresses(QList<QHostAddress>() << address);
382
383 return results;
384}
385
386/*
387 Call getaddrinfo, and returns the results as QHostInfo::addresses
388*/
389QHostInfo QHostInfoAgent::lookup(const QString &hostName)
390{
391 QHostInfo results;
392
393 // IDN support
394 QByteArray aceHostname = QUrl::toAce(domain: hostName);
395 results.setHostName(hostName);
396 if (aceHostname.isEmpty()) {
397 results.setError(QHostInfo::HostNotFound);
398 results.setErrorString(hostName.isEmpty() ?
399 QCoreApplication::translate(context: "QHostInfoAgent", key: "No host name given") :
400 QCoreApplication::translate(context: "QHostInfoAgent", key: "Invalid hostname"));
401 return results;
402 }
403
404 addrinfo *res = nullptr;
405 struct addrinfo hints;
406 memset(s: &hints, c: 0, n: sizeof(hints));
407 hints.ai_family = PF_UNSPEC;
408#ifdef Q_ADDRCONFIG
409 hints.ai_flags = Q_ADDRCONFIG;
410#endif
411
412 int result = getaddrinfo(name: aceHostname.constData(), service: nullptr, req: &hints, pai: &res);
413# ifdef Q_ADDRCONFIG
414 if (result == EAI_BADFLAGS) {
415 // if the lookup failed with AI_ADDRCONFIG set, try again without it
416 hints.ai_flags = 0;
417 result = getaddrinfo(name: aceHostname.constData(), service: nullptr, req: &hints, pai: &res);
418 }
419# endif
420
421 if (result == 0) {
422 addrinfo *node = res;
423 QList<QHostAddress> addresses;
424 while (node) {
425#ifdef QHOSTINFO_DEBUG
426 qDebug() << "getaddrinfo node: flags:" << node->ai_flags << "family:" << node->ai_family
427 << "ai_socktype:" << node->ai_socktype << "ai_protocol:" << node->ai_protocol
428 << "ai_addrlen:" << node->ai_addrlen;
429#endif
430 switch (node->ai_family) {
431 case AF_INET: {
432 QHostAddress addr;
433 addr.setAddress(ntohl(netlong: ((sockaddr_in *) node->ai_addr)->sin_addr.s_addr));
434 if (!addresses.contains(t: addr))
435 addresses.append(t: addr);
436 break;
437 }
438 case AF_INET6: {
439 QHostAddress addr;
440 sockaddr_in6 *sa6 = (sockaddr_in6 *) node->ai_addr;
441 addr.setAddress(sa6->sin6_addr.s6_addr);
442 if (sa6->sin6_scope_id)
443 addr.setScopeId(QString::number(sa6->sin6_scope_id));
444 if (!addresses.contains(t: addr))
445 addresses.append(t: addr);
446 break;
447 }
448 default:
449 results.setError(QHostInfo::UnknownError);
450 results.setErrorString(QCoreApplication::translate(context: "QHostInfoAgent", key: "Unknown address type"));
451 }
452 node = node->ai_next;
453 }
454 if (addresses.isEmpty()) {
455 // Reached the end of the list, but no addresses were found; this
456 // means the list contains one or more unknown address types.
457 results.setError(QHostInfo::UnknownError);
458 results.setErrorString(QCoreApplication::translate(context: "QHostInfoAgent", key: "Unknown address type"));
459 }
460
461 results.setAddresses(addresses);
462 freeaddrinfo(ai: res);
463 } else {
464 switch (result) {
465#ifdef Q_OS_WIN
466 case WSAHOST_NOT_FOUND: //authoritative not found
467 case WSATRY_AGAIN: //non authoritative not found
468 case WSANO_DATA: //valid name, no associated address
469#else
470 case EAI_NONAME:
471 case EAI_FAIL:
472# ifdef EAI_NODATA // EAI_NODATA is deprecated in RFC 3493
473 case EAI_NODATA:
474# endif
475#endif
476 results.setError(QHostInfo::HostNotFound);
477 results.setErrorString(QCoreApplication::translate(context: "QHostInfoAgent", key: "Host not found"));
478 break;
479 default:
480 results.setError(QHostInfo::UnknownError);
481#ifdef Q_OS_WIN
482 results.setErrorString(QString::fromWCharArray(gai_strerror(result)));
483#else
484 results.setErrorString(QString::fromLocal8Bit(ba: gai_strerror(ecode: result)));
485#endif
486 break;
487 }
488 }
489
490#if defined(QHOSTINFO_DEBUG)
491 if (results.error() != QHostInfo::NoError) {
492 qDebug("QHostInfoAgent::fromName(): error #%d %s",
493 h_errno, results.errorString().toLatin1().constData());
494 } else {
495 QString tmp;
496 QList<QHostAddress> addresses = results.addresses();
497 for (int i = 0; i < addresses.count(); ++i) {
498 if (i != 0) tmp += ", "_L1;
499 tmp += addresses.at(i).toString();
500 }
501 qDebug("QHostInfoAgent::fromName(): found %i entries for \"%s\": {%s}",
502 addresses.count(), aceHostname.constData(),
503 tmp.toLatin1().constData());
504 }
505#endif
506
507 return results;
508}
509
510/*!
511 \enum QHostInfo::HostInfoError
512
513 This enum describes the various errors that can occur when trying
514 to resolve a host name.
515
516 \value NoError The lookup was successful.
517 \value HostNotFound No IP addresses were found for the host.
518 \value UnknownError An unknown error occurred.
519
520 \sa error(), setError()
521*/
522
523/*!
524 Constructs an empty host info object with lookup ID \a id.
525
526 \sa lookupId()
527*/
528QHostInfo::QHostInfo(int id)
529 : d_ptr(new QHostInfoPrivate)
530{
531 Q_D(QHostInfo);
532 d->lookupId = id;
533}
534
535/*!
536 Constructs a copy of \a other.
537*/
538QHostInfo::QHostInfo(const QHostInfo &other)
539 : d_ptr(new QHostInfoPrivate(*other.d_ptr))
540{
541}
542
543/*!
544 \fn QHostInfo::QHostInfo(QHostInfo &&other)
545
546 Move-constructs a new QHostInfo from \a other.
547
548 \note The moved-from object \a other is placed in a
549 partially-formed state, in which the only valid operations are
550 destruction and assignment of a new value.
551
552 \since 5.14
553*/
554
555/*!
556 Assigns the data of the \a other object to this host info object,
557 and returns a reference to it.
558*/
559QHostInfo &QHostInfo::operator=(const QHostInfo &other)
560{
561 if (this == &other)
562 return *this;
563
564 Q_ASSERT(d_ptr && other.d_ptr);
565 *d_ptr = *other.d_ptr;
566 return *this;
567}
568
569/*!
570 Destroys the host info object.
571*/
572QHostInfo::~QHostInfo()
573{
574 delete d_ptr;
575}
576
577/*!
578 Returns the list of IP addresses associated with hostName(). This
579 list may be empty.
580
581 Example:
582
583 \snippet code/src_network_kernel_qhostinfo.cpp 5
584
585 \sa hostName(), error()
586*/
587QList<QHostAddress> QHostInfo::addresses() const
588{
589 Q_D(const QHostInfo);
590 return d->addrs;
591}
592
593/*!
594 Sets the list of addresses in this QHostInfo to \a addresses.
595
596 \sa addresses()
597*/
598void QHostInfo::setAddresses(const QList<QHostAddress> &addresses)
599{
600 Q_D(QHostInfo);
601 d->addrs = addresses;
602}
603
604/*!
605 Returns the name of the host whose IP addresses were looked up.
606
607 \sa localHostName()
608*/
609QString QHostInfo::hostName() const
610{
611 Q_D(const QHostInfo);
612 return d->hostName;
613}
614
615/*!
616 Sets the host name of this QHostInfo to \a hostName.
617
618 \sa hostName()
619*/
620void QHostInfo::setHostName(const QString &hostName)
621{
622 Q_D(QHostInfo);
623 d->hostName = hostName;
624}
625
626/*!
627 Returns the type of error that occurred if the host name lookup
628 failed; otherwise returns NoError.
629
630 \sa setError(), errorString()
631*/
632QHostInfo::HostInfoError QHostInfo::error() const
633{
634 Q_D(const QHostInfo);
635 return d->err;
636}
637
638/*!
639 Sets the error type of this QHostInfo to \a error.
640
641 \sa error(), errorString()
642*/
643void QHostInfo::setError(HostInfoError error)
644{
645 Q_D(QHostInfo);
646 d->err = error;
647}
648
649/*!
650 Returns the ID of this lookup.
651
652 \sa setLookupId(), abortHostLookup(), hostName()
653*/
654int QHostInfo::lookupId() const
655{
656 Q_D(const QHostInfo);
657 return d->lookupId;
658}
659
660/*!
661 Sets the ID of this lookup to \a id.
662
663 \sa lookupId(), lookupHost()
664*/
665void QHostInfo::setLookupId(int id)
666{
667 Q_D(QHostInfo);
668 d->lookupId = id;
669}
670
671/*!
672 If the lookup failed, this function returns a human readable
673 description of the error; otherwise "Unknown error" is returned.
674
675 \sa setErrorString(), error()
676*/
677QString QHostInfo::errorString() const
678{
679 Q_D(const QHostInfo);
680 return d->errorStr;
681}
682
683/*!
684 Sets the human readable description of the error that occurred to \a str
685 if the lookup failed.
686
687 \sa errorString(), setError()
688*/
689void QHostInfo::setErrorString(const QString &str)
690{
691 Q_D(QHostInfo);
692 d->errorStr = str;
693}
694
695/*!
696 \fn QString QHostInfo::localHostName()
697
698 Returns this machine's host name, if one is configured. Note that hostnames
699 are not guaranteed to be globally unique, especially if they were
700 configured automatically.
701
702 This function does not guarantee the returned host name is a Fully
703 Qualified Domain Name (FQDN). For that, use fromName() to resolve the
704 returned name to an FQDN.
705
706 This function returns the same as QSysInfo::machineHostName().
707
708 \sa hostName(), localDomainName()
709*/
710QString QHostInfo::localHostName()
711{
712 return QSysInfo::machineHostName();
713}
714
715/*!
716 \fn QString QHostInfo::localDomainName()
717
718 Returns the DNS domain of this machine.
719
720 \note DNS domains are not related to domain names found in
721 Windows networks.
722
723 \sa hostName()
724*/
725
726/*!
727 \internal
728 Called by the various lookupHost overloads to perform the lookup.
729
730 Signals either the functor encapuslated in the \a slotObjRaw in the context
731 of \a receiver, or the \a member slot of the \a receiver.
732
733 \a receiver might be the nullptr, but only if a \a slotObjRaw is provided.
734*/
735int QHostInfo::lookupHostImpl(const QString &name,
736 const QObject *receiver,
737 QtPrivate::QSlotObjectBase *slotObjRaw,
738 const char *member)
739{
740 QtPrivate::SlotObjUniquePtr slotObj{slotObjRaw};
741#if defined QHOSTINFO_DEBUG
742 qDebug("QHostInfo::lookupHostImpl(\"%s\", %p, %p, %s)",
743 name.toLatin1().constData(), receiver, slotObj.get(), member ? member + 1 : 0);
744#endif
745 Q_ASSERT(!member != !slotObj); // one of these must be set, but not both
746 Q_ASSERT(receiver || slotObj);
747 Q_ASSERT(!member || receiver); // if member is set, also is receiver
748 const bool isUsingStringBasedSlot = static_cast<bool>(member);
749
750 if (!QAbstractEventDispatcher::instance()) {
751 qWarning(msg: "QHostInfo::lookupHost() called with no event dispatcher");
752 return -1;
753 }
754
755 qRegisterMetaType<QHostInfo>();
756
757 int id = nextId(); // generate unique ID
758
759 if (Q_UNLIKELY(name.isEmpty())) {
760 QHostInfo hostInfo(id);
761 hostInfo.setError(QHostInfo::HostNotFound);
762 hostInfo.setErrorString(QCoreApplication::translate(context: "QHostInfo", key: "No host name given"));
763
764 QHostInfoResult result(receiver, std::move(slotObj));
765 if (isUsingStringBasedSlot) {
766 QObject::connect(sender: &result, SIGNAL(resultsReady(QHostInfo)),
767 receiver, member, Qt::QueuedConnection);
768 }
769 result.postResultsReady(info: hostInfo);
770
771 return id;
772 }
773
774#ifdef Q_OS_WASM
775 // Resolve the host name directly without using a thread or cache,
776 // since Emscripten's host lookup is fast. Emscripten maintains an internal
777 // mapping of hosts and addresses for the purposes of WebSocket socket
778 // tunnelling, and does not perform an actual host lookup.
779 QHostInfo hostInfo = QHostInfoAgent::lookup(name);
780 hostInfo.setLookupId(id);
781
782 QHostInfoResult result(receiver, std::move(slotObj));
783 if (isUsingStringBasedSlot) {
784 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
785 receiver, member, Qt::QueuedConnection);
786 }
787 result.postResultsReady(hostInfo);
788#else
789 QHostInfoLookupManager *manager = theHostInfoLookupManager();
790
791 if (Q_LIKELY(manager)) {
792 // the application is still alive
793 if (manager->cache.isEnabled()) {
794 // check cache first
795 bool valid = false;
796 QHostInfo info = manager->cache.get(name, valid: &valid);
797 if (valid) {
798 info.setLookupId(id);
799 QHostInfoResult result(receiver, std::move(slotObj));
800 if (isUsingStringBasedSlot) {
801 QObject::connect(sender: &result, SIGNAL(resultsReady(QHostInfo)),
802 receiver, member, Qt::QueuedConnection);
803 }
804 result.postResultsReady(info);
805 return id;
806 }
807 }
808
809 // cache is not enabled or it was not in the cache, do normal lookup
810 QHostInfoRunnable *runnable = new QHostInfoRunnable(name, id, receiver, std::move(slotObj));
811 if (isUsingStringBasedSlot) {
812 QObject::connect(sender: &runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)),
813 receiver, member, Qt::QueuedConnection);
814 }
815 manager->scheduleLookup(r: runnable);
816 }
817#endif // Q_OS_WASM
818 return id;
819}
820
821QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i, const QObject *receiver,
822 QtPrivate::SlotObjUniquePtr slotObj)
823 : toBeLookedUp{hn}, id{i}, resultEmitter{receiver, std::move(slotObj)}
824{
825 setAutoDelete(true);
826}
827
828QHostInfoRunnable::~QHostInfoRunnable()
829 = default;
830
831// the QHostInfoLookupManager will at some point call this via a QThreadPool
832void QHostInfoRunnable::run()
833{
834 QHostInfoLookupManager *manager = theHostInfoLookupManager();
835 const auto sg = qScopeGuard(f: [&] { manager->lookupFinished(r: this); });
836 // check aborted
837 if (manager->wasAborted(id))
838 return;
839
840 QHostInfo hostInfo;
841
842 // QHostInfo::lookupHost already checks the cache. However we need to check
843 // it here too because it might have been cache saved by another QHostInfoRunnable
844 // in the meanwhile while this QHostInfoRunnable was scheduled but not running
845 if (manager->cache.isEnabled()) {
846 // check the cache first
847 bool valid = false;
848 hostInfo = manager->cache.get(name: toBeLookedUp, valid: &valid);
849 if (!valid) {
850 // not in cache, we need to do the lookup and store the result in the cache
851 hostInfo = QHostInfoAgent::fromName(hostName: toBeLookedUp);
852 manager->cache.put(name: toBeLookedUp, info: hostInfo);
853 }
854 } else {
855 // cache is not enabled, just do the lookup and continue
856 hostInfo = QHostInfoAgent::fromName(hostName: toBeLookedUp);
857 }
858
859 // check aborted again
860 if (manager->wasAborted(id))
861 return;
862
863 // signal emission
864 hostInfo.setLookupId(id);
865 resultEmitter.postResultsReady(info: hostInfo);
866
867#if QT_CONFIG(thread)
868 // now also iterate through the postponed ones
869 {
870 QMutexLocker locker(&manager->mutex);
871 const auto partitionBegin = std::stable_partition(first: manager->postponedLookups.rbegin(), last: manager->postponedLookups.rend(),
872 pred: ToBeLookedUpEquals(toBeLookedUp)).base();
873 const auto partitionEnd = manager->postponedLookups.end();
874 for (auto it = partitionBegin; it != partitionEnd; ++it) {
875 QHostInfoRunnable* postponed = *it;
876 // we can now emit
877 hostInfo.setLookupId(postponed->id);
878 postponed->resultEmitter.postResultsReady(info: hostInfo);
879 delete postponed;
880 }
881 manager->postponedLookups.erase(abegin: partitionBegin, aend: partitionEnd);
882 }
883
884#endif
885 // thread goes back to QThreadPool
886}
887
888QHostInfoLookupManager::QHostInfoLookupManager() : wasDeleted(false)
889{
890#if QT_CONFIG(thread)
891 QObject::connect(sender: QCoreApplication::instance(), signal: &QObject::destroyed,
892 context: &threadPool, slot: [&](QObject *) { threadPool.waitForDone(); },
893 type: Qt::DirectConnection);
894 threadPool.setMaxThreadCount(20); // do up to 20 DNS lookups in parallel
895#endif
896}
897
898QHostInfoLookupManager::~QHostInfoLookupManager()
899{
900 QMutexLocker locker(&mutex);
901 wasDeleted = true;
902 locker.unlock();
903
904 // don't qDeleteAll currentLookups, the QThreadPool has ownership
905 clear();
906}
907
908void QHostInfoLookupManager::clear()
909{
910 {
911 QMutexLocker locker(&mutex);
912 qDeleteAll(c: scheduledLookups);
913 qDeleteAll(c: finishedLookups);
914#if QT_CONFIG(thread)
915 qDeleteAll(c: postponedLookups);
916 postponedLookups.clear();
917#endif
918 scheduledLookups.clear();
919 finishedLookups.clear();
920 }
921
922#if QT_CONFIG(thread)
923 threadPool.waitForDone();
924#endif
925 cache.clear();
926}
927
928// assumes mutex is locked by caller
929void QHostInfoLookupManager::rescheduleWithMutexHeld()
930{
931 if (wasDeleted)
932 return;
933
934 // goals of this function:
935 // - launch new lookups via the thread pool
936 // - make sure only one lookup per host/IP is in progress
937
938 if (!finishedLookups.isEmpty()) {
939 // remove ID from aborted if it is in there
940 for (int i = 0; i < finishedLookups.size(); i++) {
941 abortedLookups.removeAll(t: finishedLookups.at(i)->id);
942 }
943
944 finishedLookups.clear();
945 }
946
947#if QT_CONFIG(thread)
948 auto isAlreadyRunning = [this](QHostInfoRunnable *lookup) {
949 return std::any_of(first: currentLookups.cbegin(), last: currentLookups.cend(), pred: ToBeLookedUpEquals(lookup->toBeLookedUp));
950 };
951
952 // Transfer any postponed lookups that aren't currently running to the scheduled list, keeping already-running lookups:
953 postponedLookups.erase(abegin: separate_if(first: postponedLookups.begin(),
954 last: postponedLookups.end(),
955 dest1: postponedLookups.begin(),
956 dest2: std::front_inserter(x&: scheduledLookups), // prepend! we want to finish it ASAP
957 p: isAlreadyRunning).first,
958 aend: postponedLookups.end());
959
960 // Unschedule and postpone any that are currently running:
961 scheduledLookups.erase(abegin: separate_if(first: scheduledLookups.begin(),
962 last: scheduledLookups.end(),
963 dest1: std::back_inserter(x&: postponedLookups),
964 dest2: scheduledLookups.begin(),
965 p: isAlreadyRunning).second,
966 aend: scheduledLookups.end());
967
968 const int availableThreads = std::max(a: threadPool.maxThreadCount(), b: 1) - currentLookups.size();
969 if (availableThreads > 0) {
970 int readyToStartCount = qMin(a: availableThreads, b: scheduledLookups.size());
971 auto it = scheduledLookups.begin();
972 while (readyToStartCount--) {
973 // runnable now running in new thread, track this in currentLookups
974 threadPool.start(runnable: *it);
975 currentLookups.push_back(t: std::move(*it));
976 ++it;
977 }
978 scheduledLookups.erase(abegin: scheduledLookups.begin(), aend: it);
979 }
980#else
981 if (!scheduledLookups.isEmpty())
982 scheduledLookups.takeFirst()->run();
983#endif
984}
985
986// called by QHostInfo
987void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r)
988{
989 QMutexLocker locker(&this->mutex);
990
991 if (wasDeleted)
992 return;
993
994 scheduledLookups.enqueue(t: r);
995 rescheduleWithMutexHeld();
996}
997
998// called by QHostInfo
999void QHostInfoLookupManager::abortLookup(int id)
1000{
1001 QMutexLocker locker(&this->mutex);
1002
1003 if (wasDeleted)
1004 return;
1005
1006 if (id == -1)
1007 return;
1008
1009#if QT_CONFIG(thread)
1010 // is postponed? delete and return
1011 for (int i = 0; i < postponedLookups.size(); i++) {
1012 if (postponedLookups.at(i)->id == id) {
1013 delete postponedLookups.takeAt(i);
1014 return;
1015 }
1016 }
1017#endif
1018
1019 // is scheduled? delete and return
1020 for (int i = 0; i < scheduledLookups.size(); i++) {
1021 if (scheduledLookups.at(i)->id == id) {
1022 delete scheduledLookups.takeAt(i);
1023 return;
1024 }
1025 }
1026
1027 if (!abortedLookups.contains(t: id))
1028 abortedLookups.append(t: id);
1029}
1030
1031// called from QHostInfoRunnable
1032bool QHostInfoLookupManager::wasAborted(int id)
1033{
1034 QMutexLocker locker(&this->mutex);
1035
1036 if (wasDeleted)
1037 return true;
1038
1039 return abortedLookups.contains(t: id);
1040}
1041
1042// called from QHostInfoRunnable
1043void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r)
1044{
1045 QMutexLocker locker(&this->mutex);
1046
1047 if (wasDeleted)
1048 return;
1049
1050#if QT_CONFIG(thread)
1051 currentLookups.removeOne(t: r);
1052#endif
1053 finishedLookups.append(t: r);
1054 rescheduleWithMutexHeld();
1055}
1056
1057// This function returns immediately when we had a result in the cache, else it will later emit a signal
1058QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)
1059{
1060 *valid = false;
1061 *id = -1;
1062
1063 // check cache
1064 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1065 if (manager && manager->cache.isEnabled()) {
1066 QHostInfo info = manager->cache.get(name, valid);
1067 if (*valid) {
1068 return info;
1069 }
1070 }
1071
1072 // was not in cache, trigger lookup
1073 *id = QHostInfo::lookupHostImpl(name, receiver, slotObjRaw: nullptr, member);
1074
1075 // return empty response, valid==false
1076 return QHostInfo();
1077}
1078
1079void qt_qhostinfo_clear_cache()
1080{
1081 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1082 if (manager) {
1083 manager->clear();
1084 }
1085}
1086
1087#ifdef QT_BUILD_INTERNAL
1088void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e)
1089{
1090 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1091 if (manager) {
1092 manager->cache.setEnabled(e);
1093 }
1094}
1095
1096void qt_qhostinfo_cache_inject(const QString &hostname, const QHostInfo &resolution)
1097{
1098 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1099 if (!manager || !manager->cache.isEnabled())
1100 return;
1101
1102 manager->cache.put(name: hostname, info: resolution);
1103}
1104#endif
1105
1106// cache for 60 seconds
1107// cache 128 items
1108QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(128)
1109{
1110#ifdef QT_QHOSTINFO_CACHE_DISABLED_BY_DEFAULT
1111 enabled.store(false, std::memory_order_relaxed);
1112#endif
1113}
1114
1115QHostInfo QHostInfoCache::get(const QString &name, bool *valid)
1116{
1117 QMutexLocker locker(&this->mutex);
1118
1119 *valid = false;
1120 if (QHostInfoCacheElement *element = cache.object(key: name)) {
1121 if (element->age.elapsed() < max_age*1000)
1122 *valid = true;
1123 return element->info;
1124
1125 // FIXME idea:
1126 // if too old but not expired, trigger a new lookup
1127 // to freshen our cache
1128 }
1129
1130 return QHostInfo();
1131}
1132
1133void QHostInfoCache::put(const QString &name, const QHostInfo &info)
1134{
1135 // if the lookup failed, don't cache
1136 if (info.error() != QHostInfo::NoError)
1137 return;
1138
1139 QHostInfoCacheElement* element = new QHostInfoCacheElement();
1140 element->info = info;
1141 element->age = QElapsedTimer();
1142 element->age.start();
1143
1144 QMutexLocker locker(&this->mutex);
1145 cache.insert(key: name, object: element); // cache will take ownership
1146}
1147
1148void QHostInfoCache::clear()
1149{
1150 QMutexLocker locker(&this->mutex);
1151 cache.clear();
1152}
1153
1154QT_END_NAMESPACE
1155
1156#include "moc_qhostinfo_p.cpp"
1157#include "moc_qhostinfo.cpp"
1158

source code of qtbase/src/network/kernel/qhostinfo.cpp