Coverage Report

Created: 2026-03-12 07:14

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