Merge hinted string account options from default soc.2012.gg

Tue, 14 Aug 2012 22:26:33 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Tue, 14 Aug 2012 22:26:33 +0200
branch
soc.2012.gg
changeset 33338
8966c9ad5be9
parent 33337
458d578d553d (diff)
parent 33267
c3ee96290bfd (current diff)
child 33339
02ee2834030c

Merge hinted string account options from default

--- a/configure.ac	Tue Aug 14 22:05:05 2012 +0200
+++ b/configure.ac	Tue Aug 14 22:26:33 2012 +0200
@@ -816,6 +816,19 @@
 AC_SUBST(LIBXML_LIBS)
 
 dnl #######################################################################
+dnl # Check for zlib (required)
+dnl #######################################################################
+
+PKG_CHECK_MODULES(ZLIB, [zlib >= 1.2.0], , [
+	AC_MSG_RESULT(no)
+	AC_MSG_ERROR([
+You must have zlib >= 1.2.0 development headers installed to build.
+])])
+
+AC_SUBST(ZLIB_CFLAGS)
+AC_SUBST(ZLIB_LIBS)
+
+dnl #######################################################################
 dnl # GConf schemas
 dnl #######################################################################
 AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
@@ -1069,13 +1082,14 @@
 AC_ARG_WITH(gadu-libs, [AC_HELP_STRING([--with-gadu-libs=DIR], [compile the Gadu-Gadu plugin against the libs in DIR])], [ac_gadu_libs="$withval"], [ac_gadu_libs="no"])
 GADU_CFLAGS=""
 GADU_LIBS=""
+GADU_LIBGADU_VERSION=1.11.2
 if test -n "$with_gadu_includes" || test -n "$with_gadu_libs"; then
 	gadu_manual_check="yes"
 else
 	gadu_manual_check="no"
 fi
 if test "x$gadu_manual_check" = "xno"; then
-	PKG_CHECK_MODULES(GADU, [libgadu >= 1.11.0], [
+	PKG_CHECK_MODULES(GADU, [libgadu >= $GADU_LIBGADU_VERSION], [
 		gadu_includes="yes"
 		gadu_libs="yes"
 	], [
@@ -1107,28 +1121,7 @@
 #error "libgadu is not compatible with the GPL when compiled with OpenSSL support."
 #endif
 	]])], [
-		AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <libgadu.h>]], [[
-#if GG_DEFAULT_PROTOCOL_VERSION < 0x2e
-#error "Your libgadu version is too old. libpurple requires 1.11.0 or higher."
-#endif
-		]])], [
-			AC_MSG_RESULT(yes)
-			AC_DEFINE([HAVE_LIBGADU], [1],
-				[Define to 1 if you have libgadu.])
-		], [
-			AC_MSG_RESULT(no)
-			echo
-			echo
-			echo "Your supplied copy of libgadu is too old."
-			echo "Install version 1.11.0 or newer."
-			echo "Then rerun this ./configure"
-			echo
-			echo "Falling back to using our own copy of libgadu"
-			echo
-			GADU_LIBS=""
-			GADU_CFLAGS=""
-			gadu_libs=no
-		])
+		AC_MSG_RESULT(yes)
 	], [
 		AC_MSG_RESULT(no)
 		echo
@@ -1147,6 +1140,35 @@
 	CPPFLAGS="$CPPFLAGS_save"
 fi
 
+if test "x$gadu_libs" = "xyes" -a "x$gadu_manual_check" = "xyes"; then
+	AC_MSG_CHECKING(for supplied libgadu compatibility)
+	CPPFLAGS_save="$CPPFLAGS"
+	CPPFLAGS="$CPPFLAGS $GADU_CFLAGS"
+
+	AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <libgadu.h>]], [[
+#if GG_DEFAULT_PROTOCOL_VERSION < 0x2e
+#error "Your libgadu version is too old."
+#endif
+	]])], [
+		AC_MSG_RESULT(yes)
+	], [
+		AC_MSG_RESULT(no)
+		echo
+		echo "Your supplied copy of libgadu is too old."
+		echo "Install version $GADU_LIBGADU_VERSION or newer."
+		echo "Then rerun this ./configure"
+		echo
+		echo "Falling back to using our own copy of libgadu"
+		echo
+		GADU_LIBS=""
+		GADU_CFLAGS=""
+		gadu_libs=no
+	])
+
+	CPPFLAGS="$CPPFLAGS_save"
+fi
+
+AM_CONDITIONAL(HAVE_LIBGADU, test "x$gadu_libs" = "xyes")
 AM_CONDITIONAL(USE_INTERNAL_LIBGADU, test "x$gadu_libs" != "xyes")
 
 if test "x$gadu_libs" = "x"; then
@@ -1155,6 +1177,7 @@
 
 AC_SUBST(GADU_LIBS)
 AC_SUBST(GADU_CFLAGS)
+AC_SUBST(GADU_LIBGADU_VERSION)
 
 AC_ARG_ENABLE(distrib,,,enable_distrib=no)
 AM_CONDITIONAL(DISTRIB, test "x$enable_distrib" = "xyes")
--- a/libpurple/eventloop.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/eventloop.c	Tue Aug 14 22:26:33 2012 +0200
@@ -91,6 +91,16 @@
 	}
 }
 
+int
+purple_input_pipe(int pipefd[2])
+{
+#ifdef _WIN32
+	return wpurple_input_pipe(pipefd);
+#else
+	return pipe(pipefd);
+#endif
+}
+
 void
 purple_eventloop_set_ui_ops(PurpleEventLoopUiOps *ops)
 {
--- a/libpurple/eventloop.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/eventloop.h	Tue Aug 14 22:26:33 2012 +0200
@@ -240,6 +240,24 @@
 int
 purple_input_get_error(int fd, int *error);
 
+/**
+ * Creates a pipe - an unidirectional data channel that can be used for
+ * interprocess communication.
+ *
+ * File descriptors for both ends of pipe will be written into provided array.
+ * The first one (pipefd[0]) can be used for reading, the second one (pipefd[1])
+ * for writing.
+ *
+ * On Windows it's simulated by creating a pair of connected sockets, on other
+ * systems pipe() is used.
+ *
+ * @param pipefd Array used to return file descriptors for both ends of pipe.
+ *
+ * @return @c 0 on success, @c -1 on error.
+ */
+int
+purple_input_pipe(int pipefd[2]);
+
 
 /*@}*/
 
--- a/libpurple/protocols/gg/Makefile.am	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/Makefile.am	Tue Aug 14 22:26:33 2012 +0200
@@ -1,13 +1,20 @@
+V=0
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
 EXTRA_DIST = \
 	Makefile.mingw \
-	win32-resolver.c \
-	win32-resolver.h \
+	lib/COPYING
+
+if USE_INTERNAL_LIBGADU
+INTGGSOURCES = \
 	lib/common.c \
 	lib/compat.h \
-	lib/COPYING \
+	lib/config.h \
 	lib/dcc.c \
 	lib/dcc7.c \
 	lib/debug.c \
+	lib/debug.h \
 	lib/deflate.c \
 	lib/deflate.h \
 	lib/encoding.c \
@@ -15,11 +22,9 @@
 	lib/events.c \
 	lib/handlers.c \
 	lib/http.c \
-	lib/libgadu.h \
+	lib/internal.h \
 	lib/libgadu.c \
-	lib/libgadu-config.h \
-	lib/libgadu-debug.h \
-	lib/libgadu-internal.h \
+	lib/libgadu.h \
 	lib/message.c \
 	lib/message.h \
 	lib/obsolete.c \
@@ -31,38 +36,11 @@
 	lib/session.h \
 	lib/sha1.c
 
-pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
-
-if USE_INTERNAL_LIBGADU
-INTGGSOURCES = \
-	lib/common.c \
-	lib/compat.h \
-	lib/dcc.c \
-	lib/dcc7.c \
-	lib/debug.c \
-	lib/deflate.c \
-	lib/deflate.h \
-	lib/encoding.c \
-	lib/encoding.h \
-	lib/events.c \
-	lib/handlers.c \
-	lib/http.c \
-	lib/libgadu.h \
-	lib/libgadu.c \
-	lib/libgadu-config.h \
-	lib/libgadu-internal.h \
-	lib/message.c \
-	lib/message.h \
-	lib/obsolete.c \
-	lib/protocol.h \
-	lib/pubdir.c \
-	lib/pubdir50.c \
-	lib/resolver.c \
-	lib/resolver.h \
-	lib/session.h \
-	lib/sha1.c
-
-INTGG_CFLAGS = -I$(top_srcdir)/libpurple/protocols/gg/lib -DGG_IGNORE_DEPRECATED -DUSE_INTERNAL_LIBGADU
+INTGG_CFLAGS = -I$(top_srcdir)/libpurple/protocols/gg/lib \
+	$(ZLIB_CFLAGS) \
+	-DGG_IGNORE_DEPRECATED \
+	-DGG_INTERNAL_LIBGADU_VERSION=$(GADU_LIBGADU_VERSION)
+GADU_LIBS += $(ZLIB_LIBS)
 endif
 
 if USE_GNUTLS
@@ -72,8 +50,8 @@
 
 GGSOURCES = \
 	$(INTGGSOURCES) \
-	gg-utils.h \
-	gg-utils.c \
+	utils.h \
+	utils.c \
 	confer.h \
 	confer.c \
 	search.h \
@@ -81,7 +59,39 @@
 	buddylist.h \
 	buddylist.c \
 	gg.h \
-	gg.c
+	gg.c \
+	resolver-purple.h \
+	resolver-purple.c \
+	image.h \
+	image.c \
+	account.h \
+	account.c \
+	deprecated.h \
+	deprecated.c \
+	purplew.h \
+	purplew.c \
+	libgaduw.h \
+	libgaduw.c \
+	avatar.h \
+	avatar.c \
+	libgadu-events.h \
+	libgadu-events.c \
+	roster.c \
+	roster.h \
+	validator.c \
+	validator.h \
+	xml.c \
+	xml.h \
+	multilogon.c \
+	multilogon.h \
+	status.c \
+	status.h \
+	oauth/oauth.c \
+	oauth/oauth.h \
+	oauth/oauth-parameter.c \
+	oauth/oauth-parameter.h \
+	oauth/oauth-purple.c \
+	oauth/oauth-purple.h
 
 AM_CFLAGS = $(st)
 
@@ -105,9 +115,9 @@
 endif
 
 AM_CPPFLAGS = \
+	-Wall -Wextra -Werror \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS)
-
--- a/libpurple/protocols/gg/Makefile.mingw	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/Makefile.mingw	Tue Aug 14 22:26:33 2012 +0200
@@ -24,14 +24,14 @@
 ##
 ## INCLUDE PATHS
 ##
-INCLUDE_PATHS +=	-I. \
+INCLUDE_PATHS +=\
+			-I$(PIDGIN_TREE_TOP) \
+			-I$(PURPLE_TOP) \
+			-I$(PURPLE_TOP)/win32 \
 			-I./lib \
 			-I$(GTK_TOP)/include \
 			-I$(GTK_TOP)/include/glib-2.0 \
-			-I$(GTK_TOP)/lib/glib-2.0/include \
-			-I$(PURPLE_TOP) \
-			-I$(PURPLE_TOP)/win32 \
-			-I$(PIDGIN_TREE_TOP)
+			-I$(GTK_TOP)/lib/glib-2.0/include
 
 LIB_PATHS +=		-L$(GTK_TOP)/lib \
 			-L$(PURPLE_TOP) \
@@ -60,8 +60,8 @@
 	confer.c \
 	gg.c \
 	search.c \
-	gg-utils.c \
-	win32-resolver.c
+	utils.c \
+	resolver-purple.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/account.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,620 @@
+#include "account.h"
+
+#include <libgadu.h>
+#include <debug.h>
+
+#include "deprecated.h"
+#include "purplew.h"
+#include "utils.h"
+#include "libgaduw.h"
+#include "validator.h"
+
+/*******************************************************************************
+ * Token requesting.
+ ******************************************************************************/
+
+typedef struct
+{
+	ggp_account_token_cb callback;
+	PurpleConnection *gc;
+	void *user_data;
+} ggp_account_token_reqdata;
+
+static void ggp_account_token_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _reqdata);
+
+/******************************************************************************/
+
+void ggp_account_token_free(ggp_account_token *token)
+{
+	if (!token)
+		return;
+	g_free(token->id);
+	g_free(token->data);
+	g_free(token);
+}
+
+void ggp_account_token_request(PurpleConnection *gc,
+	ggp_account_token_cb callback, void *user_data)
+{
+	struct gg_http *h;
+	ggp_account_token_reqdata *reqdata;
+
+	purple_debug_info("gg", "ggp_account_token_request: "
+		"requesting token...\n");
+
+	if (!ggp_deprecated_setup_proxy(gc))
+	{
+		callback(gc, NULL, user_data);
+		return;
+	}
+	
+	h = gg_token(TRUE);
+	
+	if (!h)
+	{
+		callback(gc, NULL, user_data);
+		return;
+	}
+	
+	reqdata = g_new(ggp_account_token_reqdata, 1);
+	reqdata->callback = callback;
+	reqdata->gc = gc;
+	reqdata->user_data = user_data;
+	ggp_libgaduw_http_watch(gc, h, ggp_account_token_response, reqdata,
+		TRUE);
+}
+
+static void ggp_account_token_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _reqdata)
+{
+	ggp_account_token_reqdata *reqdata = _reqdata;
+	struct gg_token *token_info;
+	ggp_account_token *token = NULL;
+	
+	g_assert(!(success && cancelled));
+	
+	if (cancelled)
+		purple_debug_info("gg", "ggp_account_token_handler: "
+			"cancelled\n");
+	else if (success)
+	{
+		purple_debug_info("gg", "ggp_account_token_handler: "
+			"got token\n");
+	
+		token = g_new(ggp_account_token, 1);
+	
+		token_info = h->data;
+		token->id = g_strdup(token_info->tokenid);
+		token->size = h->body_size;
+		token->data = g_memdup(h->body, token->size);
+		token->length = token_info->length;
+	}
+	else
+	{
+		purple_debug_error("gg", "ggp_account_token_handler: error\n");
+		purple_notify_error(
+			purple_connection_get_account(reqdata->gc),
+			_("Token Error"),
+			_("Unable to fetch the token."), NULL);
+	}
+	
+	reqdata->callback(reqdata->gc, token, reqdata->user_data);
+	g_free(reqdata);
+}
+
+gboolean ggp_account_token_validate(ggp_account_token *token,
+	const gchar *value)
+{
+	if (strlen(value) != token->length)
+		return FALSE;
+	return g_regex_match_simple("^[a-zA-Z0-9]+$", value, 0, 0);
+}
+
+/*******************************************************************************
+ * New account registration.
+ ******************************************************************************/
+
+typedef struct
+{
+	ggp_account_token *token;
+	PurpleConnection *gc;
+	
+	gchar *email;
+	gchar *password;
+	gchar *token_value;
+	gboolean password_remember;
+} ggp_account_register_data;
+
+static void ggp_account_register_dialog(PurpleConnection *gc,
+	ggp_account_token *token, gpointer _register_data);
+static void ggp_account_register_dialog_ok(
+	ggp_account_register_data *register_data, PurpleRequestFields *fields);
+#if 0
+static void ggp_account_register_dialog_invalid(
+	ggp_account_register_data *register_data, const gchar *message);
+#endif
+static void ggp_account_register_dialog_cancel(
+	ggp_account_register_data *register_data, PurpleRequestFields *fields);
+static void ggp_account_register_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _reqdata);
+static void ggp_account_register_completed(
+	ggp_account_register_data *register_data, gboolean success);
+
+#define GGP_ACCOUNT_REGISTER_TITLE _("Register New Gadu-Gadu Account")
+
+/******************************************************************************/
+
+void ggp_account_register(PurpleAccount *account)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	ggp_account_register_data *register_data;
+	
+	purple_debug_info("gg", "ggp_account_register\n");
+	
+	register_data = g_new0(ggp_account_register_data, 1);
+	register_data->gc = gc;
+	register_data->password_remember = TRUE;
+	
+	ggp_account_token_request(gc, ggp_account_register_dialog,
+		register_data);
+}
+
+static void ggp_account_register_dialog(PurpleConnection *gc,
+	ggp_account_token *token, gpointer _register_data)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *main_group, *password_group, *token_group;
+	PurpleRequestField *field, *field_password;
+	ggp_account_register_data *register_data = _register_data;
+	
+	purple_debug_info("gg", "ggp_account_register_dialog(%x, %x, %x)\n",
+		(unsigned int)gc, (unsigned int)token,
+		(unsigned int)_register_data);
+	if (!token)
+	{
+		ggp_account_register_completed(register_data, FALSE);
+		return;
+	}
+	
+	fields = purple_request_fields_new();
+	main_group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, main_group);
+	
+	field = purple_request_field_string_new("email", _("Email"),
+		register_data->email, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_set_validator(field,
+		purple_request_field_email_validator, NULL);
+	purple_request_field_group_add_field(main_group, field);
+
+	password_group = purple_request_field_group_new(_("Password"));
+	purple_request_fields_add_group(fields, password_group);
+
+	field = purple_request_field_string_new("password1", _("Password"),
+		register_data->password, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_password, NULL);
+	purple_request_field_group_add_field(password_group, field);
+	field_password = field;
+	
+	field = purple_request_field_string_new("password2",
+		_("Password (again)"), register_data->password, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_password_equal,
+		field_password);
+	purple_request_field_group_add_field(password_group, field);
+	
+	field = purple_request_field_bool_new("password_remember",
+		_("Remember password"), register_data->password_remember);
+	purple_request_field_group_add_field(password_group, field);
+	
+	token_group = purple_request_field_group_new(_("Captcha"));
+	purple_request_fields_add_group(fields, token_group);
+	
+	field = purple_request_field_string_new("token_value",
+		_("Enter text from image below"), register_data->token_value,
+		FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_token, token);
+	purple_request_field_group_add_field(token_group, field);
+	purple_debug_info("gg", "token set %p\n", register_data->token);
+	
+	field = purple_request_field_image_new("token_image", _("Captcha"),
+		token->data, token->size);
+	purple_request_field_group_add_field(token_group, field);
+
+	register_data->token = token;
+	
+	purple_request_fields(gc,
+		GGP_ACCOUNT_REGISTER_TITLE,
+		GGP_ACCOUNT_REGISTER_TITLE,
+		_("Please, fill in the following fields"), fields,
+		_("OK"), G_CALLBACK(ggp_account_register_dialog_ok),
+		_("Cancel"), G_CALLBACK(ggp_account_register_dialog_cancel),
+		purple_connection_get_account(gc), NULL, NULL, register_data);
+}
+
+static void ggp_account_register_dialog_cancel(
+	ggp_account_register_data *register_data, PurpleRequestFields *fields)
+{
+	purple_debug_info("gg", "ggp_account_register_dialog_cancel(%x, %x)\n",
+		(unsigned int)register_data, (unsigned int)fields);
+
+	ggp_account_register_completed(register_data, FALSE);
+}
+
+static void ggp_account_register_dialog_ok(
+	ggp_account_register_data *register_data, PurpleRequestFields *fields)
+{
+	struct gg_http *h;
+
+	purple_debug_misc("gg", "ggp_account_register_dialog_ok(%x, %x)\n",
+		(unsigned int)register_data, (unsigned int)fields);
+
+	g_free(register_data->email);
+	g_free(register_data->password);
+	g_free(register_data->token_value);
+	
+	register_data->email = g_strdup(
+		purple_request_fields_get_string(fields, "email"));
+	register_data->password = g_strdup(
+		purple_request_fields_get_string(fields, "password1"));
+	register_data->password_remember =
+		purple_request_fields_get_bool(fields, "password_remember");
+	register_data->token_value = g_strdup(
+		purple_request_fields_get_string(fields, "token_value"));
+
+	g_assert(register_data->email != NULL);
+	g_assert(register_data->password != NULL);
+	g_assert(register_data->token_value != NULL);
+
+	h = gg_register3(register_data->email, register_data->password,
+		register_data->token->id, register_data->token_value, TRUE);
+	
+	ggp_libgaduw_http_watch(register_data->gc, h,
+		ggp_account_register_response, register_data, TRUE);
+}
+
+#if 0
+// libgadu 1.12.x: use it for invalid token
+static void ggp_account_register_dialog_invalid(
+	ggp_account_register_data *register_data, const gchar *message)
+{
+	purple_debug_warning("gg", "ggp_account_register_dialog_invalid: %s\n",
+		message);
+	ggp_account_register_dialog(register_data->gc, register_data->token,
+		register_data);
+	purple_notify_error(purple_connection_get_account(register_data->gc),
+		GGP_ACCOUNT_REGISTER_TITLE, message, NULL);
+}
+#endif
+
+static void ggp_account_register_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _register_data)
+{
+	ggp_account_register_data *register_data = _register_data;
+	PurpleAccount *account =
+		purple_connection_get_account(register_data->gc);
+	struct gg_pubdir *register_result = h->data;
+	uin_t uin;
+	gchar *tmp;
+	
+	g_assert(!(success && cancelled));
+	
+	if (cancelled)
+	{
+		purple_debug_info("gg", "ggp_account_register_response: "
+			"cancelled\n");
+		ggp_account_register_completed(register_data, FALSE);
+		return;
+	}
+	if (!success || !register_result->success)
+	{
+		//TODO (libgadu 1.12.x): check register_result->error
+		purple_debug_error("gg", "ggp_account_register_response: "
+			"error\n");
+		purple_notify_error(NULL,
+			GGP_ACCOUNT_REGISTER_TITLE,
+			_("Unable to register new account. "
+			"An unknown error occurred."), NULL);
+		ggp_account_register_completed(register_data, FALSE);
+		return;
+	}
+
+	uin = register_result->uin;
+	purple_debug_info("gg", "ggp_account_register_response: "
+		"registered uin %u\n", uin);
+	
+	purple_account_set_username(account, ggp_uin_to_str(uin));
+	purple_account_set_remember_password(account,
+		register_data->password_remember);
+	purple_account_set_password(account, register_data->password);
+	
+	tmp = g_strdup_printf(_("Your new GG number: %u."), uin);
+	purple_notify_info(account, GGP_ACCOUNT_REGISTER_TITLE,
+		_("Registration completed successfully!"), tmp);
+	g_free(tmp);
+	
+	ggp_account_register_completed(register_data, TRUE);
+}
+
+static void ggp_account_register_completed(
+	ggp_account_register_data *register_data, gboolean success)
+{
+	PurpleAccount *account =
+		purple_connection_get_account(register_data->gc);
+
+	purple_debug_misc("gg", "ggp_account_register_completed: %d\n",
+		success);
+	
+	g_free(register_data->email);
+	g_free(register_data->password);
+	g_free(register_data->token_value);
+	ggp_account_token_free(register_data->token);
+	g_free(register_data);
+	
+	purple_account_disconnect(account);
+	purple_account_register_completed(account, success);
+}
+
+/*******************************************************************************
+ * Password change.
+ ******************************************************************************/
+
+typedef struct
+{
+	ggp_account_token *token;
+	PurpleConnection *gc;
+	
+	gchar *email;
+	gchar *password_current;
+	gchar *password_new;
+	gchar *token_value;
+} ggp_account_chpass_data;
+
+static void ggp_account_chpass_data_free(ggp_account_chpass_data *chpass_data);
+static void ggp_account_chpass_dialog(PurpleConnection *gc,
+	ggp_account_token *token, gpointer _chpass_data);
+static void ggp_account_chpass_dialog_ok(
+	ggp_account_chpass_data *chpass_data, PurpleRequestFields *fields);
+static void ggp_account_chpass_dialog_invalid(
+	ggp_account_chpass_data *chpass_data, const gchar *message);
+static void ggp_account_chpass_dialog_cancel(
+	ggp_account_chpass_data *chpass_data, PurpleRequestFields *fields);
+static void ggp_account_chpass_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _chpass_data);
+
+#define GGP_ACCOUNT_CHPASS_TITLE _("Password change")
+
+/******************************************************************************/
+
+static void ggp_account_chpass_data_free(ggp_account_chpass_data *chpass_data)
+{
+	g_free(chpass_data->email);
+	g_free(chpass_data->password_current);
+	g_free(chpass_data->password_new);
+	g_free(chpass_data->token_value);
+	ggp_account_token_free(chpass_data->token);
+	g_free(chpass_data);
+}
+
+void ggp_account_chpass(PurpleConnection *gc)
+{
+	ggp_account_chpass_data *chpass_data;
+	void ggp_account_change_passwd(PurpleConnection *gc);
+	purple_debug_info("gg", "ggp_account_chpass\n");
+	
+	chpass_data = g_new0(ggp_account_chpass_data, 1);
+	chpass_data->gc = gc;
+	
+	ggp_account_token_request(gc, ggp_account_chpass_dialog, chpass_data);
+}
+
+static void ggp_account_chpass_dialog(PurpleConnection *gc,
+	ggp_account_token *token, gpointer _chpass_data)
+{
+	ggp_account_chpass_data *chpass_data = _chpass_data;
+	PurpleAccount *account = purple_connection_get_account(chpass_data->gc);
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *main_group, *password_group, *token_group;
+	PurpleRequestField *field, *field_password;
+	gchar *primary;
+	
+	purple_debug_info("gg", "ggp_account_chpass_dialog(%p, %p, %p)\n",
+		gc, token, _chpass_data);
+	if (!token)
+	{
+		ggp_account_chpass_data_free(chpass_data);
+		return;
+	}
+	
+	fields = purple_request_fields_new();
+	main_group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, main_group);
+	
+	field = purple_request_field_string_new("email",
+		_("New email address"), chpass_data->email, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_set_validator(field,
+		purple_request_field_email_validator, NULL);
+	purple_request_field_group_add_field(main_group, field);
+
+	password_group = purple_request_field_group_new(_("Password"));
+	purple_request_fields_add_group(fields, password_group);
+
+	field = purple_request_field_string_new("password_current",
+		_("Current password"), chpass_data->password_current, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_group_add_field(password_group, field);
+	
+	field = purple_request_field_string_new("password_new1",
+		_("Password"), chpass_data->password_new, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_password, NULL);
+	purple_request_field_group_add_field(password_group, field);
+	field_password = field;
+	
+	field = purple_request_field_string_new("password_new2",
+		_("Password (retype)"), chpass_data->password_new, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_password_equal,
+		field_password);
+	purple_request_field_group_add_field(password_group, field);
+	
+	token_group = purple_request_field_group_new(_("Captcha"));
+	purple_request_fields_add_group(fields, token_group);
+	
+	field = purple_request_field_string_new("token_value",
+		_("Enter text from image below"), chpass_data->token_value,
+		FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_set_validator(field, ggp_validator_token, token);
+	purple_request_field_group_add_field(token_group, field);
+	
+	field = purple_request_field_image_new("token_image", _("Captcha"),
+		token->data, token->size);
+	purple_request_field_group_add_field(token_group, field);
+	
+	chpass_data->token = token;
+	
+	primary = g_strdup_printf(_("Change password for %s"),
+		purple_account_get_username(account));
+	
+	purple_request_fields(gc, GGP_ACCOUNT_CHPASS_TITLE, primary,
+		_("Please enter your current password and your new password."),
+		fields,
+		_("OK"), G_CALLBACK(ggp_account_chpass_dialog_ok),
+		_("Cancel"), G_CALLBACK(ggp_account_chpass_dialog_cancel),
+		account, NULL, NULL, chpass_data);
+	
+	g_free(primary);
+}
+
+static void ggp_account_chpass_dialog_ok(
+	ggp_account_chpass_data *chpass_data, PurpleRequestFields *fields)
+{
+	PurpleAccount *account = purple_connection_get_account(chpass_data->gc);
+	struct gg_http *h;
+	uin_t uin;
+
+	purple_debug_misc("gg", "ggp_account_chpass_dialog_ok(%p, %p)\n",
+		chpass_data, fields);
+
+	g_free(chpass_data->email);
+	g_free(chpass_data->password_current);
+	g_free(chpass_data->password_new);
+	g_free(chpass_data->token_value);
+	
+	chpass_data->email = g_strdup(
+		purple_request_fields_get_string(fields, "email"));
+	chpass_data->password_current = g_strdup(
+		purple_request_fields_get_string(fields, "password_current"));
+	chpass_data->password_new = g_strdup(
+		purple_request_fields_get_string(fields, "password_new1"));
+	chpass_data->token_value = g_strdup(
+		purple_request_fields_get_string(fields, "token_value"));
+
+	g_assert(chpass_data->email != NULL);
+	g_assert(chpass_data->password_current != NULL);
+	g_assert(chpass_data->password_new != NULL);
+	g_assert(chpass_data->token_value != NULL);
+
+	if (g_utf8_collate(chpass_data->password_current,
+		purple_account_get_password(account)) != 0)
+	{
+		g_free(chpass_data->password_current);
+		chpass_data->password_current = NULL;
+		ggp_account_chpass_dialog_invalid(chpass_data,
+			_("Your current password is different from the one that"
+			" you specified."));
+		return;
+	}
+	if (g_utf8_collate(chpass_data->password_current,
+		chpass_data->password_new) == 0)
+	{
+		g_free(chpass_data->password_new);
+		chpass_data->password_new = NULL;
+		ggp_account_chpass_dialog_invalid(chpass_data,
+			_("New password have to be different from the current "
+			"one."));
+		return;
+	}
+
+	uin = ggp_str_to_uin(purple_account_get_username(account));
+	purple_debug_info("gg", "ggp_account_chpass_dialog_ok: validation ok "
+		"[token id=%s, value=%s]\n",
+		chpass_data->token->id, chpass_data->token_value);
+	h = gg_change_passwd4(uin, chpass_data->email,
+		chpass_data->password_current, chpass_data->password_new,
+		chpass_data->token->id, chpass_data->token_value, TRUE);
+	
+	ggp_libgaduw_http_watch(chpass_data->gc, h,
+		ggp_account_chpass_response, chpass_data, TRUE);
+}
+
+static void ggp_account_chpass_dialog_invalid(
+	ggp_account_chpass_data *chpass_data, const gchar *message)
+{
+	purple_debug_warning("gg", "ggp_account_chpass_dialog_invalid: %s\n",
+		message);
+	ggp_account_chpass_dialog(chpass_data->gc, chpass_data->token,
+		chpass_data);
+	purple_notify_error(purple_connection_get_account(chpass_data->gc),
+		GGP_ACCOUNT_CHPASS_TITLE, message, NULL);
+}
+
+static void ggp_account_chpass_dialog_cancel(
+	ggp_account_chpass_data *chpass_data, PurpleRequestFields *fields)
+{
+	ggp_account_chpass_data_free(chpass_data);
+}
+
+static void ggp_account_chpass_response(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer _chpass_data)
+{
+	ggp_account_chpass_data *chpass_data = _chpass_data;
+	PurpleAccount *account =
+		purple_connection_get_account(chpass_data->gc);
+	struct gg_pubdir *chpass_result = h->data;
+	
+	g_assert(!(success && cancelled));
+	
+	if (cancelled)
+	{
+		purple_debug_info("gg", "ggp_account_chpass_response: "
+			"cancelled\n");
+		ggp_account_chpass_data_free(chpass_data);
+		return;
+	}
+	if (!success || !chpass_result->success)
+	{
+		//TODO (libgadu 1.12.x): check chpass_result->error
+		purple_debug_error("gg", "ggp_account_chpass_response: "
+			"error\n");
+		purple_notify_error(NULL,
+			GGP_ACCOUNT_CHPASS_TITLE,
+			_("Unable to change password. "
+			"An unknown error occurred."), NULL);
+		ggp_account_chpass_data_free(chpass_data);
+		return;
+	}
+
+	purple_debug_info("gg", "ggp_account_chpass_response: "
+		"password changed\n");
+	
+	purple_account_set_password(account, chpass_data->password_new);
+
+	purple_notify_info(account, GGP_ACCOUNT_CHPASS_TITLE,
+		_("Your password has been changed."), NULL);
+	
+	ggp_account_chpass_data_free(chpass_data);
+	
+	//TODO: reconnect / check how it is done in original client
+	purple_account_disconnect(account);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/account.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,31 @@
+#ifndef _GGP_ACCOUNT_H
+#define _GGP_ACCOUNT_H
+
+#include <internal.h>
+
+typedef struct
+{
+	gchar *id;
+	gpointer data;
+	size_t size;
+	int length;
+} ggp_account_token;
+
+/**
+ * token must be free'd with ggp_account_token_free
+ */
+typedef void (*ggp_account_token_cb)(PurpleConnection *gc,
+	ggp_account_token *token, gpointer user_data);
+
+void ggp_account_token_request(PurpleConnection *gc,
+	ggp_account_token_cb callback, void *user_data);
+gboolean ggp_account_token_validate(ggp_account_token *token,
+	const gchar *value);
+void ggp_account_token_free(ggp_account_token *token);
+
+
+void ggp_account_register(PurpleAccount *account);
+
+void ggp_account_chpass(PurpleConnection *gc);
+
+#endif /* _GGP_ACCOUNT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/avatar.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,363 @@
+#include "avatar.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "utils.h"
+#include "oauth/oauth-purple.h"
+
+// Common
+
+static inline ggp_avatar_session_data *
+ggp_avatar_get_avdata(PurpleConnection *gc);
+
+static gboolean ggp_avatar_timer_cb(gpointer _gc);
+
+#define GGP_AVATAR_USERAGENT "GG Client build 11.0.0.7562"
+#define GGP_AVATAR_SIZE_MAX 1048576
+
+// Buddy avatars updating
+
+typedef struct
+{
+	uin_t uin;
+	time_t timestamp;
+	
+	PurpleConnection *gc;
+	PurpleUtilFetchUrlData *request;
+} ggp_avatar_buddy_update_req;
+
+static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc);
+static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
+	gpointer _pending_update, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+#define GGP_AVATAR_BUDDY_URL "http://avatars.gg.pl/%u/s,big"
+
+// Own avatar setting
+
+typedef struct
+{
+	PurpleStoredImage *img;
+} ggp_avatar_own_data;
+
+static void ggp_avatar_own_got_token(PurpleConnection *gc, const gchar *token,
+	gpointer img);
+static void ggp_avatar_own_sent(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+#define GGP_AVATAR_RESPONSE_MAX 10240
+
+/*******************************************************************************
+ * Common.
+ ******************************************************************************/
+
+void ggp_avatar_setup(PurpleConnection *gc)
+{
+	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+
+	avdata->pending_updates = NULL;
+	avdata->current_update = NULL;
+	avdata->own_data = g_new0(ggp_avatar_own_data, 1);
+	
+	avdata->timer = purple_timeout_add_seconds(1, ggp_avatar_timer_cb, gc);
+}
+
+void ggp_avatar_cleanup(PurpleConnection *gc)
+{
+	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+
+	purple_timeout_remove(avdata->timer);
+
+	if (avdata->current_update != NULL)
+	{
+		ggp_avatar_buddy_update_req *current_update =
+			avdata->current_update;
+		
+		purple_util_fetch_url_cancel(current_update->request);
+		g_free(current_update);
+	}
+	avdata->current_update = NULL;
+	
+	g_free(avdata->own_data);
+
+	g_list_free_full(avdata->pending_updates, &g_free);
+	avdata->pending_updates = NULL;
+}
+
+static inline ggp_avatar_session_data *
+ggp_avatar_get_avdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return &accdata->avatar_data;
+}
+
+static gboolean ggp_avatar_timer_cb(gpointer _gc)
+{
+	PurpleConnection *gc = _gc;
+	ggp_avatar_session_data *avdata;
+	
+	g_return_val_if_fail(PURPLE_CONNECTION_IS_VALID(gc), FALSE);
+	
+	avdata = ggp_avatar_get_avdata(gc);
+	if (avdata->current_update != NULL)
+	{
+		//TODO: verbose mode
+		//purple_debug_misc("gg", "ggp_avatar_timer_cb(%p): there is "
+		//	"already an update running\n", gc);
+		return TRUE;
+	}
+	
+	while (!ggp_avatar_buddy_update_next(gc));
+	
+	return TRUE;
+}
+
+/*******************************************************************************
+ * Buddy avatars updating.
+ ******************************************************************************/
+
+void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp)
+{
+	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);
+
+	purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
+		uin, timestamp);
+
+	pending_update->uin = uin;
+	pending_update->timestamp = timestamp;
+
+	avdata->pending_updates = g_list_append(avdata->pending_updates,
+		pending_update);
+}
+
+void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin)
+{
+	purple_debug_info("gg", "ggp_avatar_buddy_remove(%p, %u)\n", gc, uin);
+
+	purple_buddy_icons_set_for_user(purple_connection_get_account(gc),
+		ggp_uin_to_str(uin), NULL, 0, NULL);
+}
+
+/* return TRUE if avatar update was performed or there is no new requests,
+   FALSE if we can request another one immediately */
+static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc)
+{
+	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+	GList *pending_update_it;
+	ggp_avatar_buddy_update_req *pending_update;
+	PurpleBuddy *buddy;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	time_t old_timestamp;
+	const char *old_timestamp_str;
+	gchar *avatar_url;
+	
+	pending_update_it = g_list_first(avdata->pending_updates);
+	if (pending_update_it == NULL)
+		return TRUE;
+	
+	pending_update = pending_update_it->data;
+	avdata->pending_updates = g_list_remove(avdata->pending_updates,
+		pending_update);
+	buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
+	
+	if (!buddy)
+	{
+		if (ggp_str_to_uin(purple_account_get_username(account)) ==
+			pending_update->uin)
+		{
+			purple_debug_misc("gg",
+				"ggp_avatar_buddy_update_next(%p): own "
+				"avatar update requested, but we don't have "
+				"ourselves on buddy list\n", gc);
+		}
+		else
+		{
+			purple_debug_warning("gg",
+				"ggp_avatar_buddy_update_next(%p): "
+				"%u update requested, but he's not on buddy "
+				"list\n", gc, pending_update->uin);
+		}
+		return FALSE;
+	}
+
+	old_timestamp_str = purple_buddy_icons_get_checksum_for_user(buddy);
+	old_timestamp = old_timestamp_str ? g_ascii_strtoull(
+		old_timestamp_str, NULL, 10) : 0;
+	if (old_timestamp == pending_update->timestamp)
+	{
+		purple_debug_misc("gg",
+			"ggp_avatar_buddy_update_next(%p): "
+			"%u have up to date avatar with ts=%lu\n", gc,
+			pending_update->uin, pending_update->timestamp);
+		return FALSE;
+	}
+	if (old_timestamp > pending_update->timestamp)
+	{
+		purple_debug_warning("gg",
+			"ggp_avatar_buddy_update_next(%p): "
+			"saved timestamp for %u is newer than received "
+			"(%lu > %lu)\n", gc, pending_update->uin, old_timestamp,
+			pending_update->timestamp);
+	}
+	
+	purple_debug_info("gg",
+		"ggp_avatar_buddy_update_next(%p): "
+		"updating %u with ts=%lu...\n", gc, pending_update->uin,
+		pending_update->timestamp);
+
+	pending_update->gc = gc;
+	avdata->current_update = pending_update;
+	avatar_url = g_strdup_printf(GGP_AVATAR_BUDDY_URL, pending_update->uin);
+	pending_update->request = purple_util_fetch_url_request(account,
+		avatar_url, FALSE, GGP_AVATAR_USERAGENT, TRUE, NULL, FALSE,
+		GGP_AVATAR_SIZE_MAX, ggp_avatar_buddy_update_received,
+		pending_update);
+	g_free(avatar_url);
+	
+	return TRUE;
+}
+
+static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
+	gpointer _pending_update, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	ggp_avatar_buddy_update_req *pending_update = _pending_update;
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+	PurpleConnection *gc = pending_update->gc;
+	ggp_avatar_session_data *avdata;
+	gchar timestamp_str[20];
+
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
+	{
+		g_free(pending_update);
+		return;
+	}
+
+	avdata = ggp_avatar_get_avdata(gc);
+	g_assert(pending_update == avdata->current_update);
+	avdata->current_update = NULL;
+
+	if (len == 0)
+	{
+		purple_debug_error("gg", "ggp_avatar_buddy_update_received: bad"
+			" response while getting avatar for %u: %s\n",
+			pending_update->uin, error_message);
+		g_free(pending_update);
+		return;
+	}
+
+	account = purple_connection_get_account(gc);
+	buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
+
+	if (!buddy)
+	{
+		purple_debug_warning("gg", "ggp_avatar_buddy_update_received: "
+			"buddy %u disappeared\n", pending_update->uin);
+		g_free(pending_update);
+		return;
+	}
+
+	g_snprintf(timestamp_str, sizeof(timestamp_str), "%lu",
+		pending_update->timestamp);
+	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
+		g_memdup(url_text, len), len, timestamp_str);
+
+	purple_debug_info("gg", "ggp_avatar_buddy_update_received: "
+		"got avatar for buddy %u [ts=%lu]\n", pending_update->uin,
+		pending_update->timestamp);
+	g_free(pending_update);
+}
+
+/*******************************************************************************
+ * Own avatar setting.
+ ******************************************************************************/
+
+/**
+ * TODO: use new, GG11 method, when IMToken will be provided by libgadu.
+ *
+ * POST https://avatars.mpa.gg.pl/avatars/user,<uin>/0
+ * Authorization: IMToken 0123456789abcdef0123456789abcdef01234567
+ * photo=<avatar content>
+ */
+
+void ggp_avatar_own_set(PurpleConnection *gc, PurpleStoredImage *img)
+{
+	ggp_avatar_own_data *own_data = ggp_avatar_get_avdata(gc)->own_data;
+	
+	purple_debug_info("gg", "ggp_avatar_own_set(%p, %p)", gc, img);
+	
+	if (img == NULL)
+	{
+		purple_debug_warning("gg", "ggp_avatar_own_set: avatar removing"
+			" is probably not possible within old protocol");
+		return;
+	}
+	
+	own_data->img = img;
+	
+	ggp_oauth_request(gc, ggp_avatar_own_got_token, img);
+}
+
+static void ggp_avatar_own_got_token(PurpleConnection *gc, const gchar *token,
+	gpointer img)
+{
+	ggp_avatar_own_data *own_data = ggp_avatar_get_avdata(gc)->own_data;
+	gchar *img_data, *img_data_e, *request, *request_data;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	uin_t uin = ggp_str_to_uin(purple_account_get_username(account));
+	
+	if (img != own_data->img)
+	{
+		purple_debug_warning("gg", "ggp_avatar_own_got_token: "
+			"avatar was changed in meantime\n");
+		return;
+	}
+	own_data->img = NULL;
+	
+	img_data = purple_base64_encode(purple_imgstore_get_data(img),
+		purple_imgstore_get_size(img));
+	img_data_e = g_uri_escape_string(img_data, NULL, FALSE);
+	g_free(img_data);
+	request_data = g_strdup_printf("uin=%d&photo=%s", uin, img_data_e);
+	g_free(img_data_e);
+	
+	request = g_strdup_printf(
+		"POST /upload HTTP/1.1\r\n"
+		"Host: avatars.nowe.gg\r\n"
+		"Authorization: %s\r\n"
+		"From: avatars to avatars\r\n"
+		"Content-Length: %u\r\n"
+		"Content-Type: application/x-www-form-urlencoded\r\n"
+		"\r\n%s",
+		token, strlen(request_data), request_data);
+	g_free(request_data);
+	
+	purple_debug_misc("gg", "ggp_avatar_own_got_token: "
+		"uploading new avatar...\n");
+	purple_util_fetch_url_request(account, "http://avatars.nowe.gg/upload",
+		FALSE, NULL, TRUE, request, FALSE, GGP_AVATAR_RESPONSE_MAX,
+		ggp_avatar_own_sent, gc);
+	
+	g_free(request);
+}
+
+static void ggp_avatar_own_sent(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	PurpleConnection *gc = user_data;
+	
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
+		return;
+	
+	if (len == 0)
+		purple_debug_error("gg", "ggp_avatar_own_sent: "
+			"avatar not sent. %s\n", error_message);
+	else
+		purple_debug_info("gg", "ggp_avatar_own_sent: %s\n", url_text);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/avatar.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,24 @@
+#ifndef _GGP_AVATAR_H
+#define _GGP_AVATAR_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct
+{
+	guint timer;
+	GList *pending_updates;
+	
+	gpointer current_update;
+	gpointer own_data;
+} ggp_avatar_session_data;
+
+void ggp_avatar_setup(PurpleConnection *gc);
+void ggp_avatar_cleanup(PurpleConnection *gc);
+
+void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp);
+void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin);
+
+void ggp_avatar_own_set(PurpleConnection *gc, PurpleStoredImage *img);
+
+#endif /* _GGP_AVATAR_H */
--- a/libpurple/protocols/gg/buddylist.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/buddylist.c	Tue Aug 14 22:26:33 2012 +0200
@@ -22,9 +22,10 @@
 
 
 #include <libgadu.h>
+#include <debug.h>
 
 #include "gg.h"
-#include "gg-utils.h"
+#include "utils.h"
 #include "buddylist.h"
 
 #define F_FIRSTNAME 0
@@ -36,6 +37,7 @@
 #define F_UIN 6
 
 /* void ggp_buddylist_send(PurpleConnection *gc) {{{ */
+// this is for for notify purposes, not synchronizing buddy list
 void ggp_buddylist_send(PurpleConnection *gc)
 {
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
@@ -81,7 +83,7 @@
 	PurpleGroup *group;
 	gchar **users_tbl;
 	int i;
-	char *utf8buddylist = charset_convert(buddylist, "CP1250", "UTF-8");
+	char *utf8buddylist = ggp_convert_from_cp1250(buddylist);
 
 	/* Don't limit the number of records in a buddylist. */
 	users_tbl = g_strsplit(utf8buddylist, "\r\n", -1);
@@ -94,7 +96,7 @@
 			continue;
 
 		data_tbl = g_strsplit(users_tbl[i], ";", 8);
-		if (ggp_array_size(data_tbl) < 8) {
+		if (g_strv_length(data_tbl) < 8) {
 			purple_debug_warning("gg",
 				"Something is wrong on line %d of the buddylist. Skipping.\n",
 				i + 1);
@@ -127,7 +129,7 @@
 			/* XXX: Probably buddy should be added to all the groups. */
 			/* Hard limit to at most 50 groups */
 			gchar **group_tbl = g_strsplit(data_tbl[F_GROUP], ",", 50);
-			if (ggp_array_size(group_tbl) > 0) {
+			if (g_strv_length(group_tbl) > 0) {
 				g_free(g);
 				g = g_strdup(group_tbl[0]);
 			}
@@ -178,11 +180,22 @@
 				"", gname, bname, "", "");
 	}
 
-	ptr = charset_convert(buddylist->str, "UTF-8", "CP1250");
+	ptr = ggp_convert_to_cp1250(buddylist->str);
 	g_string_free(buddylist, TRUE);
 	return ptr;
 }
 /* }}} */
 
+const char * ggp_buddylist_get_buddy_name(PurpleConnection *gc, const uin_t uin)
+{
+	const char *uin_s = ggp_uin_to_str(uin);
+	PurpleBuddy *buddy = purple_find_buddy(
+		purple_connection_get_account(gc), uin_s);
+	
+	if (buddy != NULL)
+		return purple_buddy_get_alias(buddy);
+	else
+		return uin_s;
+}
 
 /* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/buddylist.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/buddylist.h	Tue Aug 14 22:26:33 2012 +0200
@@ -50,6 +50,16 @@
 char *
 ggp_buddylist_dump(PurpleAccount *account);
 
+/**
+ * Returns the best name of a buddy from the buddylist.
+ *
+ * @param gc  PurpleConnection instance.
+ * @param uin UIN of the buddy.
+ *
+ * @return Name of the buddy, or UIN converted to string, if there is no such
+ * user on the list.
+ */
+const char * ggp_buddylist_get_buddy_name(PurpleConnection *gc, const uin_t uin);
 
 #endif /* _PURPLE_GG_BUDDYLIST_H */
 
--- a/libpurple/protocols/gg/confer.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/confer.c	Tue Aug 14 22:26:33 2012 +0200
@@ -23,7 +23,7 @@
 
 #include <libgadu.h>
 #include "gg.h"
-#include "gg-utils.h"
+#include "utils.h"
 #include "confer.h"
 
 /* PurpleConversation *ggp_confer_find_by_name(PurpleConnection *gc, const gchar *name) {{{ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/deprecated.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,33 @@
+#include "deprecated.h"
+
+#include <libgadu.h>
+
+gboolean ggp_deprecated_setup_proxy(PurpleConnection *gc)
+{
+	PurpleProxyInfo *gpi = purple_proxy_get_setup(purple_connection_get_account(gc));
+	
+	if ((purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) &&
+		(purple_proxy_info_get_host(gpi) == NULL ||
+		purple_proxy_info_get_port(gpi) <= 0))
+	{
+		gg_proxy_enabled = 0;
+		purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
+			_("Either the host name or port number specified for your given proxy type is invalid."));
+		return FALSE;
+	}
+	
+	if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_NONE)
+	{
+		gg_proxy_enabled = 0;
+		return TRUE;
+	}
+
+	gg_proxy_enabled = 1;
+	//TODO: memleak
+	gg_proxy_host = g_strdup(purple_proxy_info_get_host(gpi));
+	gg_proxy_port = purple_proxy_info_get_port(gpi);
+	gg_proxy_username = g_strdup(purple_proxy_info_get_username(gpi));
+	gg_proxy_password = g_strdup(purple_proxy_info_get_password(gpi));
+	
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/deprecated.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,8 @@
+#ifndef _GGP_DEPRECATED_H
+#define _GGP_DEPRECATED_H
+
+#include <internal.h>
+
+gboolean ggp_deprecated_setup_proxy(PurpleConnection *gc);
+
+#endif /* _GGP_DEPRECATED_H */
--- a/libpurple/protocols/gg/gg-utils.c	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/**
- * @file gg-utils.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 "gg-utils.h"
-
-
-/* uin_t ggp_str_to_uin(const char *str) {{{ */
-uin_t ggp_str_to_uin(const char *str)
-{
-	char *tmp;
-	long num;
-
-	if (!str)
-		return 0;
-
-	errno = 0;
-	num = strtol(str, &tmp, 10);
-
-	if (*str == '\0' || *tmp != '\0')
-		return 0;
-
-	if ((errno == ERANGE || (num == LONG_MAX || num == LONG_MIN))
-#if (LONG_MAX > UINT_MAX)
-	    || num > (long)UINT_MAX
-#endif
-	    || num < 0)
-		return 0;
-
-	return (uin_t) num;
-}
-/* }}} */
-
-/* unsigned int ggp_array_size(char **array) {{{ */
-unsigned int ggp_array_size(char **array)
-{
-	unsigned int i;
-
-	for (i = 0; array[i] != NULL && i < UINT_MAX; i++)
-	{}
-
-	return i;
-}
-/* }}} */
-
-/* char *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst) {{{ */
-char *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst)
-{
-	gchar *msg;
-	GError *err = NULL;
-
-	if (locstr == NULL)
-		return NULL;
-
-	msg = g_convert_with_fallback(locstr, strlen(locstr), encdst, encsrc,
-				      "?", NULL, NULL, &err);
-	if (err != NULL) {
-		purple_debug_error("gg", "Error converting from %s to %s: %s\n",
-				 encsrc, encdst, err->message);
-		g_error_free(err);
-	}
-
-	/* Just in case? */
-	if (msg == NULL)
-		msg = g_strdup(locstr);
-
-	return msg;
-}
-/* }}} */
-
-/* ggp_get_uin(PurpleAccount *account) {{{ */
-uin_t ggp_get_uin(PurpleAccount *account)
-{
-	return ggp_str_to_uin(purple_account_get_username(account));
-}
-/* }}} */
-
-/* char *ggp_buddy_get_name(PurpleConnection *gc, const uin_t uin) {{{ */
-char *ggp_buddy_get_name(PurpleConnection *gc, const uin_t uin)
-{
-	PurpleBuddy *buddy;
-	gchar *str_uin;
-
-	str_uin = g_strdup_printf("%lu", (unsigned long int)uin);
-
-	buddy = purple_find_buddy(purple_connection_get_account(gc), str_uin);
-	if (buddy != NULL) {
-		g_free(str_uin);
-		return g_strdup(purple_buddy_get_alias(buddy));
-	} else {
-		return str_uin;
-	}
-}
-/* }}} */
-
-void ggp_status_fake_to_self(PurpleAccount *account)
-{
-	PurplePresence *presence;
-	PurpleStatus *status;
-	const char *status_id;
-	const char *msg;
-
-	if (! purple_find_buddy(account, purple_account_get_username(account)))
-		return;
-
-	presence = purple_account_get_presence(account);
-	status = purple_presence_get_active_status(presence);
-	msg = purple_status_get_attr_string(status, "message");
-	if (msg && !*msg)
-		msg = NULL;
-
-	status_id = purple_status_get_id(status);
-	if (strcmp(status_id, "invisible") == 0) {
-		status_id = "offline";
-	}
-
-	if (msg) {
-		if (strlen(msg) > GG_STATUS_DESCR_MAXSIZE) {
-			msg = purple_markup_slice(msg, 0, GG_STATUS_DESCR_MAXSIZE);
-		}
-	}
-	purple_prpl_got_user_status(account, purple_account_get_username(account),
-				    status_id,
-				    msg ? "message" : NULL, msg, NULL);
-}
-
-guint ggp_http_input_add(struct gg_http *http_req, PurpleInputFunction func,
-	gpointer user_data)
-{
-	PurpleInputCondition cond = 0;
-	int check = http_req->check;
-
-	if (check & GG_CHECK_READ)
-		cond |= PURPLE_INPUT_READ;
-	if (check & GG_CHECK_WRITE)
-		cond |= PURPLE_INPUT_WRITE;
-
-	return purple_input_add(http_req->fd, cond, func, user_data);
-}
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/gg-utils.h	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/**
- * @file gg-utils.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_UTILS_H
-#define _PURPLE_GG_UTILS_H
-
-#include "internal.h"
-
-#include "plugin.h"
-#include "version.h"
-#include "notify.h"
-#include "status.h"
-#include "blist.h"
-#include "accountopt.h"
-#include "debug.h"
-#include "util.h"
-#include "request.h"
-
-#include "gg.h"
-
-
-/**
- * Convert a base 10 string to a UIN.
- *
- * @param str The string to convert
- *
- * @return UIN or 0 if an error occurred.
- */
-uin_t
-ggp_str_to_uin(const char *str);
-
-/**
- * Calculate size of a NULL-terminated array.
- *
- * @param array The array.
- *
- * @return Size of the array.
- */
-unsigned int
-ggp_array_size(char **array);
-
-/**
- * Convert enconding of a given string.
- *
- * @param locstr Input string.
- * @param encsrc Current encoding of the string.
- * @param encdst Target encoding of the string.
- *
- * @return Converted string (it must be g_free()ed when not used. Or NULL if
- * locstr is NULL.
- */
-char *
-charset_convert(const gchar *locstr, const char *encsrc, const char *encdst);
-
-/**
- * Get UIN of a given account.
- *
- * @param account Current account.
- *
- * @return UIN of an account.
- */
-uin_t
-ggp_get_uin(PurpleAccount *account);
-
-/**
- * Returns the best name of a buddy from the buddylist.
- *
- * @param gc  PurpleConnection instance.
- * @param uin UIN of the buddy.
- *
- * @return Name of the buddy, or UIN converted to string.
- */
-char *
-ggp_buddy_get_name(PurpleConnection *gc, const uin_t uin);
-
-/**
- * Manages the display of account's status in the buddylist.
- *
- * @param account Current account.
- */
-void
-ggp_status_fake_to_self(PurpleAccount *account);
-
-
-/**
- * Adds an input handler in purple event loop for http request.
- *
- * @see purple_input_add
- *
- * @param http_req  Http connection to watch.
- * @param func      The callback function for data.
- * @param user_data User-specified data.
- *
- * @return The resulting handle (will be greater than 0).
- */
-guint
-ggp_http_input_add(struct gg_http *http_req, PurpleInputFunction func,
-	gpointer user_data);
-
-#endif /* _PURPLE_GG_UTILS_H */
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/gg.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/gg.c	Tue Aug 14 22:26:33 2012 +0200
@@ -26,12 +26,11 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include "internal.h"
+#include <internal.h>
 
 #include "plugin.h"
 #include "version.h"
 #include "notify.h"
-#include "status.h"
 #include "blist.h"
 #include "accountopt.h"
 #include "debug.h"
@@ -43,206 +42,25 @@
 #include "confer.h"
 #include "search.h"
 #include "buddylist.h"
-#include "gg-utils.h"
-
-#ifdef _WIN32
-#  include "win32-resolver.h"
-#endif
-
-static PurplePlugin *my_protocol = NULL;
+#include "utils.h"
+#include "resolver-purple.h"
+#include "account.h"
+#include "deprecated.h"
+#include "purplew.h"
+#include "libgadu-events.h"
+#include "multilogon.h"
+#include "status.h"
 
 /* Prototypes */
-static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
-static int ggp_to_gg_status(PurpleStatus *status, char **msg);
 
-/* ---------------------------------------------------------------------- */
-/* ----- EXTERNAL CALLBACKS --------------------------------------------- */
-/* ---------------------------------------------------------------------- */
+typedef struct
+{
+	gboolean blocked;
+} ggp_buddy_data;
 
 
-/* ----- HELPERS -------------------------------------------------------- */
-
-/**
- * Set up libgadu's proxy.
- *
- * @param account Account for which to set up the proxy.
- *
- * @return Zero if proxy setup is valid, otherwise -1.
- */
-static int ggp_setup_proxy(PurpleAccount *account)
-{
-	PurpleProxyInfo *gpi;
-
-	gpi = purple_proxy_get_setup(account);
-
-	if ((purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) &&
-	    (purple_proxy_info_get_host(gpi) == NULL ||
-	     purple_proxy_info_get_port(gpi) <= 0)) {
-
-		gg_proxy_enabled = 0;
-		purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
-				  _("Either the host name or port number specified for your given proxy type is invalid."));
-		return -1;
-	} else if (purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) {
-		gg_proxy_enabled = 1;
-		gg_proxy_host = g_strdup(purple_proxy_info_get_host(gpi));
-		gg_proxy_port = purple_proxy_info_get_port(gpi);
-		gg_proxy_username = g_strdup(purple_proxy_info_get_username(gpi));
-		gg_proxy_password = g_strdup(purple_proxy_info_get_password(gpi));
-	} else {
-		gg_proxy_enabled = 0;
-	}
-
-	return 0;
-}
-
-static void ggp_async_token_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
-{
-	PurpleConnection *gc = _gc;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPToken *token = info->token;
-	GGPTokenCallback cb;
-
-	struct gg_token *t = NULL;
-
-	purple_debug_info("gg", "token_handler: token->req: check = %d; state = %d;\n",
-			token->req->check, token->req->state);
-
-	if (gg_token_watch_fd(token->req) == -1 || token->req->state == GG_STATE_ERROR) {
-		purple_debug_error("gg", "token error (1): %d\n", token->req->error);
-		purple_input_remove(token->inpa);
-		gg_token_free(token->req);
-		token->req = NULL;
-
-		purple_notify_error(purple_connection_get_account(gc),
-				  _("Token Error"),
-				  _("Unable to fetch the token.\n"), NULL);
-		return;
-	}
-
-	if (token->req->state != GG_STATE_DONE) {
-		purple_input_remove(token->inpa);
-		token->inpa = purple_input_add(token->req->fd,
-						   (token->req->check == 1)
-						   	? PURPLE_INPUT_WRITE
-							: PURPLE_INPUT_READ,
-						   ggp_async_token_handler, gc);
-		return;
-	}
-
-	if (!(t = token->req->data) || !token->req->body) {
-		purple_debug_error("gg", "token error (2): %d\n", token->req->error);
-		purple_input_remove(token->inpa);
-		gg_token_free(token->req);
-		token->req = NULL;
-
-		purple_notify_error(purple_connection_get_account(gc),
-				  _("Token Error"),
-				  _("Unable to fetch the token.\n"), NULL);
-		return;
-	}
-
-	purple_input_remove(token->inpa);
-
-	token->id = g_strdup(t->tokenid);
-	token->size = token->req->body_size;
-	token->data = g_new0(char, token->size);
-	memcpy(token->data, token->req->body, token->size);
-
-	purple_debug_info("gg", "TOKEN! tokenid = %s; size = %d\n",
-			token->id, token->size);
-
-	gg_token_free(token->req);
-	token->req = NULL;
-	token->inpa = 0;
-
-	cb = token->cb;
-	token->cb = NULL;
-	cb(gc);
-}
-
-static void ggp_token_request(PurpleConnection *gc, GGPTokenCallback cb)
-{
-	PurpleAccount *account;
-	struct gg_http *req;
-	GGPInfo *info;
-
-	account = purple_connection_get_account(gc);
-
-	if (ggp_setup_proxy(account) == -1)
-		return;
-
-	info = purple_connection_get_protocol_data(gc);
-
-	if ((req = gg_token(1)) == NULL) {
-		purple_notify_error(account,
-				  _("Token Error"),
-				  _("Unable to fetch the token.\n"), NULL);
-		return;
-	}
-
-	info->token = g_new(GGPToken, 1);
-	info->token->cb = cb;
-
-	info->token->req = req;
-	info->token->inpa = purple_input_add(req->fd, PURPLE_INPUT_READ,
-					   ggp_async_token_handler, gc);
-}
-/* }}} */
-
 /* ---------------------------------------------------------------------- */
-
-/**
- * Request buddylist from the server.
- * Buddylist is received in the ggp_callback_recv().
- *
- * @param Current action handler.
- */
-static void ggp_action_buddylist_get(PurplePluginAction *action)
-{
-	PurpleConnection *gc = (PurpleConnection *)action->context;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-
-	purple_debug_info("gg", "Downloading...\n");
-
-	gg_userlist_request(info->session, GG_USERLIST_GET, NULL);
-}
-
-/**
- * Upload the buddylist to the server.
- *
- * @param action Current action handler.
- */
-static void ggp_action_buddylist_put(PurplePluginAction *action)
-{
-	PurpleConnection *gc = (PurpleConnection *)action->context;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-
-	char *buddylist = ggp_buddylist_dump(purple_connection_get_account(gc));
-
-	purple_debug_info("gg", "Uploading...\n");
-
-	if (buddylist == NULL)
-		return;
-
-	gg_userlist_request(info->session, GG_USERLIST_PUT, buddylist);
-	g_free(buddylist);
-}
-
-/**
- * Delete buddylist from the server.
- *
- * @param action Current action handler.
- */
-static void ggp_action_buddylist_delete(PurplePluginAction *action)
-{
-	PurpleConnection *gc = (PurpleConnection *)action->context;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-
-	purple_debug_info("gg", "Deleting...\n");
-
-	gg_userlist_request(info->session, GG_USERLIST_PUT, NULL);
-}
+// buddy list import/export from/to file
 
 static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *filename)
 {
@@ -332,154 +150,6 @@
 			gc);
 }
 
-static void ggp_callback_register_account_ok(PurpleConnection *gc,
-					     PurpleRequestFields *fields)
-{
-	PurpleAccount *account;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	struct gg_http *h = NULL;
-	struct gg_pubdir *s;
-	uin_t uin;
-	gchar *email, *p1, *p2, *t;
-	GGPToken *token = info->token;
-
-	email = charset_convert(purple_request_fields_get_string(fields, "email"),
-			     "UTF-8", "CP1250");
-	p1  = charset_convert(purple_request_fields_get_string(fields, "password1"),
-			     "UTF-8", "CP1250");
-	p2  = charset_convert(purple_request_fields_get_string(fields, "password2"),
-			     "UTF-8", "CP1250");
-	t   = charset_convert(purple_request_fields_get_string(fields, "token"),
-			     "UTF-8", "CP1250");
-
-	account = purple_connection_get_account(gc);
-
-	if (email == NULL || p1 == NULL || p2 == NULL || t == NULL ||
-	    *email == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') {
-		purple_connection_error (gc,
-			PURPLE_CONNECTION_ERROR_OTHER_ERROR,
-			_("You must fill in all registration fields"));
-		goto exit_err;
-	}
-
-	if (g_utf8_collate(p1, p2) != 0) {
-		purple_connection_error (gc,
-			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
-			_("Passwords do not match"));
-		goto exit_err;
-	}
-
-	purple_debug_info("gg", "register_account_ok: token_id = %s; t = %s\n",
-			token->id, t);
-	h = gg_register3(email, p1, token->id, t, 0);
-	if (h == NULL || !(s = h->data) || !s->success) {
-		purple_connection_error (gc,
-			PURPLE_CONNECTION_ERROR_OTHER_ERROR,
-			_("Unable to register new account.  An unknown error occurred."));
-		goto exit_err;
-	}
-
-	uin = s->uin;
-	purple_debug_info("gg", "registered uin: %d\n", uin);
-
-	g_free(t);
-	t = g_strdup_printf("%u", uin);
-	purple_account_set_username(account, t);
-	/* Save the password if remembering passwords for the account */
-	purple_account_set_password(account, p1);
-
-	purple_notify_info(NULL, _("New Gadu-Gadu Account Registered"),
-			 _("Registration completed successfully!"), NULL);
-
-	purple_account_register_completed(account, TRUE);
-
-	/* TODO: the currently open Accounts Window will not be updated withthe
-	 * new username and etc, we need to somehow have it refresh at this
-	 * point
-	 */
-
-	/* Need to disconnect or actually log in. For now, we disconnect. */
-	purple_account_disconnect(account);
-
-exit_err:
-	purple_account_register_completed(account, FALSE);
-
-	gg_register_free(h);
-	g_free(email);
-	g_free(p1);
-	g_free(p2);
-	g_free(t);
-	g_free(token->id);
-	g_free(token);
-}
-
-static void ggp_callback_register_account_cancel(PurpleConnection *gc,
-						 PurpleRequestFields *fields)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPToken *token = info->token;
-
-	purple_account_disconnect(purple_connection_get_account(gc));
-
-	g_free(token->id);
-	g_free(token->data);
-	g_free(token);
-
-}
-
-static void ggp_register_user_dialog(PurpleConnection *gc)
-{
-	PurpleAccount *account;
-	PurpleRequestFields *fields;
-	PurpleRequestFieldGroup *group;
-	PurpleRequestField *field;
-
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPToken *token = info->token;
-
-
-	account = purple_connection_get_account(gc);
-
-	fields = purple_request_fields_new();
-	group = purple_request_field_group_new(NULL);
-	purple_request_fields_add_group(fields, group);
-
-	field = purple_request_field_string_new("email",
-			_("Email"), "", FALSE);
-	purple_request_field_string_set_masked(field, FALSE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("password1",
-			_("Password"), "", FALSE);
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("password2",
-			_("Password (again)"), "", FALSE);
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("token",
-			_("Enter captcha text"), "", FALSE);
-	purple_request_field_string_set_masked(field, FALSE);
-	purple_request_field_group_add_field(group, field);
-
-	/* original size: 60x24 */
-	field = purple_request_field_image_new("token_img",
-			_("Captcha"), token->data, token->size);
-	purple_request_field_group_add_field(group, field);
-
-	purple_request_fields(account,
-		_("Register New Gadu-Gadu Account"),
-		_("Register New Gadu-Gadu Account"),
-		_("Please, fill in the following fields"),
-		fields,
-		_("OK"), G_CALLBACK(ggp_callback_register_account_ok),
-		_("Cancel"), G_CALLBACK(ggp_callback_register_account_cancel),
-		purple_connection_get_account(gc), NULL, NULL,
-		gc);
-}
-
 /* ----- PUBLIC DIRECTORY SEARCH ---------------------------------------- */
 
 static void ggp_callback_show_next(PurpleConnection *gc, GList *row, gpointer user_data)
@@ -617,265 +287,6 @@
 		gc);
 }
 
-/* ----- CHANGE PASSWORD ---------------------------------------------------- */
-
-typedef struct
-{
-	guint inpa;
-	struct gg_http *http_req;
-	gchar *new_password;
-	PurpleAccount *account;
-} ggp_change_passwd_request;
-
-static void ggp_callback_change_passwd_handler(gpointer _req, gint fd,
-	PurpleInputCondition cond)
-{
-	ggp_change_passwd_request *req = _req;
-	const char *messagesTitle =
-		_("Change password for the Gadu-Gadu account");
-
-	purple_input_remove(req->inpa);
-
-	if (gg_change_passwd_watch_fd(req->http_req) == -1 ||
-		req->http_req->state == GG_STATE_ERROR)
-		goto exit_error;
-
-	if (req->http_req->state != GG_STATE_DONE)
-	{
-		req->inpa = ggp_http_input_add(req->http_req,
-			ggp_callback_change_passwd_handler, req);
-		return;
-	}
-
-	if (req->http_req->data != NULL &&
-		((struct gg_pubdir*)req->http_req->data)->success == 1)
-	{
-		purple_account_set_password(req->account, req->new_password);
-		purple_notify_info(req->account, messagesTitle,
-			_("Password was changed successfully!"), NULL);
-		goto exit_cleanup;
-	}
-
-exit_error:
-	purple_notify_error(req->account, messagesTitle,
-		_("Unable to change password. Error occurred.\n"), NULL);
-
-exit_cleanup:
-	gg_change_passwd_free(req->http_req);
-	g_free(req->new_password);
-	g_free(req);
-}
-
-static void ggp_callback_change_passwd_ok(PurpleConnection *gc,
-	PurpleRequestFields *fields)
-{
-	PurpleAccount *account;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	struct gg_http *h;
-	gchar *cur, *p1, *p2, *t, *mail;
-	const char *messagesTitle =
-		_("Change password for the Gadu-Gadu account");
-
-	cur = g_strdup(purple_request_fields_get_string(fields,
-		"password_cur"));
-	p1 = g_strdup(purple_request_fields_get_string(fields, "password1"));
-	p2 = g_strdup(purple_request_fields_get_string(fields, "password2"));
-	t = g_strdup(purple_request_fields_get_string(fields, "token"));
-	mail = g_strdup(purple_request_fields_get_string(fields, "email"));
-
-	account = purple_connection_get_account(gc);
-
-	if (cur == NULL || p1 == NULL || p2 == NULL || t == NULL ||
-		mail == NULL || *cur == '\0' || *p1 == '\0' || *p2 == '\0' ||
-		*t == '\0' || *mail == '\0') {
-		purple_notify_error(account, messagesTitle,
-			_("Fill in the fields."), NULL);
-		goto exit_err;
-	}
-
-	if (g_utf8_collate(p1, p2) != 0) {
-		purple_notify_error(account, messagesTitle,
-			_("New passwords do not match."), NULL);
-		goto exit_err;
-	}
-
-	if (strlen(p1) > 15) {
-		purple_notify_error(account, messagesTitle,
-			_("New password should be at most 15 characters long."),
-			NULL);
-		goto exit_err;
-	}
-
-	if (g_utf8_collate(cur, purple_account_get_password(account)) != 0) {
-		purple_notify_error(account, messagesTitle,
-			_("Your current password is different from the one that"
-			" you specified."), NULL);
-		goto exit_err;
-	}
-
-	if (!purple_email_is_valid(mail)) {
-		purple_notify_error(account, messagesTitle,
-			_("Invalid email address"), NULL);
-		goto exit_err;
-	}
-
-	purple_debug_info("gg", "Changing password with email \"%s\"...\n",
-		mail);
-
-	h = gg_change_passwd4(ggp_get_uin(account), mail,
-		purple_account_get_password(account), p1, info->token->id, t,
-		1);
-
-	if (h == NULL)
-		purple_notify_error(account, messagesTitle,
-			_("Unable to change password. Error occurred.\n"),
-			NULL);
-	else
-	{
-		ggp_change_passwd_request *req =
-			g_new(ggp_change_passwd_request, 1);
-		req->http_req = h;
-		req->new_password = g_strdup(p1);
-		req->account = account;
-		
-		req->inpa = ggp_http_input_add(h,
-			ggp_callback_change_passwd_handler, req);
-	}
-	
-exit_err:
-	g_free(cur);
-	g_free(p1);
-	g_free(p2);
-	g_free(t);
-	g_free(mail);
-	g_free(info->token->id);
-	g_free(info->token->data);
-	g_free(info->token);
-}
-
-static void ggp_change_passwd_dialog(PurpleConnection *gc)
-{
-	PurpleRequestFields *fields;
-	PurpleRequestFieldGroup *group;
-	PurpleRequestField *field;
-
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GGPToken *token = info->token;
-
-	char *msg;
-
-	fields = purple_request_fields_new();
-	group = purple_request_field_group_new(NULL);
-	purple_request_fields_add_group(fields, group);
-
-	field = purple_request_field_string_new("password_cur",
-			_("Current password"), "", FALSE);
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("password1",
-			_("Password"), "", FALSE);
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("password2",
-			_("Password (retype)"), "", FALSE);
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("email",
-			_("Email Address"), "", FALSE);
-	purple_request_field_string_set_masked(field, FALSE);
-	purple_request_field_group_add_field(group, field);
-
-	field = purple_request_field_string_new("token",
-			_("Enter current token"), "", FALSE);
-	purple_request_field_string_set_masked(field, FALSE);
-	purple_request_field_group_add_field(group, field);
-
-	/* original size: 60x24 */
-	field = purple_request_field_image_new("token_img",
-			_("Current token"), token->data, token->size);
-	purple_request_field_group_add_field(group, field);
-
-	msg = g_strdup_printf("%s %d",
-		_("Please, enter your current password and your new password "
-		"for UIN: "), ggp_get_uin(purple_connection_get_account(gc)));
-
-	purple_request_fields(gc,
-		_("Change Gadu-Gadu Password"),
-		_("Change Gadu-Gadu Password"),
-		msg,
-		fields, _("OK"), G_CALLBACK(ggp_callback_change_passwd_ok),
-		_("Cancel"), NULL,
-		purple_connection_get_account(gc), NULL, NULL,
-		gc);
-
-	g_free(msg);
-}
-
-static void ggp_change_passwd(PurplePluginAction *action)
-{
-	PurpleConnection *gc = (PurpleConnection *)action->context;
-
-	ggp_token_request(gc, ggp_change_passwd_dialog);
-}
-
-/* ----- CHANGE STATUS BROADCASTING ------------------------------------------------ */
-
-static void ggp_action_change_status_broadcasting_ok(PurpleConnection *gc, PurpleRequestFields *fields)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	int selected_field;
-	PurpleAccount *account = purple_connection_get_account(gc);
-	PurpleStatus *status;
-
-	selected_field = purple_request_fields_get_choice(fields, "status_broadcasting");
-	
-	if (selected_field == 0)
-		info->status_broadcasting = TRUE;
-	else
-		info->status_broadcasting = FALSE;
-	
-	status = purple_account_get_active_status(account);
-	
-	ggp_set_status(account, status);
-}
-
-static void ggp_action_change_status_broadcasting(PurplePluginAction *action)
-{
-	PurpleConnection *gc = (PurpleConnection *)action->context;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	
-	PurpleRequestFields *fields;
-	PurpleRequestFieldGroup *group;
-	PurpleRequestField *field;
-
-	fields = purple_request_fields_new();
-	group = purple_request_field_group_new(NULL);
-	purple_request_fields_add_group(fields, group);
-	
-	field = purple_request_field_choice_new("status_broadcasting", _("Show status to:"), 0);
-	purple_request_field_choice_add(field, _("All people"));
-	purple_request_field_choice_add(field, _("Only buddies"));
-	purple_request_field_group_add_field(group, field);
-
-	if (info->status_broadcasting)
-		purple_request_field_choice_set_default_value(field, 0);
-	else
-		purple_request_field_choice_set_default_value(field, 1);
-
-	purple_request_fields(gc,
-		_("Change status broadcasting"),
-		_("Change status broadcasting"),
-		_("Please, select who can see your status"),
-		fields,
-		_("OK"), G_CALLBACK(ggp_action_change_status_broadcasting_ok),
-		_("Cancel"), NULL,
-		purple_connection_get_account(gc), NULL, NULL,
-		gc);
-}
-
 /* ----- CONFERENCES ---------------------------------------------------- */
 
 static void ggp_callback_add_to_chat_ok(PurpleBuddy *buddy, PurpleRequestFields *fields)
@@ -970,164 +381,6 @@
 /* ----- INTERNAL CALLBACKS --------------------------------------------- */
 /* ---------------------------------------------------------------------- */
 
-struct gg_fetch_avatar_data
-{
-	PurpleConnection *gc;
-	gchar *uin;
-	gchar *avatar_url;
-};
-
-
-static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
-               const gchar *data, size_t len, const gchar *error_message) {
-	struct gg_fetch_avatar_data *d = user_data;
-	PurpleAccount *account;
-	PurpleBuddy *buddy;
-	gpointer buddy_icon_data;
-
-	purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
-		d->uin);
-
-	/* FIXME: This shouldn't be necessary */
-	if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
-		g_free(d->uin);
-		g_free(d->avatar_url);
-		g_free(d);
-		g_return_if_reached();
-	}
-
-	account = purple_connection_get_account(d->gc);
-	buddy = purple_find_buddy(account, d->uin);
-
-	if (buddy == NULL)
-		goto out;
-
-	buddy_icon_data = g_memdup(data, len);
-
-	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
-			buddy_icon_data, len, d->avatar_url);
-	purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
-		"now\n", d->uin);
-
-out:
-	g_free(d->uin);
-	g_free(d->avatar_url);
-	g_free(d);
-}
-
-static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
-               const gchar *url_text, size_t len, const gchar *error_message) {
-	struct gg_fetch_avatar_data *data;
-	PurpleConnection *gc = user_data;
-	PurpleAccount *account;
-	PurpleBuddy *buddy;
-	const char *uin;
-	const char *is_blank;
-	const char *checksum;
-
-	gchar *bigavatar = NULL;
-	xmlnode *xml = NULL;
-	xmlnode *xmlnode_users;
-	xmlnode *xmlnode_user;
-	xmlnode *xmlnode_avatars;
-	xmlnode *xmlnode_avatar;
-	xmlnode *xmlnode_bigavatar;
-
-	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
-	account = purple_connection_get_account(gc);
-
-	if (error_message != NULL)
-		purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
-	else if (len > 0 && url_text && *url_text) {
-		xml = xmlnode_from_str(url_text, -1);
-		if (xml == NULL)
-			goto out;
-
-		xmlnode_users = xmlnode_get_child(xml, "users");
-		if (xmlnode_users == NULL)
-			goto out;
-
-		xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
-		if (xmlnode_user == NULL)
-			goto out;
-
-		uin = xmlnode_get_attrib(xmlnode_user, "uin");
-
-		xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
-		if (xmlnode_avatars == NULL)
-			goto out;
-
-		xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
-		if (xmlnode_avatar == NULL)
-			goto out;
-
-		xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar");
-		if (xmlnode_bigavatar == NULL)
-			goto out;
-
-		is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
-		bigavatar = xmlnode_get_data(xmlnode_bigavatar);
-
-		purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
-		                        "URL %s\n",
-		                  uin ? uin : "(null)", is_blank ? is_blank : "(null)",
-		                  bigavatar ? bigavatar : "(null)");
-
-		if (uin != NULL && bigavatar != NULL) {
-			buddy = purple_find_buddy(account, uin);
-			if (buddy == NULL)
-				goto out;
-
-			checksum = purple_buddy_icons_get_checksum_for_user(buddy);
-
-			if (purple_strequal(is_blank, "1")) {
-				purple_buddy_icons_set_for_user(account,
-						purple_buddy_get_name(buddy), NULL, 0, NULL);
-			} else if (!purple_strequal(checksum, bigavatar)) {
-				data = g_new0(struct gg_fetch_avatar_data, 1);
-				data->gc = gc;
-				data->uin = g_strdup(uin);
-				data->avatar_url = g_strdup(bigavatar);
-
-				purple_debug_info("gg", "gg_get_avatar_url_cb: "
-					"requesting avatar for %s\n", uin);
-				/* FIXME: This should be cancelled somewhere if not needed. */
-				url_data = purple_util_fetch_url_request(account,
-						bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
-						FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
-			}
-		}
-	}
-
-out:
-	if (xml)
-		xmlnode_free(xml);
-	g_free(bigavatar);
-}
-
-/**
- * Try to update avatar of the buddy.
- *
- * @param gc     PurpleConnection
- * @param uin    UIN of the buddy.
- */
-static void ggp_update_buddy_avatar(PurpleConnection *gc, uin_t uin)
-{
-	gchar *avatarurl;
-	PurpleUtilFetchUrlData *url_data;
-
-	purple_debug_info("gg", "ggp_update_buddy_avatar(gc, %u)\n", uin);
-
-	avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%u/0.xml", uin);
-
-	/* FIXME: This should be cancelled somewhere if not needed. */
-	url_data = purple_util_fetch_url_request(
-			purple_connection_get_account(gc), avatarurl, TRUE,
-			"Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
-			gg_get_avatar_url_cb, gc);
-
-	g_free(avatarurl);
-}
 
 /**
  * Handle change of the status of the buddy.
@@ -1143,14 +396,17 @@
 	gchar *from;
 	const char *st;
 	char *status_msg = NULL;
-
-	ggp_update_buddy_avatar(gc, uin);
+	ggp_buddy_data *buddy_data;
+	PurpleBuddy *buddy;
+	PurpleAccount *account = purple_connection_get_account(gc);
 
 	from = g_strdup_printf("%u", uin);
+	buddy = purple_find_buddy(purple_connection_get_account(gc), from);
 
 	switch (status) {
 		case GG_STATUS_NOT_AVAIL:
 		case GG_STATUS_NOT_AVAIL_DESCR:
+		case GG_STATUS_BLOCKED:
 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE);
 			break;
 		case GG_STATUS_FFC:
@@ -1173,10 +429,6 @@
 		case GG_STATUS_DND_DESCR:
 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_UNAVAILABLE);
 			break;
-		case GG_STATUS_BLOCKED:
-			/* user is blocking us.... */
-			st = "blocked";
-			break;
 		default:
 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
 			purple_debug_info("gg",
@@ -1193,13 +445,27 @@
 		}
 	}
 
+	buddy_data = purple_buddy_get_protocol_data(buddy);
+	if (!buddy_data)
+	{
+		buddy_data = g_new0(ggp_buddy_data, 1);
+		purple_buddy_set_protocol_data(buddy, buddy_data);
+	}
+	buddy_data->blocked = (status == GG_STATUS_BLOCKED);
+
+	if (uin == ggp_str_to_uin(purple_account_get_username(account)))
+	{
+		purple_debug_info("gg", "own status changed to %s [%s]\n", st,
+			status_msg ? status_msg : "");
+	}
+
 	purple_debug_info("gg", "status of %u is %s [%s]\n", uin, st,
 		status_msg ? status_msg : "");
 	if (status_msg == NULL) {
-		purple_prpl_got_user_status(purple_connection_get_account(gc),
+		purple_prpl_got_user_status(account,
 			from, st, NULL);
 	} else {
-		purple_prpl_got_user_status(purple_connection_get_account(gc),
+		purple_prpl_got_user_status(account,
 			from, st, "message", status_msg, NULL);
 		g_free(status_msg);
 	}
@@ -1461,47 +727,6 @@
 	}
 }
 
-static void ggp_recv_image_handler(PurpleConnection *gc, const struct gg_event *ev)
-{
-	gint imgid = 0;
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	GList *entry = g_list_first(info->pending_richtext_messages);
-	gchar *handlerid = g_strdup_printf("IMGID_HANDLER-%i", ev->event.image_reply.crc32);
-
-	imgid = purple_imgstore_add_with_id(
-		g_memdup(ev->event.image_reply.image, ev->event.image_reply.size),
-		ev->event.image_reply.size,
-		ev->event.image_reply.filename);
-
-	purple_debug_info("gg", "ggp_recv_image_handler: got image with crc32: %u\n", ev->event.image_reply.crc32);
-
-	while(entry) {
-		if (strstr((gchar *)entry->data, handlerid) != NULL) {
-			gchar **split = g_strsplit((gchar *)entry->data, handlerid, 3);
-			gchar *text = g_strdup_printf("%s%i%s", split[0], imgid, split[1]);
-			purple_debug_info("gg", "ggp_recv_image_handler: found message matching crc32: %s\n", (gchar *)entry->data);
-			g_strfreev(split);
-			info->pending_richtext_messages = g_list_remove(info->pending_richtext_messages, entry->data);
-			/* We don't have any more images to download */
-			if (strstr(text, "<IMG ID=\"IMGID_HANDLER") == NULL) {
-				gchar *buf = g_strdup_printf("%lu", (unsigned long int)ev->event.image_reply.sender);
-				serv_got_im(gc, buf, text, PURPLE_MESSAGE_IMAGES, time(NULL));
-				g_free(buf);
-				purple_debug_info("gg", "ggp_recv_image_handler: richtext message: %s\n", text);
-				g_free(text);
-				break;
-			}
-			info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, text);
-			break;
-		}
-		entry = g_list_next(entry);
-	}
-	g_free(handlerid);
-
-	return;
-}
-
-
 /**
  * Dispatch a message received from a buddy.
  *
@@ -1510,7 +735,7 @@
  *
  * Image receiving, some code borrowed from Kadu http://www.kadu.net
  */
-static void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event *ev)
+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;
@@ -1518,37 +743,38 @@
 	gchar *msg;
 	gchar *tmp;
 	time_t mtime;
+	uin_t sender = ev->sender;
 
-	if (ev->event.msg.message == NULL)
+	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->event.msg.sender);
+	from = g_strdup_printf("%lu", (unsigned long int)ev->sender);
 
-	/*
-	tmp = charset_convert((const char *)ev->event.msg.message,
-			      "CP1250", "UTF-8");
-	*/
-	tmp = g_strdup_printf("%s", ev->event.msg.message);
+	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->event.msg.formats_length)
+	if (ev->formats_length)
 	{
 		gboolean got_image = FALSE, bold = FALSE, italic = FALSE, under = FALSE;
-		char *cformats = (char *)ev->event.msg.formats;
-		char *cformats_end = cformats + ev->event.msg.formats_length;
+		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);
-		gchar *handlerid;
 
-		purple_debug_info("gg", "ggp_recv_message_handler: richtext msg from (%s): %s %i formats\n", from, msg, ev->event.msg.formats_length);
+		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)
 		{
@@ -1569,7 +795,10 @@
 				(actformat->font & GG_FONT_UNDERLINE) != 0,
 				increased_len);
 
-			if (actformat->font & GG_FONT_IMAGE) {
+			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);
@@ -1582,13 +811,12 @@
 					continue;
 				}
 
-				gg_image_request(info->session, ev->event.msg.sender,
+				gg_image_request(info->session, ev->sender,
 					actimage->size, actimage->crc32);
 
-				handlerid = g_strdup_printf("<IMG ID=\"IMGID_HANDLER-%i\">", actimage->crc32);
-				g_string_insert(message, byteoffset, handlerid);
-				increased_len += strlen(handlerid);
-				g_free(handlerid);
+				placeholder = ggp_image_pending_placeholder(actimage->crc32);
+				g_string_insert(message, byteoffset, placeholder);
+				increased_len += strlen(placeholder);
 				continue;
 			}
 
@@ -1636,80 +864,61 @@
 		msg = message->str;
 		g_string_free(message, FALSE);
 
-		if (got_image) {
-			info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, msg);
+		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)\n",
-			from, msg, ev->event.msg.msgclass,
-			ev->event.msg.recipients_count);
+	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 (ev->event.msg.msgclass & GG_CLASS_QUEUED)
-		mtime = ev->event.msg.time;
-	else
-		mtime = time(NULL);
-
-	if (ev->event.msg.recipients_count == 0) {
+	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;
-		char *buddy_name;
 
 		chat_name = ggp_confer_find_by_participants(gc,
-				ev->event.msg.recipients,
-				ev->event.msg.recipients_count);
+				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->event.msg.sender);
+							ev->sender);
 
 			ggp_confer_participants_add(gc, chat_name,
-						    ev->event.msg.recipients,
-						    ev->event.msg.recipients_count);
+						    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));
 
-		buddy_name = ggp_buddy_get_name(gc, ev->event.msg.sender);
-		serv_got_chat_in(gc, chat_id, buddy_name,
-				 PURPLE_MESSAGE_RECV, msg, mtime);
-		g_free(buddy_name);
+		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_send_image_handler(PurpleConnection *gc, const struct gg_event *ev)
-{
-	GGPInfo *info = purple_connection_get_protocol_data(gc);
-	PurpleStoredImage *image;
-	gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32)));
-
-	purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u, imgid: %d\n", ev->event.image_request.crc32, imgid);
-
-	if(imgid)
-	{
-		if((image = purple_imgstore_find_by_id(imgid))) {
-			gint image_size = purple_imgstore_get_size(image);
-			gconstpointer image_bin = purple_imgstore_get_data(image);
-			const char *image_filename = purple_imgstore_get_filename(image);
-
-			purple_debug_info("gg", "ggp_send_image_handler: sending image imgid: %i, crc: %u\n", imgid, ev->event.image_request.crc32);
-			gg_image_reply(info->session, (unsigned long int)ev->event.image_request.sender, image_filename, image_bin, image_size);
-			purple_imgstore_unref(image);
-		} else {
-			purple_debug_error("gg", "ggp_send_image_handler: image imgid: %i, crc: %u in hash but not found in imgstore!\n", imgid, ev->event.image_request.crc32);
-		}
-		g_hash_table_remove(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32));
-	}
-}
-
 static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
 	gchar *from;
 
@@ -1728,6 +937,7 @@
  * @param data Raw XML contents.
  *
  * @see http://toxygen.net/libgadu/protocol/#ch1.13
+ * @todo: this may not be necessary anymore
  */
 static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
 {
@@ -1776,7 +986,6 @@
 				purple_debug_info("gg",
 					"ggp_xml_event_handler: avatar updated (uid: %u)\n",
 					event_sender);
-				ggp_update_buddy_avatar(gc, event_sender);
 				break;
 			default:
 				purple_debug_error("gg",
@@ -1811,7 +1020,7 @@
 			/* Nothing happened. */
 			break;
 		case GG_EVENT_MSG:
-			ggp_recv_message_handler(gc, ev);
+			ggp_recv_message_handler(gc, &ev->event.msg, FALSE);
 			break;
 		case GG_EVENT_ACK:
 			/* Changing %u to %i fixes compiler warning */
@@ -1821,10 +1030,10 @@
 				ev->event.ack.seq);
 			break;
 		case GG_EVENT_IMAGE_REPLY:
-			ggp_recv_image_handler(gc, ev);
+			ggp_image_recv(gc, &ev->event.image_reply);
 			break;
 		case GG_EVENT_IMAGE_REQUEST:
-			ggp_send_image_handler(gc, ev);
+			ggp_image_send(gc, &ev->event.image_request);
 			break;
 		case GG_EVENT_NOTIFY:
 		case GG_EVENT_NOTIFY_DESCR:
@@ -1884,22 +1093,6 @@
 			ggp_generic_status_handler(gc, ev->event.status60.uin,
 				GG_S(ev->event.status60.status), ev->event.status60.descr);
 			break;
-		case GG_EVENT_USERLIST:
-			if (ev->event.userlist.type == GG_USERLIST_GET_REPLY) {
-				purple_debug_info("gg", "GG_USERLIST_GET_REPLY\n");
-				purple_notify_info(gc, NULL,
-					_("Buddy list downloaded"),
-					_("Your buddy list was downloaded from the server."));
-				if (ev->event.userlist.reply != NULL) {
-					ggp_buddylist_load(gc, ev->event.userlist.reply);
-				}
-			} else {
-				purple_debug_info("gg", "GG_USERLIST_PUT_REPLY\n");
-				purple_notify_info(gc, NULL,
-					_("Buddy list uploaded"),
-					_("Your buddy list was stored on the server."));
-			}
-			break;
 		case GG_EVENT_PUBDIR50_SEARCH_REPLY:
 			ggp_pubdir_reply_handler(gc, ev->event.pubdir50);
 			break;
@@ -1911,6 +1104,21 @@
 			purple_debug_info("gg", "GG_EVENT_XML_EVENT\n");
 			ggp_xml_event_handler(gc, ev->event.xml_event.data);
 			break;
+		case GG_EVENT_USER_DATA:
+			ggp_events_user_data(gc, &ev->event.user_data);
+			break;
+		case GG_EVENT_USERLIST100_VERSION:
+			ggp_roster_version(gc, &ev->event.userlist100_version);
+			break;
+		case GG_EVENT_USERLIST100_REPLY:
+			ggp_roster_reply(gc, &ev->event.userlist100_reply);
+			break;
+		case GG_EVENT_MULTILOGON_MSG:
+			ggp_multilogon_msg(gc, &ev->event.multilogon_msg);
+			break;
+		case GG_EVENT_MULTILOGON_INFO:
+			ggp_multilogon_info(gc, &ev->event.multilogon_info);
+			break;
 		default:
 			purple_debug_error("gg",
 				"unsupported event type=%d\n", ev->type);
@@ -1998,9 +1206,11 @@
 							  PURPLE_INPUT_READ,
 							  ggp_callback_recv, gc);
 
-				ggp_buddylist_send(gc);
 				purple_connection_update_progress(gc, _("Connected"), 1, 2);
 				purple_connection_set_state(gc, PURPLE_CONNECTED);
+				
+				ggp_buddylist_send(gc);
+				ggp_roster_request_update(gc);
 			}
 			break;
 		case GG_EVENT_CONN_FAILED:
@@ -2105,14 +1315,19 @@
 
 static char *ggp_status_text(PurpleBuddy *b)
 {
-	PurpleStatus *status;
 	const char *msg;
 	char *text;
 	char *tmp;
+	ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(b);
 
-	status = purple_presence_get_active_status(
-		purple_buddy_get_presence(b));
-	msg = purple_status_get_attr_string(status, "message");
+	if (buddy_data && buddy_data->blocked)
+		msg = _("Blocked");
+	else
+	{
+		PurpleStatus *status = purple_presence_get_active_status(
+			purple_buddy_get_presence(b));
+		msg = purple_status_get_attr_string(status, "message");
+	}
 
 	if (msg == NULL)
 		return NULL;
@@ -2199,15 +1414,6 @@
 		NULL);
 	types = g_list_append(types, type);
 
-	/*
-	 * This status is necessary to display guys who are blocking *us*.
-	 */
-	type = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE,
-		"blocked", _("Blocked"), TRUE, FALSE, FALSE,
-		"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-		NULL);
-	types = g_list_append(types, type);
-
 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
 		NULL, NULL, TRUE, TRUE, FALSE,
 		"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
@@ -2257,18 +1463,15 @@
 
 static void ggp_login(PurpleAccount *account)
 {
-	PurpleConnection *gc;
-	PurplePresence *presence;
-	PurpleStatus *status;
+	PurpleConnection *gc = purple_account_get_connection(account);
 	struct gg_login_params *glp;
 	GGPInfo *info;
 	const char *address;
 	const gchar *encryption_type;
 
-	if (ggp_setup_proxy(account) == -1)
+	if (!ggp_deprecated_setup_proxy(gc))
 		return;
 
-	gc = purple_account_get_connection(account);
 	glp = g_new0(struct gg_login_params, 1);
 	info = g_new0(GGPInfo, 1);
 
@@ -2276,17 +1479,19 @@
 	info->session = NULL;
 	info->chats = NULL;
 	info->chats_count = 0;
-	info->token = NULL;
 	info->searches = ggp_search_new();
-	info->pending_richtext_messages = NULL;
-	info->pending_images = g_hash_table_new(g_direct_hash, g_direct_equal);
-	info->status_broadcasting = purple_account_get_bool(account, "status_broadcasting", TRUE);
 	
 	purple_connection_set_protocol_data(gc, info);
 
-	glp->uin = ggp_get_uin(account);
-	glp->password = charset_convert(purple_account_get_password(account),
-		"UTF-8", "CP1250");
+
+	ggp_image_setup(gc);
+	ggp_avatar_setup(gc);
+	ggp_roster_setup(gc);
+	ggp_multilogon_setup(gc);
+	ggp_status_setup(gc);
+	
+	glp->uin = ggp_str_to_uin(purple_account_get_username(account));
+	glp->password = ggp_convert_to_cp1250(purple_account_get_password(account));
 
 	if (glp->uin == 0) {
 		purple_connection_error(gc,
@@ -2302,15 +1507,12 @@
 	if (purple_account_get_bool(account, "show_links_from_strangers", 1))
 		glp->status_flags |= GG_STATUS_FLAG_SPAM;
 
-	presence = purple_account_get_presence(account);
-	status = purple_presence_get_active_status(presence);
-
 	glp->encoding = GG_ENCODING_UTF8;
-	glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC
-		|GG_FEATURE_TYPING_NOTIFICATION);
+	glp->protocol_features = (GG_FEATURE_DND_FFC |
+		GG_FEATURE_TYPING_NOTIFICATION | GG_FEATURE_MULTILOGON |
+		GG_FEATURE_USER_DATA);
 
 	glp->async = 1;
-	glp->status = ggp_to_gg_status(status, &glp->status_descr);
 	
 	encryption_type = purple_account_get_string(account, "encryption",
 		"opportunistic_tls");
@@ -2333,28 +1535,22 @@
 		glp->tls = GG_SSL_DISABLED;
 	purple_debug_info("gg", "TLS mode: %d\n", glp->tls);
 
-	if (!info->status_broadcasting)
-		glp->status = glp->status|GG_STATUS_FRIENDS_MASK;
+	ggp_status_set_initial(gc, glp);
 	
 	address = purple_account_get_string(account, "gg_server", "");
-	if (address && *address) {
-		/* TODO: Make this non-blocking */
-		struct in_addr *addr = gg_gethostbyname(address);
-
-		purple_debug_info("gg", "Using gg server given by user (%s)\n", address);
-
-		if (addr == NULL) {
-			gchar *tmp = g_strdup_printf(_("Unable to resolve hostname '%s': %s"),
-					address, g_strerror(errno));
+	if (address && *address)
+	{
+		glp->server_addr = inet_addr(address);
+		glp->server_port = 8074;
+		
+		if (glp->server_addr == INADDR_NONE)
+		{
 			purple_connection_error(gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, /* should this be a settings error? */
-				tmp);
-			g_free(tmp);
+				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+				_("Provided server IP address is not valid"));
+			g_free(glp);
 			return;
 		}
-
-		glp->server_addr = inet_addr(inet_ntoa(*addr));
-		glp->server_port = 8074;
 	} else
 		purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
 
@@ -2387,22 +1583,43 @@
 	if (info) {
 		PurpleStatus *status = purple_account_get_active_status(account);
 
-		if (info->session != NULL) {
-			ggp_set_status(account, status);
+		if (info->session != NULL)
+		{
+			const gchar *status_msg = purple_status_get_attr_string(status, "message");
+			
+			if (ggp_status_set(account,
+				status_msg ? GG_STATUS_NOT_AVAIL_DESCR : GG_STATUS_NOT_AVAIL,
+				status_msg))
+			{
+				struct gg_event *ev;
+				guint64 wait_start = ggp_microtime(), now;
+				int sleep_time = 5000;
+				while ((ev = gg_watch_fd(info->session)) != NULL)
+				{
+					if (ev->type == GG_EVENT_DISCONNECT_ACK)
+						break;
+					now = ggp_microtime();
+					if (now - wait_start + sleep_time >= 100000)
+						break;
+					usleep(sleep_time);
+					sleep_time *= 2;
+				}
+			}
 			gg_logoff(info->session);
 			gg_free_session(info->session);
 		}
 
-		purple_account_set_bool(account, "status_broadcasting", info->status_broadcasting);
-
 		/* 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_search_destroy(info->searches);
-		g_list_free(info->pending_richtext_messages);
-		g_hash_table_destroy(info->pending_images);
+		ggp_image_cleanup(gc);
+		ggp_avatar_cleanup(gc);
+		ggp_roster_cleanup(gc);
+		ggp_multilogon_cleanup(gc);
+		ggp_status_cleanup(gc);
 
 		if (info->inpa > 0)
 			purple_input_remove(info->inpa);
@@ -2425,11 +1642,16 @@
 	gint pos = 0;
 	GData *attribs;
 	const char *start, *end = NULL, *last;
+	ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(
+		purple_find_buddy(purple_connection_get_account(gc), who));
 
 	if (msg == NULL || *msg == '\0') {
 		return 0;
 	}
 
+	if (buddy_data && buddy_data->blocked)
+		return -1;
+
 	last = msg;
 
 	/* Check if the message is richtext */
@@ -2439,53 +1661,58 @@
 		GString *string_buffer = g_string_new(NULL);
 		struct gg_msg_richtext fmt;
 
-		do {
-			PurpleStoredImage *image;
-			const char *id;
+		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) {
+			if(start - last)
+			{
 				pos = pos + g_utf8_strlen(last, start - last);
-				g_string_append_len(string_buffer, last, start - last);
+				g_string_append_len(string_buffer, last,
+					start - last);
+			}
+			last = end + 1;
+			
+			if (id == NULL)
+			{
+				g_datalist_clear(&attribs);
+				continue;
 			}
 
-			if((id = g_datalist_get_data(&attribs, "id")) && (image = purple_imgstore_find_by_id(atoi(id)))) {
-				struct gg_msg_richtext_format actformat;
-				struct gg_msg_richtext_image actimage;
-				gint image_size = purple_imgstore_get_size(image);
-				gconstpointer image_bin = purple_imgstore_get_data(image);
-				const char *image_filename = purple_imgstore_get_filename(image);
-				uint32_t crc32 = gg_crc32(0, image_bin, image_size);
-
-				g_hash_table_insert(info->pending_images, GINT_TO_POINTER(crc32), GINT_TO_POINTER(atoi(id)));
-				purple_imgstore_ref(image);
-				purple_debug_info("gg", "ggp_send_im_richtext: got crc: %u for imgid: %i\n", crc32, atoi(id));
-
+			/* 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;
 
-				actimage.unknown1 = 0x0109;
-				actimage.size = gg_fix32(image_size);
-				actimage.crc32 = gg_fix32(crc32);
-
-				if (actimage.size > 255000) {
-					purple_debug_warning("gg", "ggp_send_im_richtext: image over 255kb!\n");
-				} else {
-					purple_debug_info("gg", "ggp_send_im_richtext: adding images to richtext, size: %i, crc32: %u, name: %s\n", actimage.size, actimage.crc32, image_filename);
-
-					memcpy(format + format_length, &actformat, sizeof(actformat));
-					format_length += sizeof(actformat);
-					memcpy(format + format_length, &actimage, sizeof(actimage));
-					format_length += sizeof(actimage);
-				}
-			} else {
-				purple_debug_error("gg", "ggp_send_im_richtext: image not found in the image store!");
+				memcpy(format + format_length, &actformat,
+					sizeof(actformat));
+				format_length += sizeof(actformat);
+				memcpy(format + format_length, &actimage,
+					sizeof(actimage));
+				format_length += sizeof(actimage);
 			}
-
-			last = end + 1;
+			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));
+		} while (purple_markup_find_tag("img", last, &start, &end,
+			&attribs));
 
 		/* Add text after the images */
 		if(last && *last) {
@@ -2505,10 +1732,7 @@
 		plain = purple_unescape_html(msg);
 	}
 
-	/*
-	tmp = charset_convert(plain, "UTF-8", "CP1250");
-	*/
-	tmp = g_strdup_printf("%s", plain);
+	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) {
@@ -2570,101 +1794,19 @@
 	purple_debug_info("gg", "ggp_get_info(): Added seq %u", seq);
 }
 
-static int ggp_to_gg_status(PurpleStatus *status, char **msg)
-{
-	const char *status_id = purple_status_get_id(status);
-	int new_status, new_status_descr;
-	const char *new_msg;
-
-	g_return_val_if_fail(msg != NULL, 0);
-
-	purple_debug_info("gg", "ggp_to_gg_status: Requested status = %s\n",
-			status_id);
-
-	if (strcmp(status_id, "available") == 0) {
-		new_status = GG_STATUS_AVAIL;
-		new_status_descr = GG_STATUS_AVAIL_DESCR;
-	} else if (strcmp(status_id, "freeforchat") == 0) {
-		new_status = GG_STATUS_FFC;
-		new_status_descr = GG_STATUS_FFC_DESCR;
-	} else if (strcmp(status_id, "away") == 0) {
-		new_status = GG_STATUS_BUSY;
-		new_status_descr = GG_STATUS_BUSY_DESCR;
-	} else if (strcmp(status_id, "unavailable") == 0) {
-		new_status = GG_STATUS_DND;
-		new_status_descr = GG_STATUS_DND_DESCR;
-	} else if (strcmp(status_id, "invisible") == 0) {
-		new_status = GG_STATUS_INVISIBLE;
-		new_status_descr = GG_STATUS_INVISIBLE_DESCR;
-	} else if (strcmp(status_id, "offline") == 0) {
-		new_status = GG_STATUS_NOT_AVAIL;
-		new_status_descr = GG_STATUS_NOT_AVAIL_DESCR;
-	} else {
-		new_status = GG_STATUS_AVAIL;
-		new_status_descr = GG_STATUS_AVAIL_DESCR;
-		purple_debug_info("gg",
-			"ggp_set_status: unknown status requested (status_id=%s)\n",
-			status_id);
-	}
-
-	new_msg = purple_status_get_attr_string(status, "message");
-
-	if(new_msg) {
-		/*
-		char *tmp = purple_markup_strip_html(new_msg);
-		*msg = charset_convert(tmp, "UTF-8", "CP1250");
-		g_free(tmp);
-		*/
-		*msg = purple_markup_strip_html(new_msg);
-
-		return new_status_descr;
-	} else {
-		*msg = NULL;
-		return new_status;
-	}
-}
-
-static void ggp_set_status(PurpleAccount *account, PurpleStatus *status)
-{
-	PurpleConnection *gc;
-	GGPInfo *info;
-	int new_status;
-	char *new_msg = NULL;
-
-	if (!purple_status_is_active(status))
-		return;
-
-	gc = purple_account_get_connection(account);
-	info = purple_connection_get_protocol_data(gc);
-
-	new_status = ggp_to_gg_status(status, &new_msg);
-
-	if (!info->status_broadcasting)
-		new_status = new_status|GG_STATUS_FRIENDS_MASK;
-	
-	if (new_msg == NULL) {
-		gg_change_status(info->session, new_status);
-	} else {
-		gg_change_status_descr(info->session, new_status, new_msg);
-		g_free(new_msg);
-	}
-
-	ggp_status_fake_to_self(account);
-
-}
-
 static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
 {
-	PurpleAccount *account;
+	PurpleAccount *account = purple_connection_get_account(gc);
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
 	const gchar *name = purple_buddy_get_name(buddy);
 
 	gg_add_notify(info->session, ggp_str_to_uin(name));
 
-	account = purple_connection_get_account(gc);
-	if (strcmp(purple_account_get_username(account), name) == 0) {
-		ggp_status_fake_to_self(account);
-	}
+	// gg server won't tell us our status here
+	if (strcmp(purple_account_get_username(account), name) == 0)
+		ggp_status_fake_to_self(gc);
+	
+	ggp_roster_add_buddy(gc, buddy, group, message);
 }
 
 static void ggp_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
@@ -2673,6 +1815,7 @@
 	GGPInfo *info = purple_connection_get_protocol_data(gc);
 
 	gg_remove_notify(info->session, ggp_str_to_uin(purple_buddy_get_name(buddy)));
+	ggp_roster_remove_buddy(gc, buddy, group);
 }
 
 static void ggp_join_chat(PurpleConnection *gc, GHashTable *data)
@@ -2750,11 +1893,6 @@
 		uins[count++] = uin;
 	}
 
-	/*
-	plain = purple_unescape_html(message);
-	msg = charset_convert(plain, "UTF-8", "CP1250");
-	g_free(plain);
-	*/
 	msg = purple_unescape_html(message);
 	gg_send_message_confer(info->session, GG_CLASS_CHAT, count, uins,
 				(unsigned char *)msg);
@@ -2783,11 +1921,14 @@
 	}
 }
 
-static void ggp_register_user(PurpleAccount *account)
+static void ggp_action_chpass(PurplePluginAction *action)
 {
-	PurpleConnection *gc = purple_account_get_connection(account);
+	ggp_account_chpass((PurpleConnection *)action->context);
+}
 
-	ggp_token_request(gc, ggp_register_user_dialog);
+static void ggp_action_status_broadcasting(PurplePluginAction *action)
+{
+	ggp_status_broadcasting_dialog((PurpleConnection *)action->context);
 }
 
 static GList *ggp_actions(PurplePlugin *plugin, gpointer context)
@@ -2796,31 +1937,19 @@
 	PurplePluginAction *act;
 
 	act = purple_plugin_action_new(_("Change password..."),
-				     ggp_change_passwd);
+		ggp_action_chpass);
 	m = g_list_append(m, act);
 
 	act = purple_plugin_action_new(_("Find buddies..."),
 				     ggp_find_buddies);
 	m = g_list_append(m, act);
 
-	act = purple_plugin_action_new(_("Change status broadcasting"),
-				     ggp_action_change_status_broadcasting);
+	act = purple_plugin_action_new(_("Show status only for buddies"),
+		ggp_action_status_broadcasting);
 	m = g_list_append(m, act);
 
 	m = g_list_append(m, NULL);
 
-	act = purple_plugin_action_new(_("Upload buddylist to Server"),
-				     ggp_action_buddylist_put);
-	m = g_list_append(m, act);
-
-	act = purple_plugin_action_new(_("Download buddylist from Server"),
-				     ggp_action_buddylist_get);
-	m = g_list_append(m, act);
-
-	act = purple_plugin_action_new(_("Delete buddylist from Server"),
-				     ggp_action_buddylist_delete);
-	m = g_list_append(m, act);
-
 	act = purple_plugin_action_new(_("Save buddylist to file..."),
 				     ggp_action_buddylist_save);
 	m = g_list_append(m, act);
@@ -2832,20 +1961,53 @@
 	return m;
 }
 
+static const char* ggp_list_emblem(PurpleBuddy *buddy)
+{
+	ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(buddy);
+
+	if (!buddy_data)
+		return NULL;
+
+	if (buddy_data->blocked)
+		return "not-authorized";
+
+	return NULL;
+}
+
+static void ggp_buddy_free(PurpleBuddy *buddy)
+{
+	ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(buddy);
+
+	if (!buddy_data)
+		return;
+
+	g_free(buddy_data);
+
+	purple_buddy_set_protocol_data(buddy, NULL);
+}
+
 static gboolean ggp_offline_message(const PurpleBuddy *buddy)
 {
 	return TRUE;
 }
 
+static GHashTable * ggp_get_account_text_table(PurpleAccount *account)
+{
+	GHashTable *table;
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+	g_hash_table_insert(table, "login_label", (gpointer)_("GG number..."));
+	return table;
+}
+
 static PurplePluginProtocolInfo prpl_info =
 {
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_IM_IMAGE,
 	NULL,				/* user_splits */
 	NULL,				/* protocol_options */
-	{"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY},	/* icon_spec */
+	{"png", 1, 1, 200, 200, 0, PURPLE_ICON_SCALE_DISPLAY | PURPLE_ICON_SCALE_SEND},	/* icon_spec */
 	ggp_list_icon,			/* list_icon */
-	NULL,				/* list_emblem */
+	ggp_list_emblem,		/* list_emblem */
 	ggp_status_text,		/* status_text */
 	ggp_tooltip_text,		/* tooltip_text */
 	ggp_status_types,		/* status_types */
@@ -2858,7 +2020,7 @@
 	NULL,				/* set_info */
 	ggp_send_typing,		/* send_typing */
 	ggp_get_info,			/* get_info */
-	ggp_set_status,			/* set_away */
+	ggp_status_set_purplestatus,	/* set_away */
 	NULL,				/* set_idle */
 	NULL,				/* change_passwd */
 	ggp_add_buddy,			/* add_buddy */
@@ -2878,15 +2040,15 @@
 	NULL,				/* chat_whisper */
 	ggp_chat_send,			/* chat_send */
 	ggp_keepalive,			/* keepalive */
-	ggp_register_user,		/* register_user */
+	ggp_account_register,		/* register_user */
 	NULL,				/* get_cb_info */
-	NULL,				/* alias_buddy */
-	NULL,				/* group_buddy */
-	NULL,				/* rename_group */
-	NULL,				/* buddy_free */
+	ggp_roster_alias_buddy,		/* alias_buddy */
+	ggp_roster_group_buddy,		/* group_buddy */
+	ggp_roster_rename_group,	/* rename_group */
+	ggp_buddy_free,			/* buddy_free */
 	NULL,				/* convo_closed */
 	ggp_normalize,			/* normalize */
-	NULL,				/* set_buddy_icon */
+	ggp_avatar_own_set,		/* set_buddy_icon */
 	NULL,				/* remove_group */
 	NULL,				/* get_cb_real_name */
 	NULL,				/* set_chat_topic */
@@ -2904,7 +2066,7 @@
 	NULL,				/* unregister_user */
 	NULL,				/* send_attention */
 	NULL,				/* get_attention_types */
-	NULL,                           /* get_account_text_table */
+	ggp_get_account_text_table,	/* get_account_text_table */
 	NULL,                           /* initiate_media */
 	NULL,                            /* can_do_media */
 	NULL,				/* get_moods */
@@ -2975,6 +2137,9 @@
 	PurpleAccountOption *option;
 	GList *encryption_options = NULL;
 
+	purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
+		"libgadu %s...\n", gg_libgadu_version());
+
 	option = purple_account_option_string_new(_("GG server"),
 			"gg_server", "");
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
@@ -3002,14 +2167,9 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 		option);
 
-	my_protocol = plugin;
-
 	gg_debug_handler = purple_gg_debug_handler;
 	
-#ifdef _WIN32
-	gg_global_set_custom_resolver(ggp_resolver_win32thread_start,
-		ggp_resolver_win32thread_cleanup);
-#endif
+	ggp_resolver_purple_setup();
 }
 
 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
--- a/libpurple/protocols/gg/gg.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/gg.h	Tue Aug 14 22:26:33 2012 +0200
@@ -24,15 +24,22 @@
 #ifndef _PURPLE_GG_H
 #define _PURPLE_GG_H
 
-#undef printf
 #include <libgadu.h>
 #include "internal.h"
 #include "search.h"
 #include "connection.h"
 
+#include "image.h"
+#include "avatar.h"
+#include "account.h"
+#include "roster.h"
+#include "multilogon.h"
+#include "status.h"
 
 #define PUBDIR_RESULTS_MAX 20
 
+#define GGP_UIN_LEN_MAX 10
+
 
 typedef struct
 {
@@ -41,34 +48,21 @@
 
 } GGPChat;
 
-typedef void (*GGPTokenCallback)(PurpleConnection *);
-
-typedef struct
-{
-	char *id;
-	char *data;
-	unsigned int size;
-
-	struct gg_http *req;
-	guint inpa;
-
-	GGPTokenCallback cb;
-
-} GGPToken;
-
 typedef struct {
 
 	struct gg_session *session;
 	guint inpa;
-	GGPToken *token;
 	GList *chats;
 	GGPSearches *searches;
 	int chats_count;
-	GList *pending_richtext_messages;
-	GHashTable *pending_images;
-	gboolean status_broadcasting; //When TRUE status is visible to all, when FALSE status is visible only to friends.
+
+	ggp_image_connection_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;
 } GGPInfo;
 
-#endif /* _PURPLE_GG_H */
+void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event_msg *ev, gboolean multilogon);
 
-/* vim: set ts=8 sts=0 sw=8 noet: */
+#endif /* _PURPLE_GG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/image.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,265 @@
+#include "image.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "utils.h"
+
+#define GGP_PENDING_IMAGE_ID_PREFIX "gg-pending-image-"
+
+typedef struct
+{
+	uin_t from;
+	gchar *text;
+	time_t mtime;
+} ggp_image_pending_message;
+
+typedef struct
+{
+	int id;
+	gchar *conv_name;
+} ggp_image_pending_image;
+
+static void ggp_image_pending_message_free(gpointer data)
+{
+	ggp_image_pending_message *pending_message =
+		(ggp_image_pending_message*)data;
+	g_free(pending_message->text);
+	g_free(pending_message);
+}
+
+static void ggp_image_pending_image_free(gpointer data)
+{
+	ggp_image_pending_image *pending_image =
+		(ggp_image_pending_image*)data;
+	g_free(pending_image->conv_name);
+	g_free(pending_image);
+}
+
+static inline ggp_image_connection_data *
+ggp_image_get_imgdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return &accdata->image_data;
+}
+
+void ggp_image_setup(PurpleConnection *gc)
+{
+	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
+	
+	imgdata->pending_messages = NULL;
+	imgdata->pending_images = g_hash_table_new_full(NULL, NULL, NULL,
+		ggp_image_pending_image_free);
+}
+
+void ggp_image_cleanup(PurpleConnection *gc)
+{
+	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(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;
+}
+
+void ggp_image_got_im(PurpleConnection *gc, uin_t from, gchar *text,
+	time_t mtime)
+{
+	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);
+	size_t image_size;
+	gconstpointer image_data;
+	const char *image_filename;
+	uint32_t image_crc;
+	ggp_image_pending_image *pending_image;
+	
+	if (!image)
+	{
+		purple_debug_error("gg", "ggp_image_prepare_to_send: image %d "
+			"not found in image store\n", id);
+		return GGP_IMAGE_PREPARE_FAILURE;
+	}
+	
+	image_size = purple_imgstore_get_size(image);
+	
+	if (image_size > GGP_IMAGE_SIZE_MAX)
+	{
+		purple_debug_warning("gg", "ggp_image_prepare_to_send: 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=%d, filename=%s]\n",
+		id, image_crc, image_size, image_filename);
+	
+	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);
+	
+	return GGP_IMAGE_PREPARE_OK;
+}
+
+void ggp_image_recv(PurpleConnection *gc,
+	const struct gg_event_image_reply *image_reply)
+{
+	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
+	int stored_id;
+	const char *imgtag_search;
+	gchar *imgtag_replace;
+	GList *pending_messages_it;
+	
+	stored_id = purple_imgstore_add_with_id(
+		g_memdup(image_reply->image, image_reply->size),
+		image_reply->size,
+		image_reply->filename);
+	
+	purple_debug_info("gg", "ggp_image_recv: got image "
+		"[id=%d, crc=%u, size=%u, filename=\"%s\"]\n",
+		stored_id,
+		image_reply->crc32,
+		image_reply->size,
+		image_reply->filename);
+
+	imgtag_search = ggp_image_pending_placeholder(image_reply->crc32);
+	imgtag_replace = g_strdup_printf("<img src=\""
+		PURPLE_STORED_IMAGE_PROTOCOL "%u\">", stored_id);
+
+	pending_messages_it = g_list_first(imgdata->pending_messages);
+	while (pending_messages_it)
+	{
+		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);
+		
+		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);
+	}
+	g_free(imgtag_replace);
+}
+
+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;
+	PurpleStoredImage *image;
+	PurpleConversation *conv;
+	
+	purple_debug_info("gg", "ggp_image_send: got image request "
+		"[uin=%u, crc=%u, size=%u]\n",
+		image_request->sender,
+		image_request->crc32,
+		image_request->size);
+	
+	pending_image = g_hash_table_lookup(imgdata->pending_images,
+		GINT_TO_POINTER(image_request->crc32));
+	
+	if (pending_image == NULL)
+	{
+		purple_debug_warning("gg", "ggp_image_send: requested image "
+			"not found\n");
+		return;
+	}
+	
+	purple_debug_misc("gg", "ggp_image_send: requested image found "
+		"[id=%d, conv=%s]\n",
+		pending_image->id,
+		pending_image->conv_name);
+	
+	image = purple_imgstore_find_by_id(pending_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,
+			GINT_TO_POINTER(image_request->crc32));
+		return;
+	}
+	
+	//TODO: check allowed recipients
+	gg_image_reply(accdata->session, image_request->sender,
+		purple_imgstore_get_filename(image),
+		purple_imgstore_get_data(image),
+		purple_imgstore_get_size(image));
+	purple_imgstore_unref(image);
+	
+	conv = purple_find_conversation_with_account(
+		PURPLE_CONV_TYPE_IM, pending_image->conv_name,
+		purple_connection_get_account(gc));
+	if (conv != NULL)
+		purple_conversation_write(conv, "", _("Image delivered."),
+			PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NOTIFY,
+			time(NULL));
+	
+	g_hash_table_remove(imgdata->pending_images,
+		GINT_TO_POINTER(image_request->crc32));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/image.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,37 @@
+#ifndef _GGP_IMAGE_H
+#define _GGP_IMAGE_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+#define GGP_IMAGE_SIZE_MAX 255000
+
+typedef struct
+{
+	GList *pending_messages;
+	GHashTable *pending_images;
+} ggp_image_connection_data;
+
+typedef enum
+{
+	GGP_IMAGE_PREPARE_OK = 0,
+	GGP_IMAGE_PREPARE_FAILURE,
+	GGP_IMAGE_PREPARE_TOO_BIG
+} ggp_image_prepare_result;
+
+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);
+
+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);
+
+#endif /* _GGP_IMAGE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/config.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,79 @@
+/* Local libgadu configuration file. */
+
+/* libpurple's config */
+#include <config.h>
+
+#define GGP_QUOTE(x) GGP_QUOTE2(x)
+#define GGP_QUOTE2(x) #x
+#define GG_LIBGADU_VERSION GGP_QUOTE(GG_INTERNAL_LIBGADU_VERSION)
+
+/* Defined if libgadu was compiled for bigendian machine. */
+#undef GG_CONFIG_BIGENDIAN
+#ifdef WORDS_BIGENDIAN
+#  define GG_CONFIG_BIGENDIAN
+#endif
+
+/* Defined if this machine has gethostbyname_r(). */
+#undef GG_CONFIG_HAVE_GETHOSTBYNAME_R
+
+/* Define to 1 if you have the `_exit' function. */
+#define HAVE__EXIT 1
+
+/* Defined if libgadu was compiled and linked with fork support. */
+#undef GG_CONFIG_HAVE_FORK
+#ifndef _WIN32
+#  define GG_CONFIG_HAVE_FORK
+#endif
+
+/* Defined if libgadu was compiled and linked with pthread support. */
+/* We don't use pthreads - they may not be safe. */
+#undef GG_CONFIG_HAVE_PTHREAD
+
+/* Defined if this machine has C99-compiliant vsnprintf(). */
+#undef HAVE_C99_VSNPRINTF
+#ifndef _WIN32
+#  define HAVE_C99_VSNPRINTF
+#endif
+
+/* Defined if this machine has va_copy(). */
+#define GG_CONFIG_HAVE_VA_COPY
+
+/* Defined if this machine has __va_copy(). */
+#define GG_CONFIG_HAVE___VA_COPY
+
+/* Defined if this machine supports long long. */
+#undef GG_CONFIG_HAVE_LONG_LONG
+#ifdef HAVE_LONG_LONG
+#  define GG_CONFIG_HAVE_LONG_LONG
+#endif
+
+/* Defined if libgadu was compiled and linked with GnuTLS support. */
+#undef GG_CONFIG_HAVE_GNUTLS
+#ifdef HAVE_GNUTLS
+#  define GG_CONFIG_HAVE_GNUTLS
+#endif
+
+/* Defined if libgadu was compiled and linked with OpenSSL support. */
+/* OpenSSL cannot be used with libpurple due to licence type. */
+#undef GG_CONFIG_HAVE_OPENSSL
+
+/* Defined if libgadu was compiled and linked with zlib support. */
+#define GG_CONFIG_HAVE_ZLIB
+
+/* Defined if uintX_t types are defined in <stdint.h>. */
+#undef GG_CONFIG_HAVE_STDINT_H
+#ifdef HAVE_STDINT_H
+#  define GG_CONFIG_HAVE_STDINT_H
+#endif
+
+/* Defined if uintX_t types are defined in <inttypes.h>. */
+#undef GG_CONFIG_HAVE_INTTYPES_H
+#ifdef HAVE_INTTYPES_H
+#  define GG_CONFIG_HAVE_INTTYPES_H
+#endif
+
+/* Defined if uintX_t types are defined in <sys/types.h>. */
+#undef GG_CONFIG_HAVE_SYS_TYPES_H
+#ifdef HAVE_SYS_TYPES_H
+#  define GG_CONFIG_HAVE_SYS_TYPES_H
+#endif
--- a/libpurple/protocols/gg/lib/dcc7.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/dcc7.c	Tue Aug 14 22:26:33 2012 +0200
@@ -49,8 +49,8 @@
 #include "libgadu.h"
 #include "protocol.h"
 #include "resolver.h"
-#include "libgadu-internal.h"
-#include "libgadu-debug.h"
+#include "internal.h"
+#include "debug.h"
 
 #define gg_debug_dcc(dcc, level, fmt...) \
 	gg_debug_session(((dcc) != NULL) ? (dcc)->sess : NULL, level, fmt)
--- a/libpurple/protocols/gg/lib/debug.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/debug.c	Tue Aug 14 22:26:33 2012 +0200
@@ -32,7 +32,7 @@
 #include <string.h>
 
 #include "libgadu.h"
-#include "libgadu-debug.h"
+#include "debug.h"
 
 /**
  * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/debug.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,29 @@
+/*
+ *  (C) Copyright 2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+#ifndef LIBGADU_DEBUG_H
+#define LIBGADU_DEBUG_H
+
+#include "libgadu.h"
+
+const char *gg_debug_state(enum gg_state_t state);
+const char *gg_debug_event(enum gg_event_t event);
+void gg_debug_dump(struct gg_session *sess, int level, const char *buf, size_t len);
+void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap);
+
+#endif /* LIBGADU_DEBUG_H */
--- a/libpurple/protocols/gg/lib/deflate.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/deflate.h	Tue Aug 14 22:26:33 2012 +0200
@@ -1,7 +1,7 @@
 /* $Id$ */
 
 /*
- *  (C) Copyright 2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *  (C) Copyright 2011 Bartosz Brachaczek <b.brachaczek@gmail.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License Version
--- a/libpurple/protocols/gg/lib/encoding.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/encoding.c	Tue Aug 14 22:26:33 2012 +0200
@@ -35,22 +35,22 @@
  */
 static const uint16_t table_cp1250[] =
 {
-        0x20ac, '?',    0x201a, '?',    0x201e, 0x2026, 0x2020, 0x2021,
-        '?',    0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179,
-        '?',    0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
-        '?',    0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a,
-        0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7,
-        0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b,
-        0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
-        0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c,
-        0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7,
-        0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e,
-        0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7,
-        0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df,
-        0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7,
-        0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f,
-        0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7,
-        0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9,
+	0x20ac, '?',    0x201a, '?',    0x201e, 0x2026, 0x2020, 0x2021,
+	'?',    0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179,
+	'?',    0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+	'?',    0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a,
+	0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7,
+	0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b,
+	0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+	0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c,
+	0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7,
+	0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e,
+	0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7,
+	0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df,
+	0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7,
+	0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f,
+	0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7,
+	0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9,
 };
 
 /**
@@ -136,11 +136,8 @@
 	uint32_t uc = 0, uc_min = 0;
 
 	for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) {
-		if ((src[i] & 0xc0) == 0xc0) {
+		if ((src[i] & 0xc0) != 0x80)
 			len++;
-		} else if ((src[i] & 0x80) == 0x00) {
-			len++;
-		}
 	}
 
 	if ((dst_length != -1) && (len > dst_length))
--- a/libpurple/protocols/gg/lib/events.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/events.c	Tue Aug 14 22:26:33 2012 +0200
@@ -1,4 +1,4 @@
-/* $Id: events.c 1105 2011-05-25 21:34:50Z wojtekka $ */
+/* $Id: events.c 1144 2011-07-09 15:43:00Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -33,9 +33,9 @@
 #include "compat.h"
 #include "libgadu.h"
 #include "protocol.h"
-#include "libgadu-internal.h"
+#include "internal.h"
 #include "encoding.h"
-#include "libgadu-debug.h"
+#include "debug.h"
 #include "session.h"
 
 #include <errno.h>
@@ -806,14 +806,14 @@
 				const gnutls_datum_t *peers;
 				gnutls_x509_crt_t cert;
 
-				if (gnutls_x509_crt_init(&cert) >= 0) {
+				if (gnutls_x509_crt_init(&cert) == 0) {
 					peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count);
 
 					if (peers != NULL) {
 						char buf[256];
 						size_t size;
 
-						if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) >= 0) {
+						if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) == 0) {
 							size = sizeof(buf);
 							gnutls_x509_crt_get_dn(cert, buf, &size);
 							gg_debug_session(sess, GG_DEBUG_MISC, "//   cert subject: %s\n", buf);
@@ -822,6 +822,8 @@
 							gg_debug_session(sess, GG_DEBUG_MISC, "//   cert issuer: %s\n", buf);
 						}
 					}
+
+					gnutls_x509_crt_deinit(cert);
 				}
 			}
 
--- a/libpurple/protocols/gg/lib/handlers.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/handlers.c	Tue Aug 14 22:26:33 2012 +0200
@@ -39,7 +39,7 @@
 #include "protocol.h"
 #include "encoding.h"
 #include "message.h"
-#include "libgadu-internal.h"
+#include "internal.h"
 #include "deflate.h"
 
 #include <errno.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/internal.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,45 @@
+/* $Id$ */
+
+/*
+ *  (C) Copyright 2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+#ifndef LIBGADU_INTERNAL_H
+#define LIBGADU_INTERNAL_H
+
+#include "libgadu.h"
+#include "config.h"
+
+struct gg_dcc7_relay {
+	uint32_t addr;
+	uint16_t port;
+	uint8_t family;
+};
+
+typedef struct gg_dcc7_relay gg_dcc7_relay_t;
+
+int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length);
+
+int gg_resolve(int *fd, int *pid, const char *hostname);
+int gg_resolve_pthread(int *fd, void **resolver, const char *hostname);
+void gg_resolve_pthread_cleanup(void *resolver, int kill);
+
+#ifdef HAVE_UINT64_T
+uint64_t gg_fix64(uint64_t x);
+#endif
+
+#endif /* LIBGADU_INTERNAL_H */
--- a/libpurple/protocols/gg/lib/libgadu-config.h	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/* Local libgadu configuration. */
-
-#include "config.h"
-
-#ifndef __GG_LIBGADU_CONFIG_H
-#define __GG_LIBGADU_CONFIG_H
-
-/* Defined if libgadu was compiled for bigendian machine. */
-#undef __GG_LIBGADU_BIGENDIAN
-#ifdef WORDS_BIGENDIAN
-#  define __GG_LIBGADU_BIGENDIAN
-#endif
-
-/* Defined if this machine has gethostbyname_r(). */
-#undef GG_CONFIG_HAVE_GETHOSTBYNAME_R
-
-/* Defined if this machine has _exit(). */
-#define GG_CONFIG_HAVE__EXIT
-
-/* Defined if libgadu was compiled and linked with fork support. */
-#undef GG_CONFIG_HAVE_FORK
-#ifndef _WIN32
-#  define GG_CONFIG_HAVE_FORK
-#endif
-
-/* Defined if libgadu was compiled and linked with pthread support. */
-/* We don't like pthreads. */
-#undef __GG_LIBGADU_HAVE_PTHREAD
-
-/* Defined if this machine has C99-compiliant vsnprintf(). */
-#undef __GG_LIBGADU_HAVE_C99_VSNPRINTF
-#ifndef _WIN32
-#  define __GG_LIBGADU_HAVE_C99_VSNPRINTF
-#endif
-
-/* Defined if this machine has va_copy(). */
-#define __GG_LIBGADU_HAVE_VA_COPY
-
-/* Defined if this machine has __va_copy(). */
-#define __GG_LIBGADU_HAVE___VA_COPY
-
-/* Defined if this machine supports long long. */
-#undef __GG_LIBGADU_HAVE_LONG_LONG
-#ifdef HAVE_LONG_LONG
-#  define __GG_LIBGADU_HAVE_LONG_LONG
-#endif
-
-/* Defined if libgadu was compiled and linked with GnuTLS support. */
-#undef GG_CONFIG_HAVE_GNUTLS
-#ifdef HAVE_GNUTLS
-#  define GG_CONFIG_HAVE_GNUTLS
-#endif
-
-/* Defined if libgadu was compiled and linked with OpenSSL support. */
-/* Always undefined in Purple. */
-#undef __GG_LIBGADU_HAVE_OPENSSL
-
-/* Defined if libgadu was compiled and linked with zlib support. */
-#undef GG_CONFIG_HAVE_ZLIB
-
-/* Defined if uintX_t types are defined in <stdint.h>. */
-#undef GG_CONFIG_HAVE_STDINT_H
-#if HAVE_STDINT_H
-#  define GG_CONFIG_HAVE_STDINT_H
-#endif
-
-
-#define vnsprintf g_vnsprintf
-
-#endif
--- a/libpurple/protocols/gg/lib/libgadu-debug.h	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/*
- *  (C) Copyright 2009 Wojtek Kaniewski <wojtekka@irc.pl>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU Lesser General Public License Version
- *  2.1 as published by the Free Software Foundation.
- *
- *  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 Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
- *  USA.
- */
-
-#ifndef LIBGADU_DEBUG_H
-#define LIBGADU_DEBUG_H
-
-#include "libgadu.h"
-
-const char *gg_debug_state(enum gg_state_t state);
-const char *gg_debug_event(enum gg_event_t event);
-void gg_debug_dump(struct gg_session *sess, int level, const char *buf, size_t len);
-void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap);
-
-#endif /* LIBGADU_DEBUG_H */
--- a/libpurple/protocols/gg/lib/libgadu-internal.h	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/* $Id$ */
-
-/*
- *  (C) Copyright 2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU Lesser General Public License Version
- *  2.1 as published by the Free Software Foundation.
- *
- *  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 Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
- *  USA.
- */
-
-#ifndef LIBGADU_INTERNAL_H
-#define LIBGADU_INTERNAL_H
-
-#include "libgadu.h"
-
-struct gg_dcc7_relay {
-	uint32_t addr;
-	uint16_t port;
-	uint8_t family;
-};
-
-typedef struct gg_dcc7_relay gg_dcc7_relay_t;
-
-int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length);
-
-int gg_resolve(int *fd, int *pid, const char *hostname);
-int gg_resolve_pthread(int *fd, void **resolver, const char *hostname);
-void gg_resolve_pthread_cleanup(void *resolver, int kill);
-
-#ifdef HAVE_UINT64_T
-uint64_t gg_fix64(uint64_t x);
-#endif
-
-#endif /* LIBGADU_INTERNAL_H */
--- a/libpurple/protocols/gg/lib/libgadu.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/libgadu.c	Tue Aug 14 22:26:33 2012 +0200
@@ -1,4 +1,4 @@
-/* $Id: libgadu.c 1102 2011-05-05 21:17:57Z wojtekka $ */
+/* $Id: libgadu.c 1245 2012-01-10 22:48:31Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -37,9 +37,9 @@
 #include "libgadu.h"
 #include "protocol.h"
 #include "resolver.h"
-#include "libgadu-internal.h"
+#include "internal.h"
 #include "encoding.h"
-#include "libgadu-debug.h"
+#include "debug.h"
 #include "session.h"
 #include "message.h"
 #include "deflate.h"
@@ -60,8 +60,6 @@
 #  include <openssl/rand.h>
 #endif
 
-#define GG_LIBGADU_VERSION "1.11.0"
-
 /**
  * Port gniazda nasłuchującego dla połączeń bezpośrednich.
  * 
@@ -132,7 +130,7 @@
 #ifdef __GNUC__
 __attribute__ ((unused))
 #endif
-= "$Id: libgadu.c 1102 2011-05-05 21:17:57Z wojtekka $";
+= "$Id: libgadu.c 1245 2012-01-10 22:48:31Z wojtekka $";
 #endif
 
 #endif /* DOXYGEN */
@@ -149,7 +147,7 @@
 	return GG_LIBGADU_VERSION;
 }
 
-#ifdef GG_CONFIG_HAVE_UINT64_T
+#ifdef HAVE_UINT64_T
 /**
  * \internal Zamienia kolejność bajtów w 64-bitowym słowie.
  *
@@ -178,7 +176,7 @@
 		((x & (uint64_t) 0xff00000000000000ULL) >> 56));
 #endif
 }
-#endif /* GG_CONFIG_HAVE_UINT64_T */
+#endif /* HAVE_UINT64_T */
 
 /**
  * \internal Zamienia kolejność bajtów w 32-bitowym słowie.
@@ -443,11 +441,11 @@
 			res = written;
 		}
 	} else {
-		res = 0;
-
 		if (sess->send_buf == NULL) {
 			res = gg_write_common(sess, buf, length);
 
+			if (res == -1 && errno == EAGAIN)
+				res = 0;
 			if (res == -1)
 				return -1;
 		}
@@ -1112,18 +1110,6 @@
 		sess->fd = -1;
 	}
 
-#ifdef GG_CONFIG_HAVE_GNUTLS
-	if (sess->ssl != NULL) {
-		gg_session_gnutls_t *tmp;
-
-		tmp = (gg_session_gnutls_t*) sess->ssl;
-		gnutls_deinit(tmp->session);
-		gnutls_certificate_free_credentials(tmp->xcred);
-		gnutls_global_deinit();
-		free(sess->ssl);
-	}
-#endif
-
 	if (sess->send_buf) {
 		free(sess->send_buf);
 		sess->send_buf = NULL;
@@ -1155,6 +1141,18 @@
 	free(sess->recv_buf);
 	free(sess->header_buf);
 
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	if (sess->ssl != NULL) {
+		gg_session_gnutls_t *tmp;
+
+		tmp = (gg_session_gnutls_t*) sess->ssl;
+		gnutls_deinit(tmp->session);
+		gnutls_certificate_free_credentials(tmp->xcred);
+		gnutls_global_deinit();
+		free(sess->ssl);
+	}
+#endif
+
 #ifdef GG_CONFIG_HAVE_OPENSSL
 	if (sess->ssl)
 		SSL_free(sess->ssl);
--- a/libpurple/protocols/gg/lib/libgadu.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/libgadu.h	Tue Aug 14 22:26:33 2012 +0200
@@ -101,7 +101,7 @@
 /* Defined if uintX_t types are defined in <sys/types.h>. */
 #undef GG_CONFIG_HAVE_SYS_TYPES_H
 
-#include "libgadu-config.h"
+#include "config.h"
 
 #ifdef GG_CONFIG_HAVE_OPENSSL
 #include <openssl/ssl.h>
--- a/libpurple/protocols/gg/lib/obsolete.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/obsolete.c	Tue Aug 14 22:26:33 2012 +0200
@@ -34,7 +34,7 @@
 #include <errno.h>
 
 #include "libgadu.h"
-#include "libgadu-internal.h"
+#include "internal.h"
 
 struct gg_http *gg_userlist_get(uin_t uin, const char *passwd, int async)
 {
--- a/libpurple/protocols/gg/lib/pubdir50.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/pubdir50.c	Tue Aug 14 22:26:33 2012 +0200
@@ -31,7 +31,7 @@
 #include <time.h>
 
 #include "libgadu.h"
-#include "libgadu-internal.h"
+#include "internal.h"
 #include "encoding.h"
 
 /**
--- a/libpurple/protocols/gg/lib/resolver.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/lib/resolver.c	Tue Aug 14 22:26:33 2012 +0200
@@ -234,7 +234,7 @@
 	/* Kopiuj */
 
 	for (i = 0; he->h_addr_list[i] != NULL; i++)
-		memcpy(&((*result)[i]), he->h_addr_list[0], sizeof(struct in_addr));
+		memcpy(&((*result)[i]), he->h_addr_list[i], sizeof(struct in_addr));
 
 	(*result)[i].s_addr = INADDR_NONE;
 
@@ -249,6 +249,9 @@
 /**
  * \internal Rozwiązuje nazwę i zapisuje wynik do podanego desktyptora.
  *
+ * \note Użycie logowania w tej funkcji może mieć negatywny wpływ na
+ * aplikacje jednowątkowe korzystające.
+ *
  * \param fd Deskryptor
  * \param hostname Nazwa serwera
  *
@@ -260,11 +263,10 @@
 	int addr_count;
 	int res = 0;
 
-	gg_debug(GG_DEBUG_MISC, "// gg_resolver_run(%d, %s)\n", fd, hostname);
-
 	if ((addr_ip[0].s_addr = inet_addr(hostname)) == INADDR_NONE) {
 		if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 1) == -1) {
 			addr_list = addr_ip;
+			addr_count = 0;
 			/* addr_ip[0] już zawiera INADDR_NONE */
 		}
 	} else {
@@ -273,8 +275,6 @@
 		addr_count = 1;
 	}
 
-	gg_debug(GG_DEBUG_MISC, "// gg_resolver_run() count = %d\n", addr_count);
-
 	if (write(fd, addr_list, (addr_count + 1) * sizeof(struct in_addr)) != (addr_count + 1) * sizeof(struct in_addr))
 		res = -1;
 
@@ -375,7 +375,7 @@
 
 		status = (gg_resolver_run(pipes[1], hostname) == -1) ? 1 : 0;
 
-#ifdef GG_CONFIG_HAVE__EXIT
+#ifdef HAVE__EXIT
 		_exit(status);
 #else
 		exit(status);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/libgadu-events.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,54 @@
+#include "libgadu-events.h"
+
+#include <debug.h>
+
+#include "avatar.h"
+
+void ggp_events_user_data(PurpleConnection *gc, struct gg_event_user_data *data)
+{
+	int user_idx;
+	gboolean is_update;
+	
+	purple_debug_info("gg", "GG_EVENT_USER_DATA [type=%d, user_count=%d]\n",
+		data->type, data->user_count);
+	
+	/*
+	type = 
+		1, 3:	user information sent after connecting (divided by
+			20 contacts; 3 - last one; 1 - rest of them)
+		0: data update
+	*/
+	is_update = (data->type == 0);
+	
+	for (user_idx = 0; user_idx < data->user_count; user_idx++)
+	{
+		struct gg_event_user_data_user *data_user =
+			&data->users[user_idx];
+		uin_t uin = data_user->uin;
+		int attr_idx;
+		gboolean got_avatar = FALSE;
+		for (attr_idx = 0; attr_idx < data_user->attr_count; attr_idx++)
+		{
+			struct gg_event_user_data_attr *data_attr =
+				&data_user->attrs[attr_idx];
+			if (strcmp(data_attr->key, "avatar") == 0)
+			{
+				time_t timestamp;
+				if (data_attr->type == 0)
+				{
+					ggp_avatar_buddy_remove(gc, uin);
+					continue;
+				}
+				
+				timestamp = atoi(data_attr->value);
+				if (timestamp <= 0)
+					continue;
+				got_avatar = TRUE;
+				ggp_avatar_buddy_update(gc, uin, timestamp);
+			}
+		}
+		
+		if (!is_update && !got_avatar)
+			ggp_avatar_buddy_remove(gc, uin);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/libgadu-events.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,10 @@
+#ifndef _GGP_LIBGADU_EVENTS_H
+#define _GGP_LIBGADU_EVENTS_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+void ggp_events_user_data(PurpleConnection *gc,
+	struct gg_event_user_data *data);
+
+#endif /* _GGP_LIBGADU_EVENTS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/libgaduw.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,112 @@
+#include "libgaduw.h"
+
+#include <debug.h>
+
+#include "purplew.h"
+
+/*******************************************************************************
+ * HTTP requests.
+ ******************************************************************************/
+
+static void ggp_libgaduw_http_processing_cancel(PurpleConnection *gc,
+	void *_req);
+
+static void ggp_libgaduw_http_handler(gpointer _req, gint fd,
+	PurpleInputCondition cond);
+
+static void ggp_libgaduw_http_finish(ggp_libgaduw_http_req *req,
+	gboolean success);
+
+/******************************************************************************/
+
+ggp_libgaduw_http_req * ggp_libgaduw_http_watch(PurpleConnection *gc,
+	struct gg_http *h, ggp_libgaduw_http_cb cb,
+	gpointer user_data, gboolean show_processing)
+{
+	ggp_libgaduw_http_req *req;
+	purple_debug_misc("gg", "ggp_libgaduw_http_watch(h=%x, "
+		"show_processing=%d)\n", (unsigned int)h, show_processing);
+	
+	req = g_new(ggp_libgaduw_http_req, 1);
+	req->user_data = user_data;
+	req->cb = cb;
+	req->cancelled = FALSE;
+	req->h = h;
+	req->processing = NULL;
+	if (show_processing)
+		req->processing = ggp_purplew_request_processing(gc, NULL,
+			req, ggp_libgaduw_http_processing_cancel);
+	req->inpa = ggp_purplew_http_input_add(h, ggp_libgaduw_http_handler,
+		req);
+	
+	return req;
+}
+
+static void ggp_libgaduw_http_processing_cancel(PurpleConnection *gc,
+	void *_req)
+{
+	ggp_libgaduw_http_req *req = _req;
+	req->processing = NULL;
+	ggp_libgaduw_http_cancel(req);
+}
+
+static void ggp_libgaduw_http_handler(gpointer _req, gint fd,
+	PurpleInputCondition cond)
+{
+	ggp_libgaduw_http_req *req = _req;
+	
+	if (req->h->callback(req->h) == -1 || req->h->state == GG_STATE_ERROR)
+	{
+		purple_debug_error("gg", "ggp_libgaduw_http_handler: failed to "
+			"make http request: %d\n", req->h->error);
+		ggp_libgaduw_http_finish(req, FALSE);
+		return;
+	}
+	
+	//TODO: verbose mode
+	//purple_debug_misc("gg", "ggp_libgaduw_http_handler: got fd update "
+	//	"[check=%d, state=%d]\n", req->h->check, req->h->state);
+	
+	if (req->h->state != GG_STATE_DONE)
+	{
+		purple_input_remove(req->inpa);
+		req->inpa = ggp_purplew_http_input_add(req->h,
+			ggp_libgaduw_http_handler, req);
+		return;
+	}
+	
+	if (!req->h->data || !req->h->body)
+	{
+		purple_debug_error("gg", "ggp_libgaduw_http_handler: got empty "
+			"http response: %d\n", req->h->error);
+		ggp_libgaduw_http_finish(req, FALSE);
+		return;
+	}
+
+	ggp_libgaduw_http_finish(req, TRUE);
+}
+
+void ggp_libgaduw_http_cancel(ggp_libgaduw_http_req *req)
+{
+	purple_debug_misc("gg", "ggp_libgaduw_http_cancel\n");
+	req->cancelled = TRUE;
+	gg_http_stop(req->h);
+	ggp_libgaduw_http_finish(req, FALSE);
+}
+
+static void ggp_libgaduw_http_finish(ggp_libgaduw_http_req *req,
+	gboolean success)
+{
+	purple_debug_misc("gg", "ggp_libgaduw_http_finish(h=%x, processing=%x):"
+		" success=%d\n", (unsigned int)req->h,
+		(unsigned int)req->processing, success);
+	if (req->processing)
+	{
+		ggp_purplew_request_processing_done(req->processing);
+		req->processing = NULL;
+	}
+	purple_input_remove(req->inpa);
+	req->cb(req->h, success, req->cancelled, req->user_data);
+	req->h->destroy(req->h);
+	g_free(req);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/libgaduw.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,29 @@
+#ifndef _GGP_LIBGADUW_H
+#define _GGP_LIBGADUW_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+#include "purplew.h"
+
+typedef void (*ggp_libgaduw_http_cb)(struct gg_http *h, gboolean success,
+	gboolean cancelled, gpointer user_data);
+
+typedef struct
+{
+	gpointer user_data;
+	ggp_libgaduw_http_cb cb;
+	
+	gboolean cancelled;
+	struct gg_http *h;
+	ggp_purplew_request_processing_handle *processing;
+	guint inpa;
+} ggp_libgaduw_http_req;
+
+ggp_libgaduw_http_req * ggp_libgaduw_http_watch(PurpleConnection *gc,
+	struct gg_http *h, ggp_libgaduw_http_cb cb, gpointer user_data,
+	gboolean show_processing);
+void ggp_libgaduw_http_cancel(ggp_libgaduw_http_req *req);
+
+
+#endif /* _GGP_LIBGADUW_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/multilogon.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,65 @@
+#include "multilogon.h"
+
+#include <debug.h>
+
+#include "gg.h"
+
+struct _ggp_multilogon_session_data
+{
+	int session_count;
+};
+
+static inline ggp_multilogon_session_data *
+ggp_multilogon_get_mldata(PurpleConnection *gc);
+
+////////////
+
+static inline ggp_multilogon_session_data *
+ggp_multilogon_get_mldata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return accdata->multilogon_data;
+}
+
+void ggp_multilogon_setup(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	
+	ggp_multilogon_session_data *mldata = g_new0(ggp_multilogon_session_data, 1);
+	accdata->multilogon_data = mldata;
+}
+
+void ggp_multilogon_cleanup(PurpleConnection *gc)
+{
+	ggp_multilogon_session_data *mldata = ggp_multilogon_get_mldata(gc);
+	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)
+{
+	ggp_multilogon_session_data *mldata = ggp_multilogon_get_mldata(gc);
+	int i;
+	
+	purple_debug_info("gg", "ggp_multilogon_info: session list changed\n");
+	for (i = 0; i < info->count; i++)
+	{
+		purple_debug_misc("gg", "ggp_multilogon_info: "
+			"session [%s] logged in at %lu\n",
+			info->sessions[i].name,
+			info->sessions[i].logon_time);
+	}
+
+	mldata->session_count = info->count;
+}
+
+int ggp_multilogon_get_session_count(PurpleConnection *gc)
+{
+	ggp_multilogon_session_data *mldata = ggp_multilogon_get_mldata(gc);
+	return mldata->session_count;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/multilogon.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,18 @@
+#ifndef _GGP_MULTILOGON_H
+#define _GGP_MULTILOGON_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct _ggp_multilogon_session_data ggp_multilogon_session_data;
+
+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);
+
+int ggp_multilogon_get_session_count(PurpleConnection *gc);
+
+#endif /* _GGP_MULTILOGON_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth-parameter.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,154 @@
+/*
+ *  (C) Copyright 2008 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+// source: http://toxygen.net/libgadu/
+
+#include "oauth-parameter.h"
+
+struct gg_oauth_parameter {
+	char *key;
+	char *value;
+	struct gg_oauth_parameter *next;
+};
+
+int gg_oauth_parameter_set(gg_oauth_parameter_t **list, const char *key, const char *value)
+{
+	gg_oauth_parameter_t *p, *new_p;
+	char *new_key;
+	char *new_value;
+
+	if (value == NULL)
+		return 0;
+
+	if (list == NULL)
+		return -1;
+
+	new_key = strdup(key);
+
+	if (new_key == NULL)
+		return -1;
+
+	new_value = strdup(value);
+
+	if (new_value == NULL) {
+		free(new_key);
+		return -1;
+	}
+
+	new_p = malloc(sizeof(gg_oauth_parameter_t));
+
+	if (new_p == NULL) {
+		free(new_key);
+		free(new_value);
+		return -1;
+	}
+
+	memset(new_p, 0, sizeof(gg_oauth_parameter_t));
+	new_p->key = new_key;
+	new_p->value = new_value;
+
+	if (*list != NULL) {
+		p = *list;
+
+		while (p != NULL && p->next != NULL)
+			p = p->next;
+
+		p->next = new_p;
+	} else {
+		*list = new_p;
+	}
+
+	return 0;
+}
+
+char *gg_oauth_parameter_join(gg_oauth_parameter_t *list, int header)
+{
+	gg_oauth_parameter_t *p;
+	int len = 0;
+	char *res, *out;
+
+	if (header)
+		len += strlen("Authorization: OAuth ");
+
+	for (p = list; p; p = p->next) {
+		gchar *escaped;
+		len += strlen(p->key);
+
+		len += (header) ? 3 : 1;
+
+		escaped = g_uri_escape_string(p->value, NULL, FALSE);
+		len += strlen(escaped);
+		g_free(escaped);
+
+		if (p->next)
+			len += 1;
+	}
+
+	res = malloc(len + 1);
+
+	if (res == NULL)
+		return NULL;
+
+	out = res;
+
+	*out = 0;
+
+	if (header) {
+		strcpy(out, "Authorization: OAuth ");
+		out += strlen(out);
+	}
+
+	for (p = list; p; p = p->next) {
+		gchar *escaped;
+		strcpy(out, p->key);
+		out += strlen(p->key);
+
+		strcpy(out++, "=");
+
+		if (header)
+			strcpy(out++, "\"");
+
+		escaped = g_uri_escape_string(p->value, NULL, FALSE);
+		strcpy(out, escaped);
+		out += strlen(escaped);
+		g_free(escaped);
+
+		if (header)
+			strcpy(out++, "\"");
+
+		if (p->next != NULL)
+			strcpy(out++, (header) ? "," : "&");
+	}
+
+	return res;
+}
+
+void gg_oauth_parameter_free(gg_oauth_parameter_t *list)
+{
+	while (list != NULL) {
+		gg_oauth_parameter_t *next;
+
+		next = list->next;
+
+		free(list->key);
+		free(list->value);
+		free(list);
+
+		list = next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth-parameter.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,14 @@
+// source: http://toxygen.net/libgadu/
+
+#ifndef _GGP_OAUTH_PARAMETER_H
+#define _GGP_OAUTH_PARAMETER_H
+
+#include <internal.h>
+
+typedef struct gg_oauth_parameter gg_oauth_parameter_t;
+
+int gg_oauth_parameter_set(gg_oauth_parameter_t **list, const char *key, const char *value);
+char *gg_oauth_parameter_join(gg_oauth_parameter_t *list, int header);
+void gg_oauth_parameter_free(gg_oauth_parameter_t *list);
+
+#endif /* _GGP_OAUTH_PARAMETER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth-purple.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,225 @@
+#include "oauth-purple.h"
+
+#include "oauth.h"
+#include "../utils.h"
+#include "../xml.h"
+
+#include <debug.h>
+
+#define GGP_OAUTH_RESPONSE_MAX 10240
+
+typedef struct
+{
+	PurpleConnection *gc;
+	ggp_oauth_request_cb callback;
+	gpointer user_data;
+	gchar *token;
+	gchar *token_secret;
+} ggp_oauth_data;
+
+static void ggp_oauth_data_free(ggp_oauth_data *data);
+
+static void ggp_oauth_request_token_got(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+static void ggp_oauth_authorization_done(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+static void ggp_oauth_access_token_got(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message);
+
+static void ggp_oauth_data_free(ggp_oauth_data *data)
+{
+	g_free(data->token);
+	g_free(data->token_secret);
+	g_free(data);
+}
+
+void ggp_oauth_request(PurpleConnection *gc, ggp_oauth_request_cb callback,
+	gpointer user_data)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	char *auth;
+	const char *method = "POST";
+	const char *url = "http://api.gadu-gadu.pl/request_token";
+	gchar *request;
+	ggp_oauth_data *data;
+
+	purple_debug_misc("gg", "ggp_oauth_request: requesting token...\n");
+
+	auth = gg_oauth_generate_header(method, url,
+		purple_account_get_username(account),
+		purple_account_get_password(account), NULL, NULL);
+	request = g_strdup_printf(
+		"POST /request_token HTTP/1.1\r\n"
+		"Host: api.gadu-gadu.pl\r\n"
+		"%s\r\n"
+		"Content-Length: 0\r\n"
+		"\r\n",
+		auth);
+	free(auth);
+
+	data = g_new0(ggp_oauth_data, 1);
+	data->gc = gc;
+	data->callback = callback;
+	data->user_data = user_data;
+
+	purple_util_fetch_url_request(account, url, FALSE, NULL, TRUE, request,
+		FALSE, GGP_OAUTH_RESPONSE_MAX, ggp_oauth_request_token_got,
+		data);
+
+	g_free(request);
+}
+
+static void ggp_oauth_request_token_got(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	ggp_oauth_data *data = user_data;
+	PurpleAccount *account;
+	xmlnode *xml;
+	gchar *request, *request_data;
+	gboolean succ = TRUE;
+
+	if (!PURPLE_CONNECTION_IS_VALID(data->gc))
+	{
+		ggp_oauth_data_free(data);
+		return;
+	}
+	account = purple_connection_get_account(data->gc);
+
+	if (len == 0)
+	{
+		purple_debug_error("gg", "ggp_oauth_request_token_got: "
+			"requested token not received\n");
+		ggp_oauth_data_free(data);
+		return;
+	}
+
+	purple_debug_misc("gg", "ggp_oauth_request_token_got: "
+		"got request token, doing authorization...\n");
+
+	xml = xmlnode_from_str(url_text, -1);
+	if (xml == NULL)
+	{
+		purple_debug_error("gg", "ggp_oauth_request_token_got: "
+			"invalid xml\n");
+		ggp_oauth_data_free(data);
+		return;
+	}
+
+	succ &= ggp_xml_get_string(xml, "oauth_token", &data->token);
+	succ &= ggp_xml_get_string(xml, "oauth_token_secret",
+		&data->token_secret);
+	xmlnode_free(xml);
+	if (!succ)
+	{
+		purple_debug_error("gg", "ggp_oauth_request_token_got: "
+			"invalid xml - token is not present\n");
+		ggp_oauth_data_free(data);
+		return;
+	}
+
+	request_data = g_strdup_printf(
+		"callback_url=http://www.mojageneracja.pl&request_token=%s&"
+		"uin=%s&password=%s", data->token,
+		purple_account_get_username(account),
+		purple_account_get_password(account));
+	request = g_strdup_printf(
+		"POST /authorize HTTP/1.1\r\n"
+		"Host: login.gadu-gadu.pl\r\n"
+		"Content-Length: %d\r\n"
+		"Content-Type: application/x-www-form-urlencoded\r\n"
+		"\r\n%s",
+		strlen(request_data), request_data);
+	g_free(request_data);
+
+	// we don't need any results, nor 302 redirection
+	purple_util_fetch_url_request(account,
+		"https://login.gadu-gadu.pl/authorize", FALSE, NULL, TRUE,
+		request, FALSE, 0,
+		ggp_oauth_authorization_done, data);
+
+	g_free(request);
+}
+
+static void ggp_oauth_authorization_done(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	ggp_oauth_data *data = user_data;
+	PurpleAccount *account;
+	char *auth;
+	const char *url = "http://api.gadu-gadu.pl/access_token";
+	gchar *request;
+	
+	if (!PURPLE_CONNECTION_IS_VALID(data->gc))
+	{
+		ggp_oauth_data_free(data);
+		return;
+	}
+	account = purple_connection_get_account(data->gc);
+
+	purple_debug_misc("gg", "ggp_oauth_authorization_done: "
+		"authorization done, requesting access token...\n");
+
+	auth = gg_oauth_generate_header("POST", url,
+		purple_account_get_username(account),
+		purple_account_get_password(account),
+		data->token, data->token_secret);
+
+	request = g_strdup_printf(
+		"POST /access_token HTTP/1.1\r\n"
+		"Host: api.gadu-gadu.pl\r\n"
+		"%s\r\n"
+		"Content-Length: 0\r\n"
+		"\r\n",
+		auth);
+	free(auth);
+	
+	purple_util_fetch_url_request(account, url, FALSE, NULL, TRUE, request,
+		FALSE, GGP_OAUTH_RESPONSE_MAX, ggp_oauth_access_token_got,
+		data);
+
+	g_free(request);
+}
+
+static void ggp_oauth_access_token_got(PurpleUtilFetchUrlData *url_data,
+	gpointer user_data, const gchar *url_text, gsize len,
+	const gchar *error_message)
+{
+	ggp_oauth_data *data = user_data;
+	gchar *token;
+	xmlnode *xml;
+	gboolean succ = TRUE;
+
+	xml = xmlnode_from_str(url_text, -1);
+	if (xml == NULL)
+	{
+		purple_debug_error("gg", "ggp_oauth_access_token_got: "
+			"invalid xml\n");
+		ggp_oauth_data_free(data);
+		return;
+	}
+
+	succ &= ggp_xml_get_string(xml, "oauth_token", &token);
+	xmlnode_free(xml);
+	if (!succ || strlen(token) < 10)
+	{
+		purple_debug_error("gg", "ggp_oauth_access_token_got: "
+			"invalid xml - token is not present\n");
+		ggp_oauth_data_free(data);
+		return;
+	}
+
+	purple_debug_misc("gg", "ggp_oauth_access_token_got: "
+		"got access token\n");
+
+	data->callback(data->gc, token, data->user_data);
+
+	g_free(token);
+	ggp_oauth_data_free(data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth-purple.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,13 @@
+#ifndef _GGP_OAUTH_PURPLE_H
+#define _GGP_OAUTH_PURPLE_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef void (*ggp_oauth_request_cb)(PurpleConnection *gc, const gchar *token,
+	gpointer user_data);
+
+void ggp_oauth_request(PurpleConnection *gc, ggp_oauth_request_cb callback,
+	gpointer user_data);
+
+#endif /* _GGP_OAUTH_PURPLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,139 @@
+/*
+ *  (C) Copyright 2008 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+// source: http://toxygen.net/libgadu/
+
+#include "oauth.h"
+
+#include "oauth-parameter.h"
+#include <cipher.h>
+
+char *gg_oauth_static_nonce;		/* dla unit testów */
+char *gg_oauth_static_timestamp;	/* dla unit testów */
+
+static void gg_oauth_generate_nonce(char *buf, int len)
+{
+	const char charset[] = "0123456789";
+
+	if (buf == NULL || len < 1)
+		return;
+
+	while (len > 1) {
+		*buf++ = charset[(unsigned) (((float) sizeof(charset) - 1.0) * rand() / (RAND_MAX + 1.0))];
+		len--;
+	}
+
+	*buf = 0;
+}
+
+static gchar *gg_hmac_sha1(const char *key, const char *message)
+{
+	PurpleCipherContext *context;
+	guchar digest[20];
+	
+	context = purple_cipher_context_new_by_name("hmac", NULL);
+	purple_cipher_context_set_option(context, "hash", "sha1");
+	purple_cipher_context_set_key(context, (guchar *)key);
+	purple_cipher_context_append(context, (guchar *)message, strlen(message));
+	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(context);
+	
+	return purple_base64_encode(digest, sizeof(digest));
+}
+
+static char *gg_oauth_generate_signature(const char *method, const char *url, const char *request, const char *consumer_secret, const char *token_secret)
+{
+	char *text, *key, *res;
+	gchar *url_e, *request_e, *consumer_secret_e, *token_secret_e;
+
+	url_e = g_uri_escape_string(url, NULL, FALSE);
+	request_e = g_uri_escape_string(request, NULL, FALSE);
+	text = g_strdup_printf("%s&%s&%s", method, url_e, request_e);
+	g_free(url_e);
+	g_free(request_e);
+
+	consumer_secret_e = g_uri_escape_string(consumer_secret, NULL, FALSE);
+	token_secret_e = g_uri_escape_string(token_secret, NULL, FALSE);
+	key = g_strdup_printf("%s&%s", consumer_secret, token_secret ? token_secret : "");
+	g_free(consumer_secret_e);
+	g_free(token_secret_e);
+
+	res = gg_hmac_sha1(key, text);
+
+	free(key);
+	free(text);
+
+	return res;
+}
+
+char *gg_oauth_generate_header(const char *method, const char *url, const const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret)
+{
+	char *request, *signature, *res;
+	char nonce[80], timestamp[16];
+	gg_oauth_parameter_t *params = NULL;
+
+	if (gg_oauth_static_nonce == NULL)
+		gg_oauth_generate_nonce(nonce, sizeof(nonce));
+	else {
+		strncpy(nonce, gg_oauth_static_nonce, sizeof(nonce) - 1);
+		nonce[sizeof(nonce) - 1] = 0;
+	}
+
+	if (gg_oauth_static_timestamp == NULL)
+		snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL));
+	else {
+		strncpy(timestamp, gg_oauth_static_timestamp, sizeof(timestamp) - 1);
+		timestamp[sizeof(timestamp) - 1] = 0;
+	}
+
+	gg_oauth_parameter_set(&params, "oauth_consumer_key", consumer_key);
+	gg_oauth_parameter_set(&params, "oauth_nonce", nonce);
+	gg_oauth_parameter_set(&params, "oauth_signature_method", "HMAC-SHA1");
+	gg_oauth_parameter_set(&params, "oauth_timestamp", timestamp);
+	gg_oauth_parameter_set(&params, "oauth_token", token);
+	gg_oauth_parameter_set(&params, "oauth_version", "1.0");
+
+	request = gg_oauth_parameter_join(params, 0);
+
+	signature = gg_oauth_generate_signature(method, url, request, consumer_secret, token_secret);
+
+	free(request);
+
+	gg_oauth_parameter_free(params);
+	params = NULL;
+
+	if (signature == NULL)
+		return NULL;
+
+	gg_oauth_parameter_set(&params, "oauth_version", "1.0");
+	gg_oauth_parameter_set(&params, "oauth_nonce", nonce);
+	gg_oauth_parameter_set(&params, "oauth_timestamp", timestamp);
+	gg_oauth_parameter_set(&params, "oauth_consumer_key", consumer_key);
+	gg_oauth_parameter_set(&params, "oauth_token", token);
+	gg_oauth_parameter_set(&params, "oauth_signature_method", "HMAC-SHA1");
+	gg_oauth_parameter_set(&params, "oauth_signature", signature);
+
+	free(signature);
+
+	res = gg_oauth_parameter_join(params, 1);
+
+	gg_oauth_parameter_free(params);
+
+	return res;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/oauth/oauth.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,11 @@
+// source: http://toxygen.net/libgadu/
+
+#ifndef _GGP_OAUTH_H
+#define _GGP_OAUTH_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+char *gg_oauth_generate_header(const char *method, const char *url, const const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret);
+
+#endif /* _GGP_OAUTH_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/purplew.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,120 @@
+#include "purplew.h"
+
+#include <request.h>
+#include <debug.h>
+
+guint ggp_purplew_http_input_add(struct gg_http *http_req,
+	PurpleInputFunction func, gpointer user_data)
+{
+	PurpleInputCondition cond = 0;
+	int check = http_req->check;
+
+	if (check & GG_CHECK_READ)
+		cond |= PURPLE_INPUT_READ;
+	if (check & GG_CHECK_WRITE)
+		cond |= PURPLE_INPUT_WRITE;
+
+	//TODO: verbose mode
+	//purple_debug_misc("gg", "ggp_purplew_http_input_add: "
+	//	"[req=%x, fd=%d, cond=%d]\n",
+	//	(unsigned int)http_req, http_req->fd, cond);
+	return purple_input_add(http_req->fd, cond, func, user_data);
+}
+
+static void ggp_purplew_request_processing_cancel(
+	ggp_purplew_request_processing_handle *handle, gint id)
+{
+	handle->cancel_cb(handle->gc, handle->user_data);
+	g_free(handle);
+}
+
+ggp_purplew_request_processing_handle * ggp_purplew_request_processing(
+	PurpleConnection *gc, const gchar *msg, void *user_data,
+	ggp_purplew_request_processing_cancel_cb cancel_cb)
+{
+	ggp_purplew_request_processing_handle *handle =
+		g_new(ggp_purplew_request_processing_handle, 1);
+
+	handle->gc = gc;
+	handle->cancel_cb = cancel_cb;
+	handle->user_data = user_data;
+	handle->request_handle = purple_request_action(gc, _("Please wait..."),
+		(msg ? msg : _("Please wait...")), NULL,
+		PURPLE_DEFAULT_ACTION_NONE, purple_connection_get_account(gc),
+		NULL, NULL, handle, 1,
+		_("Cancel"), G_CALLBACK(ggp_purplew_request_processing_cancel));
+	
+	return handle;
+}
+
+void ggp_purplew_request_processing_done(
+	ggp_purplew_request_processing_handle *handle)
+{
+	purple_request_close(PURPLE_REQUEST_ACTION, handle->request_handle);
+	g_free(handle);
+}
+
+PurpleGroup * ggp_purplew_buddy_get_group_only(PurpleBuddy *buddy)
+{
+	PurpleGroup *group = purple_buddy_get_group(buddy);
+	if (!group)
+		return NULL;
+	if (0 == strcmp(GGP_PURPLEW_GROUP_DEFAULT, purple_group_get_name(group)))
+		return NULL;
+	return group;
+}
+
+GList * ggp_purplew_group_get_buddies(PurpleGroup *group, PurpleAccount *account)
+{
+	GList *buddies = NULL;
+	PurpleBlistNode *gnode, *cnode, *bnode;
+	
+	g_return_val_if_fail(group != NULL, NULL);
+	
+	gnode = PURPLE_BLIST_NODE(group);
+	for (cnode = gnode->child; cnode; cnode = cnode->next)
+	{
+		if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
+			continue;
+		for (bnode = cnode->child; bnode; bnode = bnode->next)
+		{
+			PurpleBuddy *buddy;
+			if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+				continue;
+			
+			buddy = PURPLE_BUDDY(bnode);
+			if (account == NULL || buddy->account == account)
+				buddies = g_list_append(buddies, buddy);
+		}
+	}
+	
+	return buddies;
+}
+
+GList * ggp_purplew_account_get_groups(PurpleAccount *account, gboolean exclusive)
+{
+	PurpleBlistNode *bnode;
+	GList *groups = NULL;
+	for (bnode = purple_blist_get_root(); bnode; bnode = bnode->next)
+	{
+		PurpleGroup *group;
+		GSList *accounts;
+		gboolean have_specified = FALSE, have_others = FALSE;
+		
+		if (!PURPLE_BLIST_NODE_IS_GROUP(bnode))
+			continue;
+		
+		group = PURPLE_GROUP(bnode);
+		for (accounts = purple_group_get_accounts(group); accounts; accounts = g_slist_delete_link(accounts, accounts))
+		{
+			if (accounts->data == account)
+				have_specified = TRUE;
+			else
+				have_others = TRUE;
+		}
+		
+		if (have_specified && (!exclusive || !have_others))
+			groups = g_list_append(groups, group);
+	}
+	return groups;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/purplew.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,49 @@
+#ifndef _GGP_PURPLEW_H
+#define _GGP_PURPLEW_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+#define GGP_PURPLEW_GROUP_DEFAULT _("Buddies")
+
+/**
+ * Adds an input handler in purple event loop for http request.
+ *
+ * @see purple_input_add
+ *
+ * @param http_req  Http connection to watch.
+ * @param func      The callback function for data.
+ * @param user_data User-specified data.
+ *
+ * @return The resulting handle (will be greater than 0).
+ */
+guint ggp_purplew_http_input_add(struct gg_http *http_req,
+	PurpleInputFunction func, gpointer user_data);
+
+typedef void (*ggp_purplew_request_processing_cancel_cb)(PurpleConnection *gc,
+	void *user_data);
+
+typedef struct
+{
+	PurpleConnection *gc;
+	ggp_purplew_request_processing_cancel_cb cancel_cb;
+	void *request_handle;
+	void *user_data;
+} ggp_purplew_request_processing_handle;
+
+ggp_purplew_request_processing_handle * ggp_purplew_request_processing(
+	PurpleConnection *gc, const gchar *msg, void *user_data,
+	ggp_purplew_request_processing_cancel_cb oncancel);
+
+void ggp_purplew_request_processing_done(
+	ggp_purplew_request_processing_handle *handle);
+
+// ignores default group
+PurpleGroup * ggp_purplew_buddy_get_group_only(PurpleBuddy *buddy);
+
+GList * ggp_purplew_group_get_buddies(PurpleGroup *group, PurpleAccount *account);
+
+// you must g_free returned list
+GList * ggp_purplew_account_get_groups(PurpleAccount *account, gboolean exclusive);
+
+#endif /* _GGP_PURPLEW_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/resolver-purple.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,163 @@
+#include <internal.h>
+#include <debug.h>
+#include <dnsquery.h>
+
+#include <libgadu.h>
+#include "resolver-purple.h"
+
+static int ggp_resolver_purple_start(int *fd, void **private_data,
+	const char *hostname);
+
+static void ggp_resolver_purple_cleanup(void **private_data, int force);
+
+static void ggp_resolver_purple_cb(GSList *hosts, gpointer cbdata,
+	const char *error_message);
+
+typedef struct
+{
+	PurpleDnsQueryData *purpleQuery;
+	
+	/**
+	 * File descriptors:
+	 *  pipes[0] - for reading
+	 *  pipes[1] - for writing
+	 */
+	int pipes[2];
+} ggp_resolver_purple_data;
+
+
+extern void ggp_resolver_purple_setup(void)
+{
+	if (gg_global_set_custom_resolver(ggp_resolver_purple_start,
+		ggp_resolver_purple_cleanup) != 0)
+	{
+		purple_debug_error("gg", "failed to set custom resolver\n");
+	}
+}
+
+void ggp_resolver_purple_cb(GSList *hosts, gpointer cbdata,
+	const char *error_message)
+{
+	ggp_resolver_purple_data *data = (ggp_resolver_purple_data*)cbdata;
+	const int fd = data->pipes[1];
+	int ipv4_count, all_count, write_size;
+	struct in_addr *addresses;
+	
+	purple_debug_misc("gg", "ggp_resolver_purple_cb(%x, %x, \"%s\")\n",
+		(unsigned int)hosts, (unsigned int)cbdata, error_message);
+	
+	if (error_message)
+	{
+		purple_debug_error("gg", "ggp_resolver_purple_cb failed: %s\n",
+			error_message);
+	}
+	
+	all_count = g_slist_length(hosts);
+	g_assert(all_count % 2 == 0);
+	all_count /= 2;
+	addresses = malloc((all_count + 1) * sizeof(struct in_addr));
+	
+	ipv4_count = 0;
+	while (hosts && (hosts = g_slist_delete_link(hosts, hosts)))
+	{
+		const struct sockaddr *addr = hosts->data;
+		char dst[INET6_ADDRSTRLEN];
+		
+		if (addr->sa_family == AF_INET6)
+		{
+			inet_ntop(addr->sa_family,
+				&((struct sockaddr_in6 *) addr)->sin6_addr,
+				dst, sizeof(dst));
+			purple_debug_misc("gg", "ggp_resolver_purple_cb "
+				"ipv6 (ignore): %s\n", dst);
+		}
+		else if (addr->sa_family == AF_INET)
+		{
+			const struct in_addr addr_ipv4 =
+				((struct sockaddr_in *) addr)->sin_addr;
+			inet_ntop(addr->sa_family, &addr_ipv4,
+				dst, sizeof(dst));
+			purple_debug_misc("gg", "ggp_resolver_purple_cb "
+				"ipv4: %s\n", dst);
+			
+			g_assert(ipv4_count < all_count);
+			addresses[ipv4_count++] = addr_ipv4;
+		}
+		else
+		{
+			purple_debug_warning("gg", "ggp_resolver_purple_cb "
+				"unexpected sa_family: %d\n", addr->sa_family);
+		}
+		
+		g_free(hosts->data);
+		hosts = g_slist_delete_link(hosts, hosts);
+	}
+	
+	addresses[ipv4_count].s_addr = INADDR_NONE;
+	
+	write_size = (ipv4_count + 1) * sizeof(struct in_addr);
+	if (write(fd, addresses, write_size) != write_size)
+	{
+		purple_debug_error("gg",
+			"ggp_resolver_purple_cb write error\n");
+	}
+	free(addresses);
+}
+
+int ggp_resolver_purple_start(int *fd, void **private_data,
+	const char *hostname)
+{
+	ggp_resolver_purple_data *data;
+	purple_debug_misc("gg", "ggp_resolver_purple_start(%x, %x, \"%s\")\n",
+		(unsigned int)fd, (unsigned int)private_data, hostname);
+	
+	data = malloc(sizeof(ggp_resolver_purple_data));
+	*private_data = (void*)data;
+	data->purpleQuery = NULL;
+	data->pipes[0] = 0;
+	data->pipes[1] = 0;
+	
+	if (purple_input_pipe(data->pipes) != 0)
+	{
+		purple_debug_error("gg", "ggp_resolver_purple_start: "
+			"unable to create pipe\n");
+		ggp_resolver_purple_cleanup(private_data, 0);
+		return -1;
+	}
+	
+	*fd = data->pipes[0];
+	
+	/* account and port is unknown in this context */
+	data->purpleQuery = purple_dnsquery_a(NULL, hostname, 80,
+		ggp_resolver_purple_cb, (gpointer)data);
+
+	if (!data->purpleQuery)
+	{
+		purple_debug_error("gg", "ggp_resolver_purple_start: "
+			"unable to call purple_dnsquery_a\n");
+		ggp_resolver_purple_cleanup(private_data, 0);
+		return -1;
+	}
+	
+	return 0;
+}
+
+void ggp_resolver_purple_cleanup(void **private_data, int force)
+{
+	ggp_resolver_purple_data *data =
+		(ggp_resolver_purple_data*)(*private_data);
+	
+	purple_debug_misc("gg", "ggp_resolver_purple_cleanup(%x, %d)\n",
+		(unsigned int)private_data, force);
+	
+	if (!data)
+		return;
+	*private_data = NULL;
+	
+	if (data->pipes[0])
+		close(data->pipes[0]);
+	if (data->pipes[1])
+		close(data->pipes[1]);
+	
+	free(data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/resolver-purple.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,9 @@
+#ifndef _GGP_RESOLVER_PURPLE_H
+#define _GGP_RESOLVER_PURPLE_H
+
+/**
+ * Registers custom resolver for libgadu, that uses libpurple for DNS queries.
+ */
+void ggp_resolver_purple_setup(void);
+
+#endif /* _GGP_RESOLVER_PURPLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/roster.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,1090 @@
+#include "roster.h"
+
+#include "gg.h"
+#include "xml.h"
+#include "utils.h"
+#include "purplew.h"
+
+#include <debug.h>
+
+#define GGP_ROSTER_SYNC_SETT "gg-synchronized"
+#define GGP_ROSTER_DEBUG 0
+#define GGP_ROSTER_GROUPID_DEFAULT "00000000-0000-0000-0000-000000000000"
+#define GGP_ROSTER_GROUPID_BOTS "0b345af6-0001-0000-0000-000000000004"
+
+// TODO: ignored contacts synchronization (?)
+
+typedef struct
+{
+	int version;
+	
+	xmlnode *xml;
+	
+	xmlnode *groups_node, *contacts_node;
+	
+	/**
+	 * Key: (uin_t) user identifier
+	 * Value: (xmlnode*) xml node for contact
+	 */
+	GHashTable *contact_nodes;
+	
+	/**
+	 * Key: (gchar*) group id
+	 * Value: (xmlnode*) xml node for group
+	 */
+	GHashTable *group_nodes;
+	
+	/**
+	 * Key: (gchar*) group name
+	 * Value: (gchar*) group id
+	 */
+	GHashTable *group_ids;
+	
+	/**
+	 * Key: (gchar*) group id
+	 * Value: (gchar*) group name
+	 */
+	GHashTable *group_names;
+
+	gchar *bots_group_id;
+
+	gboolean needs_update;
+} ggp_roster_content;
+
+typedef struct
+{
+	enum
+	{
+		GGP_ROSTER_CHANGE_CONTACT_UPDATE,
+		GGP_ROSTER_CHANGE_CONTACT_REMOVE,
+		GGP_ROSTER_CHANGE_GROUP_RENAME,
+	} type;
+	union
+	{
+		uin_t uin;
+		struct
+		{
+			gchar *old_name;
+			gchar *new_name;
+		} group_rename;
+	} data;
+} ggp_roster_change;
+
+static inline ggp_roster_session_data *
+ggp_roster_get_rdata(PurpleConnection *gc);
+static void ggp_roster_content_free(ggp_roster_content *content);
+static void ggp_roster_change_free(gpointer change);
+static int ggp_roster_get_version(PurpleConnection *gc);
+static gboolean ggp_roster_timer_cb(gpointer _gc);
+#if GGP_ROSTER_DEBUG
+static void ggp_roster_dump(ggp_roster_content *content);
+#endif
+
+// synchronization control
+static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy);
+static void ggp_roster_set_synchronized(PurpleConnection *gc,
+	PurpleBuddy *buddy, gboolean synchronized);
+
+// buddy list import
+static gboolean ggp_roster_reply_list_read_group(xmlnode *node,
+	ggp_roster_content *content);
+static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc,
+	xmlnode *node, ggp_roster_content *content, GHashTable *remove_buddies);
+static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version,
+	const char *reply);
+
+// buddy list export
+static const gchar * ggp_roster_send_update_group_add(
+	ggp_roster_content *content, PurpleGroup *group);
+static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc,
+	ggp_roster_change *change);
+static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc,
+	ggp_roster_change *change);
+static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc,
+	ggp_roster_change *change);
+static void ggp_roster_send_update(PurpleConnection *gc);
+static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version);
+static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version);
+
+/******************************************************************************/
+
+static inline ggp_roster_session_data *
+ggp_roster_get_rdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return &accdata->roster_data;
+} 
+
+static void ggp_roster_content_free(ggp_roster_content *content)
+{
+	if (content == NULL)
+		return;
+	if (content->xml)
+		xmlnode_free(content->xml);
+	if (content->contact_nodes)
+		g_hash_table_destroy(content->contact_nodes);
+	if (content->group_nodes)
+		g_hash_table_destroy(content->group_nodes);
+	if (content->group_ids)
+		g_hash_table_destroy(content->group_ids);
+	if (content->group_names)
+		g_hash_table_destroy(content->group_names);
+	if (content->bots_group_id)
+		g_free(content->bots_group_id);
+	g_free(content);
+}
+
+static void ggp_roster_change_free(gpointer _change)
+{
+	ggp_roster_change *change = _change;
+	
+	if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME)
+	{
+		g_free(change->data.group_rename.old_name);
+		g_free(change->data.group_rename.new_name);
+	}
+	
+	g_free(change);
+}
+
+static int ggp_roster_get_version(PurpleConnection *gc)
+{
+	ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
+	if (content == NULL)
+		return 0;
+	return content->version;
+}
+
+static gboolean ggp_roster_timer_cb(gpointer _gc)
+{
+	PurpleConnection *gc = _gc;
+	
+	g_return_val_if_fail(PURPLE_CONNECTION_IS_VALID(gc), FALSE);
+	
+	ggp_roster_send_update(gc);
+	
+	return TRUE;
+}
+
+#if GGP_ROSTER_DEBUG
+static void ggp_roster_dump(ggp_roster_content *content)
+{
+	char *str;
+	int len;
+	
+	g_return_if_fail(content != NULL);
+	g_return_if_fail(content->xml != NULL);
+	
+	str = xmlnode_to_formatted_str(content->xml, &len);
+	purple_debug_misc("gg", "ggp_roster_dump: [%s]\n", str);
+	g_free(str);
+}
+#endif
+
+/*******************************************************************************
+ * Setup.
+ ******************************************************************************/
+
+gboolean ggp_roster_enabled(void)
+{
+	static gboolean checked = FALSE;
+	static gboolean enabled;
+	
+	if (!checked)
+	{
+		enabled = gg_libgadu_check_feature(
+			GG_LIBGADU_FEATURE_USERLIST100);
+		checked = TRUE;
+	}
+	return enabled;
+}
+
+void ggp_roster_setup(PurpleConnection *gc)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+
+	rdata->content = NULL;
+	rdata->sent_updates = NULL;
+	rdata->pending_updates = NULL;
+	rdata->timer = 0;
+	rdata->is_updating = FALSE;
+	
+	if (ggp_roster_enabled())
+		rdata->timer = purple_timeout_add_seconds(2,
+			ggp_roster_timer_cb, gc);
+}
+
+void ggp_roster_cleanup(PurpleConnection *gc)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+
+	if (rdata->timer)
+		purple_timeout_remove(rdata->timer);
+	ggp_roster_content_free(rdata->content);
+	g_list_free_full(rdata->sent_updates, ggp_roster_change_free);
+	g_list_free_full(rdata->pending_updates, ggp_roster_change_free);
+}
+
+/*******************************************************************************
+ * Synchronization control.
+ ******************************************************************************/
+
+static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy)
+{
+	gboolean ret = purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy),
+		GGP_ROSTER_SYNC_SETT);
+	return ret;
+}
+
+static void ggp_roster_set_synchronized(PurpleConnection *gc,
+	PurpleBuddy *buddy, gboolean synchronized)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
+	ggp_roster_change *change;
+	
+	purple_blist_node_set_bool(PURPLE_BLIST_NODE(buddy),
+		GGP_ROSTER_SYNC_SETT, synchronized);
+	if (!synchronized)
+	{
+		change = g_new0(ggp_roster_change, 1);
+		change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
+		change->data.uin = uin;
+		rdata->pending_updates =
+			g_list_append(rdata->pending_updates, change);
+	}
+}
+
+void ggp_roster_request_update(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	int local_version = ggp_roster_get_version(gc);
+	
+	if (!ggp_roster_enabled())
+	{
+		purple_debug_warning("gg", "ggp_roster_request_update: "
+			"feature disabled\n");
+		return;
+	}
+	
+	purple_debug_info("gg", "ggp_roster_request_update: local=%u\n",
+		local_version);
+	
+	gg_userlist100_request(accdata->session, GG_USERLIST100_GET,
+		local_version, GG_USERLIST100_FORMAT_TYPE_GG100, NULL);
+}
+
+/*******************************************************************************
+ * Libgadu callbacks.
+ ******************************************************************************/
+
+void ggp_roster_reply(PurpleConnection *gc,
+	struct gg_event_userlist100_reply *reply)
+{
+	if (GG_USERLIST100_FORMAT_TYPE_GG100 != reply->format_type)
+	{
+		purple_debug_warning("gg", "ggp_roster_reply: "
+			"unsupported format type (%x)\n", reply->format_type);
+		return;
+	}
+	
+	if (reply->type == GG_USERLIST100_REPLY_LIST)
+		ggp_roster_reply_list(gc, reply->version, reply->reply);
+	else if (reply->type == 0x01) // list up to date (TODO: push to libgadu)
+		purple_debug_info("gg", "ggp_roster_reply: list up to date\n");
+	else if (reply->type == GG_USERLIST100_REPLY_ACK)
+		ggp_roster_reply_ack(gc, reply->version);
+	else if (reply->type == GG_USERLIST100_REPLY_REJECT)
+		ggp_roster_reply_reject(gc, reply->version);
+	else
+		purple_debug_error("gg", "ggp_roster_reply: "
+			"unsupported reply (%x)\n", reply->type);
+}
+
+void ggp_roster_version(PurpleConnection *gc,
+	struct gg_event_userlist100_version *version)
+{
+	int local_version = ggp_roster_get_version(gc);
+	int remote_version = version->version;
+
+	purple_debug_info("gg", "ggp_roster_version: local=%u, remote=%u\n",
+		local_version, remote_version);
+	
+	if (local_version < remote_version)
+		ggp_roster_request_update(gc);
+}
+
+/*******************************************************************************
+ * Libpurple callbacks.
+ ******************************************************************************/
+
+void ggp_roster_alias_buddy(PurpleConnection *gc, const char *who,
+	const char *alias)
+{
+	PurpleBuddy *buddy;
+	
+	g_return_if_fail(who != NULL);
+	
+	if (!ggp_roster_enabled())
+		return;
+	
+	purple_debug_misc("gg", "ggp_roster_alias_buddy(\"%s\", \"%s\")\n",
+		who, alias);
+	
+	buddy = purple_find_buddy(purple_connection_get_account(gc), who);
+	g_return_if_fail(buddy != NULL);
+	
+	ggp_roster_set_synchronized(gc, buddy, FALSE);
+}
+
+void ggp_roster_group_buddy(PurpleConnection *gc, const char *who,
+	const char *old_group, const char *new_group)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	ggp_roster_change *change = g_new0(ggp_roster_change, 1);
+
+	if (!ggp_roster_enabled())
+		return;
+	if (rdata->is_updating)
+		return;
+	
+	purple_debug_misc("gg", "ggp_roster_group_buddy: "
+		"who=\"%s\", group=\"%s\" -> \"%s\")\n",
+		who, old_group, new_group);
+	
+	// purple_find_buddy(..., who) is not accessible at this moment
+	change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
+	change->data.uin = ggp_str_to_uin(who);
+	rdata->pending_updates = g_list_append(rdata->pending_updates, change);
+}
+
+void ggp_roster_rename_group(PurpleConnection *gc, const char *old_name,
+	PurpleGroup *group, GList *moved_buddies)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	ggp_roster_change *change = g_new0(ggp_roster_change, 1);
+	
+	if (!ggp_roster_enabled())
+		return;
+	
+	change->type = GGP_ROSTER_CHANGE_GROUP_RENAME;
+	change->data.group_rename.old_name = g_strdup(old_name);
+	change->data.group_rename.new_name =
+		g_strdup(purple_group_get_name(group));
+	rdata->pending_updates = g_list_append(rdata->pending_updates, change);
+}
+
+void ggp_roster_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
+	PurpleGroup *group, const char *message)
+{
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(buddy != NULL);
+
+	if (!ggp_roster_enabled())
+		return;
+	
+	ggp_roster_set_synchronized(gc, buddy, FALSE);
+}
+
+void ggp_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
+	PurpleGroup *group)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	ggp_roster_change *change = g_new0(ggp_roster_change, 1);
+	
+	if (!ggp_roster_enabled())
+		return;
+	
+	change->type = GGP_ROSTER_CHANGE_CONTACT_REMOVE;
+	change->data.uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
+	rdata->pending_updates = g_list_append(rdata->pending_updates, change);
+}
+
+/*******************************************************************************
+ * Buddy list import.
+ ******************************************************************************/
+
+static gboolean ggp_roster_reply_list_read_group(xmlnode *node,
+	ggp_roster_content *content)
+{
+	char *name, *id;
+	gboolean removable;
+	gboolean succ = TRUE, is_bot, is_default;
+	
+	succ &= ggp_xml_get_string(node, "Id", &id);
+	succ &= ggp_xml_get_string(node, "Name", &name);
+	succ &= ggp_xml_get_bool(node, "IsRemovable", &removable);
+	
+	if (!succ)
+	{
+		g_free(id);
+		g_free(name);
+		g_return_val_if_reached(FALSE);
+	}
+	
+	is_bot = (strcmp(id, GGP_ROSTER_GROUPID_BOTS) == 0 ||
+		g_strcmp0(name, "Pomocnicy") == 0);
+	is_default = (strcmp(id, GGP_ROSTER_GROUPID_DEFAULT) == 0 ||
+		g_strcmp0(name, GGP_PURPLEW_GROUP_DEFAULT) == 0 ||
+		g_strcmp0(name, "[default]") == 0);
+	
+	if (!content->bots_group_id && is_bot)
+		content->bots_group_id = g_strdup(id);
+	
+	if (!removable || is_bot || is_default)
+	{
+		g_free(id);
+		g_free(name);
+		return TRUE;
+	}
+	
+	g_hash_table_insert(content->group_nodes, g_strdup(id), node);
+	g_hash_table_insert(content->group_ids, g_strdup(name), g_strdup(id));
+	g_hash_table_insert(content->group_names, id, name);
+	
+	return TRUE;
+}
+
+static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc,
+	xmlnode *node, ggp_roster_content *content, GHashTable *remove_buddies)
+{
+	gchar *alias, *group_name;
+	uin_t uin;
+	gboolean succ = TRUE;
+	xmlnode *group_list, *group_elem;
+	PurpleBuddy *buddy = NULL;
+	PurpleGroup *group = NULL;
+	PurpleGroup *currentGroup;
+	gboolean alias_changed;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	
+	succ &= ggp_xml_get_string(node, "ShowName", &alias);
+	succ &= ggp_xml_get_uint(node, "GGNumber", &uin);
+	
+	group_list = xmlnode_get_child(node, "Groups");
+	succ &= (group_list != NULL);
+	
+	if (!succ)
+	{
+		g_free(alias);
+		g_return_val_if_reached(FALSE);
+	}
+	
+	g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin), node);
+	
+	// check, if alias is set
+	if (strlen(alias) == 0 ||
+		strcmp(alias, ggp_uin_to_str(uin)) == 0)
+	{
+		g_free(alias);
+		alias = NULL;
+	}
+	
+	// getting (eventually creating) group
+	group_elem = xmlnode_get_child(group_list, "GroupId");
+	while (group_elem != NULL)
+	{
+		gchar *id;
+		gboolean isbot;
+		
+		if (!ggp_xml_get_string(group_elem, NULL, &id))
+			continue;
+		isbot = (0 == g_strcmp0(id, content->bots_group_id));
+		group_name = g_hash_table_lookup(content->group_names, id);
+		g_free(id);
+		
+		// we don't want to import bots;
+		// they are inserted to roster by default
+		if (isbot)
+		{
+			g_free(alias);
+			return TRUE;
+		}
+		
+		if (group_name != NULL)
+			break;
+		
+		group_elem = xmlnode_get_next_twin(group_elem);
+	}
+	if (group_name)
+	{
+		group = purple_find_group(group_name);
+		if (!group)
+		{
+			group = purple_group_new(group_name);
+			purple_blist_add_group(group, NULL);
+		}
+	}
+	
+	// add buddy, if doesn't exists
+	buddy = purple_find_buddy(account, ggp_uin_to_str(uin));
+	g_hash_table_remove(remove_buddies, GINT_TO_POINTER(uin));
+	if (!buddy)
+	{
+		purple_debug_info("gg", "ggp_roster_reply_list_read_buddy: "
+			"adding %u (%s) to buddy list\n", uin, alias);
+		buddy = purple_buddy_new(account, ggp_uin_to_str(uin), alias);
+		purple_blist_add_buddy(buddy, NULL, group, NULL);
+		ggp_roster_set_synchronized(gc, buddy, TRUE);
+		
+		g_free(alias);
+		return TRUE;
+	}
+	
+	// buddy exists, but is not synchronized - local list has priority
+	if (!ggp_roster_is_synchronized(buddy))
+	{
+		purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: "
+			"ignoring not synchronized %u (%s)\n",
+			uin, purple_buddy_get_name(buddy));
+		g_free(alias);
+		return TRUE;
+	}
+	
+	currentGroup = ggp_purplew_buddy_get_group_only(buddy);
+	alias_changed =
+		(0 != g_strcmp0(alias, purple_buddy_get_alias_only(buddy)));
+	
+	if (currentGroup == group && !alias_changed)
+	{
+		g_free(alias);
+		return TRUE;
+	}
+	
+	purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: "
+		"updating %u (%s) - alias=\"%s\"->\"%s\", group=%p->%p (%s)\n",
+		uin, purple_buddy_get_name(buddy),
+		purple_buddy_get_alias(buddy), alias,
+		currentGroup, group, group_name);
+	if (alias_changed)
+		purple_blist_alias_buddy(buddy, alias);
+	if (currentGroup != group)
+		purple_blist_add_buddy(buddy, NULL, group, NULL);
+	
+	g_free(alias);
+	return TRUE;
+}
+
+static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version,
+	const char *data)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	xmlnode *xml, *xml_it;
+	PurpleAccount *account;
+	GSList *local_buddies;
+	GHashTable *remove_buddies;
+	GList *update_buddies = NULL, *local_groups, *it, *table_values;
+	ggp_roster_content *content;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(data != NULL);
+
+	account = purple_connection_get_account(gc);
+	
+	purple_debug_info("gg", "ggp_roster_reply_list: got list, version=%u\n",
+		version);
+
+	xml = xmlnode_from_str(data, -1);
+	if (xml == NULL)
+	{
+		purple_debug_warning("gg", "ggp_roster_reply_list: "
+			"invalid xml\n");
+		return;
+	}
+
+	ggp_roster_content_free(rdata->content);
+	rdata->content = NULL;
+	rdata->is_updating = TRUE;
+	content = g_new0(ggp_roster_content, 1);
+	content->version = version;
+	content->xml = xml;
+	content->contact_nodes = g_hash_table_new(NULL, NULL);
+	content->group_nodes = g_hash_table_new_full(
+		g_str_hash, g_str_equal, g_free, NULL);
+	content->group_ids = g_hash_table_new_full(
+		g_str_hash, g_str_equal, g_free, g_free);
+	content->group_names = g_hash_table_new_full(
+		g_str_hash, g_str_equal, g_free, g_free);
+
+#if GGP_ROSTER_DEBUG
+	ggp_roster_dump(content);
+#endif
+
+	// reading groups
+	content->groups_node = xmlnode_get_child(xml, "Groups");
+	if (content->groups_node == NULL)
+	{
+		ggp_roster_content_free(content);
+		g_return_if_reached();
+	}
+	xml_it = xmlnode_get_child(content->groups_node, "Group");
+	while (xml_it != NULL)
+	{
+		if (!ggp_roster_reply_list_read_group(xml_it, content))
+		{
+			ggp_roster_content_free(content);
+			g_return_if_reached();
+		}
+		
+		xml_it = xmlnode_get_next_twin(xml_it);
+	}
+	
+	// dumping current group list
+	local_groups = ggp_purplew_account_get_groups(account, TRUE);
+	
+	// dumping current buddy list
+	// we will:
+	// - remove synchronized ones, if not found in list at server
+	// - upload not synchronized ones
+	local_buddies = purple_find_buddies(account, NULL);
+	remove_buddies = g_hash_table_new(g_direct_hash, g_direct_equal);
+	while (local_buddies)
+	{
+		PurpleBuddy *buddy = local_buddies->data;
+		uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
+		local_buddies =
+			g_slist_delete_link(local_buddies, local_buddies);
+
+		if (!uin)
+			continue;
+
+		if (ggp_roster_is_synchronized(buddy))
+			g_hash_table_insert(remove_buddies,
+				GINT_TO_POINTER(uin), buddy);
+		else
+			update_buddies = g_list_append(update_buddies, buddy);
+	}
+	
+	// reading buddies
+	content->contacts_node = xmlnode_get_child(xml, "Contacts");
+	if (content->contacts_node == NULL)
+	{
+		g_hash_table_destroy(remove_buddies);
+		g_list_free(update_buddies);
+		ggp_roster_content_free(content);
+		g_return_if_reached();
+	}
+	xml_it = xmlnode_get_child(content->contacts_node, "Contact");
+	while (xml_it != NULL)
+	{
+		if (!ggp_roster_reply_list_read_buddy(gc, xml_it, content,
+			remove_buddies))
+		{
+			g_hash_table_destroy(remove_buddies);
+			g_list_free(update_buddies);
+			ggp_roster_content_free(content);
+			g_return_if_reached();
+		}
+	
+		xml_it = xmlnode_get_next_twin(xml_it);
+	}
+	
+	// removing buddies, which are not present in roster
+	table_values = g_hash_table_get_values(remove_buddies);
+	it = g_list_first(table_values);
+	while (it)
+	{
+		PurpleBuddy *buddy = it->data;
+		it = g_list_next(it);
+		if (!ggp_roster_is_synchronized(buddy))
+			continue;
+		purple_debug_info("gg", "ggp_roster_reply_list: "
+			"removing %s from buddy list\n",
+			purple_buddy_get_name(buddy));
+		purple_blist_remove_buddy(buddy);
+	}
+	g_list_free(table_values);
+	g_hash_table_destroy(remove_buddies);
+	
+	// remove groups, which are empty, but had contacts before
+	// synchronization
+	it = g_list_first(local_groups);
+	while (it)
+	{
+		PurpleGroup *group = it->data;
+		it = g_list_next(it);
+		if (purple_blist_get_group_size(group, TRUE) != 0)
+			continue;
+		purple_debug_info("gg", "ggp_roster_reply_list: "
+			"removing group %s\n", purple_group_get_name(group));
+		purple_blist_remove_group(group);
+	}
+	g_list_free(local_groups);
+	
+	// adding not synchronized buddies
+	it = g_list_first(update_buddies);
+	while (it)
+	{
+		PurpleBuddy *buddy = it->data;
+		uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
+		ggp_roster_change *change;
+		
+		it = g_list_next(it);
+		g_assert(uin > 0);
+		
+		purple_debug_misc("gg", "ggp_roster_reply_list: "
+			"adding change of %u for roster\n", uin);
+		change = g_new0(ggp_roster_change, 1);
+		change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
+		change->data.uin = uin;
+		rdata->pending_updates =
+			g_list_append(rdata->pending_updates, change);
+	}
+	g_list_free(update_buddies);
+
+	rdata->content = content;
+	rdata->is_updating = FALSE;
+	purple_debug_info("gg", "ggp_roster_reply_list: "
+		"import done, version=%u\n", version);
+}
+
+/*******************************************************************************
+ * Buddy list export.
+ ******************************************************************************/
+
+static const gchar * ggp_roster_send_update_group_add(
+	ggp_roster_content *content, PurpleGroup *group)
+{
+	gchar *id_dyn;
+	const char *id_existing, *group_name;
+	static gchar id[40];
+	xmlnode *group_node;
+	gboolean succ = TRUE;
+
+	if (group)
+	{
+		group_name = purple_group_get_name(group);
+		id_existing =
+			g_hash_table_lookup(content->group_ids, group_name);
+	}
+	else
+		id_existing = GGP_ROSTER_GROUPID_DEFAULT;
+	if (id_existing)
+		return id_existing;
+
+	purple_debug_info("gg", "ggp_roster_send_update_group_add: adding %s\n",
+		purple_group_get_name(group));
+
+	id_dyn = purple_uuid_random();
+	g_snprintf(id, sizeof(id), "%s", id_dyn);
+	g_free(id_dyn);
+	
+	group_node = xmlnode_new_child(content->groups_node, "Group");
+	succ &= ggp_xml_set_string(group_node, "Id", id);
+	succ &= ggp_xml_set_string(group_node, "Name", group_name);
+	succ &= ggp_xml_set_string(group_node, "IsExpanded", "true");
+	succ &= ggp_xml_set_string(group_node, "IsRemovable", "true");
+	content->needs_update = TRUE;
+	
+	g_hash_table_insert(content->group_ids, g_strdup(group_name),
+		g_strdup(id));
+	g_hash_table_insert(content->group_nodes, g_strdup(id), group_node);
+	
+	g_return_val_if_fail(succ, NULL);
+
+	return id;
+}
+
+static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc,
+	ggp_roster_change *change)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
+	uin_t uin = change->data.uin;
+	PurpleBuddy *buddy;
+	xmlnode *buddy_node, *contact_groups;
+	gboolean succ = TRUE;
+	const char *group_id;
+	
+	g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE,
+		FALSE);
+	
+	buddy = purple_find_buddy(account, ggp_uin_to_str(uin));
+	if (!buddy)
+		return TRUE;
+	buddy_node = g_hash_table_lookup(content->contact_nodes,
+		GINT_TO_POINTER(uin));
+
+	group_id = ggp_roster_send_update_group_add(content,
+		ggp_purplew_buddy_get_group_only(buddy));
+
+	if (buddy_node)
+	{ // update existing
+		purple_debug_misc("gg", "ggp_roster_send_update_contact_update:"
+			" updating %u...\n", uin);
+		
+		succ &= ggp_xml_set_string(buddy_node, "ShowName",
+			purple_buddy_get_alias(buddy));
+		
+		contact_groups = xmlnode_get_child(buddy_node, "Groups");
+		g_assert(contact_groups);
+		ggp_xmlnode_remove_children(contact_groups);
+		succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id);
+	
+		g_return_val_if_fail(succ, FALSE);
+		
+		return TRUE;
+	}
+	
+	// add new
+	purple_debug_misc("gg", "ggp_roster_send_update_contact_update: "
+		"adding %u...\n", uin);
+	buddy_node = xmlnode_new_child(content->contacts_node, "Contact");
+	succ &= ggp_xml_set_string(buddy_node, "Guid", purple_uuid_random());
+	succ &= ggp_xml_set_uint(buddy_node, "GGNumber", uin);
+	succ &= ggp_xml_set_string(buddy_node, "ShowName",
+		purple_buddy_get_alias(buddy));
+	
+	contact_groups = xmlnode_new_child(buddy_node, "Groups");
+	g_assert(contact_groups);
+	succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id);
+	
+	xmlnode_new_child(buddy_node, "Avatars");
+	succ &= ggp_xml_set_bool(buddy_node, "FlagBuddy", TRUE);
+	succ &= ggp_xml_set_bool(buddy_node, "FlagNormal", TRUE);
+	succ &= ggp_xml_set_bool(buddy_node, "FlagFriend", TRUE);
+	
+	// we don't use Guid, so update is not needed
+	//content->needs_update = TRUE;
+	
+	g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin),
+		buddy_node);
+	
+	g_return_val_if_fail(succ, FALSE);
+	
+	return TRUE;
+}
+
+static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc,
+	ggp_roster_change *change)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
+	uin_t uin = change->data.uin;
+	PurpleBuddy *buddy;
+	xmlnode *buddy_node;
+
+	g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE,
+		FALSE);
+	
+	buddy = purple_find_buddy(account, ggp_uin_to_str(uin));
+	if (buddy)
+	{
+		purple_debug_info("gg", "ggp_roster_send_update_contact_remove:"
+			" contact %u re-added\n", uin);
+		return TRUE;
+	}
+
+	buddy_node = g_hash_table_lookup(content->contact_nodes,
+		GINT_TO_POINTER(uin));
+	if (!buddy_node) // already removed
+		return TRUE;
+	
+	purple_debug_info("gg", "ggp_roster_send_update_contact_remove: "
+		"removing %u\n", uin);
+	xmlnode_free(buddy_node);
+	g_hash_table_remove(content->contact_nodes, GINT_TO_POINTER(uin));
+	
+	return TRUE;
+}
+
+static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc,
+	ggp_roster_change *change)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
+	const char *old_name = change->data.group_rename.old_name;
+	const char *new_name = change->data.group_rename.new_name;
+	xmlnode *group_node;
+	const char *group_id;
+
+	g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_GROUP_RENAME,
+		FALSE);
+	
+	purple_debug_misc("gg", "ggp_roster_send_update_group_rename: "
+		"\"%s\"->\"%s\"\n", old_name, new_name);
+
+	// moving to or from default group instead of renaming it
+	if (0 == g_strcmp0(old_name, GGP_PURPLEW_GROUP_DEFAULT) ||
+		0 == g_strcmp0(new_name, GGP_PURPLEW_GROUP_DEFAULT))
+	{
+		PurpleGroup *group;
+		GList *group_buddies;
+		group = purple_find_group(new_name);
+		if (!group)
+			return TRUE;
+		purple_debug_info("gg", "ggp_roster_send_update_group_rename: "
+			"invalidating buddies in default group\n");
+		group_buddies = ggp_purplew_group_get_buddies(group, account);
+		while (group_buddies)
+		{
+			ggp_roster_set_synchronized(gc, group_buddies->data,
+				FALSE);
+			group_buddies = g_list_remove_link(group_buddies,
+				group_buddies);
+		}
+		return TRUE;
+	}
+	group_id = g_hash_table_lookup(content->group_ids, old_name);
+	if (!group_id)
+	{
+		purple_debug_info("gg", "ggp_roster_send_update_group_rename: "
+			"%s is not present at roster\n", old_name);
+		return TRUE;
+	}
+	
+	group_node = g_hash_table_lookup(content->group_nodes, group_id);
+	if (!group_node)
+	{
+		purple_debug_error("gg", "ggp_roster_send_update_group_rename: "
+			"node for %s not found, id=%s\n", old_name, group_id);
+		g_hash_table_remove(content->group_ids, old_name);
+		return TRUE;
+	}
+	
+	g_hash_table_remove(content->group_ids, old_name);
+	g_hash_table_insert(content->group_ids, g_strdup(new_name),
+		g_strdup(group_id));
+	g_hash_table_insert(content->group_nodes, g_strdup(group_id),
+		group_node);
+	return ggp_xml_set_string(group_node, "Name", new_name);
+}
+
+static void ggp_roster_send_update(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	ggp_roster_content *content = rdata->content;
+	GList *updates_it;
+	gchar *str;
+	int len;
+	
+	// an update is running now
+	if (rdata->sent_updates)
+		return;
+
+	// no pending updates found
+	if (!rdata->pending_updates)
+		return;
+	
+	// not initialized
+	if (!content)
+		return;
+	
+	purple_debug_info("gg", "ggp_roster_send_update: "
+		"pending updates found\n");
+	
+	rdata->sent_updates = rdata->pending_updates;
+	rdata->pending_updates = NULL;
+	
+	updates_it = g_list_first(rdata->sent_updates);
+	while (updates_it)
+	{
+		ggp_roster_change *change = updates_it->data;
+		gboolean succ = FALSE;
+		updates_it = g_list_next(updates_it);
+		
+		if (change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE)
+			succ = ggp_roster_send_update_contact_update(gc,
+				change);
+		else if (change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE)
+			succ = ggp_roster_send_update_contact_remove(gc,
+				change);
+		else if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME)
+			succ = ggp_roster_send_update_group_rename(gc, change);
+		else
+			purple_debug_fatal("gg", "ggp_roster_send_update: "
+				"not handled\n");
+		g_return_if_fail(succ);
+	}
+	
+#if GGP_ROSTER_DEBUG
+	ggp_roster_dump(content);
+#endif
+	
+	str = xmlnode_to_str(content->xml, &len);
+	gg_userlist100_request(accdata->session, GG_USERLIST100_PUT,
+		content->version, GG_USERLIST100_FORMAT_TYPE_GG100, str);
+	g_free(str);
+}
+
+static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+	ggp_roster_content *content = rdata->content;
+	GList *updates_it;
+
+	purple_debug_info("gg", "ggp_roster_reply_ack: version=%u\n", version);
+	
+	// set synchronization flag for all buddies, that were updated at roster
+	updates_it = g_list_first(rdata->sent_updates);
+	while (updates_it)
+	{
+		ggp_roster_change *change = updates_it->data;
+		PurpleBuddy *buddy;
+		updates_it = g_list_next(updates_it);
+		
+		if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE)
+			continue;
+		
+		buddy = purple_find_buddy(account,
+			ggp_uin_to_str(change->data.uin));
+		if (buddy)
+			ggp_roster_set_synchronized(gc, buddy, TRUE);
+	}
+	
+	// we need to remove "synchronized" flag for all contacts, that have
+	// beed modified between roster update start and now
+	updates_it = g_list_first(rdata->pending_updates);
+	while (updates_it)
+	{
+		ggp_roster_change *change = updates_it->data;
+		PurpleBuddy *buddy;
+		updates_it = g_list_next(updates_it);
+		
+		if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE)
+			continue;
+		
+		buddy = purple_find_buddy(account,
+			ggp_uin_to_str(change->data.uin));
+		if (buddy && ggp_roster_is_synchronized(buddy))
+			ggp_roster_set_synchronized(gc, buddy, FALSE);
+	}
+	
+	g_list_free_full(rdata->sent_updates, ggp_roster_change_free);
+	rdata->sent_updates = NULL;
+	
+	// bump roster version or update it, if needed
+	g_return_if_fail(content != NULL);
+	if (content->needs_update)
+	{
+		ggp_roster_content_free(rdata->content);
+		rdata->content = NULL;
+		// we have to wait for gg_event_userlist100_version
+		//ggp_roster_request_update(gc);
+	}
+	else
+		content->version = version;
+}
+
+static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version)
+{
+	ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
+
+	purple_debug_info("gg", "ggp_roster_reply_reject: version=%u\n",
+		version);
+	
+	g_return_if_fail(rdata->sent_updates);
+	
+	rdata->pending_updates = g_list_concat(rdata->pending_updates,
+		rdata->sent_updates);
+	rdata->sent_updates = NULL;
+	
+	ggp_roster_content_free(rdata->content);
+	rdata->content = NULL;
+	ggp_roster_request_update(gc);
+}
+
+/******************************************************************************/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/roster.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,44 @@
+#ifndef _GGP_ROSTER_H
+#define _GGP_ROSTER_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct
+{
+	gpointer content;
+	gboolean is_updating;
+	
+	GList *sent_updates;
+	GList *pending_updates;
+	
+	guint timer;
+} ggp_roster_session_data;
+
+// setup
+gboolean ggp_roster_enabled(void);
+void ggp_roster_setup(PurpleConnection *gc);
+void ggp_roster_cleanup(PurpleConnection *gc);
+
+// synchronization control
+void ggp_roster_request_update(PurpleConnection *gc);
+
+// libgadu callbacks
+void ggp_roster_reply(PurpleConnection *gc,
+	struct gg_event_userlist100_reply *reply);
+void ggp_roster_version(PurpleConnection *gc,
+	struct gg_event_userlist100_version *version);
+
+// libpurple callbacks
+void ggp_roster_alias_buddy(PurpleConnection *gc, const char *who,
+	const char *alias);
+void ggp_roster_group_buddy(PurpleConnection *gc, const char *who,
+	const char *old_group, const char *new_group);
+void ggp_roster_rename_group(PurpleConnection *, const char *old_name,
+	PurpleGroup *group, GList *moved_buddies);
+void ggp_roster_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
+	PurpleGroup *group, const char *message);
+void ggp_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
+	PurpleGroup *group);
+
+#endif /* _GGP_ROSTER_H */
--- a/libpurple/protocols/gg/search.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/search.c	Tue Aug 14 22:26:33 2012 +0200
@@ -23,9 +23,12 @@
 
 #include <libgadu.h>
 
-#include "gg-utils.h"
+#include "gg.h"
+#include "utils.h"
 #include "search.h"
 
+#include <debug.h>
+
 
 /* GGPSearchForm *ggp_search_form_new() {{{ */
 GGPSearchForm *ggp_search_form_new(GGPSearchType st)
--- a/libpurple/protocols/gg/search.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/protocols/gg/search.h	Tue Aug 14 22:26:33 2012 +0200
@@ -27,7 +27,6 @@
 #include "connection.h"
 
 #include <libgadu.h>
-#include "gg.h"
 
 
 typedef enum {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/status.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,228 @@
+#include "status.h"
+
+#include <libgadu.h>
+#include <debug.h>
+#include <request.h>
+
+#include "gg.h"
+#include "utils.h"
+
+struct _ggp_status_session_data
+{
+	gboolean status_broadcasting;
+	gchar *current_description;
+};
+
+static inline ggp_status_session_data *
+ggp_status_get_ssdata(PurpleConnection *gc);
+
+gchar * ggp_status_validate_description(const gchar* msg);
+static int ggp_status_from_purplestatus(PurpleStatus *status, gchar **message);
+
+////
+
+static inline ggp_status_session_data *
+ggp_status_get_ssdata(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	return accdata->status_data;
+}
+
+void ggp_status_setup(PurpleConnection *gc)
+{
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	ggp_status_session_data *ssdata = g_new0(ggp_status_session_data, 1);
+	accdata->status_data = ssdata;
+	
+	ssdata->status_broadcasting =
+		purple_account_get_bool(account, "status_broadcasting", TRUE);
+}
+
+void ggp_status_cleanup(PurpleConnection *gc)
+{
+	ggp_status_session_data *ssdata = ggp_status_get_ssdata(gc);
+	g_free(ssdata->current_description);
+	g_free(ssdata);
+}
+
+gchar * ggp_status_validate_description(const gchar* msg)
+{
+	if (msg == NULL || msg[0] == '\0')
+		return NULL;
+	
+	return ggp_utf8_strndup(msg, GG_STATUS_DESCR_MAXSIZE);
+}
+
+static int ggp_status_from_purplestatus(PurpleStatus *status, gchar **message)
+{
+	const char *status_id = purple_status_get_id(status);
+	const char *status_message =
+		purple_status_get_attr_string(status, "message");
+	
+	g_return_val_if_fail(message != NULL, 0);
+	
+	*message = NULL;
+	if (status_message)
+	{
+		gchar *stripped = purple_markup_strip_html(status_message);
+		*message = ggp_status_validate_description(stripped);
+		g_free(stripped);
+	}
+	
+	if (0 == strcmp(status_id, "available"))
+		return status_message ? GG_STATUS_AVAIL_DESCR : GG_STATUS_AVAIL;
+	if (0 == strcmp(status_id, "freeforchat"))
+		return status_message ? GG_STATUS_FFC_DESCR : GG_STATUS_FFC;
+	if (0 == strcmp(status_id, "away"))
+		return status_message ? GG_STATUS_BUSY_DESCR : GG_STATUS_BUSY;
+	if (0 == strcmp(status_id, "unavailable"))
+		return status_message ? GG_STATUS_DND_DESCR : GG_STATUS_DND;
+	if (0 == strcmp(status_id, "invisible"))
+		return status_message ?
+			GG_STATUS_INVISIBLE_DESCR : GG_STATUS_INVISIBLE;
+	if (0 == strcmp(status_id, "offline"))
+		return status_message ?
+			GG_STATUS_NOT_AVAIL_DESCR : GG_STATUS_NOT_AVAIL;
+	
+	purple_debug_error("gg", "ggp_status_from_purplestatus: "
+		"unknown status requested (%s)\n", status_id);
+	return status_message ? GG_STATUS_AVAIL_DESCR : GG_STATUS_AVAIL;
+}
+
+/*******************************************************************************
+ * Own status.
+ ******************************************************************************/
+
+static void ggp_status_broadcasting_dialog_ok(PurpleConnection *gc,
+	PurpleRequestFields *fields);
+
+/******************************************************************************/
+
+void ggp_status_set_initial(PurpleConnection *gc, struct gg_login_params *glp)
+{
+	ggp_status_session_data *ssdata = ggp_status_get_ssdata(gc);
+	PurpleAccount *account = purple_connection_get_account(gc);
+	
+	glp->status = ggp_status_from_purplestatus(
+		purple_account_get_active_status(account), &glp->status_descr);
+	if (!ggp_status_get_status_broadcasting(gc))
+		glp->status |= GG_STATUS_FRIENDS_MASK;
+	ssdata->current_description = g_strdup(glp->status_descr);
+}
+
+gboolean ggp_status_set(PurpleAccount *account, int status, const gchar* msg)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	ggp_status_session_data *ssdata = ggp_status_get_ssdata(gc);
+	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+	gchar *new_description = ggp_status_validate_description(msg);
+	
+	if (!ssdata->status_broadcasting)
+		status |= GG_STATUS_FRIENDS_MASK;
+	
+	if ((status == GG_STATUS_NOT_AVAIL ||
+		status == GG_STATUS_NOT_AVAIL_DESCR) &&
+		0 == g_strcmp0(ssdata->current_description, new_description))
+	{
+		purple_debug_info("gg", "ggp_status_set: new status doesn't "
+			"differ when closing connection - ignore\n");
+		g_free(new_description);
+		return FALSE;
+	}
+	g_free(ssdata->current_description);
+	ssdata->current_description = new_description;
+	
+	if (msg == NULL)
+		gg_change_status(accdata->session, status);
+	else
+		gg_change_status_descr(accdata->session, status, new_description);
+	
+	return TRUE;
+}
+
+void ggp_status_set_purplestatus(PurpleAccount *account, PurpleStatus *status)
+{
+	int status_gg;
+	gchar *msg = NULL;
+	
+	if (!purple_status_is_active(status))
+		return;
+	
+	status_gg = ggp_status_from_purplestatus(status, &msg);
+	ggp_status_set(account, status_gg, msg);
+	g_free(msg);
+}
+
+void ggp_status_fake_to_self(PurpleConnection *gc)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleStatus *status = purple_presence_get_active_status(
+		purple_account_get_presence(account));
+	const char *status_msg = purple_status_get_attr_string(status,
+		"message");
+	gchar *status_msg_gg = NULL;
+	
+	if (status_msg != NULL && status_msg[0] != '\0')
+	{
+		status_msg_gg = g_new0(gchar, GG_STATUS_DESCR_MAXSIZE + 1);
+		g_utf8_strncpy(status_msg_gg, status_msg,
+			GG_STATUS_DESCR_MAXSIZE);
+	}
+	
+	purple_prpl_got_user_status(account,
+		purple_account_get_username(account),
+		purple_status_get_id(status),
+		status_msg_gg ? "message" : NULL, status_msg_gg, NULL);
+	
+	g_free(status_msg_gg);
+}
+
+gboolean ggp_status_get_status_broadcasting(PurpleConnection *gc)
+{
+	return ggp_status_get_ssdata(gc)->status_broadcasting;
+}
+
+void ggp_status_set_status_broadcasting(PurpleConnection *gc,
+	gboolean broadcasting)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	ggp_status_get_ssdata(gc)->status_broadcasting = broadcasting;
+	purple_account_set_bool(account, "status_broadcasting", broadcasting);
+	ggp_status_set_purplestatus(account,
+		purple_account_get_active_status(account));
+}
+
+void ggp_status_broadcasting_dialog(PurpleConnection *gc)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+	
+	field = purple_request_field_bool_new("buddies_only",
+		_("Show status only for buddies"),
+		!ggp_status_get_status_broadcasting(gc));
+	purple_request_field_group_add_field(group, field);
+	
+	purple_request_fields(gc,
+		_("Change status broadcasting"),
+		_("Please, select who can see your status"),
+		NULL,
+		fields,
+		_("OK"), G_CALLBACK(ggp_status_broadcasting_dialog_ok),
+		_("Cancel"), NULL,
+		purple_connection_get_account(gc), NULL, NULL, gc);
+}
+
+static void ggp_status_broadcasting_dialog_ok(PurpleConnection *gc,
+	PurpleRequestFields *fields)
+{
+	ggp_status_set_status_broadcasting(gc,
+		!purple_request_fields_get_bool(fields, "buddies_only"));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/status.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,25 @@
+#ifndef _GGP_STATUS_H
+#define _GGP_STATUS_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct _ggp_status_session_data ggp_status_session_data;
+
+void ggp_status_setup(PurpleConnection *gc);
+void ggp_status_cleanup(PurpleConnection *gc);
+
+// own status
+
+void ggp_status_set_initial(PurpleConnection *gc, struct gg_login_params *glp);
+
+gboolean ggp_status_set(PurpleAccount *account, int status, const gchar* msg);
+void ggp_status_set_purplestatus(PurpleAccount *account, PurpleStatus *status);
+void ggp_status_fake_to_self(PurpleConnection *gc);
+
+gboolean ggp_status_get_status_broadcasting(PurpleConnection *gc);
+void ggp_status_set_status_broadcasting(PurpleConnection *gc,
+	gboolean broadcasting);
+void ggp_status_broadcasting_dialog(PurpleConnection *gc);
+
+#endif /* _GGP_STATUS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/utils.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,129 @@
+/**
+ * @file utils.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 "utils.h"
+
+#include "gg.h"
+
+#include <debug.h>
+
+uin_t ggp_str_to_uin(const char *str)
+{
+	char *endptr;
+	uin_t uin;
+
+	if (!str || str[0] < '0' || str[0] > '9')
+		return 0;
+
+	errno = 0;
+	uin = strtoul(str, &endptr, 10);
+
+	if (errno == ERANGE || endptr[0] != '\0')
+		return 0;
+
+	return uin;
+}
+
+const char * ggp_uin_to_str(uin_t uin)
+{
+	static char buff[GGP_UIN_LEN_MAX + 1];
+	
+	g_snprintf(buff, GGP_UIN_LEN_MAX + 1, "%u", uin);
+	
+	return buff;
+}
+
+static gchar * ggp_convert(const gchar *src, const char *srcenc,
+	const char *dstenc)
+{
+	gchar *dst;
+	GError *err = NULL;
+
+	if (src == NULL)
+		return NULL;
+
+	dst = g_convert_with_fallback(src, strlen(src), dstenc, srcenc, "?",
+		NULL, NULL, &err);
+	if (err != NULL)
+	{
+		purple_debug_error("gg", "error converting from %s to %s: %s\n",
+			srcenc, dstenc, err->message);
+		g_error_free(err);
+	}
+
+	if (dst == NULL)
+		dst = g_strdup(src);
+
+	return dst;
+}
+
+gchar * ggp_convert_to_cp1250(const gchar *src)
+{
+	return ggp_convert(src, "UTF-8", "CP1250");
+}
+
+gchar * ggp_convert_from_cp1250(const gchar *src)
+{
+	return ggp_convert(src, "CP1250", "UTF-8");
+}
+
+gboolean ggp_password_validate(const gchar *password)
+{
+	const int len = strlen(password);
+	if (len < 6 || len > 15)
+		return FALSE;
+	return g_regex_match_simple("^[ a-zA-Z0-9~`!@#$%^&*()_+=[\\]{};':\",./?"
+		"<>\\\\|-]+$", password, 0, 0);
+}
+
+guint64 ggp_microtime(void)
+{
+	// replace with g_get_monotonic_time, when gtk 2.28 will be available
+	GTimeVal time_s;
+	
+	g_get_current_time(&time_s);
+	
+	return ((guint64)time_s.tv_sec << 32) | time_s.tv_usec;
+}
+
+gchar * ggp_utf8_strndup(const gchar *str, gsize n)
+{
+	int raw_len = strlen(str);
+	gchar *end_ptr;
+	if (str == NULL)
+		return NULL;
+	if (raw_len <= n)
+		return g_strdup(str);
+	
+	end_ptr = g_utf8_offset_to_pointer(str, g_utf8_pointer_to_offset(str, &str[n]));
+	raw_len = end_ptr - str;
+	
+	if (raw_len > n)
+	{
+		end_ptr = g_utf8_prev_char(end_ptr);
+		raw_len = end_ptr - str;
+	}
+	
+	g_assert(raw_len <= n);
+	
+	return g_strndup(str, raw_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/utils.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,73 @@
+/**
+ * @file utils.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 _GGP_UTILS_H
+#define _GGP_UTILS_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+/**
+ * Converts stringified UIN to uin_t.
+ *
+ * @param str The string to convert.
+ *
+ * @return Converted UIN or 0 if an error occurred.
+ */
+uin_t ggp_str_to_uin(const char *str);
+
+/**
+ * Stringifies UIN.
+ *
+ * @param uin UIN to stringify.
+ *
+ * @return Stringified UIN.
+ */
+const char * ggp_uin_to_str(uin_t uin);
+
+/**
+ * Converts encoding of a given string from UTF-8 to CP1250.
+ *
+ * @param src Input string.
+ *
+ * @return Converted string (must be freed with g_free). If src is NULL,
+ * then NULL is returned.
+ */
+gchar * ggp_convert_to_cp1250(const gchar *src);
+
+/**
+ * Converts encoding of a given string from CP1250 to UTF-8.
+ *
+ * @param src Input string.
+ *
+ * @return Converted string (must be freed with g_free). If src is NULL,
+ * then NULL is returned.
+ */
+gchar * ggp_convert_from_cp1250(const gchar *src);
+
+gboolean ggp_password_validate(const gchar *password);
+
+guint64 ggp_microtime(void);
+
+gchar * ggp_utf8_strndup(const gchar *str, gsize n);
+
+#endif /* _GGP_UTILS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/validator.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,66 @@
+#include "validator.h"
+
+#include "account.h"
+#include "utils.h"
+
+gboolean ggp_validator_token(PurpleRequestField *field, gchar **errmsg,
+	void *token)
+{
+	const char *value;
+	
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(purple_request_field_get_type(field) ==
+		PURPLE_REQUEST_FIELD_STRING, FALSE);
+	
+	value = purple_request_field_string_get_value(field);
+	
+	if (value != NULL && ggp_account_token_validate(token, value))
+		return TRUE;
+	
+	if (errmsg)
+		*errmsg = g_strdup(_("Captcha validation failed"));
+	return FALSE;
+}
+
+gboolean ggp_validator_password(PurpleRequestField *field, gchar **errmsg,
+	void *user_data)
+{
+	const char *value;
+	
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(purple_request_field_get_type(field) ==
+		PURPLE_REQUEST_FIELD_STRING, FALSE);
+	
+	value = purple_request_field_string_get_value(field);
+	
+	if (value != NULL && ggp_password_validate(value))
+		return TRUE;
+	
+	if (errmsg)
+		*errmsg = g_strdup(_("Password can contain 6-15 alphanumeric characters"));
+	return FALSE;
+}
+
+gboolean ggp_validator_password_equal(PurpleRequestField *field, gchar **errmsg,
+	void *field2_p)
+{
+	const char *value1, *value2;
+	PurpleRequestField *field2 = field2_p;
+	
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(field2 != NULL, FALSE);
+	g_return_val_if_fail(purple_request_field_get_type(field) ==
+		PURPLE_REQUEST_FIELD_STRING, FALSE);
+	g_return_val_if_fail(purple_request_field_get_type(field2) ==
+		PURPLE_REQUEST_FIELD_STRING, FALSE);
+	
+	value1 = purple_request_field_string_get_value(field);
+	value2 = purple_request_field_string_get_value(field2);
+	
+	if (g_strcmp0(value1, value2) == 0)
+		return TRUE;
+	
+	if (errmsg)
+		*errmsg = g_strdup(_("Passwords do not match"));
+	return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/validator.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,16 @@
+#ifndef _GGP_VALIDATOR_H
+#define _GGP_VALIDATOR_H
+
+#include <internal.h>
+#include <request.h>
+
+gboolean ggp_validator_token(PurpleRequestField *field, gchar **errmsg,
+	void *token);
+
+gboolean ggp_validator_password(PurpleRequestField *field, gchar **errmsg,
+	void *user_data);
+
+gboolean ggp_validator_password_equal(PurpleRequestField *field, gchar **errmsg,
+	void *field2);
+
+#endif /* _GGP_VALIDATOR_H */
--- a/libpurple/protocols/gg/win32-resolver.c	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,322 +0,0 @@
-/**
- * @file win32-resolver.c
- *
- * purple
- *
- * 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 "win32-resolver.h"
-
-#include <errno.h>
-#include <resolver.h>
-#include "debug.h"
-
-#ifndef _WIN32
-#error "win32thread resolver is not supported on current platform"
-#endif
-
-/**
- * Deal with the fact that you can't select() on a win32 file fd.
- * This makes it practically impossible to tie into purple's event loop.
- *
- * -This is thanks to Tor Lillqvist.
- */
-static int ggp_resolver_win32thread_socket_pipe(int *fds)
-{
-	SOCKET temp, socket1 = -1, socket2 = -1;
-	struct sockaddr_in saddr;
-	int len;
-	u_long arg;
-	fd_set read_set, write_set;
-	struct timeval tv;
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_socket_pipe(&%d)\n",
-		*fds);
-
-	temp = socket(AF_INET, SOCK_STREAM, 0);
-
-	if (temp == INVALID_SOCKET) {
-		goto out0;
-	}
-
-	arg = 1;
-	if (ioctlsocket(temp, FIONBIO, &arg) == SOCKET_ERROR) {
-		goto out0;
-	}
-
-	memset(&saddr, 0, sizeof(saddr));
-	saddr.sin_family = AF_INET;
-	saddr.sin_port = 0;
-	saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-
-	if (bind(temp, (struct sockaddr *)&saddr, sizeof (saddr))) {
-		goto out0;
-	}
-
-	if (listen(temp, 1) == SOCKET_ERROR) {
-		goto out0;
-	}
-
-	len = sizeof(saddr);
-	if (getsockname(temp, (struct sockaddr *)&saddr, &len)) {
-		goto out0;
-	}
-
-	socket1 = socket(AF_INET, SOCK_STREAM, 0);
-
-	if (socket1 == INVALID_SOCKET) {
-		goto out0;
-	}
-
-	arg = 1;
-	if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) {
-		goto out1;
-	}
-
-	if (connect(socket1, (struct sockaddr *)&saddr, len) != SOCKET_ERROR ||
-			WSAGetLastError() != WSAEWOULDBLOCK) {
-		goto out1;
-	}
-
-	FD_ZERO(&read_set);
-	FD_SET(temp, &read_set);
-
-	tv.tv_sec = 0;
-	tv.tv_usec = 0;
-
-	if (select(0, &read_set, NULL, NULL, NULL) == SOCKET_ERROR) {
-		goto out1;
-	}
-
-	if (!FD_ISSET(temp, &read_set)) {
-		goto out1;
-	}
-
-	socket2 = accept(temp, (struct sockaddr *) &saddr, &len);
-	if (socket2 == INVALID_SOCKET) {
-		goto out1;
-	}
-
-	FD_ZERO(&write_set);
-	FD_SET(socket1, &write_set);
-
-	tv.tv_sec = 0;
-	tv.tv_usec = 0;
-
-	if (select(0, NULL, &write_set, NULL, NULL) == SOCKET_ERROR) {
-		goto out2;
-	}
-
-	if (!FD_ISSET(socket1, &write_set)) {
-		goto out2;
-	}
-
-	arg = 0;
-	if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) {
-		goto out2;
-	}
-
-	arg = 0;
-	if (ioctlsocket(socket2, FIONBIO, &arg) == SOCKET_ERROR) {
-		goto out2;
-	}
-
-	fds[0] = socket1;
-	fds[1] = socket2;
-
-	closesocket (temp);
-
-	return 0;
-
-out2:
-	closesocket (socket2);
-out1:
-	closesocket (socket1);
-out0:
-	closesocket (temp);
-	errno = EIO; /* XXX */
-
-	return -1;
-}
-
-struct ggp_resolver_win32thread_data {
-	char *hostname;
-	int fd;
-};
-
-/**
- * Copy-paste from gg_resolver_run().
- */
-static DWORD WINAPI ggp_resolver_win32thread_thread(LPVOID arg)
-{
-	struct ggp_resolver_win32thread_data *data = arg;
-	struct in_addr addr_ip[2], *addr_list;
-	int addr_count;
-
-	purple_debug_info("gg", "ggp_resolver_win32thread_thread() host: %s, "
-		"fd: %i called\n", data->hostname, data->fd);
-
-	if ((addr_ip[0].s_addr = inet_addr(data->hostname)) == INADDR_NONE) {
-		if (gg_gethostbyname_real(data->hostname, &addr_list,
-			&addr_count, 0) == -1) {
-			addr_list = addr_ip;
-			/* addr_ip[0] już zawiera INADDR_NONE */
-		}
-	} else {
-		addr_list = addr_ip;
-		addr_ip[1].s_addr = INADDR_NONE;
-		addr_count = 1;
-	}
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_thread() "
-		"count = %d\n", addr_count);
-
-	write(data->fd, addr_list, (addr_count + 1) * sizeof(struct in_addr));
-	close(data->fd);
-
-	free(data->hostname);
-	data->hostname = NULL;
-
-	free(data);
-
-	if (addr_list != addr_ip)
-		free(addr_list);
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_thread() done\n");
-
-	return 0;
-}
-
-
-int ggp_resolver_win32thread_start(int *fd, void **private_data,
-	const char *hostname)
-{
-	struct ggp_resolver_win32thread_data *data = NULL;
-	HANDLE h;
-	DWORD dwTId;
-	int pipes[2], new_errno;
-
-	purple_debug_info("gg", "ggp_resolver_win32thread_start(%p, %p, "
-		"\"%s\");\n", fd, private_data, hostname);
-
-	if (!private_data || !fd || !hostname) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_start() "
-			"invalid arguments\n");
-		errno = EFAULT;
-		return -1;
-	}
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_start() creating "
-		"pipes...\n");
-
-	if (ggp_resolver_win32thread_socket_pipe(pipes) == -1) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_start() "
-			"unable to create pipes (errno=%d, %s)\n",
-			errno, strerror(errno));
-		return -1;
-	}
-
-	if (!(data = malloc(sizeof(*data)))) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_start() out "
-			"of memory\n");
-		new_errno = errno;
-		goto cleanup;
-	}
-
-	data->hostname = NULL;
-
-	if (!(data->hostname = strdup(hostname))) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_start() out "
-			"of memory\n");
-		new_errno = errno;
-		goto cleanup;
-	}
-
-	data->fd = pipes[1];
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_start() creating "
-		"thread...\n");
-
-	h = CreateThread(NULL, 0, ggp_resolver_win32thread_thread, data, 0,
-		&dwTId);
-
-	if (h == NULL) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_start() "
-			"unable to create thread\n");
-		new_errno = errno;
-		goto cleanup;
-	}
-
-	*private_data = h;
-	*fd = pipes[0];
-
-	purple_debug_misc("gg", "ggp_resolver_win32thread_start() done\n");
-
-	return 0;
-
-cleanup:
-	if (data) {
-		free(data->hostname);
-		free(data);
-	}
-
-	close(pipes[0]);
-	close(pipes[1]);
-
-	errno = new_errno;
-
-	return -1;
-
-}
-
-void ggp_resolver_win32thread_cleanup(void **private_data, int force)
-{
-	struct ggp_resolver_win32thread_data *data;
-
-	purple_debug_info("gg", "ggp_resolver_win32thread_cleanup() force: %i "
-		"called\n", force);
-
-	if (private_data == NULL || *private_data == NULL) {
-		purple_debug_error("gg", "ggp_resolver_win32thread_cleanup() "
-			"private_data: NULL\n");
-		return;
-	}
-	return; /* XXX */
-
-	data = (struct ggp_resolver_win32thread_data*) *private_data;
-	purple_debug_misc("gg", "ggp_resolver_win32thread_cleanup() data: "
-		"%s called\n", data->hostname);
-	*private_data = NULL;
-
-	if (force) {
-		purple_debug_misc("gg", "ggp_resolver_win32thread_cleanup() "
-			"force called\n");
-		//pthread_cancel(data->thread);
-		//pthread_join(data->thread, NULL);
-	}
-
-	free(data->hostname);
-	data->hostname = NULL;
-
-	if (data->fd != -1) {
-		close(data->fd);
-		data->fd = -1;
-	}
-	purple_debug_info("gg", "ggp_resolver_win32thread_cleanup() done\n");
-	free(data);
-}
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/win32-resolver.h	Tue Aug 14 22:05:05 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/**
- * @file win32-resolver.h
- *
- * purple
- *
- * 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_WIN32_RESOLVER
-#define _PURPLE_GG_WIN32_RESOLVER
-
-/**
- * Starts hostname resolving in new win32 thread.
- *
- * @param fd           Pointer to variable, where pipe descriptor will be saved.
- * @param private_data Pointer to variable, where pointer to private data will
- *                     be saved.
- * @param hostname     Hostname to resolve.
- */
-int ggp_resolver_win32thread_start(int *fd, void **private_data,
-	const char *hostname);
-
-/**
- * Cleans up resources after hostname resolving.
- *
- * @param private_data Pointer to variable storing pointer to private data.
- * @param force        TRUE, if resources should be cleaned up even, if
- *                     resolving process didn't finished.
- */
-void ggp_resolver_win32thread_cleanup(void **private_data, int force);
-
-#endif /* _PURPLE_GG_WIN32_RESOLVER */
-
-/* vim: set ts=8 sts=0 sw=8 noet: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/xml.c	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,103 @@
+#include "xml.h"
+
+#include "debug.h"
+
+gboolean ggp_xml_get_string(const xmlnode *xml, gchar *childName, gchar **var)
+{
+	char *str;
+	
+	g_return_val_if_fail(xml != NULL, FALSE);
+	g_return_val_if_fail(var != NULL, FALSE);
+	
+	if (childName != NULL)
+	{
+		xml = xmlnode_get_child(xml, childName);
+		g_return_val_if_fail(xml != NULL, FALSE);
+	}
+	
+	str = xmlnode_get_data(xml);
+	g_return_val_if_fail(str != NULL, FALSE);
+	
+	*var = str;
+	return TRUE;
+}
+
+gboolean ggp_xml_get_bool(const xmlnode *xml, gchar *childName, gboolean *var)
+{
+	char *str;
+	gboolean succ;
+	
+	succ = ggp_xml_get_string(xml, childName, &str);
+	g_return_val_if_fail(succ, FALSE);
+	
+	*var = (strcmp(str, "true") == 0 ||
+		strcmp(str, "True") == 0 ||
+		strcmp(str, "TRUE") == 0 ||
+		strcmp(str, "1") == 0);
+	g_free(str);
+	
+	return TRUE;
+}
+
+gboolean ggp_xml_get_uint(const xmlnode *xml, gchar *childName, unsigned int *var)
+{
+	char *str, *endptr;
+	gboolean succ;
+	unsigned int val;
+	
+	succ = ggp_xml_get_string(xml, childName, &str);
+	g_return_val_if_fail(succ, FALSE);
+	
+	errno = 0;
+	val = strtoul(str, &endptr, 10);
+	
+	succ = (errno != ERANGE && endptr[0] == '\0');
+	g_free(str);
+	
+	if (succ)
+		*var = val;
+	return succ;
+}
+
+gboolean ggp_xml_set_string(xmlnode *xml, gchar *childName, const gchar *val)
+{
+	g_return_val_if_fail(xml != NULL, FALSE);
+	g_return_val_if_fail(val != NULL, FALSE);
+	
+	if (childName != NULL)
+	{
+		xmlnode *child = xmlnode_get_child(xml, childName);
+		if (child == NULL)
+			child = xmlnode_new_child(xml, childName);
+		xml = child;
+	}
+	
+	ggp_xmlnode_remove_children(xml);
+	xmlnode_insert_data(xml, val, -1);
+	
+	return TRUE;
+}
+
+gboolean ggp_xml_set_bool(xmlnode *xml, gchar *childName, gboolean val)
+{
+	return ggp_xml_set_string(xml, childName, val ? "true" : "false");
+}
+
+gboolean ggp_xml_set_uint(xmlnode *xml, gchar *childName, unsigned int val)
+{
+	gchar buff[20];
+	g_snprintf(buff, sizeof(buff), "%u", val);
+	return ggp_xml_set_string(xml, childName, buff);
+}
+
+void ggp_xmlnode_remove_children(xmlnode *xml)
+{
+	xmlnode *child = xml->child;
+	while (child)
+	{
+		xmlnode *next = child->next;
+		if (child->type != XMLNODE_TYPE_ATTRIB)
+			xmlnode_free(child);
+		child = next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/xml.h	Tue Aug 14 22:26:33 2012 +0200
@@ -0,0 +1,17 @@
+#ifndef _GGP_XML_H
+#define _GGP_XML_H
+
+#include <internal.h>
+#include <xmlnode.h>
+
+gboolean ggp_xml_get_string(const xmlnode *xml, gchar *childName, gchar **var);
+gboolean ggp_xml_get_bool(const xmlnode *xml, gchar *childName, gboolean *var);
+gboolean ggp_xml_get_uint(const xmlnode *xml, gchar *childName, unsigned int *var);
+
+gboolean ggp_xml_set_string(xmlnode *xml, gchar *childName, const gchar *val);
+gboolean ggp_xml_set_bool(xmlnode *xml, gchar *childName, gboolean val);
+gboolean ggp_xml_set_uint(xmlnode *xml, gchar *childName, unsigned int val);
+
+void ggp_xmlnode_remove_children(xmlnode *xml);
+
+#endif /* _GGP_XML_H */
--- a/libpurple/win32/win32dep.c	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/win32/win32dep.c	Tue Aug 14 22:26:33 2012 +0200
@@ -328,6 +328,111 @@
 	return result;
 }
 
+int wpurple_input_pipe(int pipefd[2])
+{
+	SOCKET sock_server, sock_client, sock_server_established;
+	struct sockaddr_in saddr_in;
+	struct sockaddr * const saddr_p = (struct sockaddr *)&saddr_in;
+	int saddr_len = sizeof(struct sockaddr_in);
+	u_long arg;
+	fd_set select_set;
+	char succ = 1;
+
+	sock_server = sock_client = sock_server_established = INVALID_SOCKET;
+
+	purple_debug_misc("wpurple", "wpurple_input_pipe(0x%x[%d,%d])\n",
+		(unsigned int)pipefd, pipefd[0], pipefd[1]);
+
+	/* create client and passive server sockets */
+	sock_server = socket(AF_INET, SOCK_STREAM, 0);
+	sock_client = socket(AF_INET, SOCK_STREAM, 0);
+	succ = (sock_server != INVALID_SOCKET || sock_client != INVALID_SOCKET);
+
+	/* set created sockets into nonblocking mode */
+	arg = 1;
+	succ = (succ &&
+		ioctlsocket(sock_server, FIONBIO, &arg) != SOCKET_ERROR);
+	arg = 1;
+	succ = (succ &&
+		ioctlsocket(sock_client, FIONBIO, &arg) != SOCKET_ERROR);
+
+	/* listen on server socket */
+	memset(&saddr_in, 0, saddr_len);
+	saddr_in.sin_family = AF_INET;
+	saddr_in.sin_port = 0;
+	saddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	succ = (succ &&
+		bind(sock_server, saddr_p, saddr_len) != SOCKET_ERROR &&
+		listen(sock_server, 1) != SOCKET_ERROR &&
+		getsockname(sock_server, saddr_p, &saddr_len) != SOCKET_ERROR);
+
+	/* request a connection from client to server socket */
+	succ = (succ &&
+		connect(sock_client, saddr_p, saddr_len) == SOCKET_ERROR &&
+		WSAGetLastError() == WSAEWOULDBLOCK);
+
+	/* ensure, that server socket is readable */
+	if (succ)
+	{
+		FD_ZERO(&select_set);
+		FD_SET(sock_server, &select_set);
+	}
+	succ = (succ &&
+		select(0, &select_set, NULL, NULL, NULL) != SOCKET_ERROR &&
+		FD_ISSET(sock_server, &select_set));
+
+	/* accept (establish) connection from client socket */
+	if (succ)
+	{
+		sock_server_established =
+			accept(sock_server, saddr_p, &saddr_len);
+		succ = (sock_server_established != INVALID_SOCKET);
+	}
+
+	/* ensure, that client socket is writable */
+	if (succ)
+	{
+		FD_ZERO(&select_set);
+		FD_SET(sock_client, &select_set);
+	}
+	succ = (succ &&
+		select(0, NULL, &select_set, NULL, NULL) != SOCKET_ERROR &&
+		FD_ISSET(sock_client, &select_set));
+
+	/* set sockets into blocking mode */
+	arg = 0;
+	succ = (succ &&
+		ioctlsocket(sock_client, FIONBIO, &arg) != SOCKET_ERROR);
+	arg = 0;
+	succ = (succ &&
+		ioctlsocket(sock_server_established, FIONBIO, &arg)
+			!= SOCKET_ERROR);
+
+	/* we don't need (passive) server socket anymore */
+	if (sock_server != INVALID_SOCKET)
+		closesocket(sock_server);
+
+	if (succ)
+	{
+		purple_debug_misc("wpurple",
+			"wpurple_input_pipe created pipe [%d,%d]\n",
+			sock_client, sock_server_established);
+		pipefd[0] = sock_client; /* for reading */
+		pipefd[1] = sock_server_established; /* for writing */
+		return 0;
+	}
+	else
+	{
+		purple_debug_error("wpurple", "wpurple_input_pipe failed\n");
+		if (sock_client != INVALID_SOCKET)
+			closesocket(sock_client);
+		if (sock_server_established != INVALID_SOCKET)
+			closesocket(sock_server_established);
+		errno = EMFILE;
+		return -1;
+	}
+}
+
 void wpurple_init(void) {
 	WORD wVersionRequested;
 	WSADATA wsaData;
--- a/libpurple/win32/win32dep.h	Tue Aug 14 22:05:05 2012 +0200
+++ b/libpurple/win32/win32dep.h	Tue Aug 14 22:26:33 2012 +0200
@@ -60,6 +60,9 @@
 char *wpurple_escape_dirsep(const char *filename); /* needs to be g_free'd */
 GIOChannel *wpurple_g_io_channel_win32_new_socket(int socket); /* Until we get the post-2.8 glib win32 giochannel implementation working, use the thread-based one */
 
+/* Simulate unix pipes by creating a pair of connected sockets */
+int wpurple_input_pipe(int pipefd[2]);
+
 /* Determine Purple paths */
 gchar *wpurple_get_special_folder(int folder_type); /* needs to be g_free'd */
 const char *wpurple_install_dir(void);

mercurial