diff libpurple/protocols/qq/buddy_opt.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,580 @@
+/**
+ * @file buddy_opt.c
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ */
+
+#include "debug.h"
+#include "internal.h"
+#include "notify.h"
+#include "request.h"
+
+#include "buddy_info.h"
+#include "buddy_list.h"
+#include "buddy_opt.h"
+#include "char_conv.h"
+#include "crypt.h"
+#include "header_info.h"
+#include "im.h"
+#include "keep_alive.h"
+#include "packet_parse.h"
+#include "send_core.h"
+#include "utils.h"
+
+#define GAIM_GROUP_QQ_FORMAT          "QQ (%s)"
+#define GAIM_GROUP_QQ_UNKNOWN         "QQ Unknown"
+#define GAIM_GROUP_QQ_BLOCKED         "QQ Blocked"
+
+#define QQ_REMOVE_BUDDY_REPLY_OK      0x00
+#define QQ_REMOVE_SELF_REPLY_OK       0x00
+#define QQ_ADD_BUDDY_AUTH_REPLY_OK    0x30	/* ASCII value of "0" */
+
+enum {
+	QQ_MY_AUTH_APPROVE = 0x30,	/* ASCII value of "0" */
+	QQ_MY_AUTH_REJECT = 0x31,	/* ASCII value of "1" */
+	QQ_MY_AUTH_REQUEST = 0x32,	/* ASCII value of "2" */
+};
+
+typedef struct _qq_add_buddy_request {
+	guint32 uid;
+	guint16 seq;
+} qq_add_buddy_request;
+
+/* send packet to remove a buddy from my buddy list */
+static void _qq_send_packet_remove_buddy(GaimConnection *gc, guint32 uid)
+{
+	gchar uid_str[11];
+
+	g_return_if_fail(uid > 0);
+
+	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	qq_send_cmd(gc, QQ_CMD_DEL_FRIEND, TRUE, 0, 
+			TRUE, (guint8 *) uid_str, strlen(uid_str));
+}
+
+/* try to remove myself from someone's buddy list */
+static void _qq_send_packet_remove_self_from(GaimConnection *gc, guint32 uid)
+{
+	guint8 *raw_data, *cursor;
+
+	g_return_if_fail(uid > 0);
+
+	raw_data = g_newa(guint8, 4);
+	cursor = raw_data;
+	create_packet_dw(raw_data, &cursor, uid);
+
+	qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, TRUE, 0, TRUE, raw_data, 4);
+}
+
+/* try to add a buddy without authentication */
+static void _qq_send_packet_add_buddy(GaimConnection *gc, guint32 uid)
+{
+	qq_data *qd;
+	qq_add_buddy_request *req;
+	gchar uid_str[11];
+
+	g_return_if_fail(uid > 0);
+
+	/* we need to send the ascii code of this uid to qq server */
+	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	qq_send_cmd(gc, QQ_CMD_ADD_FRIEND_WO_AUTH, TRUE, 0, 
+			TRUE, (guint8 *) uid_str, strlen(uid_str));
+
+	/* must be set after sending packet to get the correct send_seq */
+	qd = (qq_data *) gc->proto_data;
+	req = g_new0(qq_add_buddy_request, 1);
+	req->seq = qd->send_seq;
+	req->uid = uid;
+	qd->add_buddy_request = g_list_append(qd->add_buddy_request, req);
+}
+
+/* this buddy needs authentication, text conversion is done at lowest level */
+static void _qq_send_packet_buddy_auth(GaimConnection *gc, guint32 uid, const gchar response, const gchar *text)
+{
+	gchar *text_qq, uid_str[11];
+	guint8 bar, *cursor, *raw_data;
+
+	g_return_if_fail(uid != 0);
+
+	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	bar = 0x1f;
+	raw_data = g_newa(guint8, QQ_MSG_IM_MAX);
+	cursor = raw_data;
+
+	create_packet_data(raw_data, &cursor, (guint8 *) uid_str, strlen(uid_str));
+	create_packet_b(raw_data, &cursor, bar);
+	create_packet_b(raw_data, &cursor, response);
+
+	if (text != NULL) {
+		text_qq = utf8_to_qq(text, QQ_CHARSET_DEFAULT);
+		create_packet_b(raw_data, &cursor, bar);
+		create_packet_data(raw_data, &cursor, (guint8 *) text_qq, strlen(text_qq));
+		g_free(text_qq);
+	}
+
+	qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+}
+
+static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid *g, const gchar *text)
+{
+	GaimConnection *gc;
+	guint32 uid;
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text);
+	g_free(g);
+}
+
+/* the real packet to reject and request is sent from here */
+static void _qq_reject_add_request_real(gc_and_uid *g, const gchar *reason)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason);
+	g_free(g);
+}
+
+/* we approve other's request of adding me as friend */
+void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL);
+	g_free(g);
+}
+
+void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg)
+{
+	g_free(g);
+}
+
+/* we reject other's request of adding me as friend */
+void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g)
+{
+	gint uid;
+	gchar *msg1, *msg2;
+	GaimConnection *gc;
+	gc_and_uid *g2;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid != 0);
+
+	g_free(g);
+
+	g2 = g_new0(gc_and_uid, 1);
+	g2->gc = gc;
+	g2->uid = uid;
+
+	msg1 = g_strdup_printf(_("You rejected %d's request"), uid);
+	msg2 = g_strdup(_("Input your reason:"));
+
+	gaim_request_input(gc, _("Reject request"), msg1, msg2,
+			   _("Sorry, you are not my type..."), TRUE, FALSE,
+			   NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL, g2);
+}
+
+void qq_add_buddy_with_gc_and_uid(gc_and_uid *g)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid != 0);
+
+	_qq_send_packet_add_buddy(gc, uid);
+	g_free(g);
+}
+
+void qq_block_buddy_with_gc_and_uid(gc_and_uid *g)
+{
+	guint32 uid;
+	GaimConnection *gc;
+	GaimBuddy buddy;
+	GaimGroup group;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(uid > 0);
+
+	buddy.name = uid_to_gaim_name(uid);
+	group.name = GAIM_GROUP_QQ_BLOCKED;
+
+	qq_remove_buddy(gc, &buddy, &group);
+	_qq_send_packet_remove_self_from(gc, uid);
+}
+
+/*  process reply to add_buddy_auth request */
+void qq_process_add_buddy_auth_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+	gchar **segments, *msg_utf8;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+	cursor = data;
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n");
+			if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+				return;
+			msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+			gaim_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8);
+			g_free(msg_utf8);
+		} else {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
+		}
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n");
+	}
+}
+
+/* process the server reply for my request to remove a buddy */
+void qq_process_remove_buddy_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_REMOVE_BUDDY_REPLY_OK) {
+			/* there is no reason return from server */
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
+		} else {		/* if reply */
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+			gaim_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
+		}
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove buddy reply\n");
+	}
+}
+
+/* process the server reply for my request to remove myself from a buddy */
+void qq_process_remove_self_reply(guint8 *buf, gint buf_len, GaimConnection *gc) 
+{
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_REMOVE_SELF_REPLY_OK)
+			/* there is no reason return from server */
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove self fails\n");
+		else {		/* if reply */
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
+			gaim_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL);
+		}
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
+	}
+}
+
+void qq_process_add_buddy_reply(guint8 *buf, gint buf_len, guint16 seq, GaimConnection *gc)
+{
+	qq_data *qd;
+	gint len, for_uid;
+	gchar *msg, **segments, *uid, *reply;
+	guint8 *data;
+	GList *list;
+	GaimBuddy *b;
+	gc_and_uid *g;
+	qq_add_buddy_request *req;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	for_uid = 0;
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+
+	list = qd->add_buddy_request;
+	while (list != NULL) {
+		req = (qq_add_buddy_request *) list->data;
+		if (req->seq == seq) {	/* reply to this */
+			for_uid = req->uid;
+			qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data);
+			g_free(req);
+			break;
+		}
+		list = list->next;
+	}
+
+	if (for_uid == 0) {	/* we have no record for this */
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no record for add buddy reply [%d], discard\n", seq);
+		return;
+	} else {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid);
+	}
+
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+			return;
+		uid = segments[0];
+		reply = segments[1];
+		if (strtol(uid, NULL, 10) != qd->uid) {	/* should not happen */
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Add buddy reply is to [%s], not me!", uid);
+			g_strfreev(segments);
+			return;
+		}
+
+		if (strtol(reply, NULL, 10) > 0) {	/* need auth */
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy attempt fails, need authentication\n");
+			b = gaim_find_buddy(gc->account, uid_to_gaim_name(for_uid));
+			if (b != NULL)
+				gaim_blist_remove_buddy(b);
+			g = g_new0(gc_and_uid, 1);
+			g->gc = gc;
+			g->uid = for_uid;
+			msg = g_strdup_printf(_("User %d needs authentication"), for_uid);
+			gaim_request_input(gc, NULL, msg,
+					   _("Input request here"),
+					   _("Would you be my friend?"),
+					   TRUE, FALSE, NULL, _("Send"),
+					   G_CALLBACK
+					   (_qq_send_packet_add_buddy_auth_with_gc_and_uid),
+					   _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g);
+			g_free(msg);
+		} else {	/* add OK */
+			qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
+			msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid);
+			gaim_notify_info(gc, NULL, msg, NULL);
+			g_free(msg);
+		}
+		g_strfreev(segments);
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy reply\n");
+	}
+}
+
+GaimGroup *qq_get_gaim_group(const gchar *group_name)
+{
+	GaimGroup *g;
+
+	g_return_val_if_fail(group_name != NULL, NULL);
+
+	g = gaim_find_group(group_name);
+	if (g == NULL) {
+		g = gaim_group_new(group_name);
+		gaim_blist_add_group(g, NULL);
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new group: %s\n", group_name);
+	}
+
+	return g;
+}
+
+/* we add new buddy, if the received packet is from someone not in my list
+ * return the GaimBuddy that is just created */
+GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection *gc, guint32 uid, gboolean is_known, gboolean create)
+{
+	GaimAccount *a;
+	GaimBuddy *b;
+	GaimGroup *g;
+	qq_data *qd;
+	qq_buddy *q_bud;
+	gchar *name, *group_name;
+
+	a = gc->account;
+	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(a != NULL && uid != 0, NULL);
+
+	group_name = is_known ?
+	    g_strdup_printf(GAIM_GROUP_QQ_FORMAT, gaim_account_get_username(a)) : g_strdup(GAIM_GROUP_QQ_UNKNOWN);
+
+	g = qq_get_gaim_group(group_name);
+
+	name = uid_to_gaim_name(uid);
+	b = gaim_find_buddy(gc->account, name);
+	/* remove old, we can not simply return here
+	 * because there might be old local copy of this buddy */
+	if (b != NULL)
+		gaim_blist_remove_buddy(b);
+
+	b = gaim_buddy_new(a, name, NULL);
+
+	if (!create)
+		b->proto_data = NULL;
+	else {
+		q_bud = g_new0(qq_buddy, 1);
+		q_bud->uid = uid;
+		b->proto_data = q_bud;
+		qd->buddies = g_list_append(qd->buddies, q_bud);
+		qq_send_packet_get_info(gc, q_bud->uid, FALSE);
+		qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
+	}
+
+	gaim_blist_add_buddy(b, NULL, g, NULL);
+	gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new buddy: [%s]\n", name);
+
+	g_free(name);
+	g_free(group_name);
+
+	return b;
+}
+
+/* add a buddy and send packet to QQ server
+ * note that when gaim load local cached buddy list into its blist
+ * it also calls this funtion, so we have to 
+ * define qd->logged_in=TRUE AFTER serv_finish_login(gc) */
+void qq_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+	qq_data *qd;
+	guint32 uid;
+	GaimBuddy *b;
+
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->logged_in)
+		return;		/* IMPORTANT ! */
+
+	uid = gaim_name_to_uid(buddy->name);
+	if (uid > 0)
+		_qq_send_packet_add_buddy(gc, uid);
+	else {
+		b = gaim_find_buddy(gc->account, buddy->name);
+		if (b != NULL)
+			gaim_blist_remove_buddy(b);
+		gaim_notify_error(gc, NULL,
+				  _("QQid Error"),
+				  _("Invalid QQid"));
+	}
+}
+
+/* remove a buddy and send packet to QQ server accordingly */
+void qq_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+	qq_data *qd;
+	GaimBuddy *b;
+	qq_buddy *q_bud;
+	guint32 uid;
+
+	qd = (qq_data *) gc->proto_data;
+	uid = gaim_name_to_uid(buddy->name);
+
+	if (!qd->logged_in)
+		return;
+
+	if (uid > 0)
+		_qq_send_packet_remove_buddy(gc, uid);
+
+	b = gaim_find_buddy(gc->account, buddy->name);
+	if (b != NULL) {
+		q_bud = (qq_buddy *) b->proto_data;
+		if (q_bud != NULL)
+			qd->buddies = g_list_remove(qd->buddies, q_bud);
+		else
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no qq_buddy record for %s\n", buddy->name);
+		/* remove buddy on blist, this does not trigger qq_remove_buddy again
+		 * do this only if the request comes from block request,
+		 * otherwise gaim segmentation fault */
+		if (g_ascii_strcasecmp(group->name, GAIM_GROUP_QQ_BLOCKED) == 0)
+			gaim_blist_remove_buddy(b);
+	}
+}
+
+/* free add buddy request queue */
+void qq_add_buddy_request_free(qq_data *qd)
+{
+	gint i;
+	qq_add_buddy_request *p;
+
+	i = 0;
+	while (qd->add_buddy_request) {
+		p = (qq_add_buddy_request *) (qd->add_buddy_request->data);
+		qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p);
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d add buddy requests are freed!\n", i);
+}
+
+/* free up all qq_buddy */
+void qq_buddies_list_free(GaimAccount *account, qq_data *qd)
+{
+	gint i;
+	qq_buddy *p;
+	gchar *name;
+	GaimBuddy *b;
+
+	i = 0;
+	while (qd->buddies) {
+		p = (qq_buddy *) (qd->buddies->data);
+		qd->buddies = g_list_remove(qd->buddies, p);
+		name = uid_to_gaim_name(p->uid);
+		b = gaim_find_buddy(account, name);   	
+		if(b != NULL) 
+			b->proto_data = NULL;
+		else
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_buddy %s not found in gaim proto_data\n", name);
+		g_free(name);
+
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d qq_buddy structures are freed!\n", i);
+}