view src/protocols/gg/gg.c @ 2856:b1e300a85678

[gaim-migrate @ 2869] rewrote the html parser in gtkimhtml. yes, that's really all i did. the reason for the massive change is because i added a length argument, which then needed to be propogated down to everything that would ever receive anything that would get drawn to the window. the new parser isn't any faster. that wasn't my goal. it's much more understandable now (hopefully, anyway). committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Sat, 08 Dec 2001 09:48:52 +0000
parents 4b3f17ca66bf
children 7a158753b8d6
line wrap: on
line source

/*
 * gaim - Gadu-Gadu Protocol Plugin
 * $Id: gg.c 2869 2001-12-08 09:48:52Z warmenhoven $
 *
 * Copyright (C) 2001 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
 * 
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

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

#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <ctype.h>
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#ifdef HAVE_ICONV
#include <iconv.h>
#include "iconv_string.h"
#endif
/* Library from EKG (Eksperymentalny Klient Gadu-Gadu) */
#include "libgg.h"
#include "multi.h"
#include "prpl.h"
#include "gaim.h"
#include "proxy.h"

#include "pixmaps/gg_suncloud.xpm"
#include "pixmaps/gg_sunred.xpm"
#include "pixmaps/gg_sunwhitered.xpm"
#include "pixmaps/gg_sunyellow.xpm"

#define USEROPT_NICK 0

#define AGG_BUF_LEN 1024

#define AGG_GENDER_NONE -1

#define AGG_PUBDIR_USERLIST_EXPORT_FORM "/appsvc/fmcontactsput.asp"
#define AGG_PUBDIR_USERLIST_IMPORT_FORM "/appsvc/fmcontactsget.asp"
#define AGG_PUBDIR_SEARCH_FORM "/appsvc/fmpubquery2.asp"
#define AGG_REGISTER_DATA_FORM "/appsvc/fmregister.asp"
#define AGG_PUBDIR_MAX_ENTRIES 200

#define AGG_STATUS_AVAIL              _("Available")
#define AGG_STATUS_AVAIL_FRIENDS      _("Available for friends only")
#define AGG_STATUS_BUSY               _("Away")
#define AGG_STATUS_BUSY_FRIENDS       _("Away for friends only")
#define AGG_STATUS_INVISIBLE          _("Invisible")
#define AGG_STATUS_INVISIBLE_FRIENDS  _("Invisible for friends only")
#define AGG_STATUS_NOT_AVAIL          _("Unavailable")

#define AGG_HTTP_NONE			0
#define AGG_HTTP_SEARCH			1
#define AGG_HTTP_USERLIST_IMPORT	2
#define AGG_HTTP_USERLIST_EXPORT	3
#define AGG_HTTP_USERLIST_DELETE	4
#define AGG_HTTP_PASSWORD_CHANGE	5

#define UC_NORMAL 2

struct agg_data {
	struct gg_session *sess;
	int own_status;
};

struct agg_http {
	struct gaim_connection *gc;
	gchar *request;
	gchar *form;
	gchar *host;
	int inpa;
	int type;
};

static char *agg_name()
{
	return "Gadu-Gadu";
}

static gchar *charset_convert(const gchar *locstr, char *encsrc, char *encdst)
{
#ifdef HAVE_ICONV
	gchar *result = NULL;
	if (iconv_string(encdst, encsrc, locstr, locstr + strlen(locstr) + 1, &result, NULL) >= 0)
		return result;
#endif
	return g_strdup(locstr);
}

static gboolean invalid_uin(char *uin)
{
	unsigned long int res = strtol(uin, (char **)NULL, 10);
	if (res == LONG_MIN || res == LONG_MAX || res == 0)
		return TRUE;
	return FALSE;
}

static gint args_compare(gconstpointer a, gconstpointer b)
{
	gchar *arg_a = (gchar *)a;
	gchar *arg_b = (gchar *)b;

	return g_strcasecmp(arg_a, arg_b);
}

static gboolean allowed_uin(struct gaim_connection *gc, char *uin)
{
	switch (gc->permdeny) {
	case 1:
		/* permit all, deny none */
		return TRUE;
		break;
	case 2:
		/* deny all, permit none. */
		return FALSE;
		break;
	case 3:
		/* permit some. */
		if (g_slist_find_custom(gc->permit, uin, args_compare))
			return TRUE;
		return FALSE;
		break;
	case 4:
		/* deny some. */
		if (g_slist_find_custom(gc->deny, uin, args_compare))
			return FALSE;
		return TRUE;
		break;
	default:
		return TRUE;
		break;
	}
}

static gchar *find_local_charset(void)
{
	gchar *gg_localenc = g_getenv("GG_CHARSET");

	if (gg_localenc == NULL) {
#ifdef HAVE_LANGINFO_CODESET
		gg_localenc = nl_langinfo(CODESET);
#else
		gg_localenc = "US-ASCII";
#endif
	}
	return gg_localenc;
}

static char *handle_errcode(struct gaim_connection *gc, int errcode)
{
	static char msg[AGG_BUF_LEN];

	switch (errcode) {
	case GG_FAILURE_RESOLVING:
		g_snprintf(msg, sizeof(msg), _("Unable to resolve hostname."));
		break;
	case GG_FAILURE_CONNECTING:
		g_snprintf(msg, sizeof(msg), _("Unable to connect to server."));
		break;
	case GG_FAILURE_INVALID:
		g_snprintf(msg, sizeof(msg), _("Invalid response from server."));
		break;
	case GG_FAILURE_READING:
		g_snprintf(msg, sizeof(msg), _("Error while reading from socket."));
		break;
	case GG_FAILURE_WRITING:
		g_snprintf(msg, sizeof(msg), _("Error while writting to socket."));
		break;
	case GG_FAILURE_PASSWORD:
		g_snprintf(msg, sizeof(msg), _("Authentification failed."));
		break;
	default:
		g_snprintf(msg, sizeof(msg), _("Unknown Error Code."));
		break;
	}

	hide_login_progress(gc, msg);

	return msg;
}

static void agg_set_away(struct gaim_connection *gc, char *state, char *msg)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	int status = gd->own_status;

	if (gc->away)
		gc->away = NULL;

	if (!g_strcasecmp(state, AGG_STATUS_AVAIL))
		status = GG_STATUS_AVAIL;
	else if (!g_strcasecmp(state, AGG_STATUS_AVAIL_FRIENDS))
		status = GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK;
	else if (!g_strcasecmp(state, AGG_STATUS_BUSY)) {
		status = GG_STATUS_BUSY;
		gc->away = "";
	} else if (!g_strcasecmp(state, AGG_STATUS_BUSY_FRIENDS)) {
		status =  GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK;
		gc->away = "";
	} else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE)) {
		status = GG_STATUS_INVISIBLE;
		gc->away = "";
	} else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE_FRIENDS)) {
		status = GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK;
		gc->away = "";
	} else if (!g_strcasecmp(state, AGG_STATUS_NOT_AVAIL)) {
		status = GG_STATUS_NOT_AVAIL;
		gc->away = "";
	} else if (!g_strcasecmp(state, GAIM_AWAY_CUSTOM)) {
		if (msg) {
			status = GG_STATUS_BUSY;
			gc->away = "";
		} else
			status = GG_STATUS_AVAIL;

		if (gd->own_status & GG_STATUS_FRIENDS_MASK)
			status |= GG_STATUS_FRIENDS_MASK;
	}

	gd->own_status = status;
	gg_change_status(gd->sess, status);
}

static gchar *get_away_text(int uc)
{
	if (uc == UC_UNAVAILABLE)
		return AGG_STATUS_NOT_AVAIL;
	uc = uc >> 5;
	switch (uc) {
	case GG_STATUS_AVAIL:
	default:
		return AGG_STATUS_AVAIL;
	case GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK:
		return AGG_STATUS_AVAIL_FRIENDS;
	case GG_STATUS_BUSY:
		return AGG_STATUS_BUSY;
	case GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK:
		return AGG_STATUS_BUSY_FRIENDS;
	case GG_STATUS_INVISIBLE:
		return AGG_STATUS_INVISIBLE;
	case GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK:
		return AGG_STATUS_INVISIBLE_FRIENDS;
	case GG_STATUS_NOT_AVAIL:
		return AGG_STATUS_NOT_AVAIL;
	}
}

static GList *agg_away_states(struct gaim_connection *gc)
{
	GList *m = NULL;

	m = g_list_append(m, AGG_STATUS_AVAIL);
	m = g_list_append(m, AGG_STATUS_BUSY);
	m = g_list_append(m, AGG_STATUS_INVISIBLE);
	m = g_list_append(m, AGG_STATUS_AVAIL_FRIENDS);
	m = g_list_append(m, AGG_STATUS_BUSY_FRIENDS);
	m = g_list_append(m, AGG_STATUS_INVISIBLE_FRIENDS);
	m = g_list_append(m, AGG_STATUS_NOT_AVAIL);
	return m;
}

/* Enhance these functions, more options and such stuff */
static GList *agg_buddy_menu(struct gaim_connection *gc, char *who)
{
	GList *m = NULL;
	struct proto_buddy_menu *pbm;
	struct buddy *b = find_buddy(gc, who);
	static char buf[AGG_BUF_LEN];

	if (!b) {
		return m;
	}

	pbm = g_new0(struct proto_buddy_menu, 1);
	g_snprintf(buf, sizeof(buf), _("Status: %s"), get_away_text(b->uc));
	pbm->label = buf;
	pbm->callback = NULL;
	pbm->gc = gc;
	m = g_list_append(m, pbm);

	return m;
}

static GList *agg_user_opts()
{
	GList *m = NULL;
	struct proto_user_opt *puo;

	puo = g_new0(struct proto_user_opt, 1);
	puo->label = _("Nick:");
	puo->def = _("Gadu-Gadu User");
	puo->pos = USEROPT_NICK;
	m = g_list_append(m, puo);

	return m;
}

static void main_callback(gpointer data, gint source, GaimInputCondition cond)
{
	struct gaim_connection *gc = data;
	struct agg_data *gd = gc->proto_data;
	struct gg_event *e;

	debug_printf("main_callback enter: begin\n");

	if (gd->sess->fd != source)
		gd->sess->fd = source;

	if (source == -1) {
		signoff(gc);
		return;
	}

	if (!(e = gg_watch_fd(gd->sess))) {
		debug_printf("main_callback: gg_watch_fd failed - CRITICAL!\n");
		hide_login_progress(gc, _("Unable to read socket"));
		signoff(gc);
		return;
	}

	switch (e->type) {
	case GG_EVENT_NONE:
		/* do nothing */
		break;
	case GG_EVENT_CONN_SUCCESS:
		debug_printf("main_callback: CONNECTED AGAIN!?\n");
		break;
	case GG_EVENT_CONN_FAILED:
		if (gc->inpa)
			gaim_input_remove(gc->inpa);
		handle_errcode(gc, e->event.failure);
		signoff(gc);
		break;
	case GG_EVENT_MSG:
		{
			gchar *imsg;
			gchar user[20];

			g_snprintf(user, sizeof(user), "%lu", e->event.msg.sender);
			if (!allowed_uin(gc, user))
				break;
			imsg = charset_convert(e->event.msg.message, "CP1250", find_local_charset());
			strip_linefeed(imsg);
			/* e->event.msg.time - we don't know what this time is for */
			serv_got_im(gc, user, imsg, 0, time(NULL), -1);
			g_free(imsg);
		}
		break;
	case GG_EVENT_NOTIFY:
		{
			gchar user[20];
			struct gg_notify_reply *n = e->event.notify;
			guint status;

			while (n->uin) {
				switch (n->status) {
				case GG_STATUS_NOT_AVAIL:
					status = UC_UNAVAILABLE;
					break;
				case GG_STATUS_AVAIL:
				case GG_STATUS_BUSY:
				case GG_STATUS_INVISIBLE:
					status = UC_NORMAL | (n->status << 5);
					break;
				default:
					status = UC_NORMAL;
					break;
				}

				g_snprintf(user, sizeof(user), "%lu", n->uin);
				serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0,
						status, 0);
				n++;
			}
		}
		break;
	case GG_EVENT_STATUS:
		{
			gchar user[20];
			guint status;

			switch (e->event.status.status) {
			case GG_STATUS_NOT_AVAIL:
				status = UC_UNAVAILABLE;
				break;
			case GG_STATUS_AVAIL:
			case GG_STATUS_BUSY:
			case GG_STATUS_INVISIBLE:
				status = UC_NORMAL | (e->event.status.status << 5);
				break;
			default:
				status = UC_NORMAL;
				break;
			}

			g_snprintf(user, sizeof(user), "%lu", e->event.status.uin);
			serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0,
					status, 0);
		}
		break;
	case GG_EVENT_ACK:
		debug_printf("main_callback: message %d to %u sent with status %d\n",
			     e->event.ack.seq, e->event.ack.recipient, e->event.ack.status);
		break;
	default:
		debug_printf("main_callback: unsupported event %d\n", e->type);
		break;
	}
	gg_free_event(e);
}

static void login_callback(gpointer data, gint source, GaimInputCondition cond)
{
	struct gaim_connection *gc = data;
	struct agg_data *gd = gc->proto_data;
	struct gg_event *e;

	if (!g_slist_find(connections, data)) {
		close(source);
		return;
	}

	if (gd->sess->fd != source)
		gd->sess->fd = source;

	if (source == -1) {
		hide_login_progress(gc, _("Unable to connect."));
		signoff(gc);
		return;
	}

	if (gc->inpa == 0)
		gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, login_callback, gc);

	switch (gd->sess->state) {
	case GG_STATE_READING_DATA:
		set_login_progress(gc, 2, _("Reading data"));
		break;
	case GG_STATE_CONNECTING_GG:
		set_login_progress(gc, 3, _("Balancer handshake"));
		break;
	case GG_STATE_READING_KEY:
		set_login_progress(gc, 4, _("Reading server key"));
		break;
	case GG_STATE_READING_REPLY:
		set_login_progress(gc, 5, _("Exchanging key hash"));
		break;
	default:
		break;
	}

	if (!(e = gg_watch_fd(gd->sess))) {
		debug_printf("login_callback: gg_watch_fd failed - CRITICAL!\n");
		hide_login_progress(gc, _("Critical error in GG library\n"));
		signoff(gc);
		return;
	}

	switch (e->type) {
	case GG_EVENT_NONE:
		/* nothing */
		break;
	case GG_EVENT_CONN_SUCCESS:
		/* Setup new input handler */
		if (gc->inpa)
			gaim_input_remove(gc->inpa);
		gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, main_callback, gc);

		/* Our signon is complete */
		account_online(gc);
		serv_finish_login(gc);

		if (bud_list_cache_exists(gc))
			do_import(gc, NULL);
		break;
	case GG_EVENT_CONN_FAILED:
		gaim_input_remove(gc->inpa);
		gc->inpa = 0;
		handle_errcode(gc, e->event.failure);
		signoff(gc);
		break;
	default:
		break;
	}

	gg_free_event(e);
}

static void agg_keepalive(struct gaim_connection *gc)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	if (gg_ping(gd->sess) < 0) {
		hide_login_progress(gc, _("Unable to ping server"));
		signoff(gc);
		return;
	}
}

static void agg_login(struct aim_user *user)
{
	struct gaim_connection *gc = new_gaim_conn(user);
	struct agg_data *gd = gc->proto_data = g_new0(struct agg_data, 1);
	char buf[80];

	gc->checkbox = _("Send as message");

	gd->sess = g_new0(struct gg_session, 1);

	if (user->proto_opt[USEROPT_NICK][0])
		g_snprintf(gc->displayname, sizeof(gc->displayname), "%s",
			   user->proto_opt[USEROPT_NICK]);

	set_login_progress(gc, 1, _("Looking up GG server"));

	if (invalid_uin(user->username)) {
		hide_login_progress(gc, _("Invalid Gadu-Gadu UIN specified"));
		signoff(gc);
		return;
	}

	gc->inpa = 0;

	/*
	   if (gg_login(gd->sess, strtol(user->username, (char **)NULL, 10), user->password, 1) < 0) {
	   debug_printf("uin=%u, pass=%s", strtol(user->username, (char **)NULL, 10), user->password); 
	   hide_login_progress(gc, "Unable to connect.");
	   signoff(gc);
	   return;
	   }

	   gg_login() sucks for me, so I'm using proxy_connect()
	 */

	gd->sess->uin = (uin_t) strtol(user->username, (char **)NULL, 10);
	gd->sess->password = g_strdup(user->password);
	gd->sess->state = GG_STATE_CONNECTING;
	gd->sess->check = GG_CHECK_WRITE;
	gd->sess->async = 1;
	gd->sess->fd = proxy_connect(GG_APPMSG_HOST, GG_APPMSG_PORT, login_callback, gc);

	if (gd->sess->fd < 0) {
		g_snprintf(buf, sizeof(buf), _("Connect to %s failed"), GG_APPMSG_HOST);
		hide_login_progress(gc, buf);
		signoff(gc);
		return;
	}
}

static void agg_close(struct gaim_connection *gc)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	if (gc->inpa)
		gaim_input_remove(gc->inpa);
	gg_logoff(gd->sess);
	gg_free_session(gd->sess);
	g_free(gc->proto_data);
	gd->own_status = GG_STATUS_NOT_AVAIL;
}

static int agg_send_im(struct gaim_connection *gc, char *who, char *msg, int flags)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	gchar *imsg;

	if (invalid_uin(who)) {
		do_error_dialog(_("You are trying to send message to invalid Gadu-Gadu UIN!"),
				_("Gadu-Gadu Error"));
		return -1;
	}

	if (strlen(msg) > 0) {
		imsg = charset_convert(msg, find_local_charset(), "CP1250");
		if (gg_send_message(gd->sess, (flags & IM_FLAG_CHECKBOX)
				    ? GG_CLASS_MSG : GG_CLASS_CHAT,
				    strtol(who, (char **)NULL, 10), imsg) < 0)
			return -1;
		g_free(imsg);
	}
	return 1;
}

static void agg_add_buddy(struct gaim_connection *gc, char *who)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	if (invalid_uin(who))
		return;
	gg_add_notify(gd->sess, strtol(who, (char **)NULL, 10));
}

static void agg_rem_buddy(struct gaim_connection *gc, char *who, char *group)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	if (invalid_uin(who))
		return;
	gg_remove_notify(gd->sess, strtol(who, (char **)NULL, 10));
}

static void agg_add_buddies(struct gaim_connection *gc, GList *whos)
{
	struct agg_data *gd = (struct agg_data *)gc->proto_data;
	uin_t *userlist = NULL;
	int userlist_size = 0;

	while (whos) {
		if (!invalid_uin(whos->data)) {
			userlist_size++;
			userlist = g_renew(uin_t, userlist, userlist_size);
			userlist[userlist_size - 1] =
			    (uin_t) strtol((char *)whos->data, (char **)NULL, 10);
		}
		whos = g_list_next(whos);
	}

	if (userlist) {
		gg_notify(gd->sess, userlist, userlist_size);
		g_free(userlist);
	}
}

static void search_results(struct gaim_connection *gc, gchar *webdata)
{
	gchar **webdata_tbl;
	gchar *buf;
	char *ptr;
	int i, j;

	if ((ptr = strstr(webdata, "query_results:")) == NULL || (ptr = strchr(ptr, '\n')) == NULL) {
		debug_printf("search_callback: pubdir result [%s]\n", webdata);
		do_error_dialog(_("Couldn't get search results"), _("Gadu-Gadu Error"));
		return;
	}
	ptr++;

	buf = g_strconcat("<B>", _("Gadu-Gadu Search Engine"), "</B><BR>\n", NULL);

	webdata_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES);

	j = 0;

	/* Parse array */
	for (i = 0; webdata_tbl[i] != NULL; i++) {
		gchar *p, *oldibuf;
		static gchar *ibuf;

		g_strdelimit(webdata_tbl[i], "\t\n", ' ');

		/* GG_PUBDIR_HOST service returns 7 lines of data per directory entry */
		if (i % 8 == 0)
			j = 0;

		p = charset_convert(g_strstrip(webdata_tbl[i]), "CP1250", find_local_charset());

		oldibuf = ibuf;

		switch (j) {
		case 0:
			ibuf = g_strconcat("---------------------------------<BR>\n", NULL);
			oldibuf = ibuf;
			ibuf = g_strconcat(oldibuf, "<B>", _("Active"), ":</B> ",
					   (atoi(p) == 2) ? _("yes") : _("no"), "<BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 1:
			ibuf = g_strconcat(oldibuf, "<B>", _("UIN"), ":</B> ", p, "<BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 2:
			ibuf = g_strconcat(oldibuf, "<B>", _("First name"), ":</B> ", p, "<BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 3:
			ibuf =
			    g_strconcat(oldibuf, "<B>", _("Second Name"), ":</B> ", p, "<BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 4:
			ibuf = g_strconcat(oldibuf, "<B>", _("Nick"), ":</B> ", p, "<BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 5:
			/* Hack, invalid_uin does what we really want here but may change in future */
			if (invalid_uin(p))
				ibuf =
				    g_strconcat(oldibuf, "<B>", _("Birth year"), ":</B> <BR>\n", NULL);
			else
				ibuf =
				    g_strconcat(oldibuf, "<B>", _("Birth year"), ":</B> ", p, "<BR>\n",
						NULL);
			g_free(oldibuf);
			break;
		case 6:
			if (atoi(p) == GG_GENDER_FEMALE)
				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> woman<BR>\n", NULL);
			else if (atoi(p) == GG_GENDER_MALE)
				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> man<BR>\n", NULL);
			else
				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> <BR>\n", NULL);
			g_free(oldibuf);
			break;
		case 7:
			ibuf = g_strconcat(oldibuf, "<B>", _("City"), ":</B> ", p, "<BR>\n", NULL);
			g_free(oldibuf);

			/* We have all lines, so add them to buffer */
			{
				gchar *oldbuf = buf;
				buf = g_strconcat(oldbuf, ibuf, NULL);
				g_free(oldbuf);
			}

			g_free(ibuf);
			break;
		}

		g_free(p);

		j++;
	}

	g_strfreev(webdata_tbl);

	g_show_info_text(gc, NULL, 2, buf, NULL);

	g_free(buf);
}

static void import_buddies_server_results(struct gaim_connection *gc, gchar *webdata)
{
	gchar *ptr;
	gchar **users_tbl;
	int i;
	if (strstr(webdata, "no_data:")) {
		do_error_dialog(_("There is no Buddy List stored on server. Sorry!"),
				_("Gadu-Gadu Error"));
		return;
	}

	if ((ptr = strstr(webdata, "get_results:")) == NULL || (ptr = strchr(ptr, ':')) == NULL) {
		debug_printf("import_buddies_server_results: import buddies result [%s]\n", webdata);
		do_error_dialog(_("Couldn't Import Buddies List from Server"), _("Gadu-Gadu Error"));
		return;
	}
	ptr++;

	users_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES);

	/* Parse array of Buddies List */
	for (i = 0; users_tbl[i] != NULL; i++) {
		gchar **data_tbl;
		gchar *name, *show;

		g_strdelimit(users_tbl[i], "\r\t\n\015", ' ');
		data_tbl = g_strsplit(users_tbl[i], ";", 8);

		show = data_tbl[3];
		name = data_tbl[6];

		if (invalid_uin(name)) {
			continue;
		}

		debug_printf("import_buddies_server_results: uin: %s\n", name);
		if (!find_buddy(gc, name)) {
			/* Default group if none specified on server */
			gchar *group = g_strdup("Gadu-Gadu");
			if (strlen(data_tbl[5])) {
				gchar **group_tbl = g_strsplit(data_tbl[5], ",", 2);
				if (strlen(group_tbl[0])) {
					g_free(group);
					group = g_strdup(group_tbl[0]);
				}
				g_strfreev(group_tbl);
			}
			/* Add Buddy to our userlist */
			add_buddy(gc, group, name, strlen(show) ? show : name);
			do_export(gc);
			g_free(group);
		}
		g_strfreev(data_tbl);
	}
	g_strfreev(users_tbl);
}

static void export_buddies_server_results(struct gaim_connection *gc, gchar *webdata)
{
	if (strstr(webdata, "put_success:")) {
		do_error_dialog(_("Buddies List sucessfully transfered into Server"),
				_("Gadu-Gadu Information"));
		return;
	}

	debug_printf("export_buddies_server_results: webdata [%s]\n", webdata);
	do_error_dialog(_("Couldn't transfer Buddies List into Server"), _("Gadu-Gadu Error"));
}

static void delete_buddies_server_results(struct gaim_connection *gc, gchar *webdata)
{
	if (strstr(webdata, "put_success:")) {
		do_error_dialog(_("Buddies List sucessfully deleted from Server"),
				_("Gadu-Gadu Information"));
		return;
	}

	debug_printf("delete_buddies_server_results: webdata [%s]\n", webdata);
	do_error_dialog(_("Couldn't delete Buddies List from Server"), _("Gadu-Gadu Error"));
}

static void password_change_server_results(struct gaim_connection *gc, gchar *webdata)
{
	if (strstr(webdata, "reg_success:")) {
		do_error_dialog(_("Password changed sucessfully"),
				_("Gadu-Gadu Information"));
		return;
	}

	debug_printf("delete_buddies_server_results: webdata [%s]\n", webdata);
	do_error_dialog(_("Password couldn't be changed"), _("Gadu-Gadu Error"));
}

static void http_results(gpointer data, gint source, GaimInputCondition cond)
{
	struct agg_http *hdata = data;
	struct gaim_connection *gc = hdata->gc;
	char *webdata;
	int len;
	char read_data;

	debug_printf("http_results: begin\n");

	if (!g_slist_find(connections, gc)) {
		debug_printf("search_callback: g_slist_find error\n");
		gaim_input_remove(hdata->inpa);
		g_free(hdata);
		close(source);
		return;
	}

	webdata = NULL;
	len = 0;

	while (read(source, &read_data, 1) > 0 || errno == EWOULDBLOCK) {
		if (errno == EWOULDBLOCK) {
			errno = 0;
			continue;
		}

		if (!read_data)
			continue;

		len++;
		webdata = g_realloc(webdata, len);
		webdata[len - 1] = read_data;
	}

	webdata = g_realloc(webdata, len + 1);
	webdata[len] = 0;

	gaim_input_remove(hdata->inpa);
	close(source);

	debug_printf("http_results: type %d, webdata [%s]\n", hdata->type, webdata);

	switch (hdata->type) {
	case AGG_HTTP_SEARCH:
		search_results(gc, webdata);
		break;
	case AGG_HTTP_USERLIST_IMPORT:
		import_buddies_server_results(gc, webdata);
		break;
	case AGG_HTTP_USERLIST_EXPORT:
		export_buddies_server_results(gc, webdata);
		break;
	case AGG_HTTP_USERLIST_DELETE:
	        delete_buddies_server_results(gc, webdata);
		break;
	case AGG_HTTP_PASSWORD_CHANGE:
		password_change_server_results(gc, webdata);
		break;
	case AGG_HTTP_NONE:
	default:
		debug_printf("http_results: unsupported type %d\n", hdata->type);
		break;
	}

	g_free(webdata);
	g_free(hdata);
}

static void http_req_callback(gpointer data, gint source, GaimInputCondition cond)
{
	struct agg_http *hdata = data;
	struct gaim_connection *gc = hdata->gc;
	gchar *request = hdata->request;
	gchar *buf;

	debug_printf("http_req_callback: begin\n");

	if (!g_slist_find(connections, gc)) {
		debug_printf("http_req_callback: g_slist_find error\n");
		g_free(request);
		g_free(hdata);
		close(source);
		return;
	}

	if (source == -1) {
		g_free(request);
		g_free(hdata);
		return;
	}

	debug_printf("http_req_callback: http request [%s]\n", request);

	buf = g_strdup_printf("POST %s HTTP/1.0\r\n"
			      "Host: %s\r\n"
			      "Content-Type: application/x-www-form-urlencoded\r\n"
			      "User-Agent: " GG_HTTP_USERAGENT "\r\n"
			      "Content-Length: %d\r\n"
			      "Pragma: no-cache\r\n" "\r\n" "%s\r\n",
			      hdata->form, hdata->host, strlen(request), request);

	g_free(request);

	if (write(source, buf, strlen(buf)) < strlen(buf)) {
		g_free(buf);
		g_free(hdata);
		close(source);
		do_error_dialog(_("Couldn't send http request"), _("Gadu-Gadu Error"));
		return;
	}

	g_free(buf);

	hdata->inpa = gaim_input_add(source, GAIM_INPUT_READ, http_results, hdata);
}

static void import_buddies_server(struct gaim_connection *gc)
{
	struct agg_http *hi = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];
	gchar *u = gg_urlencode(gc->username);
	gchar *p = gg_urlencode(gc->password);

	hi->gc = gc;
	hi->type = AGG_HTTP_USERLIST_IMPORT;
	hi->form = AGG_PUBDIR_USERLIST_IMPORT_FORM;
	hi->host = GG_PUBDIR_HOST;
	hi->request = g_strdup_printf("FmNum=%s&Pass=%s", u, p);

	g_free(u);
	g_free(p);

	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, hi) < 0) {
		g_snprintf(msg, sizeof(msg), _("Buddies List import from Server failed (%s)"),
			   GG_PUBDIR_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(hi->request);
		g_free(hi);
		return;
	}
}

static void export_buddies_server(struct gaim_connection *gc)
{
	struct agg_http *he = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];
	gchar *ptr;
	gchar *u = gg_urlencode(gc->username);
	gchar *p = gg_urlencode(gc->password);

	GSList *gr = gc->groups;

	he->gc = gc;
	he->type = AGG_HTTP_USERLIST_EXPORT;
	he->form = AGG_PUBDIR_USERLIST_EXPORT_FORM;
	he->host = GG_PUBDIR_HOST;
	he->request = g_strdup_printf("FmNum=%s&Pass=%s&Contacts=", u, p);

	g_free(u);
	g_free(p);

	while (gr) {
		struct group *g = gr->data;
		GSList *m = g->members;
		while (m) {
			struct buddy *b = m->data;
			gchar *newdata;
			/* GG Number */
			gchar *name = gg_urlencode(b->name);
			/* GG Pseudo */
			gchar *show = gg_urlencode(strlen(b->show) ? b->show : b->name);
			/* Group Name */
			gchar *gname = gg_urlencode(g->name);

			ptr = he->request;
			newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s\r\n",
						  show, show, show, show, "", gname, name);
			he->request = g_strconcat(ptr, newdata, NULL);

			g_free(newdata);
			g_free(ptr);

			g_free(gname);
			g_free(show);
			g_free(name);

			m = g_slist_next(m);
		}
		gr = g_slist_next(gr);
	}

	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, he) < 0) {
		g_snprintf(msg, sizeof(msg), _("Buddies List export to Server failed (%s)"),
			   GG_PUBDIR_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(he->request);
		g_free(he);
		return;
	}
}

static void delete_buddies_server(struct gaim_connection *gc)
{
	struct agg_http *he = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];
	gchar *u = gg_urlencode(gc->username);
	gchar *p = gg_urlencode(gc->password);

	he->gc = gc;
	he->type = AGG_HTTP_USERLIST_DELETE;
	he->form = AGG_PUBDIR_USERLIST_EXPORT_FORM;
	he->host = GG_PUBDIR_HOST;
	he->request = g_strdup_printf("FmNum=%s&Pass=%s&Delete=1", u, p);

	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, he) < 0) {
		g_snprintf(msg, sizeof(msg), _("Deletion of Buddies List from Server failed (%s)"),
			   GG_PUBDIR_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(he->request);
		g_free(he);
		return;
	}
}

static void agg_dir_search(struct gaim_connection *gc, char *first, char *middle,
			   char *last, char *maiden, char *city, char *state,
			   char *country, char *email)
{
	struct agg_http *srch = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];

	srch->gc = gc;
	srch->type = AGG_HTTP_SEARCH;
	srch->form = AGG_PUBDIR_SEARCH_FORM;
	srch->host = GG_PUBDIR_HOST;

	if (email && strlen(email)) {
		gchar *eemail = gg_urlencode(email);
		srch->request = g_strdup_printf("Mode=1&Email=%s", eemail);
		g_free(eemail);
	} else {
		gchar *new_first = charset_convert(first, find_local_charset(), "CP1250");
		gchar *new_last = charset_convert(last, find_local_charset(), "CP1250");
		gchar *new_city = charset_convert(city, find_local_charset(), "CP1250");

		gchar *enew_first = gg_urlencode(new_first);
		gchar *enew_last = gg_urlencode(new_last);
		gchar *enew_city = gg_urlencode(new_city);

		g_free(new_first);
		g_free(new_last);
		g_free(new_city);

		/* For active only add &ActiveOnly= */
		srch->request = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d"
						"&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d",
						enew_first, enew_last, AGG_GENDER_NONE,
						"", enew_city, 0, 0);

		g_free(enew_first);
		g_free(enew_last);
		g_free(enew_city);
	}

	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, srch) < 0) {
		g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"),
			   GG_PUBDIR_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(srch->request);
		g_free(srch);
		return;
	}
}

static void agg_change_passwd(struct gaim_connection *gc, char *old, char *new)
{
	struct agg_http *hpass = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];
	gchar *u = gg_urlencode(gc->username);
	gchar *p = gg_urlencode(gc->password);
	gchar *enew = gg_urlencode(new);
	gchar *eold = gg_urlencode(old);

	hpass->gc = gc;
	hpass->type = AGG_HTTP_PASSWORD_CHANGE;
	hpass->form = AGG_REGISTER_DATA_FORM;
	hpass->host = GG_REGISTER_HOST;

	/* We are using old password as place for email - it's ugly */
	hpass->request = g_strdup_printf("fmnumber=%s&fmpwd=%s&pwd=%s&email=%s&code=%u",
					 u, p, enew, eold, gg_http_hash(old, new));

	g_free(u);
	g_free(p);
	g_free(enew);
	g_free(eold);

	if (proxy_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, http_req_callback, hpass) < 0) {
		g_snprintf(msg, sizeof(msg), _("Changing Password failed (%s)"),
			   GG_REGISTER_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(hpass->request);
		g_free(hpass);
		return;
	}                                        
}

static void agg_do_action(struct gaim_connection *gc, char *action)
{
	if (!strcmp(action, _("Directory Search"))) {
		show_find_info(gc);
	} else if (!strcmp(action, _("Change Password"))) {
		show_change_passwd(gc);
	} else if (!strcmp(action, _("Import Buddies List from Server"))) {
		import_buddies_server(gc);
	} else if (!strcmp(action, _("Export Buddies List to Server"))) {
		export_buddies_server(gc);
	} else if (!strcmp(action, _("Delete Buddies List from Server"))) {
	        delete_buddies_server(gc);
	}
}

static GList *agg_actions()
{
	GList *m = NULL;

	m = g_list_append(m, _("Directory Search"));
	m = g_list_append(m, NULL);
	m = g_list_append(m, _("Change Password"));
	m = g_list_append(m, NULL);
	m = g_list_append(m, _("Import Buddies List from Server"));
	m = g_list_append(m, _("Export Buddies List to Server"));
	m = g_list_append(m, _("Delete Buddies List from Server"));

	return m;
}

static void agg_get_info(struct gaim_connection *gc, char *who)
{
	struct agg_http *srch = g_new0(struct agg_http, 1);
	static char msg[AGG_BUF_LEN];

	srch->gc = gc;
	srch->type = AGG_HTTP_SEARCH;
	srch->form = AGG_PUBDIR_SEARCH_FORM;
	srch->host = GG_PUBDIR_HOST;

	/* If it's invalid uin then maybe it's nickname? */
	if (invalid_uin(who)) {
		gchar *new_who = charset_convert(who, find_local_charset(), "CP1250");
		gchar *enew_who = gg_urlencode(new_who);
		
		g_free(new_who);

		srch->request = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d"
						"&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d",
						"", "", AGG_GENDER_NONE, enew_who, "", 0, 0);

		g_free(enew_who);
	} else
		srch->request = g_strdup_printf("Mode=3&UserId=%s", who);

	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, srch) < 0) {
		g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"),
			   GG_PUBDIR_HOST);
		do_error_dialog(msg, _("Gadu-Gadu Error"));
		g_free(srch->request);
		g_free(srch);
		return;
	}
}

static char **agg_list_icon(int uc)
{
	guint status;
	if (uc == UC_UNAVAILABLE)
		return (char **)gg_sunred_xpm;
	status = uc >> 5;
	/* Drop all masks */
	status &= ~(GG_STATUS_FRIENDS_MASK);
	if (status == GG_STATUS_AVAIL)
		return (char **)gg_sunyellow_xpm;
	if (status == GG_STATUS_BUSY)
		return (char **)gg_suncloud_xpm;
	if (status == GG_STATUS_INVISIBLE)
		return (char **)gg_sunwhitered_xpm;
	return (char **)gg_sunyellow_xpm;
}

static void agg_set_permit_deny_dummy(struct gaim_connection *gc)
{
	/* It's implemented on client side because GG server doesn't support this */
}

static void agg_permit_deny_dummy(struct gaim_connection *gc, char *who)
{
	/* It's implemented on client side because GG server doesn't support this */
}

static struct prpl *my_protocol = NULL;

void gg_init(struct prpl *ret)
{
	ret->protocol = PROTO_GADUGADU;
	ret->options = 0;
	ret->name = agg_name;
	ret->list_icon = agg_list_icon;
	ret->away_states = agg_away_states;
	ret->actions = agg_actions;
	ret->do_action = agg_do_action;
	ret->user_opts = agg_user_opts;
	ret->buddy_menu = agg_buddy_menu;
	ret->chat_info = NULL;
	ret->login = agg_login;
	ret->close = agg_close;
	ret->send_im = agg_send_im;
	ret->set_info = NULL;
	ret->get_info = agg_get_info;
	ret->set_away = agg_set_away;
	ret->set_dir = NULL;
	ret->get_dir = agg_get_info;
	ret->dir_search = agg_dir_search;
	ret->set_idle = NULL;
	ret->change_passwd = agg_change_passwd;
	ret->add_buddy = agg_add_buddy;
	ret->add_buddies = agg_add_buddies;
	ret->remove_buddy = agg_rem_buddy;
	ret->add_permit = agg_permit_deny_dummy;
	ret->add_deny = agg_permit_deny_dummy;
	ret->rem_permit = agg_permit_deny_dummy;
	ret->rem_deny = agg_permit_deny_dummy;
	ret->set_permit_deny = agg_set_permit_deny_dummy;
	ret->warn = NULL;
	ret->join_chat = NULL;
	ret->chat_invite = NULL;
	ret->chat_leave = NULL;
	ret->chat_whisper = NULL;
	ret->chat_send = NULL;
	ret->keepalive = agg_keepalive;
	ret->normalize = NULL;
	my_protocol = ret;
}

#ifndef STATIC

char *gaim_plugin_init(GModule *handle)
{
	load_protocol(gg_init, sizeof(struct prpl));
	return NULL;
}

void gaim_plugin_remove()
{
	struct prpl *p = find_prpl(PROTO_GADUGADU);
	if (p == my_protocol)
		unload_protocol(p);
}

char *name()
{
	return "Gadu-Gadu";
}

char *description()
{
	return PRPL_DESC("Gadu-Gadu");
}

#endif

/*
 * Local variables:
 * c-indentation-style: k&r
 * c-basic-offset: 8
 * indent-tabs-mode: notnil
 * End:
 *
 * vim: shiftwidth=8:
 */