/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 Takuro Ashie
 *  Copyright (C) 2004-2005 Hidetaka Iwai
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#define _XOPEN_SOURCE

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */

#include "gnet.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <glib/gi18n.h>
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

#ifdef USE_SSL
#include <gnutls/gnutls.h>
#endif

#include "kz-http.h"
#include "kazehakase.h"
#include "kz-profile.h"
#include "kz-proxy-item.h"
#include "kz-prompt-dialog.h"
#include "kz-base64.h"
#include "eggmd5.h"

#define BUFFER_SIZE 256

enum {
	PROP_0,
	PROP_METHOD,
	PROP_HOSTNAME,
	PROP_PORT,
	PROP_PATH
};

enum {
	AUTH_METHOD_UNKNOWN,
	AUTH_METHOD_BASIC,
	AUTH_METHOD_DIGEST
};

const gchar *methods[] = {
	"GET",
	"HEAD",
	"POST"
};
static guint n_methods = G_N_ELEMENTS(methods);

#ifdef USE_SSL
typedef struct _KzSSL
{
	gnutls_session_t session;
	gnutls_certificate_credentials_t xcred;
} KzSSL;
#endif

typedef struct _AuthParam
{
	guint     method;
	gchar    *string;
	gchar    *realm;
	gchar    *domain;
	gchar    *nonce;
	gchar    *opaque;
	gboolean  stale;
	gchar    *algorithm;
	gchar    *qop;
} AuthParam;

typedef struct _KzHTTPPrivate KzHTTPPrivate;
struct _KzHTTPPrivate 
{
	GIOChannel *iochannel;

	GTcpSocket *socket;

	KzHTTPMethodType method; /* Access Method */
	
	gchar *hostname;
	guint port;
	gchar *path;

	gboolean header;
	gboolean use_proxy;

	gboolean chunked; /* Transfer-Encoding is chunked or not */
	gsize chunk_size;

	gboolean redirection; /* for Redirection 3xx */
	gchar    *location;   /* Redirect-URI */
	gchar    *content_type;
	gchar    *content_encoding;

	gboolean   auth;
	AuthParam *auth_param;
#ifdef USE_SSL
	KzSSL    *kz_ssl;
#endif
	gchar *post_data;
};

#define KZ_HTTP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_HTTP, KzHTTPPrivate))

static void set_property    (GObject *object,
	        	     guint prop_id,
	        	     const GValue *value,
	        	     GParamSpec *pspec);
static void get_property    (GObject *object,
	        	     guint prop_id,
	        	     GValue *value,
	        	     GParamSpec *pspec);
static void dispose         (GObject *object);

static GIOStatus kz_http_in_header       (KzHTTP *http,
					  GIOChannel *iochannel);
static GIOStatus kz_http_in_body         (KzHTTP *http,
					  GIOChannel *iochannel);
static GIOStatus kz_http_in_chunked_body (KzHTTP *http,
					  GIOChannel *iochannel);
static GIOStatus kz_http_read_from_io    (KzIO *io,
					  GIOChannel *iochannel);

static void     cb_http_connect  (GTcpSocket *socket, 
				  GTcpSocketConnectAsyncStatus status,
				  gpointer data);

static void kz_http_error (KzHTTP *http);
static void kz_http_start (KzIO *io);

static void     kz_http_set_chunked_mode (KzHTTP *http);
static void     kz_http_set_redirection  (KzHTTP *http);
static gboolean kz_http_use_proxy        (KzHTTP *http);
static gboolean kz_http_is_in_header     (KzHTTP *http);
static gboolean kz_http_is_chunked_mode  (KzHTTP *http);
static gboolean kz_http_is_redirection   (KzHTTP *http);
static gboolean kz_http_is_authentication(KzHTTP *http);
static gboolean kz_http_show_dialog      (KzHTTP *http);

static GIOStatus kz_http_read_chars (KzHTTP *http,
	       			     GIOChannel *iochannel, 
				     gchar *buffer, 
				     gsize count,
				     gsize *bytes_read);

static GIOStatus kz_http_read_line  (KzHTTP *http,
	       			     GIOChannel *iochannel, 
				     gchar **buffer,
				     gsize *length,
				     gsize *terminator_pos);

#ifdef USE_SSL
static void ssl_init (KzSSL *kz_ssl, GIOChannel *channel);
static void ssl_term (KzSSL *kz_ssl);
#endif

GType
kz_http_method_type_get_type (void)
{
	static GType etype = 0;
	if (etype == 0) {
		static const GEnumValue values[] = {
			{ KZ_HTTP_METHOD_GET,  "KZ_HTTP_METHOD_GET",  "GET" },
			{ KZ_HTTP_METHOD_HEAD, "KZ_HTTP_METHOD_HEAD", "HEAD" },
			{ KZ_HTTP_METHOD_POST, "KZ_HTTP_METHOD_POST", "POST" },
			{ 0, NULL, NULL }
		};
		etype = g_enum_register_static ("KzHTTPMethodType", values);
	}
	return etype;
}

G_DEFINE_TYPE(KzHTTP, kz_http, KZ_TYPE_IO)

static void
kz_http_class_init (KzHTTPClass *klass)
{
	GObjectClass *object_class;
	KzIOClass *io_class;

	object_class = (GObjectClass *) klass;
	io_class     = (KzIOClass *) klass;

	object_class->dispose      = dispose;
	object_class->set_property = set_property;
	object_class->get_property = get_property;
	
	io_class->read_from_io  = kz_http_read_from_io;
	io_class->io_start      = kz_http_start;

	g_object_class_install_property(
		object_class,
		PROP_METHOD,
		g_param_spec_enum(
			"method",
			_("Method"),
			_("Request Method"),
			KZ_TYPE_HTTP_METHOD_TYPE,
			KZ_HTTP_METHOD_GET,
			G_PARAM_READWRITE));
	g_object_class_install_property(
		object_class,
		PROP_HOSTNAME,
		g_param_spec_string(
			"hostname",
			_("Hostname"),
			_("The Hostname of the URI"),
			NULL,
			G_PARAM_READWRITE));
	g_object_class_install_property(
		object_class,
		PROP_PORT,
		g_param_spec_uint(
			"port",
			_("Port"),
			_("The Port number of the URI"),
			0,
			65535,
			80,
			G_PARAM_READWRITE));
	g_object_class_install_property(
		object_class,
		PROP_PATH,
		g_param_spec_string(
			"path",
			_("Path"),
			_("The Path of the URI"),
			NULL,
			G_PARAM_READWRITE));

	g_type_class_add_private (object_class, sizeof(KzHTTPPrivate));
}


static void
kz_http_init (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	priv->socket           = NULL;

	priv->hostname         = NULL;
	priv->port             = 80;
	priv->path             = NULL;
	
	priv->header           = TRUE;
	priv->chunked          = FALSE;
	priv->redirection      = FALSE;
	priv->location         = NULL;
	priv->content_type     = NULL;
	priv->content_encoding = NULL;

	priv->auth             = FALSE;
	priv->auth_param       = NULL;

#ifdef USE_SSL
	priv->kz_ssl           = NULL;
#endif

	priv->chunk_size       = 0;

	priv->post_data        = NULL;
}


void
dispose (GObject *object)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (object);

	if (priv->socket)
		gnet_tcp_socket_unref(priv->socket);
	if (priv->hostname)
		g_free(priv->hostname);
	if (priv->path)
		g_free(priv->path);
	if (priv->location)
		g_free(priv->location);
	if (priv->content_type)
		g_free(priv->content_type);
	if (priv->content_encoding)
		g_free(priv->content_encoding);
	if (priv->post_data)
		g_free(priv->post_data);
	if (priv->auth_param)
	{
		AuthParam *ap = priv->auth_param;
		if (ap->string)
			g_free(ap->string);
		if (ap->realm)
			g_free(ap->realm);
		if (ap->domain)
			g_free(ap->domain);
		if (ap->nonce)
			g_free(ap->nonce);
		if (ap->opaque)
			g_free(ap->opaque);
		if (ap->algorithm)
			g_free(ap->algorithm);
		if (ap->qop)
			g_free(ap->qop);
		ap->string    = NULL;
		ap->realm     = NULL;
		ap->domain    = NULL;
		ap->nonce     = NULL;
		ap->opaque    = NULL;
		ap->algorithm = NULL;
		ap->qop       = NULL;
		g_free(priv->auth_param);
	}

#ifdef USE_SSL
	if (priv->kz_ssl)
	{
		ssl_term(priv->kz_ssl);
		g_free(priv->kz_ssl);
		priv->kz_ssl = NULL;
	}
#endif
	priv->socket           = NULL;
	priv->hostname         = NULL;
	priv->path             = NULL;
	priv->location         = NULL;
	priv->content_type     = NULL;
	priv->content_encoding = NULL;
	priv->post_data        = NULL;
	priv->auth_param       = NULL;

	if (G_OBJECT_CLASS (kz_http_parent_class)->dispose)
		G_OBJECT_CLASS (kz_http_parent_class)->dispose(object);
}


static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (object);

	switch (prop_id)
	{
	case PROP_METHOD:
		priv->method = g_value_get_enum(value);
		break;
	case PROP_HOSTNAME:
		g_free(priv->hostname);
		priv->hostname = g_value_dup_string(value);
		break;
	case PROP_PORT:
		priv->port = g_value_get_uint(value);
		break;
	case PROP_PATH:
		g_free(priv->path);
		priv->path = g_value_dup_string(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (object);

	switch (prop_id)
	{
	case PROP_METHOD:
		g_value_get_enum(value);
		break;
	case PROP_HOSTNAME:
		g_value_set_string(value, priv->hostname);
		break;
	case PROP_PORT:
		g_value_set_uint(value, priv->port);
		break;
	case PROP_PATH:
		g_value_set_string(value, priv->path);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzHTTP *
kz_http_new (const gchar *uri)
{
	KzHTTP *http;
	GURI *guri;
	gchar *path = NULL, *hostname = NULL;
	guint port = 80;

	guri = gnet_uri_new(uri);

	if (guri)
	{
		hostname = guri->hostname;
		if (guri->port)
			port = guri->port;
		else if (strncmp(guri->scheme, "https", 5) == 0)
			port = 443;
		else
			port = 80;
		if (guri->query)
			path = g_strdup_printf("%s?%s",
					       guri->path, guri->query);
		else
			path = g_strdup(guri->path);
	}
	
	http = g_object_new(KZ_TYPE_HTTP,
			   "uri",      uri,
			   "hostname", hostname,
			   "port",     port,
			   "path",     path,
			   NULL);
	
#if USE_SSL
	if (guri && strncmp(guri->scheme, "https", 5) == 0)
	{
		KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
		priv->kz_ssl = g_new0(KzSSL, 1);
	}
#endif

	if (guri)
		gnet_uri_delete(guri);
	g_free(path);

	return http;
}


KzHTTP *
kz_http_post_new (const gchar *uri, const gchar *post_data)
{
	KzHTTP *http = kz_http_new(uri);
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	g_object_set(G_OBJECT(http), "method", KZ_HTTP_METHOD_POST, NULL);
	priv->post_data = g_strdup(post_data);

	return http;
}

static gchar *
get_string (const gchar *string)
{
	gchar *ret = NULL;

	if (!string)
		return NULL;

	if (string[0] == '"')
	{
		gchar *pos = NULL;
		pos = strchr(string+1, '"');
		if (pos)
		{
			ret = g_strndup(string+1, pos - string - 1);
		}
	}
	else
	{
		ret = g_strdup(string);
	}

	return ret;
}


static gboolean
get_basic_auth_value (KzHTTP *http, const gchar *string)
{
	gchar *tmp;
	AuthParam *ap;
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	ap = g_new0(AuthParam, 1);
	tmp = g_strchug((gchar *)string);
	if (g_ascii_strncasecmp(tmp, "realm=", 6) == 0)
	{
		ap->realm = get_string(tmp+6);
	}

	ap->method = AUTH_METHOD_BASIC;
	priv->auth_param = ap;

	return TRUE;
}

static gboolean
get_digest_auth_values (KzHTTP *http, const gchar *string)
{
	gchar **values;
	gchar *tmp;
	AuthParam *ap;
	gint i;
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	tmp = g_strchug((gchar *)string);
	
	values = g_strsplit(tmp, ",", -1);

	if (!values)
		return FALSE;	

	ap = g_new0(AuthParam, 1);
	for (i = 0; values[i]; i++)
	{
		tmp = g_strchug(values[i]);

		if (g_ascii_strncasecmp(tmp, "realm=", 6) == 0)
		{
			ap->realm = get_string(tmp+6);
		}
		if (g_ascii_strncasecmp(tmp, "domain=", 7) == 0)
		{
			ap->domain = get_string(tmp+7);
		}
		else if (g_ascii_strncasecmp(tmp, "nonce=", 6) == 0)
		{
			ap->nonce = get_string(tmp+6);
		}
		else if (g_ascii_strncasecmp(tmp, "opaque=", 7) == 0)
		{
			ap->opaque = get_string(tmp+7);
		}
		else if (g_ascii_strncasecmp(tmp, "stale=", 6) == 0)
		{
			gchar *stale;
			stale = get_string(tmp+6);
			
			if (!stale)
				continue;
			if (g_ascii_strncasecmp(stale, "true", 5) == 0)
			{
				ap->stale = TRUE;
			}
			else if (g_ascii_strncasecmp(stale, "false", 5) == 0)
			{
				ap->stale = FALSE;
			}
			g_free(stale);
		}
		else if (g_ascii_strncasecmp(tmp, "algorithm=", 10) == 0)
		{
			ap->algorithm = get_string(tmp+10);
		}
		else if (g_ascii_strncasecmp(tmp, "qop=", 4) == 0)
		{
			gchar *qop_string;
			gchar **strv;
			gint i;
			gboolean auth_flag = TRUE;

			qop_string = get_string(tmp+4);
			if (!qop_string)
				continue;

			strv = g_strsplit(qop_string, ",", 2);

			for (i = 0; strv[i]; i++)
			{
				if (g_ascii_strncasecmp(strv[i], "auth", 4))
					auth_flag = FALSE;
			}
			if (!auth_flag)
				continue;

			if (strv[0] && strv[1])
			{
				if (g_ascii_strncasecmp(strv[0], "auth", 4) == 0)
					ap->qop = g_strdup(strv[0]);
				else /* maybe argv[0] is auth-int */
					ap->qop = g_strdup(strv[1]);
			}
			else
			{
				ap->qop = g_strdup(strv[0]);
			}

			g_free(qop_string);
			g_strfreev(strv);
		}
		else
		{
			/* unknown parameter */
		}
	}

	ap->method = AUTH_METHOD_DIGEST;
	priv->auth_param = ap;

	g_strfreev(values);

	return TRUE;
}

static GIOStatus
kz_http_read_chars (KzHTTP *http, GIOChannel *iochannel,
	            gchar *buffer, gsize count, gsize *bytes_read)
{
	GIOStatus iostatus;
#ifdef USE_SSL
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
#endif
	/* Read the data into our buffer */
#ifdef USE_SSL
	if (priv->kz_ssl)
	{
		gsize len = count;
		gchar *pos;
		gboolean f = TRUE;
		pos = buffer;
		while (len > 0 && f)
		{
			int ret;
			ret = gnutls_record_recv (priv->kz_ssl->session, pos, len);

			if (ret > 0)
			{
				pos += ret;
				len -= ret;
			}
			else if (ret == 0)
			{
                        /* EOF */
				f = FALSE;

			}
			else if ((ret == GNUTLS_E_INTERRUPTED) ||
				 (ret == GNUTLS_E_AGAIN))
			{
                        /* read again */
				continue;
			}
			else {
				gnutls_perror (ret);
				f = FALSE;
			}
		}
		*bytes_read = count - len;
		if (*bytes_read == 0)
			iostatus = G_IO_STATUS_EOF;
		else
			iostatus = *bytes_read > 0 ? G_IO_STATUS_NORMAL : G_IO_STATUS_ERROR;
	}
	else
	{
		iostatus = g_io_channel_read_chars(iochannel, buffer, 
				count,
				bytes_read,
				NULL);
	}
#else
	iostatus = g_io_channel_read_chars(iochannel, buffer, 
					   count,
					   bytes_read,
					   NULL);
#endif

	return iostatus;
}

static GIOStatus
kz_http_read_line (KzHTTP *http, GIOChannel *iochannel, 
		   gchar **buffer, gsize *length,
		   gsize *terminator_pos)
{
	GIOStatus iostatus;
#ifdef USE_SSL
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
#endif
	/* Read the data into our buffer */
#ifdef USE_SSL

	if (priv->kz_ssl)
	{
		char c, prev_c = 0;
		int l;
		GString *tmp;
		iostatus = G_IO_STATUS_NORMAL;

		tmp = g_string_new(NULL);
		while (TRUE)
		{
			l = gnutls_record_recv (priv->kz_ssl->session, &c, 1);
			if (l > 0)
			{
				tmp = g_string_append_c(tmp, c);

				if (c == '\n' && prev_c == '\r')
					break;	

				prev_c = c;
			}
			else if (l == 0)
			{
				break;
			}
			else if ((l == GNUTLS_E_INTERRUPTED) ||
				 (l == GNUTLS_E_AGAIN))
			{
				continue;
			}
			else
			{
				gnutls_perror (l);
				iostatus = G_IO_STATUS_ERROR;
				break;
			}
		}

		if (tmp->len > 0)
		{
			*length = tmp->len;
			*buffer = g_string_free(tmp, FALSE);
		}
		else
		{
			g_string_free(tmp, TRUE);
		}
	}
	else
	{
		iostatus = g_io_channel_read_line(iochannel, buffer,
						  length,
						  terminator_pos,
						  NULL);
	}
#else
	iostatus = g_io_channel_read_line(iochannel, buffer, 
					  length,
					  terminator_pos,
					  NULL);
#endif

	return iostatus;
}

static GIOStatus
kz_http_in_header (KzHTTP *http, GIOChannel *iochannel)
{
	GIOStatus iostatus;
	gchar *buffer = NULL;
	gsize length;
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	/* Read the data into our buffer */
	iostatus = kz_http_read_line(http, iochannel,
				     &buffer, &length,
				     NULL);
	
	if (iostatus == G_IO_STATUS_ERROR)
		return iostatus;

	if (!buffer)
		return G_IO_STATUS_ERROR;
	
	if (strncmp(buffer, "HTTP/1.1", 8) == 0)
	{
		switch (buffer[9])
		{
		 case '2': /* 2xx Succeccful */
			break;
		 case '3': /* 3xx Redirection   */
			kz_http_set_redirection(http);
			break;
		 case '4': /* 4xx Client Error  */
			{
				const gchar *value = buffer + 9;
				if (g_ascii_strncasecmp(value, "401", 3) == 0)
				{
					priv->auth = TRUE;
					break;
				}
			}
		 case '1': /* 1xx Informational */
		 case '5': /* 5xx Server Error  */
		 default:
			{
				iostatus = G_IO_STATUS_ERROR;
				break;
			}
		}
	}
	else if (g_ascii_strncasecmp(buffer, "Content-Length:", 15) == 0)
	{
		guint size = (guint)strtol(buffer + 15, NULL, 10);
		g_object_set(G_OBJECT(KZ_IO(http)),
			     "file_size", size, NULL);
	}
	else if (g_ascii_strncasecmp(buffer, "Transfer-Encoding:", 18) == 0)
	{
		const gchar *value = buffer + 18;
		while (*value && g_ascii_isspace(*value))
			++value;
		if (g_str_has_prefix(value, "chunked"))
			kz_http_set_chunked_mode(http);
	}
	else if (g_ascii_strncasecmp(buffer, "Content-Type:", 13) == 0)
	{
		const gchar *value = buffer + 13;
		while (*value && g_ascii_isspace(*value))
			++value;
		priv->content_type = g_strchomp(g_strdup(value));
	}
	else if (g_ascii_strncasecmp(buffer, "Content-Encoding:", 17) == 0)
	{
		const gchar *value = buffer + 17;
		while (*value && g_ascii_isspace(*value))
			++value;
		priv->content_encoding = g_strchomp(g_strdup(value));
	}
	else if (g_ascii_strncasecmp(buffer, "Location:", 9) == 0)
	{
		const gchar *value = buffer + 9;
		while (*value && g_ascii_isspace(*value))
			++value;
		priv->location = g_strchomp(g_strdup(value));
	}
	else if (g_ascii_strncasecmp(buffer, "Last-Modified:", 15) == 0)
	{
		struct tm t;
		strptime(buffer + 15,
			 " %a, %d %b %Y %H:%M:%S %z", &t);
		g_object_set(G_OBJECT(KZ_IO(http)),
			     "last_modified", (guint)mktime(&t), NULL);
	}
	else if (g_ascii_strncasecmp(buffer, "WWW-Authenticate:", 17) == 0)
	{
		const gchar *value = buffer + 17;
		while (*value && g_ascii_isspace(*value))
			++value;
	
		if (g_ascii_strncasecmp(value, "Basic", 5) == 0)
		{
			value += 5;
			get_basic_auth_value(http, value);
		}
		else if (g_ascii_strncasecmp(value, "Digest", 6) == 0)
		{
			value += 6;
			get_digest_auth_values(http, value);
		}
	}
	else if (strncmp(buffer, "\r\n", 2) == 0) /* End of Header*/ 
	{
		priv->header = FALSE;
	}

	g_free(buffer);
	
	return iostatus;
}

static GIOStatus
kz_http_in_chunked_body(KzHTTP *http, GIOChannel *iochannel)
{
	GIOStatus iostatus = G_IO_STATUS_NORMAL;
	gchar *buffer = NULL;
	gsize bytes_read;
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);

	/* These codes are quite silly! FIX!! */
	/* Get chunk size */
	if (priv->chunk_size <= 0)
	{
		iostatus = kz_http_read_line(http, iochannel,
					     &buffer, &bytes_read,
					     NULL);
		if (iostatus == G_IO_STATUS_NORMAL)
		{
			if (buffer)
			{
				priv->chunk_size = strtol(buffer, NULL, 16);
				g_free(buffer);
				buffer = NULL;
			}
			/* check EOF */
			if (priv->chunk_size == 0)
				iostatus = G_IO_STATUS_EOF;
		}

	}
	if (iostatus == G_IO_STATUS_NORMAL)
	{
		/* Get a chunked body */
		buffer = g_new0(gchar, priv->chunk_size + 1);
		iostatus = kz_http_read_chars(http, iochannel,
					      buffer,
					      priv->chunk_size,
					      &bytes_read);
		if (iostatus == G_IO_STATUS_NORMAL)
		{
			KZ_IO_CLASS (kz_http_parent_class)->io_to_buffer(KZ_IO(http), bytes_read, buffer);

			priv->chunk_size -= bytes_read;
		}
		if (buffer)
		{
			g_free(buffer);
			buffer = NULL;
		}
	}
	if (iostatus == G_IO_STATUS_NORMAL)
	{
		/* Get CRLF */
		if (priv->chunk_size <= 0)
		{
			iostatus = kz_http_read_line(http, iochannel,
						     &buffer, &bytes_read,
						     NULL);
			if (buffer)
			{
				g_free(buffer);
			}
		}
	}

	return iostatus;
}


static GIOStatus
kz_http_in_body(KzHTTP *http, GIOChannel *iochannel)
{
	GIOStatus iostatus;
	gsize bytes_read;
	gchar buffer[BUFFER_SIZE + 1];

	/* Read the data into our buffer */
	iostatus = kz_http_read_chars (http, iochannel,
				       buffer,
				       BUFFER_SIZE,
				       &bytes_read);

	if (iostatus == G_IO_STATUS_NORMAL)
	{	
		KZ_IO_CLASS (kz_http_parent_class)->io_to_buffer(KZ_IO(http), bytes_read, buffer);
		if (bytes_read == 0)
			iostatus = G_IO_STATUS_EOF;
	}

	return iostatus;
}


static GIOStatus
kz_http_read_from_io (KzIO *io, GIOChannel *iochannel)
{
	KzHTTP *http;
	KzHTTPPrivate *priv;
	GIOStatus iostatus;

	g_return_val_if_fail(KZ_IS_HTTP(io), G_IO_STATUS_ERROR);

	http = KZ_HTTP(io);
	priv = KZ_HTTP_GET_PRIVATE (http);

	if (kz_http_is_in_header(http)) /* Header Section */
		iostatus = kz_http_in_header(http, iochannel);
	else if (kz_http_is_chunked_mode(http)) /* Chunked Body */
		iostatus = kz_http_in_chunked_body(http, iochannel);
	else 		/* Entity-Body Section */
		iostatus = kz_http_in_body(http, iochannel);

	if (iostatus == G_IO_STATUS_EOF)
	{
		if (kz_http_is_redirection(http))
		{
			g_object_set(G_OBJECT(http),
				     "uri", priv->location,
				     NULL);
			/* iostatus = G_IO_STATUS_AGAIN; */
		}
		else if (kz_http_is_authentication(http))
		{
			gboolean ret;
			ret = kz_http_show_dialog(http);
			if (ret)
			{
				iostatus = G_IO_STATUS_AGAIN;
			} else {
				iostatus = G_IO_STATUS_ERROR;
			}
		}
		else if (priv->content_encoding)
		{
			return kz_io_decode_buffer(KZ_IO(http), priv->content_encoding);
		}
	}

	return iostatus;
}


static void 
cb_http_connect(GTcpSocket *socket,
		GTcpSocketConnectAsyncStatus status, gpointer data)
{
	KzHTTP *http;
	GIOStatus iostatus = G_IO_STATUS_NORMAL;
	GIOChannel *iochannel;
	gchar *command, *tmp_ua;
	const gchar *method = methods[0];
	gsize n;
	gchar *URL, *host_header, *user_agent = NULL;
	gboolean override = FALSE;
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (data);

	http = KZ_HTTP(data);

	if ( status != GTCP_SOCKET_CONNECT_ASYNC_STATUS_OK )
	{
		kz_http_error(http);
		return;
	}

	/* Get the IOChannel */
	iochannel = gnet_tcp_socket_get_io_channel(socket);
	if (iochannel == NULL)
	{
		kz_http_error(http);
		return;
	}

	priv->socket = socket;
	g_io_channel_ref(iochannel);
	KZ_IO(http)->iochannel = iochannel;

	g_io_channel_set_flags(KZ_IO(http)->iochannel,
			       G_IO_FLAG_NONBLOCK, NULL);

	/* proxy */
	if (kz_http_use_proxy(http))
	{
		URL = g_strdup_printf("http://%s:%u%s",
				      priv->hostname,
				      priv->port,
				      priv->path);
	}
	else
	{
		URL = g_strdup(priv->path);
	}

	/* Set method */
	if (priv->method >= 0 && priv->method < n_methods)
		method = methods[priv->method];
	else
		g_warning("KzHTTP: Invalid method type was specified!");

#ifdef USE_SSL
	if (priv->kz_ssl)
	{
		host_header = g_strdup_printf("Host: %s\r\n",
					      priv->hostname);
	}
	else
	{
		host_header = g_strdup_printf("Host: %s:%u\r\n",
					      priv->hostname, priv->port);
	}
#else
	host_header = g_strdup_printf("Host: %s:%u\r\n",
				      priv->hostname, priv->port);
#endif

	KZ_CONF_GET("Global", "override_user_agent", override, BOOL);
	if (override)
		user_agent = KZ_CONF_GET_STR("Global", "user_agent");
	tmp_ua = user_agent ? user_agent : "Kazehakase/"VERSION;
	/* Send GET command */
	if (priv->method == KZ_HTTP_METHOD_POST)
	{
		gchar *c_len;

		if (priv->post_data)
			c_len = g_strdup_printf("%d", strlen(priv->post_data));
		else
			c_len = g_strdup("0");

		command = g_strconcat(method, " ",
				      URL, " HTTP/1.1\r\n",
				      host_header,
				      "User-Agent: ", tmp_ua, "\r\n",
				      "Content-Type: text/xml\r\n",
				      "Content-Length: ", c_len, "\r\n",
				      "Accept-Encoding: gzip,deflate\r\n",
				      "Connection: close\r\n\r\n",
				      priv->post_data, "\r\n", NULL);
		g_free(c_len);
	}
	else
	{
		if (priv->auth && priv->auth_param && priv->auth_param->string)
		{
			command = g_strconcat(method, " ",
					URL, " HTTP/1.1\r\n",
					host_header,
					"Authorization: ", priv->auth_param->string ,"\r\n",
				        "User-Agent: ", tmp_ua, "\r\n",
					"Accept-Encoding: gzip,deflate\r\n",
					"Connection: close\r\n\r\n", NULL);
			g_free (priv->auth_param->string);
			priv->auth_param->string = NULL;
			priv->auth = FALSE;
		}
		else
		{
			command = g_strconcat(method, " ",
					URL, " HTTP/1.1\r\n",
					host_header,
				        "User-Agent: ", tmp_ua, "\r\n",
					"Accept-Encoding: gzip,deflate\r\n",
					"Connection: close\r\n\r\n", NULL);
		}
	}

	if (user_agent)
		g_free(user_agent);

#ifdef USE_SSL
	if (priv->kz_ssl)
	{
		gchar *pos = command;
		int len = strlen(command);
		ssl_init(priv->kz_ssl, iochannel);

		while (len > 0)
		{
			int ret;
			ret = gnutls_record_send (priv->kz_ssl->session, pos, len);

			if(ret > 0)
			{
				pos += ret;
				len -= ret;
			}
			else if ((ret == GNUTLS_E_INTERRUPTED) ||
				 (ret == GNUTLS_E_AGAIN))
			{
                                /* write again */
				continue;
			}
			else
			{
				gnutls_perror (ret);
				break;
			}
		}
		iostatus = G_IO_STATUS_NORMAL;
	}
	else
	{
		iostatus = g_io_channel_write_chars(KZ_IO(http)->iochannel,
				command,
				strlen(command),
				&n,
				NULL);
	}
#else
	iostatus = g_io_channel_write_chars(KZ_IO(http)->iochannel,
					    command,
					    strlen(command),
					    &n,
					    NULL);
#endif
	
	g_free(command);
	g_free(host_header);
	g_free(URL);

	if (iostatus != G_IO_STATUS_NORMAL)
	{
		kz_http_error(http);
		return;
	}

	/* call io_set_iochannel, start to loading */
	KZ_IO_CLASS (kz_http_parent_class)->io_set_channel(KZ_IO(http));
}

static void
kz_http_start (KzIO *io)
{
	gchar proxy_name[1024];
	gboolean exist, use_proxy;
	gchar *http_host = NULL;
	guint http_port;
	KzHTTP *http;
	KzProxyItem *item = NULL;
	KzHTTPPrivate *priv;
	
	g_return_if_fail(KZ_IS_HTTP(io));
	http = KZ_HTTP(io);
	priv = KZ_HTTP_GET_PRIVATE (http);

	priv->chunked = FALSE;
	priv->header = TRUE;

	/* proxy check */
	KZ_CONF_GET("Global", "use_proxy", use_proxy, BOOL);
	if (!use_proxy)
		goto NO_PROXY;
	
	exist = KZ_CONF_GET("Global", "proxy_name", proxy_name, STRING);
	if (!exist)
	{
		/* We can find no proxy setting, so connect without proxy. */
		goto NO_PROXY;
	}

	item = kz_proxy_find(proxy_name);
	if(!item)
	{
		/* There is no proxy object whose name is proxy_name, 
		   so connect without proxy */
		goto NO_PROXY;
	}
	
	/* check the need of the poxy */
	if (item->no_proxies_on)
	{
		gchar **no_proxies;
		gint i = 0;
		no_proxies = g_strsplit_set(item->no_proxies_on, ", ", -1);

		if (!no_proxies)	
			goto NO_PROXY;
		while (no_proxies[i])
		{
			if (g_str_has_suffix(priv->hostname, no_proxies[i]))
			{
				g_strfreev(no_proxies);
				goto NO_PROXY;
			}
			i++;
		}
		g_strfreev(no_proxies);
	}

	/* There is a valid proxy setting, so connect with this proxy*/
	priv->use_proxy = TRUE;
	gnet_tcp_socket_connect_async(item->http_host, item->http_port,
	 			      cb_http_connect, http);

	g_object_unref(G_OBJECT(item));
	return;
	
NO_PROXY:
	http_host = priv->hostname;
	http_port = priv->port;
	gnet_tcp_socket_connect_async(http_host, http_port,
				      cb_http_connect, http);
	return;
}


static void
kz_http_error (KzHTTP *http)
{
	g_return_if_fail(KZ_IS_HTTP(http));

	KZ_IO_CLASS (kz_http_parent_class)->io_error(KZ_IO(http));
}


static void
kz_http_set_chunked_mode (KzHTTP *http)
{
	KzHTTPPrivate *priv;
	g_return_if_fail(KZ_IS_HTTP(http));
	priv = KZ_HTTP_GET_PRIVATE (http);

	priv->chunked = TRUE;
}

static void
kz_http_set_redirection (KzHTTP *http)
{
	KzHTTPPrivate *priv;
	g_return_if_fail(KZ_IS_HTTP(http));
	priv = KZ_HTTP_GET_PRIVATE (http);

	priv->redirection = TRUE;
}

static gboolean
kz_http_use_proxy (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	return priv->use_proxy;
}

static gboolean
kz_http_is_in_header (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	return priv->header;
}

static gboolean
kz_http_is_chunked_mode (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	return priv->chunked;
}

static gboolean
kz_http_is_redirection (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	return priv->redirection;
}

static gboolean
kz_http_is_authentication (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	return priv->auth;
}

static gboolean
kz_http_show_dialog (KzHTTP *http)
{
	KzHTTPPrivate *priv = KZ_HTTP_GET_PRIVATE (http);
	KzPromptDialog *prompt;
	gboolean ret;
	gchar *message;
	AuthParam *ap = priv->auth_param;
	gchar *scheme = "http";
	guint default_port = 80;


	prompt= KZ_PROMPT_DIALOG(kz_prompt_dialog_new(TYPE_PROMPT_USER_PASS));

	kz_prompt_dialog_set_title(prompt, _("Authentication"));
#ifdef USE_SSL
	if (priv->kz_ssl)
	{
		scheme = "https";
		default_port = 443;
	}
#endif
	if (priv->port == default_port)
	{
		message = g_strdup_printf (_("Enter username and password for \"%s\" at %s://%s"), ap->realm, scheme, priv->hostname);
	}
	else
	{
		message = g_strdup_printf (_("Enter username and password for \"%s\" at %s://%s:%u"), ap->realm, scheme, priv->hostname, priv->port);
	}
	kz_prompt_dialog_set_message_text(prompt, message);
	g_free(message);

	kz_prompt_dialog_run(prompt);

	ret = kz_prompt_dialog_get_confirm_value(prompt);
	if (ret)
	{
		const gchar *user = kz_prompt_dialog_get_user(prompt);
		const gchar *pass = kz_prompt_dialog_get_password(prompt);

		switch (ap->method)
		{
		 case AUTH_METHOD_BASIC:
		 {
			gchar *str, *base64;
			str = g_strdup_printf("%s:%s", user, pass);
			base64 = kz_base64_encode(str);
			ap->string = g_strdup_printf("Basic %s", base64);
			g_free(str);
			if (base64)
				g_free(base64);
			break;
		 }
		 case AUTH_METHOD_DIGEST:
		 {
			gchar *response, *response_md5, *a1, *a2, *a1_md5, *a2_md5;
			const gchar *cnonce = "hoge";
			const gchar *method = methods[0];
			
			if (priv->method >= 0 && priv->method < n_methods)
				method = methods[priv->method];

			if (ap->algorithm && g_ascii_strncasecmp(ap->algorithm, "MD5-sess", 8) == 0)
			{
				gchar *tmp, *tmp_md5;
				tmp = g_strdup_printf("%s:%s:%s", user, ap->realm, pass);
				tmp_md5 = egg_str_get_md5_str(tmp);
				a1 = g_strdup_printf("%s:%s:%s", tmp_md5, ap->nonce, cnonce);
				g_free(tmp);
				g_free(tmp_md5);
			}
			else
			{
				a1 = g_strdup_printf("%s:%s:%s", user, ap->realm, pass);
			}

			if (ap->qop && g_ascii_strncasecmp(ap->qop, "auth-int", 8) == 0)
			{
				gchar *ent_md5;
				ent_md5 = egg_str_get_md5_str(priv->post_data);
				a2 = g_strdup_printf("%s:%s:%s", method, priv->path, ent_md5);

				g_free(ent_md5);
			}
			else
			{
				a2 = g_strdup_printf("%s:%s", method, priv->path);
			}
			a1_md5 = egg_str_get_md5_str(a1);
			a2_md5 = egg_str_get_md5_str(a2);

			if (ap->qop)
			{
				response = g_strdup_printf ("%s:%s:%08X:%s:%s:%s",
						a1_md5, 
						ap->nonce, 
						1, /* nc */
						cnonce, /* cnonce */
						ap->qop, /* qop */
						a2_md5);
			}
			else
			{
				response = g_strdup_printf ("%s:%s:%s",
						a1_md5, 
						ap->nonce, 
						a2_md5);
			}
			response_md5 = egg_str_get_md5_str(response);
			ap->string = g_strconcat("Digest username=\"", user, "\",",
			       	          " realm=\"", ap->realm, "\",",
			       	          " nonce=\"", ap->nonce, "\",",
			       	          " uri=\"", priv->path, "\",",
			       	          " algorithm=", ap->algorithm, ","
			       	          " qop=", ap->qop, ",",
			       	          " nc=", "00000001", ",", 
			       	          " cnonce=\"", cnonce, "\",",
			       	          " response=\"", response_md5, "\"", NULL);

			g_free(response);
			g_free(response_md5);
			g_free(a1);
			g_free(a2);
			g_free(a1_md5);
			g_free(a2_md5);
			break;
		 }
		 default:
			break;
		}
	}
	gtk_widget_destroy(GTK_WIDGET(prompt));

	return ret;
}

#ifdef USE_SSL

static void
ssl_init (KzSSL *kz_ssl, GIOChannel *channel)
{
	gint fd;
	gint ret;
	const int cert_type_priority[] = { GNUTLS_CRT_X509,
					   0 };

	ret = gnutls_certificate_allocate_credentials (&kz_ssl->xcred);
	if(ret < 0)
	{
		g_warning("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(ret));
		return;
	}
        /* initialize TLS session */
	gnutls_init (&kz_ssl->session, GNUTLS_CLIENT);

	gnutls_set_default_priority (kz_ssl->session);
	gnutls_certificate_type_set_priority (kz_ssl->session, cert_type_priority);

	gnutls_credentials_set (kz_ssl->session, GNUTLS_CRD_CERTIFICATE, kz_ssl->xcred);
	fd = g_io_channel_unix_get_fd(channel);
	gnutls_transport_set_ptr (kz_ssl->session,  (gnutls_transport_ptr)fd);

	do
	{
		ret = gnutls_handshake (kz_ssl->session);
	}
	while ((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED));

	if (ret < 0)
	{
		gnutls_perror(ret);
		return;
	}
}

static void
ssl_term (KzSSL *kz_ssl)
{
	if (!kz_ssl)
		return;
	gnutls_bye (kz_ssl->session, GNUTLS_SHUT_RDWR);
	gnutls_certificate_free_credentials (kz_ssl->xcred);
	gnutls_deinit (kz_ssl->session);
}

#endif

