/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2001-2003, Ximian, Inc.
 * Copyright (C) 2013 Igalia, S.L.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include <libsoup/soup.h>

static SoupSession *session;
static GMainLoop *loop;
static gboolean debug, head, quiet;
static const gchar *output_file_path = NULL;

#define OUTPUT_BUFFER_SIZE 8192

static void
on_stream_splice (GObject *source, GAsyncResult *result, gpointer user_data)
{
        GError *error = NULL;
        g_output_stream_splice_finish (G_OUTPUT_STREAM (source),
                                       result,
                                       &error);
        if (error) {
                g_printerr ("Failed to download: %s\n", error->message);
                g_error_free (error);
                g_main_loop_quit (loop);
                return;
        }

        g_main_loop_quit (loop);
}

static void
on_read_ready (GObject *source, GAsyncResult *result, gpointer user_data)
{
        GInputStream *in = G_INPUT_STREAM (source);
        GError *error = NULL;
        gsize bytes_read = 0;
        char *output_buffer = user_data;

        g_input_stream_read_all_finish (in, result, &bytes_read, &error);

        if (bytes_read) {
                g_print ("%.*s", (int)bytes_read, output_buffer);
        }

        if (error) {
                g_printerr ("\nFailed to read stream: %s\n", error->message);
                g_error_free (error);
                g_free (output_buffer);
                g_main_loop_quit (loop);
        } else if (!bytes_read) {
                g_print ("\n");
                g_free (output_buffer);
                g_main_loop_quit (loop);
        } else {
                g_input_stream_read_all_async (in, output_buffer, OUTPUT_BUFFER_SIZE,
                                               G_PRIORITY_DEFAULT, NULL, on_read_ready, output_buffer);
        }
}

static void
on_request_sent (GObject *source, GAsyncResult *result, gpointer user_data)
{
        GError *error = NULL;
        GInputStream *in = soup_session_send_finish (SOUP_SESSION (source), result, &error);

        if (error) {
                g_printerr ("Failed to send request: %s\n", error->message);
                g_error_free (error);
                g_main_loop_quit (loop);
                return;
        }

        if (output_file_path) {
                GFile *output_file = g_file_new_for_commandline_arg (output_file_path);
                GOutputStream *out = G_OUTPUT_STREAM (g_file_create (output_file, G_FILE_CREATE_NONE,
                                                      NULL, &error));
                if (error) {
                        g_print ("Failed to create \"%s\": %s\n", output_file_path, error->message);
                        g_error_free (error);
                        g_object_unref (in);
                        g_object_unref (output_file);
                        g_main_loop_quit (loop);
                        return;
                }

                /* Start downloading to the file */
                g_output_stream_splice_async (G_OUTPUT_STREAM (out), in,
                                        G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
                                        G_PRIORITY_DEFAULT,
                                        NULL,
                                        on_stream_splice,
                                        NULL);

                g_object_unref (out);
	} else {
                char *output_buffer = g_new (char, OUTPUT_BUFFER_SIZE);
                g_input_stream_read_all_async (in, output_buffer, OUTPUT_BUFFER_SIZE,
                                               G_PRIORITY_DEFAULT, NULL, on_read_ready, output_buffer);
        }

        g_object_unref (in);
}


/* Inline class for providing a pre-configured client certificate */
typedef struct _GetTlsCertInteraction        GetTlsCertInteraction;
typedef struct _GetTlsCertInteractionClass   GetTlsCertInteractionClass;

static GType                    _get_tls_cert_interaction_get_type    (void) G_GNUC_CONST;
static GetTlsCertInteraction *  _get_tls_cert_interaction_new         (GTlsCertificate *cert);

struct _GetTlsCertInteraction
{
	GTlsInteraction parent_instance;
	GTlsCertificate *cert;
};

struct _GetTlsCertInteractionClass
{
	GTlsInteractionClass parent_class;
};

G_DEFINE_TYPE (GetTlsCertInteraction, _get_tls_cert_interaction, G_TYPE_TLS_INTERACTION);

static GTlsInteractionResult
request_certificate (GTlsInteraction              *interaction,
                     GTlsConnection               *connection,
                     GTlsCertificateRequestFlags   flags,
                     GCancellable                 *cancellable,
                     GError                      **error)
{
	GetTlsCertInteraction *self = (GetTlsCertInteraction*)interaction;
	g_tls_connection_set_certificate (connection, self->cert);
	return G_TLS_INTERACTION_HANDLED;
}

static void
_get_tls_cert_interaction_init (GetTlsCertInteraction *interaction)
{
}

static void
_get_tls_cert_interaction_class_init (GetTlsCertInteractionClass *klass)
{
	GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
	interaction_class->request_certificate = request_certificate;
}

GetTlsCertInteraction *
_get_tls_cert_interaction_new (GTlsCertificate *cert)
{
	GetTlsCertInteraction *self = g_object_new (_get_tls_cert_interaction_get_type (), NULL);
	self->cert = g_object_ref (cert);
	return self;
}

static const char *ca_file, *proxy;
static char *client_cert_file, *client_key_file;
static gboolean ntlm;
static gboolean negotiate;

static GOptionEntry entries[] = {
	{ "ca-file", 'c', 0,
	  G_OPTION_ARG_STRING, &ca_file,
	  "Use FILE as the TLS CA file", "FILE" },
	{ "cert", 0, 0,
	  G_OPTION_ARG_STRING, &client_cert_file,
	  "Use FILE as the TLS client certificate file", "FILE" },
	{ "key", 0, 0,
	  G_OPTION_ARG_STRING, &client_key_file,
	  "Use FILE as the TLS client key file", "FILE" },
	{ "debug", 'd', 0,
	  G_OPTION_ARG_NONE, &debug,
	  "Show HTTP headers", NULL },
	{ "head", 'h', 0,
          G_OPTION_ARG_NONE, &head,
          "Do HEAD rather than GET", NULL },
	{ "ntlm", 'n', 0,
	  G_OPTION_ARG_NONE, &ntlm,
	  "Use NTLM authentication", NULL },
	{ "output", 'o', 0,
	  G_OPTION_ARG_STRING, &output_file_path,
	  "Write the received data to FILE instead of stdout", "FILE" },
	{ "proxy", 'p', 0,
	  G_OPTION_ARG_STRING, &proxy,
	  "Use URL as an HTTP proxy", "URL" },
	{ "quiet", 'q', 0,
	  G_OPTION_ARG_NONE, &quiet,
	  "Don't show HTTP status code", NULL },
	{ NULL }
};

static GOptionEntry negotiate_entries[] = {
	{ "negotiate", 'N', 0,
	  G_OPTION_ARG_NONE, &negotiate,
	  "Use Negotiate authentication", NULL },
	{ NULL }
};

int
main (int argc, char **argv)
{
	GOptionContext *opts;
	const char *url;
	SoupMessage *msg;
	GUri *parsed;
	GError *error = NULL;

	opts = g_option_context_new (NULL);
	g_option_context_add_main_entries (opts, entries, NULL);
	if (soup_auth_negotiate_supported())
		g_option_context_add_main_entries (opts, negotiate_entries, NULL);
	if (!g_option_context_parse (opts, &argc, &argv, &error)) {
		g_printerr ("Could not parse arguments: %s\n",
			    error->message);
		g_printerr ("%s",
			    g_option_context_get_help (opts, TRUE, NULL));
		exit (1);
	}

	if (argc != 2) {
		char *help = g_option_context_get_help (opts, TRUE, NULL);
		g_printerr ("%s", help);
		g_free (help);
		exit (1);
	}
	g_option_context_free (opts);

        /* Validate the URL */
	url = argv[1];
	parsed = g_uri_parse (url, SOUP_HTTP_URI_FLAGS, &error);
	if (!parsed) {
		g_printerr ("Could not parse '%s' as a URL: %s\n", url, error->message);
		exit (1);
	}
	g_uri_unref (parsed);

        /* Build the session with all of the features we need */
	session = soup_session_new_with_options ("user-agent", "get ",
                                                 "accept-language-auto", TRUE,
                                                 "timeout", 15,
                                                 NULL);
        soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
	if (ntlm)
		soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
#ifdef LIBSOUP_HAVE_GSSAPI
	if (negotiate)
		soup_session_add_feature_by_type (session,
						  SOUP_TYPE_AUTH_NEGOTIATE);
#endif

        if (ca_file) {
                GTlsDatabase *tls_db = g_tls_file_database_new (ca_file, &error);
                if (error) {
                        g_printerr ("Failed to load TLS database \"%s\": %s", ca_file, error->message);
                        g_error_free (error);
                        g_object_unref (session);
                        exit (1);
                }

		soup_session_set_tls_database (session, tls_db);
                g_object_unref (tls_db);
        }

	if (client_cert_file) {
		GTlsCertificate *client_cert;
		GetTlsCertInteraction *interaction;
		if (!client_key_file) {
			g_printerr ("--key is required with --cert\n");
                        g_object_unref (session);
			exit (1);
		}
		client_cert = g_tls_certificate_new_from_files (client_cert_file, client_key_file, &error);
		if (!client_cert) {
			g_printerr ("%s\n", error->message);
                        g_error_free (error);
                        g_object_unref (session);
			exit (1);
		}
		interaction = _get_tls_cert_interaction_new (client_cert);
		soup_session_set_tls_interaction (session, G_TLS_INTERACTION (interaction));
		g_object_unref (interaction);
	}

	if (debug) {
		SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_HEADERS);
		soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
		g_object_unref (logger);
	}

	if (proxy) {
		GProxyResolver *resolver;
		GUri *proxy_uri = g_uri_parse (proxy, SOUP_HTTP_URI_FLAGS, &error);
		if (!proxy_uri) {
			g_printerr ("Could not parse '%s' as URI: %s\n",
				    proxy, error->message);
                        g_error_free (error);
                        g_object_unref (session);
			exit (1);
		}

		resolver = g_simple_proxy_resolver_new (proxy, NULL);
		soup_session_set_proxy_resolver (session, resolver);
		g_uri_unref (proxy_uri);
		g_object_unref (resolver);
	}

        /* Send the request */
	msg = soup_message_new (head ? "HEAD" : "GET", url);
        soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL,
				 on_request_sent, NULL);
	g_object_unref (msg);

        /* Run the loop */
        loop = g_main_loop_new (NULL, FALSE);
        g_main_loop_run (loop);
	g_main_loop_unref (loop);
	g_object_unref (session);

	return 0;
}
