+static gboolean
+hostname_is_localhost (const char *hostname)
+{
+ size_t len = strlen (hostname);
+ const char *p;
+
+ /* Match "localhost", "localhost.", "*.localhost" and "*.localhost." */
+ if (len < strlen ("localhost"))
+ return FALSE;
+
+ if (hostname[len - 1] == '.')
+ len--;
+
+ /* Scan backwards in @hostname to find the right-most dot (excluding the final dot, if it exists, as it was chopped off above).
+ * We can’t use strrchr() because because we need to operate with string lengths.
+ * End with @p pointing to the character after the right-most dot. */
+ p = hostname + len - 1;
+ while (p >= hostname)
+ {
+ if (*p == '.')
+ {
+ p++;
+ break;
+ }
+ else if (p == hostname)
+ break;
+ p--;
+ }
+
+ len -= p - hostname;
+
+ return g_ascii_strncasecmp (p, "localhost", MAX (len, strlen ("localhost"))) == 0;
+}
+
+/* Note that this does not follow the "FALSE means @error is set"
+ * convention. The return value tells the caller whether it should
+ * return @addrs and @error to the caller right away, or if it should
+ * continue and trying to resolve the name as a hostname.
+ */
+static gboolean
+handle_ip_address_or_localhost (const char *hostname,
+ GList **addrs,
+ GResolverNameLookupFlags flags,
+ GError **error)
+{
+ GInetAddress *addr;
+
+#ifndef G_OS_WIN32
+ struct in_addr ip4addr;
+#endif
+
+ addr = g_inet_address_new_from_string (hostname);
+ if (addr)
+ {
+ *addrs = g_list_append (NULL, addr);
+ return TRUE;
+ }
+
+ *addrs = NULL;
+
+#ifdef G_OS_WIN32
+
+ /* Reject IPv6 addresses that have brackets ('[' or ']') and/or port numbers,
+ * as no valid addresses should contain these at this point.
+ * Non-standard IPv4 addresses would be rejected during the call to
+ * getaddrinfo() later.
+ */
+ if (strrchr (hostname, '[') != NULL ||
+ strrchr (hostname, ']') != NULL)
+#else
+
+ /* Reject non-standard IPv4 numbers-and-dots addresses.
+ * g_inet_address_new_from_string() will have accepted any "real" IP
+ * address, so if inet_aton() succeeds, then it's an address we want
+ * to reject.
+ */
+ if (inet_aton (hostname, &ip4addr))
+#endif
+ {
+#ifdef G_OS_WIN32
+ gchar *error_message = g_win32_error_message (WSAHOST_NOT_FOUND);
+#else
+ gchar *error_message = g_locale_to_utf8 (gai_strerror (EAI_NONAME), -1, NULL, NULL, NULL);
+ if (error_message == NULL)
+ error_message = g_strdup ("[Invalid UTF-8]");
+#endif
+ g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
+ _("Error resolving “%s”: %s"),
+ hostname, error_message);
+ g_free (error_message);
+
+ return TRUE;
+ }
+
+ /* Always resolve localhost to a loopback address so it can be reliably considered secure.
+ This behavior is being adopted by browsers:
+ - https://w3c.github.io/webappsec-secure-contexts/
+ - https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/RC9dSw-O3fE/E3_0XaT0BAAJ
+ - https://chromium.googlesource.com/chromium/src.git/+/8da2a80724a9b896890602ff77ef2216cb951399
+ - https://bugs.webkit.org/show_bug.cgi?id=171934
+ - https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06
+ */
+ if (hostname_is_localhost (hostname))
+ {
+ if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6));
+ if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4));
+ if (*addrs == NULL)
+ {
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6));
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4));
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GList *
+lookup_by_name_real (GResolver *resolver,
+ const gchar *hostname,
+ GResolverNameLookupFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *addrs;
+ gchar *ascii_hostname = NULL;
+
+ g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
+ g_return_val_if_fail (hostname != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* Check if @hostname is just an IP address */
+ if (handle_ip_address_or_localhost (hostname, &addrs, flags, error))
+ return addrs;
+
+ if (g_hostname_is_non_ascii (hostname))
+ hostname = ascii_hostname = g_hostname_to_ascii (hostname);
+
+ if (!hostname)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid hostname"));
+ return NULL;
+ }
+
+ maybe_emit_reload (resolver);
+
+ if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
+ {
+ if (!G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ /* Translators: The placeholder is for a function name. */
+ _("%s not implemented"), "lookup_by_name_with_flags");
+ g_free (ascii_hostname);
+ return NULL;
+ }
+ addrs = G_RESOLVER_GET_CLASS (resolver)->
+ lookup_by_name_with_flags (resolver, hostname, flags, cancellable, error);
+ }
+ else
+ addrs = G_RESOLVER_GET_CLASS (resolver)->
+ lookup_by_name (resolver, hostname, cancellable, error);
+
+ remove_duplicates (addrs);
+
+ g_free (ascii_hostname);
+ return addrs;
+}