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