Add support for DNS lookups using native APIs
authorJeremy Lainé <jeremy.laine@m4x.org>
Mon, 23 Jan 2012 17:25:39 +0000 (18:25 +0100)
committerQt by Nokia <qt-info@nokia.com>
Mon, 30 Jan 2012 15:39:46 +0000 (16:39 +0100)
The QDnsLookup class provides asynchronous APIs for performing
DNS lookups. For now, the following lookups are supported:
- A and AAAA
- CNAME as defined per RFC 1035
- MX as defined per RFC 1035
- NS as defined per RFC 1035
- PTR as defined per RFC 1035
- SRV as defined per RFC 2782
- TXT as defined per RFC 1035

Task-number: QTBUG-10481
Change-Id: I46c1741ec23615863eeca3a1231d5e3f8942495e
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
16 files changed:
doc/src/snippets/code/src_network_kernel_qdnslookup.cpp [new file with mode: 0644]
examples/network/dnslookup/dnslookup.cpp [new file with mode: 0644]
examples/network/dnslookup/dnslookup.h [new file with mode: 0644]
examples/network/dnslookup/dnslookup.pro [new file with mode: 0644]
examples/network/network.pro
src/network/kernel/kernel.pri
src/network/kernel/qdnslookup.cpp [new file with mode: 0644]
src/network/kernel/qdnslookup.h [new file with mode: 0644]
src/network/kernel/qdnslookup_p.h [new file with mode: 0644]
src/network/kernel/qdnslookup_unix.cpp [new file with mode: 0644]
src/network/kernel/qdnslookup_win.cpp [new file with mode: 0644]
tests/auto/network/kernel/kernel.pro
tests/auto/network/kernel/qdnslookup/qdnslookup.pro [new file with mode: 0644]
tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp [new file with mode: 0644]
tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro [new file with mode: 0644]
tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp [new file with mode: 0644]

diff --git a/doc/src/snippets/code/src_network_kernel_qdnslookup.cpp b/doc/src/snippets/code/src_network_kernel_qdnslookup.cpp
new file mode 100644 (file)
index 0000000..f2e530f
--- /dev/null
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! [0]
+void MyObject::lookupServers()
+{
+    // Create a DNS lookup.
+    dns = new QDnsLookup(this);
+    connect(dns, SIGNAL(finished()),
+            this, SLOT(handleServers()));
+
+    // Find the XMPP servers for gmail.com
+    dns->setType(QDnsLookup::SRV);
+    dns->setName("_xmpp-client._tcp.gmail.com");
+    dns->lookup();
+}
+//! [0]
+
+
+//! [1]
+void MyObject::handleServers()
+{
+    // Check the lookup succeeded.
+    if (dns->error() != QDnsLookup::NoError) {
+        qWarning("DNS lookup failed");
+        dns->deleteLater();
+        return;
+    }
+
+    // Handle the results.
+    foreach (const QDnsServiceRecord &record, dns->serviceRecords()) {
+        ...
+    }
+    dns->deleteLater();
+}
+//! [1]
diff --git a/examples/network/dnslookup/dnslookup.cpp b/examples/network/dnslookup/dnslookup.cpp
new file mode 100644 (file)
index 0000000..90cf914
--- /dev/null
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "dnslookup.h"
+
+#include <QCoreApplication>
+#include <QDnsLookup>
+#include <QHostAddress>
+#include <QStringList>
+#include <QTimer>
+
+#include <stdio.h>
+
+static void usage() {
+    printf("Qt DNS example - performs DNS lookups\n"
+           "Usage: dnslookup [-t <type>] name\n\n");
+}
+
+DnsManager::DnsManager()
+{
+    dns = new QDnsLookup(this);
+    connect(dns, SIGNAL(finished()), this, SLOT(showResults()));
+}
+
+void DnsManager::execute()
+{
+    QStringList args = QCoreApplication::instance()->arguments();
+    args.takeFirst();
+
+    // lookup type
+    dns->setType(QDnsLookup::A);
+    if (args.size() > 1 && args.first() == "-t") {
+        args.takeFirst();
+        const QString type = args.takeFirst().toLower();
+        if (type == "a")
+            dns->setType(QDnsLookup::A);
+        else if (type == "aaaa")
+            dns->setType(QDnsLookup::AAAA);
+        else if (type == "any")
+            dns->setType(QDnsLookup::ANY);
+        else if (type == "cname")
+            dns->setType(QDnsLookup::CNAME);
+        else if (type == "mx")
+            dns->setType(QDnsLookup::MX);
+        else if (type == "ns")
+            dns->setType(QDnsLookup::NS);
+        else if (type == "ptr")
+            dns->setType(QDnsLookup::PTR);
+        else if (type == "srv")
+            dns->setType(QDnsLookup::SRV);
+        else if (type == "txt")
+            dns->setType(QDnsLookup::TXT);
+        else {
+            printf("Bad record type: %s\n", qPrintable(type));
+            QCoreApplication::instance()->quit();
+            return;
+        }
+    }
+    if (args.isEmpty()) {
+        usage();
+        QCoreApplication::instance()->quit();
+        return;
+    }
+    dns->setName(args.takeFirst());
+    dns->lookup();
+}
+
+void DnsManager::showResults()
+{
+    if (dns->error() != QDnsLookup::NoError)
+        printf("Error: %i (%s)\n", dns->error(), qPrintable(dns->errorString()));
+
+    // CNAME records
+    foreach (const QDnsDomainNameRecord &record, dns->canonicalNameRecords())
+        printf("%s\t%i\tIN\tCNAME\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value()));
+
+    // A and AAAA records
+    foreach (const QDnsHostAddressRecord &record, dns->hostAddressRecords()) {
+        const char *type = (record.value().protocol() == QAbstractSocket::IPv6Protocol) ? "AAAA" : "A";
+        printf("%s\t%i\tIN\t%s\t%s\n", qPrintable(record.name()), record.timeToLive(), type, qPrintable(record.value().toString()));
+    }
+
+    // MX records
+    foreach (const QDnsMailExchangeRecord &record, dns->mailExchangeRecords())
+        printf("%s\t%i\tIN\tMX\t%u %s\n", qPrintable(record.name()), record.timeToLive(), record.preference(), qPrintable(record.exchange()));
+
+    // NS records
+    foreach (const QDnsDomainNameRecord &record, dns->nameServerRecords())
+        printf("%s\t%i\tIN\tNS\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value()));
+
+    // PTR records
+    foreach (const QDnsDomainNameRecord &record, dns->pointerRecords())
+        printf("%s\t%i\tIN\tPTR\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value()));
+
+    // SRV records
+    foreach (const QDnsServiceRecord &record, dns->serviceRecords())
+        printf("%s\t%i\tIN\tSRV\t%u %u %u %s\n", qPrintable(record.name()), record.timeToLive(), record.priority(), record.weight(), record.port(), qPrintable(record.target()));
+
+    // TXT records
+    foreach (const QDnsTextRecord &record, dns->textRecords()) {
+        QStringList values;
+        foreach (const QByteArray &ba, record.values())
+            values << "\"" + QString::fromAscii(ba) + "\"";
+        printf("%s\t%i\tIN\tTXT\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(values.join(" ")));
+    }
+
+    QCoreApplication::instance()->quit();
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    DnsManager manager;
+    QTimer::singleShot(0, &manager, SLOT(execute()));
+
+    return app.exec();
+}
diff --git a/examples/network/dnslookup/dnslookup.h b/examples/network/dnslookup/dnslookup.h
new file mode 100644 (file)
index 0000000..3c59173
--- /dev/null
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QObject>
+
+QT_BEGIN_NAMESPACE
+class QDnsLookup;
+QT_END_NAMESPACE
+
+class DnsManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    DnsManager();
+
+public slots:
+    void execute();
+    void showResults();
+
+private:
+    QDnsLookup *dns;
+};
+
diff --git a/examples/network/dnslookup/dnslookup.pro b/examples/network/dnslookup/dnslookup.pro
new file mode 100644 (file)
index 0000000..160666d
--- /dev/null
@@ -0,0 +1,12 @@
+TEMPLATE = app
+QT = core network
+mac:CONFIG -= app_bundle
+win32:CONFIG += console
+HEADERS += dnslookup.h
+SOURCES += dnslookup.cpp
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/dnslookup
+sources.files = $$SOURCES $$HEADERS $$FORMS $$RESOURCES *.pro
+sources.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/dnslookup
+INSTALLS += target sources
index 0496cbb..4342c81 100644 (file)
@@ -1,5 +1,6 @@
 TEMPLATE      = subdirs
 SUBDIRS       = \
+                dnslookup \
                 download \
                 downloadmanager
 
index d6e0997..ea937da 100644 (file)
@@ -5,6 +5,8 @@ INCLUDEPATH += $$PWD
 
 HEADERS += kernel/qauthenticator.h \
           kernel/qauthenticator_p.h \
+           kernel/qdnslookup.h \
+           kernel/qdnslookup_p.h \
            kernel/qhostaddress.h \
            kernel/qhostinfo.h \
            kernel/qhostinfo_p.h \
@@ -14,15 +16,16 @@ HEADERS += kernel/qauthenticator.h \
           kernel/qnetworkinterface_p.h
 
 SOURCES += kernel/qauthenticator.cpp \
+           kernel/qdnslookup.cpp \
            kernel/qhostaddress.cpp \
            kernel/qhostinfo.cpp \
            kernel/qurlinfo.cpp \
            kernel/qnetworkproxy.cpp \
           kernel/qnetworkinterface.cpp
 
-unix:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp
-win32:SOURCES += kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp
-integrity:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp
+unix:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp
+win32:SOURCES += kernel/qdnslookup_win.cpp kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp
+integrity:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp
 
 mac:LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation
 mac:SOURCES += kernel/qnetworkproxy_mac.cpp
diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp
new file mode 100644 (file)
index 0000000..f4b143a
--- /dev/null
@@ -0,0 +1,988 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdnslookup.h"
+#include "qdnslookup_p.h"
+
+#include <qcoreapplication.h>
+#include <qdatetime.h>
+#include <qthreadstorage.h>
+#include <qurl.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_GLOBAL_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool);
+Q_GLOBAL_STATIC(QThreadStorage<bool *>, theDnsLookupSeedStorage);
+
+static bool qt_qdnsmailexchangerecord_less_than(const QDnsMailExchangeRecord &r1, const QDnsMailExchangeRecord &r2)
+{
+    // Lower numbers are more preferred than higher ones.
+    return r1.preference() < r2.preference();
+}
+
+/*!
+    Sorts a list of QDnsMailExchangeRecord objects according to RFC 5321.
+*/
+
+static void qt_qdnsmailexchangerecord_sort(QList<QDnsMailExchangeRecord> &records)
+{
+    // If we have no more than one result, we are done.
+    if (records.size() <= 1)
+        return;
+
+    // Order the records by preference.
+    qSort(records.begin(), records.end(), qt_qdnsmailexchangerecord_less_than);
+
+    int i = 0;
+    while (i < records.size()) {
+
+        // Determine the slice of records with the current preference.
+        QList<QDnsMailExchangeRecord> slice;
+        const quint16 slicePreference = records[i].preference();
+        for (int j = i; j < records.size(); ++j) {
+            if (records[j].preference() != slicePreference)
+                break;
+            slice << records[j];
+        }
+
+        // Randomize the slice of records.
+        while (!slice.isEmpty()) {
+            const unsigned int pos = qrand() % slice.size();
+            records[i++] = slice.takeAt(pos);
+        }
+    }
+}
+
+static bool qt_qdnsservicerecord_less_than(const QDnsServiceRecord &r1, const QDnsServiceRecord &r2)
+{
+    // Order by priority, or if the priorities are equal,
+    // put zero weight records first.
+    return r1.priority() < r2.priority()
+       || (r1.priority() == r2.priority()
+        && r1.weight() == 0 && r2.weight() > 0);
+}
+
+/*!
+    Sorts a list of QDnsServiceRecord objects according to RFC 2782.
+*/
+
+static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records)
+{
+    // If we have no more than one result, we are done.
+    if (records.size() <= 1)
+        return;
+
+    // Order the records by priority, and for records with an equal
+    // priority, put records with a zero weight first.
+    qSort(records.begin(), records.end(), qt_qdnsservicerecord_less_than);
+
+    int i = 0;
+    while (i < records.size()) {
+
+        // Determine the slice of records with the current priority.
+        QList<QDnsServiceRecord> slice;
+        const quint16 slicePriority = records[i].priority();
+        unsigned int sliceWeight = 0;
+        for (int j = i; j < records.size(); ++j) {
+            if (records[j].priority() != slicePriority)
+                break;
+            sliceWeight += records[j].weight();
+            slice << records[j];
+        }
+#ifdef QDNSLOOKUP_DEBUG
+        qDebug("qt_qdnsservicerecord_sort() : priority %i (size: %i, total weight: %i)",
+               slicePriority, slice.size(), sliceWeight);
+#endif
+
+        // Order the slice of records.
+        while (!slice.isEmpty()) {
+            const unsigned int weightThreshold = qrand() % (sliceWeight + 1);
+            unsigned int summedWeight = 0;
+            for (int j = 0; j < slice.size(); ++j) {
+                summedWeight += slice[j].weight();
+                if (summedWeight >= weightThreshold) {
+#ifdef QDNSLOOKUP_DEBUG
+                    qDebug("qt_qdnsservicerecord_sort() : adding %s %i (weight: %i)",
+                           qPrintable(slice[j].target()), slice[j].port(),
+                           slice[j].weight());
+#endif
+                    // Adjust the slice weight and take the current record.
+                    sliceWeight -= slice[j].weight();
+                    records[i++] = slice.takeAt(j);
+                    break;
+                }
+            }
+        }
+    }
+}
+
+/*!
+    \class QDnsLookup
+    \brief The QDnsLookup class represents a DNS lookup.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    QDnsLookup uses the mechanisms provided by the operating system to perform
+    DNS lookups. To perform a lookup you need to specify a \l name and \l type
+    then invoke the \l{QDnsLookup::lookup()}{lookup()} slot. The
+    \l{QDnsLookup::finished()}{finished()} signal will be emitted upon
+    completion.
+
+    For example, you can determine which servers an XMPP chat client should
+    connect to for a given domain with:
+
+    \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 0
+
+    Once the request finishes you can handle the results with:
+
+    \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 1
+
+    \note If you simply want to find the IP address(es) associated with a host
+    name, or the host name associated with an IP address you should use
+    QHostInfo instead.
+*/
+
+/*!
+    \enum QDnsLookup::Error
+
+    Indicates all possible error conditions found during the
+    processing of the DNS lookup.
+
+    \value NoError              no error condition.
+
+    \value ResolverError        there was an error initializing the system's
+    DNS resolver.
+
+    \value OperationCancelledError  the lookup was aborted using the abort()
+    method.
+
+    \value InvalidRequestError  the requested DNS lookup was invalid.
+
+    \value InvalidReplyError    the reply returned by the server was invalid.
+
+    \value ServerFailureError   the server encountered an internal failure
+    while processing the request (SERVFAIL).
+
+    \value ServerRefusedError   the server refused to process the request for
+    security or policy reasons (REFUSED).
+
+    \value NotFoundError        the requested domain name does not exist
+    (NXDOMAIN).
+*/
+
+/*!
+    \enum QDnsLookup::Type
+
+    Indicates the type of DNS lookup that was performed.
+
+    \value A        IPv4 address records.
+
+    \value AAAA     IPv6 address records.
+
+    \value ANY      any records.
+
+    \value CNAME    canonical name records.
+
+    \value MX       mail exchange records.
+
+    \value NS       name server records.
+
+    \value PTR      pointer records.
+
+    \value SRV      service records.
+
+    \value TXT      text records.
+*/
+
+/*!
+    \fn void QDnsLookup::finished()
+
+    This signal is emitted when the reply has finished processing.
+*/
+
+/*!
+    \fn void QDnsLookup::nameChanged(const QString &name)
+
+    This signal is emitted when the lookup \l name changes.
+    \a name is the new lookup name.
+*/
+
+/*!
+    \fn void QDnsLookup::typeChanged(Type type)
+
+    This signal is emitted when the lookup \l type changes.
+    \a type is the new lookup type.
+*/
+
+/*!
+    Constructs a QDnsLookup object and sets \a parent as the parent object.
+
+    The \l type property will default to QDnsLookup::A.
+*/
+
+QDnsLookup::QDnsLookup(QObject *parent)
+    : QObject(*new QDnsLookupPrivate, parent)
+{
+    qRegisterMetaType<QDnsLookupReply>();
+}
+/*!
+    Constructs a QDnsLookup object for the given \a type and \a name and sets
+    \a parent as the parent object.
+*/
+
+QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent)
+    : QObject(*new QDnsLookupPrivate, parent)
+{
+    Q_D(QDnsLookup);
+    qRegisterMetaType<QDnsLookupReply>();
+    d->name = name;
+    d->type = type;
+}
+
+/*!
+    Destroys the QDnsLookup object.
+
+    It is safe to delete a QDnsLookup object even if it is not finished, you
+    will simply never receive its results.
+*/
+
+QDnsLookup::~QDnsLookup()
+{
+}
+
+/*!
+    \property QDnsLookup::error
+    \brief the type of error that occurred if the DNS lookup failed, or NoError.
+*/
+
+QDnsLookup::Error QDnsLookup::error() const
+{
+    return d_func()->reply.error;
+}
+
+/*!
+    \property QDnsLookup::errorString
+    \brief a human-readable description of the error if the DNS lookup failed.
+*/
+
+QString QDnsLookup::errorString() const
+{
+    return d_func()->reply.errorString;
+}
+
+/*!
+    \property QDnsLookup::finished
+    \brief whether the reply has finished or was aborted.
+*/
+
+bool QDnsLookup::isFinished() const
+{
+    return d_func()->isFinished;
+}
+
+/*!
+    \property QDnsLookup::name
+    \brief the name to lookup.
+
+    \note The name will be encoded using IDNA, which means it's unsuitable for
+    querying SRV records compatible with the DNS-SD specification.
+*/
+
+QString QDnsLookup::name() const
+{
+    return d_func()->name;
+}
+
+void QDnsLookup::setName(const QString &name)
+{
+    Q_D(QDnsLookup);
+    if (name != d->name) {
+        d->name = name;
+        emit nameChanged(name);
+    }
+}
+
+/*!
+    \property QDnsLookup::type
+    \brief the type of DNS lookup.
+*/
+
+QDnsLookup::Type QDnsLookup::type() const
+{
+    return d_func()->type;
+}
+
+void QDnsLookup::setType(Type type)
+{
+    Q_D(QDnsLookup);
+    if (type != d->type) {
+        d->type = type;
+        emit typeChanged(type);
+    }
+}
+
+/*!
+    Returns the list of canonical name records associated with this lookup.
+*/
+
+QList<QDnsDomainNameRecord> QDnsLookup::canonicalNameRecords() const
+{
+    return d_func()->reply.canonicalNameRecords;
+}
+
+/*!
+    Returns the list of host address records associated with this lookup.
+*/
+
+QList<QDnsHostAddressRecord> QDnsLookup::hostAddressRecords() const
+{
+    return d_func()->reply.hostAddressRecords;
+}
+
+/*!
+    Returns the list of mail exchange records associated with this lookup.
+
+    The records are sorted according to
+    \l{http://www.rfc-editor.org/rfc/rfc5321.txt}{RFC 5321}, so if you use them
+    to connect to servers, you should try them in the order they are listed.
+*/
+
+QList<QDnsMailExchangeRecord> QDnsLookup::mailExchangeRecords() const
+{
+    return d_func()->reply.mailExchangeRecords;
+}
+
+/*!
+    Returns the list of name server records associated with this lookup.
+*/
+
+QList<QDnsDomainNameRecord> QDnsLookup::nameServerRecords() const
+{
+    return d_func()->reply.nameServerRecords;
+}
+
+/*!
+    Returns the list of pointer records associated with this lookup.
+*/
+
+QList<QDnsDomainNameRecord> QDnsLookup::pointerRecords() const
+{
+    return d_func()->reply.pointerRecords;
+}
+
+/*!
+    Returns the list of service records associated with this lookup.
+
+    The records are sorted according to
+    \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}, so if you use them
+    to connect to servers, you should try them in the order they are listed.
+*/
+
+QList<QDnsServiceRecord> QDnsLookup::serviceRecords() const
+{
+    return d_func()->reply.serviceRecords;
+}
+
+/*!
+    Returns the list of text records associated with this lookup.
+*/
+
+QList<QDnsTextRecord> QDnsLookup::textRecords() const
+{
+    return d_func()->reply.textRecords;
+}
+
+/*!
+    Aborts the DNS lookup operation.
+
+    If the lookup is already finished, does nothing.
+*/
+
+void QDnsLookup::abort()
+{
+    Q_D(QDnsLookup);
+    if (d->runnable) {
+        d->runnable = 0;
+        d->reply = QDnsLookupReply();
+        d->reply.error = QDnsLookup::OperationCancelledError;
+        d->reply.errorString = tr("Operation cancelled");
+        d->isFinished = true;
+        emit finished();
+    }
+}
+
+/*!
+    Performs the DNS lookup.
+
+    The \l{QDnsLookup::finished()}{finished()} signal is emitted upon completion.
+*/
+
+void QDnsLookup::lookup()
+{
+    Q_D(QDnsLookup);
+    d->isFinished = false;
+    d->reply = QDnsLookupReply();
+    d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name));
+    connect(d->runnable, SIGNAL(finished(QDnsLookupReply)),
+            this, SLOT(_q_lookupFinished(QDnsLookupReply)),
+            Qt::BlockingQueuedConnection);
+    theDnsLookupThreadPool()->start(d->runnable);
+}
+
+/*!
+    \class QDnsDomainNameRecord
+    \brief The QDnsDomainNameRecord class stores information about a domain
+    name record.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    When performing a name server lookup, zero or more records will be returned.
+    Each record is represented by a QDnsDomainNameRecord instance.
+
+    \sa QDnsLookup
+*/
+
+/*!
+    Constructs an empty domain name record object.
+*/
+
+QDnsDomainNameRecord::QDnsDomainNameRecord()
+    : d(new QDnsDomainNameRecordPrivate)
+{
+}
+
+/*!
+    Constructs a copy of \a other.
+*/
+
+QDnsDomainNameRecord::QDnsDomainNameRecord(const QDnsDomainNameRecord &other)
+    : d(other.d)
+{
+}
+
+/*!
+    Destroys a domain name record.
+*/
+
+QDnsDomainNameRecord::~QDnsDomainNameRecord()
+{
+}
+
+/*!
+    Returns the name for this record.
+*/
+
+QString QDnsDomainNameRecord::name() const
+{
+    return d->name;
+}
+
+/*!
+    Returns the duration in seconds for which this record is valid.
+*/
+
+quint32 QDnsDomainNameRecord::timeToLive() const
+{
+    return d->timeToLive;
+}
+
+/*!
+    Returns the value for this domain name record.
+*/
+
+QString QDnsDomainNameRecord::value() const
+{
+    return d->value;
+}
+
+/*!
+    Assigns the data of the \a other object to this record object,
+    and returns a reference to it.
+*/
+
+QDnsDomainNameRecord &QDnsDomainNameRecord::operator=(const QDnsDomainNameRecord &other)
+{
+    d = other.d;
+    return *this;
+}
+
+/*!
+    \class QDnsHostAddressRecord
+    \brief The QDnsHostAddressRecord class stores information about a host
+    address record.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    When performing an address lookup, zero or more records will be
+    returned. Each record is represented by a QDnsHostAddressRecord instance.
+
+    \sa QDnsLookup
+*/
+
+/*!
+    Constructs an empty host address record object.
+*/
+
+QDnsHostAddressRecord::QDnsHostAddressRecord()
+    : d(new QDnsHostAddressRecordPrivate)
+{
+}
+
+/*!
+    Constructs a copy of \a other.
+*/
+
+QDnsHostAddressRecord::QDnsHostAddressRecord(const QDnsHostAddressRecord &other)
+    : d(other.d)
+{
+}
+
+/*!
+    Destroys a host address record.
+*/
+
+QDnsHostAddressRecord::~QDnsHostAddressRecord()
+{
+}
+
+/*!
+    Returns the name for this record.
+*/
+
+QString QDnsHostAddressRecord::name() const
+{
+    return d->name;
+}
+
+/*!
+    Returns the duration in seconds for which this record is valid.
+*/
+
+quint32 QDnsHostAddressRecord::timeToLive() const
+{
+    return d->timeToLive;
+}
+
+/*!
+    Returns the value for this host address record.
+*/
+
+QHostAddress QDnsHostAddressRecord::value() const
+{
+    return d->value;
+}
+
+/*!
+    Assigns the data of the \a other object to this record object,
+    and returns a reference to it.
+*/
+
+QDnsHostAddressRecord &QDnsHostAddressRecord::operator=(const QDnsHostAddressRecord &other)
+{
+    d = other.d;
+    return *this;
+}
+
+/*!
+    \class QDnsMailExchangeRecord
+    \brief The QDnsMailExchangeRecord class stores information about a DNS MX record.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    When performing a lookup on a service, zero or more records will be
+    returned. Each record is represented by a QDnsMailExchangeRecord instance.
+
+    The meaning of the fields is defined in
+    \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}.
+
+    \sa QDnsLookup
+*/
+
+/*!
+    Constructs an empty mail exchange record object.
+*/
+
+QDnsMailExchangeRecord::QDnsMailExchangeRecord()
+    : d(new QDnsMailExchangeRecordPrivate)
+{
+}
+
+/*!
+    Constructs a copy of \a other.
+*/
+
+QDnsMailExchangeRecord::QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other)
+    : d(other.d)
+{
+}
+
+/*!
+    Destroys a mail exchange record.
+*/
+
+QDnsMailExchangeRecord::~QDnsMailExchangeRecord()
+{
+}
+
+/*!
+    Returns the domain name of the mail exchange for this record.
+*/
+
+QString QDnsMailExchangeRecord::exchange() const
+{
+    return d->exchange;
+}
+
+/*!
+    Returns the name for this record.
+*/
+
+QString QDnsMailExchangeRecord::name() const
+{
+    return d->name;
+}
+
+/*!
+    Returns the preference for this record.
+*/
+
+quint16 QDnsMailExchangeRecord::preference() const
+{
+    return d->preference;
+}
+
+/*!
+    Returns the duration in seconds for which this record is valid.
+*/
+
+quint32 QDnsMailExchangeRecord::timeToLive() const
+{
+    return d->timeToLive;
+}
+
+/*!
+    Assigns the data of the \a other object to this record object,
+    and returns a reference to it.
+*/
+
+QDnsMailExchangeRecord &QDnsMailExchangeRecord::operator=(const QDnsMailExchangeRecord &other)
+{
+    d = other.d;
+    return *this;
+}
+
+/*!
+    \class QDnsServiceRecord
+    \brief The QDnsServiceRecord class stores information about a DNS SRV record.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    When performing a lookup on a service, zero or more records will be
+    returned. Each record is represented by a QDnsServiceRecord instance.
+
+    The meaning of the fields is defined in
+    \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}.
+
+    \sa QDnsLookup
+*/
+
+/*!
+    Constructs an empty service record object.
+*/
+
+QDnsServiceRecord::QDnsServiceRecord()
+    : d(new QDnsServiceRecordPrivate)
+{
+}
+
+/*!
+    Constructs a copy of \a other.
+*/
+
+QDnsServiceRecord::QDnsServiceRecord(const QDnsServiceRecord &other)
+    : d(other.d)
+{
+}
+
+/*!
+    Destroys a service record.
+*/
+
+QDnsServiceRecord::~QDnsServiceRecord()
+{
+}
+
+/*!
+    Returns the name for this record.
+*/
+
+QString QDnsServiceRecord::name() const
+{
+    return d->name;
+}
+
+/*!
+    Returns the port on the target host for this service record.
+*/
+
+quint16 QDnsServiceRecord::port() const
+{
+    return d->port;
+}
+
+/*!
+    Returns the priority for this service record.
+
+    A client must attempt to contact the target host with the lowest-numbered
+    priority.
+*/
+
+quint16 QDnsServiceRecord::priority() const
+{
+    return d->priority;
+}
+
+/*!
+    Returns the domain name of the target host for this service record.
+*/
+
+QString QDnsServiceRecord::target() const
+{
+    return d->target;
+}
+
+/*!
+    Returns the duration in seconds for which this record is valid.
+*/
+
+quint32 QDnsServiceRecord::timeToLive() const
+{
+    return d->timeToLive;
+}
+
+/*!
+    Returns the weight for this service record.
+
+    The weight field specifies a relative weight for entries with the same
+    priority. Entries with higher weights should be selected with a higher
+    probability.
+*/
+
+quint16 QDnsServiceRecord::weight() const
+{
+    return d->weight;
+}
+
+/*!
+    Assigns the data of the \a other object to this record object,
+    and returns a reference to it.
+*/
+
+QDnsServiceRecord &QDnsServiceRecord::operator=(const QDnsServiceRecord &other)
+{
+    d = other.d;
+    return *this;
+}
+
+/*!
+    \class QDnsTextRecord
+    \brief The QDnsTextRecord class stores information about a DNS TXT record.
+
+    \inmodule QtNetwork
+    \ingroup network
+
+    When performing a text lookup, zero or more records will be
+    returned. Each record is represented by a QDnsTextRecord instance.
+
+    The meaning of the fields is defined in
+    \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}.
+
+    \sa QDnsLookup
+*/
+
+/*!
+    Constructs an empty text record object.
+*/
+
+QDnsTextRecord::QDnsTextRecord()
+    : d(new QDnsTextRecordPrivate)
+{
+}
+
+/*!
+    Constructs a copy of \a other.
+*/
+
+QDnsTextRecord::QDnsTextRecord(const QDnsTextRecord &other)
+    : d(other.d)
+{
+}
+
+/*!
+    Destroys a text record.
+*/
+
+QDnsTextRecord::~QDnsTextRecord()
+{
+}
+
+/*!
+    Returns the name for this text record.
+*/
+
+QString QDnsTextRecord::name() const
+{
+    return d->name;
+}
+
+/*!
+    Returns the duration in seconds for which this record is valid.
+*/
+
+quint32 QDnsTextRecord::timeToLive() const
+{
+    return d->timeToLive;
+}
+
+/*!
+    Returns the values for this text record.
+*/
+
+QList<QByteArray> QDnsTextRecord::values() const
+{
+    return d->values;
+}
+
+/*!
+    Assigns the data of the \a other object to this record object,
+    and returns a reference to it.
+*/
+
+QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other)
+{
+    d = other.d;
+    return *this;
+}
+
+void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply)
+{
+    Q_Q(QDnsLookup);
+    if (runnable == q->sender()) {
+#ifdef QDNSLOOKUP_DEBUG
+        qDebug("DNS reply for %s: %i (%s)", qPrintable(name), _reply.error, qPrintable(_reply.errorString));
+#endif
+        reply = _reply;
+        runnable = 0;
+        isFinished = true;
+        emit q->finished();
+    }
+}
+
+void QDnsLookupRunnable::run()
+{
+    QDnsLookupReply reply;
+
+    // Validate input.
+    if (requestName.isEmpty()) {
+        reply.error = QDnsLookup::InvalidRequestError;
+        reply.errorString = tr("Invalid domain name");
+        emit finished(reply);
+        return;
+    }
+
+    // Perform request.
+    query(requestType, requestName, &reply);
+
+    // Sort results.
+    if (!theDnsLookupSeedStorage()->hasLocalData()) {
+        qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) ^ reinterpret_cast<quintptr>(this));
+        theDnsLookupSeedStorage()->setLocalData(new bool(true));
+    }
+    qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords);
+    qt_qdnsservicerecord_sort(reply.serviceRecords);
+
+    emit finished(reply);
+}
+
+QDnsLookupThreadPool::QDnsLookupThreadPool()
+    : signalsConnected(false)
+{
+    // Run up to 5 lookups in parallel.
+    setMaxThreadCount(5);
+}
+
+void QDnsLookupThreadPool::start(QRunnable *runnable)
+{
+    // Ensure threads complete at application destruction.
+    if (!signalsConnected) {
+        QMutexLocker signalsLocker(&signalsMutex);
+        if (!signalsConnected) {
+            QCoreApplication *app = QCoreApplication::instance();
+            if (!app) {
+                qWarning("QDnsLookup requires a QCoreApplication");
+                delete runnable;
+                return;
+            }
+
+            moveToThread(app->thread());
+            connect(app, SIGNAL(destroyed()),
+                SLOT(_q_applicationDestroyed()), Qt::DirectConnection);
+            signalsConnected = true;
+        }
+    }
+
+    QThreadPool::start(runnable);
+}
+
+void QDnsLookupThreadPool::_q_applicationDestroyed()
+{
+    waitForDone();
+    signalsConnected = false;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qdnslookup.cpp"
diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h
new file mode 100644 (file)
index 0000000..198b19d
--- /dev/null
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QDNSLOOKUP_H
+#define QDNSLOOKUP_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Network)
+
+class QHostAddress;
+class QDnsLookupPrivate;
+class QDnsDomainNameRecordPrivate;
+class QDnsHostAddressRecordPrivate;
+class QDnsMailExchangeRecordPrivate;
+class QDnsServiceRecordPrivate;
+class QDnsTextRecordPrivate;
+
+class Q_NETWORK_EXPORT QDnsDomainNameRecord
+{
+public:
+    QDnsDomainNameRecord();
+    QDnsDomainNameRecord(const QDnsDomainNameRecord &other);
+    ~QDnsDomainNameRecord();
+
+    QString name() const;
+    quint32 timeToLive() const;
+    QString value() const;
+
+    QDnsDomainNameRecord &operator=(const QDnsDomainNameRecord &other);
+
+private:
+    QSharedDataPointer<QDnsDomainNameRecordPrivate> d;
+    friend class QDnsLookupRunnable;
+};
+
+class Q_NETWORK_EXPORT QDnsHostAddressRecord
+{
+public:
+    QDnsHostAddressRecord();
+    QDnsHostAddressRecord(const QDnsHostAddressRecord &other);
+    ~QDnsHostAddressRecord();
+
+    QString name() const;
+    quint32 timeToLive() const;
+    QHostAddress value() const;
+
+    QDnsHostAddressRecord &operator=(const QDnsHostAddressRecord &other);
+
+private:
+    QSharedDataPointer<QDnsHostAddressRecordPrivate> d;
+    friend class QDnsLookupRunnable;
+};
+
+class Q_NETWORK_EXPORT QDnsMailExchangeRecord
+{
+public:
+    QDnsMailExchangeRecord();
+    QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other);
+    ~QDnsMailExchangeRecord();
+
+    QString exchange() const;
+    QString name() const;
+    quint16 preference() const;
+    quint32 timeToLive() const;
+
+    QDnsMailExchangeRecord &operator=(const QDnsMailExchangeRecord &other);
+
+private:
+    QSharedDataPointer<QDnsMailExchangeRecordPrivate> d;
+    friend class QDnsLookupRunnable;
+};
+
+class Q_NETWORK_EXPORT QDnsServiceRecord
+{
+public:
+    QDnsServiceRecord();
+    QDnsServiceRecord(const QDnsServiceRecord &other);
+    ~QDnsServiceRecord();
+
+    QString name() const;
+    quint16 port() const;
+    quint16 priority() const;
+    QString target() const;
+    quint32 timeToLive() const;
+    quint16 weight() const;
+
+    QDnsServiceRecord &operator=(const QDnsServiceRecord &other);
+
+private:
+    QSharedDataPointer<QDnsServiceRecordPrivate> d;
+    friend class QDnsLookupRunnable;
+};
+
+class Q_NETWORK_EXPORT QDnsTextRecord
+{
+public:
+    QDnsTextRecord();
+    QDnsTextRecord(const QDnsTextRecord &other);
+    ~QDnsTextRecord();
+
+    QString name() const;
+    quint32 timeToLive() const;
+    QList<QByteArray> values() const;
+
+    QDnsTextRecord &operator=(const QDnsTextRecord &other);
+
+private:
+    QSharedDataPointer<QDnsTextRecordPrivate> d;
+    friend class QDnsLookupRunnable;
+};
+
+class Q_NETWORK_EXPORT QDnsLookup : public QObject
+{
+    Q_OBJECT
+    Q_ENUMS(Error Type)
+    Q_PROPERTY(Error error READ error NOTIFY finished)
+    Q_PROPERTY(QString errorString READ errorString NOTIFY finished)
+    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+    Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged)
+
+public:
+    enum Error
+    {
+        NoError = 0,
+        ResolverError,
+        OperationCancelledError,
+        InvalidRequestError,
+        InvalidReplyError,
+        ServerFailureError,
+        ServerRefusedError,
+        NotFoundError
+    };
+
+    enum Type
+    {
+        A = 1,
+        AAAA = 28,
+        ANY = 255,
+        CNAME = 5,
+        MX = 15,
+        NS = 2,
+        PTR = 12,
+        SRV = 33,
+        TXT = 16
+    };
+
+    QDnsLookup(QObject *parent = 0);
+    QDnsLookup(Type type, const QString &name, QObject *parent = 0);
+    ~QDnsLookup();
+
+    Error error() const;
+    QString errorString() const;
+    bool isFinished() const;
+
+    QString name() const;
+    void setName(const QString &name);
+
+    Type type() const;
+    void setType(QDnsLookup::Type);
+
+    QList<QDnsDomainNameRecord> canonicalNameRecords() const;
+    QList<QDnsHostAddressRecord> hostAddressRecords() const;
+    QList<QDnsMailExchangeRecord> mailExchangeRecords() const;
+    QList<QDnsDomainNameRecord> nameServerRecords() const;
+    QList<QDnsDomainNameRecord> pointerRecords() const;
+    QList<QDnsServiceRecord> serviceRecords() const;
+    QList<QDnsTextRecord> textRecords() const;
+
+
+public Q_SLOTS:
+    void abort();
+    void lookup();
+
+Q_SIGNALS:
+    void finished();
+    void nameChanged(const QString &name);
+    void typeChanged(Type type);
+
+private:
+    Q_DECLARE_PRIVATE(QDnsLookup)
+    Q_PRIVATE_SLOT(d_func(), void _q_lookupFinished(const QDnsLookupReply &reply))
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QDNSLOOKUP_H
diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h
new file mode 100644 (file)
index 0000000..8515620
--- /dev/null
@@ -0,0 +1,213 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QDNSLOOKUP_P_H
+#define QDNSLOOKUP_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists for the convenience
+// of the QDnsLookup class.  This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtCore/qmutex.h"
+#include "QtCore/qrunnable.h"
+#include "QtCore/qsharedpointer.h"
+#include "QtCore/qthreadpool.h"
+#include "QtNetwork/qdnslookup.h"
+#include "QtNetwork/qhostaddress.h"
+#include "private/qobject_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//#define QDNSLOOKUP_DEBUG
+
+class QDnsLookupRunnable;
+
+class QDnsLookupReply
+{
+public:
+    QDnsLookupReply()
+        : error(QDnsLookup::NoError)
+    { }
+
+    QDnsLookup::Error error;
+    QString errorString;
+
+    QList<QDnsDomainNameRecord> canonicalNameRecords;
+    QList<QDnsHostAddressRecord> hostAddressRecords;
+    QList<QDnsMailExchangeRecord> mailExchangeRecords;
+    QList<QDnsDomainNameRecord> nameServerRecords;
+    QList<QDnsDomainNameRecord> pointerRecords;
+    QList<QDnsServiceRecord> serviceRecords;
+    QList<QDnsTextRecord> textRecords;
+};
+
+class QDnsLookupPrivate : public QObjectPrivate
+{
+public:
+    QDnsLookupPrivate()
+        : isFinished(false)
+        , type(QDnsLookup::A)
+        , runnable(0)
+    { }
+
+    void _q_lookupFinished(const QDnsLookupReply &reply);
+
+    bool isFinished;
+    QString name;
+    QDnsLookup::Type type;
+    QDnsLookupReply reply;
+    QDnsLookupRunnable *runnable;
+
+    Q_DECLARE_PUBLIC(QDnsLookup)
+};
+
+class QDnsLookupRunnable : public QObject, public QRunnable
+{
+    Q_OBJECT
+
+public:
+    QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name)
+        : requestType(type)
+        , requestName(name)
+    { }
+    void run();
+
+signals:
+    void finished(const QDnsLookupReply &reply);
+
+private:
+    static void query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply);
+    QDnsLookup::Type requestType;
+    QByteArray requestName;
+};
+
+class QDnsLookupThreadPool : public QThreadPool
+{
+    Q_OBJECT
+
+public:
+    QDnsLookupThreadPool();
+    void start(QRunnable *runnable);
+
+private slots:
+    void _q_applicationDestroyed();
+
+private:
+    QMutex signalsMutex;
+    bool signalsConnected;
+};
+
+class QDnsRecordPrivate : public QSharedData
+{
+public:
+    QDnsRecordPrivate()
+        : timeToLive(0)
+    { }
+
+    QString name;
+    quint32 timeToLive;
+};
+
+class QDnsDomainNameRecordPrivate : public QDnsRecordPrivate
+{
+public:
+    QDnsDomainNameRecordPrivate()
+    { }
+
+    QString value;
+};
+
+class QDnsHostAddressRecordPrivate : public QDnsRecordPrivate
+{
+public:
+    QDnsHostAddressRecordPrivate()
+    { }
+
+    QHostAddress value;
+};
+
+class QDnsMailExchangeRecordPrivate : public QDnsRecordPrivate
+{
+public:
+    QDnsMailExchangeRecordPrivate()
+        : preference(0)
+    { }
+
+    QString exchange;
+    quint16 preference;
+};
+
+class QDnsServiceRecordPrivate : public QDnsRecordPrivate
+{
+public:
+    QDnsServiceRecordPrivate()
+        : port(0),
+          priority(0),
+          weight(0)
+    { }
+
+    QString target;
+    quint16 port;
+    quint16 priority;
+    quint16 weight;
+};
+
+class QDnsTextRecordPrivate : public QDnsRecordPrivate
+{
+public:
+    QDnsTextRecordPrivate()
+    { }
+
+    QList<QByteArray> values;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QDnsLookupReply)
+
+#endif // QDNSLOOKUP_P_H
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp
new file mode 100644 (file)
index 0000000..21a7135
--- /dev/null
@@ -0,0 +1,324 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdnslookup_p.h"
+
+#include <qlibrary.h>
+#include <qscopedpointer.h>
+#include <qurl.h>
+#include <private/qmutexpool_p.h>
+
+#include <sys/types.h>
+#include <netdb.h>
+#if defined(Q_OS_MAC)
+#include <arpa/nameser.h>
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int);
+static dn_expand_proto local_dn_expand = 0;
+typedef void (*res_nclose_proto)(res_state);
+static res_nclose_proto local_res_nclose = 0;
+typedef int (*res_ninit_proto)(res_state);
+static res_ninit_proto local_res_ninit = 0;
+typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int);
+static res_nquery_proto local_res_nquery = 0;
+
+// Custom deleter to close resolver state.
+
+struct QDnsLookupStateDeleter
+{
+    static inline void cleanup(struct __res_state *pointer)
+    {
+        local_res_nclose(pointer);
+    }
+};
+
+static void resolveLibrary()
+{
+    QLibrary lib(QLatin1String("resolv"));
+    if (!lib.load())
+        return;
+
+    local_dn_expand = dn_expand_proto(lib.resolve("__dn_expand"));
+    if (!local_dn_expand)
+        local_dn_expand = dn_expand_proto(lib.resolve("dn_expand"));
+
+    local_res_nclose = res_nclose_proto(lib.resolve("__res_nclose"));
+    if (!local_res_nclose)
+        local_res_nclose = res_nclose_proto(lib.resolve("res_9_nclose"));
+    if (!local_res_nclose)
+        local_res_nclose = res_nclose_proto(lib.resolve("res_nclose"));
+
+    local_res_ninit = res_ninit_proto(lib.resolve("__res_ninit"));
+    if (!local_res_ninit)
+        local_res_ninit = res_ninit_proto(lib.resolve("res_9_ninit"));
+    if (!local_res_ninit)
+        local_res_ninit = res_ninit_proto(lib.resolve("res_ninit"));
+
+    local_res_nquery = res_nquery_proto(lib.resolve("__res_nquery"));
+    if (!local_res_nquery)
+        local_res_nquery = res_nquery_proto(lib.resolve("res_9_nquery"));
+    if (!local_res_nquery)
+        local_res_nquery = res_nquery_proto(lib.resolve("res_nquery"));
+}
+
+void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply)
+{
+    // Load dn_expand, res_ninit and res_nquery on demand.
+    static volatile bool triedResolve = false;
+    if (!triedResolve) {
+        QMutexLocker locker(QMutexPool::globalInstanceGet(&local_res_ninit));
+        if (!triedResolve) {
+            resolveLibrary();
+            triedResolve = true;
+        }
+    }
+
+    // If dn_expand, res_ninit or res_nquery is missing, fail.
+    if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) {
+        reply->error = QDnsLookup::ResolverError;
+        reply->errorString = tr("Resolver functions not found");
+        return;
+    }
+
+    // Initialize state.
+    struct __res_state state;
+    memset(&state, 0, sizeof(state));
+    if (local_res_ninit(&state) < 0) {
+        reply->error = QDnsLookup::ResolverError;
+        reply->errorString = tr("Resolver initialization failed");
+        return;
+    }
+#ifdef QDNSLOOKUP_DEBUG
+    state.options |= RES_DEBUG;
+#endif
+    QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state);
+
+    // Perform DNS query.
+    unsigned char response[PACKETSZ];
+    memset(response, 0, sizeof(response));
+    const int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, response, sizeof(response));
+
+    // Check the response header.
+    HEADER *header = (HEADER*)response;
+    const int answerCount = ntohs(header->ancount);
+    switch (header->rcode) {
+    case NOERROR:
+        break;
+    case FORMERR:
+        reply->error = QDnsLookup::InvalidRequestError;
+        reply->errorString = tr("Server could not process query");
+        return;
+    case SERVFAIL:
+        reply->error = QDnsLookup::ServerFailureError;
+        reply->errorString = tr("Server failure");
+        return;
+    case NXDOMAIN:
+        reply->error = QDnsLookup::NotFoundError;
+        reply->errorString = tr("Non existent domain");
+        return;
+    case REFUSED:
+        reply->error = QDnsLookup::ServerRefusedError;
+        reply->errorString = tr("Server refused to answer");
+        return;
+    default:
+        reply->error = QDnsLookup::InvalidReplyError;
+        reply->errorString = tr("Invalid reply received");
+        return;
+    }
+
+    // Check the reply is valid.
+    if (responseLength < int(sizeof(HEADER))) {
+        reply->error = QDnsLookup::InvalidReplyError;
+        reply->errorString = tr("Invalid reply received");
+        return;
+    }
+
+    // Skip the query host, type (2 bytes) and class (2 bytes).
+    char host[PACKETSZ], answer[PACKETSZ];
+    unsigned char *p = response + sizeof(HEADER);
+    int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
+    if (status < 0) {
+        reply->error = QDnsLookup::InvalidReplyError;
+        reply->errorString = tr("Could not expand domain name");
+        return;
+    }
+    p += status + 4;
+
+    // Extract results.
+    int answerIndex = 0;
+    while ((p < response + responseLength) && (answerIndex < answerCount)) {
+        status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
+        if (status < 0) {
+            reply->error = QDnsLookup::InvalidReplyError;
+            reply->errorString = tr("Could not expand domain name");
+            return;
+        }
+        const QString name = QUrl::fromAce(host);
+
+        p += status;
+        const quint16 type = (p[0] << 8) | p[1];
+        p += 2; // RR type
+        p += 2; // RR class
+        const quint32 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+        p += 4;
+        const quint16 size = (p[0] << 8) | p[1];
+        p += 2;
+
+        if (type == QDnsLookup::A) {
+            if (size != 4) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid IPv4 address record");
+                return;
+            }
+            const quint32 addr = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+            QDnsHostAddressRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            record.d->value = QHostAddress(addr);
+            reply->hostAddressRecords.append(record);
+        } else if (type == QDnsLookup::AAAA) {
+            if (size != 16) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid IPv6 address record");
+                return;
+            }
+            QDnsHostAddressRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            record.d->value = QHostAddress(p);
+            reply->hostAddressRecords.append(record);
+        } else if (type == QDnsLookup::CNAME) {
+            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            if (status < 0) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid canonical name record");
+                return;
+            }
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            record.d->value = QUrl::fromAce(answer);
+            reply->canonicalNameRecords.append(record);
+        } else if (type == QDnsLookup::NS) {
+            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            if (status < 0) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid name server record");
+                return;
+            }
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            record.d->value = QUrl::fromAce(answer);
+            reply->nameServerRecords.append(record);
+        } else if (type == QDnsLookup::PTR) {
+            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            if (status < 0) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid pointer record");
+                return;
+            }
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            record.d->value = QUrl::fromAce(answer);
+            reply->pointerRecords.append(record);
+        } else if (type == QDnsLookup::MX) {
+            const quint16 preference = (p[0] << 8) | p[1];
+            status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer));
+            if (status < 0) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid mail exchange record");
+                return;
+            }
+            QDnsMailExchangeRecord record;
+            record.d->exchange = QUrl::fromAce(answer);
+            record.d->name = name;
+            record.d->preference = preference;
+            record.d->timeToLive = ttl;
+            reply->mailExchangeRecords.append(record);
+        } else if (type == QDnsLookup::SRV) {
+            const quint16 priority = (p[0] << 8) | p[1];
+            const quint16 weight = (p[2] << 8) | p[3];
+            const quint16 port = (p[4] << 8) | p[5];
+            status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer));
+            if (status < 0) {
+                reply->error = QDnsLookup::InvalidReplyError;
+                reply->errorString = tr("Invalid service record");
+                return;
+            }
+            QDnsServiceRecord record;
+            record.d->name = name;
+            record.d->target = QUrl::fromAce(answer);
+            record.d->port = port;
+            record.d->priority = priority;
+            record.d->timeToLive = ttl;
+            record.d->weight = weight;
+            reply->serviceRecords.append(record);
+        } else if (type == QDnsLookup::TXT) {
+            unsigned char *txt = p;
+            QDnsTextRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ttl;
+            while (txt < p + size) {
+                const unsigned char length = *txt;
+                txt++;
+                if (txt + length > p + size) {
+                    reply->error = QDnsLookup::InvalidReplyError;
+                    reply->errorString = tr("Invalid text record");
+                    return;
+                }
+                record.d->values << QByteArray((char*)txt, length);
+                txt += length;
+            }
+            reply->textRecords.append(record);
+        }
+        p += size;
+        answerIndex++;
+    }
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/kernel/qdnslookup_win.cpp b/src/network/kernel/qdnslookup_win.cpp
new file mode 100644 (file)
index 0000000..97a82de
--- /dev/null
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdnslookup_p.h"
+
+#include <qurl.h>
+#include <private/qmutexpool_p.h>
+#include <private/qsystemlibrary_p.h>
+
+#include <windows.h>
+#include <windns.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef DNS_STATUS (*dns_query_utf8_proto)(PCSTR,WORD,DWORD,PIP4_ARRAY,PDNS_RECORD*,PVOID*);
+static dns_query_utf8_proto local_dns_query_utf8 = 0;
+typedef void (*dns_record_list_free_proto)(PDNS_RECORD,DNS_FREE_TYPE);
+static dns_record_list_free_proto local_dns_record_list_free = 0;
+
+static void resolveLibrary()
+{
+    local_dns_query_utf8 = (dns_query_utf8_proto) QSystemLibrary::resolve(QLatin1String("dnsapi"), "DnsQuery_UTF8");
+    local_dns_record_list_free = (dns_record_list_free_proto) QSystemLibrary::resolve(QLatin1String("dnsapi"), "DnsRecordListFree");
+}
+
+void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply)
+{
+    // Load DnsQuery_UTF8 and DnsRecordListFree on demand.
+    static volatile bool triedResolve = false;
+    if (!triedResolve) {
+        QMutexLocker locker(QMutexPool::globalInstanceGet(&local_dns_query_utf8));
+        if (!triedResolve) {
+            resolveLibrary();
+            triedResolve = true;
+        }
+    }
+
+    // If DnsQuery_UTF8 or DnsRecordListFree is missing, fail.
+    if (!local_dns_query_utf8 || !local_dns_record_list_free) {
+        reply->error = QDnsLookup::ResolverError,
+        reply->errorString = tr("Resolver functions not found");
+        return;
+    }
+
+    // Perform DNS query.
+    PDNS_RECORD dns_records;
+    const DNS_STATUS status = local_dns_query_utf8(requestName, requestType, DNS_QUERY_STANDARD, NULL, &dns_records, NULL);
+    switch (status) {
+    case ERROR_SUCCESS:
+        break;
+    case DNS_ERROR_RCODE_FORMAT_ERROR:
+        reply->error = QDnsLookup::InvalidRequestError;
+        reply->errorString = tr("Server could not process query");
+        return;
+    case DNS_ERROR_RCODE_SERVER_FAILURE:
+        reply->error = QDnsLookup::ServerFailureError;
+        reply->errorString = tr("Server failure");
+        return;
+    case DNS_ERROR_RCODE_NAME_ERROR:
+        reply->error = QDnsLookup::NotFoundError;
+        reply->errorString = tr("Non existent domain");
+        return;
+    case DNS_ERROR_RCODE_REFUSED:
+        reply->error = QDnsLookup::ServerRefusedError;
+        reply->errorString = tr("Server refused to answer");
+        return;
+    default:
+        reply->error = QDnsLookup::InvalidReplyError;
+        reply->errorString = tr("Invalid reply received");
+        return;
+    }
+
+    // Extract results.
+    for (PDNS_RECORD ptr = dns_records; ptr != NULL; ptr = ptr->pNext) {
+        const QString name = QUrl::fromAce((char*)ptr->pName);
+        if (ptr->wType == QDnsLookup::A) {
+            QDnsHostAddressRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress));
+            reply->hostAddressRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::AAAA) {
+            Q_IPV6ADDR addr;
+            memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR));
+
+            QDnsHostAddressRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->value = QHostAddress(addr);
+            reply->hostAddressRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::CNAME) {
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->value = QUrl::fromAce((char*)ptr->Data.Cname.pNameHost);
+            reply->canonicalNameRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::MX) {
+            QDnsMailExchangeRecord record;
+            record.d->name = name;
+            record.d->exchange = QUrl::fromAce((char*)ptr->Data.Mx.pNameExchange);
+            record.d->preference = ptr->Data.Mx.wPreference;
+            record.d->timeToLive = ptr->dwTtl;
+            reply->mailExchangeRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::NS) {
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->value = QUrl::fromAce((char*)ptr->Data.Ns.pNameHost);
+            reply->nameServerRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::PTR) {
+            QDnsDomainNameRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->value = QUrl::fromAce((char*)ptr->Data.Ptr.pNameHost);
+            reply->pointerRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::SRV) {
+            QDnsServiceRecord record;
+            record.d->name = name;
+            record.d->target = QUrl::fromAce((char*)ptr->Data.Srv.pNameTarget);
+            record.d->port = ptr->Data.Srv.wPort;
+            record.d->priority = ptr->Data.Srv.wPriority;
+            record.d->timeToLive = ptr->dwTtl;
+            record.d->weight = ptr->Data.Srv.wWeight;
+            reply->serviceRecords.append(record);
+        } else if (ptr->wType == QDnsLookup::TXT) {
+            QDnsTextRecord record;
+            record.d->name = name;
+            record.d->timeToLive = ptr->dwTtl;
+            for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) {
+                record.d->values << QByteArray((char*)ptr->Data.Txt.pStringArray[i]);
+            }
+            reply->textRecords.append(record);
+        }
+    }
+
+    local_dns_record_list_free(dns_records, DnsFreeRecordList);
+}
+
+QT_END_NAMESPACE
index d1c901d..32ba8b4 100644 (file)
@@ -1,5 +1,7 @@
 TEMPLATE=subdirs
 SUBDIRS=\
+   qdnslookup \
+   qdnslookup_appless \
    qhostinfo \
 #   qnetworkproxyfactory \ # Uses a hardcoded proxy configuration
    qauthenticator \
diff --git a/tests/auto/network/kernel/qdnslookup/qdnslookup.pro b/tests/auto/network/kernel/qdnslookup/qdnslookup.pro
new file mode 100644 (file)
index 0000000..f14ffd0
--- /dev/null
@@ -0,0 +1,7 @@
+CONFIG += testcase
+
+TARGET = tst_qdnslookup
+
+SOURCES  += tst_qdnslookup.cpp
+
+QT = core network testlib
diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp
new file mode 100644 (file)
index 0000000..3baca3c
--- /dev/null
@@ -0,0 +1,274 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtTest/QtTest>
+#include <QtNetwork/QDnsLookup>
+#include <QtNetwork/QHostAddress>
+
+static bool waitForDone(QDnsLookup *lookup)
+{
+    if (lookup->isFinished())
+        return true;
+
+    QObject::connect(lookup, SIGNAL(finished()),
+                     &QTestEventLoop::instance(), SLOT(exitLoop()));
+    QTestEventLoop::instance().enterLoop(10);
+    return !QTestEventLoop::instance().timeout();
+}
+
+class tst_QDnsLookup: public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void lookup_data();
+    void lookup();
+    void lookupReuse();
+    void lookupAbortRetry();
+};
+
+void tst_QDnsLookup::lookup_data()
+{
+    QTest::addColumn<int>("type");
+    QTest::addColumn<QString>("domain");
+    QTest::addColumn<int>("error");
+    QTest::addColumn<QString>("cname");
+    QTest::addColumn<QString>("host");
+    QTest::addColumn<QString>("mx");
+    QTest::addColumn<QString>("ns");
+    QTest::addColumn<QString>("ptr");
+    QTest::addColumn<QString>("srv");
+    QTest::addColumn<QByteArray>("txt");
+
+    QTest::newRow("a-empty") << int(QDnsLookup::A) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << ""<< "" << QByteArray();
+    QTest::newRow("a-notfound") << int(QDnsLookup::A) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("a-idn") << int(QDnsLookup::A) << QString::fromUtf8("alqualondë.troll.no") << int(QDnsLookup::NoError) << "alqualonde.troll.no" << "10.3.3.55" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("a-single") << int(QDnsLookup::A) << "lupinella.troll.no" << int(QDnsLookup::NoError) << "" << "10.3.4.6" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("a-multi") << int(QDnsLookup::A) << "multi.dev.troll.no" << int(QDnsLookup::NoError) << "" << "1.2.3.4 1.2.3.5 10.3.3.31" << "" << "" << "" << "" << QByteArray();
+
+    QTest::newRow("aaaa-empty") << int(QDnsLookup::AAAA) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("aaaa-notfound") << int(QDnsLookup::AAAA) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("aaaa-single") << int(QDnsLookup::AAAA) << "dns6-test-dev.troll.no" << int(QDnsLookup::NoError) << "" << "2001:470:1f01:115::10" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("aaaa-multi") << int(QDnsLookup::AAAA) << "multi-dns6-test-dev.troll.no" << int(QDnsLookup::NoError) << "" << "2001:470:1f01:115::11 2001:470:1f01:115::12" << "" << "" << "" << "" << QByteArray();
+
+    QTest::newRow("any-empty") << int(QDnsLookup::ANY) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("any-notfound") << int(QDnsLookup::ANY) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("any-ascii") << int(QDnsLookup::ANY) << "fluke.troll.no" << int(QDnsLookup::NoError) << "" << "10.3.3.31" << "" << "" << "" << ""  << QByteArray();
+
+    QTest::newRow("mx-empty") << int(QDnsLookup::MX) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("mx-notfound") << int(QDnsLookup::MX) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("mx-ascii") << int(QDnsLookup::MX) << "troll.no" << int(QDnsLookup::NoError) << "" << "" << "10 smtp.trolltech.com" << "" << "" << "" << QByteArray();
+    // FIXME: we need an IDN MX record in the troll.no domain
+    QTest::newRow("mx-idn") << int(QDnsLookup::MX) << QString::fromUtf8("råkat.se") << int(QDnsLookup::NoError) << "" << "" << "10 mail.cdr.se" << "" << "" << "" << QByteArray();
+
+    QTest::newRow("ns-empty") << int(QDnsLookup::NS) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("ns-notfound") << int(QDnsLookup::NS) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("ns-ascii") << int(QDnsLookup::NS) << "troll.no" << int(QDnsLookup::NoError) << "" << "" << "" << "ns-0.trolltech.net ns-1.trolltech.net ns-i.trolltech.net" << "" << "" << QByteArray();
+
+    QTest::newRow("ptr-empty") << int(QDnsLookup::PTR) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("ptr-notfound") << int(QDnsLookup::PTR) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    // FIXME: we need PTR records in the troll.no domain
+    QTest::newRow("ptr-ascii") << int(QDnsLookup::PTR) << "168.52.238.87.in-addr.arpa" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "gitorious.org" << "" << QByteArray();
+
+    QTest::newRow("srv-empty") << int(QDnsLookup::SRV) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("srv-notfound") << int(QDnsLookup::SRV) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    // FIXME: we need SRV records in the troll.no domain
+    QTest::newRow("srv-idn") << int(QDnsLookup::SRV) << QString::fromUtf8("_xmpp-client._tcp.råkat.se") << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 5224 jabber.cdr.se" << QByteArray();
+
+    QTest::newRow("txt-empty") << int(QDnsLookup::TXT) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    QTest::newRow("txt-notfound") << int(QDnsLookup::TXT) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray();
+    // FIXME: we need TXT records in the troll.no domain
+    QTest::newRow("txt-ascii") << int(QDnsLookup::TXT) << "gmail.com" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << QByteArray("v=spf1 redirect=_spf.google.com");
+}
+
+void tst_QDnsLookup::lookup()
+{
+    QFETCH(int, type);
+    QFETCH(QString, domain);
+    QFETCH(int, error);
+    QFETCH(QString, cname);
+    QFETCH(QString, host);
+    QFETCH(QString, mx);
+    QFETCH(QString, ns);
+    QFETCH(QString, ptr);
+    QFETCH(QString, srv);
+    QFETCH(QByteArray, txt);
+
+    QDnsLookup lookup;
+    lookup.setType(static_cast<QDnsLookup::Type>(type));
+    lookup.setName(domain);
+    lookup.lookup();
+    QVERIFY(waitForDone(&lookup));
+    QVERIFY(lookup.isFinished());
+    QCOMPARE(int(lookup.error()), error);
+    if (error == QDnsLookup::NoError)
+        QVERIFY(lookup.errorString().isEmpty());
+    QCOMPARE(int(lookup.type()), type);
+    QCOMPARE(lookup.name(), domain);
+
+    // canonical names
+    if (!cname.isEmpty()) {
+        QVERIFY(!lookup.canonicalNameRecords().isEmpty());
+        const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first();
+        QCOMPARE(cnameRecord.name(), domain);
+        QCOMPARE(cnameRecord.value(), cname);
+    } else {
+        QVERIFY(lookup.canonicalNameRecords().isEmpty());
+    }
+
+    // host addresses
+    const QString hostName = cname.isEmpty() ? domain : cname;
+    QStringList addresses;
+    foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) {
+        QCOMPARE(record.name(), hostName);
+        addresses << record.value().toString().toLower();
+    }
+    addresses.sort();
+    QCOMPARE(addresses.join(" "), host);
+
+    // mail exchanges
+    QStringList mailExchanges;
+    foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) {
+        QCOMPARE(record.name(), domain);
+        mailExchanges << QString("%1 %2").arg(QString::number(record.preference()), record.exchange());
+    }
+    QCOMPARE(mailExchanges.join(" "), mx);
+
+    // name servers
+    QStringList nameServers;
+    foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) {
+        QCOMPARE(record.name(), domain);
+        nameServers << record.value();
+    }
+    nameServers.sort();
+    QCOMPARE(nameServers.join(" "), ns);
+
+    // pointers
+    if (!ptr.isEmpty()) {
+        QVERIFY(!lookup.pointerRecords().isEmpty());
+        const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first();
+        QCOMPARE(ptrRecord.name(), domain);
+        QCOMPARE(ptrRecord.value(), ptr);
+    } else {
+        QVERIFY(lookup.pointerRecords().isEmpty());
+    }
+
+    // services
+    QStringList services;
+    foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) {
+        QCOMPARE(record.name(), domain);
+        services << QString("%1 %2 %3 %4").arg(
+                QString::number(record.priority()),
+                QString::number(record.weight()),
+                QString::number(record.port()),
+                record.target());
+    }
+    QCOMPARE(services.join(" "), srv);
+
+    // text
+    if (!txt.isEmpty()) {
+        QVERIFY(!lookup.textRecords().isEmpty());
+        const QDnsTextRecord firstRecord = lookup.textRecords().first();
+        QCOMPARE(firstRecord.name(), domain);
+        QCOMPARE(firstRecord.values().size(), 1);
+        QCOMPARE(firstRecord.values().first(), txt);
+    } else {
+        QVERIFY(lookup.textRecords().isEmpty());
+    }
+}
+
+void tst_QDnsLookup::lookupReuse()
+{
+    QDnsLookup lookup;
+
+    // first lookup
+    lookup.setType(QDnsLookup::A);
+    lookup.setName("lupinella.troll.no");
+    lookup.lookup();
+    QVERIFY(waitForDone(&lookup));
+    QVERIFY(lookup.isFinished());
+    QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
+    QVERIFY(!lookup.hostAddressRecords().isEmpty());
+    QCOMPARE(lookup.hostAddressRecords().first().name(), QString("lupinella.troll.no"));
+    QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("10.3.4.6"));
+
+    // second lookup
+    lookup.setType(QDnsLookup::AAAA);
+    lookup.setName("dns6-test-dev.troll.no");
+    lookup.lookup();
+    QVERIFY(waitForDone(&lookup));
+    QVERIFY(lookup.isFinished());
+    QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
+    QVERIFY(!lookup.hostAddressRecords().isEmpty());
+    QCOMPARE(lookup.hostAddressRecords().first().name(), QString("dns6-test-dev.troll.no"));
+    QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:470:1f01:115::10"));
+}
+
+
+void tst_QDnsLookup::lookupAbortRetry()
+{
+    QDnsLookup lookup;
+
+    // try and abort the lookup
+    lookup.setType(QDnsLookup::A);
+    lookup.setName("lupinella.troll.no");
+    lookup.lookup();
+    lookup.abort();
+    QVERIFY(waitForDone(&lookup));
+    QVERIFY(lookup.isFinished());
+    QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError));
+    QVERIFY(lookup.hostAddressRecords().isEmpty());
+
+    // retry a different lookup
+    lookup.setType(QDnsLookup::AAAA);
+    lookup.setName("dns6-test-dev.troll.no");
+    lookup.lookup();
+    QVERIFY(waitForDone(&lookup));
+    QVERIFY(lookup.isFinished());
+    QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
+    QVERIFY(!lookup.hostAddressRecords().isEmpty());
+    QCOMPARE(lookup.hostAddressRecords().first().name(), QString("dns6-test-dev.troll.no"));
+    QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:470:1f01:115::10"));
+}
+
+QTEST_MAIN(tst_QDnsLookup)
+#include "tst_qdnslookup.moc"
diff --git a/tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro b/tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro
new file mode 100644 (file)
index 0000000..25d76b5
--- /dev/null
@@ -0,0 +1,7 @@
+CONFIG += testcase
+
+TARGET = tst_qdnslookup_appless
+
+SOURCES  += tst_qdnslookup_appless.cpp
+
+QT = core network testlib
diff --git a/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp b/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp
new file mode 100644 (file)
index 0000000..a183cfd
--- /dev/null
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/QCoreApplication>
+#include <QtNetwork/QDnsLookup>
+#include <QtTest/QtTest>
+
+class tst_QDnsLookup_Appless : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void noApplication();
+    void recreateApplication();
+    void destroyApplicationDuringLookup();
+};
+
+void tst_QDnsLookup_Appless::noApplication()
+{
+    QTest::ignoreMessage(QtWarningMsg, "QDnsLookup requires a QCoreApplication");
+    QDnsLookup dns(QDnsLookup::A, "troll.no");
+    dns.lookup();
+}
+
+void tst_QDnsLookup_Appless::recreateApplication()
+{
+    int argc = 0;
+    char **argv = 0;
+    for (int i = 0; i < 10; ++i) {
+        QCoreApplication app(argc, argv);
+        QDnsLookup dns(QDnsLookup::A, "lupinella.troll.no");
+        dns.lookup();
+        if (!dns.isFinished()) {
+            QObject::connect(&dns, SIGNAL(finished()),
+                             &QTestEventLoop::instance(), SLOT(exitLoop()));
+            QTestEventLoop::instance().enterLoop(10);
+        }
+        QVERIFY(dns.isFinished());
+    }
+}
+
+void tst_QDnsLookup_Appless::destroyApplicationDuringLookup()
+{
+    int argc = 0;
+    char **argv = 0;
+    for (int i = 0; i < 10; ++i) {
+        QCoreApplication app(argc, argv);
+        QDnsLookup dns(QDnsLookup::A, "lupinella.troll.no");
+        dns.lookup();
+    }
+}
+
+QTEST_APPLESS_MAIN(tst_QDnsLookup_Appless)
+#include "tst_qdnslookup_appless.moc"