Merged soc.2013.gobjectification branch soc.2013.gobjectification.plugins

Mon, 16 Sep 2013 18:08:19 +0530

author
Ankit Vani <a@nevitus.org>
date
Mon, 16 Sep 2013 18:08:19 +0530
branch
soc.2013.gobjectification.plugins
changeset 36744
a7c26ee6e466
parent 36743
3d44db6dd9b4 (current diff)
parent 34946
76aa2e0f8701 (diff)
child 36745
75ed0608dcd6

Merged soc.2013.gobjectification branch

libpurple/protocols/gg/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/gg/confer.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/confer.h file | annotate | diff | comparison | revisions
libpurple/protocols/gg/gg.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/gg.h file | annotate | diff | comparison | revisions
libpurple/protocols/gg/image.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/servconn.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/status.c file | annotate | diff | comparison | revisions
--- a/libpurple/Makefile.mingw	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/Makefile.mingw	Mon Sep 16 18:08:19 2013 +0530
@@ -156,6 +156,7 @@
 		-lnss3 \
 		-lnspr4 \
 		-lgnutls \
+		-lz \
 		$(VV_LIBS)
 
 include $(PIDGIN_COMMON_RULES)
--- a/libpurple/plugins/perl/common/Request.xs	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/plugins/perl/common/Request.xs	Mon Sep 16 18:08:19 2013 +0530
@@ -287,41 +287,48 @@
 purple_request_field_choice_new(class, id, text, default_value = 0)
 	const char *id
 	const char *text
-	int default_value
+	gpointer default_value
 	C_ARGS: id, text, default_value
 
 void
-purple_request_field_choice_add(field, label)
+purple_request_field_choice_add(field, label, value)
 	Purple::Request::Field field
 	const char *label
+	gpointer value
 
-int
+gpointer
 purple_request_field_choice_get_default_value(field)
 	Purple::Request::Field field
 
-void
-purple_request_field_choice_get_labels(field)
-	Purple::Request::Field field
-PREINIT:
-	GList *l;
-PPCODE:
-	for (l = purple_request_field_choice_get_labels(field); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
-	}
+ # I'm not sure, if this is the correct implementation - if anyone will need it,
+ # he will add this back to API.
+ #void
+ #purple_request_field_choice_get_elements(field)
+ #	Purple::Request::Field field
+ #PREINIT:
+ #	GList *l;
+ #PPCODE:
+ #	for (l = purple_request_field_choice_get_elements(field); l != NULL; l = l->next) {
+ #		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ #		l = l->next;
+ #		if (l == NULL)
+ #			break;
+ #		XPUSHs(l->data);
+ #	}
 
-int
+gpointer
 purple_request_field_choice_get_value(field)
 	Purple::Request::Field field
 
 void
 purple_request_field_choice_set_default_value(field, default_value)
 	Purple::Request::Field field
-	int default_value
+	gpointer default_value
 
 void
 purple_request_field_choice_set_value(field, value)
 	Purple::Request::Field field
-	int value
+	gpointer value
 
 MODULE = Purple::Request  PACKAGE = Purple::Request::Field  PREFIX = purple_request_field_
 PROTOTYPES: ENABLE
@@ -604,7 +611,7 @@
 	Purple::Request::Fields fields
 	const char *id
 
-int
+gpointer
 purple_request_fields_get_choice(fields, id)
 	Purple::Request::Fields fields
 	const char *id
--- a/libpurple/protocols/gg/Makefile.am	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/Makefile.am	Mon Sep 16 18:08:19 2013 +0530
@@ -1,5 +1,7 @@
 #V=0
-#GADU_EXTRA_WARNINGS = -Wall -Wextra -Werror
+#CFLAGS = -g -O0
+GADU_EXTRA = -Wall -Wextra -fno-inline
+#GADU_EXTRA += -Werror
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
@@ -49,46 +51,56 @@
 GADU_CFLAGS += $(GNUTLS_CFLAGS)
 endif
 
+GADU_LIBS += $(JSON_LIBS)
+
 GGSOURCES = \
 	$(INTGGSOURCES) \
-	utils.h \
-	utils.c \
-	confer.h \
-	confer.c \
-	blist.h \
+	account.c \
+	account.h \
+	avatar.c \
+	avatar.h \
 	blist.c \
-	gg.h \
+	blist.h \
+	chat.c \
+	chat.h \
+	deprecated.c \
+	deprecated.h \
+	edisc.c \
+	edisc.h \
 	gg.c \
-	resolver-purple.h \
-	resolver-purple.c \
-	image.h \
+	gg.h \
+	html.c \
+	html.h \
 	image.c \
-	account.h \
-	account.c \
-	deprecated.h \
-	deprecated.c \
-	purplew.h \
+	image.h \
+	libgadu-events.c \
+	libgadu-events.h \
+	libgaduw.c \
+	libgaduw.h \
+	message-prpl.c \
+	message-prpl.h \
+	multilogon.c \
+	multilogon.h \
+	pubdir-prpl.c \
+	pubdir-prpl.h \
 	purplew.c \
-	libgaduw.h \
-	libgaduw.c \
-	avatar.h \
-	avatar.c \
-	libgadu-events.h \
-	libgadu-events.c \
+	purplew.h \
+	resolver-purple.c \
+	resolver-purple.h \
 	roster.c \
 	roster.h \
+	servconn.c \
+	servconn.h \
+	status.c \
+	status.h \
+	tcpsocket.c \
+	tcpsocket.h \
+	utils.c \
+	utils.h \
 	validator.c \
 	validator.h \
 	xml.c \
 	xml.h \
-	multilogon.c \
-	multilogon.h \
-	status.c \
-	status.h \
-	servconn.c \
-	servconn.h \
-	pubdir-prpl.c \
-	pubdir-prpl.h \
 	oauth/oauth.c \
 	oauth/oauth.h \
 	oauth/oauth-parameter.c \
@@ -118,10 +130,11 @@
 endif
 
 AM_CPPFLAGS = \
-	$(GADU_EXTRA_WARNINGS) \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(JSON_CFLAGS) \
 	$(GPLUGIN_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(GADU_EXTRA)
--- a/libpurple/protocols/gg/Makefile.mingw	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/Makefile.mingw	Mon Sep 16 18:08:19 2013 +0530
@@ -32,11 +32,13 @@
 			-I$(GTK_TOP)/include \
 			-I$(GTK_TOP)/include/glib-2.0 \
 			-I$(GTK_TOP)/lib/glib-2.0/include \
-			-I$(GNUTLS_TOP)/include
+			-I$(GNUTLS_TOP)/include \
+			-I$(JSON_GLIB_TOP)/include/json-glib-1.0
 
 LIB_PATHS += \
 			-L$(GTK_TOP)/lib \
 			-L$(GNUTLS_TOP)/lib \
+			-L$(JSON_GLIB_TOP)/lib \
 			-L$(PURPLE_TOP)
 
 ##
@@ -46,9 +48,11 @@
 	account.c \
 	avatar.c \
 	buddylist.c \
-	confer.c \
+	chat.c \
 	deprecated.c \
+	edisc.c \
 	gg.c \
+	html.c \
 	image.c \
 	lib/common.c \
 	lib/dcc.c \
@@ -68,6 +72,7 @@
 	lib/sha1.c \
 	libgadu-events.c \
 	libgaduw.c \
+	message-prpl.c \
 	multilogon.c \
 	oauth/oauth.c \
 	oauth/oauth-parameter.c \
@@ -78,6 +83,7 @@
 	roster.c \
 	servconn.c \
 	status.c \
+	tcpsocket.c \
 	utils.c \
 	validator.c \
 	xml.c
@@ -89,8 +95,10 @@
 ##
 LIBS =	\
 			-lglib-2.0 \
+			-lgobject-2.0 \
 			-lgnutls \
 			-lintl \
+			-ljson-glib-1.0 \
 			-lpurple \
 			-lws2_32 \
 			-lz
--- a/libpurple/protocols/gg/avatar.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/avatar.c	Mon Sep 16 18:08:19 2013 +0530
@@ -151,10 +151,12 @@
 {
 	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
 	ggp_avatar_buddy_update_req *pending_update =
-		g_new(ggp_avatar_buddy_update_req, 1);
+		g_new(ggp_avatar_buddy_update_req, 1); //TODO: leak?
 
-	purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
-		uin, timestamp);
+	if (purple_debug_is_verbose()) {
+		purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
+			uin, timestamp);
+	}
 
 	pending_update->uin = uin;
 	pending_update->timestamp = timestamp;
@@ -165,7 +167,9 @@
 
 void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin)
 {
-	purple_debug_info("gg", "ggp_avatar_buddy_remove(%p, %u)\n", gc, uin);
+	if (purple_debug_is_verbose()) {
+		purple_debug_misc("gg", "ggp_avatar_buddy_remove(%p, %u)\n", gc, uin);
+	}
 
 	purple_buddy_icons_set_for_user(purple_connection_get_account(gc),
 		ggp_uin_to_str(uin), NULL, 0, NULL);
@@ -218,10 +222,12 @@
 		old_timestamp_str, NULL, 10) : 0;
 	if (old_timestamp == pending_update->timestamp)
 	{
-		purple_debug_misc("gg",
-			"ggp_avatar_buddy_update_next(%p): "
-			"%u have up to date avatar with ts=%lu\n", gc,
-			pending_update->uin, pending_update->timestamp);
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("gg",
+				"ggp_avatar_buddy_update_next(%p): "
+				"%u have up to date avatar with ts=%lu\n", gc,
+				pending_update->uin, pending_update->timestamp);
+		}
 		return FALSE;
 	}
 	if (old_timestamp > pending_update->timestamp)
@@ -322,10 +328,15 @@
 
 void ggp_avatar_own_set(PurpleConnection *gc, PurpleStoredImage *img)
 {
-	ggp_avatar_own_data *own_data = ggp_avatar_get_avdata(gc)->own_data;
+	ggp_avatar_own_data *own_data;
+	
+	if (!PURPLE_CONNECTION_IS_VALID(gc) || !PURPLE_CONNECTION_IS_CONNECTED(gc))
+		return;
 	
 	purple_debug_info("gg", "ggp_avatar_own_set(%p, %p)", gc, img);
 	
+	own_data = ggp_avatar_get_avdata(gc)->own_data;
+	
 	if (img == NULL)
 	{
 		purple_debug_warning("gg", "ggp_avatar_own_set: avatar removing"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/chat.c	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,619 @@
+#include "chat.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "utils.h"
+#include "message-prpl.h"
+
+#if GGP_ENABLE_GG11
+
+typedef struct _ggp_chat_local_info ggp_chat_local_info;
+
+struct _ggp_chat_session_data
+{
+	ggp_chat_local_info *chats;
+	int chats_count;
+
+	gboolean got_all_chats_info;
+	GSList *pending_joins;
+};
+
+struct _ggp_chat_local_info
+{
+	int local_id;
+	uint64_t id;
+	
+	PurpleConversation *conv;
+	PurpleConnection *gc;
+	
+	gboolean left;
+	gboolean previously_joined;
+	
+	uin_t *participants;
+	int participants_count;
+};
+
+static ggp_chat_local_info * ggp_chat_new(PurpleConnection *gc, uint64_t id);
+static ggp_chat_local_info * ggp_chat_get(PurpleConnection *gc, uint64_t id);
+static void ggp_chat_open_conv(ggp_chat_local_info *chat);
+static ggp_chat_local_info * ggp_chat_get_local(PurpleConnection *gc,
+	int local_id);
+static void ggp_chat_joined(ggp_chat_local_info *chat, uin_t uin);
+static void ggp_chat_left(ggp_chat_local_info *chat, uin_t uin);
+static const gchar * ggp_chat_get_name_from_id(uint64_t id);
+static uint64_t ggp_chat_get_id_from_name(const gchar * name);
+static void ggp_chat_join_id(PurpleConnection *gc, uint64_t id);
+
+static inline ggp_chat_session_data *
+ggp_chat_get_sdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return accdata->chat_data;
+}
+
+void ggp_chat_setup(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_chat_session_data *sdata = g_new0(ggp_chat_session_data, 1);
+
+	accdata->chat_data = sdata;
+}
+
+void ggp_chat_cleanup(PurpleConnection *gc)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	int i;
+
+	g_slist_free_full(sdata->pending_joins, g_free);
+	for (i = 0; i < sdata->chats_count; i++)
+		g_free(sdata->chats[i].participants);
+	g_free(sdata->chats);
+	g_free(sdata);
+}
+
+static ggp_chat_local_info * ggp_chat_new(PurpleConnection *gc, uint64_t id)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	int local_id;
+	ggp_chat_local_info *chat;
+
+	if (NULL != (chat = ggp_chat_get(gc, id)))
+		return chat;
+
+	local_id = sdata->chats_count++;
+	sdata->chats = g_realloc(sdata->chats,
+		sdata->chats_count * sizeof(ggp_chat_local_info));
+	chat = &sdata->chats[local_id];
+
+	chat->local_id = local_id;
+	chat->id = id;
+	chat->conv = NULL;
+	chat->gc = gc;
+	chat->left = FALSE;
+	chat->previously_joined = FALSE;
+	chat->participants = NULL;
+	chat->participants_count = 0;
+
+	return chat;
+}
+
+static ggp_chat_local_info * ggp_chat_get(PurpleConnection *gc, uint64_t id)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	int i;
+
+	for (i = 0; i < sdata->chats_count; i++)
+		if (sdata->chats[i].id == id)
+			return &sdata->chats[i];
+
+	return NULL;
+}
+
+static void ggp_chat_open_conv(ggp_chat_local_info *chat)
+{
+	PurpleConvChat *pcchat;
+	int i;
+
+	if (chat->conv != NULL)
+		return;
+
+	chat->conv = serv_got_joined_chat(chat->gc, chat->local_id,
+		ggp_chat_get_name_from_id(chat->id));
+	if (chat->previously_joined)
+	{
+		purple_conversation_write(chat->conv, NULL,
+			_("You have re-joined the chat"), PURPLE_MESSAGE_SYSTEM,
+			time(NULL));
+	}
+	chat->previously_joined = TRUE;
+
+	pcchat = purple_conversation_get_chat_data(chat->conv);
+	purple_conv_chat_clear_users(pcchat);
+	for (i = 0; i < chat->participants_count; i++)
+		purple_conv_chat_add_user(pcchat,
+			ggp_uin_to_str(chat->participants[i]), NULL,
+			PURPLE_CBFLAGS_NONE, FALSE);
+}
+
+static ggp_chat_local_info * ggp_chat_get_local(PurpleConnection *gc,
+	int local_id)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	int i;
+
+	for (i = 0; i < sdata->chats_count; i++)
+		if (sdata->chats[i].local_id == local_id)
+			return &sdata->chats[i];
+
+	return NULL;
+}
+
+void ggp_chat_got_event(PurpleConnection *gc, const struct gg_event *ev)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	ggp_chat_local_info *chat;
+	uint32_t i;
+
+	if (ev->type == GG_EVENT_CHAT_INFO)
+	{
+		const struct gg_event_chat_info *eci = &ev->event.chat_info;
+		chat = ggp_chat_new(gc, eci->id);
+		for (i = 0; i < eci->participants_count; i++)
+			ggp_chat_joined(chat, eci->participants[i]);
+	}
+	else if (ev->type == GG_EVENT_CHAT_INFO_GOT_ALL)
+	{
+		GSList *it = sdata->pending_joins;
+		sdata->got_all_chats_info = TRUE;
+		while (it)
+		{
+			uint64_t *id_p = it->data;
+			ggp_chat_join_id(gc, *id_p);
+			it = g_slist_next(it);
+		}
+		g_slist_free_full(sdata->pending_joins, g_free);
+		sdata->pending_joins = NULL;
+	}
+	else if (ev->type == GG_EVENT_CHAT_INFO_UPDATE)
+	{
+		const struct gg_event_chat_info_update *eciu =
+			&ev->event.chat_info_update;
+		chat = ggp_chat_get(gc, eciu->id);
+		if (!chat)
+		{
+			purple_debug_error("gg", "ggp_chat_got_event: "
+				"chat %" G_GUINT64_FORMAT " not found\n",
+				eciu->id);
+			return;
+		}
+		if (eciu->type == GG_CHAT_INFO_UPDATE_ENTERED)
+			ggp_chat_joined(chat, eciu->participant);
+		else if (eciu->type == GG_CHAT_INFO_UPDATE_EXITED)
+			ggp_chat_left(chat, eciu->participant);
+		else
+			purple_debug_warning("gg", "ggp_chat_got_event: "
+				"unknown update type - %d", eciu->type);
+	}
+	else if (ev->type == GG_EVENT_CHAT_CREATED)
+	{
+		const struct gg_event_chat_created *ecc =
+			&ev->event.chat_created;
+		uin_t me = ggp_str_to_uin(purple_account_get_username(
+			purple_connection_get_account(gc)));
+		chat = ggp_chat_new(gc, ecc->id);
+		ggp_chat_joined(chat, me);
+		ggp_chat_open_conv(chat);
+	}
+	else if (ev->type == GG_EVENT_CHAT_INVITE_ACK)
+	{
+		/* ignore */
+	}
+	else
+	{
+		purple_debug_fatal("gg", "ggp_chat_got_event: unexpected event "
+			"- %d\n", ev->type);
+	}
+}
+
+static int ggp_chat_participant_find(ggp_chat_local_info *chat, uin_t uin)
+{
+	int i;
+	for (i = 0; i < chat->participants_count; i++)
+		if (chat->participants[i] == uin)
+			return i;
+	return -1;
+}
+
+static void ggp_chat_joined(ggp_chat_local_info *chat, uin_t uin)
+{
+	int idx = ggp_chat_participant_find(chat, uin);
+	if (idx >= 0)
+	{
+		purple_debug_warning("gg", "ggp_chat_joined: "
+			"user %u is already present in chat %" G_GUINT64_FORMAT
+			"\n", uin, chat->id);
+		return;
+	}
+	chat->participants_count++;
+	chat->participants = g_realloc(chat->participants,
+		sizeof(uin) * chat->participants_count);
+	chat->participants[chat->participants_count - 1] = uin;
+	
+	if (!chat->conv)
+		return;
+	purple_conv_chat_add_user(purple_conversation_get_chat_data(chat->conv),
+		ggp_uin_to_str(uin), NULL, PURPLE_CBFLAGS_NONE, TRUE);
+}
+
+static void ggp_chat_left(ggp_chat_local_info *chat, uin_t uin)
+{
+	uin_t me;
+	int idx = ggp_chat_participant_find(chat, uin);
+
+	if (idx < 0)
+	{
+		purple_debug_warning("gg", "ggp_chat_joined: "
+			"user %u isn't present in chat %" G_GUINT64_FORMAT "\n",
+			uin, chat->id);
+		return;
+	}
+	chat->participants[idx] =
+		chat->participants[chat->participants_count - 1];
+	chat->participants_count--;
+	chat->participants = g_realloc(chat->participants,
+		sizeof(uin) * chat->participants_count);
+
+	if (chat->conv == NULL)
+		return;
+
+	me = ggp_str_to_uin(purple_account_get_username(
+		purple_connection_get_account(chat->gc)));
+
+	if (me == uin)
+	{
+		purple_conversation_write(chat->conv, NULL,
+			_("You have left the chat"), PURPLE_MESSAGE_SYSTEM,
+			time(NULL));
+		serv_got_chat_left(chat->gc, chat->local_id);
+		chat->conv = NULL;
+		chat->left = TRUE;
+	}
+	purple_conv_chat_remove_user(purple_conversation_get_chat_data(
+		chat->conv), ggp_uin_to_str(uin), NULL);
+}
+
+GList * ggp_chat_info(PurpleConnection *gc)
+{
+	GList *m = NULL;
+	struct proto_chat_entry *pce;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Conference identifier:");
+	pce->identifier = "id";
+	pce->required = FALSE;
+	m = g_list_append(m, pce);
+	
+	return m;
+}
+
+GHashTable * ggp_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
+{
+	GHashTable *defaults;
+
+	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	if (chat_name != NULL && ggp_chat_get_id_from_name(chat_name) != 0)
+		g_hash_table_insert(defaults, "id", g_strdup(chat_name));
+
+	return defaults;
+}
+
+char * ggp_chat_get_name(GHashTable *components)
+{
+	return g_strdup((gchar*)g_hash_table_lookup(components, "id"));
+}
+
+static const gchar * ggp_chat_get_name_from_id(uint64_t id)
+{
+	static gchar buff[30];
+	g_snprintf(buff, sizeof(buff), "%" G_GUINT64_FORMAT, id);
+	return buff;
+}
+
+static uint64_t ggp_chat_get_id_from_name(const gchar * name)
+{
+	uint64_t id;
+	gchar *endptr;
+
+	if (name == NULL)
+		return 0;
+
+	id = g_ascii_strtoull(name, &endptr, 10);
+
+	if (*endptr != '\0' || id == G_MAXUINT64)
+		return 0;
+
+	return id;
+}
+
+void ggp_chat_join(PurpleConnection *gc, GHashTable *components)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	const gchar *id_cs;
+	gchar *id_s;
+	uint64_t id;
+
+	id_cs = g_hash_table_lookup(components, "id");
+	id_s = g_strdup(id_cs);
+	if (id_s)
+		g_strstrip(id_s);
+	if (id_s == NULL || id_s[0] == '\0')
+	{
+		g_free(id_s);
+		if (gg_chat_create(info->session) < 0)
+		{
+			purple_debug_error("gg", "ggp_chat_join; "
+				"cannot create\n");
+			purple_serv_got_join_chat_failed(gc, components);
+		}
+		return;
+	}
+	id = ggp_chat_get_id_from_name(id_s);
+	g_free(id_s);
+
+	if (!id)
+	{
+		char *buff = g_strdup_printf(
+			_("%s is not a valid room identifier"), id_cs);
+		purple_notify_error(gc, _("Invalid Room Identifier"),
+			_("Invalid Room Identifier"), buff);
+		g_free(buff);
+		purple_serv_got_join_chat_failed(gc, components);
+		return;
+	}
+
+	if (sdata->got_all_chats_info)
+		ggp_chat_join_id(gc, id);
+	else
+	{
+		uint64_t *id_p = g_new(uint64_t, 1);
+		*id_p = id;
+		sdata->pending_joins = g_slist_append(sdata->pending_joins, id_p);
+	}
+
+}
+
+static void ggp_chat_join_id(PurpleConnection *gc, uint64_t id)
+{
+	GHashTable *components;
+	ggp_chat_local_info *chat = ggp_chat_get(gc, id);
+
+	if (chat && !chat->left)
+	{
+		ggp_chat_open_conv(chat);
+		return;
+	}
+	
+	if (!chat)
+	{
+		char *id_s = g_strdup_printf("%" G_GUINT64_FORMAT, id);
+		char *buff = g_strdup_printf(
+			_("%s is not a valid room identifier"), id_s);
+		g_free(id_s);
+		purple_notify_error(gc, _("Invalid Room Identifier"),
+			_("Invalid Room Identifier"), buff);
+		g_free(buff);
+	}
+	else /* if (chat->left) */
+	{
+		purple_notify_error(gc, _("Could not join chat room"),
+			_("Could not join chat room"),
+			_("You have to ask for invitation from another chat "
+			"participant"));
+	}
+
+	components = ggp_chat_info_defaults(gc, ggp_chat_get_name_from_id(id));
+	purple_serv_got_join_chat_failed(gc, components);
+	g_hash_table_destroy(components);
+}
+
+void ggp_chat_leave(PurpleConnection *gc, int local_id)
+{
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	ggp_chat_local_info *chat;
+	uin_t me;
+	
+	chat = ggp_chat_get_local(gc, local_id);
+	if (!chat)
+	{
+		purple_debug_error("gg", "ggp_chat_leave: "
+			"chat %u doesn't exists\n", local_id);
+		return;
+	}
+	
+	if (gg_chat_leave(info->session, chat->id) < 0)
+	{
+		purple_debug_error("gg", "ggp_chat_leave: "
+			"unable to leave chat %" G_GUINT64_FORMAT "\n",
+			chat->id);
+	}
+	chat->conv = NULL;
+
+	me = ggp_str_to_uin(purple_account_get_username(
+		purple_connection_get_account(chat->gc)));
+
+	ggp_chat_left(chat, me);
+	chat->left = TRUE;
+}
+
+void ggp_chat_invite(PurpleConnection *gc, int local_id, const char *message,
+	const char *who)
+{
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	ggp_chat_local_info *chat;
+	uin_t invited;
+	
+	chat = ggp_chat_get_local(gc, local_id);
+	if (!chat)
+	{
+		purple_debug_error("gg", "ggp_chat_invite: "
+			"chat %u doesn't exists\n", local_id);
+		return;
+	}
+	
+	invited = ggp_str_to_uin(who);
+	if (gg_chat_invite(info->session, chat->id, &invited, 1) < 0)
+	{
+		purple_debug_error("gg", "ggp_chat_invite: "
+			"unable to invite %s to chat %" G_GUINT64_FORMAT "\n",
+			who, chat->id);
+	}
+}
+
+int ggp_chat_send(PurpleConnection *gc, int local_id, const char *message,
+	PurpleMessageFlags flags)
+{
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	PurpleConversation *conv;
+	ggp_chat_local_info *chat;
+	gboolean succ = TRUE;
+	const gchar *me;
+	gchar *gg_msg;
+	
+	chat = ggp_chat_get_local(gc, local_id);
+	if (!chat)
+	{
+		purple_debug_error("gg", "ggp_chat_send: "
+			"chat %u doesn't exists\n", local_id);
+		return -1;
+	}
+	
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+		ggp_chat_get_name_from_id(chat->id),
+		purple_connection_get_account(gc));
+
+	gg_msg = ggp_message_format_to_gg(conv, message);
+
+	if (gg_chat_send_message(info->session, chat->id, gg_msg, TRUE) < 0)
+		succ = FALSE;
+	g_free(gg_msg);
+
+	me = purple_account_get_username(purple_connection_get_account(gc));
+	serv_got_chat_in(gc, chat->local_id, me, flags, message, time(NULL));
+
+	return succ ? 0 : -1;
+}
+
+void ggp_chat_got_message(PurpleConnection *gc, uint64_t chat_id,
+	const char *message, time_t time, uin_t who)
+{
+	ggp_chat_local_info *chat;
+	uin_t me;
+	
+	me = ggp_str_to_uin(purple_account_get_username(
+		purple_connection_get_account(gc)));
+	
+	chat = ggp_chat_get(gc, chat_id);
+	if (!chat)
+	{
+		purple_debug_error("gg", "ggp_chat_got_message: "
+			"chat %" G_GUINT64_FORMAT " doesn't exists\n", chat_id);
+		return;
+	}
+
+	ggp_chat_open_conv(chat);
+	if (who == me)
+	{
+		purple_conversation_write(chat->conv, ggp_uin_to_str(who),
+			message, PURPLE_MESSAGE_SEND, time);
+	}
+	else
+	{
+		serv_got_chat_in(gc, chat->local_id, ggp_uin_to_str(who),
+			PURPLE_MESSAGE_RECV, message, time);
+	}
+}
+
+static gboolean ggp_chat_roomlist_get_list_finish(gpointer roomlist)
+{
+	purple_roomlist_set_in_progress((PurpleRoomlist*)roomlist, FALSE);
+	return FALSE;
+}
+
+PurpleRoomlist * ggp_chat_roomlist_get_list(PurpleConnection *gc)
+{
+	ggp_chat_session_data *sdata = ggp_chat_get_sdata(gc);
+	PurpleRoomlist *roomlist;
+	GList *fields = NULL;
+	int i;
+
+	purple_debug_info("gg", "ggp_chat_roomlist_get_list\n");
+
+	roomlist = purple_roomlist_new(purple_connection_get_account(gc));
+
+	fields = g_list_append(fields, purple_roomlist_field_new(
+		PURPLE_ROOMLIST_FIELD_STRING, _("Conference identifier"), "id",
+		TRUE));
+
+	fields = g_list_append(fields, purple_roomlist_field_new(
+		PURPLE_ROOMLIST_FIELD_STRING, _("Start Date"), "date",
+		FALSE));
+
+	fields = g_list_append(fields, purple_roomlist_field_new(
+		PURPLE_ROOMLIST_FIELD_INT, _("User Count"), "users",
+		FALSE));
+
+	fields = g_list_append(fields, purple_roomlist_field_new(
+		PURPLE_ROOMLIST_FIELD_STRING, _("Status"), "status",
+		FALSE));
+
+	purple_roomlist_set_fields(roomlist, fields);
+
+	for (i = sdata->chats_count - 1; i >= 0 ; i--)
+	{
+		PurpleRoomlistRoom *room;
+		ggp_chat_local_info *chat = &sdata->chats[i];
+		const gchar *name;
+		time_t date;
+		const gchar *status;
+		int count = chat->participants_count;
+		
+		date = (uint32_t)(chat->id >> 32);
+		
+		if (chat->conv)
+			status = _("Joined");
+		else if (chat->left)
+			status = _("Chat left");
+		else
+		{
+			status = _("Can join chat");
+			count--;
+		}
+		
+		name = ggp_chat_get_name_from_id(chat->id);
+		room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM,
+			name, NULL);
+		purple_roomlist_room_add_field(roomlist, room, name);
+		purple_roomlist_room_add_field(roomlist, room, purple_date_format_full(localtime(&date)));
+		purple_roomlist_room_add_field(roomlist, room, GINT_TO_POINTER(count));
+		purple_roomlist_room_add_field(roomlist, room, status);
+		purple_roomlist_room_add(roomlist, room);
+	}
+
+	//TODO
+	//purple_roomlist_set_in_progress(roomlist, FALSE);
+	purple_timeout_add(1, ggp_chat_roomlist_get_list_finish, roomlist);
+	return roomlist;
+}
+
+#else
+void ggp_chat_setup(PurpleConnection *gc)
+{
+}
+
+void ggp_chat_cleanup(PurpleConnection *gc)
+{
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/chat.h	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,34 @@
+#ifndef _GGP_CHAT_H
+#define _GGP_CHAT_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct _ggp_chat_session_data ggp_chat_session_data;
+
+#include "gg.h"
+
+void ggp_chat_setup(PurpleConnection *gc);
+void ggp_chat_cleanup(PurpleConnection *gc);
+
+#if GGP_ENABLE_GG11
+void ggp_chat_got_event(PurpleConnection *gc, const struct gg_event *ev);
+
+GList * ggp_chat_info(PurpleConnection *gc);
+GHashTable * ggp_chat_info_defaults(PurpleConnection *gc,
+	const char *chat_name);
+char * ggp_chat_get_name(GHashTable *components);
+void ggp_chat_join(PurpleConnection *gc, GHashTable *components);
+void ggp_chat_leave(PurpleConnection *gc, int local_id);
+void ggp_chat_invite(PurpleConnection *gc, int local_id, const char *message,
+	const char *who);
+int ggp_chat_send(PurpleConnection *gc, int local_id, const char *message,
+	PurpleMessageFlags flags);
+
+void ggp_chat_got_message(PurpleConnection *gc, uint64_t chat_id,
+	const char *message, time_t time, uin_t who);
+
+PurpleRoomlist * ggp_chat_roomlist_get_list(PurpleConnection *gc);
+#endif
+
+#endif /* _GGP_CHAT_H */
--- a/libpurple/protocols/gg/confer.c	Mon Sep 16 16:48:10 2013 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/**
- * @file confer.c
- *
- * purple
- *
- * Copyright (C) 2005  Bartosz Oler <bartosz@bzimage.us>
- *
- * 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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-
-#include <libgadu.h>
-#include "gg.h"
-#include "utils.h"
-#include "confer.h"
-
-/* PurpleConversation *ggp_confer_find_by_name(PurpleConnection *gc, const gchar *name) {{{ */
-PurpleChatConversation *ggp_confer_find_by_name(PurpleConnection *gc, const gchar *name)
-{
-	g_return_val_if_fail(gc   != NULL, NULL);
-	g_return_val_if_fail(name != NULL, NULL);
-
-	return purple_conversations_find_chat_with_account(name,
-			purple_connection_get_account(gc));
-}
-/* }}} */
-
-/* void ggp_confer_participants_add_uin(PurpleConnection *gc, const gchar *chat_name, const uin_t uin) {{{ */
-void ggp_confer_participants_add_uin(PurpleConnection *gc, const gchar *chat_name,
-							 const uin_t uin)
-{
-	PurpleChatConversation *conv;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPChat *chat;
-	GList *l;
-	gchar *str_uin;
-
-	for (l = info->chats; l != NULL; l = l->next) {
-		chat = l->data;
-
-		if (g_utf8_collate(chat->name, chat_name) != 0)
-			continue;
-
-		if (g_list_find(chat->participants, GINT_TO_POINTER(uin)) == NULL) {
-			chat->participants = g_list_append(
-						chat->participants, GINT_TO_POINTER(uin));
-
-			str_uin = g_strdup_printf("%lu", (unsigned long int)uin);
-			conv = ggp_confer_find_by_name(gc, chat_name);
-			purple_chat_conversation_add_user(conv, str_uin, NULL,
-						PURPLE_CHAT_USER_NONE, TRUE);
-
-			g_free(str_uin);
-		}
-		break;
-	}
-}
-/* }}} */
-
-/* void ggp_confer_participants_add(PurpleConnection *gc, const gchar *chat_name, const uin_t *recipients, int count) {{{ */
-void ggp_confer_participants_add(PurpleConnection *gc, const gchar *chat_name,
-				 const uin_t *recipients, int count)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GList *l;
-	gchar *str_uin;
-
-	for (l = info->chats; l != NULL; l = l->next) {
-		GGPChat *chat = l->data;
-		int i;
-
-		if (g_utf8_collate(chat->name, chat_name) != 0)
-			continue;
-
-		for (i = 0; i < count; i++) {
-			PurpleChatConversation *conv;
-
-			if (g_list_find(chat->participants,
-					GINT_TO_POINTER(recipients[i])) != NULL) {
-				continue;
-			}
-
-			chat->participants = g_list_append(chat->participants,
-							   GINT_TO_POINTER(recipients[i]));
-
-			str_uin = g_strdup_printf("%lu", (unsigned long int)recipients[i]);
-			conv = ggp_confer_find_by_name(gc, chat_name);
-			purple_chat_conversation_add_user(conv, str_uin, NULL,
-						PURPLE_CHAT_USER_NONE, TRUE);
-			g_free(str_uin);
-		}
-		break;
-	}
-}
-/* }}} */
-
-/* const char *ggp_confer_find_by_participants(PurpleConnection *gc, const uin_t *recipients, int count) {{{ */
-const char *ggp_confer_find_by_participants(PurpleConnection *gc,
-					    const uin_t *recipients, int count)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPChat *chat = NULL;
-	GList *l;
-	int matches;
-
-	g_return_val_if_fail(info->chats != NULL, NULL);
-
-	for (l = info->chats; l != NULL; l = l->next) {
-		GList *m;
-
-		chat = l->data;
-		matches = 0;
-
-		for (m = chat->participants; m != NULL; m = m->next) {
-			uin_t uin = GPOINTER_TO_INT(m->data);
-			int i;
-
-			for (i = 0; i < count; i++)
-				if (uin == recipients[i])
-					matches++;
-		}
-
-		if (matches == count)
-			break;
-
-		chat = NULL;
-	}
-
-	if (chat == NULL)
-		return NULL;
-	else
-		return chat->name;
-}
-/* }}} */
-
-/* const char *ggp_confer_add_new(PurpleConnection *gc, const char *name) {{{ */
-const char *ggp_confer_add_new(PurpleConnection *gc, const char *name)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPChat *chat;
-
-	chat = g_new0(GGPChat, 1);
-
-	if (name == NULL)
-		chat->name = g_strdup_printf("conf#%d", info->chats_count++);
-	else
-		chat->name = g_strdup(name);
-
-	chat->participants = NULL;
-
-	info->chats = g_list_append(info->chats, chat);
-
-	return chat->name;
-}
-/* }}} */
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/confer.h	Mon Sep 16 16:48:10 2013 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/**
- * @file confer.h
- *
- * purple
- *
- * Copyright (C) 2005  Bartosz Oler <bartosz@bzimage.us>
- *
- * 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 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-
-#ifndef _PURPLE_GG_CONFER_H
-#define _PURPLE_GG_CONFER_H
-
-#include "gg.h"
-
-/**
- * Finds a CHAT conversation for the current account with the specified name.
- *
- * @param gc   PurpleConnection instance.
- * @param name Name of the conversation.
- *
- * @return PurpleConversation or NULL if not found.
- */
-PurpleChatConversation *
-ggp_confer_find_by_name(PurpleConnection *gc, const gchar *name);
-
-/**
- * Adds the specified UIN to the specified conversation.
- *
- * @param gc        PurpleConnection.
- * @param chat_name Name of the conversation.
- */
-void
-ggp_confer_participants_add_uin(PurpleConnection *gc, const gchar *chat_name,
-						    const uin_t uin);
-
-/**
- * Add the specified UINs to the specified conversation.
- *
- * @param gc         PurpleConnection.
- * @param chat_name  Name of the conversation.
- * @param recipients List of the UINs.
- * @param count      Number of the UINs.
- */
-void
-ggp_confer_participants_add(PurpleConnection *gc, const gchar *chat_name,
-			    const uin_t *recipients, int count);
-
-/**
- * Finds a conversation in which all the specified recipients participate.
- *
- * TODO: This function should be rewritten to better handle situations when
- * somebody adds more people to the converation.
- *
- * @param gc         PurpleConnection.
- * @param recipients List of the people in the conversation.
- * @param count      Number of people.
- *
- * @return Name of the conversation.
- */
-const char*
-ggp_confer_find_by_participants(PurpleConnection *gc, const uin_t *recipients,
-						    int count);
-
-/**
- * Adds a new conversation to the internal list of conversations.
- * If name is NULL then it will be automagically generated.
- *
- * @param gc   PurpleConnection.
- * @param name Name of the conversation.
- *
- * @return Name of the conversation.
- */
-const char*
-ggp_confer_add_new(PurpleConnection *gc, const char *name);
-
-
-#endif /* _PURPLE_GG_CONFER_H */
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/edisc.c	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,1175 @@
+#include "edisc.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "libgaduw.h"
+#include "utils.h"
+#include <http.h>
+
+#include <json-glib/json-glib.h>
+
+#define GGP_EDISC_OS "WINNT x86-msvc"
+#define GGP_EDISC_TYPE "desktop"
+#define GGP_EDISC_API "6"
+
+#define GGP_EDISC_RESPONSE_MAX 10240
+#define GGP_EDISC_FNAME_ALLOWED "1234567890" \
+	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+	" [](){}-+=_;'<>,.&$!"
+
+typedef struct _ggp_edisc_auth_data ggp_edisc_auth_data;
+
+typedef struct _ggp_edisc_xfer ggp_edisc_xfer;
+
+struct _ggp_edisc_session_data
+{
+	GHashTable *xfers_initialized;
+	GHashTable *xfers_history;
+
+	PurpleHttpCookieJar *cookies;
+	gchar *security_token;
+
+	PurpleHttpConnection *auth_request;
+	gboolean auth_done;
+	GList *auth_pending;
+};
+
+struct _ggp_edisc_xfer
+{
+	gchar *filename;
+	gchar *ticket_id;
+
+	gboolean allowed, ready;
+
+	PurpleConnection *gc;
+	PurpleHttpConnection *hc;
+
+	gsize already_read;
+};
+
+typedef enum
+{
+	GGP_EDISC_XFER_ACK_STATUS_UNKNOWN,
+	GGP_EDISC_XFER_ACK_STATUS_ALLOWED,
+	GGP_EDISC_XFER_ACK_STATUS_REJECTED
+} ggp_edisc_xfer_ack_status;
+
+typedef void (*ggp_ggdrive_auth_cb)(PurpleConnection *gc, gboolean success,
+	gpointer user_data);
+
+/* Setting up. */
+static inline ggp_edisc_session_data *
+ggp_edisc_get_sdata(PurpleConnection *gc);
+
+/* Misc. */
+static void ggp_edisc_set_defaults(PurpleHttpRequest *req);
+static int ggp_edisc_parse_error(const gchar *data);
+
+/* General xfer functions. */
+static void ggp_edisc_xfer_free(PurpleXfer *xfer);
+static void ggp_edisc_xfer_error(PurpleXfer *xfer, const gchar *msg);
+static void ggp_edisc_xfer_cancel(PurpleXfer *xfer);
+static const gchar * ggp_edisc_xfer_ticket_url(const gchar *ticket_id);
+static void ggp_edisc_xfer_progress_watcher(PurpleHttpConnection *hc,
+	gboolean reading_state, int processed, int total, gpointer _xfer);
+static ggp_edisc_xfer_ack_status
+ggp_edisc_xfer_parse_ack_status(const gchar *str);
+
+
+/* Sending a file. */
+static void ggp_edisc_xfer_send_ticket_changed(PurpleConnection *gc,
+	PurpleXfer *xfer, gboolean is_allowed);
+
+/* Receiving a file. */
+static void ggp_edisc_xfer_recv_reject(PurpleXfer *xfer);
+static void ggp_edisc_xfer_recv_ticket_got(PurpleConnection *gc,
+	const gchar *ticket_id);
+static void ggp_edisc_xfer_recv_ticket_completed(PurpleXfer *xfer);
+
+/* Authentication. */
+static void ggp_ggdrive_auth(PurpleConnection *gc, ggp_ggdrive_auth_cb cb,
+	gpointer user_data);
+
+/*******************************************************************************
+ * Setting up.
+ ******************************************************************************/
+
+static inline ggp_edisc_session_data *
+ggp_edisc_get_sdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata;
+
+	g_return_val_if_fail(PURPLE_CONNECTION_IS_VALID(gc), NULL);
+
+	accdata = purple_connection_get_protocol_data(gc);
+	g_return_val_if_fail(accdata != NULL, NULL);
+
+	return accdata->edisc_data;
+}
+
+void ggp_edisc_setup(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_edisc_session_data *sdata = g_new0(ggp_edisc_session_data, 1);
+
+	accdata->edisc_data = sdata;
+
+	sdata->cookies = purple_http_cookie_jar_new();
+	sdata->xfers_initialized = g_hash_table_new(g_str_hash, g_str_equal);
+	sdata->xfers_history = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+}
+
+void ggp_edisc_cleanup(PurpleConnection *gc)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+
+	purple_http_conn_cancel(sdata->auth_request);
+	g_list_free_full(sdata->auth_pending, g_free);
+	g_free(sdata->security_token);
+
+	purple_http_cookie_jar_unref(sdata->cookies);
+	g_hash_table_destroy(sdata->xfers_initialized);
+	g_hash_table_destroy(sdata->xfers_history);
+
+	g_free(sdata);
+}
+
+/*******************************************************************************
+ * Misc.
+ ******************************************************************************/
+
+static void ggp_edisc_set_defaults(PurpleHttpRequest *req)
+{
+	purple_http_request_set_max_len(req, GGP_EDISC_RESPONSE_MAX);
+	purple_http_request_header_set(req, "X-gged-api-version",
+		GGP_EDISC_API);
+
+	/* optional fields */
+	purple_http_request_header_set(req, "User-Agent", "Mozilla/5.0 (Windows"
+		" NT 6.1; rv:11.0) Gecko/20120613 GG/11.0.0.8169 (WINNT_x86-msv"
+		"c; pl; beta; standard)");
+	purple_http_request_header_set(req, "Accept", "text/html,application/xh"
+		"tml+xml,application/xml;q=0.9,*/*;q=0.8");
+	purple_http_request_header_set(req, "Accept-Language",
+		"pl,en-us;q=0.7,en;q=0.3");
+	/* purple_http_request_header_set(req, "Accept-Encoding",
+	 * "gzip, deflate"); */
+	purple_http_request_header_set(req, "Accept-Charset",
+		"ISO-8859-2,utf-8;q=0.7,*;q=0.7");
+	purple_http_request_header_set(req, "Connection", "keep-alive");
+	purple_http_request_header_set(req, "Content-Type",
+		"application/x-www-form-urlencoded; charset=UTF-8");
+}
+
+static int ggp_edisc_parse_error(const gchar *data)
+{
+	JsonParser *parser;
+	JsonObject *result;
+	int error_id;
+
+	parser = ggp_json_parse(data);
+	result = json_node_get_object(json_parser_get_root(parser));
+	result = json_object_get_object_member(result, "result");
+	error_id = json_object_get_int_member(result, "appStatus");
+	purple_debug_info("gg", "edisc error: %s (%d)\n",
+		json_object_get_string_member(result, "errorMsg"),
+		error_id);
+	g_object_unref(parser);
+
+	return error_id;
+}
+
+/*******************************************************************************
+ * General xfer functions.
+ ******************************************************************************/
+
+static void ggp_edisc_xfer_free(PurpleXfer *xfer)
+{
+	ggp_edisc_session_data *sdata;
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+
+	if (edisc_xfer == NULL)
+		return;
+
+	g_free(edisc_xfer->filename);
+	purple_http_conn_cancel(edisc_xfer->hc);
+
+	sdata = ggp_edisc_get_sdata(edisc_xfer->gc);
+	if (edisc_xfer->ticket_id != NULL)
+		g_hash_table_remove(sdata->xfers_initialized,
+			edisc_xfer->ticket_id);
+
+	g_free(edisc_xfer);
+	purple_xfer_set_protocol_data(xfer, NULL);
+
+}
+
+static void ggp_edisc_xfer_error(PurpleXfer *xfer, const gchar *msg)
+{
+	if (purple_xfer_is_cancelled(xfer))
+		g_return_if_reached();
+	purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
+	purple_xfer_conversation_write(xfer, msg, TRUE);
+	purple_xfer_error(
+		purple_xfer_get_xfer_type(xfer),
+		purple_xfer_get_account(xfer),
+		purple_xfer_get_remote_user(xfer),
+		msg);
+	ggp_edisc_xfer_free(xfer);
+	purple_xfer_end(xfer);
+}
+
+void ggp_edisc_xfer_ticket_changed(PurpleConnection *gc, const char *data)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	PurpleXfer *xfer;
+	JsonParser *parser;
+	JsonObject *ticket;
+	const gchar *ticket_id, *send_status;
+	ggp_edisc_xfer_ack_status ack_status;
+	gboolean is_completed;
+
+	parser = ggp_json_parse(data);
+	ticket = json_node_get_object(json_parser_get_root(parser));
+	ticket_id = json_object_get_string_member(ticket, "id");
+	ack_status = ggp_edisc_xfer_parse_ack_status(
+		json_object_get_string_member(ticket, "ack_status"));
+	send_status = json_object_get_string_member(ticket, "send_status");
+
+	if (ticket_id == NULL)
+		ticket_id = "";
+	xfer = g_hash_table_lookup(sdata->xfers_initialized, ticket_id);
+	if (xfer == NULL) {
+		purple_debug_misc("gg", "ggp_edisc_event_ticket_changed: "
+			"ticket %s not found, updating it...\n",
+			purple_debug_is_unsafe() ? ticket_id : "");
+		ggp_edisc_xfer_recv_ticket_got(gc, ticket_id);
+		g_object_unref(parser);
+		return;
+	}
+
+	is_completed = FALSE;
+	if (g_strcmp0("in_progress", send_status) == 0) {
+		/* do nothing */
+	} else if (g_strcmp0("completed", send_status) == 0) {
+		is_completed = TRUE;
+	} else if (g_strcmp0("expired", send_status) == 0)
+		ggp_edisc_xfer_error(xfer, _("File transfer expired."));
+	else {
+		purple_debug_warning("gg", "ggp_edisc_event_ticket_changed: "
+			"unknown send_status=%s\n", send_status);
+		g_object_unref(parser);
+		return;
+	}
+
+	g_object_unref(parser);
+
+	if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
+		if (is_completed)
+			ggp_edisc_xfer_recv_ticket_completed(xfer);
+	} else {
+		if (ack_status != GGP_EDISC_XFER_ACK_STATUS_UNKNOWN)
+			ggp_edisc_xfer_send_ticket_changed(gc, xfer, ack_status
+				== GGP_EDISC_XFER_ACK_STATUS_ALLOWED);
+	}
+
+}
+
+static void ggp_edisc_xfer_cancel(PurpleXfer *xfer)
+{
+	g_return_if_fail(xfer != NULL);
+
+	ggp_edisc_xfer_free(xfer);
+}
+
+static const gchar * ggp_edisc_xfer_ticket_url(const gchar *ticket_id)
+{
+	static gchar ticket_url[150];
+
+	g_snprintf(ticket_url, sizeof(ticket_url),
+		"https://drive.mpa.gg.pl/send_ticket/%s", ticket_id);
+
+	return ticket_url;
+}
+
+static void ggp_edisc_xfer_progress_watcher(PurpleHttpConnection *hc,
+	gboolean reading_state, int processed, int total, gpointer _xfer)
+{
+	PurpleXfer *xfer = _xfer;
+	gboolean eof;
+	int total_real;
+
+	if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
+		if (!reading_state)
+			return;
+	} else {
+		if (reading_state)
+			return;
+	}
+
+	eof = (processed >= total);
+	total_real = purple_xfer_get_size(xfer);
+	if (eof || processed > total_real)
+		processed = total_real; /* just to be sure */
+
+	purple_xfer_set_bytes_sent(xfer, processed);
+	purple_xfer_update_progress(xfer);
+}
+
+static ggp_edisc_xfer_ack_status
+ggp_edisc_xfer_parse_ack_status(const gchar *str)
+{
+	g_return_val_if_fail(str != NULL, GGP_EDISC_XFER_ACK_STATUS_UNKNOWN);
+
+	if (g_strcmp0("unknown", str) == 0)
+		return GGP_EDISC_XFER_ACK_STATUS_UNKNOWN;
+	if (g_strcmp0("allowed", str) == 0)
+		return GGP_EDISC_XFER_ACK_STATUS_ALLOWED;
+	if (g_strcmp0("rejected", str) == 0)
+		return GGP_EDISC_XFER_ACK_STATUS_REJECTED;
+
+	purple_debug_warning("gg", "ggp_edisc_xfer_parse_ack_status: "
+		"unknown status (%s)\n", str);
+	return GGP_EDISC_XFER_ACK_STATUS_UNKNOWN;
+}
+
+/*******************************************************************************
+ * Sending a file.
+ ******************************************************************************/
+
+static void ggp_edisc_xfer_send_init(PurpleXfer *xfer);
+static void ggp_edisc_xfer_send_init_authenticated(PurpleConnection *gc,
+	gboolean success, gpointer _xfer);
+static void ggp_edisc_xfer_send_init_ticket_created(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer);
+static void ggp_edisc_xfer_send_reader(PurpleHttpConnection *hc,
+	gchar *buffer, size_t offset, size_t length, gpointer _xfer,
+	PurpleHttpContentReaderCb cb);
+static void ggp_edisc_xfer_send_start(PurpleXfer *xfer);
+static void ggp_edisc_xfer_send_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer);
+
+gboolean ggp_edisc_xfer_can_receive_file(PurpleConnection *gc,
+	const char *who)
+{
+	PurpleBuddy *buddy;
+
+	g_return_val_if_fail(gc != NULL, FALSE);
+	g_return_val_if_fail(who != NULL, FALSE);
+
+	buddy = purple_blist_find_buddy(purple_connection_get_account(gc), who);
+	if (buddy == NULL)
+		return FALSE;
+
+	/* TODO: check, if this buddy have us on his list */
+
+	return PURPLE_BUDDY_IS_ONLINE(buddy);
+}
+
+static void ggp_edisc_xfer_send_init(PurpleXfer *xfer)
+{
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+
+	purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_NOT_STARTED);
+
+	edisc_xfer->filename = g_strdup(purple_xfer_get_filename(xfer));
+	g_strcanon(edisc_xfer->filename, GGP_EDISC_FNAME_ALLOWED, '_');
+
+	ggp_ggdrive_auth(edisc_xfer->gc, ggp_edisc_xfer_send_init_authenticated,
+		xfer);
+}
+
+static void ggp_edisc_xfer_send_init_authenticated(PurpleConnection *gc,
+	gboolean success, gpointer _xfer)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	PurpleHttpRequest *req;
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	gchar *data;
+
+	if (purple_xfer_is_cancelled(xfer))
+		return;
+
+	if (!success) {
+		ggp_edisc_xfer_error(xfer, _("Authentication failed"));
+		return;
+	}
+
+	req = purple_http_request_new("https://drive.mpa.gg.pl/send_ticket");
+	purple_http_request_set_method(req, "PUT");
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+
+	purple_http_request_header_set(req, "X-gged-security-token",
+		sdata->security_token);
+
+	data = g_strdup_printf("{\"send_ticket\":{"
+		"\"recipient\":\"%s\","
+		"\"file_name\":\"%s\","
+		"\"file_size\":\"%u\""
+		"}}",
+		purple_xfer_get_remote_user(xfer),
+		edisc_xfer->filename,
+		(int)purple_xfer_get_size(xfer));
+	purple_http_request_set_contents(req, data, -1);
+	g_free(data);
+
+	edisc_xfer->hc = purple_http_request(gc, req,
+		ggp_edisc_xfer_send_init_ticket_created, xfer);
+	purple_http_request_unref(req);
+}
+
+static void ggp_edisc_xfer_send_init_ticket_created(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(
+		purple_http_conn_get_purple_connection(hc));
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	const gchar *data = purple_http_response_get_data(response, NULL);
+	ggp_edisc_xfer_ack_status ack_status;
+	JsonParser *parser;
+	JsonObject *ticket;
+
+	if (purple_xfer_is_cancelled(xfer))
+		return;
+
+	edisc_xfer->hc = NULL;
+
+	if (!purple_http_response_is_successful(response)) {
+		int error_id = ggp_edisc_parse_error(data);
+		if (error_id == 206) /* recipient not logged in */
+			ggp_edisc_xfer_error(xfer,
+				_("Recipient not logged in"));
+		else if (error_id == 207) /* bad sender recipient relation */
+			ggp_edisc_xfer_error(xfer, _("Recipient didn't added "
+				"you to his buddy list"));
+		else
+			ggp_edisc_xfer_error(xfer,
+				_("Cannot offer sending a file"));
+		return;
+	}
+
+	parser = ggp_json_parse(data);
+	ticket = json_node_get_object(json_parser_get_root(parser));
+	ticket = json_object_get_object_member(ticket, "result");
+	ticket = json_object_get_object_member(ticket, "send_ticket");
+	edisc_xfer->ticket_id = g_strdup(json_object_get_string_member(
+		ticket, "id"));
+	ack_status = ggp_edisc_xfer_parse_ack_status(
+		json_object_get_string_member(ticket, "ack_status"));
+	/* send_mode: "normal", "publink" (for legacy clients) */
+
+	g_object_unref(parser);
+
+	if (edisc_xfer->ticket_id == NULL) {
+		purple_debug_error("gg",
+			"ggp_edisc_xfer_send_init_ticket_created: "
+			"couldn't get ticket id\n");
+		return;
+	}
+
+	purple_debug_info("gg", "ggp_edisc_xfer_send_init_ticket_created: "
+		"ticket \"%s\" created\n", edisc_xfer->ticket_id);
+
+	g_hash_table_insert(sdata->xfers_initialized,
+		edisc_xfer->ticket_id, xfer);
+	g_hash_table_insert(sdata->xfers_history,
+		g_strdup(edisc_xfer->ticket_id), GINT_TO_POINTER(1));
+
+	if (ack_status != GGP_EDISC_XFER_ACK_STATUS_UNKNOWN)
+		ggp_edisc_xfer_send_ticket_changed(edisc_xfer->gc, xfer,
+			ack_status == GGP_EDISC_XFER_ACK_STATUS_ALLOWED);
+}
+
+void ggp_edisc_xfer_send_ticket_changed(PurpleConnection *gc, PurpleXfer *xfer,
+	gboolean is_allowed)
+{
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	if (!edisc_xfer) {
+		purple_debug_fatal("gg", "ggp_edisc_event_ticket_changed: "
+			"transfer %p already free'd\n", xfer);
+		return;
+	}
+
+	if (!is_allowed) {
+		purple_debug_info("gg", "ggp_edisc_event_ticket_changed: "
+			"transfer %p rejected\n", xfer);
+		purple_xfer_cancel_remote(xfer);
+		ggp_edisc_xfer_free(xfer);
+		return;
+	}
+
+	if (edisc_xfer->allowed) {
+		purple_debug_misc("gg", "ggp_edisc_event_ticket_changed: "
+			"transfer %p already allowed\n", xfer);
+		return;
+	}
+	edisc_xfer->allowed = TRUE;
+
+	purple_xfer_start(xfer, -1, NULL, 0);
+}
+
+static void ggp_edisc_xfer_send_reader(PurpleHttpConnection *hc,
+	gchar *buffer, size_t offset, size_t length, gpointer _xfer,
+	PurpleHttpContentReaderCb cb)
+{
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer;
+	int stored;
+	gboolean success, eof = FALSE;
+
+	g_return_if_fail(xfer != NULL);
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	g_return_if_fail(edisc_xfer != NULL);
+
+	if (edisc_xfer->already_read != offset) {
+		purple_debug_error("gg", "ggp_edisc_xfer_send_reader: "
+			"Invalid offset (%d != %" G_GSIZE_FORMAT ")\n",
+			edisc_xfer->already_read, offset);
+		ggp_edisc_xfer_error(xfer, _("Error while reading a file"));
+		return;
+	}
+
+	stored = purple_xfer_read_file(xfer, (guchar *)buffer, length);
+
+	if (stored < 0)
+		success = FALSE;
+	else {
+		success = TRUE;
+		edisc_xfer->already_read += stored;
+		eof = (edisc_xfer->already_read >= purple_xfer_get_size(xfer));
+	}
+
+	cb(hc, success, eof, stored);
+}
+
+static void ggp_edisc_xfer_send_start(PurpleXfer *xfer)
+{
+	ggp_edisc_session_data *sdata;
+	ggp_edisc_xfer *edisc_xfer;
+	gchar *upload_url, *filename_e;
+	PurpleHttpRequest *req;
+
+	g_return_if_fail(xfer != NULL);
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	g_return_if_fail(edisc_xfer != NULL);
+	sdata = ggp_edisc_get_sdata(edisc_xfer->gc);
+
+	filename_e = purple_strreplace(edisc_xfer->filename, " ", "%20");
+	upload_url = g_strdup_printf("https://drive.mpa.gg.pl/me/file/outbox/"
+		"%s%%2C%s", edisc_xfer->ticket_id, filename_e);
+	g_free(filename_e);
+	req = purple_http_request_new(upload_url);
+	g_free(upload_url);
+
+	purple_http_request_set_method(req, "PUT");
+	purple_http_request_set_timeout(req, -1);
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+
+	purple_http_request_header_set(req, "X-gged-local-revision", "0");
+	purple_http_request_header_set(req, "X-gged-security-token",
+		sdata->security_token);
+	purple_http_request_header_set(req, "X-gged-metadata",
+		"{\"node_type\": \"file\"}");
+
+	purple_http_request_set_contents_reader(req, ggp_edisc_xfer_send_reader,
+		purple_xfer_get_size(xfer), xfer);
+
+	edisc_xfer->hc = purple_http_request(edisc_xfer->gc, req,
+		ggp_edisc_xfer_send_done, xfer);
+	purple_http_request_unref(req);
+
+	purple_http_conn_set_progress_watcher(edisc_xfer->hc,
+		ggp_edisc_xfer_progress_watcher, xfer, 250000);
+}
+
+static void ggp_edisc_xfer_send_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer)
+{
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	const gchar *data = purple_http_response_get_data(response, NULL);
+	JsonParser *parser;
+	JsonObject *result;
+	int result_status = -1;
+
+	if (purple_xfer_is_cancelled(xfer))
+		return;
+
+	g_return_if_fail(edisc_xfer != NULL);
+
+	edisc_xfer->hc = NULL;
+
+	if (!purple_http_response_is_successful(response)) {
+		ggp_edisc_xfer_error(xfer, _("Error while sending a file"));
+		return;
+	}
+
+	parser = ggp_json_parse(data);
+	result = json_node_get_object(json_parser_get_root(parser));
+	result = json_object_get_object_member(result, "result");
+	if (json_object_has_member(result, "status"))
+		result_status = json_object_get_int_member(result, "status");
+	g_object_unref(parser);
+
+	if (result_status == 0) {
+		purple_xfer_set_completed(xfer, TRUE);
+		purple_xfer_end(xfer);
+		ggp_edisc_xfer_free(xfer);
+	} else
+		ggp_edisc_xfer_error(xfer, _("Error while sending a file"));
+}
+
+PurpleXfer * ggp_edisc_xfer_send_new(PurpleConnection *gc, const char *who)
+{
+	PurpleXfer *xfer;
+	ggp_edisc_xfer *edisc_xfer;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+	g_return_val_if_fail(who != NULL, NULL);
+
+	xfer = purple_xfer_new(purple_connection_get_account(gc),
+		PURPLE_XFER_TYPE_SEND, who);
+	edisc_xfer = g_new0(ggp_edisc_xfer, 1);
+	purple_xfer_set_protocol_data(xfer, edisc_xfer);
+
+	edisc_xfer->gc = gc;
+
+	purple_xfer_set_init_fnc(xfer, ggp_edisc_xfer_send_init);
+	purple_xfer_set_start_fnc(xfer, ggp_edisc_xfer_send_start);
+	purple_xfer_set_cancel_send_fnc(xfer, ggp_edisc_xfer_cancel);
+
+	return xfer;
+}
+
+void ggp_edisc_xfer_send_file(PurpleConnection *gc, const char *who,
+	const char *filename)
+{
+	PurpleXfer *xfer;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(who != NULL);
+
+	/* Nothing interesting here, this code is common among prpls.
+	 * See ggp_edisc_xfer_send_new. */
+
+	xfer = ggp_edisc_xfer_send_new(gc, who);
+	if (filename)
+		purple_xfer_request_accepted(xfer, filename);
+	else
+		purple_xfer_request(xfer);
+}
+
+/*******************************************************************************
+ * Receiving a file.
+ ******************************************************************************/
+
+static void ggp_edisc_xfer_recv_ticket_update_authenticated(
+	PurpleConnection *gc, gboolean success, gpointer _ticket);
+static void ggp_edisc_xfer_recv_ticket_update_got(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer user_data);
+static PurpleXfer * ggp_edisc_xfer_recv_new(PurpleConnection *gc,
+	const char *who);
+static void ggp_edisc_xfer_recv_accept(PurpleXfer *xfer);
+static void ggp_edisc_xfer_recv_start(PurpleXfer *xfer);
+static void ggp_edisc_xfer_recv_ack(PurpleXfer *xfer, gboolean accept);
+static void ggp_edisc_xfer_recv_ack_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer);
+static gboolean ggp_edisc_xfer_recv_writer(PurpleHttpConnection *http_conn,
+	PurpleHttpResponse *response, const gchar *buffer, size_t offset,
+	size_t length, gpointer user_data);
+static void ggp_edisc_xfer_recv_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer);
+
+static void ggp_edisc_xfer_recv_ticket_got(PurpleConnection *gc,
+	const gchar *ticket_id)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+
+	if (g_hash_table_lookup(sdata->xfers_history, ticket_id))
+		return;
+
+	ggp_ggdrive_auth(gc, ggp_edisc_xfer_recv_ticket_update_authenticated,
+		g_strdup(ticket_id));
+}
+
+static void ggp_edisc_xfer_recv_ticket_update_authenticated(
+	PurpleConnection *gc, gboolean success, gpointer _ticket)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	PurpleHttpRequest *req;
+	gchar *ticket = _ticket;
+
+	if (!success) {
+		purple_debug_warning("gg",
+			"ggp_edisc_xfer_recv_ticket_update_authenticated: "
+			"update of ticket %s aborted due to authentication "
+			"failure\n", ticket);
+		g_free(ticket);
+		return;
+	}
+
+	req = purple_http_request_new(ggp_edisc_xfer_ticket_url(ticket));
+	g_free(ticket);
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+
+	purple_http_request_header_set(req, "X-gged-security-token",
+		sdata->security_token);
+
+	purple_http_request(gc, req, ggp_edisc_xfer_recv_ticket_update_got,
+		NULL);
+	purple_http_request_unref(req);
+}
+
+static void ggp_edisc_xfer_recv_ticket_update_got(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer user_data)
+{
+	PurpleConnection *gc = purple_http_conn_get_purple_connection(hc);
+	PurpleXfer *xfer;
+	ggp_edisc_xfer *edisc_xfer;
+	JsonParser *parser;
+	JsonObject *result;
+	int status = -1;
+	ggp_edisc_session_data *sdata;
+
+	const gchar *ticket_id, *file_name, *send_mode_str;
+	uin_t sender, recipient;
+	int file_size;
+
+	if (!purple_http_response_is_successful(response)) {
+		purple_debug_error("gg",
+			"ggp_edisc_xfer_recv_ticket_update_got: "
+			"cannot fetch update for ticket (code=%d)\n",
+			purple_http_response_get_code(response));
+		return;
+	}
+
+	sdata = ggp_edisc_get_sdata(gc);
+
+	parser = ggp_json_parse(purple_http_response_get_data(response, NULL));
+	result = json_node_get_object(json_parser_get_root(parser));
+	result = json_object_get_object_member(result, "result");
+	if (json_object_has_member(result, "status"))
+		status = json_object_get_int_member(result, "status");
+	result = json_object_get_object_member(result, "send_ticket");
+
+	if (status != 0) {
+		purple_debug_warning("gg",
+			"ggp_edisc_xfer_recv_ticket_update_got: failed to get "
+			"update (status=%d)\n", status);
+		g_object_unref(parser);
+		return;
+	}
+
+	ticket_id = json_object_get_string_member(result, "id");
+	sender = ggp_str_to_uin(json_object_get_string_member(result,
+		"sender"));
+	recipient = ggp_str_to_uin(json_object_get_string_member(result,
+		"recipient"));
+	file_size = g_ascii_strtoll(json_object_get_string_member(result,
+		"file_size"), NULL, 10);
+	file_name = json_object_get_string_member(result, "file_name");
+
+	/* GG11: normal
+	 * AQQ 2.4.2.10: direct_inbox
+	 */
+	send_mode_str = json_object_get_string_member(result, "send_mode");
+
+	/* more fields:
+	 * send_progress (float), ack_status, send_status
+	 */
+
+	if (purple_debug_is_verbose() && purple_debug_is_unsafe())
+		purple_debug_info("gg", "Got ticket update: id=%s, sender=%u, "
+			"recipient=%u, file name=\"%s\", file size=%d, "
+			"send mode=%s)\n",
+			ticket_id,
+			sender, recipient,
+			file_name, file_size,
+			send_mode_str);
+
+	xfer = g_hash_table_lookup(sdata->xfers_initialized, ticket_id);
+	if (xfer != NULL) {
+		purple_debug_misc("gg", "ggp_edisc_xfer_recv_ticket_update_got:"
+			" ticket %s already updated\n",
+			purple_debug_is_unsafe() ? ticket_id : "");
+		g_object_unref(parser);
+		return;
+	}
+
+	if (recipient != ggp_get_my_uin(gc)) {
+		purple_debug_misc("gg", "ggp_edisc_xfer_recv_ticket_update_got:"
+			" ticket %s is not for incoming transfer "
+			"(its from %u to %u)\n",
+			purple_debug_is_unsafe() ? ticket_id : "",
+			sender, recipient);
+		g_object_unref(parser);
+		return;
+	}
+
+	xfer = ggp_edisc_xfer_recv_new(gc, ggp_uin_to_str(sender));
+	purple_xfer_set_filename(xfer, file_name);
+	purple_xfer_set_size(xfer, file_size);
+	purple_xfer_request(xfer);
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	edisc_xfer->ticket_id = g_strdup(ticket_id);
+	g_hash_table_insert(sdata->xfers_initialized,
+		edisc_xfer->ticket_id, xfer);
+	g_hash_table_insert(sdata->xfers_history,
+		g_strdup(ticket_id), GINT_TO_POINTER(1));
+
+	g_object_unref(parser);
+}
+
+static PurpleXfer * ggp_edisc_xfer_recv_new(PurpleConnection *gc,
+	const char *who)
+{
+	PurpleXfer *xfer;
+	ggp_edisc_xfer *edisc_xfer;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+	g_return_val_if_fail(who != NULL, NULL);
+
+	xfer = purple_xfer_new(purple_connection_get_account(gc),
+		PURPLE_XFER_TYPE_RECEIVE, who);
+	edisc_xfer = g_new0(ggp_edisc_xfer, 1);
+	purple_xfer_set_protocol_data(xfer, edisc_xfer);
+
+	edisc_xfer->gc = gc;
+
+	purple_xfer_set_init_fnc(xfer, ggp_edisc_xfer_recv_accept);
+	purple_xfer_set_request_denied_fnc(xfer, ggp_edisc_xfer_recv_reject);
+	purple_xfer_set_start_fnc(xfer, ggp_edisc_xfer_recv_start);
+	purple_xfer_set_cancel_recv_fnc(xfer, ggp_edisc_xfer_cancel);
+
+	return xfer;
+}
+
+static void ggp_edisc_xfer_recv_reject(PurpleXfer *xfer)
+{
+	ggp_edisc_xfer_recv_ack(xfer, FALSE);
+}
+
+static void ggp_edisc_xfer_recv_accept(PurpleXfer *xfer)
+{
+	ggp_edisc_xfer_recv_ack(xfer, TRUE);
+}
+
+static void ggp_edisc_xfer_recv_ack(PurpleXfer *xfer, gboolean accept)
+{
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(edisc_xfer->gc);
+	PurpleHttpRequest *req;
+
+	edisc_xfer->allowed = accept;
+
+	req = purple_http_request_new(ggp_edisc_xfer_ticket_url(
+		edisc_xfer->ticket_id));
+	purple_http_request_set_method(req, "PUT");
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+	purple_http_request_header_set(req, "X-gged-security-token",
+		sdata->security_token);
+
+	purple_http_request_header_set(req, "X-gged-ack-status",
+		accept ? "allow" : "reject");
+
+	edisc_xfer->hc = purple_http_request(edisc_xfer->gc, req,
+		accept ? ggp_edisc_xfer_recv_ack_done : NULL, xfer);
+	purple_http_request_unref(req);
+
+	if (!accept) {
+		edisc_xfer->hc = NULL;
+		ggp_edisc_xfer_free(xfer);
+	}
+}
+
+static void ggp_edisc_xfer_recv_ack_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer)
+{
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer;
+
+	if (purple_xfer_is_cancelled(xfer))
+		g_return_if_reached();
+
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	edisc_xfer->hc = NULL;
+
+	if (!purple_http_response_is_successful(response)) {
+		ggp_edisc_xfer_error(xfer, _("Cannot confirm file transfer."));
+		return;
+	}
+
+	purple_debug_info("gg", "ggp_edisc_xfer_recv_ack_done: [%s]\n",
+		purple_http_response_get_data(response, NULL));
+}
+
+static void ggp_edisc_xfer_recv_ticket_completed(PurpleXfer *xfer)
+{
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+
+	if (edisc_xfer->ready)
+		return;
+	edisc_xfer->ready = TRUE;
+
+	purple_xfer_start(xfer, -1, NULL, 0);
+}
+
+static void ggp_edisc_xfer_recv_start(PurpleXfer *xfer)
+{
+	ggp_edisc_session_data *sdata;
+	ggp_edisc_xfer *edisc_xfer;
+	gchar *upload_url;
+	PurpleHttpRequest *req;
+
+	g_return_if_fail(xfer != NULL);
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	g_return_if_fail(edisc_xfer != NULL);
+	sdata = ggp_edisc_get_sdata(edisc_xfer->gc);
+
+	upload_url = g_strdup_printf("https://drive.mpa.gg.pl/me/file/inbox/"
+		"%s,%s?api_version=%s&security_token=%s",
+		edisc_xfer->ticket_id, purple_url_encode(purple_xfer_get_filename(xfer)),
+		GGP_EDISC_API, sdata->security_token);
+	req = purple_http_request_new(upload_url);
+	g_free(upload_url);
+
+	purple_http_request_set_timeout(req, -1);
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_max_len(req, purple_xfer_get_size(xfer) + 1);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+
+	purple_http_request_set_response_writer(req, ggp_edisc_xfer_recv_writer,
+		xfer);
+
+	edisc_xfer->hc = purple_http_request(edisc_xfer->gc, req,
+		ggp_edisc_xfer_recv_done, xfer);
+	purple_http_request_unref(req);
+
+	purple_http_conn_set_progress_watcher(edisc_xfer->hc,
+		ggp_edisc_xfer_progress_watcher, xfer, 250000);
+}
+
+static gboolean ggp_edisc_xfer_recv_writer(PurpleHttpConnection *http_conn,
+	PurpleHttpResponse *response, const gchar *buffer, size_t offset,
+	size_t length, gpointer _xfer)
+{
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer;
+	gssize stored;
+
+	g_return_val_if_fail(xfer != NULL, FALSE);
+	edisc_xfer = purple_xfer_get_protocol_data(xfer);
+	g_return_val_if_fail(edisc_xfer != NULL, FALSE);
+
+	stored = purple_xfer_write_file(xfer, (guchar *)buffer, length) ?
+			length : -1;
+
+	if (stored < 0 || (gsize)stored != length) {
+		purple_debug_error("gg", "ggp_edisc_xfer_recv_writer: "
+			"saved too less\n");
+		return FALSE;
+	}
+
+	if (stored > purple_xfer_get_bytes_remaining(xfer)) {
+		purple_debug_error("gg", "ggp_edisc_xfer_recv_writer: "
+			"saved too much (%d > %d)\n",
+			stored, (int)purple_xfer_get_bytes_remaining(xfer));
+		return FALSE;
+	}
+
+	/* May look redundant with ggp_edisc_xfer_progress_watcher,
+	 * but it isn't!
+	 */
+	purple_xfer_set_bytes_sent(xfer,
+		purple_xfer_get_bytes_sent(xfer) + stored);
+
+	return TRUE;
+}
+
+static void ggp_edisc_xfer_recv_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer _xfer)
+{
+	PurpleXfer *xfer = _xfer;
+	ggp_edisc_xfer *edisc_xfer = purple_xfer_get_protocol_data(xfer);
+
+	if (purple_xfer_is_cancelled(xfer))
+		return;
+
+	g_return_if_fail(edisc_xfer != NULL);
+
+	edisc_xfer->hc = NULL;
+
+	if (!purple_http_response_is_successful(response)) {
+		ggp_edisc_xfer_error(xfer, _("Error while receiving a file"));
+		return;
+	}
+
+	if (purple_xfer_get_bytes_remaining(xfer) == 0) {
+		purple_xfer_set_completed(xfer, TRUE);
+		purple_xfer_end(xfer);
+		ggp_edisc_xfer_free(xfer);
+	} else {
+		purple_debug_warning("gg", "ggp_edisc_xfer_recv_done: didn't "
+			"received everything\n");
+		ggp_edisc_xfer_error(xfer, _("Error while receiving a file"));
+	}
+}
+
+/*******************************************************************************
+ * Authentication.
+ ******************************************************************************/
+
+struct _ggp_edisc_auth_data
+{
+	ggp_ggdrive_auth_cb cb;
+	gpointer user_data;
+};
+
+static void ggp_ggdrive_auth_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer user_data);
+
+static void ggp_ggdrive_auth(PurpleConnection *gc, ggp_ggdrive_auth_cb cb,
+	gpointer user_data)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	ggp_edisc_auth_data *auth;
+	const gchar *imtoken;
+	gchar *metadata;
+	PurpleHttpRequest *req;
+
+	imtoken = ggp_get_imtoken(gc);
+	if (!imtoken)
+	{
+		cb(gc, FALSE, user_data);
+		return;
+	}
+
+	if (sdata->auth_done)
+	{
+		cb(gc, sdata->security_token != NULL, user_data);
+		return;
+	}
+
+	auth = g_new0(ggp_edisc_auth_data, 1);
+	auth->cb = cb;
+	auth->user_data = user_data;
+	sdata->auth_pending = g_list_prepend(sdata->auth_pending, auth);
+
+	if (sdata->auth_request)
+		return;
+
+	purple_debug_info("gg", "ggp_ggdrive_auth(gc=%p)\n", gc);
+
+	req = purple_http_request_new("https://drive.mpa.gg.pl/signin");
+	purple_http_request_set_method(req, "PUT");
+
+	ggp_edisc_set_defaults(req);
+	purple_http_request_set_cookie_jar(req, sdata->cookies);
+
+	metadata = g_strdup_printf("{"
+		"\"id\": \"%032x\", "
+		"\"name\": \"%s\", "
+		"\"os_version\": \"" GGP_EDISC_OS "\", "
+		"\"client_version\": \"%s\", "
+		"\"type\": \"" GGP_EDISC_TYPE "\"}",
+		g_random_int_range(1, 1 << 16),
+		purple_get_host_name(),
+		ggp_libgaduw_version(gc));
+
+	purple_http_request_header_set_printf(req, "Authorization",
+		"IMToken %s", imtoken);
+	purple_http_request_header_set_printf(req, "X-gged-user",
+		"gg/pl:%u", accdata->session->uin);
+	purple_http_request_header_set(req, "X-gged-client-metadata", metadata);
+	g_free(metadata);
+
+	sdata->auth_request = purple_http_request(gc, req,
+		ggp_ggdrive_auth_done, NULL);
+	purple_http_request_unref(req);
+}
+
+static void ggp_ggdrive_auth_results(PurpleConnection *gc, gboolean success)
+{
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	GList *it;
+
+	purple_debug_info("gg", "ggp_ggdrive_auth_results(gc=%p): %d\n",
+		gc, success);
+
+	it = g_list_first(sdata->auth_pending);
+	while (it) {
+		ggp_edisc_auth_data *auth = it->data;
+		it = g_list_next(it);
+
+		auth->cb(gc, success, auth->user_data);
+		g_free(auth);
+	}
+	g_list_free(sdata->auth_pending);
+	sdata->auth_pending = NULL;
+	sdata->auth_done = TRUE;
+}
+
+static void ggp_ggdrive_auth_done(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer user_data)
+{
+	PurpleConnection *gc = purple_http_conn_get_purple_connection(hc);
+	ggp_edisc_session_data *sdata = ggp_edisc_get_sdata(gc);
+	JsonParser *parser;
+	JsonObject *result;
+	int status = -1;
+
+	sdata->auth_request = NULL;
+
+	if (!purple_http_response_is_successful(response)) {
+		purple_debug_misc("gg", "ggp_ggdrive_auth_done: authentication "
+			"failed due to unsuccessful request (code = %d)\n",
+			purple_http_response_get_code(response));
+		ggp_ggdrive_auth_results(gc, FALSE);
+		return;
+	}
+
+	parser = ggp_json_parse(purple_http_response_get_data(response, NULL));
+	result = json_node_get_object(json_parser_get_root(parser));
+	result = json_object_get_object_member(result, "result");
+	if (json_object_has_member(result, "status"))
+		status = json_object_get_int_member(result, "status");
+	g_object_unref(parser);
+
+	if (status != 0 ) {
+		purple_debug_misc("gg", "ggp_ggdrive_auth_done: authentication "
+			"failed due to bad result (status=%d)\n", status);
+		if (purple_debug_is_verbose())
+			purple_debug_misc("gg", "ggp_ggdrive_auth_done: "
+				"result = %s\n",
+				purple_http_response_get_data(response, NULL));
+		ggp_ggdrive_auth_results(gc, FALSE);
+		return;
+	}
+
+	sdata->security_token = g_strdup(purple_http_response_get_header(
+		response, "X-gged-security-token"));
+	if (!sdata->security_token) {
+		purple_debug_misc("gg", "ggp_ggdrive_auth_done: authentication "
+			"failed due to missing security token header\n");
+		ggp_ggdrive_auth_results(gc, FALSE);
+		return;
+	}
+
+	if (purple_debug_is_unsafe())
+		purple_debug_misc("gg", "ggp_ggdrive_auth_done: "
+			"security_token=%s\n", sdata->security_token);
+	ggp_ggdrive_auth_results(gc, TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/edisc.h	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,22 @@
+#ifndef _GGP_EDISC_H
+#define _GGP_EDISC_H
+
+#include <internal.h>
+
+typedef struct _ggp_edisc_session_data ggp_edisc_session_data;
+
+/* Setting up. */
+void ggp_edisc_setup(PurpleConnection *gc);
+void ggp_edisc_cleanup(PurpleConnection *gc);
+
+/* General xfer functions. */
+void ggp_edisc_xfer_ticket_changed(PurpleConnection *gc,
+	const char *data);
+
+/* Sending a file. */
+gboolean ggp_edisc_xfer_can_receive_file(PurpleConnection *gc, const char *who);
+void ggp_edisc_xfer_send_file(PurpleConnection *gc, const char *who,
+	const char *filename);
+PurpleXfer * ggp_edisc_xfer_send_new(PurpleConnection *gc, const char *who);
+
+#endif /* _GGP_EDISC_H */
--- a/libpurple/protocols/gg/gg.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/gg.c	Mon Sep 16 18:08:19 2013 +0530
@@ -39,7 +39,7 @@
 #include "xmlnode.h"
 
 #include "gg.h"
-#include "confer.h"
+#include "chat.h"
 #include "search.h"
 #include "blist.h"
 #include "utils.h"
@@ -51,7 +51,11 @@
 #include "multilogon.h"
 #include "status.h"
 #include "servconn.h"
+#include "tcpsocket.h"
 #include "pubdir-prpl.h"
+#include "message-prpl.h"
+#include "html.h"
+#include "libgaduw.h"
 
 /* ---------------------------------------------------------------------- */
 static PurpleProtocol *my_protocol = NULL;
@@ -65,7 +69,7 @@
 	if (buddy_data)
 		return buddy_data;
 	
-	buddy_data = g_new0(ggp_buddy_data, 1);
+	buddy_data = g_new0(ggp_buddy_data, 1); //TODO: leak
 	purple_buddy_set_protocol_data(buddy, buddy_data);
 	return buddy_data;
 }
@@ -81,6 +85,30 @@
 	purple_buddy_set_protocol_data(buddy, NULL);
 }
 
+const gchar * ggp_get_imtoken(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+
+	if (accdata->imtoken)
+		return accdata->imtoken;
+
+	if (accdata->imtoken_warned)
+		return NULL;
+	accdata->imtoken_warned = TRUE;
+
+	purple_notify_error(gc, _("Authentication failed"),
+		_("IMToken value has not been received."),
+		_("Some features will be disabled. "
+		"You may try again after a while."));
+	return NULL;
+}
+
+uin_t ggp_own_uin(PurpleConnection *gc)
+{
+	return ggp_str_to_uin(purple_account_get_username(
+		purple_connection_get_account(gc)));
+}
+
 /* ---------------------------------------------------------------------- */
 // buddy list import/export from/to file
 
@@ -169,72 +197,6 @@
 		purple_request_cpar_from_connection(gc), gc);
 }
 
-/* ----- CONFERENCES ---------------------------------------------------- */
-
-static void ggp_callback_add_to_chat_ok(PurpleBuddy *buddy, PurpleRequestFields *fields)
-{
-	PurpleConnection *conn;
-	PurpleRequestField *field;
-	GList *sel;
-
-	conn = purple_account_get_connection(purple_buddy_get_account(buddy));
-
-	g_return_if_fail(conn != NULL);
-
-	field = purple_request_fields_get_field(fields, "name");
-	sel = purple_request_field_list_get_selected(field);
-
-	if (sel == NULL) {
-		purple_debug_error("gg", "No chat selected\n");
-		return;
-	}
-
-	ggp_confer_participants_add_uin(conn, sel->data,
-					ggp_str_to_uin(purple_buddy_get_name(buddy)));
-}
-
-static void ggp_bmenu_add_to_chat(PurpleBlistNode *node, gpointer ignored)
-{
-	PurpleBuddy *buddy;
-	PurpleConnection *gc;
-	GGPInfo *info;
-
-	PurpleRequestFields *fields;
-	PurpleRequestFieldGroup *group;
-	PurpleRequestField *field;
-
-	GList *l;
-	gchar *msg;
-
-	buddy = (PurpleBuddy *)node;
-	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
-	info = purple_connection_get_protocol_data(gc);
-
-	fields = purple_request_fields_new();
-	group = purple_request_field_group_new(NULL);
-	purple_request_fields_add_group(fields, group);
-
-	field = purple_request_field_list_new("name", "Chat name");
-	for (l = info->chats; l != NULL; l = l->next) {
-		GGPChat *chat = l->data;
-		purple_request_field_list_add_icon(field, chat->name, NULL, chat->name);
-	}
-	purple_request_field_group_add_field(group, field);
-
-	msg = g_strdup_printf(_("Select a chat for buddy: %s"),
-			      purple_buddy_get_alias(buddy));
-	purple_request_fields(gc,
-			_("Add to chat..."),
-			_("Add to chat..."),
-			msg,
-			fields,
-			_("Add"), G_CALLBACK(ggp_callback_add_to_chat_ok),
-			_("Cancel"), NULL,
-			purple_request_cpar_from_connection(gc),
-			buddy);
-	g_free(msg);
-}
-
 /* ----- BLOCK BUDDIES -------------------------------------------------- */
 
 static void ggp_add_deny(PurpleConnection *gc, const char *who)
@@ -263,197 +225,6 @@
 /* ----- INTERNAL CALLBACKS --------------------------------------------- */
 /* ---------------------------------------------------------------------- */
 
-/**
- * Dispatch a message received from a buddy.
- *
- * @param gc PurpleConnection.
- * @param ev Gadu-Gadu event structure.
- *
- * Image receiving, some code borrowed from Kadu http://www.kadu.net
- */
-void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event_msg *ev, gboolean multilogon)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	PurpleChatConversation *chat;
-	gchar *from;
-	gchar *msg;
-	gchar *tmp;
-	time_t mtime;
-	uin_t sender = ev->sender;
-
-	if (ev->message == NULL)
-	{
-		purple_debug_warning("gg", "ggp_recv_message_handler: NULL as message pointer\n");
-		return;
-	}
-
-	from = g_strdup_printf("%lu", (unsigned long int)ev->sender);
-
-	tmp = g_strdup_printf("%s", ev->message);
-	purple_str_strip_char(tmp, '\r');
-	msg = g_markup_escape_text(tmp, -1);
-	g_free(tmp);
-
-	if (ev->msgclass & GG_CLASS_QUEUED)
-		mtime = ev->time;
-	else
-		mtime = time(NULL);
-
-	/* We got richtext message */
-	if (ev->formats_length)
-	{
-		gboolean got_image = FALSE, bold = FALSE, italic = FALSE, under = FALSE;
-		char *cformats = (char *)ev->formats;
-		char *cformats_end = cformats + ev->formats_length;
-		gint increased_len = 0;
-		struct gg_msg_richtext_format *actformat;
-		struct gg_msg_richtext_image *actimage;
-		GString *message = g_string_new(msg);
-
-		purple_debug_info("gg", "ggp_recv_message_handler: richtext msg from (%s): %s %i formats\n", from, msg, ev->formats_length);
-
-		while (cformats < cformats_end)
-		{
-			gint byteoffset;
-			actformat = (struct gg_msg_richtext_format *)cformats;
-			cformats += sizeof(struct gg_msg_richtext_format);
-			byteoffset = g_utf8_offset_to_pointer(message->str, actformat->position + increased_len) - message->str;
-
-			if(actformat->position == 0 && actformat->font == 0) {
-				purple_debug_warning("gg", "ggp_recv_message_handler: bogus formatting (inc: %i)\n", increased_len);
-				continue;
-			}
-			purple_debug_info("gg", "ggp_recv_message_handler: format at pos: %i, image:%i, bold:%i, italic: %i, under:%i (inc: %i)\n",
-				actformat->position,
-				(actformat->font & GG_FONT_IMAGE) != 0,
-				(actformat->font & GG_FONT_BOLD) != 0,
-				(actformat->font & GG_FONT_ITALIC) != 0,
-				(actformat->font & GG_FONT_UNDERLINE) != 0,
-				increased_len);
-
-			if (actformat->font & GG_FONT_IMAGE)
-			{
-				const char *placeholder;
-			
-				got_image = TRUE;
-				actimage = (struct gg_msg_richtext_image*)(cformats);
-				cformats += sizeof(struct gg_msg_richtext_image);
-				purple_debug_info("gg", "ggp_recv_message_handler: image received, size: %d, crc32: %i\n", actimage->size, actimage->crc32);
-
-				/* Checking for errors, image size shouldn't be
-				 * larger than 255.000 bytes */
-				if (actimage->size > 255000) {
-					purple_debug_warning("gg", "ggp_recv_message_handler: received image large than 255 kb\n");
-					continue;
-				}
-
-				gg_image_request(info->session, ev->sender,
-					actimage->size, actimage->crc32);
-
-				placeholder = ggp_image_pending_placeholder(actimage->crc32);
-				g_string_insert(message, byteoffset, placeholder);
-				increased_len += strlen(placeholder);
-				continue;
-			}
-
-			if (actformat->font & GG_FONT_BOLD) {
-				if (bold == FALSE) {
-					g_string_insert(message, byteoffset, "<b>");
-					increased_len += 3;
-					bold = TRUE;
-				}
-			} else if (bold) {
-				g_string_insert(message, byteoffset, "</b>");
-				increased_len += 4;
-				bold = FALSE;
-			}
-
-			if (actformat->font & GG_FONT_ITALIC) {
-				if (italic == FALSE) {
-					g_string_insert(message, byteoffset, "<i>");
-					increased_len += 3;
-					italic = TRUE;
-				}
-			} else if (italic) {
-				g_string_insert(message, byteoffset, "</i>");
-				increased_len += 4;
-				italic = FALSE;
-			}
-
-			if (actformat->font & GG_FONT_UNDERLINE) {
-				if (under == FALSE) {
-					g_string_insert(message, byteoffset, "<u>");
-					increased_len += 3;
-					under = TRUE;
-				}
-			} else if (under) {
-				g_string_insert(message, byteoffset, "</u>");
-				increased_len += 4;
-				under = FALSE;
-			}
-
-			if (actformat->font & GG_FONT_COLOR) {
-				cformats += sizeof(struct gg_msg_richtext_color);
-			}
-		}
-
-		msg = message->str;
-		g_string_free(message, FALSE);
-
-		if (got_image)
-		{
-			ggp_image_got_im(gc, sender, msg, mtime);
-			return;
-		}
-	}
-
-	purple_debug_info("gg", "ggp_recv_message_handler: msg from (%s): %s (class = %d; rcpt_count = %d; multilogon = %d)\n",
-			from, msg, ev->msgclass,
-			ev->recipients_count,
-			multilogon);
-
-	if (multilogon && ev->recipients_count != 0) {
-		purple_debug_warning("gg", "ggp_recv_message_handler: conference multilogon messages are not yet handled\n");
-	} else if (multilogon) {
-		PurpleAccount *account = purple_connection_get_account(gc);
-		PurpleIMConversation *im;
-		const gchar *who = ggp_uin_to_str(ev->sender); // not really sender
-		im = purple_conversations_find_im_with_account(who, account);
-		if (im == NULL)
-			im = purple_im_conversation_new(account, who);
-		purple_conversation_write(PURPLE_CONVERSATION(im), purple_account_get_username(account), msg, PURPLE_MESSAGE_SEND, mtime);
-	} else if (ev->recipients_count == 0) {
-		serv_got_im(gc, from, msg, 0, mtime);
-	} else {
-		const char *chat_name;
-		int chat_id;
-
-		chat_name = ggp_confer_find_by_participants(gc,
-				ev->recipients,
-				ev->recipients_count);
-
-		if (chat_name == NULL) {
-			chat_name = ggp_confer_add_new(gc, NULL);
-			serv_got_joined_chat(gc, info->chats_count, chat_name);
-
-			ggp_confer_participants_add_uin(gc, chat_name,
-							ev->sender);
-
-			ggp_confer_participants_add(gc, chat_name,
-						    ev->recipients,
-						    ev->recipients_count);
-		}
-		chat = ggp_confer_find_by_name(gc, chat_name);
-		chat_id = purple_chat_conversation_get_id(chat);
-
-		serv_got_chat_in(gc, chat_id,
-			ggp_buddylist_get_buddy_name(gc, ev->sender),
-			PURPLE_MESSAGE_RECV, msg, mtime);
-	}
-	g_free(msg);
-	g_free(from);
-}
-
 static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
 	gchar *from;
 
@@ -477,39 +248,43 @@
 static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
 {
 	PurpleXmlNode *xml = NULL;
-	PurpleXmlNode *purple_xmlnode_next_event;
+	PurpleXmlNode *xmlnode_next_event;
 
 	xml = purple_xmlnode_from_str(data, -1);
 	if (xml == NULL)
+	{
+		purple_debug_error("gg", "ggp_xml_event_handler: "
+			"invalid xml: [%s]\n", data);
 		goto out;
+	}
 
-	purple_xmlnode_next_event = purple_xmlnode_get_child(xml, "event");
-	while (purple_xmlnode_next_event != NULL)
+	xmlnode_next_event = purple_xmlnode_get_child(xml, "event");
+	while (xmlnode_next_event != NULL)
 	{
-		PurpleXmlNode *purple_xmlnode_current_event = purple_xmlnode_next_event;
+		PurpleXmlNode *xmlnode_current_event = xmlnode_next_event;
 		
-		PurpleXmlNode *purple_xmlnode_type;
+		PurpleXmlNode *xmlnode_type;
 		char *event_type_raw;
 		int event_type = 0;
 		
-		PurpleXmlNode *purple_xmlnode_sender;
+		PurpleXmlNode *xmlnode_sender;
 		char *event_sender_raw;
 		uin_t event_sender = 0;
 
-		purple_xmlnode_next_event = purple_xmlnode_get_next_twin(purple_xmlnode_next_event);
+		xmlnode_next_event = purple_xmlnode_get_next_twin(xmlnode_next_event);
 		
-		purple_xmlnode_type = purple_xmlnode_get_child(purple_xmlnode_current_event, "type");
-		if (purple_xmlnode_type == NULL)
+		xmlnode_type = purple_xmlnode_get_child(xmlnode_current_event, "type");
+		if (xmlnode_type == NULL)
 			continue;
-		event_type_raw = purple_xmlnode_get_data(purple_xmlnode_type);
+		event_type_raw = purple_xmlnode_get_data(xmlnode_type);
 		if (event_type_raw != NULL)
 			event_type = atoi(event_type_raw);
 		g_free(event_type_raw);
 		
-		purple_xmlnode_sender = purple_xmlnode_get_child(purple_xmlnode_current_event, "sender");
-		if (purple_xmlnode_sender != NULL)
+		xmlnode_sender = purple_xmlnode_get_child(xmlnode_current_event, "sender");
+		if (xmlnode_sender != NULL)
 		{
-			event_sender_raw = purple_xmlnode_get_data(purple_xmlnode_sender);
+			event_sender_raw = purple_xmlnode_get_data(xmlnode_sender);
 			if (event_sender_raw != NULL)
 				event_sender = ggp_str_to_uin(event_sender_raw);
 			g_free(event_sender_raw);
@@ -553,16 +328,18 @@
 		case GG_EVENT_NONE:
 			/* Nothing happened. */
 			break;
-		case GG_EVENT_MSG:
-			ggp_recv_message_handler(gc, &ev->event.msg, FALSE);
+		case GG_EVENT_CONN_FAILED:
+			purple_connection_error (gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Server disconnected"));
 			break;
-		case GG_EVENT_ACK:
-			/* Changing %u to %i fixes compiler warning */
-			purple_debug_info("gg",
-				"ggp_callback_recv: message sent to: %i, delivery status=%d, seq=%d\n",
-				ev->event.ack.recipient, ev->event.ack.status,
-				ev->event.ack.seq);
+		case GG_EVENT_MSG:
+			ggp_message_got(gc, &ev->event.msg);
 			break;
+#if GGP_ENABLE_GG11
+		case GG_EVENT_ACK110:
+			break;
+#endif
 		case GG_EVENT_IMAGE_REPLY:
 			ggp_image_recv(gc, &ev->event.image_reply);
 			break;
@@ -584,6 +361,11 @@
 		case GG_EVENT_USER_DATA:
 			ggp_events_user_data(gc, &ev->event.user_data);
 			break;
+#if GGP_ENABLE_GG11
+		case GG_EVENT_JSON_EVENT:
+			ggp_events_json(gc, &ev->event.json_event);
+			break;
+#endif
 		case GG_EVENT_USERLIST100_VERSION:
 			ggp_roster_version(gc, &ev->event.userlist100_version);
 			break;
@@ -591,13 +373,30 @@
 			ggp_roster_reply(gc, &ev->event.userlist100_reply);
 			break;
 		case GG_EVENT_MULTILOGON_MSG:
-			ggp_multilogon_msg(gc, &ev->event.multilogon_msg);
+			ggp_message_got_multilogon(gc, &ev->event.multilogon_msg);
 			break;
 		case GG_EVENT_MULTILOGON_INFO:
 			ggp_multilogon_info(gc, &ev->event.multilogon_info);
 			break;
+#if GGP_ENABLE_GG11
+		case GG_EVENT_IMTOKEN:
+			purple_debug_info("gg", "gg11: got IMTOKEN\n");
+			g_free(info->imtoken);
+			info->imtoken = g_strdup(ev->event.imtoken.imtoken);
+			break;
+		case GG_EVENT_PONG110:
+			purple_debug_info("gg", "gg11: got PONG110 %lu\n", ev->event.pong110.time);
+			break;
+		case GG_EVENT_CHAT_INFO:
+		case GG_EVENT_CHAT_INFO_GOT_ALL:
+		case GG_EVENT_CHAT_INFO_UPDATE:
+		case GG_EVENT_CHAT_CREATED:
+		case GG_EVENT_CHAT_INVITE_ACK:
+			ggp_chat_got_event(gc, ev);
+			break;
+#endif
 		default:
-			purple_debug_error("gg",
+			purple_debug_warning("gg",
 				"unsupported event type=%d\n", ev->type);
 			break;
 	}
@@ -605,7 +404,7 @@
 	gg_free_event(ev);
 }
 
-static void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
+void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
 {
 	PurpleConnection *gc = _gc;
 	GGPInfo *info;
@@ -643,6 +442,14 @@
 		case GG_STATE_TLS_NEGOTIATION:
 			purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
 			break;
+#if GGP_ENABLE_GG11
+		case GG_STATE_RESOLVING_HUB:
+			purple_debug_info("gg", "GG_STATE_RESOLVING_HUB\n");
+			break;
+		case GG_STATE_READING_HUB:
+			purple_debug_info("gg", "GG_STATE_READING_HUB\n");
+			break;
+#endif
 		default:
 			purple_debug_error("gg", "unknown state = %d\n",
 					 info->session->state);
@@ -677,12 +484,13 @@
 			break;
 		case GG_EVENT_CONN_SUCCESS:
 			{
-				const gchar * server_ip = ggp_ipv4_to_str(
-					info->session->server_addr);
+#if GGP_ENABLE_GG11
 				purple_debug_info("gg", "GG_EVENT_CONN_SUCCESS:"
 					" successfully connected to %s\n",
-					server_ip);
-				ggp_servconn_add_server(server_ip);
+					info->session->connect_host);
+				ggp_servconn_add_server(info->session->
+					connect_host);
+#endif
 				purple_input_remove(info->inpa);
 				info->inpa = purple_input_add(info->session->fd,
 							  PURPLE_INPUT_READ,
@@ -798,6 +606,10 @@
 	return normalized;
 }
 
+/* TODO:
+ * - move to status.c ?
+ * - add information about not adding to his buddy list (not_a_friend)
+ */
 static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
 {
 	PurpleStatus *status;
@@ -829,72 +641,40 @@
 	}
 }
 
-static GList *ggp_blist_node_menu(PurpleBlistNode *node)
-{
-	PurpleMenuAction *act;
-	GList *m = NULL;
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	GGPInfo *info;
-
-	if (!PURPLE_IS_BUDDY(node))
-		return NULL;
-
-	account = purple_buddy_get_account((PurpleBuddy *) node);
-	gc = purple_account_get_connection(account);
-	info = purple_connection_get_protocol_data(gc);
-	if (info->chats) {
-		act = purple_menu_action_new(_("Add to chat"),
-			PURPLE_CALLBACK(ggp_bmenu_add_to_chat),
-			NULL, NULL);
-		m = g_list_append(m, act);
-	}
-
-	return m;
-}
-
-static GList *ggp_chat_info(PurpleConnection *gc)
-{
-	GList *m = NULL;
-	PurpleProtocolChatEntry *pce;
-
-	pce = g_new0(PurpleProtocolChatEntry, 1);
-	pce->label = _("Chat _name:");
-	pce->identifier = "name";
-	pce->required = TRUE;
-	m = g_list_append(m, pce);
-
-	return m;
-}
-
 static void ggp_login(PurpleAccount *account)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
 	struct gg_login_params *glp;
 	GGPInfo *info;
+#if GGP_ENABLE_GG11
 	const char *address;
-	const gchar *encryption_type;
+#endif
+	const gchar *encryption_type, *protocol_version;
 
 	if (!ggp_deprecated_setup_proxy(gc))
 		return;
 
+	purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_HTML |
+			PURPLE_CONNECTION_FLAG_NO_URLDESC);
+
 	glp = g_new0(struct gg_login_params, 1);
+#if GGP_ENABLE_GG11
+	glp->struct_size = sizeof(struct gg_login_params);
+#endif
 	info = g_new0(GGPInfo, 1);
 
-	/* Probably this should be moved to *_new() function. */
-	info->session = NULL;
-	info->chats = NULL;
-	info->chats_count = 0;
-	
 	purple_connection_set_protocol_data(gc, info);
 
-
+	ggp_tcpsocket_setup(gc, glp);
 	ggp_image_setup(gc);
 	ggp_avatar_setup(gc);
 	ggp_roster_setup(gc);
 	ggp_multilogon_setup(gc);
 	ggp_status_setup(gc);
-	
+	ggp_chat_setup(gc);
+	ggp_message_setup(gc);
+	ggp_edisc_setup(gc);
+
 	glp->uin = ggp_str_to_uin(purple_account_get_username(account));
 	glp->password =
 		ggp_convert_to_cp1250(purple_connection_get_password(gc));
@@ -903,6 +683,7 @@
 		purple_connection_error(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_USERNAME,
 			_("The username specified is invalid."));
+		purple_str_wipe(glp->password);
 		g_free(glp);
 		return;
 	}
@@ -933,44 +714,55 @@
 			purple_connection_error(gc,
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("SSL support unavailable"));
+			purple_str_wipe(glp->password);
 			g_free(glp);
 			return;
 		}
 	}
 	else /* encryption_type == "none" */
 		glp->tls = GG_SSL_DISABLED;
-	purple_debug_info("gg", "TLS mode: %d\n", glp->tls);
+	purple_debug_misc("gg", "TLS mode: %d\n", glp->tls);
+
+	protocol_version = purple_account_get_string(account,
+		"protocol_version", "default");
+	purple_debug_info("gg", "Requested protocol version: %s\n",
+		protocol_version);
+#if GGP_ENABLE_GG11
+	if (g_strcmp0(protocol_version, "gg10") == 0)
+		glp->protocol_version = GG_PROTOCOL_VERSION_100;
+	else if (g_strcmp0(protocol_version, "gg11") == 0)
+		glp->protocol_version = GG_PROTOCOL_VERSION_110;
+#else
+	glp->protocol_version = 0x2e;
+#endif
 
 	ggp_status_set_initial(gc, glp);
-	
+
+#if GGP_ENABLE_GG11
 	address = purple_account_get_string(account, "gg_server", "");
 	if (address && *address)
-	{
-		glp->server_addr = inet_addr(address);
-		glp->server_port = 8074;
-		
-		if (glp->server_addr == INADDR_NONE)
-		{
-			purple_connection_error(gc,
-				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-				_("Provided server IP address is not valid"));
-			g_free(glp);
-			return;
-		}
-	} else
-		purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
+		glp->connect_host = g_strdup(address);
+#endif
 
 	info->session = gg_login(glp);
+#if GGP_ENABLE_GG11
+	g_free(glp->connect_host);
+#endif
+	purple_str_wipe(glp->password);
+	g_free(glp);
+
 	purple_connection_update_progress(gc, _("Connecting"), 0, 2);
 	if (info->session == NULL) {
 		purple_connection_error (gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Connection failed"));
-		g_free(glp);
 		return;
 	}
-	info->inpa = purple_input_add(info->session->fd, PURPLE_INPUT_READ,
-				  ggp_async_login_handler, gc);
+
+	if (info->session->fd > 0) {
+		info->inpa = purple_input_add(info->session->fd,
+			PURPLE_INPUT_READ, ggp_async_login_handler, gc);
+	}
 }
 
 static void ggp_close(PurpleConnection *gc)
@@ -978,14 +770,13 @@
 	PurpleAccount *account;
 	GGPInfo *info;;
 
-	if (gc == NULL) {
-		purple_debug_info("gg", "gc == NULL\n");
-		return;
-	}
+	g_return_if_fail(gc != NULL);
 
 	account = purple_connection_get_account(gc);
 	info = purple_connection_get_protocol_data(gc);
 
+	purple_notify_close_with_handle(gc);
+
 	if (info) {
 		if (info->session != NULL)
 		{
@@ -994,19 +785,18 @@
 			gg_free_session(info->session);
 		}
 
-		/* Immediately close any notifications on this handle since that process depends
-		 * upon the contents of info->searches, which we are about to destroy.
-		 */
-		purple_notify_close_with_handle(gc);
-
 		ggp_image_cleanup(gc);
 		ggp_avatar_cleanup(gc);
 		ggp_roster_cleanup(gc);
 		ggp_multilogon_cleanup(gc);
 		ggp_status_cleanup(gc);
+		ggp_chat_cleanup(gc);
+		ggp_message_cleanup(gc);
+		ggp_edisc_cleanup(gc);
 
 		if (info->inpa > 0)
 			purple_input_remove(info->inpa);
+		g_free(info->imtoken);
 
 		purple_connection_set_protocol_data(gc, NULL);
 		g_free(info);
@@ -1015,131 +805,6 @@
 	purple_debug_info("gg", "Connection closed.\n");
 }
 
-static int ggp_send_im(PurpleConnection *gc, const char *who, const char *msg,
-		       PurpleMessageFlags flags)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	char *tmp, *plain;
-	int ret = 1;
-	unsigned char format[1024];
-	unsigned int format_length = sizeof(struct gg_msg_richtext);
-	gint pos = 0;
-	GData *attribs;
-	const char *start, *end = NULL, *last;
-	ggp_buddy_data *buddy_data = ggp_buddy_get_data(
-		purple_blist_find_buddy(purple_connection_get_account(gc), who));
-
-	if (msg == NULL || *msg == '\0') {
-		return 0;
-	}
-
-	if (buddy_data->blocked)
-		return -1;
-
-	last = msg;
-
-	/* Check if the message is richtext */
-	/* TODO: Check formatting, too */
-	if(purple_markup_find_tag("img", last, &start, &end, &attribs)) {
-
-		GString *string_buffer = g_string_new(NULL);
-		struct gg_msg_richtext fmt;
-
-		do
-		{
-			const char *id = g_datalist_get_data(&attribs, "id");
-			struct gg_msg_richtext_format actformat;
-			struct gg_msg_richtext_image actimage;
-			ggp_image_prepare_result prepare_result;
-
-			/* Add text before the image */
-			if(start - last)
-			{
-				pos = pos + g_utf8_strlen(last, start - last);
-				g_string_append_len(string_buffer, last,
-					start - last);
-			}
-			last = end + 1;
-			
-			if (id == NULL)
-			{
-				g_datalist_clear(&attribs);
-				continue;
-			}
-
-			/* add the image itself */
-			prepare_result = ggp_image_prepare(
-				gc, atoi(id), who, &actimage);
-			if (prepare_result == GGP_IMAGE_PREPARE_OK)
-			{
-				actformat.font = GG_FONT_IMAGE;
-				actformat.position = pos;
-
-				memcpy(format + format_length, &actformat,
-					sizeof(actformat));
-				format_length += sizeof(actformat);
-				memcpy(format + format_length, &actimage,
-					sizeof(actimage));
-				format_length += sizeof(actimage);
-			}
-			else if (prepare_result == GGP_IMAGE_PREPARE_TOO_BIG)
-			{
-				PurpleIMConversation *im =
-					purple_conversations_find_im_with_account(who,
-						purple_connection_get_account(gc));
-				purple_conversation_write(PURPLE_CONVERSATION(im), "",
-					_("Image is too large, please try "
-					"smaller one."), PURPLE_MESSAGE_ERROR,
-					time(NULL));
-			}
-			
-			g_datalist_clear(&attribs);
-		} while (purple_markup_find_tag("img", last, &start, &end,
-			&attribs));
-
-		/* Add text after the images */
-		if(last && *last) {
-			pos = pos + g_utf8_strlen(last, -1);
-			g_string_append(string_buffer, last);
-		}
-
-		fmt.flag = 2;
-		fmt.length = format_length - sizeof(fmt);
-		memcpy(format, &fmt, sizeof(fmt));
-
-		purple_debug_info("gg", "ggp_send_im: richtext msg = %s\n", string_buffer->str);
-		plain = purple_unescape_html(string_buffer->str);
-		g_string_free(string_buffer, TRUE);
-	} else {
-		purple_debug_info("gg", "ggp_send_im: msg = %s\n", msg);
-		plain = purple_unescape_html(msg);
-	}
-
-	tmp = g_strdup(plain);
-
-	if (tmp && (format_length - sizeof(struct gg_msg_richtext))) {
-		if(gg_send_message_richtext(info->session, GG_CLASS_CHAT, ggp_str_to_uin(who), (unsigned char *)tmp, format, format_length) < 0) {
-			ret = -1;
-		} else {
-			ret = 1;
-		}
-	} else if (NULL == tmp || *tmp == 0) {
-		ret = 0;
-	} else if (strlen(tmp) > GG_MSG_MAXSIZE) {
-		ret = -E2BIG;
-	} else if (gg_send_message(info->session, GG_CLASS_CHAT,
-				ggp_str_to_uin(who), (unsigned char *)tmp) < 0) {
-		ret = -1;
-	} else {
-		ret = 1;
-	}
-
-	g_free(plain);
-	g_free(tmp);
-
-	return ret;
-}
-
 static unsigned int ggp_send_typing(PurpleConnection *gc, const char *name, PurpleIMTypingState state)
 {
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
@@ -1186,94 +851,6 @@
 	ggp_roster_remove_buddy(gc, buddy, group);
 }
 
-static void ggp_join_chat(PurpleConnection *gc, GHashTable *data)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPChat *chat;
-	char *chat_name;
-	GList *l;
-	PurpleChatConversation *conv;
-	PurpleAccount *account = purple_connection_get_account(gc);
-
-	chat_name = g_hash_table_lookup(data, "name");
-
-	if (chat_name == NULL)
-		return;
-
-	purple_debug_info("gg", "joined %s chat\n", chat_name);
-
-	for (l = info->chats; l != NULL; l = l->next) {
-		 chat = l->data;
-
-		 if (chat != NULL && g_utf8_collate(chat->name, chat_name) == 0) {
-			 purple_notify_error(gc, _("Chat error"),
-				 _("This chat name is already in use"), NULL);
-			 return;
-		 }
-	}
-
-	ggp_confer_add_new(gc, chat_name);
-	conv = serv_got_joined_chat(gc, info->chats_count, chat_name);
-	purple_chat_conversation_add_user(conv, purple_account_get_username(account),
-				NULL, PURPLE_CHAT_USER_NONE, TRUE);
-}
-
-static char *ggp_get_chat_name(GHashTable *data) {
-	return g_strdup(g_hash_table_lookup(data, "name"));
-}
-
-static int ggp_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
-{
-	PurpleChatConversation *conv;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPChat *chat = NULL;
-	GList *l;
-	/* char *msg, *plain; */
-	gchar *msg;
-	uin_t *uins;
-	int count = 0;
-
-	if ((conv = purple_conversations_find_chat(gc, id)) == NULL)
-		return -EINVAL;
-
-	for (l = info->chats; l != NULL; l = l->next) {
-		chat = l->data;
-
-		if (g_utf8_collate(chat->name, purple_conversation_get_name(
-				PURPLE_CONVERSATION(conv))) == 0) {
-			break;
-		}
-
-		chat = NULL;
-	}
-
-	if (chat == NULL) {
-		purple_debug_error("gg",
-			"ggp_chat_send: Hm... that's strange. No such chat?\n");
-		return -EINVAL;
-	}
-
-	uins = g_new0(uin_t, g_list_length(chat->participants));
-
-	for (l = chat->participants; l != NULL; l = l->next) {
-		uin_t uin = GPOINTER_TO_INT(l->data);
-
-		uins[count++] = uin;
-	}
-
-	msg = purple_unescape_html(message);
-	gg_send_message_confer(info->session, GG_CLASS_CHAT, count, uins,
-				(unsigned char *)msg);
-	g_free(msg);
-	g_free(uins);
-
-	serv_got_chat_in(gc, id,
-			 purple_account_get_username(purple_connection_get_account(gc)),
-			 flags, message, time(NULL));
-
-	return 0;
-}
-
 static void ggp_keepalive(PurpleConnection *gc)
 {
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
@@ -1351,6 +928,8 @@
 
 	if (buddy_data->blocked)
 		return "not-authorized";
+	if (buddy_data->not_a_friend)
+		return "unavailable";
 
 	return NULL;
 }
@@ -1375,34 +954,12 @@
 	return 1232;
 }
 
-static void purple_gg_debug_handler(int level, const char * format, va_list args) {
-	PurpleDebugLevel purple_level;
-	char *msg = g_strdup_vprintf(format, args);
-
-	/* This is pretty pointless since the GG_DEBUG levels don't correspond to
-	 * the purple ones */
-	switch (level) {
-		case GG_DEBUG_FUNCTION:
-			purple_level = PURPLE_DEBUG_INFO;
-			break;
-		case GG_DEBUG_MISC:
-		case GG_DEBUG_NET:
-		case GG_DEBUG_DUMP:
-		case GG_DEBUG_TRAFFIC:
-		default:
-			purple_level = PURPLE_DEBUG_MISC;
-			break;
-	}
-
-	purple_debug(purple_level, "gg", "%s", msg);
-	g_free(msg);
-}
-
 static void
 ggp_protocol_init(PurpleProtocol *protocol)
 {
 	PurpleAccountOption *option;
 	GList *encryption_options = NULL;
+	GList *protocol_version = NULL;
 
 	protocol->id        = "gg";
 	protocol->name      = "Gadu-Gadu";
@@ -1436,6 +993,15 @@
 	protocol->protocol_options = g_list_append(protocol->protocol_options,
 		option);
 
+	ADD_VALUE(protocol_version, _("Default"), "default");
+	ADD_VALUE(protocol_version, "GG 10", "gg10");
+	ADD_VALUE(protocol_version, "GG 11", "gg11");
+
+	option = purple_account_option_list_new(_("Protocol version"),
+		"protocol_version", protocol_version);
+	protocol->protocol_options = g_list_append(protocol->protocol_options,
+		option);
+
 	option = purple_account_option_bool_new(_("Show links from strangers"),
 		"show_links_from_strangers", 1);
 	protocol->protocol_options = g_list_append(protocol->protocol_options,
@@ -1458,7 +1024,6 @@
 	client_iface->list_emblem            = ggp_list_emblem;
 	client_iface->status_text            = ggp_status_buddy_text;
 	client_iface->tooltip_text           = ggp_tooltip_text;
-	client_iface->blist_node_menu        = ggp_blist_node_menu;
 	client_iface->buddy_free             = ggp_buddy_free;
 	client_iface->normalize              = ggp_normalize;
 	client_iface->offline_message        = ggp_offline_message;
@@ -1484,18 +1049,31 @@
 static void
 ggp_protocol_im_iface_init(PurpleProtocolIMIface *im_iface)
 {
-	im_iface->send        = ggp_send_im;
+	im_iface->send        = ggp_message_send_im;
 	im_iface->send_typing = ggp_send_typing;
 }
 
+#if GGP_ENABLE_GG11
+static void
+ggp_protocol_chat_iface_init(PurpleProtocolChatIface *chat_iface)
+{
+	chat_iface->info          = ggp_chat_info;
+	chat_iface->info_defaults = ggp_chat_info_defaults;
+	chat_iface->join          = ggp_chat_join;
+	chat_iface->get_name      = ggp_chat_get_name;
+	chat_iface->invite        = ggp_chat_invite;
+	chat_iface->leave         = ggp_chat_leave;
+	chat_iface->send          = ggp_chat_send;
+
+	chat_iface->reject        = NULL; /* TODO */
+}
+
 static void
-ggp_protocol_chat_iface_init(PurpleProtocolChatIface *chat_iface)
+ggp_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *roomlist_iface)
 {
-	chat_iface->info     = ggp_chat_info;
-	chat_iface->join     = ggp_join_chat;
-	chat_iface->get_name = ggp_get_chat_name;
-	chat_iface->send     = ggp_chat_send;
+	roomlist_iface->get_list = ggp_chat_roomlist_get_list;
 }
+#endif
 
 static void
 ggp_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *privacy_iface)
@@ -1504,6 +1082,14 @@
 	privacy_iface->rem_deny = ggp_rem_deny;
 }
 
+static void
+ggp_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+{
+	xfer_iface->can_receive = ggp_edisc_xfer_can_receive_file;
+	xfer_iface->send        = ggp_edisc_xfer_send_file;
+	xfer_iface->new_xfer    = ggp_edisc_xfer_send_new;
+}
+
 PURPLE_DEFINE_TYPE_EXTENDED(
 	GGPProtocol, ggp_protocol, PURPLE_TYPE_PROTOCOL, 0,
 
@@ -1515,12 +1101,18 @@
 
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
 	                                  ggp_protocol_im_iface_init)
-
+#if GGP_ENABLE_GG11
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
 	                                  ggp_protocol_chat_iface_init)
 
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
+	                                  ggp_protocol_roomlist_iface_init)
+#endif
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
 	                                  ggp_protocol_privacy_iface_init)
+
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	                                  ggp_protocol_xfer_iface_init)
 );
 
 static PurplePluginInfo *
@@ -1558,13 +1150,14 @@
 
 	purple_prefs_add_none("/protocols/gg");
 
-	gg_debug_handler = purple_gg_debug_handler;
-
 	purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
 		"libgadu %s...\n", gg_libgadu_version());
 
+	ggp_libgaduw_setup();
 	ggp_resolver_purple_setup();
 	ggp_servconn_setup(ggp_server_option);
+	ggp_html_setup();
+	ggp_message_setup_global();
 
 	return TRUE;
 }
@@ -1573,6 +1166,9 @@
 plugin_unload(PurplePlugin *plugin, GError **error)
 {
 	ggp_servconn_cleanup();
+	ggp_html_cleanup();
+	ggp_message_cleanup_global();
+	ggp_libgaduw_cleanup();
 
 	if (!purple_protocols_remove(my_protocol, error))
 		return FALSE;
--- a/libpurple/protocols/gg/gg.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/gg.h	Mon Sep 16 18:08:19 2013 +0530
@@ -24,6 +24,9 @@
 #ifndef _PURPLE_GG_H
 #define _PURPLE_GG_H
 
+#define GGP_UIN_LEN_MAX 10
+#define GGP_ENABLE_GG11 0
+
 #include <libgadu.h>
 #include "internal.h"
 #include "search.h"
@@ -35,10 +38,9 @@
 #include "roster.h"
 #include "multilogon.h"
 #include "status.h"
-
-#define PUBDIR_RESULTS_MAX 20
-
-#define GGP_UIN_LEN_MAX 10
+#include "chat.h"
+#include "message-prpl.h"
+#include "edisc.h"
 
 #define GGP_TYPE_PROTOCOL             (ggp_protocol_get_type())
 #define GGP_PROTOCOL(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GGP_TYPE_PROTOCOL, GGPProtocol))
@@ -57,39 +59,37 @@
 	PurpleProtocolClass parent_class;
 } GGPProtocolClass;
 
-typedef struct
-{
-	char *name;
-	GList *participants;
-
-} GGPChat;
-
 typedef struct {
-
 	struct gg_session *session;
 	guint inpa;
-	GList *chats;
-	int chats_count;
 
-	ggp_image_connection_data image_data;
+	gchar *imtoken;
+	gboolean imtoken_warned;
+
+	ggp_image_session_data *image_data;
 	ggp_avatar_session_data avatar_data;
 	ggp_roster_session_data roster_data;
 	ggp_multilogon_session_data *multilogon_data;
 	ggp_status_session_data *status_data;
+	ggp_chat_session_data *chat_data;
+	ggp_message_session_data *message_data;
+	ggp_edisc_session_data *edisc_data;
 } GGPInfo;
 
 typedef struct
 {
 	gboolean blocked;
+	gboolean not_a_friend;
 } ggp_buddy_data;
 
-/**
- * Returns the GType for the GGPProtocol object.
- */
 GType ggp_protocol_get_type(void);
 
-void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event_msg *ev, gboolean multilogon);
-
 ggp_buddy_data * ggp_buddy_get_data(PurpleBuddy *buddy);
 
+const gchar * ggp_get_imtoken(PurpleConnection *gc);
+
+uin_t ggp_own_uin(PurpleConnection *gc);
+
+void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond);
+
 #endif /* _PURPLE_GG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/html.c	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,158 @@
+#include "html.h"
+
+#include <debug.h>
+
+typedef struct
+{
+	GRegex *re_html_attr;
+	GRegex *re_css_attr;
+	GRegex *re_color_hex;
+	GRegex *re_color_rgb;
+} ggp_html_global_data;
+
+static ggp_html_global_data global_data;
+
+void ggp_html_setup(void)
+{
+	global_data.re_html_attr = g_regex_new(
+		"([a-z-]+)=\"([^\"]+)\"",
+		G_REGEX_OPTIMIZE, 0, NULL);
+	global_data.re_css_attr = g_regex_new(
+		"([a-z-]+): *([^;]+)",
+		G_REGEX_OPTIMIZE, 0, NULL);
+	global_data.re_color_hex = g_regex_new(
+		"^#([0-9a-fA-F]+){6}$",
+		G_REGEX_OPTIMIZE, 0, NULL);
+	global_data.re_color_rgb = g_regex_new(
+		"^rgb\\(([0-9]+), *([0-9]+), *([0-9]+)\\)$",
+		G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+void ggp_html_cleanup(void)
+{
+	g_regex_unref(global_data.re_html_attr);
+	g_regex_unref(global_data.re_css_attr);
+	g_regex_unref(global_data.re_color_hex);
+	g_regex_unref(global_data.re_color_rgb);
+}
+
+GHashTable * ggp_html_tag_attribs(const gchar *attribs_str)
+{
+	GMatchInfo *match;
+	GHashTable *attribs = g_hash_table_new_full(g_str_hash, g_str_equal,
+		g_free, g_free);
+
+	if (attribs_str == NULL)
+		return attribs;
+
+	g_regex_match(global_data.re_html_attr, attribs_str, 0, &match);
+	while (g_match_info_matches(match))
+	{
+		g_hash_table_insert(attribs,
+			g_match_info_fetch(match, 1),
+			g_match_info_fetch(match, 2));
+
+		g_match_info_next(match, NULL);
+	}
+	g_match_info_free(match);
+
+	return attribs;
+}
+
+GHashTable * ggp_html_css_attribs(const gchar *attribs_str)
+{
+	GMatchInfo *match;
+	GHashTable *attribs = g_hash_table_new_full(g_str_hash, g_str_equal,
+		g_free, g_free);
+
+	if (attribs_str == NULL)
+		return attribs;
+
+	g_regex_match(global_data.re_css_attr, attribs_str, 0, &match);
+	while (g_match_info_matches(match))
+	{
+		g_hash_table_insert(attribs,
+			g_match_info_fetch(match, 1),
+			g_match_info_fetch(match, 2));
+
+		g_match_info_next(match, NULL);
+	}
+	g_match_info_free(match);
+
+	return attribs;
+}
+
+int ggp_html_decode_color(const gchar *str)
+{
+	GMatchInfo *match;
+	int color = -1;
+
+	g_regex_match(global_data.re_color_hex, str, 0, &match);
+	if (g_match_info_matches(match))
+	{
+		if (sscanf(str + 1, "%x", &color) != 1)
+			color = -1;
+	}
+	g_match_info_free(match);
+	if (color >= 0)
+		return color;
+
+	g_regex_match(global_data.re_color_rgb, str, 0, &match);
+	if (g_match_info_matches(match))
+	{
+		int r = -1, g = -1, b = -1;
+		gchar *c_str;
+
+		c_str = g_match_info_fetch(match, 1);
+		if (c_str)
+			r = atoi(c_str);
+		g_free(c_str);
+
+		c_str = g_match_info_fetch(match, 2);
+		if (c_str)
+			g = atoi(c_str);
+		g_free(c_str);
+
+		c_str = g_match_info_fetch(match, 3);
+		if (c_str)
+			b = atoi(c_str);
+		g_free(c_str);
+
+		if (r >= 0 && r < 256 && g >= 0 && g < 256 && b >= 0 && b < 256)
+			color = (r << 16) | (g << 8) | b;
+	}
+	g_match_info_free(match);
+	if (color >= 0)
+		return color;
+
+	return -1;
+}
+
+ggp_html_tag ggp_html_parse_tag(const gchar *tag_str)
+{
+	if (0 == g_ascii_strcasecmp(tag_str, "eom"))
+		return GGP_HTML_TAG_EOM;
+	if (0 == g_ascii_strcasecmp(tag_str, "span"))
+		return GGP_HTML_TAG_SPAN;
+	if (0 == g_ascii_strcasecmp(tag_str, "div"))
+		return GGP_HTML_TAG_DIV;
+	if (0 == g_ascii_strcasecmp(tag_str, "br"))
+		return GGP_HTML_TAG_BR;
+	if (0 == g_ascii_strcasecmp(tag_str, "a"))
+		return GGP_HTML_TAG_A;
+	if (0 == g_ascii_strcasecmp(tag_str, "b"))
+		return GGP_HTML_TAG_B;
+	if (0 == g_ascii_strcasecmp(tag_str, "i"))
+		return GGP_HTML_TAG_I;
+	if (0 == g_ascii_strcasecmp(tag_str, "u"))
+		return GGP_HTML_TAG_U;
+	if (0 == g_ascii_strcasecmp(tag_str, "s"))
+		return GGP_HTML_TAG_S;
+	if (0 == g_ascii_strcasecmp(tag_str, "img"))
+		return GGP_HTML_TAG_IMG;
+	if (0 == g_ascii_strcasecmp(tag_str, "font"))
+		return GGP_HTML_TAG_FONT;
+	if (0 == g_ascii_strcasecmp(tag_str, "hr"))
+		return GGP_HTML_TAG_HR;
+	return GGP_HTML_TAG_UNKNOWN;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/html.h	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,32 @@
+#ifndef _GGP_HTML_H
+#define _GGP_HTML_H
+
+#include <internal.h>
+
+typedef enum
+{
+	GGP_HTML_TAG_UNKNOWN,
+	GGP_HTML_TAG_EOM,
+	GGP_HTML_TAG_A,
+	GGP_HTML_TAG_B,
+	GGP_HTML_TAG_I,
+	GGP_HTML_TAG_U,
+	GGP_HTML_TAG_S,
+	GGP_HTML_TAG_IMG,
+	GGP_HTML_TAG_FONT,
+	GGP_HTML_TAG_SPAN,
+	GGP_HTML_TAG_DIV,
+	GGP_HTML_TAG_BR,
+	GGP_HTML_TAG_HR,
+} ggp_html_tag;
+
+void ggp_html_setup(void);
+void ggp_html_cleanup(void);
+
+GHashTable * ggp_html_tag_attribs(const gchar *attribs_str);
+GHashTable * ggp_html_css_attribs(const gchar *attribs_str);
+int ggp_html_decode_color(const gchar *str);
+ggp_html_tag ggp_html_parse_tag(const gchar *tag_str);
+
+
+#endif /* _GGP_HTML_H */
--- a/libpurple/protocols/gg/image.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/image.c	Mon Sep 16 18:08:19 2013 +0530
@@ -35,105 +35,106 @@
 #include "gg.h"
 #include "utils.h"
 
-#define GGP_PENDING_IMAGE_ID_PREFIX "gg-pending-image-"
-
-typedef struct
+struct _ggp_image_session_data
 {
-	uin_t from;
-	gchar *text;
-	time_t mtime;
-} ggp_image_pending_message;
+	GHashTable *got_images;
+	GHashTable *incoming_images;
+	GHashTable *sent_images;
+};
 
 typedef struct
 {
 	int id;
-	gchar *conv_name;
-} ggp_image_pending_image;
+	gchar *conv_name; /* TODO: callback */
+} ggp_image_sent;
 
-static void ggp_image_pending_message_free(gpointer data)
+typedef struct
 {
-	ggp_image_pending_message *pending_message =
-		(ggp_image_pending_message*)data;
-	g_free(pending_message->text);
-	g_free(pending_message);
+	GList *listeners;
+} ggp_image_requested;
+
+typedef struct
+{
+	ggp_image_request_cb cb;
+	gpointer user_data;
+} ggp_image_requested_listener;
+
+static void ggp_image_got_free(gpointer data)
+{
+	int id = GPOINTER_TO_INT(data);
+	purple_imgstore_unref_by_id(id);
 }
 
-static void ggp_image_pending_image_free(gpointer data)
+static void ggp_image_sent_free(gpointer data)
 {
-	ggp_image_pending_image *pending_image =
-		(ggp_image_pending_image*)data;
-	g_free(pending_image->conv_name);
-	g_free(pending_image);
+	ggp_image_sent *sent_image = (ggp_image_sent*)data;
+	purple_imgstore_unref_by_id(sent_image->id);
+	g_free(sent_image->conv_name);
+	g_free(sent_image);
 }
 
-static inline ggp_image_connection_data *
-ggp_image_get_imgdata(PurpleConnection *gc)
+static void ggp_image_requested_free(gpointer data)
+{
+	ggp_image_requested *req = data;
+	g_list_free_full(req->listeners, g_free);
+	g_free(req);
+}
+
+static uint64_t ggp_image_params_to_id(uint32_t crc32, uint32_t size)
+{
+	return ((uint64_t)crc32 << 32) | size;
+}
+
+static inline ggp_image_session_data *
+ggp_image_get_sdata(PurpleConnection *gc)
 {
 	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
-	return &accdata->image_data;
+	return accdata->image_data;
 }
 
 void ggp_image_setup(PurpleConnection *gc)
 {
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_image_session_data *sdata = g_new0(ggp_image_session_data, 1);
+	
+	accdata->image_data = sdata;
 	
-	imgdata->pending_messages = NULL;
-	imgdata->pending_images = g_hash_table_new_full(NULL, NULL, NULL,
-		ggp_image_pending_image_free);
+	sdata->got_images = g_hash_table_new_full(
+		g_int64_hash, g_int64_equal, g_free,
+		ggp_image_got_free);
+	sdata->incoming_images = g_hash_table_new_full(
+		g_int64_hash, g_int64_equal, g_free,
+		ggp_image_requested_free);
+	sdata->sent_images = g_hash_table_new_full(
+		g_int64_hash, g_int64_equal, g_free,
+		ggp_image_sent_free);
 }
 
 void ggp_image_cleanup(PurpleConnection *gc)
 {
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
 	
-	g_list_free_full(imgdata->pending_messages,
-		&ggp_image_pending_message_free);
-	g_hash_table_destroy(imgdata->pending_images);
-}
-
-const char * ggp_image_pending_placeholder(uint32_t id)
-{
-	static char buff[50];
-	
-	g_snprintf(buff, 50, "<img id=\"" GGP_PENDING_IMAGE_ID_PREFIX
-		"%u\">", id);
-	
-	return buff;
+	g_hash_table_destroy(sdata->got_images);
+	g_hash_table_destroy(sdata->incoming_images);
+	g_hash_table_destroy(sdata->sent_images);
+	g_free(sdata);
 }
 
-void ggp_image_got_im(PurpleConnection *gc, uin_t from, gchar *text,
-	time_t mtime)
+ggp_image_prepare_result ggp_image_prepare(PurpleConversation *conv,
+	const int stored_id, uint64_t *id)
 {
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
-	ggp_image_pending_message *pending_message =
-		g_new(ggp_image_pending_message, 1);
-	
-	purple_debug_info("gg", "ggp_image_got_im: received message with "
-		"images from %u: %s\n", from, text);
-	
-	pending_message->from = from;
-	pending_message->text = text;
-	pending_message->mtime = mtime;
-	
-	imgdata->pending_messages = g_list_append(imgdata->pending_messages,
-		pending_message);
-}
-
-ggp_image_prepare_result ggp_image_prepare(PurpleConnection *gc, const int id,
-	const char *conv_name, struct gg_msg_richtext_image *image_info)
-{
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
-	PurpleStoredImage *image = purple_imgstore_find_by_id(id);
+	PurpleConnection *gc = purple_conversation_get_connection(conv);
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
+	PurpleStoredImage *image = purple_imgstore_find_by_id(stored_id);
 	size_t image_size;
 	gconstpointer image_data;
-	const char *image_filename;
 	uint32_t image_crc;
-	ggp_image_pending_image *pending_image;
+	ggp_image_sent *sent_image;
 	
 	if (!image)
 	{
-		purple_debug_error("gg", "ggp_image_prepare_to_send: image %d "
-			"not found in image store\n", id);
+		purple_debug_error("gg", "ggp_image_prepare: image %d "
+			"not found in image store\n", stored_id);
 		return GGP_IMAGE_PREPARE_FAILURE;
 	}
 	
@@ -141,29 +142,26 @@
 	
 	if (image_size > GGP_IMAGE_SIZE_MAX)
 	{
-		purple_debug_warning("gg", "ggp_image_prepare_to_send: image "
+		purple_debug_warning("gg", "ggp_image_prepare: image "
 			"is too big (max bytes: %d)\n", GGP_IMAGE_SIZE_MAX);
 		return GGP_IMAGE_PREPARE_TOO_BIG;
 	}
 	
 	purple_imgstore_ref(image);
 	image_data = purple_imgstore_get_data(image);
-	image_filename = purple_imgstore_get_filename(image);
 	image_crc = gg_crc32(0, image_data, image_size);
 	
-	purple_debug_info("gg", "ggp_image_prepare_to_send: image prepared "
-		"[id=%d, crc=%u, size=%" G_GSIZE_FORMAT ", filename=%s]\n",
-		id, image_crc, image_size, image_filename);
+	purple_debug_info("gg", "ggp_image_prepare: image prepared "
+		"[id=%d, crc=%u, size=%" G_GSIZE_FORMAT "]\n",
+		stored_id, image_crc, image_size);
 	
-	pending_image = g_new(ggp_image_pending_image, 1);
-	pending_image->id = id;
-	pending_image->conv_name = g_strdup(conv_name);
-	g_hash_table_insert(imgdata->pending_images, GINT_TO_POINTER(image_crc),
-		pending_image);
-	
-	image_info->unknown1 = 0x0109;
-	image_info->size = gg_fix32(image_size);
-	image_info->crc32 = gg_fix32(image_crc);
+	*id = ggp_image_params_to_id(image_crc, image_size);
+
+	sent_image = g_new(ggp_image_sent, 1);
+	sent_image->id = stored_id;
+	sent_image->conv_name = g_strdup(purple_conversation_get_name(conv));
+	g_hash_table_insert(sdata->sent_images, ggp_uint64dup(*id),
+		sent_image);
 	
 	return GGP_IMAGE_PREPARE_OK;
 }
@@ -171,11 +169,11 @@
 void ggp_image_recv(PurpleConnection *gc,
 	const struct gg_event_image_reply *image_reply)
 {
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
 	int stored_id;
-	const char *imgtag_search;
-	gchar *imgtag_replace;
-	GList *pending_messages_it;
+	ggp_image_requested *req;
+	GList *it;
+	uint64_t id;
 	
 	/* TODO: This PurpleStoredImage will be rendered within the IM window
 	   and right-clicking the image will allow the user to save the image
@@ -190,66 +188,50 @@
 		image_reply->size,
 		image_reply->filename);
 	
+	id = ggp_image_params_to_id(image_reply->crc32, image_reply->size);
+	
 	purple_debug_info("gg", "ggp_image_recv: got image "
-		"[id=%d, crc=%u, size=%u, filename=\"%s\"]\n",
+		"[stored_id=%d, crc=%u, size=%u, filename=%s, id="
+		GGP_IMAGE_ID_FORMAT "]\n",
 		stored_id,
 		image_reply->crc32,
 		image_reply->size,
-		image_reply->filename);
+		image_reply->filename,
+		id);
 
-	imgtag_search = ggp_image_pending_placeholder(image_reply->crc32);
-	imgtag_replace = g_strdup_printf("<img src=\""
-		PURPLE_STORED_IMAGE_PROTOCOL "%u\">", stored_id);
+	g_hash_table_insert(sdata->got_images, ggp_uint64dup(id),
+		GINT_TO_POINTER(stored_id));
 
-	pending_messages_it = g_list_first(imgdata->pending_messages);
-	while (pending_messages_it)
+	req = g_hash_table_lookup(sdata->incoming_images, &id);
+	if (!req)
 	{
-		ggp_image_pending_message *pending_message =
-			(ggp_image_pending_message*)pending_messages_it->data;
-		gchar *newText;
-		
-		if (strstr(pending_message->text, imgtag_search) == NULL)
-		{
-			pending_messages_it = g_list_next(pending_messages_it);
-			continue;
-		}
-		
-		purple_debug_misc("gg", "ggp_image_recv: found message "
-			"containing image: %s\n", pending_message->text);
+		purple_debug_warning("gg", "ggp_image_recv: "
+			"image " GGP_IMAGE_ID_FORMAT " wasn't requested\n",
+			id);
+		return;
+	}
+
+	it = g_list_first(req->listeners);
+	while (it)
+	{
+		ggp_image_requested_listener *listener = it->data;
+		it = g_list_next(it);
 		
-		newText = purple_strreplace(pending_message->text,
-			imgtag_search, imgtag_replace);
-		g_free(pending_message->text);
-		pending_message->text = newText;
-
-		if (strstr(pending_message->text,
-			"<img id=\"" GGP_PENDING_IMAGE_ID_PREFIX) == NULL)
-		{
-			purple_debug_info("gg", "ggp_image_recv: "
-				"message is ready to display\n");
-			serv_got_im(gc, ggp_uin_to_str(pending_message->from),
-				pending_message->text,
-				PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_IMAGES,
-				pending_message->mtime);
-			
-			ggp_image_pending_message_free(pending_message);
-			imgdata->pending_messages = g_list_remove(
-				imgdata->pending_messages, pending_message);
-		}
-		
-		pending_messages_it = g_list_next(pending_messages_it);
+		listener->cb(gc, id, stored_id, listener->user_data);
 	}
-	g_free(imgtag_replace);
+	g_hash_table_remove(sdata->incoming_images, &id);
 }
 
 void ggp_image_send(PurpleConnection *gc,
 	const struct gg_event_image_request *image_request)
 {
 	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
-	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
-	ggp_image_pending_image *pending_image;
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
+	ggp_image_sent *sent_image;
 	PurpleStoredImage *image;
 	PurpleConversation *conv;
+	uint64_t id;
+	gchar *gg_filename;
 	
 	purple_debug_info("gg", "ggp_image_send: got image request "
 		"[uin=%u, crc=%u, size=%u]\n",
@@ -257,10 +239,18 @@
 		image_request->crc32,
 		image_request->size);
 	
-	pending_image = g_hash_table_lookup(imgdata->pending_images,
-		GINT_TO_POINTER(image_request->crc32));
+	id = ggp_image_params_to_id(image_request->crc32, image_request->size);
+	
+	sent_image = g_hash_table_lookup(sdata->sent_images, &id);
 	
-	if (pending_image == NULL)
+	if (sent_image == NULL && image_request->sender == ggp_str_to_uin(
+		purple_account_get_username(purple_connection_get_account(gc))))
+	{
+		purple_debug_misc("gg", "ggp_image_send: requested image "
+			"not found, but this may be another session request\n");
+		return;
+	}
+	if (sent_image == NULL)
 	{
 		purple_debug_warning("gg", "ggp_image_send: requested image "
 			"not found\n");
@@ -268,35 +258,91 @@
 	}
 	
 	purple_debug_misc("gg", "ggp_image_send: requested image found "
-		"[id=%d, conv=%s]\n",
-		pending_image->id,
-		pending_image->conv_name);
+		"[id=" GGP_IMAGE_ID_FORMAT ", stored id=%d, conv=%s]\n",
+		id,
+		sent_image->id,
+		sent_image->conv_name);
 	
-	image = purple_imgstore_find_by_id(pending_image->id);
+	image = purple_imgstore_find_by_id(sent_image->id);
 	
 	if (!image)
 	{
 		purple_debug_error("gg", "ggp_image_send: requested image "
 			"found, but doesn't exists in image store\n");
-		g_hash_table_remove(imgdata->pending_images,
+		g_hash_table_remove(sdata->sent_images,
 			GINT_TO_POINTER(image_request->crc32));
 		return;
 	}
 	
 	//TODO: check allowed recipients
+	gg_filename = g_strdup_printf(GGP_IMAGE_ID_FORMAT, id);
 	gg_image_reply(accdata->session, image_request->sender,
-		purple_imgstore_get_filename(image),
+		gg_filename,
 		purple_imgstore_get_data(image),
 		purple_imgstore_get_size(image));
-	purple_imgstore_unref(image);
+	g_free(gg_filename);
 	
-	conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(
-		pending_image->conv_name, purple_connection_get_account(gc)));
+	conv = purple_conversations_find_with_account(
+		sent_image->conv_name,
+		purple_connection_get_account(gc));
 	if (conv != NULL)
-		purple_conversation_write(conv, "", _("Image delivered."),
+	{
+		gchar *msg = g_strdup_printf(_("Image delivered to %u."),
+			image_request->sender);
+		purple_conversation_write(conv, "", msg,
 			PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NOTIFY,
 			time(NULL));
+		g_free(msg);
+	}
+}
+
+void ggp_image_request(PurpleConnection *gc, uin_t uin, uint64_t id,
+	ggp_image_request_cb cb, gpointer user_data)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
+	ggp_image_requested *req;
+	ggp_image_requested_listener *listener;
+	uint32_t crc = id >> 32;
+	uint32_t size = id;
 	
-	g_hash_table_remove(imgdata->pending_images,
-		GINT_TO_POINTER(image_request->crc32));
+	if (size > GGP_IMAGE_SIZE_MAX && crc <= GGP_IMAGE_SIZE_MAX)
+	{
+		uint32_t tmp;
+		purple_debug_warning("gg", "ggp_image_request: "
+			"crc and size are swapped!\n");
+		tmp = crc;
+		crc = size;
+		size = tmp;
+	}
+	
+	req = g_hash_table_lookup(sdata->incoming_images, &id);
+	if (!req)
+	{
+		req = g_new0(ggp_image_requested, 1);
+		g_hash_table_insert(sdata->incoming_images,
+			ggp_uint64dup(id), req);
+		purple_debug_info("gg", "ggp_image_request: "
+			"requesting image " GGP_IMAGE_ID_FORMAT "\n", id);
+		if (gg_image_request(accdata->session, uin, size, crc) != 0)
+			purple_debug_error("gg", "ggp_image_request: failed\n");
+	}
+	else
+	{
+		purple_debug_info("gg", "ggp_image_request: "
+			"image " GGP_IMAGE_ID_FORMAT " already requested\n",
+			id);
+	}
+	
+	listener = g_new0(ggp_image_requested_listener, 1);
+	listener->cb = cb;
+	listener->user_data = user_data;
+	req->listeners = g_list_append(req->listeners, listener);
 }
+
+int ggp_image_get_cached(PurpleConnection *gc, uint64_t id)
+{
+	ggp_image_session_data *sdata = ggp_image_get_sdata(gc);
+
+	return GPOINTER_TO_INT(g_hash_table_lookup(sdata->got_images, &id));
+}
--- a/libpurple/protocols/gg/image.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/image.h	Mon Sep 16 18:08:19 2013 +0530
@@ -34,12 +34,9 @@
 #include <libgadu.h>
 
 #define GGP_IMAGE_SIZE_MAX 255000
+#define GGP_IMAGE_ID_FORMAT "%016" G_GINT64_MODIFIER "x"
 
-typedef struct
-{
-	GList *pending_messages;
-	GHashTable *pending_images;
-} ggp_image_connection_data;
+typedef struct _ggp_image_session_data ggp_image_session_data;
 
 typedef enum
 {
@@ -48,19 +45,21 @@
 	GGP_IMAGE_PREPARE_TOO_BIG
 } ggp_image_prepare_result;
 
+typedef void (*ggp_image_request_cb)(PurpleConnection *gc, uint64_t id,
+	int stored_id, gpointer user_data);
+
 void ggp_image_setup(PurpleConnection *gc);
 void ggp_image_cleanup(PurpleConnection *gc);
 
-const char * ggp_image_pending_placeholder(uint32_t id);
-
-void ggp_image_got_im(PurpleConnection *gc, uin_t from, gchar *msg,
-	time_t mtime);
-ggp_image_prepare_result ggp_image_prepare(PurpleConnection *gc, const int id,
-	const char *conv_name, struct gg_msg_richtext_image *image_info);
+ggp_image_prepare_result ggp_image_prepare(PurpleConversation *conv,
+	const int stored_id, uint64_t *id);
 
 void ggp_image_recv(PurpleConnection *gc,
 	const struct gg_event_image_reply *image_reply);
 void ggp_image_send(PurpleConnection *gc,
 	const struct gg_event_image_request *image_request);
+void ggp_image_request(PurpleConnection *gc, uin_t uin, uint64_t id,
+	ggp_image_request_cb cb, gpointer user_data);
+int ggp_image_get_cached(PurpleConnection *gc, uint64_t id);
 
 #endif /* _GGP_IMAGE_H */
--- a/libpurple/protocols/gg/libgadu-events.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/libgadu-events.c	Mon Sep 16 18:08:19 2013 +0530
@@ -32,6 +32,7 @@
 #include <debug.h>
 
 #include "avatar.h"
+#include "edisc.h"
 
 void ggp_events_user_data(PurpleConnection *gc, struct gg_event_user_data *data)
 {
@@ -81,3 +82,47 @@
 			ggp_avatar_buddy_remove(gc, uin);
 	}
 }
+
+#if GGP_ENABLE_GG11
+static void ggp_events_new_version(const gchar *data)
+{
+	/* data = {"severity":"download"} */
+	purple_debug_info("gg", "Gadu-Gadu server reports new client version."
+		" %s", data);
+}
+
+void ggp_events_json(PurpleConnection *gc, struct gg_event_json_event *ev)
+{
+	static const gchar *ignored_events[] = {
+		"edisc/scope_files_changed",
+		"notifications/state",
+		"invitations/list",
+		"notifications/list", /* gifts */
+		NULL
+	};
+	const gchar **it;
+
+	if (g_strcmp0("edisc/send_ticket_changed", ev->type) == 0) {
+		ggp_edisc_xfer_ticket_changed(gc, ev->data);
+		return;
+	}
+
+	if (g_strcmp0("updates/new-version", ev->type) == 0) {
+		ggp_events_new_version(ev->data);
+		return;
+	}
+
+	for (it = ignored_events; *it != NULL; it++) {
+		if (g_strcmp0(*it, ev->type) == 0)
+			return;
+	}
+
+	if (purple_debug_is_unsafe() && purple_debug_is_verbose())
+		purple_debug_warning("gg", "ggp_events_json: "
+			"unhandled event \"%s\": %s\n",
+			ev->type, ev->data);
+	else
+		purple_debug_warning("gg", "ggp_events_json: "
+			"unhandled event \"%s\"\n", ev->type);
+}
+#endif
--- a/libpurple/protocols/gg/libgadu-events.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/libgadu-events.h	Mon Sep 16 18:08:19 2013 +0530
@@ -33,7 +33,13 @@
 #include <internal.h>
 #include <libgadu.h>
 
+#include "gg.h"
+
 void ggp_events_user_data(PurpleConnection *gc,
 	struct gg_event_user_data *data);
 
+#if GGP_ENABLE_GG11
+void ggp_events_json(PurpleConnection *gc, struct gg_event_json_event *ev);
+#endif
+
 #endif /* _GGP_LIBGADU_EVENTS_H */
--- a/libpurple/protocols/gg/libgaduw.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/libgaduw.c	Mon Sep 16 18:08:19 2013 +0530
@@ -32,6 +32,75 @@
 #include <debug.h>
 
 #include "purplew.h"
+#include "gg.h"
+
+static void ggp_libgaduw_debug_handler(int level, const char * format,
+	va_list args);
+
+/*******************************************************************************
+ * Setup/cleanup.
+ ******************************************************************************/
+
+void ggp_libgaduw_setup(void)
+{
+	gg_debug_handler = ggp_libgaduw_debug_handler;
+}
+
+void ggp_libgaduw_cleanup(void)
+{
+	gg_debug_handler = NULL;
+}
+
+/*******************************************************************************
+ * General.
+ ******************************************************************************/
+
+const gchar * ggp_libgaduw_version(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	const gchar *ver = accdata->session->client_version;
+	
+	if (ver != NULL && isdigit(ver[0]))
+		return ver;
+	return GG_DEFAULT_CLIENT_VERSION;
+}
+
+static void ggp_libgaduw_debug_handler(int level, const char * format,
+	va_list args)
+{
+	PurpleDebugLevel purple_level;
+	char *msg;
+
+	if ((level & GG_DEBUG_NET) ||
+		(level & GG_DEBUG_FUNCTION)
+#if GGP_ENABLE_GG11
+		|| (level & GG_DEBUG_VERBOSE)
+#endif
+		) {
+		if (!purple_debug_is_verbose())
+			return;
+	}
+
+	if ((level & GG_DEBUG_DUMP) || /* GG session protocol packets */
+		(level & GG_DEBUG_TRAFFIC)) { /* HTTP traffic */
+		if (!purple_debug_is_verbose() || !purple_debug_is_unsafe())
+			return;
+	}
+
+	msg = g_strdup_vprintf(format, args);
+
+#if GGP_ENABLE_GG11
+	if (level & GG_DEBUG_ERROR)
+		purple_level = PURPLE_DEBUG_ERROR;
+	else if (level & GG_DEBUG_WARNING)
+		purple_level = PURPLE_DEBUG_WARNING;
+	else
+#endif
+		purple_level = PURPLE_DEBUG_MISC;
+
+	purple_debug(purple_level, "gg", "%s", msg);
+	g_free(msg);
+}
 
 /*******************************************************************************
  * HTTP requests.
--- a/libpurple/protocols/gg/libgaduw.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/libgaduw.h	Mon Sep 16 18:08:19 2013 +0530
@@ -49,10 +49,14 @@
 	guint inpa;
 } ggp_libgaduw_http_req;
 
+void ggp_libgaduw_setup(void);
+void ggp_libgaduw_cleanup(void);
+
+const gchar * ggp_libgaduw_version(PurpleConnection *gc);
+
 ggp_libgaduw_http_req * ggp_libgaduw_http_watch(PurpleConnection *gc,
 	struct gg_http *h, ggp_libgaduw_http_cb cb, gpointer user_data,
 	gboolean show_processing);
 void ggp_libgaduw_http_cancel(ggp_libgaduw_http_req *req);
 
-
 #endif /* _GGP_LIBGADUW_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/message-prpl.c	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,805 @@
+#include "message-prpl.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "chat.h"
+#include "utils.h"
+#include "html.h"
+
+#define GGP_GG10_DEFAULT_FORMAT "<span style=\"color:#000000; " \
+	"font-family:'MS Shell Dlg 2'; font-size:9pt; \">"
+#define GGP_GG10_DEFAULT_FORMAT_REPLACEMENT "<span>"
+#define GGP_GG11_FORCE_COMPAT FALSE
+
+#define GGP_IMAGE_REPLACEMENT "<img id=\"gg-pending-image-" GGP_IMAGE_ID_FORMAT "\">"
+#define GGP_IMAGE_DESTINATION "<img src=\"" PURPLE_STORED_IMAGE_PROTOCOL "%u\">"
+
+typedef struct
+{
+	enum
+	{
+		GGP_MESSAGE_GOT_TYPE_IM,
+		GGP_MESSAGE_GOT_TYPE_CHAT,
+		GGP_MESSAGE_GOT_TYPE_MULTILOGON
+	} type;
+
+	uin_t user;
+	gchar *text;
+	time_t time;
+	uint64_t chat_id;
+	GList *pending_images;
+
+	PurpleConnection *gc;
+} ggp_message_got_data;
+
+typedef struct
+{
+	GRegex *re_html_tag;
+	GRegex *re_gg_img;
+} ggp_message_global_data;
+
+static ggp_message_global_data global_data;
+
+struct _ggp_message_session_data
+{
+	GList *pending_messages;
+};
+
+typedef struct
+{
+	int size;
+	gchar *face;
+	int color, bgcolor;
+	gboolean b, i, u, s;
+} ggp_font;
+
+static ggp_font * ggp_font_new(void);
+static ggp_font * ggp_font_clone(ggp_font *font);
+static void ggp_font_free(gpointer font);
+
+static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc,
+	uin_t uin);
+static void ggp_message_got_data_free(ggp_message_got_data *msg);
+static gboolean ggp_message_request_images(PurpleConnection *gc,
+	ggp_message_got_data *msg);
+static void ggp_message_got_display(PurpleConnection *gc,
+	ggp_message_got_data *msg);
+static void ggp_message_format_from_gg(ggp_message_got_data *msg,
+	const gchar *text);
+
+/**************/
+
+void ggp_message_setup_global(void)
+{
+	global_data.re_html_tag = g_regex_new(
+		"<(/)?([a-zA-Z]+)( [^>]+)?>",
+		G_REGEX_OPTIMIZE, 0, NULL);
+	global_data.re_gg_img = g_regex_new(
+		"<img name=\"([0-9a-fA-F]+)\"/?>",
+		G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+void ggp_message_cleanup_global(void)
+{
+	g_regex_unref(global_data.re_html_tag);
+	g_regex_unref(global_data.re_gg_img);
+}
+
+static inline ggp_message_session_data *
+ggp_message_get_sdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return accdata->message_data;
+}
+
+void ggp_message_setup(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_message_session_data *sdata = g_new0(ggp_message_session_data, 1);
+
+	accdata->message_data = sdata;
+}
+
+void ggp_message_cleanup(PurpleConnection *gc)
+{
+	ggp_message_session_data *sdata = ggp_message_get_sdata(gc);
+	
+	g_list_free_full(sdata->pending_messages,
+		(GDestroyNotify)ggp_message_got_data_free);
+	g_free(sdata);
+}
+
+static ggp_font * ggp_font_new(void)
+{
+	ggp_font *font;
+
+	font = g_new0(ggp_font, 1);
+	font->color = -1;
+	font->bgcolor = -1;
+
+	return font;
+}
+
+static ggp_font * ggp_font_clone(ggp_font * font)
+{
+	ggp_font *clone = g_new0(ggp_font, 1);
+
+	*clone = *font;
+	clone->face = g_strdup(font->face);
+
+	return clone;
+}
+
+static void ggp_font_free(gpointer _font)
+{
+	ggp_font *font = _font;
+
+	g_free(font->face);
+	g_free(font);
+}
+
+/**/
+
+static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc,
+	uin_t uin)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleIMConversation *im;
+	const gchar *who = ggp_uin_to_str(uin);
+
+	im = purple_conversations_find_im_with_account(who, account);
+	if (im)
+		return im;
+	im = purple_im_conversation_new(account, who);
+	return im;
+}
+
+static void ggp_message_got_data_free(ggp_message_got_data *msg)
+{
+	g_list_free_full(msg->pending_images, g_free);
+	g_free(msg->text);
+	g_free(msg);
+}
+
+static void ggp_message_request_images_got(PurpleConnection *gc, uint64_t id,
+	int stored_id, gpointer _msg)
+{
+	ggp_message_session_data *sdata = ggp_message_get_sdata(gc);
+	ggp_message_got_data *msg = _msg;
+	GList *m_it, *i_it;
+	gchar *tmp, *tag_search, *tag_replace;
+
+	m_it = g_list_find(sdata->pending_messages, msg);
+	if (!m_it)
+	{
+		purple_debug_error("gg", "ggp_message_request_images_got: "
+			"message %p is not in queue\n", msg);
+		return;
+	}
+
+	i_it = g_list_find_custom(msg->pending_images, &id, ggp_int64_compare);
+	if (!i_it)
+	{
+		purple_debug_error("gg", "ggp_message_request_images_got: "
+			"image " GGP_IMAGE_ID_FORMAT " is not present in this "
+			"message\n", id);
+		return;
+	}
+
+	tag_search = g_strdup_printf(GGP_IMAGE_REPLACEMENT, id);
+	tag_replace = g_strdup_printf(GGP_IMAGE_DESTINATION, stored_id);
+
+	tmp = msg->text;
+	msg->text = purple_strreplace(msg->text, tag_search, tag_replace);
+	g_free(tmp);
+
+	g_free(tag_search);
+	g_free(tag_replace);
+
+	g_free(i_it->data);
+	msg->pending_images = g_list_delete_link(msg->pending_images, i_it);
+	if (msg->pending_images != NULL)
+	{
+		purple_debug_info("gg", "ggp_message_request_images_got: "
+			"got image " GGP_IMAGE_ID_FORMAT ", but the message "
+			"contains more of them\n", id);
+		return;
+	}
+
+	ggp_message_got_display(gc, msg);
+	ggp_message_got_data_free(msg);
+	sdata->pending_messages = g_list_delete_link(sdata->pending_messages,
+		m_it);
+}
+
+static gboolean ggp_message_request_images(PurpleConnection *gc,
+	ggp_message_got_data *msg)
+{
+	ggp_message_session_data *sdata = ggp_message_get_sdata(gc);
+	GList *it;
+	if (msg->pending_images == NULL)
+		return FALSE;
+	
+	it = msg->pending_images;
+	while (it)
+	{
+		ggp_image_request(gc, msg->user, *(uint64_t*)it->data,
+			ggp_message_request_images_got, msg);
+		it = g_list_next(it);
+	}
+
+	sdata->pending_messages = g_list_append(sdata->pending_messages, msg);
+
+	return TRUE;
+}
+
+void ggp_message_got(PurpleConnection *gc, const struct gg_event_msg *ev)
+{
+	ggp_message_got_data *msg = g_new0(ggp_message_got_data, 1);
+
+	msg->gc = gc;
+	msg->time = ev->time;
+	msg->user = ev->sender;
+
+#if GGP_ENABLE_GG11
+	if (ev->chat_id != 0)
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
+		msg->chat_id = ev->chat_id;
+	}
+	else
+#endif
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_IM;
+	}
+
+	ggp_message_format_from_gg(msg, ev->xhtml_message);
+	if (ggp_message_request_images(gc, msg))
+		return;
+
+	ggp_message_got_display(gc, msg);
+	ggp_message_got_data_free(msg);
+}
+
+void ggp_message_got_multilogon(PurpleConnection *gc,
+	const struct gg_event_msg *ev)
+{
+	ggp_message_got_data *msg = g_new0(ggp_message_got_data, 1);
+
+	msg->gc = gc;
+	msg->time = ev->time;
+	msg->user = ev->sender; /* not really a sender*/
+
+#if GGP_ENABLE_GG11
+	if (ev->chat_id != 0)
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
+		msg->chat_id = ev->chat_id;
+	}
+	else
+#endif
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_MULTILOGON;
+	}
+
+	ggp_message_format_from_gg(msg, ev->xhtml_message);
+	if (ggp_message_request_images(gc, msg))
+		return;
+
+	ggp_message_got_display(gc, msg);
+	ggp_message_got_data_free(msg);
+}
+
+static void ggp_message_got_display(PurpleConnection *gc,
+	ggp_message_got_data *msg)
+{
+	if (msg->type == GGP_MESSAGE_GOT_TYPE_IM)
+	{
+		serv_got_im(gc, ggp_uin_to_str(msg->user), msg->text,
+			PURPLE_MESSAGE_RECV, msg->time);
+	}
+#if GGP_ENABLE_GG11
+	else if (msg->type == GGP_MESSAGE_GOT_TYPE_CHAT)
+	{
+		ggp_chat_got_message(gc, msg->chat_id, msg->text, msg->time,
+			msg->user);
+	}
+#endif
+	else if (msg->type == GGP_MESSAGE_GOT_TYPE_MULTILOGON)
+	{
+		PurpleIMConversation *im = ggp_message_get_conv(gc, msg->user);
+		const gchar *me = purple_account_get_username(
+			purple_connection_get_account(gc));
+
+		purple_conversation_write(PURPLE_CONVERSATION(im), me, msg->text,
+			PURPLE_MESSAGE_SEND, msg->time);
+	}
+	else
+		purple_debug_error("gg", "ggp_message_got_display: "
+			"unexpected message type: %d\n", msg->type);
+}
+
+static gboolean ggp_message_format_from_gg_found_img(const GMatchInfo *info,
+	GString *res, gpointer data)
+{
+	ggp_message_got_data *msg = data;
+	gchar *name, *replacement;
+	int64_t id;
+	int stored_id;
+
+	name = g_match_info_fetch(info, 1);
+	if (sscanf(name, "%" G_GINT64_MODIFIER "x", &id) != 1)
+		id = 0;
+	g_free(name);
+	if (!id)
+	{
+		g_string_append(res, "[");
+		g_string_append(res, _("broken image"));
+		g_string_append(res, "]");
+		return FALSE;
+	}
+	
+	stored_id = ggp_image_get_cached(msg->gc, id);
+	
+	if (stored_id > 0)
+	{
+		purple_debug_info("gg", "ggp_message_format_from_gg_found_img: "
+			"getting image " GGP_IMAGE_ID_FORMAT " from cache\n",
+			id);
+		replacement = g_strdup_printf(GGP_IMAGE_DESTINATION, stored_id);
+		g_string_append(res, replacement);
+		g_free(replacement);
+		return FALSE;
+	}
+	
+	if (NULL == g_list_find_custom(msg->pending_images, &id,
+		ggp_int64_compare))
+	{
+		msg->pending_images = g_list_append(msg->pending_images,
+			ggp_uint64dup(id));
+	}
+	
+	replacement = g_strdup_printf(GGP_IMAGE_REPLACEMENT, id);
+	g_string_append(res, replacement);
+	g_free(replacement);
+
+	return FALSE;
+}
+
+static void ggp_message_format_from_gg(ggp_message_got_data *msg,
+	const gchar *text)
+{
+	gchar *text_new, *tmp;
+
+	if (text == NULL)
+	{
+		msg->text = g_strdup("");
+		return;
+	}
+
+	text_new = g_strdup(text);
+	purple_str_strip_char(text_new, '\r');
+
+	tmp = text_new;
+	text_new = purple_strreplace(text_new, GGP_GG10_DEFAULT_FORMAT,
+		GGP_GG10_DEFAULT_FORMAT_REPLACEMENT);
+	g_free(tmp);
+
+	tmp = text_new;
+	text_new = g_regex_replace_eval(global_data.re_gg_img, text_new, -1, 0,
+		0, ggp_message_format_from_gg_found_img, msg, NULL);
+	g_free(tmp);
+
+	msg->text = text_new;
+}
+
+gchar * ggp_message_format_to_gg(PurpleConversation *conv, const gchar *text)
+{
+	gchar *text_new, *tmp;
+	GList *rt = NULL; /* reformatted text */
+	GMatchInfo *match;
+	guint pos = 0;
+	GList *pending_objects = NULL;
+	GList *font_stack = NULL;
+	static int html_sizes_pt[7] = { 7, 8, 9, 10, 12, 14, 16 };
+
+	ggp_font *font_new, *font_current, *font_base;
+	gboolean font_changed = FALSE;
+	gboolean in_any_tag = FALSE;
+
+	/* TODO: verbose
+	 * purple_debug_info("gg", "ggp formatting text: [%s]\n", text);
+	 */
+
+	/* default font */
+	font_base = ggp_font_new();
+	font_current = ggp_font_new();
+	font_new = ggp_font_new();
+
+	/* GG11 doesn't use nbsp, it just print spaces */
+	text_new = purple_strreplace(text, "&nbsp;", " ");
+
+	/* add end-of-message tag */
+	if (strstr(text_new, "<eom>") != NULL)
+	{
+		tmp = text_new;
+		text_new = purple_strreplace(text_new, "<eom>", "");
+		g_free(tmp);
+		purple_debug_warning("gg", "ggp_message_format_to_gg: "
+			"unexpected <eom> tag\n");
+	}
+	tmp = text_new;
+	text_new = g_strdup_printf("%s<eom></eom>", text_new);
+	g_free(tmp);
+
+	g_regex_match(global_data.re_html_tag, text_new, 0, &match);
+	while (g_match_info_matches(match))
+	{
+		int m_start, m_end, m_pos;
+		gboolean tag_close;
+		gchar *tag_str, *attribs_str;
+		ggp_html_tag tag;
+		gboolean text_before;
+
+		/* reading tag and its contents */
+		g_match_info_fetch_pos(match, 0, &m_start, &m_end);
+		g_assert(m_start >= 0 && m_end >= 0);
+		text_before = ((guint)m_start > pos);
+		g_match_info_fetch_pos(match, 1, &m_pos, NULL);
+		tag_close = (m_pos >= 0);
+		tag_str = g_match_info_fetch(match, 2);
+		tag = ggp_html_parse_tag(tag_str);
+		attribs_str = g_match_info_fetch(match, 3);
+		g_match_info_next(match, NULL);
+
+		if (tag == GGP_HTML_TAG_UNKNOWN)
+			purple_debug_warning("gg", "ggp_message_format_to_gg: "
+				"uknown tag %s\n", tag_str);
+
+		/* closing *all* formatting-related tags (GG11 weirness)
+		 * and adding pending objects */
+		if ((text_before && (font_changed || pending_objects)) ||
+			(tag == GGP_HTML_TAG_EOM && tag_close))
+		{
+			font_changed = FALSE;
+			if (in_any_tag)
+			{
+				in_any_tag = FALSE;
+				if (font_current->s && !GGP_GG11_FORCE_COMPAT)
+					rt = g_list_prepend(rt,
+						g_strdup("</s>"));
+				if (font_current->u)
+					rt = g_list_prepend(rt,
+						g_strdup("</u>"));
+				if (font_current->i)
+					rt = g_list_prepend(rt,
+						g_strdup("</i>"));
+				if (font_current->b)
+					rt = g_list_prepend(rt,
+						g_strdup("</b>"));
+				rt = g_list_prepend(rt, g_strdup("</span>"));
+			}
+			if (pending_objects)
+			{
+				rt = g_list_concat(pending_objects, rt);
+				pending_objects = NULL;
+			}
+		}
+		
+		/* opening formatting-related tags again */
+		if (text_before && !in_any_tag)
+		{
+			gchar *style;
+			GList *styles = NULL;
+			gboolean has_size = (font_new->size > 0 &&
+				font_new->size <= 7 && font_new->size != 3);
+			
+			if (has_size)
+				styles = g_list_append(styles, g_strdup_printf(
+					"font-size:%dpt;",
+					html_sizes_pt[font_new->size - 1]));
+			if (font_new->face)
+				styles = g_list_append(styles, g_strdup_printf(
+					"font-family:%s;", font_new->face));
+			if (font_new->bgcolor >= 0 && !GGP_GG11_FORCE_COMPAT)
+				styles = g_list_append(styles, g_strdup_printf(
+					"background-color:#%06x;",
+					font_new->bgcolor));
+			if (font_new->color >= 0)
+				styles = g_list_append(styles, g_strdup_printf(
+					"color:#%06x;", font_new->color));
+			
+			if (styles)
+			{
+				gchar *combined = ggp_strjoin_list(" ", styles);
+				g_list_free_full(styles, g_free);
+				style = g_strdup_printf(" style=\"%s\"",
+					combined);
+				g_free(combined);
+			}
+			else
+				style = g_strdup("");
+			rt = g_list_prepend(rt, g_strdup_printf("<span%s>",
+				style));
+			g_free(style);
+
+			if (font_new->b)
+				rt = g_list_prepend(rt, g_strdup("<b>"));
+			if (font_new->i)
+				rt = g_list_prepend(rt, g_strdup("<i>"));
+			if (font_new->u)
+				rt = g_list_prepend(rt, g_strdup("<u>"));
+			if (font_new->s && !GGP_GG11_FORCE_COMPAT)
+				rt = g_list_prepend(rt, g_strdup("<s>"));
+
+			ggp_font_free(font_current);
+			font_current = font_new;
+			font_new = ggp_font_clone(font_current);
+
+			in_any_tag = TRUE;
+		}
+		if (text_before)
+		{
+			rt = g_list_prepend(rt,
+				g_strndup(text_new + pos, m_start - pos));
+		}
+
+		/* set formatting of a following text */
+		if (tag == GGP_HTML_TAG_B)
+		{
+			font_changed |= (font_new->b != !tag_close);
+			font_new->b = !tag_close;
+		}
+		else if (tag == GGP_HTML_TAG_I)
+		{
+			font_changed |= (font_new->i != !tag_close);
+			font_new->i = !tag_close;
+		}
+		else if (tag == GGP_HTML_TAG_U)
+		{
+			font_changed |= (font_new->u != !tag_close);
+			font_new->u = !tag_close;
+		}
+		else if (tag == GGP_HTML_TAG_S)
+		{
+			font_changed |= (font_new->s != !tag_close);
+			font_new->s = !tag_close;
+		}
+		else if (tag == GGP_HTML_TAG_IMG && !tag_close)
+		{
+			GHashTable *attribs = ggp_html_tag_attribs(attribs_str);
+			gchar *val = NULL;
+			uint64_t id;
+			int stored_id = -1;
+			ggp_image_prepare_result res = -1;
+		
+			if ((val = g_hash_table_lookup(attribs, "src")) != NULL
+				&& g_str_has_prefix(val,
+				PURPLE_STORED_IMAGE_PROTOCOL))
+			{
+				val += strlen(PURPLE_STORED_IMAGE_PROTOCOL);
+				if (sscanf(val, "%u", &stored_id) != 1)
+					stored_id = -1;
+			}
+			
+			if (stored_id >= 0)
+				res = ggp_image_prepare(conv, stored_id, &id);
+			
+			if (res == GGP_IMAGE_PREPARE_OK)
+			{
+				pending_objects = g_list_prepend(
+					pending_objects, g_strdup_printf(
+					"<img name=\"" GGP_IMAGE_ID_FORMAT
+					"\">", id));
+			}
+			else if (res == GGP_IMAGE_PREPARE_TOO_BIG)
+			{
+				purple_conversation_write(conv, "",
+					_("Image is too large, please try "
+					"smaller one."), PURPLE_MESSAGE_ERROR,
+					time(NULL));
+			}
+			else
+			{
+				purple_conversation_write(conv, "",
+					_("Image cannot be sent."),
+					PURPLE_MESSAGE_ERROR, time(NULL));
+			}
+			
+			g_hash_table_destroy(attribs);
+		}
+		else if (tag == GGP_HTML_TAG_FONT && !tag_close)
+		{
+			GHashTable *attribs = ggp_html_tag_attribs(attribs_str);
+			gchar *val = NULL;
+
+			font_stack = g_list_prepend(font_stack,
+				ggp_font_clone(font_new));
+
+			if ((val = g_hash_table_lookup(attribs, "size")) != NULL
+				&& val[0] >= '1' && val[0] <= '7' &&
+				val[1] == '\0')
+			{
+				int size = val[0] - '0';
+				font_changed |= (font_new->size != size);
+				font_new->size = size;
+			}
+
+			if ((val = g_hash_table_lookup(attribs, "face"))
+				!= NULL)
+			{
+				font_changed |=
+					(g_strcmp0(font_new->face, val) != 0);
+				g_free(font_new->face);
+				font_new->face = g_strdup(val);
+			}
+
+			if ((val = g_hash_table_lookup(attribs, "color"))
+				!= NULL && val[0] == '#' && strlen(val) == 7)
+			{
+				int color = ggp_html_decode_color(val);
+				font_changed |= (font_new->color != color);
+				font_new->color = color;
+			}
+
+			g_hash_table_destroy(attribs);
+		}
+		else if ((tag == GGP_HTML_TAG_SPAN || tag == GGP_HTML_TAG_DIV)
+			&& !tag_close)
+		{
+			GHashTable *attribs, *styles = NULL;
+			gchar *style = NULL;
+			gchar *val = NULL;
+
+			attribs = ggp_html_tag_attribs(attribs_str);
+
+			font_stack = g_list_prepend(font_stack,
+				ggp_font_clone(font_new));
+			if (tag == GGP_HTML_TAG_DIV)
+				pending_objects = g_list_prepend(
+					pending_objects, g_strdup("<br>"));
+
+			style = g_hash_table_lookup(attribs, "style");
+			if (style)
+				styles = ggp_html_css_attribs(style);
+
+			if ((val = g_hash_table_lookup(styles,
+				"background-color")) != NULL)
+			{
+				int color = ggp_html_decode_color(val);
+				font_changed |= (font_new->bgcolor != color);
+				font_new->bgcolor = color;
+			}
+
+			if ((val = g_hash_table_lookup(styles,
+				"color")) != NULL)
+			{
+				int color = ggp_html_decode_color(val);
+				font_changed |= (font_new->color != color);
+				font_new->color = color;
+			}
+
+			if (styles)
+				g_hash_table_destroy(styles);
+			g_hash_table_destroy(attribs);
+		}
+		else if ((tag == GGP_HTML_TAG_FONT || tag == GGP_HTML_TAG_SPAN
+			|| tag == GGP_HTML_TAG_DIV) && tag_close)
+		{
+			font_changed = TRUE;
+			
+			ggp_font_free(font_new);
+			if (font_stack)
+			{
+				font_new = (ggp_font*)font_stack->data;
+				font_stack = g_list_delete_link(
+					font_stack, font_stack);
+			}
+			else
+				font_new = ggp_font_clone(font_base);
+		}
+		else if (tag == GGP_HTML_TAG_BR)
+		{
+			pending_objects = g_list_prepend(pending_objects,
+				g_strdup("<br>"));
+		}
+		else if (tag == GGP_HTML_TAG_HR)
+		{
+			pending_objects = g_list_prepend(pending_objects,
+				g_strdup("<br><span>---</span><br>"));
+		}
+		else if (tag == GGP_HTML_TAG_A || tag == GGP_HTML_TAG_EOM)
+		{
+			/* do nothing */
+		}
+		else if (tag == GGP_HTML_TAG_UNKNOWN)
+		{
+			purple_debug_warning("gg", "ggp_message_format_to_gg: "
+				"uknown tag %s\n", tag_str);
+		}
+		else
+		{
+			purple_debug_error("gg", "ggp_message_format_to_gg: "
+				"not handled tag %s\n", tag_str);
+		}
+
+		pos = m_end;
+		g_free(tag_str);
+		g_free(attribs_str);
+	}
+	g_match_info_free(match);
+
+	if (pos < strlen(text_new) || in_any_tag)
+	{
+		purple_debug_fatal("gg", "ggp_message_format_to_gg: "
+			"end of message not reached\n");
+	}
+
+	/* releasing fonts recources */
+	ggp_font_free(font_new);
+	ggp_font_free(font_current);
+	ggp_font_free(font_base);
+	g_list_free_full(font_stack, ggp_font_free);
+
+	/* combining reformatted text info one string */
+	rt = g_list_reverse(rt);
+	g_free(text_new);
+	text_new = ggp_strjoin_list("", rt);
+	g_list_free_full(rt, g_free);
+
+	/* TODO: verbose
+	 * purple_debug_info("gg", "reformatted text: [%s]\n", text_new);
+	 */
+
+	return text_new;
+}
+
+int ggp_message_send_im(PurpleConnection *gc, const char *who,
+	const char *message, PurpleMessageFlags flags)
+{
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	PurpleIMConversation *im;
+	ggp_buddy_data *buddy_data;
+	gchar *gg_msg;
+	gboolean succ;
+
+	/* TODO: return -ENOTCONN, if not connected */
+
+	if (message == NULL || message[0] == '\0')
+		return 0;
+
+	buddy_data = ggp_buddy_get_data(purple_blist_find_buddy(
+		purple_connection_get_account(gc), who));
+
+	if (buddy_data->blocked)
+		return -1;
+
+	im = purple_conversations_find_im_with_account(
+		who, purple_connection_get_account(gc));
+
+	gg_msg = ggp_message_format_to_gg(PURPLE_CONVERSATION(im), message);
+
+	/* TODO: splitting messages */
+	if (strlen(gg_msg) > GG_MSG_MAXSIZE)
+	{
+		g_free(gg_msg);
+		return -E2BIG;
+	}
+
+#if GGP_ENABLE_GG11
+	succ = (gg_send_message_html(info->session, GG_CLASS_CHAT,
+		ggp_str_to_uin(who), (unsigned char *)gg_msg) >= 0);
+#else
+	{
+		gchar *plain = purple_markup_strip_html(gg_msg);
+		succ = (gg_send_message(info->session, GG_CLASS_CHAT,
+			ggp_str_to_uin(who), (unsigned char *)plain) >= 0);
+		g_free(plain);
+	}
+#endif
+
+	g_free(gg_msg);
+
+	return succ ? 1 : -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/message-prpl.h	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,22 @@
+#ifndef _GGP_MESSAGE_PRPL_H
+#define _GGP_MESSAGE_PRPL_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct _ggp_message_session_data ggp_message_session_data;
+
+void ggp_message_setup_global(void);
+void ggp_message_cleanup_global(void);
+void ggp_message_setup(PurpleConnection *gc);
+void ggp_message_cleanup(PurpleConnection *gc);
+
+void ggp_message_got(PurpleConnection *gc, const struct gg_event_msg *ev);
+void ggp_message_got_multilogon(PurpleConnection *gc,
+	const struct gg_event_msg *ev);
+
+int ggp_message_send_im(PurpleConnection *gc, const char *who,
+	const char *message, PurpleMessageFlags flags);
+gchar * ggp_message_format_to_gg(PurpleConversation *conv, const gchar *text);
+
+#endif /* _GGP_MESSAGE_PRPL_H */
--- a/libpurple/protocols/gg/multilogon.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/multilogon.c	Mon Sep 16 18:08:19 2013 +0530
@@ -24,7 +24,7 @@
  *
  * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
  */
 
 #include "multilogon.h"
@@ -32,6 +32,8 @@
 #include <debug.h>
 
 #include "gg.h"
+#include "utils.h"
+#include "message-prpl.h"
 
 struct _ggp_multilogon_session_data
 {
@@ -54,7 +56,8 @@
 {
 	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
 	
-	ggp_multilogon_session_data *mldata = g_new0(ggp_multilogon_session_data, 1);
+	ggp_multilogon_session_data *mldata =
+		g_new0(ggp_multilogon_session_data, 1);
 	accdata->multilogon_data = mldata;
 }
 
@@ -64,11 +67,6 @@
 	g_free(mldata);
 }
 
-void ggp_multilogon_msg(PurpleConnection *gc, struct gg_event_msg *msg)
-{
-	ggp_recv_message_handler(gc, msg, TRUE);
-}
-
 void ggp_multilogon_info(PurpleConnection *gc,
 	struct gg_event_multilogon_info *info)
 {
--- a/libpurple/protocols/gg/multilogon.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/multilogon.h	Mon Sep 16 18:08:19 2013 +0530
@@ -38,7 +38,6 @@
 void ggp_multilogon_setup(PurpleConnection *gc);
 void ggp_multilogon_cleanup(PurpleConnection *gc);
 
-void ggp_multilogon_msg(PurpleConnection *gc, struct gg_event_msg *msg);
 void ggp_multilogon_info(PurpleConnection *gc,
 	struct gg_event_multilogon_info *msg);
 
--- a/libpurple/protocols/gg/purplew.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/purplew.c	Mon Sep 16 18:08:19 2013 +0530
@@ -32,22 +32,18 @@
 #include <request.h>
 #include <debug.h>
 
+#include "tcpsocket.h"
+
 guint ggp_purplew_http_input_add(struct gg_http *http_req,
 	PurpleInputFunction func, gpointer user_data)
 {
-	PurpleInputCondition cond = 0;
-	int check = http_req->check;
-
-	if (check & GG_CHECK_READ)
-		cond |= PURPLE_INPUT_READ;
-	if (check & GG_CHECK_WRITE)
-		cond |= PURPLE_INPUT_WRITE;
-
 	//TODO: verbose mode
 	//purple_debug_misc("gg", "ggp_purplew_http_input_add: "
 	//	"[req=%x, fd=%d, cond=%d]\n",
 	//	(unsigned int)http_req, http_req->fd, cond);
-	return purple_input_add(http_req->fd, cond, func, user_data);
+	return purple_input_add(http_req->fd,
+		ggp_tcpsocket_inputcond_gg_to_purple(http_req->check),
+		func, user_data);
 }
 
 static void ggp_purplew_request_processing_cancel(
--- a/libpurple/protocols/gg/roster.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/roster.c	Mon Sep 16 18:08:19 2013 +0530
@@ -478,7 +478,7 @@
 static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc,
 	PurpleXmlNode *node, ggp_roster_content *content, GHashTable *remove_buddies)
 {
-	gchar *alias, *group_name;
+	gchar *alias, *group_name = NULL;
 	uin_t uin;
 	gboolean succ = TRUE;
 	PurpleXmlNode *group_list, *group_elem;
--- a/libpurple/protocols/gg/servconn.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/servconn.c	Mon Sep 16 18:08:19 2013 +0530
@@ -68,6 +68,7 @@
 void ggp_servconn_add_server(const gchar *server)
 {
 	GList *old_entry;
+	gchar *joined;
 	
 	old_entry = g_list_find_custom(global_data.server_history, server,
 		(GCompareFunc)g_strcmp0);
@@ -84,8 +85,9 @@
 		global_data.server_history, GGP_SERVCONN_HISTORY_MAXLEN,
 		g_free);
 
-	purple_prefs_set_string(GGP_SERVCONN_HISTORY_PREF, ggp_strjoin_list(";",
-		global_data.server_history));
+	joined = ggp_strjoin_list(";", global_data.server_history);
+	purple_prefs_set_string(GGP_SERVCONN_HISTORY_PREF, joined);
+	g_free(joined);
 	purple_account_option_string_set_hints(global_data.server_option,
 		ggp_servconn_get_servers());
 }
--- a/libpurple/protocols/gg/status.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/status.c	Mon Sep 16 18:08:19 2013 +0530
@@ -164,6 +164,9 @@
 		case GG_STATUS_NOT_AVAIL:
 		case GG_STATUS_NOT_AVAIL_DESCR:
 		case GG_STATUS_BLOCKED:
+#if GGP_ENABLE_GG11
+		case GG_STATUS_UNKNOWN:
+#endif
 			return purple_primitive_get_id_from_type(
 				PURPLE_STATUS_OFFLINE);
 		case GG_STATUS_FFC:
@@ -186,7 +189,7 @@
 			return purple_primitive_get_id_from_type(
 				PURPLE_STATUS_UNAVAILABLE);
 		default:
-			purple_debug_warning("gg", "ggp_status_to_purplestatus: unknown status %d\n", status);
+			purple_debug_warning("gg", "ggp_status_to_purplestatus: unknown status %#02x\n", status);
 			return purple_primitive_get_id_from_type(
 				PURPLE_STATUS_AVAILABLE);
 	}
@@ -419,7 +422,12 @@
 		return;
 	}
 	ggp_buddy_get_data(buddy)->blocked = (status == GG_STATUS_BLOCKED);
-	
+#if GGP_ENABLE_GG11
+	ggp_buddy_get_data(buddy)->not_a_friend = (status == GG_STATUS_UNKNOWN);
+#else
+	ggp_buddy_get_data(buddy)->not_a_friend = FALSE;
+#endif
+
 	if (descr != NULL)
 	{
 		status_message = g_strdup(descr);
@@ -437,7 +445,7 @@
 			"own status changed to %s [%s]\n",
 			purple_status, status_message ? status_message : "");
 	}
-	else
+	else if (purple_debug_is_verbose())
 	{
 		purple_debug_misc("gg", "ggp_status_got_others_buddy: "
 			"status of %u changed to %s [%s]\n", uin,
@@ -462,15 +470,17 @@
 {
 	ggp_buddy_data *buddy_data = ggp_buddy_get_data(buddy);
 	const gchar *purple_message;
-	
+
 	if (buddy_data->blocked)
 		return g_strdup(_("Blocked"));
-	
+	if (buddy_data->not_a_friend)
+		return g_strdup(_("Not a buddy"));
+
 	purple_message = purple_status_get_attr_string(
 		purple_presence_get_active_status(
 			purple_buddy_get_presence(buddy)), "message");
 	if (!purple_message)
 		return NULL;
-	
+
 	return g_markup_escape_text(purple_message, -1);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/tcpsocket.c	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,113 @@
+#include "tcpsocket.h"
+
+#include "gg.h"
+
+#include "debug.h"
+#include "purple-socket.h"
+
+#if GGP_ENABLE_GG11
+
+static void
+ggp_tcpsocket_connected(PurpleSocket *ps, const gchar *error, gpointer priv_gg)
+{
+	PurpleConnection *gc = purple_socket_get_connection(ps);
+	GGPInfo *info = purple_connection_get_protocol_data(gc);
+	int fd = -1;
+
+	if (error == NULL)
+		fd = purple_socket_get_fd(ps);
+
+	if (!gg_socket_manager_connected(ps, priv_gg, fd)) {
+		purple_debug_error("gg", "socket not handled");
+		purple_socket_destroy(ps);
+		return;
+	}
+
+	if (info->inpa > 0)
+		purple_input_remove(info->inpa);
+	info->inpa = purple_input_add(fd, ggp_tcpsocket_inputcond_gg_to_purple(
+		info->session->check), ggp_async_login_handler, gc);
+}
+
+static void*
+ggp_tcpsocket_connect(void *_gc, const char *host, int port, int is_tls,
+	int is_async, void *priv)
+{
+	PurpleConnection *gc = _gc;
+	PurpleSocket *ps;
+
+	g_return_val_if_fail(host != NULL, NULL);
+	g_return_val_if_fail(is_async, NULL);
+
+	purple_debug_misc("gg", "ggp_tcpsocket_connect(%p, %s:%d, %s, %p)",
+		gc, host, port, is_tls ? "tls" : "tcp", priv);
+
+	ps = purple_socket_new(gc);
+	purple_socket_set_tls(ps, is_tls);
+	purple_socket_set_host(ps, host);
+	purple_socket_set_port(ps, port);
+	if (!purple_socket_connect(ps, ggp_tcpsocket_connected, priv)) {
+		purple_socket_destroy(ps);
+		return NULL;
+	}
+
+	return ps;
+}
+
+static void
+ggp_tcpsocket_close(void *_gc, void *_ps)
+{
+	PurpleSocket *ps = _ps;
+
+	purple_socket_destroy(ps);
+}
+
+static ssize_t
+ggp_tcpsocket_read(void *_gc, void *_ps, unsigned char *buffer, size_t bufsize)
+{
+	PurpleSocket *ps = _ps;
+
+	return purple_socket_read(ps, buffer, bufsize);
+}
+
+static ssize_t
+ggp_tcpsocket_write(void *_gc, void *_ps, const unsigned char *data, size_t len)
+{
+	PurpleSocket *ps = _ps;
+
+	return purple_socket_write(ps, data, len);
+}
+
+void
+ggp_tcpsocket_setup(PurpleConnection *gc, struct gg_login_params *glp)
+{
+	glp->socket_manager_type = purple_ssl_is_supported() ?
+		GG_SOCKET_MANAGER_TYPE_TLS : GG_SOCKET_MANAGER_TYPE_TCP;
+	glp->socket_manager.cb_data = gc;
+	glp->socket_manager.connect = ggp_tcpsocket_connect;
+	glp->socket_manager.close = ggp_tcpsocket_close;
+	glp->socket_manager.read = ggp_tcpsocket_read;
+	glp->socket_manager.write = ggp_tcpsocket_write;
+}
+
+#else
+
+void
+ggp_tcpsocket_setup(PurpleConnection *gc, struct gg_login_params *glp)
+{
+}
+
+#endif
+
+PurpleInputCondition
+ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check)
+{
+	PurpleInputCondition cond = 0;
+
+	if (check & GG_CHECK_READ)
+		cond |= PURPLE_INPUT_READ;
+	if (check & GG_CHECK_WRITE)
+		cond |= PURPLE_INPUT_WRITE;
+
+	return cond;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/tcpsocket.h	Mon Sep 16 18:08:19 2013 +0530
@@ -0,0 +1,13 @@
+#ifndef _GGP_TCPSOCKET_H
+#define _GGP_TCPSOCKET_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+void
+ggp_tcpsocket_setup(PurpleConnection *gc, struct gg_login_params *glp);
+
+PurpleInputCondition
+ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check);
+
+#endif /* _GGP_TCPSOCKET_H */
--- a/libpurple/protocols/gg/utils.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/utils.c	Mon Sep 16 18:08:19 2013 +0530
@@ -59,6 +59,14 @@
 	return buff;
 }
 
+uin_t ggp_get_my_uin(PurpleConnection *gc)
+{
+	g_return_val_if_fail(gc != NULL, 0);
+	
+	return ggp_str_to_uin(purple_account_get_username(
+		purple_connection_get_account(gc)));
+}
+
 static gchar * ggp_convert(const gchar *src, const char *srcenc,
 	const char *dstenc)
 {
@@ -102,16 +110,6 @@
 		"<>\\\\|-]+$", password, 0, 0);
 }
 
-guint64 ggp_microtime(void)
-{
-	// replace with g_get_monotonic_time, when gtk 2.28 will be available
-	GTimeVal time_s;
-	
-	g_get_current_time(&time_s);
-	
-	return ((guint64)time_s.tv_sec << 32) | time_s.tv_usec;
-}
-
 gchar * ggp_utf8_strndup(const gchar *str, gsize n)
 {
 	size_t raw_len = strlen(str);
@@ -192,20 +190,6 @@
 	return joined;
 }
 
-const gchar * ggp_ipv4_to_str(uint32_t raw_ip)
-{
-	static gchar buff[INET_ADDRSTRLEN];
-	buff[0] = '\0';
-	
-	g_snprintf(buff, sizeof(buff), "%d.%d.%d.%d",
-		((raw_ip >>  0) & 0xFF),
-		((raw_ip >>  8) & 0xFF),
-		((raw_ip >> 16) & 0xFF),
-		((raw_ip >> 24) & 0xFF));
-	
-	return buff;
-}
-
 GList * ggp_list_truncate(GList *list, guint length, GDestroyNotify free_func)
 {
 	while (g_list_length(list) > length)
@@ -248,3 +232,35 @@
 		return 0;
 	return g_timeval.tv_sec;
 }
+
+uint64_t * ggp_uint64dup(uint64_t val)
+{
+	uint64_t *ptr = g_new(uint64_t, 1);
+	*ptr = val;
+	return ptr;
+}
+
+gint ggp_int64_compare(gconstpointer _a, gconstpointer _b)
+{
+	const int64_t *ap = _a, *bp = _b;
+	const int64_t a = *ap, b = *bp;
+	if (a == b)
+		return 0;
+	if (a < b)
+		return -1;
+	else
+		return 1;
+}
+
+JsonParser * ggp_json_parse(const gchar *data)
+{
+	JsonParser *parser;
+
+	parser = json_parser_new();
+	if (json_parser_load_from_data(parser, data, -1, NULL))
+		return parser;
+
+	if (purple_debug_is_unsafe())
+		purple_debug_warning("gg", "Invalid JSON: %s\n", data);
+	return NULL;
+}
--- a/libpurple/protocols/gg/utils.h	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/gg/utils.h	Mon Sep 16 18:08:19 2013 +0530
@@ -32,6 +32,7 @@
 
 #include <internal.h>
 #include <libgadu.h>
+#include <json-glib/json-glib.h>
 
 /**
  * Converts stringified UIN to uin_t.
@@ -52,6 +53,14 @@
 const char * ggp_uin_to_str(uin_t uin);
 
 /**
+ * Gets UIN for the account.
+ *
+ * @param gc The connection, in which account is connected.
+ * @return UIN for this account.
+ */
+uin_t ggp_get_my_uin(PurpleConnection *gc);
+
+/**
  * Converts encoding of a given string from UTF-8 to CP1250.
  *
  * @param src Input string.
@@ -73,8 +82,6 @@
 
 gboolean ggp_password_validate(const gchar *password);
 
-guint64 ggp_microtime(void);
-
 gchar * ggp_utf8_strndup(const gchar *str, gsize n);
 
 GSList * ggp_list_copy_to_slist_deep(GList *list, GCopyFunc func,
@@ -85,8 +92,6 @@
 
 gchar * ggp_strjoin_list(const gchar *separator, GList *list);
 
-const gchar * ggp_ipv4_to_str(uint32_t raw_ip);
-
 GList * ggp_list_truncate(GList *list, guint length, GDestroyNotify free_func);
 
 gchar * ggp_free_if_equal(gchar *str, const gchar *pattern);
@@ -95,4 +100,10 @@
 
 time_t ggp_date_from_iso8601(const gchar *str);
 
+uint64_t * ggp_uint64dup(uint64_t val);
+
+gint ggp_int64_compare(gconstpointer a, gconstpointer b);
+
+JsonParser * ggp_json_parse(const gchar *data);
+
 #endif /* _GGP_UTILS_H */
--- a/libpurple/protocols/jabber/auth_cyrus.c	Mon Sep 16 16:48:10 2013 +0530
+++ b/libpurple/protocols/jabber/auth_cyrus.c	Mon Sep 16 18:08:19 2013 +0530
@@ -262,7 +262,7 @@
 					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 							_("Plaintext Authentication"),
 							msg,
-							1, account, NULL, NULL, account,
+							1, purple_request_cpar_from_account(account), account,
 							allow_cyrus_plaintext_auth,
 							disallow_plaintext_auth);
 					g_free(msg);
--- a/valgrind-suppressions	Mon Sep 16 16:48:10 2013 +0530
+++ b/valgrind-suppressions	Mon Sep 16 18:08:19 2013 +0530
@@ -161,6 +161,75 @@
 {
    webkitgtk uninitialized values
    Memcheck:Cond
+   ...
+   obj:/usr/lib/libwebkitgtk-*
+   ...
+}
+{
+   webkitgtk uninitialized values 2
+   Memcheck:Value4
+   ...
    obj:/usr/lib/libwebkitgtk-*
    ...
 }
+{
+   webkitgtk uninitialized values 3
+   Memcheck:Cond
+   ...
+   fun:_ZN3JSC17ConservativeRoots14genericAddSpanINS_13DummyMarkHookEEEvPvS3_RT_
+   ...
+}
+{
+   webkitgtk uninitialized values 4
+   Memcheck:Value4
+   ...
+   fun:_ZN3JSC17ConservativeRoots14genericAddSpanINS_13DummyMarkHookEEEvPvS3_RT_
+   ...
+}
+{
+   webkitgtk uninitialized values 5
+   Memcheck:Value4
+   ...
+   obj:/usr/lib/libjavascriptcoregtk-
+}
+{
+   wcslen_sse2 optimization
+   Memcheck:Addr8
+   fun:__wcslen_sse2
+   ...
+}
+{
+   wcslen_sse2 optimization 2
+   Memcheck:Cond
+   fun:__wcslen_sse2
+   ...
+}
+{
+   idna bug
+   Memcheck:Addr4
+   fun:idna_to_ascii_4z
+   fun:idna_to_ascii_8z
+   ...
+}
+{
+   libcairo uninitialized values
+   Memcheck:Cond
+   obj:*libpixman-1.so.*
+   obj:*libpixman-1.so.*
+   obj:*libpixman-1.so.*
+   obj:*libpixman-1.so.*
+   fun:pixman_image_composite32
+   obj:*libcairo.so.2.*
+   obj:*libcairo.so.2.*
+   obj:*libcairo.so.2.*
+   obj:*libcairo.so.2.*
+   obj:*
+}
+{
+   librsvg uninitialized values
+   Memcheck:Cond
+   obj:*librsvg-2.so.2.*
+   fun:rsvg_handle_get_pixbuf_sub
+   fun:rsvg_handle_get_pixbuf
+   obj:*
+}

mercurial