/*  -*- C++ -*-
 *  Copyright (C) 2003-2005 Thiago Macieira <thiago@kde.org>
 *
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "k3resolver.h"
#include "k3resolver_p.h"

#include <config-network.h>

// System includes
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>

// Qt includes
#include <QCoreApplication>
#include <QPointer>
#include <QSet>
#include <QUrl>

#include <QStringList>
#include <QSharedData>
#include <QTime>
#include <QTimer>

// KDE
#include <klocalizedstring.h>

// Us
#include "k3socketaddress.h"

#ifdef NEED_MUTEX
#ifdef __GNUC__
#warning "mutex"
#endif
QMutex getXXbyYYmutex;
#endif

#ifdef __OpenBSD__
#define USE_OPENBSD 1
#endif

using namespace KNetwork;
using namespace KNetwork::Internal;

/////////////////////////////////////////////
// class KResolverEntry

class KNetwork::KResolverEntryPrivate: public QSharedData
{
public:
    KSocketAddress addr;
    int socktype;
    int protocol;
    QString canonName;
    QByteArray encodedName;

    inline KResolverEntryPrivate() :
        socktype(0), protocol(0)
    { }
};

// default constructor
KResolverEntry::KResolverEntry() :
    d(nullptr)
{
}

// constructor with stuff
KResolverEntry::KResolverEntry(const KSocketAddress &addr, int socktype, int protocol,
                               const QString &canonName, const QByteArray &encodedName) :
    d(new KResolverEntryPrivate)
{
    d->addr = addr;
    d->socktype = socktype;
    d->protocol = protocol;
    d->canonName = canonName;
    d->encodedName = encodedName;
}

// constructor with even more stuff
KResolverEntry::KResolverEntry(const struct sockaddr *sa, quint16 salen, int socktype,
                               int protocol, const QString &canonName,
                               const QByteArray &encodedName) :
    d(new KResolverEntryPrivate)
{
    d->addr = KSocketAddress(sa, salen);
    d->socktype = socktype;
    d->protocol = protocol;
    d->canonName = canonName;
    d->encodedName = encodedName;
}

// copy constructor
KResolverEntry::KResolverEntry(const KResolverEntry &that) :
    d(nullptr)
{
    *this = that;
}

// destructor
KResolverEntry::~KResolverEntry()
{
}

// returns the socket address
KSocketAddress KResolverEntry::address() const
{
    return d->addr;
}

// returns the length
quint16 KResolverEntry::length() const
{
    return d->addr.length();
}

// returns the family
int KResolverEntry::family() const
{
    return d->addr.family();
}

// returns the canonical name
QString KResolverEntry::canonicalName() const
{
    return d->canonName;
}

// returns the encoded name
QByteArray KResolverEntry::encodedName() const
{
    return d->encodedName;
}

// returns the socket type
int KResolverEntry::socketType() const
{
    return d->socktype;
}

// returns the protocol
int KResolverEntry::protocol() const
{
    return d->protocol;
}

// assignment operator
KResolverEntry &KResolverEntry::operator= (const KResolverEntry &that)
{
    d = that.d;
    return *this;
}

/////////////////////////////////////////////
// class KResolverResults

class KNetwork::KResolverResultsPrivate: public QSharedData
{
public:
    QString node, service;
    int errorcode, syserror;

    KResolverResultsPrivate() :
        errorcode(0), syserror(0)
    { }
};

// default constructor
KResolverResults::KResolverResults()
    : d(new KResolverResultsPrivate)
{
}

// copy constructor
KResolverResults::KResolverResults(const KResolverResults &other)
    : QList<KResolverEntry>(other), d(new KResolverResultsPrivate)
{
    d = other.d;
}

// destructor
KResolverResults::~KResolverResults()
{
}

// assignment operator
KResolverResults &
KResolverResults::operator= (const KResolverResults &other)
{
    // copy over the other data
    d = other.d;

    // now let QList do the rest of the work
    QList<KResolverEntry>::operator =(other);

    return *this;
}

// gets the error code
int KResolverResults::error() const
{
    return d->errorcode;
}

// gets the system errno
int KResolverResults::systemError() const
{
    return d->syserror;
}

// sets the error codes
void KResolverResults::setError(int errorcode, int systemerror)
{
    d->errorcode = errorcode;
    d->syserror = systemerror;
}

// gets the hostname
QString KResolverResults::nodeName() const
{
    return d->node;
}

// gets the service name
QString KResolverResults::serviceName() const
{
    return d->service;
}

// sets the address
void KResolverResults::setAddress(const QString &node,
                                  const QString &service)
{
    d->node = node;
    d->service = service;
}

void KResolverResults::virtual_hook(int, void *)
{
    /*BASE::virtual_hook( id, data );*/
}

///////////////////////
// class KResolver

// default constructor
KResolver::KResolver(QObject *parent)
    : QObject(parent), d(new KResolverPrivate(this))
{
}

// constructor with host and service
KResolver::KResolver(const QString &nodename, const QString &servicename,
                     QObject *parent)
    : QObject(parent), d(new KResolverPrivate(this, nodename, servicename))
{
}

// destructor
KResolver::~KResolver()
{
    cancel(false);
    delete d;
}

// get the status
int KResolver::status() const
{
    return d->status;
}

// get the error code
int KResolver::error() const
{
    return d->errorcode;
}

// get the errno
int KResolver::systemError() const
{
    return d->syserror;
}

QString KResolver::errorString() const
{
    return errorString(error(), systemError());
}

// are we running?
bool KResolver::isRunning() const
{
    return d->status > 0 && d->status < Success;
}

// get the hostname
QString KResolver::nodeName() const
{
    return d->input.node;
}

// get the service
QString KResolver::serviceName() const
{
    return d->input.service;
}

// sets the hostname
void KResolver::setNodeName(const QString &nodename)
{
    // don't touch those values if we're working!
    if (!isRunning()) {
        d->input.node = nodename;
        d->status = Idle;
        d->results.setAddress(nodename, d->input.service);
    }
}

// sets the service
void KResolver::setServiceName(const QString &service)
{
    // don't change if running
    if (!isRunning()) {
        d->input.service = service;
        d->status = Idle;
        d->results.setAddress(d->input.node, service);
    }
}

// sets the address
void KResolver::setAddress(const QString &nodename, const QString &service)
{
    setNodeName(nodename);
    setServiceName(service);
}

// get the flags
int KResolver::flags() const
{
    return d->input.flags;
}

// sets the flags
int KResolver::setFlags(int flags)
{
    int oldflags = d->input.flags;
    if (!isRunning()) {
        d->input.flags = flags;
        d->status = Idle;
    }
    return oldflags;
}

// sets the family mask
void KResolver::setFamily(int families)
{
    if (!isRunning()) {
        d->input.familyMask = families;
        d->status = Idle;
    }
}

// sets the socket type
void KResolver::setSocketType(int type)
{
    if (!isRunning()) {
        d->input.socktype = type;
        d->status = Idle;
    }
}

// sets the protocol
void KResolver::setProtocol(int protonum, const char *name)
{
    if (isRunning()) {
        return;    // can't change now
    }

    // we copy the given protocol name. If it isn't an empty string
    // and the protocol number was 0, we will look it up in /etc/protocols
    // we also leave the error reporting to the actual lookup routines, in
    // case the given protocol name doesn't exist

    d->input.protocolName = name;
    if (protonum == 0 && name != nullptr && *name != '\0') {
        // must look up the protocol number
        d->input.protocol = KResolver::protocolNumber(name);
    } else {
        d->input.protocol = protonum;
    }
    d->status = Idle;
}

bool KResolver::start()
{
    if (!isRunning()) {
        d->results.clear();

        // is there anything to be queued?
        if (d->input.node.isEmpty() && d->input.service.isEmpty()) {
            d->status = KResolver::Success;
            emitFinished();
        } else {
            KResolverManager::manager()->enqueue(this, nullptr);
        }
    }

    return true;
}

bool KResolver::wait(int msec)
{
    if (!isRunning()) {
        emitFinished();
        return true;
    }

    QMutexLocker locker(&d->mutex);

    if (!isRunning()) {
        // it was running and no longer is?
        // That means the manager has finished its processing and has posted
        // an event for the signal to be emitted already. This means the signal
        // will be emitted twice!

        emitFinished();
        return true;
    } else {
        QTime t;
        t.start();

        while (!msec || t.elapsed() < msec) {
            // wait on the manager to broadcast completion
            d->waiting = true;
            if (msec) {
                KResolverManager::manager()->notifyWaiters.wait(&d->mutex, msec - t.elapsed());
            } else {
                KResolverManager::manager()->notifyWaiters.wait(&d->mutex);
            }

            // the manager has processed
            // see if this object is done
            if (!isRunning()) {
                // it's done
                d->waiting = false;
                emitFinished();
                return true;
            }
        }

        // if we've got here, we've timed out
        d->waiting = false;
        return false;
    }
}

void KResolver::cancel(bool emitSignal)
{
    KResolverManager::manager()->dequeue(this);
    if (emitSignal) {
        emitFinished();
    }
}

KResolverResults
KResolver::results() const
{
    if (!isRunning()) {
        return d->results;
    }

    // return a dummy, empty result
    KResolverResults r;
    r.setAddress(d->input.node, d->input.service);
    r.setError(d->errorcode, d->syserror);
    return r;
}

bool KResolver::event(QEvent *e)
{
    if (static_cast<int>(e->type()) == KResolverManager::ResolutionCompleted) {
        emitFinished();
        return true;
    }

    return QObject::event(e);
}

void KResolver::emitFinished()
{
    if (isRunning()) {
        d->status = KResolver::Success;
    }

    QPointer<QObject> p = this; // guard against deletion

    emit finished(d->results);

    if (p && d->deleteWhenDone) {
        deleteLater();    // in QObject
    }
}

QString KResolver::errorString(int errorcode, int syserror)
{
    // no i18n now...
    static const char messages[] = {
        I18N_NOOP("no error")"\0"   // NoError
        I18N_NOOP("requested family not supported for this host name")"\0" // AddrFamily
        I18N_NOOP("temporary failure in name resolution")"\0"   // TryAgain
        I18N_NOOP("non-recoverable failure in name resolution")"\0" // NonRecoverable
        I18N_NOOP("invalid flags")"\0"      // BadFlags
        I18N_NOOP("memory allocation failure")"\0"  // Memory
        I18N_NOOP("name or service not known")"\0"  // NoName
        I18N_NOOP("requested family not supported")"\0" // UnsupportedFamily
        I18N_NOOP("requested service not supported for this socket type")"\0" // UnsupportedService
        I18N_NOOP("requested socket type not supported")"\0"    // UnsupportedSocketType
        I18N_NOOP("unknown error")"\0"          // UnknownError
        I18N_NOOP2("1: the i18n'ed system error code, from errno",
        "system error: %1")"\0"       // SystemError
        "\0"
    };
    // index table generated by generate_string_table.pl
    static const int messages_indices[] = {
        0,    9,   59,   96,  139,  153,  179,  205,
        236,  289,  325,    0
    };

    // handle the special value
    if (errorcode == Canceled) {
        return i18n("request was canceled");
    }

    Q_ASSERT(int(SystemError) <= -(int)(sizeof(messages_indices) / sizeof(messages_indices[0])));
    if (errorcode > 0 || errorcode < SystemError) {
        return QString();
    }

    QString msg = i18n(messages + messages_indices[-errorcode]);
    if (errorcode == SystemError) {
        msg = msg.arg(QString::fromLocal8Bit(strerror(syserror)));
    }

    return msg;
}

KResolverResults
KResolver::resolve(const QString &host, const QString &service, int flags,
                   int families)
{
    KResolver qres(host, service, QCoreApplication::instance());
    qres.setObjectName(QString::fromLatin1("synchronous KResolver"));
    qres.setFlags(flags);
    qres.setFamily(families);
    qres.start();
    qres.wait();
    return qres.results();
}

bool KResolver::resolveAsync(QObject *userObj, const char *userSlot,
                             const QString &host, const QString &service,
                             int flags, int families)
{
    KResolver *qres = new KResolver(host, service, QCoreApplication::instance());
    QObject::connect(qres, SIGNAL(finished(KNetwork::KResolverResults)),
                     userObj, userSlot);
    qres->setObjectName(QString::fromLatin1("asynchronous KResolver"));
    qres->setFlags(flags);
    qres->setFamily(families);
    qres->d->deleteWhenDone = true; // this is the only difference from the example code
    return qres->start();
}

QList<QByteArray> KResolver::protocolName(int protonum)
{
    struct protoent *pe;
#if !HAVE_GETPROTOBYNAME_R
    QMutexLocker locker(&getXXbyYYmutex);

    pe = getprotobynumber(protonum);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct protoent protobuf;
    struct protoent_data pdata;
    ::memset(&pdata, 0, sizeof pdata);

    if (getprotobynumber_r(protonum, &protobuf, &pdata) == 0) {
        pe = &protobuf;
    } else {
        pe = 0;
    }

# else
    size_t buflen = 1024;
    struct protoent protobuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 4 argument getprotobynumber_r which returns struct *protoent or NULL
        if ((pe = getprotobynumber_r(protonum, &protobuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getprotobynumber_r(protonum, &protobuf, buf, buflen, &pe) == ERANGE)
#  endif
        {
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (pe == nullptr);
# endif
#endif

    // Do common processing
    QList<QByteArray> lst;
    if (pe != nullptr) {
        lst.append(pe->p_name);
        for (char **p = pe->p_aliases; *p; p++) {
            lst.append(*p);
        }
    }

#if HAVE_GETPROTOBYNAME_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return lst;
}

QList<QByteArray> KResolver::protocolName(const char *protoname)
{
    struct protoent *pe = nullptr;
#if !HAVE_GETPROTOBYNAME_R
    QMutexLocker locker(&getXXbyYYmutex);

    pe = getprotobyname(protoname);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct protoent protobuf;
    struct protoent_data pdata;
    ::memset(&pdata, 0, sizeof pdata);

    if (getprotobyname_r(protoname, &protobuf, &pdata) == 0) {
        pe = &protobuf;
    } else {
        pe = 0;
    }
# else
    size_t buflen = 1024;
    struct protoent protobuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 4 argument getprotobyname_r which returns struct *protoent or NULL
        if ((pe = getprotobyname_r(protoname, &protobuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getprotobyname_r(protoname, &protobuf, buf, buflen, &pe) == ERANGE)
#  endif
        {
            pe = nullptr;
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (pe == nullptr);
# endif
#endif

    // Do common processing
    QList<QByteArray> lst;
    if (pe != nullptr) {
        lst.append(pe->p_name);
        for (char **p = pe->p_aliases; *p; p++) {
            lst.append(*p);
        }
    }

#if HAVE_GETPROTOBYNAME_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return lst;
}

int KResolver::protocolNumber(const char *protoname)
{
    struct protoent *pe = nullptr;
#if !HAVE_GETPROTOBYNAME_R
    QMutexLocker locker(&getXXbyYYmutex);

    pe = getprotobyname(protoname);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct protoent protobuf;
    struct protoent_data pdata;
    ::memset(&pdata, 0, sizeof pdata);

    if (getprotobyname_r(protoname, &protobuf, &pdata) == 0) {
        pe = &protobuf;
    } else {
        pe = 0;
    }

# else
    size_t buflen = 1024;
    struct protoent protobuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 4 argument getprotobyname_r which returns struct *protoent or NULL
        if ((pe = getprotobyname_r(protoname, &protobuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getprotobyname_r(protoname, &protobuf, buf, buflen, &pe) == ERANGE)
#  endif
        {
            pe = nullptr;
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (pe == nullptr);
# endif
#endif

    // Do common processing
    int protonum = -1;
    if (pe != nullptr) {
        protonum = pe->p_proto;
    }

#if HAVE_GETPROTOBYNAME_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return protonum;
}

int KResolver::servicePort(const char *servname, const char *protoname)
{
    struct servent *se = nullptr;
#if !HAVE_GETSERVBYNAME_R
    QMutexLocker locker(&getXXbyYYmutex);

    se = getservbyname(servname, protoname);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct servent servbuf;
    struct servent_data sdata;
    ::memset(&sdata, 0, sizeof sdata);
    if (getservbyname_r(servname, protoname, &servbuf, &sdata) == 0) {
        se = &servbuf;
    } else {
        se = 0;
    }

# else
    size_t buflen = 1024;
    struct servent servbuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 5 argument getservbyname_r which returns struct *servent or NULL
        if ((se = getservbyname_r(servname, protoname, &servbuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getservbyname_r(servname, protoname, &servbuf, buf, buflen, &se) == ERANGE)
#  endif
        {
            se = nullptr;
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (se == nullptr);
# endif
#endif

    // Do common processing
    int servport = -1;
    if (se != nullptr) {
        servport = ntohs(se->s_port);
    }

#if HAVE_GETSERVBYNAME_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return servport;
}

QList<QByteArray> KResolver::serviceName(const char *servname, const char *protoname)
{
    struct servent *se = nullptr;
#if !HAVE_GETSERVBYNAME_R
    QMutexLocker locker(&getXXbyYYmutex);

    se = getservbyname(servname, protoname);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct servent servbuf;
    struct servent_data sdata;
    ::memset(&sdata, 0, sizeof sdata);
    if (getservbyname_r(servname, protoname, &servbuf, &sdata) == 0) {
        se = &servbuf;
    } else {
        se = 0;
    }

# else
    size_t buflen = 1024;
    struct servent servbuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 5 argument getservbyname_r which returns struct *servent or NULL
        if ((se = getservbyname_r(servname, protoname, &servbuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getservbyname_r(servname, protoname, &servbuf, buf, buflen, &se) == ERANGE)
#  endif
        {
            se = nullptr;
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (se == nullptr);
# endif
#endif

    // Do common processing
    QList<QByteArray> lst;
    if (se != nullptr) {
        lst.append(se->s_name);
        for (char **p = se->s_aliases; *p; p++) {
            lst.append(*p);
        }
    }

#if HAVE_GETSERVBYNAME_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return lst;
}

QList<QByteArray> KResolver::serviceName(int port, const char *protoname)
{
    struct servent *se = nullptr;
#if !HAVE_GETSERVBYPORT_R
    QMutexLocker locker(&getXXbyYYmutex);

    se = getservbyport(port, protoname);

#else
# ifdef USE_OPENBSD // OpenBSD uses an HP/IBM/DEC API
    struct servent servbuf;
    struct servent_data sdata;
    ::memset(&sdata, 0, sizeof sdata);
    if (getservbyport_r(port, protoname, &servbuf, &sdata) == 0) {
        se = &servbuf;
    } else {
        se = 0;
    }

# else
    size_t buflen = 1024;
    struct servent servbuf;
    char *buf;
    do {
        buf = new char[buflen];
#  ifdef Q_OS_SOLARIS // Solaris uses a 5 argument getservbyport_r which returns struct *servent or NULL
        if ((se = getservbyport_r(port, protoname, &servbuf, buf, buflen)) && (errno == ERANGE))
#  else
        if (getservbyport_r(port, protoname, &servbuf, buf, buflen, &se) == ERANGE)
#  endif
        {
            se = nullptr;
            buflen += 1024;
            delete [] buf;
        } else {
            break;
        }
    } while (se == nullptr);
# endif
#endif

    // Do common processing
    QList<QByteArray> lst;
    if (se != nullptr) {
        lst.append(se->s_name);
        for (char **p = se->s_aliases; *p; p++) {
            lst.append(*p);
        }
    }

#if HAVE_GETSERVBYPORT_R
# ifndef USE_OPENBSD
    delete [] buf;
# endif
#endif

    return lst;
}

QString KResolver::localHostName()
{
    QByteArray name;
    int len;

#ifdef MAXHOSTNAMELEN
    len = MAXHOSTNAMELEN;
#else
    len = 256;
#endif

    while (true) {
        name.resize(len);

        if (gethostname(name.data(), len) == 0) {
            // Call succeeded, but it's not guaranteed to be NUL-terminated
            // Fortunately, QByteArray is always NUL-terminated

            // Note that some systems return success even if they did truncation
            break;
        }

        // Call failed
        if (errno == ENAMETOOLONG || errno == EINVAL) {
            len += 256;
        } else {
            // Oops! Unknown error!
            name.clear();
        }
    }

    if (name.isEmpty()) {
        return QLatin1String("localhost");
    }

    if (name.indexOf('.') == -1) {
        // not fully qualified
        // must resolve
        KResolverResults results = resolve(QString::fromLocal8Bit(name), QString::fromLatin1("0"), CanonName);
        if (results.isEmpty())
            // cannot find a valid hostname!
        {
            return QLatin1String("localhost");
        } else {
            return results.first().canonicalName();
        }
    }

    return domainToUnicode(name);
}

static void KResolver_initIdnDomains()
{
    static bool init = false;
    if (!init) {
        QByteArray kde_use_idn = qgetenv("KDE_USE_IDN");
        if (!kde_use_idn.isEmpty()) {
            QUrl::setIdnWhitelist(QString::fromLatin1(kde_use_idn).toLower().split(QLatin1Char(':')));
        }
        init = true;
    }
}

// implement the ToAscii function, as described by IDN documents
QByteArray KResolver::domainToAscii(const QString &unicodeDomain)
{
    KResolver_initIdnDomains();
    return QUrl::toAce(unicodeDomain);
}

QString KResolver::domainToUnicode(const QByteArray &asciiDomain)
{
    return domainToUnicode(QString::fromLatin1(asciiDomain));
}

// implement the ToUnicode function, as described by IDN documents
QString KResolver::domainToUnicode(const QString &asciiDomain)
{
    if (asciiDomain.isEmpty()) {
        return asciiDomain;
    }
    KResolver_initIdnDomains();
    return QUrl::fromAce(asciiDomain.toLatin1());
}

QString KResolver::normalizeDomain(const QString &domain)
{
    return domainToUnicode(domainToAscii(domain));
}

void KResolver::virtual_hook(int, void *)
{
    /*BASE::virtual_hook( id, data );*/
}

// here follows IDN functions
// all IDN functions conform to the following documents:
//  RFC 3454 - Preparation of Internationalized Strings
//  RFC 3490 - Internationalizing Domain Names in Applications (IDNA)
//  RFC 3491 - Nameprep: A Stringprep Profile for
//                Internationalized Domain Names (IDN
//  RFC 3492 - Punycode: A Bootstring encoding of Unicode
//          for Internationalized Domain Names in Applications (IDNA)

