summaryrefslogtreecommitdiff
path: root/src/camel/camel-net-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/camel/camel-net-utils.c')
-rw-r--r--src/camel/camel-net-utils.c848
1 files changed, 848 insertions, 0 deletions
diff --git a/src/camel/camel-net-utils.c b/src/camel/camel-net-utils.c
new file mode 100644
index 000000000..cf56e821e
--- /dev/null
+++ b/src/camel/camel-net-utils.c
@@ -0,0 +1,848 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <glib/gi18n-lib.h>
+#include <unicode/uidna.h>
+#include <unicode/ustring.h>
+
+#include "camel-msgport.h"
+#include "camel-net-utils.h"
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#ifdef HAVE_WSPIAPI_H
+#include <wspiapi.h>
+#endif
+#endif
+#include "camel-object.h"
+#include "camel-operation.h"
+#include "camel-service.h"
+
+#define d(x)
+
+/* These are GNU extensions */
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
+#ifdef G_OS_WIN32
+
+typedef gshort in_port_t;
+
+#undef gai_strerror
+#define gai_strerror my_gai_strerror
+
+/* gai_strerror() is implemented as an inline function in Microsoft's
+ * SDK, but mingw lacks that. So implement here. The EAI_* errors can
+ * be handled with the normal FormatMessage() API,
+ * i.e. g_win32_error_message().
+ */
+
+static const gchar *
+gai_strerror (gint error_code)
+{
+ gchar *msg = g_win32_error_message (error_code);
+ GQuark quark = g_quark_from_string (msg);
+ const gchar *retval = g_quark_to_string (quark);
+
+ g_free (msg);
+
+ return retval;
+}
+
+#endif
+
+/* gethostbyname emulation code for emulating getaddrinfo code ...
+ *
+ * This should probably go away */
+
+#ifdef NEED_ADDRINFO
+
+#if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
+G_LOCK_DEFINE_STATIC (gethost_mutex);
+#endif
+
+#define ALIGN(x) (((x) + (sizeof (gchar *) - 1)) & ~(sizeof (gchar *) - 1))
+
+#define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \
+ gint num_aliases = 0, num_addrs = 0; \
+ gint req_length; \
+ gchar *p; \
+ gint i; \
+ \
+ /* check to make sure we have enough room in our buffer */ \
+ req_length = 0; \
+ if (h->h_aliases) { \
+ for (i = 0; h->h_aliases[i]; i++) \
+ req_length += strlen (h->h_aliases[i]) + 1; \
+ num_aliases = i; \
+ } \
+ \
+ if (h->h_addr_list) { \
+ for (i = 0; h->h_addr_list[i]; i++) \
+ req_length += h->h_length; \
+ num_addrs = i; \
+ } \
+ \
+ req_length += sizeof (gchar *) * (num_aliases + 1); \
+ req_length += sizeof (gchar *) * (num_addrs + 1); \
+ req_length += strlen (h->h_name) + 1; \
+ \
+ if (buflen < req_length) { \
+ *herr = ERANGE; \
+ G_UNLOCK (gethost_mutex); \
+ return ERANGE; \
+ } \
+ \
+ /* we store the alias/addr pointers in the buffer */ \
+ /* their addresses here. */ \
+ p = buf; \
+ if (num_aliases) { \
+ host->h_aliases = (gchar **) p; \
+ p += sizeof (gchar *) * (num_aliases + 1); \
+ } else \
+ host->h_aliases = NULL; \
+ \
+ if (num_addrs) { \
+ host->h_addr_list = (gchar **) p; \
+ p += sizeof (gchar *) * (num_addrs + 1); \
+ } else \
+ host->h_addr_list = NULL; \
+ \
+ /* copy the host name into the buffer */ \
+ host->h_name = p; \
+ strcpy (p, h->h_name); \
+ p += strlen (h->h_name) + 1; \
+ host->h_addrtype = h->h_addrtype; \
+ host->h_length = h->h_length; \
+ \
+ /* copy the aliases/addresses into the buffer */ \
+ /* and assign pointers into the hostent */ \
+ *p = 0; \
+ if (num_aliases) { \
+ for (i = 0; i < num_aliases; i++) { \
+ strcpy (p, h->h_aliases[i]); \
+ host->h_aliases[i] = p; \
+ p += strlen (h->h_aliases[i]); \
+ } \
+ host->h_aliases[num_aliases] = NULL; \
+ } \
+ \
+ if (num_addrs) { \
+ for (i = 0; i < num_addrs; i++) { \
+ memcpy (p, h->h_addr_list[i], h->h_length); \
+ host->h_addr_list[i] = p; \
+ p += h->h_length; \
+ } \
+ host->h_addr_list[num_addrs] = NULL; \
+ } \
+} G_STMT_END
+
+#ifdef ENABLE_IPv6
+/* some helpful utils for IPv6 lookups */
+#define IPv6_BUFLEN_MIN (sizeof (gchar *) * 3)
+
+static gint
+ai_to_herr (gint error)
+{
+ switch (error) {
+ case EAI_NONAME:
+ case EAI_FAIL:
+ return HOST_NOT_FOUND;
+ break;
+ case EAI_SERVICE:
+ return NO_DATA;
+ break;
+ case EAI_ADDRFAMILY:
+ return NO_ADDRESS;
+ break;
+ case EAI_NODATA:
+ return NO_DATA;
+ break;
+ case EAI_MEMORY:
+ return ENOMEM;
+ break;
+ case EAI_AGAIN:
+ return TRY_AGAIN;
+ break;
+ case EAI_SYSTEM:
+ return errno;
+ break;
+ default:
+ return NO_RECOVERY;
+ break;
+ }
+}
+
+#endif /* ENABLE_IPv6 */
+
+static gint
+camel_gethostbyname_r (const gchar *name,
+ struct hostent *host,
+ gchar *buf,
+ gsize buflen,
+ gint *herr)
+{
+#ifdef ENABLE_IPv6
+ struct addrinfo hints, *res;
+ gint retval, len;
+ gchar *addr;
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+#ifdef HAVE_AI_ADDRCONFIG
+ hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
+#else
+ hints.ai_flags = AI_CANONNAME;
+#endif
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
+ *herr = ai_to_herr (retval);
+ return -1;
+ }
+
+ len = ALIGN (strlen (res->ai_canonname) + 1);
+ if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (gchar *))
+ return ERANGE;
+
+ /* h_name */
+ g_strlcpy (buf, res->ai_canonname, buflen);
+ host->h_name = buf;
+ buf += len;
+
+ /* h_aliases */
+ ((gchar **) buf)[0] = NULL;
+ host->h_aliases = (gchar **) buf;
+ buf += sizeof (gchar *);
+
+ /* h_addrtype and h_length */
+ host->h_length = res->ai_addrlen;
+ if (res->ai_family == PF_INET6) {
+ host->h_addrtype = AF_INET6;
+
+ addr = (gchar *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
+ } else {
+ host->h_addrtype = AF_INET;
+
+ addr = (gchar *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
+ }
+
+ memcpy (buf, addr, host->h_length);
+ addr = buf;
+ buf += ALIGN (host->h_length);
+
+ /* h_addr_list */
+ ((gchar **) buf)[0] = addr;
+ ((gchar **) buf)[1] = NULL;
+ host->h_addr_list = (gchar **) buf;
+
+ freeaddrinfo (res);
+
+ return 0;
+#else /* No support for IPv6 addresses */
+#ifdef HAVE_GETHOSTBYNAME_R
+#ifdef GETHOSTBYNAME_R_FIVE_ARGS
+ if (gethostbyname_r (name, host, buf, buflen, herr))
+ return 0;
+ else
+ return errno;
+#else
+ struct hostent *hp;
+ gint retval;
+
+ retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
+ if (hp != NULL) {
+ *herr = 0;
+ } else if (retval == 0) {
+ /* glibc 2.3.2 workaround - it seems that
+ * gethostbyname_r will sometimes return 0 on fail and
+ * not set the hostent values (hence the crash in bug
+ * #56337). Hopefully we can depend on @hp being NULL
+ * in this error case like we do with
+ * gethostbyaddr_r().
+ */
+ retval = -1;
+ }
+
+ return retval;
+#endif
+#else /* No support for gethostbyname_r */
+ struct hostent *h;
+
+ G_LOCK (gethost_mutex);
+
+ h = gethostbyname (name);
+
+ if (!h) {
+ *herr = h_errno;
+ G_UNLOCK (gethost_mutex);
+ return -1;
+ }
+
+ GETHOST_PROCESS (h, host, buf, buflen, herr);
+
+ G_UNLOCK (gethost_mutex);
+
+ return 0;
+#endif /* HAVE_GETHOSTBYNAME_R */
+#endif /* ENABLE_IPv6 */
+}
+
+static gint
+camel_gethostbyaddr_r (const gchar *addr,
+ gint addrlen,
+ gint type,
+ struct hostent *host,
+ gchar *buf,
+ gsize buflen,
+ gint *herr)
+{
+#ifdef ENABLE_IPv6
+ gint retval, len;
+
+ if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
+ *herr = ai_to_herr (retval);
+ return -1;
+ }
+
+ len = ALIGN (strlen (buf) + 1);
+ if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (gchar *))
+ return ERANGE;
+
+ /* h_name */
+ host->h_name = buf;
+ buf += len;
+
+ /* h_aliases */
+ ((gchar **) buf)[0] = NULL;
+ host->h_aliases = (gchar **) buf;
+ buf += sizeof (gchar *);
+
+ /* h_addrtype and h_length */
+ host->h_length = addrlen;
+ host->h_addrtype = type;
+
+ memcpy (buf, addr, host->h_length);
+ addr = buf;
+ buf += ALIGN (host->h_length);
+
+ /* h_addr_list */
+ ((gchar **) buf)[0] = addr;
+ ((gchar **) buf)[1] = NULL;
+ host->h_addr_list = (gchar **) buf;
+
+ return 0;
+#else /* No support for IPv6 addresses */
+#ifdef HAVE_GETHOSTBYADDR_R
+#ifdef GETHOSTBYADDR_R_SEVEN_ARGS
+ if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr))
+ return 0;
+ else
+ return errno;
+#else
+ struct hostent *hp;
+ gint retval;
+
+ retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
+ if (hp != NULL) {
+ *herr = 0;
+ retval = 0;
+ } else if (retval == 0) {
+ /* glibc 2.3.2 workaround - it seems that
+ * gethostbyaddr_r will sometimes return 0 on fail and
+ * fill @host with garbage strings from /etc/hosts
+ * (failure to parse the file? who knows). Luckily, it
+ * seems that we can rely on @hp being NULL on
+ * fail.
+ */
+ retval = -1;
+ }
+
+ return retval;
+#endif
+#else /* No support for gethostbyaddr_r */
+ struct hostent *h;
+
+ G_LOCK (gethost_mutex);
+
+ h = gethostbyaddr (addr, addrlen, type);
+
+ if (!h) {
+ *herr = h_errno;
+ G_UNLOCK (gethost_mutex);
+ return -1;
+ }
+
+ GETHOST_PROCESS (h, host, buf, buflen, herr);
+
+ G_UNLOCK (gethost_mutex);
+
+ return 0;
+#endif /* HAVE_GETHOSTBYADDR_R */
+#endif /* ENABLE_IPv6 */
+}
+#endif /* NEED_ADDRINFO */
+
+/* ********************************************************************** */
+struct _addrinfo_msg {
+ CamelMsg msg;
+ guint cancelled : 1;
+
+ /* for host lookup */
+ const gchar *name;
+ const gchar *service;
+ gint result;
+ const struct addrinfo *hints;
+ struct addrinfo **res;
+
+ /* for host lookup emulation */
+#ifdef NEED_ADDRINFO
+ struct hostent hostbuf;
+ gint hostbuflen;
+ gchar *hostbufmem;
+#endif
+
+ /* for name lookup */
+ const struct sockaddr *addr;
+ socklen_t addrlen;
+ gchar *host;
+ gint hostlen;
+ gchar *serv;
+ gint servlen;
+ gint flags;
+};
+
+static void
+cs_freeinfo (struct _addrinfo_msg *msg)
+{
+ g_free (msg->host);
+ g_free (msg->serv);
+#ifdef NEED_ADDRINFO
+ g_free (msg->hostbufmem);
+#endif
+ g_free (msg);
+}
+
+/* returns -1 if we didn't wait for reply from thread */
+static gint
+cs_waitinfo (gpointer (worker)(gpointer),
+ struct _addrinfo_msg *msg,
+ const gchar *errmsg,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMsgPort *reply_port;
+ GThread *thread;
+ gint cancel_fd, cancel = 0, fd;
+
+ cancel_fd = g_cancellable_get_fd (cancellable);
+ if (cancel_fd == -1) {
+ worker (msg);
+ return 0;
+ }
+
+ reply_port = msg->msg.reply_port = camel_msgport_new ();
+ fd = camel_msgport_fd (msg->msg.reply_port);
+ if ((thread = g_thread_new (NULL, worker, msg)) != NULL) {
+ gint status;
+#ifndef G_OS_WIN32
+ GPollFD polls[2];
+
+ polls[0].fd = fd;
+ polls[0].events = G_IO_IN;
+ polls[1].fd = cancel_fd;
+ polls[1].events = G_IO_IN;
+
+ d (printf ("waiting for name return/cancellation in main process\n"));
+ do {
+ polls[0].revents = 0;
+ polls[1].revents = 0;
+ status = g_poll (polls, 2, -1);
+ } while (status == -1 && errno == EINTR);
+#else
+ fd_set read_set;
+
+ FD_ZERO (&read_set);
+ FD_SET (fd, &read_set);
+ FD_SET (cancel_fd, &read_set);
+
+ status = select (MAX (fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
+#endif
+
+ if (status == -1 ||
+#ifndef G_OS_WIN32
+ (polls[1].revents & G_IO_IN)
+#else
+ FD_ISSET (cancel_fd, &read_set)
+#endif
+ ) {
+ if (status == -1)
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s: %s", errmsg,
+#ifndef G_OS_WIN32
+ g_strerror (errno)
+#else
+ g_win32_error_message (WSAGetLastError ())
+#endif
+ );
+ else
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Cancelled"));
+
+ /* We cancel so if the thread impl is decent it causes immediate exit.
+ * We check the reply port incase we had a reply in the mean time, which we free later */
+ d (printf ("Canceling lookup thread and leaving it\n"));
+ msg->cancelled = 1;
+ g_thread_join (thread);
+ cancel = 1;
+ } else {
+ struct _addrinfo_msg *reply;
+
+ d (printf ("waiting for child to exit\n"));
+ g_thread_join (thread);
+ d (printf ("child done\n"));
+
+ reply = (struct _addrinfo_msg *) camel_msgport_try_pop (reply_port);
+ if (reply != msg)
+ g_warning ("%s: Received msg reply %p doesn't match msg %p", G_STRFUNC, reply, msg);
+ }
+ }
+ camel_msgport_destroy (reply_port);
+
+ g_cancellable_release_fd (cancellable);
+
+ return cancel;
+}
+
+#ifdef NEED_ADDRINFO
+static gpointer
+cs_getaddrinfo (gpointer data)
+{
+ struct _addrinfo_msg *msg = data;
+ gint herr;
+ struct hostent h;
+ struct addrinfo *res, *last = NULL;
+ struct sockaddr_in *sin;
+ in_port_t port = 0;
+ gint i;
+
+ /* This is a pretty simplistic emulation of getaddrinfo */
+
+ while ((msg->result = camel_gethostbyname_r (msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
+ if (msg->cancelled)
+ break;
+ msg->hostbuflen *= 2;
+ msg->hostbufmem = g_realloc (msg->hostbufmem, msg->hostbuflen);
+ }
+
+ /* If we got cancelled, dont reply, just free it */
+ if (msg->cancelled)
+ goto cancel;
+
+ /* FIXME: map error numbers across */
+ if (msg->result != 0)
+ goto reply;
+
+ /* check hints matched */
+ if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* we only support ipv4 for this interface, even if it could supply ipv6 */
+ if (h.h_addrtype != AF_INET) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* check service mapping */
+ if (msg->service) {
+ const gchar *p = msg->service;
+
+ while (*p) {
+ if (*p < '0' || *p > '9')
+ break;
+ p++;
+ }
+
+ if (*p) {
+ const gchar *socktype = NULL;
+ struct servent *serv;
+
+ if (msg->hints && msg->hints->ai_socktype) {
+ if (msg->hints->ai_socktype == SOCK_STREAM)
+ socktype = "tcp";
+ else if (msg->hints->ai_socktype == SOCK_DGRAM)
+ socktype = "udp";
+ }
+
+ serv = getservbyname (msg->service, socktype);
+ if (serv == NULL) {
+ msg->result = EAI_NONAME;
+ goto reply;
+ }
+ port = serv->s_port;
+ } else {
+ port = htons (strtoul (msg->service, NULL, 10));
+ }
+ }
+
+ for (i = 0; h.h_addr_list[i] && !msg->cancelled; i++) {
+ res = g_malloc0 (sizeof (*res));
+ if (msg->hints) {
+ res->ai_flags = msg->hints->ai_flags;
+ if (msg->hints->ai_flags & AI_CANONNAME)
+ res->ai_canonname = g_strdup (h.h_name);
+ res->ai_socktype = msg->hints->ai_socktype;
+ res->ai_protocol = msg->hints->ai_protocol;
+ } else {
+ res->ai_flags = 0;
+ res->ai_socktype = SOCK_STREAM; /* fudge */
+ res->ai_protocol = 0; /* fudge */
+ }
+ res->ai_family = AF_INET;
+ res->ai_addrlen = sizeof (*sin);
+ res->ai_addr = g_malloc (sizeof (*sin));
+ sin = (struct sockaddr_in *) res->ai_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_port = port;
+ memcpy (&sin->sin_addr, h.h_addr_list[i], sizeof (sin->sin_addr));
+
+ if (last == NULL) {
+ *msg->res = last = res;
+ } else {
+ last->ai_next = res;
+ last = res;
+ }
+ }
+reply:
+ camel_msgport_reply ((CamelMsg *) msg);
+cancel:
+ return NULL;
+}
+#else
+static gpointer
+cs_getaddrinfo (gpointer data)
+{
+ struct _addrinfo_msg *info = data;
+
+ info->result = getaddrinfo (info->name, info->service, info->hints, info->res);
+
+ /* On Solaris, the service name 'http' or 'https' is not defined.
+ * Use the port as the service name directly. */
+ if (info->result && info->service) {
+ if (strcmp (info->service, "http") == 0)
+ info->result = getaddrinfo (info->name, "80", info->hints, info->res);
+ else if (strcmp (info->service, "https") == 0)
+ info->result = getaddrinfo (info->name, "443", info->hints, info->res);
+ }
+
+ if (!info->cancelled)
+ camel_msgport_reply ((CamelMsg *) info);
+
+ return NULL;
+}
+#endif /* NEED_ADDRINFO */
+
+/**
+ * camel_getaddrinfo:
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.22
+ **/
+struct addrinfo *
+camel_getaddrinfo (const gchar *name,
+ const gchar *service,
+ const struct addrinfo *hints,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _addrinfo_msg *msg;
+ struct addrinfo *res = NULL;
+#ifndef ENABLE_IPv6
+ struct addrinfo myhints;
+#endif
+ gchar *ascii_name;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ camel_operation_push_message (
+ cancellable, _("Resolving: %s"), name);
+
+ /* force ipv4 addresses only */
+#ifndef ENABLE_IPv6
+ if (hints == NULL)
+ memset (&myhints, 0, sizeof (myhints));
+ else
+ memcpy (&myhints, hints, sizeof (myhints));
+
+ myhints.ai_family = AF_INET;
+ hints = &myhints;
+#endif
+
+ ascii_name = camel_host_idna_to_ascii (name);
+
+ msg = g_malloc0 (sizeof (*msg));
+ msg->name = ascii_name;
+ msg->service = service;
+ msg->hints = hints;
+ msg->res = &res;
+#ifdef NEED_ADDRINFO
+ msg->hostbuflen = 1024;
+ msg->hostbufmem = g_malloc (msg->hostbuflen);
+#endif
+ if (cs_waitinfo (
+ cs_getaddrinfo, msg, _("Host lookup failed"),
+ cancellable, error) == 0) {
+
+ if (msg->result == EAI_NONAME || msg->result == EAI_FAIL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Host lookup '%s' failed. Check your host name for spelling errors."), name);
+ } else if (msg->result != 0) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Host lookup '%s' failed: %s"),
+ name, gai_strerror (msg->result));
+ }
+ } else
+ res = NULL;
+
+ cs_freeinfo (msg);
+ g_free (ascii_name);
+
+ camel_operation_pop_message (cancellable);
+
+ return res;
+}
+
+/**
+ * camel_freeaddrinfo:
+ *
+ * Since: 2.22
+ **/
+void
+camel_freeaddrinfo (struct addrinfo *host)
+{
+#ifdef NEED_ADDRINFO
+ while (host) {
+ struct addrinfo *next = host->ai_next;
+
+ g_free (host->ai_canonname);
+ g_free (host->ai_addr);
+ g_free (host);
+ host = next;
+ }
+#else
+ freeaddrinfo (host);
+#endif
+}
+
+/**
+ * camel_host_idna_to_ascii:
+ * @host: Host name, with or without non-ascii letters in utf8
+ *
+ * Converts IDN (Internationalized Domain Name) into ASCII representation.
+ * If there's a failure or the @host has only ASCII letters, then a copy
+ * of @host is returned.
+ *
+ * Returns: Newly allocated string with only ASCII letters describing the @host.
+ * Free it with g_free() when done with it.
+ *
+ * Since: 3.16
+ **/
+gchar *
+camel_host_idna_to_ascii (const gchar *host)
+{
+ UErrorCode uerror = U_ZERO_ERROR;
+ int32_t uhost_len = 0;
+ const gchar *ptr;
+ gchar *ascii = NULL;
+
+ g_return_val_if_fail (host != NULL, NULL);
+
+ ptr = host;
+ while (*ptr > 0)
+ ptr++;
+
+ if (!*ptr) {
+ /* Did read whole buffer, it should be ASCII string already */
+ return g_strdup (host);
+ }
+
+ u_strFromUTF8 (NULL, 0, &uhost_len, host, -1, &uerror);
+ if (uhost_len > 0) {
+ UChar *uhost = g_new0 (UChar, uhost_len + 2);
+
+ uerror = U_ZERO_ERROR;
+ u_strFromUTF8 (uhost, uhost_len + 1, &uhost_len, host, -1, &uerror);
+ if (uerror == U_ZERO_ERROR && uhost_len > 0) {
+ int32_t buffer_len = uhost_len * 6 + 6, nconverted;
+ UChar *buffer = g_new0 (UChar, buffer_len);
+
+ nconverted = uidna_IDNToASCII (uhost, uhost_len, buffer, buffer_len, UIDNA_ALLOW_UNASSIGNED, 0, &uerror);
+ if (uerror == U_ZERO_ERROR && nconverted > 0) {
+ int32_t ascii_len = 0;
+
+ u_strToUTF8 (NULL, 0, &ascii_len, buffer, nconverted, &uerror);
+ if (ascii_len > 0) {
+ uerror = U_ZERO_ERROR;
+ ascii = g_new0 (gchar, ascii_len + 2);
+
+ u_strToUTF8 (ascii, ascii_len + 1, &ascii_len, buffer, nconverted, &uerror);
+ if (uerror == U_ZERO_ERROR && ascii_len > 0) {
+ ascii[ascii_len] = '\0';
+ } else {
+ g_free (ascii);
+ ascii = NULL;
+ }
+ }
+ }
+
+ g_free (buffer);
+ }
+
+ g_free (uhost);
+ }
+
+ if (!ascii)
+ ascii = g_strdup (host);
+
+ return ascii;
+}