libgaim/protocols/msn/slp.c

branch
cpw.khc.msnp14
changeset 20472
6a6d2ef151e6
parent 13861
dc0021ad2b73
parent 15345
797936e968ff
child 20473
91e1b3a49d10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/protocols/msn/slp.c	Sun Apr 15 02:10:37 2007 +0000
@@ -0,0 +1,1147 @@
+/**
+ * @file msnslp.c MSNSLP support
+ *
+ * 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 "msn.h"
+#include "slp.h"
+#include "slpcall.h"
+#include "slpmsg.h"
+#include "slpsession.h"
+
+#include "object.h"
+#include "user.h"
+#include "switchboard.h"
+
+/* ms to delay between sending buddy icon requests to the server. */
+#define BUDDY_ICON_DELAY 20000
+/*debug SLP*/
+#define MSN_DEBUG_UD
+
+static void send_ok(MsnSlpCall *slpcall, const char *branch,
+					const char *type, const char *content);
+
+static void send_decline(MsnSlpCall *slpcall, const char *branch,
+						 const char *type, const char *content);
+
+void msn_request_user_display(MsnUser *user);
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+static char *
+get_token(const char *str, const char *start, const char *end)
+{
+	const char *c, *c2;
+
+	if ((c = strstr(str, start)) == NULL)
+		return NULL;
+
+	c += strlen(start);
+
+	if (end != NULL)
+	{
+		if ((c2 = strstr(c, end)) == NULL)
+			return NULL;
+
+		return g_strndup(c, c2 - c);
+	}
+	else
+	{
+		/* This has to be changed */
+		return g_strdup(c);
+	}
+
+}
+
+/**************************************************************************
+ * Xfer
+ **************************************************************************/
+
+static void
+msn_xfer_init(GaimXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	/* MsnSlpLink *slplink; */
+	char *content;
+
+	gaim_debug_info("msn", "xfer_init\n");
+
+	slpcall = xfer->data;
+
+	/* Send Ok */
+	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+							  slpcall->session_id);
+
+	send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+			content);
+
+	g_free(content);
+	msn_slplink_unleash(slpcall->slplink);
+}
+
+void
+msn_xfer_cancel(GaimXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *content;
+
+	g_return_if_fail(xfer != NULL);
+	g_return_if_fail(xfer->data != NULL);
+
+	slpcall = xfer->data;
+
+	if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL)
+	{
+		if (slpcall->started)
+		{
+			msn_slp_call_close(slpcall);
+		}
+		else
+		{
+			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+									slpcall->session_id);
+
+			send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+						content);
+
+			g_free(content);
+			msn_slplink_unleash(slpcall->slplink);
+
+			msn_slp_call_destroy(slpcall);
+		}
+	}
+}
+
+void
+msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset)
+{
+	GaimXfer *xfer;
+
+	xfer = slpcall->xfer;
+
+	xfer->bytes_sent = (offset + len);
+	xfer->bytes_remaining = total_length - (offset + len);
+
+	gaim_xfer_update_progress(xfer);
+}
+
+void
+msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
+{
+	if ((gaim_xfer_get_status(slpcall->xfer) != GAIM_XFER_STATUS_DONE) &&
+		(gaim_xfer_get_status(slpcall->xfer) != GAIM_XFER_STATUS_CANCEL_REMOTE) &&
+		(gaim_xfer_get_status(slpcall->xfer) != GAIM_XFER_STATUS_CANCEL_LOCAL))
+	{
+		gaim_xfer_cancel_remote(slpcall->xfer);
+	}
+}
+
+void
+msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body,
+					  gsize size)
+{
+	gaim_xfer_set_completed(slpcall->xfer, TRUE);
+}
+
+/**************************************************************************
+ * SLP Control
+ **************************************************************************/
+
+#if 0
+static void
+got_transresp(MsnSlpCall *slpcall, const char *nonce,
+			  const char *ips_str, int port)
+{
+	MsnDirectConn *directconn;
+	char **ip_addrs, **c;
+
+	directconn = msn_directconn_new(slpcall->slplink);
+
+	directconn->initial_call = slpcall;
+
+	/* msn_directconn_parse_nonce(directconn, nonce); */
+	directconn->nonce = g_strdup(nonce);
+
+	ip_addrs = g_strsplit(ips_str, " ", -1);
+
+	for (c = ip_addrs; *c != NULL; c++)
+	{
+		gaim_debug_info("msn", "ip_addr = %s\n", *c);
+		if (msn_directconn_connect(directconn, *c, port))
+			break;
+	}
+
+	g_strfreev(ip_addrs);
+}
+#endif
+
+static void
+send_ok(MsnSlpCall *slpcall, const char *branch,
+		const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+
+	slplink = slpcall->slplink;
+
+	/* 200 OK */
+	slpmsg = msn_slpmsg_sip_new(slpcall, 1,
+								"MSNSLP/1.0 200 OK",
+								branch, type, content);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP 200 OK";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+	msn_slp_call_session_init(slpcall);
+}
+
+static void
+send_decline(MsnSlpCall *slpcall, const char *branch,
+			 const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+
+	slplink = slpcall->slplink;
+
+	/* 603 Decline */
+	slpmsg = msn_slpmsg_sip_new(slpcall, 1,
+								"MSNSLP/1.0 603 Decline",
+								branch, type, content);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP 603 Decline";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+#define MAX_FILE_NAME_LEN 0x226
+
+static void
+got_sessionreq(MsnSlpCall *slpcall, const char *branch,
+			   const char *euf_guid, const char *context)
+{
+	if (!strcmp(euf_guid, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6"))
+	{
+		/* Emoticon or UserDisplay */
+		MsnSlpSession *slpsession;
+		MsnSlpLink *slplink;
+		MsnSlpMessage *slpmsg;
+		MsnObject *obj;
+		char *msnobj_data;
+		const char *file_name;
+		char *content;
+		gsize len;
+		int type;
+
+		/* Send Ok */
+		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+								  slpcall->session_id);
+
+		send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
+				content);
+
+		g_free(content);
+
+		slplink = slpcall->slplink;
+
+		msnobj_data = (char *)gaim_base64_decode(context, &len);
+		obj = msn_object_new_from_string(msnobj_data);
+		type = msn_object_get_type(obj);
+		g_free(msnobj_data);
+
+		if (!(type == MSN_OBJECT_USERTILE))
+		{
+			gaim_debug_error("msn", "Wrong object?\n");
+			msn_object_destroy(obj);
+			g_return_if_reached();
+		}
+
+		file_name = msn_object_get_real_location(obj);
+
+		if (file_name == NULL)
+		{
+			gaim_debug_error("msn", "Wrong object.\n");
+			msn_object_destroy(obj);
+			g_return_if_reached();
+		}
+
+		msn_object_destroy(obj);
+
+		slpsession = msn_slplink_find_slp_session(slplink,
+												  slpcall->session_id);
+
+		/* DATA PREP */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpcall = slpcall;
+		slpmsg->slpsession = slpsession;
+		slpmsg->session_id = slpsession->id;
+		msn_slpmsg_set_body(slpmsg, NULL, 4);
+#ifdef MSN_DEBUG_SLP
+		slpmsg->info = "SLP DATA PREP";
+#endif
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+		/* DATA */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpcall = slpcall;
+		slpmsg->slpsession = slpsession;
+		slpmsg->flags = 0x20;
+#ifdef MSN_DEBUG_SLP
+		slpmsg->info = "SLP DATA";
+#endif
+		msn_slpmsg_open_file(slpmsg, file_name);
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+	}
+	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
+	{
+		/* File Transfer */
+		GaimAccount *account;
+		GaimXfer *xfer;
+		char *bin;
+		gsize bin_len;
+		guint32 file_size;
+		char *file_name;
+		gunichar2 *uni_name;
+
+		account = slpcall->slplink->session->account;
+
+		slpcall->cb = msn_xfer_completed_cb;
+		slpcall->end_cb = msn_xfer_end_cb;
+		slpcall->progress_cb = msn_xfer_progress_cb;
+		slpcall->branch = g_strdup(branch);
+
+		slpcall->pending = TRUE;
+
+		xfer = gaim_xfer_new(account, GAIM_XFER_RECEIVE,
+							 slpcall->slplink->remote_user);
+		if (xfer)
+		{
+			bin = (char *)gaim_base64_decode(context, &bin_len);
+			file_size = GUINT32_FROM_LE(*((gsize *)bin + 2));
+
+			uni_name = (gunichar2 *)(bin + 20);
+			while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) {
+				*uni_name = GUINT16_FROM_LE(*uni_name);
+				uni_name++;
+			}
+
+			file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1,
+										NULL, NULL, NULL);
+
+			g_free(bin);
+
+			gaim_xfer_set_filename(xfer, file_name);
+			gaim_xfer_set_size(xfer, file_size);
+			gaim_xfer_set_init_fnc(xfer, msn_xfer_init);
+			gaim_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
+			gaim_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
+
+			slpcall->xfer = xfer;
+			xfer->data = slpcall;
+
+			gaim_xfer_request(xfer);
+		}
+	}
+}
+
+void
+send_bye(MsnSlpCall *slpcall, const char *type)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+	char *header;
+
+	slplink = slpcall->slplink;
+
+	g_return_if_fail(slplink != NULL);
+
+	header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0",
+							 slplink->local_user);
+
+	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header,
+								"A0D624A6-6C0C-4283-A9E0-BC97B4B46D32",
+								type,
+								"\r\n");
+	g_free(header);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP BYE";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+static void
+got_invite(MsnSlpCall *slpcall,
+		   const char *branch, const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+
+	slplink = slpcall->slplink;
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+		char *euf_guid, *context;
+		char *temp;
+
+		euf_guid = get_token(content, "EUF-GUID: {", "}\r\n");
+
+		temp = get_token(content, "SessionID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->session_id = atoi(temp);
+		g_free(temp);
+
+		temp = get_token(content, "AppID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->app_id = atoi(temp);
+		g_free(temp);
+
+		context = get_token(content, "Context: ", "\r\n");
+
+		if (context != NULL)
+			got_sessionreq(slpcall, branch, euf_guid, context);
+
+		g_free(context);
+		g_free(euf_guid);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* A direct connection? */
+
+		char *listening, *nonce;
+		char *content;
+
+		if (FALSE)
+		{
+#if 0
+			MsnDirectConn *directconn;
+			/* const char *ip_addr; */
+			char *ip_port;
+			int port;
+
+			/* ip_addr = gaim_prefs_get_string("/core/ft/public_ip"); */
+			ip_port = "5190";
+			listening = "true";
+			nonce = rand_guid();
+
+			directconn = msn_directconn_new(slplink);
+
+			/* msn_directconn_parse_nonce(directconn, nonce); */
+			directconn->nonce = g_strdup(nonce);
+
+			msn_directconn_listen(directconn);
+
+			port = directconn->port;
+
+			content = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: %s\r\n"
+				"Nonce: {%s}\r\n"
+				"Ipv4Internal-Addrs: 192.168.0.82\r\n"
+				"Ipv4Internal-Port: %d\r\n"
+				"\r\n",
+				listening,
+				nonce,
+				port);
+#endif
+		}
+		else
+		{
+			listening = "false";
+			nonce = g_strdup("00000000-0000-0000-0000-000000000000");
+
+			content = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: %s\r\n"
+				"Nonce: {%s}\r\n"
+				"\r\n",
+				listening,
+				nonce);
+		}
+
+		send_ok(slpcall, branch,
+				"application/x-msnmsgr-transrespbody", content);
+
+		g_free(content);
+		g_free(nonce);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+#if 0
+		char *ip_addrs;
+		char *temp;
+		char *nonce;
+		int port;
+
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+
+		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
+		if (temp != NULL)
+			port = atoi(temp);
+		else
+			port = -1;
+		g_free(temp);
+
+		if (ip_addrs == NULL)
+			return;
+
+		if (port > 0)
+			got_transresp(slpcall, nonce, ip_addrs, port);
+
+		g_free(nonce);
+		g_free(ip_addrs);
+#endif
+	}
+}
+
+static void
+got_ok(MsnSlpCall *slpcall,
+	   const char *type, const char *content)
+{
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(type    != NULL);
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+#if 0
+		if (slpcall->type == MSN_SLPCALL_DC)
+		{
+			/* First let's try a DirectConnection. */
+
+			MsnSlpLink *slplink;
+			MsnSlpMessage *slpmsg;
+			char *header;
+			char *content;
+			char *branch;
+
+			slplink = slpcall->slplink;
+
+			branch = rand_guid();
+
+			content = g_strdup_printf(
+				"Bridges: TRUDPv1 TCPv1\r\n"
+				"NetID: 0\r\n"
+				"Conn-Type: Direct-Connect\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+			);
+
+			header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0",
+									 slplink->remote_user);
+
+			slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch,
+										"application/x-msnmsgr-transreqbody",
+										content);
+
+#ifdef MSN_DEBUG_SLP
+			slpmsg->info = "SLP INVITE";
+			slpmsg->text_body = TRUE;
+#endif
+			msn_slplink_send_slpmsg(slplink, slpmsg);
+
+			g_free(header);
+			g_free(content);
+
+			g_free(branch);
+		}
+		else
+		{
+			msn_slp_call_session_init(slpcall);
+		}
+#else
+		msn_slp_call_session_init(slpcall);
+#endif
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* Do we get this? */
+		gaim_debug_info("msn", "OK with transreqbody\n");
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+#if 0
+		char *ip_addrs;
+		char *temp;
+		char *nonce;
+		int port;
+
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+
+		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
+		if (temp != NULL)
+			port = atoi(temp);
+		else
+			port = -1;
+		g_free(temp);
+
+		if (ip_addrs == NULL)
+			return;
+
+		if (port > 0)
+			got_transresp(slpcall, nonce, ip_addrs, port);
+
+		g_free(nonce);
+		g_free(ip_addrs);
+#endif
+	}
+}
+
+MsnSlpCall *
+msn_slp_sip_recv(MsnSlpLink *slplink, const char *body)
+{
+	MsnSlpCall *slpcall;
+
+	if (body == NULL)
+	{
+		gaim_debug_warning("msn", "received bogus message\n");
+		return NULL;
+	}
+
+	if (!strncmp(body, "INVITE", strlen("INVITE")))
+	{
+		char *branch;
+		char *content;
+		char *content_type;
+
+		slpcall = msn_slp_call_new(slplink);
+
+		/* From: <msnmsgr:buddy@hotmail.com> */
+#if 0
+		slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n");
+#endif
+
+		branch = get_token(body, ";branch={", "}");
+
+		slpcall->id = get_token(body, "Call-ID: {", "}");
+
+#if 0
+		long content_len = -1;
+
+		temp = get_token(body, "Content-Length: ", "\r\n");
+		if (temp != NULL)
+			content_len = atoi(temp);
+		g_free(temp);
+#endif
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		got_invite(slpcall, branch, content_type, content);
+
+		g_free(branch);
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 ")))
+	{
+		char *content;
+		char *content_type;
+		/* Make sure this is "OK" */
+		const char *status = body + strlen("MSNSLP/1.0 ");
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		g_return_val_if_fail(slpcall != NULL, NULL);
+
+		if (strncmp(status, "200 OK", 6))
+		{
+			/* It's not valid. Kill this off. */
+			char temp[32];
+			const char *c;
+
+			/* Eww */
+			if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) ||
+				(c = strchr(status, '\0')))
+			{
+				size_t offset =  c - status;
+				if (offset >= sizeof(temp))
+					offset = sizeof(temp) - 1;
+
+				strncpy(temp, status, offset);
+				temp[offset] = '\0';
+			}
+
+			gaim_debug_error("msn", "Received non-OK result: %s\n", temp);
+
+			slpcall->wasted = TRUE;
+
+			/* msn_slp_call_destroy(slpcall); */
+			return slpcall;
+		}
+
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		got_ok(slpcall, content_type, content);
+
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "BYE", strlen("BYE")))
+	{
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		if (slpcall != NULL)
+			slpcall->wasted = TRUE;
+
+		/* msn_slp_call_destroy(slpcall); */
+	}
+	else
+		slpcall = NULL;
+
+	return slpcall;
+}
+
+/**************************************************************************
+ * Msg Callbacks
+ **************************************************************************/
+
+void
+msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+
+	session = cmdproc->servconn->session;
+	slplink = msn_session_get_slplink(session, msg->remote_user);
+
+	if (slplink->swboard == NULL)
+	{
+		/* We will need this in order to change its flags. */
+		slplink->swboard = (MsnSwitchBoard *)cmdproc->data;
+		/* If swboard is NULL, something has probably gone wrong earlier on
+		 * I didn't want to do this, but MSN 7 is somehow causing us to crash
+		 * here, I couldn't reproduce it to debug more, and people are
+		 * reporting bugs. Hopefully this doesn't cause more crashes. Stu.
+		 */
+		if (slplink->swboard != NULL)
+			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+		else
+			gaim_debug_error("msn", "msn_p2p_msg, swboard is NULL, ouch!\n");
+	}
+
+	msn_slplink_process_msg(slplink, msg);
+}
+
+static void
+got_emoticon(MsnSlpCall *slpcall,
+			 const guchar *data, gsize size)
+{
+
+	GaimConversation *conv;
+	GaimConnection *gc;
+	const char *who;
+
+	gc = slpcall->slplink->session->account->gc;
+	who = slpcall->slplink->remote_user;
+
+	if ((conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY, who, gc->account))) {
+
+		/* FIXME: it would be better if we wrote the data as we received it
+		          instead of all at once, calling write multiple times and
+		          close once at the very end
+		*/
+		gaim_conv_custom_smiley_write(conv, slpcall->data_info, data, size);
+		gaim_conv_custom_smiley_close(conv, slpcall->data_info);
+	}
+#ifdef MSN_DEBUG_UD
+	gaim_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
+#endif
+}
+
+void
+msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnObject *obj;
+	char **tokens;
+	char *smile, *body_str;
+	const char *body, *who, *sha1c;
+	guint tok;
+	size_t body_len;
+
+	GaimConversation *conv;
+
+	session = cmdproc->servconn->session;
+
+	if (!gaim_account_get_bool(session->account, "custom_smileys", TRUE))
+		return;
+
+	body = msn_message_get_bin_data(msg, &body_len);
+	body_str = g_strndup(body, body_len);
+
+	/* MSN Messenger 7 may send more than one MSNObject in a single message...
+	 * Maybe 10 tokens is a reasonable max value. */
+	tokens = g_strsplit(body_str, "\t", 10);
+
+	g_free(body_str);
+
+	for (tok = 0; tok < 9; tok += 2) {
+		if (tokens[tok] == NULL || tokens[tok + 1] == NULL) {
+			break;
+		}
+
+		smile = tokens[tok];
+		obj = msn_object_new_from_string(gaim_url_decode(tokens[tok + 1]));
+
+		if (obj == NULL)
+			break;
+
+		who = msn_object_get_creator(obj);
+		sha1c = msn_object_get_sha1c(obj);
+
+		slplink = msn_session_get_slplink(session, who);
+
+		conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY, who,
+												   session->account);
+
+		/* If the conversation doesn't exist then this is a custom smiley
+		 * used in the first message in a MSN conversation: we need to create
+		 * the conversation now, otherwise the custom smiley won't be shown.
+		 * This happens because every GtkIMHtml has its own smiley tree: if
+		 * the conversation doesn't exist then we cannot associate the new
+		 * smiley with its GtkIMHtml widget. */
+		if (!conv) {
+			conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, session->account, who);
+		}
+
+		if (gaim_conv_custom_smiley_add(conv, smile, "sha1", sha1c, TRUE)) {
+			msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj);
+		}
+
+		msn_object_destroy(obj);
+		obj =   NULL;
+		who =   NULL;
+		sha1c = NULL;
+	}
+	g_strfreev(tokens);
+}
+
+static gboolean
+buddy_icon_cached(GaimConnection *gc, MsnObject *obj)
+{
+	GaimAccount *account;
+	GaimBuddy *buddy;
+	const char *old;
+	const char *new;
+
+	g_return_val_if_fail(obj != NULL, FALSE);
+
+	account = gaim_connection_get_account(gc);
+
+	buddy = gaim_find_buddy(account, msn_object_get_creator(obj));
+	if (buddy == NULL)
+		return FALSE;
+
+	old = gaim_blist_node_get_string((GaimBlistNode *)buddy, "icon_checksum");
+	new = msn_object_get_sha1c(obj);
+
+	if (new == NULL)
+		return FALSE;
+
+	/* If the old and new checksums are the same, and the file actually exists,
+	 * then return TRUE */
+	if (old != NULL && !strcmp(old, new) && (gaim_buddy_icons_find(account, gaim_buddy_get_name(buddy)) != NULL))
+		return TRUE;
+
+	return FALSE;
+}
+
+static void
+msn_release_buddy_icon_request(MsnUserList *userlist)
+{
+	MsnUser *user;
+
+	g_return_if_fail(userlist != NULL);
+
+#ifdef MSN_DEBUG_UD
+	gaim_debug_info("msn", "Releasing buddy icon request\n");
+#endif
+
+	if (userlist->buddy_icon_window > 0)
+	{
+		GQueue *queue;
+		GaimAccount *account;
+		const char *username;
+
+		queue = userlist->buddy_icon_requests;
+
+		if (g_queue_is_empty(userlist->buddy_icon_requests))
+			return;
+
+		user = g_queue_pop_head(queue);
+
+		account  = userlist->session->account;
+		username = user->passport;
+
+		userlist->buddy_icon_window--;
+		msn_request_user_display(user);
+
+#ifdef MSN_DEBUG_UD
+		gaim_debug_info("msn", "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
+						userlist->buddy_icon_window);
+#endif
+	}
+}
+
+/*
+ * Called on a timeout from end_user_display(). Frees a buddy icon window slow and dequeues the next
+ * buddy icon request if there is one.
+ */
+static gboolean
+msn_release_buddy_icon_request_timeout(gpointer data)
+{
+	MsnUserList *userlist = (MsnUserList *)data;
+	
+	/* Free one window slot */
+	userlist->buddy_icon_window++;	
+	
+	/* Clear the tag for our former request timer */
+	userlist->buddy_icon_request_timer = 0;
+	
+	msn_release_buddy_icon_request(userlist);
+	
+	return FALSE;
+}
+
+void
+msn_queue_buddy_icon_request(MsnUser *user)
+{
+	GaimAccount *account;
+	MsnObject *obj;
+	GQueue *queue;
+
+	g_return_if_fail(user != NULL);
+
+	account = user->userlist->session->account;
+
+	obj = msn_user_get_object(user);
+
+	if (obj == NULL)
+	{
+		/* It seems the user has not set a msnobject */
+		GSList *sl, *list;
+
+		list = gaim_find_buddies(account, user->passport);
+
+		for (sl = list; sl != NULL; sl = sl->next)
+		{
+			GaimBuddy *buddy = (GaimBuddy *)sl->data;
+			if (buddy->icon)
+				gaim_blist_node_remove_setting((GaimBlistNode*)buddy, "icon_checksum");
+		}
+		g_slist_free(list);
+
+		/* TODO: I think we need better buddy icon core functions. */
+		gaim_buddy_icons_set_for_user(account, user->passport, NULL, 0);
+
+		return;
+	}
+
+	if (!buddy_icon_cached(account->gc, obj))
+	{
+		MsnUserList *userlist;
+
+		userlist = user->userlist;
+		queue = userlist->buddy_icon_requests;
+
+#ifdef MSN_DEBUG_UD
+		gaim_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n",
+						user->passport, userlist->buddy_icon_window);
+#endif
+
+		g_queue_push_tail(queue, user);
+
+		if (userlist->buddy_icon_window > 0)
+			msn_release_buddy_icon_request(userlist);
+	}
+}
+
+static void
+got_user_display(MsnSlpCall *slpcall,
+				 const guchar *data, gsize size)
+{
+	MsnUserList *userlist;
+	const char *info;
+	GaimAccount *account;
+	GSList *sl, *list;
+
+	g_return_if_fail(slpcall != NULL);
+
+	info = slpcall->data_info;
+#ifdef MSN_DEBUG_UD
+	gaim_debug_info("msn", "Got User Display: %s\n", slpcall->slplink->remote_user);
+#endif
+
+	userlist = slpcall->slplink->session->userlist;
+	account = slpcall->slplink->session->account;
+
+	/* TODO: I think we need better buddy icon core functions. */
+	gaim_buddy_icons_set_for_user(account, slpcall->slplink->remote_user,
+								  (void *)data, size);
+
+	list = gaim_find_buddies(account, slpcall->slplink->remote_user);
+
+	for (sl = list; sl != NULL; sl = sl->next)
+	{
+		GaimBuddy *buddy = (GaimBuddy *)sl->data;
+		gaim_blist_node_set_string((GaimBlistNode*)buddy, "icon_checksum", info);
+	}
+	g_slist_free(list);
+
+#if 0
+	/* Free one window slot */
+	userlist->buddy_icon_window++;
+
+	gaim_debug_info("msn", "got_user_display(): buddy_icon_window++ yields =%d\n",
+					userlist->buddy_icon_window);
+
+	msn_release_buddy_icon_request(userlist);
+#endif
+}
+
+static void
+end_user_display(MsnSlpCall *slpcall, MsnSession *session)
+{
+	MsnUserList *userlist;
+
+	g_return_if_fail(session != NULL);
+
+#ifdef MSN_DEBUG_UD
+	gaim_debug_info("msn", "End User Display\n");
+#endif
+
+	userlist = session->userlist;
+
+	/* If the session is being destroyed we better stop doing anything. */
+	if (session->destroying)
+		return;
+
+	/* Delay before freeing a buddy icon window slot and requesting the next icon, if appropriate.
+	 * If we don't delay, we'll rapidly hit the MSN equivalent of AIM's rate limiting; the server will
+	 * send us an error 800 like so:
+	 *
+	 * C: NS 000: XFR 21 SB
+	 * S: NS 000: 800 21
+	 */
+	if (userlist->buddy_icon_request_timer) {
+		/* Free the window slot used by this previous request */
+		userlist->buddy_icon_window++;
+
+		/* Clear our pending timeout */
+		gaim_timeout_remove(userlist->buddy_icon_request_timer);
+	}
+
+	/* Wait BUDDY_ICON_DELAY ms before freeing our window slot and requesting the next icon. */
+	userlist->buddy_icon_request_timer = gaim_timeout_add(BUDDY_ICON_DELAY, 
+														  msn_release_buddy_icon_request_timeout, userlist);
+}
+
+void
+msn_request_user_display(MsnUser *user)
+{
+	GaimAccount *account;
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnObject *obj;
+	const char *info;
+
+	session = user->userlist->session;
+	account = session->account;
+
+	slplink = msn_session_get_slplink(session, user->passport);
+
+	obj = msn_user_get_object(user);
+
+	info = msn_object_get_sha1c(obj);
+
+	if (g_ascii_strcasecmp(user->passport,
+						   gaim_account_get_username(account)))
+	{
+		msn_slplink_request_object(slplink, info, got_user_display,
+								   end_user_display, obj);
+	}
+	else
+	{
+		MsnObject *my_obj = NULL;
+		gchar *data = NULL;
+		gsize len = 0;
+		GSList *sl, *list;
+
+#ifdef MSN_DEBUG_UD
+		gaim_debug_info("msn", "Requesting our own user display\n");
+#endif
+
+		my_obj = msn_user_get_object(session->user);
+
+		if (my_obj != NULL)
+		{
+			const char *filename = msn_object_get_real_location(my_obj);
+
+			if (filename != NULL)
+				g_file_get_contents(filename, &data, &len, NULL);
+		}
+
+		/* TODO: I think we need better buddy icon core functions. */
+		gaim_buddy_icons_set_for_user(account, user->passport, (void *)data, len);
+		g_free(data);
+
+		list = gaim_find_buddies(account, user->passport);
+
+		for (sl = list; sl != NULL; sl = sl->next)
+		{
+			GaimBuddy *buddy = (GaimBuddy *)sl->data;
+			gaim_blist_node_set_string((GaimBlistNode*)buddy, "icon_checksum", info);
+		}
+		g_slist_free(list);
+
+		/* Free one window slot */
+		session->userlist->buddy_icon_window++;
+
+#ifdef MSN_DEBUG_UD
+		gaim_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n",
+						session->userlist->buddy_icon_window);
+#endif
+
+		msn_release_buddy_icon_request(session->userlist);
+	}
+}

mercurial