Doc: Modularize QtNetwork documentation.
[profile/ivi/qtbase.git] / src / network / kernel / qhostinfo.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qhostinfo.h"
43 #include "qhostinfo_p.h"
44
45 #include "QtCore/qscopedpointer.h"
46 #include <qabstracteventdispatcher.h>
47 #include <qcoreapplication.h>
48 #include <qmetaobject.h>
49 #include <qstringlist.h>
50 #include <qthread.h>
51 #include <qurl.h>
52 #include <private/qnetworksession_p.h>
53
54 #ifdef Q_OS_UNIX
55 #  include <unistd.h>
56 #endif
57
58 QT_BEGIN_NAMESPACE
59
60 //#define QHOSTINFO_DEBUG
61
62 Q_GLOBAL_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
63
64 /*!
65     \class QHostInfo
66     \brief The QHostInfo class provides static functions for host name lookups.
67
68     \reentrant
69     \inmodule QtNetwork
70     \ingroup network
71
72     QHostInfo uses the lookup mechanisms provided by the operating
73     system to find the IP address(es) associated with a host name,
74     or the host name associated with an IP address.
75     The class provides two static convenience functions: one that
76     works asynchronously and emits a signal once the host is found,
77     and one that blocks and returns a QHostInfo object.
78
79     To look up a host's IP addresses asynchronously, call lookupHost(),
80     which takes the host name or IP address, a receiver object, and a slot
81     signature as arguments and returns an ID. You can abort the
82     lookup by calling abortHostLookup() with the lookup ID.
83
84     Example:
85
86     \snippet code/src_network_kernel_qhostinfo.cpp 0
87
88
89     The slot is invoked when the results are ready. The results are
90     stored in a QHostInfo object. Call
91     addresses() to get the list of IP addresses for the host, and
92     hostName() to get the host name that was looked up.
93
94     If the lookup failed, error() returns the type of error that
95     occurred. errorString() gives a human-readable description of the
96     lookup error.
97
98     If you want a blocking lookup, use the QHostInfo::fromName() function:
99
100     \snippet code/src_network_kernel_qhostinfo.cpp 1
101
102     QHostInfo supports Internationalized Domain Names (IDNs) through the
103     IDNA and Punycode standards.
104
105     To retrieve the name of the local host, use the static
106     QHostInfo::localHostName() function.
107
108     \note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup
109     instead of one dedicated DNS thread. This improves performance,
110     but also changes the order of signal emissions when using lookupHost()
111     compared to previous versions of Qt.
112     \note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache
113     for performance improvements.
114
115     \sa QAbstractSocket, {http://www.rfc-editor.org/rfc/rfc3492.txt}{RFC 3492}
116 */
117
118 static QBasicAtomicInt theIdCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
119
120 /*!
121     Looks up the IP address(es) associated with host name \a name, and
122     returns an ID for the lookup. When the result of the lookup is
123     ready, the slot or signal \a member in \a receiver is called with
124     a QHostInfo argument. The QHostInfo object can then be inspected
125     to get the results of the lookup.
126
127     The lookup is performed by a single function call, for example:
128
129     \snippet code/src_network_kernel_qhostinfo.cpp 2
130
131     The implementation of the slot prints basic information about the
132     addresses returned by the lookup, or reports an error if it failed:
133
134     \snippet code/src_network_kernel_qhostinfo.cpp 3
135
136     If you pass a literal IP address to \a name instead of a host name,
137     QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
138     perform a \e reverse lookup). On success, the resulting QHostInfo will
139     contain both the resolved domain name and IP addresses for the host
140     name. Example:
141
142     \snippet code/src_network_kernel_qhostinfo.cpp 4
143
144     \note There is no guarantee on the order the signals will be emitted
145     if you start multiple requests with lookupHost().
146
147     \sa abortHostLookup(), addresses(), error(), fromName()
148 */
149 int QHostInfo::lookupHost(const QString &name, QObject *receiver,
150                           const char *member)
151 {
152 #if defined QHOSTINFO_DEBUG
153     qDebug("QHostInfo::lookupHost(\"%s\", %p, %s)",
154            name.toLatin1().constData(), receiver, member ? member + 1 : 0);
155 #endif
156
157     if (!QAbstractEventDispatcher::instance(QThread::currentThread())) {
158         qWarning("QHostInfo::lookupHost() called with no event dispatcher");
159         return -1;
160     }
161
162     qRegisterMetaType<QHostInfo>("QHostInfo");
163
164     int id = theIdCounter.fetchAndAddRelaxed(1); // generate unique ID
165
166     if (name.isEmpty()) {
167         QHostInfo hostInfo(id);
168         hostInfo.setError(QHostInfo::HostNotFound);
169         hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
170         QScopedPointer<QHostInfoResult> result(new QHostInfoResult);
171         QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)),
172                          receiver, member, Qt::QueuedConnection);
173         result.data()->emitResultsReady(hostInfo);
174         return id;
175     }
176
177     QHostInfoLookupManager *manager = theHostInfoLookupManager();
178
179     if (manager) {
180         // the application is still alive
181         if (manager->cache.isEnabled()) {
182             // check cache first
183             bool valid = false;
184             QHostInfo info = manager->cache.get(name, &valid);
185             if (valid) {
186                 info.setLookupId(id);
187                 QHostInfoResult result;
188                 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
189                 result.emitResultsReady(info);
190                 return id;
191             }
192         }
193
194         // cache is not enabled or it was not in the cache, do normal lookup
195         QHostInfoRunnable* runnable = new QHostInfoRunnable(name, id);
196         QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
197         manager->scheduleLookup(runnable);
198     }
199     return id;
200 }
201
202 /*!
203     Aborts the host lookup with the ID \a id, as returned by lookupHost().
204
205     \sa lookupHost(), lookupId()
206 */
207 void QHostInfo::abortHostLookup(int id)
208 {
209     theHostInfoLookupManager()->abortLookup(id);
210 }
211
212 /*!
213     Looks up the IP address(es) for the given host \a name. The
214     function blocks during the lookup which means that execution of
215     the program is suspended until the results of the lookup are
216     ready. Returns the result of the lookup in a QHostInfo object.
217
218     If you pass a literal IP address to \a name instead of a host name,
219     QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
220     perform a \e reverse lookup). On success, the returned QHostInfo will
221     contain both the resolved domain name and IP addresses for the host name.
222
223     \sa lookupHost()
224 */
225 QHostInfo QHostInfo::fromName(const QString &name)
226 {
227 #if defined QHOSTINFO_DEBUG
228     qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData());
229 #endif
230
231     QHostInfo hostInfo = QHostInfoAgent::fromName(name);
232     QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
233     manager->cache.put(name, hostInfo);
234     return hostInfo;
235 }
236
237 #ifndef QT_NO_BEARERMANAGEMENT
238 QHostInfo QHostInfoPrivate::fromName(const QString &name, QSharedPointer<QNetworkSession> session)
239 {
240 #if defined QHOSTINFO_DEBUG
241     qDebug("QHostInfoPrivate::fromName(\"%s\") with session %p",name.toLatin1().constData(), session.data());
242 #endif
243
244     QHostInfo hostInfo = QHostInfoAgent::fromName(name, session);
245     QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
246     manager->cache.put(name, hostInfo);
247     return hostInfo;
248 }
249 #endif
250
251 #ifndef QT_NO_BEARERMANAGEMENT
252 QHostInfo QHostInfoAgent::fromName(const QString &hostName, QSharedPointer<QNetworkSession>)
253 {
254     return QHostInfoAgent::fromName(hostName);
255 }
256 #endif
257
258
259 /*!
260     \enum QHostInfo::HostInfoError
261
262     This enum describes the various errors that can occur when trying
263     to resolve a host name.
264
265     \value NoError The lookup was successful.
266     \value HostNotFound No IP addresses were found for the host.
267     \value UnknownError An unknown error occurred.
268
269     \sa error(), setError()
270 */
271
272 /*!
273     Constructs an empty host info object with lookup ID \a id.
274
275     \sa lookupId()
276 */
277 QHostInfo::QHostInfo(int id)
278     : d(new QHostInfoPrivate)
279 {
280     d->lookupId = id;
281 }
282
283 /*!
284     Constructs a copy of \a other.
285 */
286 QHostInfo::QHostInfo(const QHostInfo &other)
287     : d(new QHostInfoPrivate(*other.d.data()))
288 {
289 }
290
291 /*!
292     Assigns the data of the \a other object to this host info object,
293     and returns a reference to it.
294 */
295 QHostInfo &QHostInfo::operator=(const QHostInfo &other)
296 {
297     *d.data() = *other.d.data();
298     return *this;
299 }
300
301 /*!
302     Destroys the host info object.
303 */
304 QHostInfo::~QHostInfo()
305 {
306 }
307
308 /*!
309     Returns the list of IP addresses associated with hostName(). This
310     list may be empty.
311
312     Example:
313
314     \snippet code/src_network_kernel_qhostinfo.cpp 5
315
316     \sa hostName(), error()
317 */
318 QList<QHostAddress> QHostInfo::addresses() const
319 {
320     return d->addrs;
321 }
322
323 /*!
324     Sets the list of addresses in this QHostInfo to \a addresses.
325
326     \sa addresses()
327 */
328 void QHostInfo::setAddresses(const QList<QHostAddress> &addresses)
329 {
330     d->addrs = addresses;
331 }
332
333 /*!
334     Returns the name of the host whose IP addresses were looked up.
335
336     \sa localHostName()
337 */
338 QString QHostInfo::hostName() const
339 {
340     return d->hostName;
341 }
342
343 /*!
344     Sets the host name of this QHostInfo to \a hostName.
345
346     \sa hostName()
347 */
348 void QHostInfo::setHostName(const QString &hostName)
349 {
350     d->hostName = hostName;
351 }
352
353 /*!
354     Returns the type of error that occurred if the host name lookup
355     failed; otherwise returns NoError.
356
357     \sa setError(), errorString()
358 */
359 QHostInfo::HostInfoError QHostInfo::error() const
360 {
361     return d->err;
362 }
363
364 /*!
365     Sets the error type of this QHostInfo to \a error.
366
367     \sa error(), errorString()
368 */
369 void QHostInfo::setError(HostInfoError error)
370 {
371     d->err = error;
372 }
373
374 /*!
375     Returns the ID of this lookup.
376
377     \sa setLookupId(), abortHostLookup(), hostName()
378 */
379 int QHostInfo::lookupId() const
380 {
381     return d->lookupId;
382 }
383
384 /*!
385     Sets the ID of this lookup to \a id.
386
387     \sa lookupId(), lookupHost()
388 */
389 void QHostInfo::setLookupId(int id)
390 {
391     d->lookupId = id;
392 }
393
394 /*!
395     If the lookup failed, this function returns a human readable
396     description of the error; otherwise "Unknown error" is returned.
397
398     \sa setErrorString(), error()
399 */
400 QString QHostInfo::errorString() const
401 {
402     return d->errorStr;
403 }
404
405 /*!
406     Sets the human readable description of the error that occurred to \a str
407     if the lookup failed.
408
409     \sa errorString(), setError()
410 */
411 void QHostInfo::setErrorString(const QString &str)
412 {
413     d->errorStr = str;
414 }
415
416 /*!
417     \fn QString QHostInfo::localHostName()
418
419     Returns the host name of this machine.
420
421     \sa hostName()
422 */
423
424 /*!
425     \fn QString QHostInfo::localDomainName()
426
427     Returns the DNS domain of this machine.
428
429     Note: DNS domains are not related to domain names found in
430     Windows networks.
431
432     \sa hostName()
433 */
434
435 QHostInfoRunnable::QHostInfoRunnable(QString hn, int i) : toBeLookedUp(hn), id(i)
436 {
437     setAutoDelete(true);
438 }
439
440 // the QHostInfoLookupManager will at some point call this via a QThreadPool
441 void QHostInfoRunnable::run()
442 {
443     QHostInfoLookupManager *manager = theHostInfoLookupManager();
444     // check aborted
445     if (manager->wasAborted(id)) {
446         manager->lookupFinished(this);
447         return;
448     }
449
450     QHostInfo hostInfo;
451
452     // QHostInfo::lookupHost already checks the cache. However we need to check
453     // it here too because it might have been cache saved by another QHostInfoRunnable
454     // in the meanwhile while this QHostInfoRunnable was scheduled but not running
455     if (manager->cache.isEnabled()) {
456         // check the cache first
457         bool valid = false;
458         hostInfo = manager->cache.get(toBeLookedUp, &valid);
459         if (!valid) {
460             // not in cache, we need to do the lookup and store the result in the cache
461             hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
462             manager->cache.put(toBeLookedUp, hostInfo);
463         }
464     } else {
465         // cache is not enabled, just do the lookup and continue
466         hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
467     }
468
469     // check aborted again
470     if (manager->wasAborted(id)) {
471         manager->lookupFinished(this);
472         return;
473     }
474
475     // signal emission
476     hostInfo.setLookupId(id);
477     resultEmitter.emitResultsReady(hostInfo);
478
479     // now also iterate through the postponed ones
480     {
481         QMutexLocker locker(&manager->mutex);
482         QMutableListIterator<QHostInfoRunnable*> iterator(manager->postponedLookups);
483         while (iterator.hasNext()) {
484             QHostInfoRunnable* postponed = iterator.next();
485             if (toBeLookedUp == postponed->toBeLookedUp) {
486                 // we can now emit
487                 iterator.remove();
488                 hostInfo.setLookupId(postponed->id);
489                 postponed->resultEmitter.emitResultsReady(hostInfo);
490                 delete postponed;
491             }
492         }
493     }
494
495     manager->lookupFinished(this);
496
497     // thread goes back to QThreadPool
498 }
499
500 QHostInfoLookupManager::QHostInfoLookupManager() : mutex(QMutex::Recursive), wasDeleted(false)
501 {
502     moveToThread(QCoreApplicationPrivate::mainThread());
503     connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection);
504     threadPool.setMaxThreadCount(5); // do 5 DNS lookups in parallel
505 }
506
507 QHostInfoLookupManager::~QHostInfoLookupManager()
508 {
509     wasDeleted = true;
510
511     // don't qDeleteAll currentLookups, the QThreadPool has ownership
512     clear();
513 }
514
515 void QHostInfoLookupManager::clear()
516 {
517     {
518         QMutexLocker locker(&mutex);
519         qDeleteAll(postponedLookups);
520         qDeleteAll(scheduledLookups);
521         qDeleteAll(finishedLookups);
522         postponedLookups.clear();
523         scheduledLookups.clear();
524         finishedLookups.clear();
525     }
526
527     threadPool.waitForDone();
528     cache.clear();
529 }
530
531 void QHostInfoLookupManager::work()
532 {
533     if (wasDeleted)
534         return;
535
536     // goals of this function:
537     //  - launch new lookups via the thread pool
538     //  - make sure only one lookup per host/IP is in progress
539
540     QMutexLocker locker(&mutex);
541
542     if (!finishedLookups.isEmpty()) {
543         // remove ID from aborted if it is in there
544         for (int i = 0; i < finishedLookups.length(); i++) {
545            abortedLookups.removeAll(finishedLookups.at(i)->id);
546         }
547
548         finishedLookups.clear();
549     }
550
551     if (!postponedLookups.isEmpty()) {
552         // try to start the postponed ones
553
554         QMutableListIterator<QHostInfoRunnable*> iterator(postponedLookups);
555         while (iterator.hasNext()) {
556             QHostInfoRunnable* postponed = iterator.next();
557
558             // check if none of the postponed hostnames is currently running
559             bool alreadyRunning = false;
560             for (int i = 0; i < currentLookups.length(); i++) {
561                 if (currentLookups.at(i)->toBeLookedUp == postponed->toBeLookedUp) {
562                     alreadyRunning = true;
563                     break;
564                 }
565             }
566             if (!alreadyRunning) {
567                 iterator.remove();
568                 scheduledLookups.prepend(postponed); // prepend! we want to finish it ASAP
569             }
570         }
571     }
572
573     if (!scheduledLookups.isEmpty()) {
574         // try to start the new ones
575         QMutableListIterator<QHostInfoRunnable*> iterator(scheduledLookups);
576         while (iterator.hasNext()) {
577             QHostInfoRunnable *scheduled = iterator.next();
578
579             // check if a lookup for this host is already running, then postpone
580             for (int i = 0; i < currentLookups.size(); i++) {
581                 if (currentLookups.at(i)->toBeLookedUp == scheduled->toBeLookedUp) {
582                     iterator.remove();
583                     postponedLookups.append(scheduled);
584                     scheduled = 0;
585                     break;
586                 }
587             }
588
589             if (scheduled && currentLookups.size() < threadPool.maxThreadCount()) {
590                 // runnable now running in new thread, track this in currentLookups
591                 threadPool.start(scheduled);
592                 iterator.remove();
593                 currentLookups.append(scheduled);
594             } else {
595                 // was postponed, continue iterating
596                 continue;
597             }
598         };
599     }
600 }
601
602 // called by QHostInfo
603 void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r)
604 {
605     if (wasDeleted)
606         return;
607
608     QMutexLocker locker(&this->mutex);
609     scheduledLookups.enqueue(r);
610     work();
611 }
612
613 // called by QHostInfo
614 void QHostInfoLookupManager::abortLookup(int id)
615 {
616     if (wasDeleted)
617         return;
618
619     QMutexLocker locker(&this->mutex);
620
621     // is postponed? delete and return
622     for (int i = 0; i < postponedLookups.length(); i++) {
623         if (postponedLookups.at(i)->id == id) {
624             delete postponedLookups.takeAt(i);
625             return;
626         }
627     }
628
629     // is scheduled? delete and return
630     for (int i = 0; i < scheduledLookups.length(); i++) {
631         if (scheduledLookups.at(i)->id == id) {
632             delete scheduledLookups.takeAt(i);
633             return;
634         }
635     }
636
637     if (!abortedLookups.contains(id))
638         abortedLookups.append(id);
639 }
640
641 // called from QHostInfoRunnable
642 bool QHostInfoLookupManager::wasAborted(int id)
643 {
644     if (wasDeleted)
645         return true;
646
647     QMutexLocker locker(&this->mutex);
648     return abortedLookups.contains(id);
649 }
650
651 // called from QHostInfoRunnable
652 void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r)
653 {
654     if (wasDeleted)
655         return;
656
657     QMutexLocker locker(&this->mutex);
658     currentLookups.removeOne(r);
659     finishedLookups.append(r);
660     work();
661 }
662
663 // This function returns immediately when we had a result in the cache, else it will later emit a signal
664 QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)
665 {
666     *valid = false;
667     *id = -1;
668
669     // check cache
670     QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
671     if (manager && manager->cache.isEnabled()) {
672         QHostInfo info = manager->cache.get(name, valid);
673         if (*valid) {
674             return info;
675         }
676     }
677
678     // was not in cache, trigger lookup
679     *id = QHostInfo::lookupHost(name, receiver, member);
680
681     // return empty response, valid==false
682     return QHostInfo();
683 }
684
685 void qt_qhostinfo_clear_cache()
686 {
687     QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
688     if (manager) {
689         manager->clear();
690     }
691 }
692
693 void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e)
694 {
695     QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
696     if (manager) {
697         manager->cache.setEnabled(e);
698     }
699 }
700
701 // cache for 60 seconds
702 // cache 64 items
703 QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(64)
704 {
705 #ifdef QT_QHOSTINFO_CACHE_DISABLED_BY_DEFAULT
706     enabled = false;
707 #endif
708 }
709
710 bool QHostInfoCache::isEnabled()
711 {
712     return enabled;
713 }
714
715 // this function is currently only used for the auto tests
716 // and not usable by public API
717 void QHostInfoCache::setEnabled(bool e)
718 {
719     enabled = e;
720 }
721
722
723 QHostInfo QHostInfoCache::get(const QString &name, bool *valid)
724 {
725     QMutexLocker locker(&this->mutex);
726
727     *valid = false;
728     if (cache.contains(name)) {
729         QHostInfoCacheElement *element = cache.object(name);
730         if (element->age.elapsed() < max_age*1000)
731             *valid = true;
732         return element->info;
733
734         // FIXME idea:
735         // if too old but not expired, trigger a new lookup
736         // to freshen our cache
737     }
738
739     return QHostInfo();
740 }
741
742 void QHostInfoCache::put(const QString &name, const QHostInfo &info)
743 {
744     // if the lookup failed, don't cache
745     if (info.error() != QHostInfo::NoError)
746         return;
747
748     QHostInfoCacheElement* element = new QHostInfoCacheElement();
749     element->info = info;
750     element->age = QElapsedTimer();
751     element->age.start();
752
753     QMutexLocker locker(&this->mutex);
754     cache.insert(name, element); // cache will take ownership
755 }
756
757 void QHostInfoCache::clear()
758 {
759     QMutexLocker locker(&this->mutex);
760     cache.clear();
761 }
762
763 QAbstractHostInfoLookupManager* QAbstractHostInfoLookupManager::globalInstance()
764 {
765     return theHostInfoLookupManager();
766 }
767
768 QT_END_NAMESPACE