/* GLib testing framework examples and tests
 *
 * Copyright (C) 2010 Collabora, Ltd.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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 .
 *
 * Authors: Nicolas Dufresne 
 */
#include "config.h"
#include 
#include 
#include 
#include 
#include 
#include "glibintl.h"
#ifdef G_OS_UNIX
#include "gio/gunixsocketaddress.h"
#endif
static const gchar *info = NULL;
static GCancellable *cancellable = NULL;
static gint return_value = 0;
static G_NORETURN void
usage (void)
{
  fprintf (stderr, "Usage: proxy [-s] (uri|host:port|ip:port|path|srv/protocol/domain)\n");
  fprintf (stderr, "       Use -t to enable threading.\n");
  fprintf (stderr, "       Use -s to do synchronous lookups.\n");
  fprintf (stderr, "       Use -c to cancel operation.\n");
  fprintf (stderr, "       Use -e to use enumerator.\n");
  fprintf (stderr, "       Use -inet to use GInetSocketAddress enumerator (ip:port).\n");
#ifdef G_OS_UNIX
  fprintf (stderr, "       Use -unix to use GUnixSocketAddress enumerator (path).\n");
#endif
  fprintf (stderr, "       Use -proxyaddr tp use GProxyAddress enumerator "
                   "(ip:port:protocol:dest_host:dest_port[:username[:password]]).\n");
  fprintf (stderr, "       Use -netaddr to use GNetworkAddress enumerator (host:port).\n");
  fprintf (stderr, "       Use -neturi to use GNetworkAddress enumerator (uri).\n");
  fprintf (stderr, "       Use -netsrv to use GNetworkService enumerator (srv/protocol/domain).\n");
  fprintf (stderr, "       Use -connect to create a connection using GSocketClient object (uri).\n");
  exit (1);
}
static void
print_and_free_error (GError *error)
{
  fprintf (stderr, "Failed to obtain proxies: %s\n", error->message);
  g_error_free (error);
  return_value = 1;
}
static void
print_proxies (const gchar *local_info, gchar **proxies)
{
  printf ("Proxies for URI '%s' are:\n", local_info);
  if (proxies == NULL || proxies[0] == NULL)
    printf ("\tnone\n");
  else
    for (; proxies[0]; proxies++)
      printf ("\t%s\n", proxies[0]);
}
static void
_proxy_lookup_cb (GObject *source_object,
		  GAsyncResult *result,
		  gpointer user_data)
{
  GError *error = NULL;
  gchar **proxies;
  GMainLoop *loop = user_data;
  proxies = g_proxy_resolver_lookup_finish (G_PROXY_RESOLVER (source_object),
					    result,
					    &error);
  if (error)
    {
      print_and_free_error (error);
    }
  else
    {
      print_proxies (info, proxies);
      g_strfreev (proxies);
    }
  g_main_loop_quit (loop);
}
static void
use_resolver (gboolean synchronous)
{
  GProxyResolver *resolver;
  resolver = g_proxy_resolver_get_default ();
  if (synchronous)
    {
      GError *error = NULL;
      gchar **proxies;
      proxies = g_proxy_resolver_lookup (resolver, info, cancellable, &error);
      if (error)
	  print_and_free_error (error);
      else
	  print_proxies (info, proxies);
      g_strfreev (proxies);
    }
  else
    {
      GMainLoop *loop = g_main_loop_new (NULL, FALSE);
      g_proxy_resolver_lookup_async (resolver,
				     info,
				     cancellable,
				     _proxy_lookup_cb,
				     loop);
      g_main_loop_run (loop);
      g_main_loop_unref (loop);
    }
}
static void
print_proxy_address (GSocketAddress *sockaddr)
{
  GProxyAddress *proxy = NULL;
  if (sockaddr == NULL)
    {
      printf ("\tdirect://\n");
      return;
    }
  if (G_IS_PROXY_ADDRESS (sockaddr))
    {
      proxy = G_PROXY_ADDRESS (sockaddr);
      printf ("\t%s://", g_proxy_address_get_protocol(proxy));
    }
  else
    {
      printf ("\tdirect://");
    }
  if (G_IS_INET_SOCKET_ADDRESS (sockaddr))
    {
      GInetAddress *inetaddr;
      guint port;
      gchar *addr;
      g_object_get (sockaddr,
		    "address", &inetaddr,
		    "port", &port,
		    NULL);
      addr = g_inet_address_to_string (inetaddr);
      printf ("%s:%u", addr, port);
      g_free (addr);
    }
  if (proxy)
    {
      if (g_proxy_address_get_username(proxy))
        printf (" (Username: %s  Password: %s)",
                g_proxy_address_get_username(proxy),
                g_proxy_address_get_password(proxy));
      printf (" (Hostname: %s, Port: %i)",
              g_proxy_address_get_destination_hostname (proxy),
              g_proxy_address_get_destination_port (proxy));
    }
  printf ("\n");
}
static void
_proxy_enumerate_cb (GObject *object,
		     GAsyncResult *result,
		     gpointer user_data)
{
  GError *error = NULL;
  GMainLoop *loop = user_data;
  GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (object);
  GSocketAddress *sockaddr;
  sockaddr = g_socket_address_enumerator_next_finish (enumerator,
						      result,
						      &error);
  if (sockaddr)
    {
      print_proxy_address (sockaddr);
      g_socket_address_enumerator_next_async (enumerator,
                                              cancellable,
					      _proxy_enumerate_cb,
					      loop);
      g_object_unref (sockaddr);
    }
  else
    {
      if (error)
	print_and_free_error (error);
      g_main_loop_quit (loop);
    }
}
static void
run_with_enumerator (gboolean synchronous, GSocketAddressEnumerator *enumerator)
{
  GError *error = NULL;
  if (synchronous)
    {
      GSocketAddress *sockaddr;
      while ((sockaddr = g_socket_address_enumerator_next (enumerator,
							   cancellable,
							   &error)))
	{
	  print_proxy_address (sockaddr);
	  g_object_unref (sockaddr);
	}
      if (error)
	print_and_free_error (error);
    }
  else
    {
      GMainLoop *loop = g_main_loop_new (NULL, FALSE);
      g_socket_address_enumerator_next_async (enumerator,
                                              cancellable,
					      _proxy_enumerate_cb,
					      loop);
      g_main_loop_run (loop);
      g_main_loop_unref (loop);
    }
}
static void
use_enumerator (gboolean synchronous)
{
  GSocketAddressEnumerator *enumerator;
  enumerator = g_object_new (G_TYPE_PROXY_ADDRESS_ENUMERATOR,
			     "uri", info,
			     NULL);
  printf ("Proxies for URI '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
static void
use_inet_address (gboolean synchronous)
{
  GSocketAddressEnumerator *enumerator;
  GSocketAddress *sockaddr;
  GInetAddress *addr = NULL;
  guint port = 0;
  gchar **ip_and_port;
  ip_and_port = g_strsplit (info, ":", 2);
  if (ip_and_port[0])
    {
      addr = g_inet_address_new_from_string (ip_and_port[0]);
      if (ip_and_port [1])
	port = strtoul (ip_and_port [1], NULL, 10);
    }
  g_strfreev (ip_and_port);
  if (addr == NULL || port <= 0 || port >= 65535)
    {
      fprintf (stderr, "Bad 'ip:port' parameter '%s'\n", info);
      if (addr)
	g_object_unref (addr);
      return_value = 1;
      return;
    }
  sockaddr = g_inet_socket_address_new (addr, port);
  g_object_unref (addr);
  enumerator =
    g_socket_connectable_proxy_enumerate (G_SOCKET_CONNECTABLE (sockaddr));
  g_object_unref (sockaddr);
  printf ("Proxies for ip and port '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
#ifdef G_OS_UNIX
static void
use_unix_address (gboolean synchronous)
{
  GSocketAddressEnumerator *enumerator;
  GSocketAddress *sockaddr;
  sockaddr = g_unix_socket_address_new_with_type (info, -1, G_UNIX_SOCKET_ADDRESS_ABSTRACT);
  if (sockaddr == NULL)
    {
      fprintf (stderr, "Failed to create unix socket with name '%s'\n", info);
      return_value = 1;
      return;
    }
  enumerator =
    g_socket_connectable_proxy_enumerate (G_SOCKET_CONNECTABLE (sockaddr));
  g_object_unref (sockaddr);
  printf ("Proxies for path '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
#endif
static void
use_proxy_address (gboolean synchronous)
{
  GSocketAddressEnumerator *enumerator;
  GSocketAddress *sockaddr;
  GInetAddress *addr;
  guint port = 0;
  gchar *protocol;
  gchar *dest_host;
  guint dest_port;
  gchar *username = NULL;
  gchar *password = NULL;
  gchar **split_info;
  split_info = g_strsplit (info, ":", 7);
  if (!split_info[0]
      || !split_info[1]
      || !split_info[2]
      || !split_info[3]
      || !split_info[4])
    {
      fprintf (stderr, "Bad 'ip:port:protocol:dest_host:dest_port' parameter '%s'\n", info);
      return_value = 1;
      return;
    }
  addr = g_inet_address_new_from_string (split_info[0]);
  port = strtoul (split_info [1], NULL, 10);
  protocol = g_strdup (split_info[2]);
  dest_host = g_strdup (split_info[3]);
  dest_port = strtoul (split_info[4], NULL, 10);
 
  if (split_info[5])
    {
      username = g_strdup (split_info[5]);
      if (split_info[6])
        password = g_strdup (split_info[6]);
    }
  g_strfreev (split_info);
  sockaddr = g_proxy_address_new (addr, port,
                                  protocol, dest_host, dest_port,
                                  username, password);
  
  g_object_unref (addr);
  g_free (protocol);
  g_free (dest_host);
  g_free (username);
  g_free (password);
  enumerator =
    g_socket_connectable_proxy_enumerate (G_SOCKET_CONNECTABLE (sockaddr));
  g_object_unref (sockaddr);
  printf ("Proxies for ip and port '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
static void
use_network_address (gboolean synchronous)
{
  GError *error = NULL;
  GSocketAddressEnumerator *enumerator;
  GSocketConnectable *connectable;
  connectable = g_network_address_parse (info, -1, &error);
  if (error)
    {
      print_and_free_error (error);
      return;
    }
  enumerator = g_socket_connectable_proxy_enumerate (connectable);
  g_object_unref (connectable);
  printf ("Proxies for hostname and port '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
static void
use_network_uri (gboolean synchronous)
{
  GError *error = NULL;
  GSocketAddressEnumerator *enumerator;
  GSocketConnectable *connectable;
  connectable = g_network_address_parse_uri (info, 0, &error);
  if (error)
    {
      print_and_free_error (error);
      return;
    }
  enumerator = g_socket_connectable_proxy_enumerate (connectable);
  g_object_unref (connectable);
  printf ("Proxies for URI '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
static void
use_network_service (gboolean synchronous)
{
  GSocketAddressEnumerator *enumerator;
  GSocketConnectable *connectable = NULL;
  gchar **split;
  split = g_strsplit (info, "/", 3);
  if (split[0] && split[1] && split[2])
    connectable = g_network_service_new (split[0], split[1], split[2]);
  g_strfreev (split);
  if (connectable == NULL)
    {
       fprintf (stderr, "Bad 'srv/protocol/domain' parameter '%s'\n", info);
       return_value = 1;
       return;
    }
  enumerator = g_socket_connectable_proxy_enumerate (connectable);
  g_object_unref (connectable);
  printf ("Proxies for hostname and port '%s' are:\n", info);
  run_with_enumerator (synchronous, enumerator);
  g_object_unref (enumerator);
}
static void
_socket_connect_cb (GObject *object,
		    GAsyncResult *result,
		    gpointer user_data)
{
  GError *error = NULL;
  GMainLoop *loop = user_data;
  GSocketClient *client = G_SOCKET_CLIENT (object);
  GSocketConnection *connection;
  connection = g_socket_client_connect_to_uri_finish (client,
						      result,
						      &error);
  if (connection)
    {
      GSocketAddress *proxy_addr;
      proxy_addr = g_socket_connection_get_remote_address (connection, NULL);
      print_proxy_address (proxy_addr);
    }
  else
    {
      print_and_free_error (error);
    }
  g_main_loop_quit (loop);
}
static void
use_socket_client (gboolean synchronous)
{
  GError *error = NULL;
  GSocketClient *client;
  client = g_socket_client_new ();
  printf ("Proxies for URI '%s' are:\n", info);
  if (synchronous)
    {
      GSocketConnection *connection;
      GSocketAddress *proxy_addr;
      connection = g_socket_client_connect_to_uri (client,
						   info,
						   0,
      						   cancellable,
						   &error);
      if (connection)
	{
	  proxy_addr = g_socket_connection_get_remote_address (connection, NULL);
	  print_proxy_address (proxy_addr);
	}
      else
	{
	  print_and_free_error (error);
	}
    }
  else
    {
      GMainLoop *loop = g_main_loop_new (NULL, FALSE);
      g_socket_client_connect_to_uri_async (client,
					    info,
					    0,
					    cancellable,
					    _socket_connect_cb,
					    loop);
      g_main_loop_run (loop);
      g_main_loop_unref (loop);
    }
  g_object_unref (client);
}
typedef enum
{
  USE_RESOLVER,
  USE_ENUMERATOR,
#ifdef G_OS_UNIX
  USE_UNIX_SOCKET_ADDRESS,
#endif
  USE_INET_SOCKET_ADDRESS,
  USE_PROXY_ADDRESS,
  USE_NETWORK_ADDRESS,
  USE_NETWORK_URI,
  USE_NETWORK_SERVICE,
  USE_SOCKET_CLIENT,
} ProxyTestType;
gint
main (gint argc, gchar **argv)
{
  gboolean synchronous = FALSE;
  gboolean cancel = FALSE;
  ProxyTestType type = USE_RESOLVER;
  while (argc >= 2 && argv[1][0] == '-')
    {
      if (!strcmp (argv[1], "-s"))
        synchronous = TRUE;
      else if (!strcmp (argv[1], "-c"))
        cancel = TRUE;
      else if (!strcmp (argv[1], "-e"))
        type = USE_ENUMERATOR;
      else if (!strcmp (argv[1], "-inet"))
        type = USE_INET_SOCKET_ADDRESS;
#ifdef G_OS_UNIX
      else if (!strcmp (argv[1], "-unix"))
        type = USE_UNIX_SOCKET_ADDRESS;
#endif
      else if (!strcmp (argv[1], "-proxyaddr"))
        type = USE_PROXY_ADDRESS;
      else if (!strcmp (argv[1], "-netaddr"))
        type = USE_NETWORK_ADDRESS;
      else if (!strcmp (argv[1], "-neturi"))
        type = USE_NETWORK_URI;
      else if (!strcmp (argv[1], "-netsrv"))
        type = USE_NETWORK_SERVICE;
      else if (!strcmp (argv[1], "-connect"))
        type = USE_SOCKET_CLIENT;
      else
	usage ();
      argv++;
      argc--;
    }
  if (argc != 2)
    usage ();
  /* Save URI for asynchronous callback */
  info = argv[1];
  if (cancel)
    {
      cancellable = g_cancellable_new ();
      g_cancellable_cancel (cancellable);
    }
  switch (type)
    {
    case USE_RESOLVER:
      use_resolver (synchronous);
      break;
    case USE_ENUMERATOR:
      use_enumerator (synchronous);
      break;
    case USE_INET_SOCKET_ADDRESS:
      use_inet_address (synchronous);
      break;
#ifdef G_OS_UNIX
    case USE_UNIX_SOCKET_ADDRESS:
      use_unix_address (synchronous);
      break;
#endif
    case USE_PROXY_ADDRESS:
      use_proxy_address (synchronous);
      break;
    case USE_NETWORK_ADDRESS:
      use_network_address (synchronous);
      break;
    case USE_NETWORK_URI:
      use_network_uri (synchronous);
      break;
    case USE_NETWORK_SERVICE:
      use_network_service (synchronous);
      break;
    case USE_SOCKET_CLIENT:
      use_socket_client (synchronous);
      break;
    }
  return return_value;
}