Merge from trunk

Sun, 30 Sep 2012 21:57:44 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Sun, 30 Sep 2012 21:57:44 +0200
changeset 34376
5fdb91ff33a6
parent 34375
a92d20a93731 (diff)
parent 33414
ae630b0851a9 (current diff)
child 34377
5ba37b370be6

Merge from trunk

--- a/libpurple/protocols/gg/Makefile.am	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/Makefile.am	Sun Sep 30 21:57:44 2012 +0200
@@ -1,5 +1,6 @@
 #V=0
-#GADU_EXTRA_WARNINGS = -Wall -Wextra -Werror
+#CFLAGS = -g -O0
+GADU_EXTRA = -Wall -Wextra -Werror -fno-inline
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
@@ -51,44 +52,50 @@
 
 GGSOURCES = \
 	$(INTGGSOURCES) \
-	utils.h \
-	utils.c \
-	confer.h \
-	confer.c \
-	buddylist.h \
+	account.c \
+	account.h \
+	avatar.c \
+	avatar.h \
 	buddylist.c \
-	gg.h \
+	buddylist.h \
+	chat.c \
+	chat.h \
+	deprecated.c \
+	deprecated.h \
 	gg.c \
-	resolver-purple.h \
-	resolver-purple.c \
-	image.h \
+	gg.h \
+	ggdrive.c \
+	ggdrive.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 \
+	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,9 +125,9 @@
 endif
 
 AM_CPPFLAGS = \
-	$(GADU_EXTRA_WARNINGS) \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(GADU_EXTRA)
--- a/libpurple/protocols/gg/avatar.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/avatar.c	Sun Sep 30 21:57:44 2012 +0200
@@ -152,7 +152,7 @@
 {
 	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);
@@ -317,10 +317,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	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,602 @@
+#include "chat.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "utils.h"
+#include "message-prpl.h"
+
+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;
+	int 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 %llu 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 ||
+		ev->type == GG_EVENT_CHAT_SEND_MSG_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 %llu\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 %llu\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), "%llu", 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 *buff = g_strdup_printf(
+			_("%llu is not a valid room identifier"), id);
+		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 %llu\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 %llu\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 %llu 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/chat.h	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,30 @@
+#ifndef _GGP_CHAT_H
+#define _GGP_CHAT_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct _ggp_chat_session_data ggp_chat_session_data;
+
+void ggp_chat_setup(PurpleConnection *gc);
+void ggp_chat_cleanup(PurpleConnection *gc);
+
+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 /* _GGP_CHAT_H */
--- a/libpurple/protocols/gg/confer.c	Sun Sep 30 21:38:30 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +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) {{{ */
-PurpleConversation *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_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, 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)
-{
-	PurpleConversation *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_conv_chat_add_user(PURPLE_CONV_CHAT(conv), str_uin, NULL,
-						PURPLE_CBFLAGS_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++) {
-			PurpleConversation *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_conv_chat_add_user(PURPLE_CONV_CHAT(conv),
-						str_uin, NULL,
-						PURPLE_CBFLAGS_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	Sun Sep 30 21:38:30 2012 +0200
+++ /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.
- */
-PurpleConversation *
-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: */
--- a/libpurple/protocols/gg/gg.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/gg.c	Sun Sep 30 21:57:44 2012 +0200
@@ -39,7 +39,7 @@
 #include "xmlnode.h"
 
 #include "gg.h"
-#include "confer.h"
+#include "chat.h"
 #include "search.h"
 #include "buddylist.h"
 #include "utils.h"
@@ -52,6 +52,9 @@
 #include "status.h"
 #include "servconn.h"
 #include "pubdir-prpl.h"
+#include "message-prpl.h"
+#include "html.h"
+#include "ggdrive.h"
 
 /* ---------------------------------------------------------------------- */
 
@@ -61,7 +64,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;
 }
@@ -77,6 +80,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
 
@@ -168,72 +195,6 @@
 			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_connection_get_account(gc), NULL, NULL,
-			buddy);
-	g_free(msg);
-}
-
 /* ----- BLOCK BUDDIES -------------------------------------------------- */
 
 static void ggp_add_deny(PurpleConnection *gc, const char *who)
@@ -262,198 +223,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);
-	PurpleConversation *conv;
-	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);
-		PurpleConversation *conv;
-		const gchar *who = ggp_uin_to_str(ev->sender); // not really sender
-		conv = purple_find_conversation_with_account(
-			PURPLE_CONV_TYPE_IM, who, account);
-		if (conv == NULL)
-			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
-		purple_conversation_write(conv, 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);
-		}
-		conv = ggp_confer_find_by_name(gc, chat_name);
-		chat_id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
-
-		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;
 
@@ -481,7 +250,11 @@
 
 	xml = xmlnode_from_str(data, -1);
 	if (xml == NULL)
+	{
+		purple_debug_error("gg", "ggp_xml_event_handler: "
+			"invalid xml: [%s]\n", data);
 		goto out;
+	}
 
 	xmlnode_next_event = xmlnode_get_child(xml, "event");
 	while (xmlnode_next_event != NULL)
@@ -553,8 +326,13 @@
 		case GG_EVENT_NONE:
 			/* Nothing happened. */
 			break;
+		case GG_EVENT_CONN_FAILED:
+			purple_connection_error (gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Server disconnected"));
+			break;
 		case GG_EVENT_MSG:
-			ggp_recv_message_handler(gc, &ev->event.msg, FALSE);
+			ggp_message_got(gc, &ev->event.msg);
 			break;
 		case GG_EVENT_ACK:
 			/* Changing %u to %i fixes compiler warning */
@@ -591,13 +369,33 @@
 			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;
+		case GG_EVENT_IMTOKEN:
+			purple_debug_info("gg", "gg11: got IMTOKEN\n");
+			g_free(info->imtoken);
+			info->imtoken = g_strdup(ev->event.imtoken.imtoken);
+			ggp_ggdrive_test(gc);
+			break;
+		case GG_EVENT_PONG110:
+			purple_debug_info("gg", "gg11: got PONG110 %lu\n", ev->event.pong110.time);
+			break;
+		case GG_EVENT_JSON_EVENT:
+			purple_debug_info("gg", "gg11: got JSON event\n");
+			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:
+		case GG_EVENT_CHAT_SEND_MSG_ACK:
+			ggp_chat_got_event(gc, ev);
+			break;
 		default:
-			purple_debug_error("gg",
+			purple_debug_warning("gg",
 				"unsupported event type=%d\n", ev->type);
 			break;
 	}
@@ -643,6 +441,12 @@
 		case GG_STATE_TLS_NEGOTIATION:
 			purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
 			break;
+		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;
 		default:
 			purple_debug_error("gg", "unknown state = %d\n",
 					 info->session->state);
@@ -829,44 +633,6 @@
 	}
 }
 
-static GList *ggp_blist_node_menu(PurpleBlistNode *node)
-{
-	PurpleMenuAction *act;
-	GList *m = NULL;
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	GGPInfo *info;
-
-	if (!PURPLE_BLIST_NODE_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;
-	struct proto_chat_entry *pce;
-
-	pce = g_new0(struct proto_chat_entry, 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);
@@ -878,23 +644,21 @@
 	if (!ggp_deprecated_setup_proxy(gc))
 		return;
 
+	purple_connection_set_flags(gc, PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC);
+
 	glp = g_new0(struct gg_login_params, 1);
 	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_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);
+
 	glp->uin = ggp_str_to_uin(purple_account_get_username(account));
 	glp->password = ggp_convert_to_cp1250(purple_account_get_password(account));
 
@@ -977,14 +741,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)
 		{
@@ -993,19 +756,17 @@
 			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);
 
 		if (info->inpa > 0)
 			purple_input_remove(info->inpa);
+		g_free(info->imtoken);
 
 		purple_connection_set_protocol_data(gc, NULL);
 		g_free(info);
@@ -1014,132 +775,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_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)
-			{
-				PurpleConversation *conv =
-					purple_find_conversation_with_account(
-						PURPLE_CONV_TYPE_IM, who,
-						purple_connection_get_account(gc));
-				purple_conversation_write(conv, "",
-					_("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, PurpleTypingState state)
 {
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
@@ -1186,94 +821,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;
-	PurpleConversation *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_conv_chat_add_user(PURPLE_CONV_CHAT(conv),
-				purple_account_get_username(account), NULL,
-				PURPLE_CBFLAGS_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)
-{
-	PurpleConversation *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_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(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);
@@ -1380,12 +927,12 @@
 	ggp_status_buddy_text,		/* status_text */
 	ggp_tooltip_text,		/* tooltip_text */
 	ggp_status_types,		/* status_types */
-	ggp_blist_node_menu,		/* blist_node_menu */
+	NULL,				/* blist_node_menu */
 	ggp_chat_info,			/* chat_info */
-	NULL,				/* chat_info_defaults */
+	ggp_chat_info_defaults,		/* chat_info_defaults */
 	ggp_login,			/* login */
 	ggp_close,			/* close */
-	ggp_send_im,			/* send_im */
+	ggp_message_send_im,		/* send_im */
 	NULL,				/* set_info */
 	ggp_send_typing,		/* send_typing */
 	ggp_pubdir_get_info_prpl,	/* get_info */
@@ -1401,11 +948,11 @@
 	NULL,				/* rem_permit */
 	ggp_rem_deny,			/* rem_deny */
 	NULL,				/* set_permit_deny */
-	ggp_join_chat,			/* join_chat */
-	NULL,				/* reject_chat */
-	ggp_get_chat_name,		/* get_chat_name */
-	NULL,				/* chat_invite */
-	NULL,				/* chat_leave */
+	ggp_chat_join,			/* join_chat */
+	NULL, /* TODO */		/* reject_chat */
+	ggp_chat_get_name,		/* get_chat_name */
+	ggp_chat_invite,		/* chat_invite */
+	ggp_chat_leave,			/* chat_leave */
 	NULL,				/* chat_whisper */
 	ggp_chat_send,			/* chat_send */
 	ggp_keepalive,			/* keepalive */
@@ -1422,7 +969,7 @@
 	NULL,				/* get_cb_real_name */
 	NULL,				/* set_chat_topic */
 	NULL,				/* find_blist_chat */
-	NULL,				/* roomlist_get_list */
+	ggp_chat_roomlist_get_list,	/* roomlist_get_list */
 	NULL,				/* roomlist_cancel */
 	NULL,				/* roomlist_expand_category */
 	NULL,				/* can_receive_file */
@@ -1551,13 +1098,18 @@
 
 	ggp_resolver_purple_setup();
 	ggp_servconn_setup(ggp_server_option);
-	
+	ggp_html_setup();
+	ggp_message_setup_global();
+
 	return TRUE;
 }
 
 static gboolean ggp_unload(PurplePlugin *plugin)
 {
 	ggp_servconn_cleanup();
+	ggp_html_cleanup();
+	ggp_message_cleanup_global();
+
 	return TRUE;
 }
 
--- a/libpurple/protocols/gg/gg.h	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/gg.h	Sun Sep 30 21:57:44 2012 +0200
@@ -35,31 +35,25 @@
 #include "roster.h"
 #include "multilogon.h"
 #include "status.h"
-
-#define PUBDIR_RESULTS_MAX 20
+#include "chat.h"
+#include "message-prpl.h"
 
 #define GGP_UIN_LEN_MAX 10
 
-
-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;
 } GGPInfo;
 
 typedef struct
@@ -67,8 +61,10 @@
 	gboolean blocked;
 } ggp_buddy_data;
 
-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);
+
 #endif /* _PURPLE_GG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/ggdrive.c	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,127 @@
+#include "ggdrive.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "libgaduw.h"
+
+#define GGP_GGDRIVE_HOSTNAME "MyComputer"
+#define GGP_GGDRIVE_OS "WINNT x86-msvc"
+#define GGP_GGDRIVE_TYPE "desktop"
+
+#define GGP_GGDRIVE_RESPONSE_MAX 1024
+
+/*******************************************************************************
+ * Authentication
+ ******************************************************************************/
+
+typedef void (*ggp_ggdrive_authentication_cb)(const gchar *security_token,
+	gpointer user_data);
+
+static void ggp_ggdrive_authenticate(PurpleConnection *gc,
+	ggp_ggdrive_authentication_cb cb, gpointer user_data);
+
+static void ggp_ggdrive_authenticate_done(PurpleUtilFetchUrlData *url_data,
+	gpointer _user_data, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+typedef struct
+{
+	PurpleConnection *gc;
+	ggp_ggdrive_authentication_cb cb;
+	gpointer user_data;
+} ggp_ggdrive_authenticate_data;
+
+/******************************************************************************/
+
+static void ggp_ggdrive_authenticate(PurpleConnection *gc,
+	ggp_ggdrive_authentication_cb cb, gpointer user_data)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	const gchar *imtoken;
+	gchar *request, *metadata;
+	ggp_ggdrive_authenticate_data *req_data;
+
+	imtoken = ggp_get_imtoken(gc);
+	if (!imtoken)
+	{
+		cb(NULL, user_data);
+		return;
+	}
+	
+	metadata = g_strdup_printf("{"
+		"\"id\": \"%032x\", "
+		"\"name\": \"" GGP_GGDRIVE_HOSTNAME "\", "
+		"\"os_version\": \"" GGP_GGDRIVE_OS "\", "
+		"\"client_version\": \"%s\", "
+		"\"type\": \"" GGP_GGDRIVE_TYPE "\"}",
+		g_random_int_range(1, 1 << 16),
+		ggp_libgaduw_version(gc));
+	
+	request = g_strdup_printf(
+		"PUT /signin HTTP/1.1\r\n"
+		"Host: drive.mpa.gg.pl\r\n"
+		"Authorization: IMToken %s\r\n"
+		"X-gged-user: gg/pl:%u\r\n"
+		"X-gged-client-metadata: %s\r\n"
+		"X-gged-api-version: 6\r\n"
+		"Content-Type: application/x-www-form-urlencoded\r\n"
+		"Content-Length: 0\r\n"
+		"\r\n",
+		imtoken, ggp_own_uin(gc), metadata);
+
+	req_data = g_new0(ggp_ggdrive_authenticate_data, 1);
+	req_data->gc = gc;
+	req_data->cb = cb;
+	req_data->user_data = user_data;
+
+	purple_util_fetch_url_request(account, "https://drive.mpa.gg.pl/signin",
+		FALSE, NULL, TRUE, request, TRUE, GGP_GGDRIVE_RESPONSE_MAX,
+		ggp_ggdrive_authenticate_done, req_data);
+
+	g_free(metadata);
+	g_free(request);
+}
+
+static void ggp_ggdrive_authenticate_done(PurpleUtilFetchUrlData *url_data,
+	gpointer _user_data, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	ggp_ggdrive_authenticate_data *req_data = _user_data;
+	PurpleConnection *gc = req_data->gc;
+	ggp_ggdrive_authentication_cb cb = req_data->cb;
+	gpointer user_data = req_data->user_data;
+
+	g_free(req_data);
+
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
+	{
+		cb(NULL, user_data);
+		return;
+	}
+
+	if (len == 0)
+	{
+		purple_debug_error("gg", "ggp_ggdrive_authenticate_done: failed\n");
+		cb(NULL, user_data);
+		return;
+	}
+
+	purple_debug_info("gg", "ggp_ggdrive_authenticate_done: [%s]\n", url_text);
+}
+
+/***/
+
+static void ggp_ggdrive_test_cb(const gchar *security_token,
+	gpointer user_data)
+{
+	if (!security_token)
+		purple_debug_error("gg", "ggp_ggdrive_test_cb: didn't got security token\n");
+	else
+		purple_debug_info("gg", "ggp_ggdrive_test_cb: got security token [%s]\n", security_token);
+}
+
+void ggp_ggdrive_test(PurpleConnection *gc)
+{
+	ggp_ggdrive_authenticate(gc, ggp_ggdrive_test_cb, NULL);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/ggdrive.h	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,8 @@
+#ifndef _GGP_GGDRIVE_H
+#define _GGP_GGDRIVE_H
+
+#include <internal.h>
+
+void ggp_ggdrive_test(PurpleConnection *gc);
+
+#endif /* _GGP_GGDRIVE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/html.c	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,160 @@
+#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 == strcmp(tag_str, "eom"))
+		return GGP_HTML_TAG_EOM;
+	if (0 == strcmp(tag_str, "span"))
+		return GGP_HTML_TAG_SPAN;
+	if (0 == strcmp(tag_str, "div"))
+		return GGP_HTML_TAG_DIV;
+	if (0 == strcmp(tag_str, "br"))
+		return GGP_HTML_TAG_BR;
+	if (0 == strcmp(tag_str, "a"))
+		return GGP_HTML_TAG_A;
+	if (0 == strcmp(tag_str, "b"))
+		return GGP_HTML_TAG_B;
+	if (0 == strcmp(tag_str, "i"))
+		return GGP_HTML_TAG_I;
+	if (0 == strcmp(tag_str, "u"))
+		return GGP_HTML_TAG_U;
+	if (0 == strcmp(tag_str, "s"))
+		return GGP_HTML_TAG_S;
+	if (0 == strcmp(tag_str, "img"))
+		return GGP_HTML_TAG_IMG;
+	if (0 == strcmp(tag_str, "font"))
+		return GGP_HTML_TAG_FONT;
+	if (0 == strcmp(tag_str, "hr"))
+		return GGP_HTML_TAG_HR;
+	if (0 == strcmp(tag_str, "a"))
+		return GGP_HTML_TAG_IGNORED;
+	return GGP_HTML_TAG_UNKNOWN;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/html.h	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,33 @@
+#ifndef _GGP_HTML_H
+#define _GGP_HTML_H
+
+#include <internal.h>
+
+typedef enum
+{
+	GGP_HTML_TAG_UNKNOWN,
+	GGP_HTML_TAG_IGNORED,
+	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	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/image.c	Sun Sep 30 21:57:44 2012 +0200
@@ -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=%zu, filename=%s]\n",
-		id, image_crc, image_size, image_filename);
+	purple_debug_info("gg", "ggp_image_prepare: image prepared "
+		"[id=%d, crc=%u, size=%zu]\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,77 +169,59 @@
 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;
 	
 	stored_id = purple_imgstore_add_with_id(
 		g_memdup(image_reply->image, image_reply->size),
 		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=%016llx]\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 %016llx 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",
@@ -249,10 +229,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");
@@ -260,36 +248,90 @@
 	}
 	
 	purple_debug_misc("gg", "ggp_image_send: requested image found "
-		"[id=%d, conv=%s]\n",
-		pending_image->id,
-		pending_image->conv_name);
+		"[id=%016llx, 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("%016llx", 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_find_conversation_with_account(
-		PURPLE_CONV_TYPE_IM, pending_image->conv_name,
+		PURPLE_CONV_TYPE_ANY, 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 %016llx\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 %016llx 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	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/image.h	Sun Sep 30 21:57:44 2012 +0200
@@ -35,11 +35,7 @@
 
 #define GGP_IMAGE_SIZE_MAX 255000
 
-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 +44,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/libgaduw.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/libgaduw.c	Sun Sep 30 21:57:44 2012 +0200
@@ -32,6 +32,7 @@
 #include <debug.h>
 
 #include "purplew.h"
+#include "gg.h"
 
 /*******************************************************************************
  * HTTP requests.
@@ -138,3 +139,13 @@
 	req->h->destroy(req->h);
 	g_free(req);
 }
+
+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;
+}
--- a/libpurple/protocols/gg/libgaduw.h	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/libgaduw.h	Sun Sep 30 21:57:44 2012 +0200
@@ -54,5 +54,7 @@
 	gboolean show_processing);
 void ggp_libgaduw_http_cancel(ggp_libgaduw_http_req *req);
 
+const gchar * ggp_libgaduw_version(PurpleConnection *gc);
+
 
 #endif /* _GGP_LIBGADUW_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/message-prpl.c	Sun Sep 30 21:57:44 2012 +0200
@@ -0,0 +1,788 @@
+#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-%016llx\">"
+#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 PurpleConversation * 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-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 PurpleConversation * ggp_message_get_conv(PurpleConnection *gc,
+	uin_t uin)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleConversation *conv;
+	const gchar *who = ggp_uin_to_str(uin);
+
+	conv = purple_find_conversation_with_account(
+		PURPLE_CONV_TYPE_IM, who, account);
+	if (conv)
+		return conv;
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
+	return conv;
+}
+
+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 %016llx 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 %016llx, 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 (ev->chat_id != 0)
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
+		msg->chat_id = ev->chat_id;
+	}
+	else
+	{
+		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 (ev->chat_id != 0)
+	{
+		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
+		msg->chat_id = ev->chat_id;
+	}
+	else
+	{
+		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);
+	}
+	else if (msg->type == GGP_MESSAGE_GOT_TYPE_CHAT)
+	{
+		ggp_chat_got_message(gc, msg->chat_id, msg->text, msg->time,
+			msg->user);
+	}
+	else if (msg->type == GGP_MESSAGE_GOT_TYPE_MULTILOGON)
+	{
+		PurpleConversation *conv = ggp_message_get_conv(gc, msg->user);
+		const gchar *me = purple_account_get_username(
+			purple_connection_get_account(gc));
+
+		purple_conversation_write(conv, 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, "%llx", &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 %016llx 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;
+	int 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 = (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=\"%016llx\">", 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);
+	PurpleConversation *conv;
+	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_find_buddy(
+		purple_connection_get_account(gc), who));
+
+	if (buddy_data->blocked)
+		return -1;
+
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+		who, purple_connection_get_account(gc));
+
+	gg_msg = ggp_message_format_to_gg(conv, message);
+
+	/* TODO: splitting messages */
+	if (strlen(gg_msg) > GG_MSG_MAXSIZE)
+	{
+		g_free(gg_msg);
+		return -E2BIG;
+	}
+
+	succ = (gg_send_message_html(info->session, GG_CLASS_CHAT,
+		ggp_str_to_uin(who), (unsigned char *)gg_msg) >= 0);
+
+	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	Sun Sep 30 21:57:44 2012 +0200
@@ -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	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/multilogon.c	Sun Sep 30 21:57:44 2012 +0200
@@ -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	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/multilogon.h	Sun Sep 30 21:57:44 2012 +0200
@@ -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/resolver-purple.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/resolver-purple.c	Sun Sep 30 21:57:44 2012 +0200
@@ -68,7 +68,7 @@
 	const char *error_message)
 {
 	ggp_resolver_purple_data *data = (ggp_resolver_purple_data*)cbdata;
-	const int fd = data->pipes[1];
+	const int fd = data->pipes[1]; /* TODO: invalid read after logoff */
 	int ipv4_count, all_count, write_size;
 	struct in_addr *addresses;
 	
--- a/libpurple/protocols/gg/servconn.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/servconn.c	Sun Sep 30 21:57:44 2012 +0200
@@ -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/utils.c	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/utils.c	Sun Sep 30 21:57:44 2012 +0200
@@ -232,7 +232,7 @@
 	GDate g_date;
 	static gchar buff[30];
 	
-	g_date_set_time(&g_date, date);
+	g_date_set_time_t(&g_date, date);
 	if (0 == g_date_strftime(buff, sizeof(buff), format, &g_date))
 		return NULL;
 	return buff;
@@ -248,3 +248,22 @@
 		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;
+}
--- a/libpurple/protocols/gg/utils.h	Sun Sep 30 21:38:30 2012 +0200
+++ b/libpurple/protocols/gg/utils.h	Sun Sep 30 21:57:44 2012 +0200
@@ -95,4 +95,8 @@
 
 time_t ggp_date_from_iso8601(const gchar *str);
 
+uint64_t * ggp_uint64dup(uint64_t val);
+
+gint ggp_int64_compare(gconstpointer a, gconstpointer b);
+
 #endif /* _GGP_UTILS_H */

mercurial