diff libpurple/protocols/msn/slp.c @ 31292:47b6eda87723

propagate from branch 'im.pidgin.pidgin' (head 07d0765c444a097af45c2650f54323afb900a07b) to branch 'im.pidgin.soc.2010.msn-tlc' (head f3998422a4724ab424e4e2328f58fc0504856557)
author masca@cpw.pidgin.im
date Mon, 19 Jul 2010 21:11:32 +0000
parents 7b1b7a4e0bb4
children 23be655cc688
line wrap: on
line diff
--- a/libpurple/protocols/msn/slp.c	Mon Jul 19 18:25:47 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Mon Jul 19 21:11:32 2010 +0000
@@ -21,7 +21,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
 #include "slp.h"
 #include "slpcall.h"
 #include "slpmsg.h"
@@ -29,177 +32,14 @@
 
 #include "object.h"
 #include "user.h"
-#include "switchboard.h"
+#include "sbconn.h"
 #include "directconn.h"
-
-#include "smiley.h"
+#include "p2p.h"
+#include "xfer.h"
 
 /* seconds to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20
 
-static void 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(PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	/* MsnSlpLink *slplink; */
-	char *content;
-
-	purple_debug_info("msn", "xfer_init\n");
-
-	slpcall = xfer->data;
-
-	/* Send Ok */
-	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-							  slpcall->session_id);
-
-	msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
-			content);
-
-	g_free(content);
-	msn_slplink_send_queued_slpmsgs(slpcall->slplink);
-}
-
-void
-msn_xfer_cancel(PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	char *content;
-
-	g_return_if_fail(xfer != NULL);
-	g_return_if_fail(xfer->data != NULL);
-
-	slpcall = xfer->data;
-
-	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
-	{
-		if (slpcall->started)
-		{
-			msn_slpcall_close(slpcall);
-		}
-		else
-		{
-			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-									slpcall->session_id);
-
-			msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
-						content);
-
-			g_free(content);
-			msn_slplink_send_queued_slpmsgs(slpcall->slplink);
-
-			if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
-				slpcall->wasted = TRUE;
-			else
-				msn_slpcall_destroy(slpcall);
-		}
-	}
-}
-
-gssize
-msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-
-	g_return_val_if_fail(xfer != NULL, -1);
-	g_return_val_if_fail(data != NULL, -1);
-	g_return_val_if_fail(len > 0, -1);
-
-	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1);
-
-	slpcall = xfer->data;
-	/* Not sure I trust it'll be there */
-	g_return_val_if_fail(slpcall != NULL, -1);
-
-	g_return_val_if_fail(slpcall->xfer_msg != NULL, -1);
-
-	slpcall->u.outgoing.len = len;
-	slpcall->u.outgoing.data = data;
-	msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg);
-	msn_message_unref(slpcall->xfer_msg->msg);
-	return MIN(1202, len);
-}
-
-gssize
-msn_xfer_read(guchar **data, PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	gsize len;
-
-	g_return_val_if_fail(xfer != NULL, -1);
-	g_return_val_if_fail(data != NULL, -1);
-
-	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1);
-
-	slpcall = xfer->data;
-	/* Not sure I trust it'll be there */
-	g_return_val_if_fail(slpcall != NULL, -1);
-
-	/* Just pass up the whole GByteArray. We'll make another. */
-	*data = slpcall->u.incoming_data->data;
-	len = slpcall->u.incoming_data->len;
-
-	g_byte_array_free(slpcall->u.incoming_data, FALSE);
-	slpcall->u.incoming_data = g_byte_array_new();
-
-	return len;
-}
-
-void
-msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
-{
-	if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) &&
-		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) &&
-		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL))
-	{
-		purple_xfer_cancel_remote(slpcall->xfer);
-	}
-}
-
-void
-msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body,
-					  gsize size)
-{
-	PurpleXfer *xfer = slpcall->xfer;
-
-	purple_xfer_set_completed(xfer, TRUE);
-	purple_xfer_end(xfer);
-}
 
 /**************************************************************************
  * SLP Control
@@ -245,1041 +85,10 @@
 	msn_slplink_queue_slpmsg(slplink, slpmsg);
 }
 
-/* XXX: this could be improved if we tracked custom smileys
- * per-protocol, per-account, per-session or (ideally) per-conversation
- */
-static PurpleStoredImage *
-find_valid_emoticon(PurpleAccount *account, const char *path)
-{
-	GList *smileys;
-
-	if (!purple_account_get_bool(account, "custom_smileys", TRUE))
-		return NULL;
-
-	smileys = purple_smileys_get_all();
-
-	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
-		PurpleSmiley *smiley;
-		PurpleStoredImage *img;
-
-		smiley = smileys->data;
-		img = purple_smiley_get_stored_image(smiley);
-
-		if (purple_strequal(path, purple_imgstore_get_filename(img))) {
-			g_list_free(smileys);
-			return img;
-		}
-
-		purple_imgstore_unref(img);
-	}
-
-	purple_debug_error("msn", "Received illegal request for file %s\n", path);
-	return NULL;
-}
-
-static char *
-parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype)
-{
-	char *nonce;
-
-	*ntype = DC_NONCE_UNKNOWN;
-
-	nonce = get_token(content, "Hashed-Nonce: {", "}\r\n");
-	if (nonce) {
-		*ntype = DC_NONCE_SHA1;
-	} else {
-		guint32 n1, n6;
-		guint16 n2, n3, n4, n5;
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (nonce
-		 && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%04hx%08x",
-		           &n1, &n2, &n3, &n4, &n5, &n6) == 6) {
-			*ntype = DC_NONCE_PLAIN;
-			g_free(nonce);
-			nonce = g_malloc(16);
-			*(guint32 *)(nonce +  0) = GUINT32_TO_LE(n1);
-			*(guint16 *)(nonce +  4) = GUINT16_TO_LE(n2);
-			*(guint16 *)(nonce +  6) = GUINT16_TO_LE(n3);
-			*(guint16 *)(nonce +  8) = GUINT16_TO_BE(n4);
-			*(guint16 *)(nonce + 10) = GUINT16_TO_BE(n5);
-			*(guint32 *)(nonce + 12) = GUINT32_TO_BE(n6);
-		} else {
-			/* Invalid nonce, so ignore request */
-			g_free(nonce);
-			nonce = NULL;
-		}
-	}
-
-	return nonce;
-}
-
-static void
-msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
-{
-	/* A direct connection negotiation response */
-	char *bridge;
-	char *nonce;
-	char *listening;
-	MsnDirectConn *dc = slpcall->slplink->dc;
-	MsnDirectConnNonceType ntype;
-
-	purple_debug_info("msn", "process_transresp\n");
-
-	/* Direct connections are disabled. */
-	if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE))
-		return;
-
-	g_return_if_fail(dc != NULL);
-	g_return_if_fail(dc->state == DC_STATE_CLOSED);
-
-	bridge = get_token(content, "Bridge: ", "\r\n");
-	nonce = parse_dc_nonce(content, &ntype);
-	listening = get_token(content, "Listening: ", "\r\n");
-	if (listening && bridge && !strcmp(bridge, "TCPv1")) {
-		/* Ok, the client supports direct TCP connection */
-
-		/* We always need this. */
-		if (ntype == DC_NONCE_SHA1) {
-			strncpy(dc->remote_nonce, nonce, 36);
-			dc->remote_nonce[36] = '\0';
-		}
-
-		if (!strcasecmp(listening, "false")) {
-			if (dc->listen_data != NULL) {
-				/*
-				 * We'll listen for incoming connections but
-				 * the listening socket isn't ready yet so we cannot
-				 * send the INVITE packet now. Put the slpcall into waiting mode
-				 * and let the callback send the invite.
-				 */
-				slpcall->wait_for_socket = TRUE;
-
-			} else if (dc->listenfd != -1) {
-				/* The listening socket is ready. Send the INVITE here. */
-				msn_dc_send_invite(dc);
-
-			} else {
-				/* We weren't able to create a listener either. Use SB. */
-				msn_dc_fallback_to_sb(dc);
-			}
-
-		} else {
-			/*
-			 * We should connect to the client so parse
-			 * IP/port from response.
-			 */
-			char *ip, *port_str;
-			int port = 0;
-
-			if (ntype == DC_NONCE_PLAIN) {
-				/* Only needed for listening side. */
-				memcpy(dc->nonce, nonce, 16);
-			}
-
-			/* Cancel any listen attempts because we don't need them. */
-			if (dc->listenfd_handle != 0) {
-				purple_input_remove(dc->listenfd_handle);
-				dc->listenfd_handle = 0;
-			}
-			if (dc->connect_timeout_handle != 0) {
-				purple_timeout_remove(dc->connect_timeout_handle);
-				dc->connect_timeout_handle = 0;
-			}
-			if (dc->listenfd != -1) {
-				purple_network_remove_port_mapping(dc->listenfd);
-				close(dc->listenfd);
-				dc->listenfd = -1;
-			}
-			if (dc->listen_data != NULL) {
-				purple_network_listen_cancel(dc->listen_data);
-				dc->listen_data = NULL;
-			}
-
-			/* Save external IP/port for later use. We'll try local connection first. */
-			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
-			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
-			if (port_str) {
-				dc->ext_port = atoi(port_str);
-				g_free(port_str);
-			}
-
-			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
-			if (port_str) {
-				port = atoi(port_str);
-				g_free(port_str);
-			}
-
-			if (ip && port) {
-				/* Try internal address first */
-				dc->connect_data = purple_proxy_connect(
-					NULL,
-					slpcall->slplink->session->account,
-					ip,
-					port,
-					msn_dc_connected_to_peer_cb,
-					dc
-				);
-
-				if (dc->connect_data) {
-					/* Add connect timeout handle */
-					dc->connect_timeout_handle = purple_timeout_add_seconds(
-						DC_OUTGOING_TIMEOUT,
-						msn_dc_outgoing_connection_timeout_cb,
-						dc
-					);
-				} else {
-					/*
-					 * Connection failed
-					 * Try external IP/port (if specified)
-					 */
-					msn_dc_outgoing_connection_timeout_cb(dc);
-				}
-
-			} else {
-				/*
-				 * Omitted or invalid internal IP address / port
-				 * Try external IP/port (if specified)
-				 */
-				msn_dc_outgoing_connection_timeout_cb(dc);
-			}
-
-			g_free(ip);
-		}
-
-	} else {
-		/*
-		 * Invalid direct connect invitation or
-		 * TCP connection is not supported
-		 */
-	}
-
-	g_free(listening);
-	g_free(nonce);
-	g_free(bridge);
-
-	return;
-}
-
-static void
-got_sessionreq(MsnSlpCall *slpcall, const char *branch,
-			   const char *euf_guid, const char *context)
-{
-	gboolean accepted = FALSE;
-
-	if (!strcmp(euf_guid, MSN_OBJ_GUID))
-	{
-		/* Emoticon or UserDisplay */
-		char *content;
-		gsize len;
-		MsnSlpLink *slplink;
-		MsnSlpMessage *slpmsg;
-		MsnObject *obj;
-		char *msnobj_data;
-		PurpleStoredImage *img = NULL;
-		int type;
-
-		/* Send Ok */
-		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-								  slpcall->session_id);
-
-		msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
-				content);
-
-		g_free(content);
-
-		slplink = slpcall->slplink;
-
-		msnobj_data = (char *)purple_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_EMOTICON) {
-			img = find_valid_emoticon(slplink->session->account, obj->location);
-		} else if (type == MSN_OBJECT_USERTILE) {
-			img = msn_object_get_image(obj);
-			if (img)
-				purple_imgstore_ref(img);
-		}
-		msn_object_destroy(obj);
-
-		if (img != NULL) {
-			/* DATA PREP */
-			slpmsg = msn_slpmsg_new(slplink);
-			slpmsg->slpcall = slpcall;
-			slpmsg->session_id = slpcall->session_id;
-			msn_slpmsg_set_body(slpmsg, NULL, 4);
-			slpmsg->info = "SLP DATA PREP";
-			msn_slplink_queue_slpmsg(slplink, slpmsg);
-
-			/* DATA */
-			slpmsg = msn_slpmsg_new(slplink);
-			slpmsg->slpcall = slpcall;
-			slpmsg->flags = 0x20;
-			slpmsg->info = "SLP DATA";
-			msn_slpmsg_set_image(slpmsg, img);
-			msn_slplink_queue_slpmsg(slplink, slpmsg);
-			purple_imgstore_unref(img);
-
-			accepted = TRUE;
-
-		} else {
-			purple_debug_error("msn", "Wrong object.\n");
-		}
-	}
-
-	else if (!strcmp(euf_guid, MSN_FT_GUID))
-	{
-		/* File Transfer */
-		PurpleAccount *account;
-		PurpleXfer *xfer;
-		MsnFileContext *header;
-		gsize bin_len;
-		guint32 file_size;
-		char *file_name;
-
-		account = slpcall->slplink->session->account;
-
-		slpcall->end_cb = msn_xfer_end_cb;
-		slpcall->branch = g_strdup(branch);
-
-		slpcall->pending = TRUE;
-
-		xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE,
-							 slpcall->slplink->remote_user);
-
-		header = (MsnFileContext *)purple_base64_decode(context, &bin_len);
-		if (bin_len >= sizeof(MsnFileContext) - 1 &&
-			(header->version == 2 ||
-			 (header->version == 3 && header->length == sizeof(MsnFileContext) + 63))) {
-			file_size = GUINT64_FROM_LE(header->file_size);
-
-			file_name = g_convert((const gchar *)&header->file_name,
-			                      MAX_FILE_NAME_LEN * 2,
-			                      "UTF-8", "UTF-16LE",
-			                      NULL, NULL, NULL);
-
-			purple_xfer_set_filename(xfer, file_name ? file_name : "");
-			g_free(file_name);
-			purple_xfer_set_size(xfer, file_size);
-			purple_xfer_set_init_fnc(xfer, msn_xfer_init);
-			purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
-			purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
-			purple_xfer_set_read_fnc(xfer, msn_xfer_read);
-			purple_xfer_set_write_fnc(xfer, msn_xfer_write);
-
-			slpcall->u.incoming_data = g_byte_array_new();
-
-			slpcall->xfer = xfer;
-			purple_xfer_ref(slpcall->xfer);
-
-			xfer->data = slpcall;
-
-			if (header->type == 0 && bin_len >= sizeof(MsnFileContext)) {
-				purple_xfer_set_thumbnail(xfer, &header->preview,
-				                          bin_len - sizeof(MsnFileContext),
-				    					  "image/png");
-			}
-
-			purple_xfer_request(xfer);
-		}
-		g_free(header);
-
-		accepted = TRUE;
-
-	} else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) {
-		purple_debug_info("msn", "Cam request.\n");
-		if (slpcall && slpcall->slplink &&
-				slpcall->slplink->session) {
-			PurpleConversation *conv;
-			gchar *from = slpcall->slplink->remote_user;
-			conv = purple_find_conversation_with_account(
-					PURPLE_CONV_TYPE_IM, from,
-					slpcall->slplink->session->account);
-			if (conv) {
-				char *buf;
-				buf = g_strdup_printf(
-						_("%s requests to view your "
-						"webcam, but this request is "
-						"not yet supported."), from);
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
-			}
-		}
-
-	} else if (!strcmp(euf_guid, MSN_CAM_GUID)) {
-		purple_debug_info("msn", "Cam invite.\n");
-		if (slpcall && slpcall->slplink &&
-				slpcall->slplink->session) {
-			PurpleConversation *conv;
-			gchar *from = slpcall->slplink->remote_user;
-			conv = purple_find_conversation_with_account(
-					PURPLE_CONV_TYPE_IM, from,
-					slpcall->slplink->session->account);
-			if (conv) {
-				char *buf;
-				buf = g_strdup_printf(
-						_("%s invited you to view his/her webcam, but "
-						"this is not yet supported."), from);
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
-			}
-		}
-
-	} else
-		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
-
-	if (!accepted) {
-		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-		                                slpcall->session_id);
-		msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
-		g_free(content);
-	}
-}
-
-void
-send_bye(MsnSlpCall *slpcall, const char *type)
-{
-	MsnSlpLink *slplink;
-	PurpleAccount *account;
-	MsnSlpMessage *slpmsg;
-	char *header;
-
-	slplink = slpcall->slplink;
-
-	g_return_if_fail(slplink != NULL);
-
-	account = slplink->session->account;
-
-	header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0",
-							 purple_account_get_username(account));
-
-	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header,
-								"A0D624A6-6C0C-4283-A9E0-BC97B4B46D32",
-								type,
-								"\r\n");
-	g_free(header);
-
-	slpmsg->info = "SLP BYE";
-	slpmsg->text_body = TRUE;
-
-	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 negotiation request */
-		char *bridges;
-		char *nonce;
-		MsnDirectConnNonceType ntype;
-
-		purple_debug_info("msn", "got_invite: transreqbody received\n");
-
-		/* Direct connections may be disabled. */
-		if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) {
-			msn_slp_send_ok(slpcall, branch,
-				"application/x-msnmsgr-transrespbody",
-				"Bridge: TCPv1\r\n"
-				"Listening: false\r\n"
-				"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-				"\r\n");
-			msn_slpcall_session_init(slpcall);
-
-			return;
-		}
-
-		/* Don't do anything if we already have a direct connection */
-		if (slplink->dc != NULL)
-			return;
-
-		bridges = get_token(content, "Bridges: ", "\r\n");
-		nonce = parse_dc_nonce(content, &ntype);
-		if (bridges && strstr(bridges, "TCPv1") != NULL) {
-			/*
-			 * Ok, the client supports direct TCP connection
-			 * Try to create a listening port
-			 */
-			MsnDirectConn *dc;
-
-			dc = msn_dc_new(slpcall);
-			if (ntype == DC_NONCE_PLAIN) {
-				/* There is only one nonce for plain auth. */
-				dc->nonce_type = ntype;
-				memcpy(dc->nonce, nonce, 16);
-			} else if (ntype == DC_NONCE_SHA1) {
-				/* Each side has a nonce in SHA1 auth. */
-				dc->nonce_type = ntype;
-				strncpy(dc->remote_nonce, nonce, 36);
-				dc->remote_nonce[36] = '\0';
-			}
-
-			dc->listen_data = purple_network_listen_range(
-				0, 0,
-				SOCK_STREAM,
-				msn_dc_listen_socket_created_cb,
-				dc
-			);
-
-			if (dc->listen_data == NULL) {
-				/* Listen socket creation failed */
-
-				purple_debug_info("msn", "got_invite: listening failed\n");
-
-				if (dc->nonce_type != DC_NONCE_PLAIN)
-					msn_slp_send_ok(slpcall, branch,
-						"application/x-msnmsgr-transrespbody",
-						"Bridge: TCPv1\r\n"
-						"Listening: false\r\n"
-						"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-						"\r\n");
-				else
-					msn_slp_send_ok(slpcall, branch,
-						"application/x-msnmsgr-transrespbody",
-						"Bridge: TCPv1\r\n"
-						"Listening: false\r\n"
-						"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-						"\r\n");
-
-			} else {
-				/*
-				 * Listen socket created successfully.
-				 * Don't send anything here because we don't know the parameters
-				 * of the created socket yet. msn_dc_send_ok will be called from
-				 * the callback function: dc_listen_socket_created_cb
-				 */
-				purple_debug_info("msn", "got_invite: listening socket created\n");
-
-				dc->send_connection_info_msg_cb = msn_dc_send_ok;
-				slpcall->wait_for_socket = TRUE;
-			}
-
-		} else {
-			/*
-			 * Invalid direct connect invitation or
-			 * TCP connection is not supported.
-			 */
-		}
-
-		g_free(nonce);
-		g_free(bridges);
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
-	{
-		/* A direct connection negotiation response */
-		msn_slp_process_transresp(slpcall, content);
-	}
-}
-
-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"))
-	{
-		char *content;
-		char *header;
-		char *nonce = NULL;
-		MsnSession *session = slpcall->slplink->session;
-		MsnSlpMessage *msg;
-		MsnDirectConn *dc;
-		MsnUser *user;
-
-		if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) {
-			/* Don't attempt a direct connection if disabled. */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		if (slpcall->slplink->dc != NULL) {
-			/* If we already have an established direct connection
-			 * then just start the transfer.
-			 */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		user = msn_userlist_find_user(session->userlist,
-		                              slpcall->slplink->remote_user);
-		if (!user || !(user->clientid & 0xF0000000))	{
-			/* Just start a normal SB transfer. */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		/* Try direct file transfer by sending a second INVITE */
-		dc = msn_dc_new(slpcall);
-		slpcall->branch = rand_guid();
-
-		dc->listen_data = purple_network_listen_range(
-			0, 0,
-			SOCK_STREAM,
-			msn_dc_listen_socket_created_cb,
-			dc
-		);
-
-		header = g_strdup_printf(
-			"INVITE MSNMSGR:%s MSNSLP/1.0",
-			slpcall->slplink->remote_user
-		);
-
-		if (dc->nonce_type == DC_NONCE_SHA1)
-			nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash);
-
-		if (dc->listen_data == NULL) {
-			/* Listen socket creation failed */
-			purple_debug_info("msn", "got_ok: listening failed\n");
-
-			content = g_strdup_printf(
-				"Bridges: TCPv1\r\n"
-				"NetID: %u\r\n"
-				"Conn-Type: IP-Restrict-NAT\r\n"
-				"UPnPNat: false\r\n"
-				"ICF: false\r\n"
-				"%s"
-				"\r\n",
-
-				rand() % G_MAXUINT32,
-				nonce ? nonce : ""
-			);
-
-		} else {
-			/* Listen socket created successfully. */
-			purple_debug_info("msn", "got_ok: listening socket created\n");
-
-			content = g_strdup_printf(
-				"Bridges: TCPv1\r\n"
-				"NetID: 0\r\n"
-				"Conn-Type: Direct-Connect\r\n"
-				"UPnPNat: false\r\n"
-				"ICF: false\r\n"
-				"%s"
-				"\r\n",
-
-				nonce ? nonce : ""
-			);
-		}
-
-		msg = msn_slpmsg_sip_new(
-			slpcall,
-			0,
-			header,
-			slpcall->branch,
-			"application/x-msnmsgr-transreqbody",
-			content
-		);
-		msg->info = "DC INVITE";
-		msg->text_body = TRUE;
-		g_free(nonce);
-		g_free(header);
-		g_free(content);
-
-		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
-	{
-		/* Do we get this? */
-		purple_debug_info("msn", "OK with transreqbody\n");
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
-	{
-		msn_slp_process_transresp(slpcall, content);
-	}
-}
-
-static void
-got_error(MsnSlpCall *slpcall,
-          const char *error, const char *type, const char *content)
-{
-	/* It's not valid. Kill this off. */
-	purple_debug_error("msn", "Received non-OK result: %s\n",
-	                   error ? error : "Unknown");
-
-	if (type && !strcmp(type, "application/x-msnmsgr-transreqbody")) {
-		MsnDirectConn *dc = slpcall->slplink->dc;
-		if (dc) {
-			msn_dc_fallback_to_sb(dc);
-			return;
-		}
-	}
-
-	slpcall->wasted = TRUE;
-}
-
-MsnSlpCall *
-msn_slp_sip_recv(MsnSlpLink *slplink, const char *body)
-{
-	MsnSlpCall *slpcall;
-
-	if (body == NULL)
-	{
-		purple_debug_warning("msn", "received bogus message\n");
-		return NULL;
-	}
-
-	if (!strncmp(body, "INVITE", strlen("INVITE")))
-	{
-		char *branch;
-		char *call_id;
-		char *content;
-		char *content_type;
-
-		/* From: <msnmsgr:buddy@hotmail.com> */
-#if 0
-		slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n");
-#endif
-
-		branch = get_token(body, ";branch={", "}");
-
-		call_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);
-
-		slpcall = NULL;
-		if (branch && call_id)
-		{
-			slpcall = msn_slplink_find_slp_call(slplink, call_id);
-			if (slpcall)
-			{
-				g_free(slpcall->branch);
-				slpcall->branch = g_strdup(branch);
-				got_invite(slpcall, branch, content_type, content);
-			}
-			else if (content_type && content)
-			{
-				slpcall = msn_slpcall_new(slplink);
-				slpcall->id = g_strdup(call_id);
-				got_invite(slpcall, branch, content_type, content);
-			}
-		}
-
-		g_free(call_id);
-		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);
-
-		content_type = get_token(body, "Content-Type: ", "\r\n");
-
-		content = get_token(body, "\r\n\r\n", NULL);
-
-		if (strncmp(status, "200 OK", 6))
-		{
-			char *error = NULL;
-			const char *c;
-
-			/* Eww */
-			if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) ||
-				(c = strchr(status, '\0')))
-			{
-				size_t len = c - status;
-				error = g_strndup(status, len);
-			}
-
-			got_error(slpcall, error, content_type, content);
-			g_free(error);
-
-		} else {
-			/* Everything's just dandy */
-			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_slpcall_destroy(slpcall); */
-	}
-	else
-		slpcall = NULL;
-
-	return slpcall;
-}
-
 /**************************************************************************
  * Msg Callbacks
  **************************************************************************/
 
-void
-msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	MsnSession *session;
-	MsnSlpLink *slplink;
-	const char *data;
-	gsize len;
-
-	session = cmdproc->servconn->session;
-	slplink = msn_session_get_slplink(session, msg->remote_user);
-
-	if (slplink->swboard == NULL)
-	{
-		/*
-		 * We will need swboard in order to change its flags.  If its
-		 * 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 (cmdproc->data == NULL)
-			g_warning("msn_p2p_msg cmdproc->data was NULL\n");
-		else {
-			slplink->swboard = (MsnSwitchBoard *)cmdproc->data;
-			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
-		}
-	}
-
-	data = msn_message_get_bin_data(msg, &len);
-
-	msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len);
-}
-
-static void
-got_emoticon(MsnSlpCall *slpcall,
-			 const guchar *data, gsize size)
-{
-	PurpleConversation *conv;
-	MsnSwitchBoard *swboard;
-
-	swboard = slpcall->slplink->swboard;
-	conv = swboard->conv;
-
-	if (conv) {
-		/* 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
-		 */
-		purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size);
-		purple_conv_custom_smiley_close(conv, slpcall->data_info );
-	}
-	if (purple_debug_is_verbose())
-		purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
-}
-
-void
-msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	MsnSession *session;
-	MsnSlpLink *slplink;
-	MsnSwitchBoard *swboard;
-	MsnObject *obj;
-	char **tokens;
-	char *smile, *body_str;
-	const char *body, *who, *sha1;
-	guint tok;
-	size_t body_len;
-
-	PurpleConversation *conv;
-
-	session = cmdproc->servconn->session;
-
-	if (!purple_account_get_bool(session->account, "custom_smileys", TRUE))
-		return;
-
-	swboard = cmdproc->data;
-	conv = swboard->conv;
-
-	body = msn_message_get_bin_data(msg, &body_len);
-	if (!body || !body_len)
-		return;
-	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(purple_url_decode(tokens[tok + 1]));
-
-		if (obj == NULL)
-			break;
-
-		who = msn_object_get_creator(obj);
-		sha1 = msn_object_get_sha1(obj);
-
-		slplink = msn_session_get_slplink(session, who);
-		if (slplink->swboard != swboard) {
-			if (slplink->swboard != NULL)
-				/*
-				 * Apparently we're using a different switchboard now or
-				 * something?  I don't know if this is normal, but it
-				 * definitely happens.  So make sure the old switchboard
-				 * doesn't still have a reference to us.
-				 */
-				slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink);
-			slplink->swboard = swboard;
-			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
-		}
-
-		/* 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 = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who);
-		}
-
-		if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) {
-			msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj);
-		}
-
-		msn_object_destroy(obj);
-		obj =   NULL;
-		who =   NULL;
-		sha1 = NULL;
-	}
-	g_strfreev(tokens);
-}
-
-static gboolean
-buddy_icon_cached(PurpleConnection *gc, MsnObject *obj)
-{
-	PurpleAccount *account;
-	PurpleBuddy *buddy;
-	const char *old;
-	const char *new;
-
-	g_return_val_if_fail(obj != NULL, FALSE);
-
-	account = purple_connection_get_account(gc);
-
-	buddy = purple_find_buddy(account, msn_object_get_creator(obj));
-	if (buddy == NULL)
-		return FALSE;
-
-	old = purple_buddy_icons_get_checksum_for_user(buddy);
-	new = msn_object_get_sha1(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))
-		return TRUE;
-
-	return FALSE;
-}
-
-static void
-msn_release_buddy_icon_request(MsnUserList *userlist)
-{
-	MsnUser *user;
-
-	g_return_if_fail(userlist != NULL);
-
-	if (purple_debug_is_verbose())
-		purple_debug_info("msn", "Releasing buddy icon request\n");
-
-	if (userlist->buddy_icon_window > 0)
-	{
-		GQueue *queue;
-		PurpleAccount *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--;
-		request_user_display(user);
-
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn",
-			                  "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
-			                  userlist->buddy_icon_window);
-	}
-}
-
 /*
  * 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.
@@ -1300,43 +109,6 @@
 	return FALSE;
 }
 
-void
-msn_queue_buddy_icon_request(MsnUser *user)
-{
-	PurpleAccount *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)
-	{
-		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL);
-		return;
-	}
-
-	if (!buddy_icon_cached(account->gc, obj))
-	{
-		MsnUserList *userlist;
-
-		userlist = user->userlist;
-		queue = userlist->buddy_icon_requests;
-
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n",
-			                  user->passport, userlist->buddy_icon_window);
-
-		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)
@@ -1405,7 +177,43 @@
 }
 
 static void
-request_user_display(MsnUser *user)
+request_own_user_display(MsnUser *user)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnObject *my_obj = NULL;
+	gconstpointer data = NULL;
+	const char *info;
+	size_t len = 0;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "Requesting our own user display\n");
+
+	session = user->userlist->session;
+	account = session->account;
+	my_obj = msn_user_get_object(user);
+
+	if (my_obj != NULL) {
+		PurpleStoredImage *img = msn_object_get_image(my_obj);
+		data = purple_imgstore_get_data(img);
+		len = purple_imgstore_get_size(img);
+		info = msn_object_get_sha1(my_obj);
+	}
+
+	purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info);
+
+	/* Free one window slot */
+	session->userlist->buddy_icon_window++;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n",
+				session->userlist->buddy_icon_window);
+
+	msn_release_buddy_icon_request(session->userlist);
+}
+
+void
+msn_request_user_display(MsnUser *user)
 {
 	PurpleAccount *account;
 	MsnSession *session;
@@ -1423,38 +231,138 @@
 	info = msn_object_get_sha1(obj);
 
 	if (g_ascii_strcasecmp(user->passport,
-						   purple_account_get_username(account)))
-	{
+				purple_account_get_username(account)))
 		msn_slplink_request_object(slplink, info, got_user_display,
-								   end_user_display, obj);
-	}
+				end_user_display, obj);
 	else
-	{
-		MsnObject *my_obj = NULL;
-		gconstpointer data = NULL;
-		size_t len = 0;
+		request_own_user_display(user);
+}
+
+static void
+send_file_cb(MsnSlpCall *slpcall)
+{
+	MsnSlpMessage *slpmsg;
+	PurpleXfer *xfer;
+
+	xfer = (PurpleXfer *)slpcall->xfer;
+	if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED)
+		return;
+
+	purple_xfer_ref(xfer);
+	purple_xfer_start(xfer, -1, NULL, 0);
+	if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) {
+		purple_xfer_unref(xfer);
+		return;
+	}
+	purple_xfer_unref(xfer);
+
+	slpmsg = msn_slpmsg_file_new(slpcall, purple_xfer_get_size(xfer));
+	msn_slpmsg_set_slplink(slpmsg, slpcall->slplink);
+
+	msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
+}
 
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "Requesting our own user display\n");
+static gchar *
+gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path)
+{
+	gsize size = 0;
+	MsnFileContext *header;
+	gchar *u8 = NULL;
+	gchar *ret;
+	gunichar2 *uni = NULL;
+	glong currentChar = 0;
+	glong len = 0;
+	const char *preview;
+	gsize preview_len;
+
+	size = purple_xfer_get_size(xfer);
+
+	purple_xfer_prepare_thumbnail(xfer, "png");
 
-		my_obj = msn_user_get_object(session->user);
+	if (!file_name) {
+		gchar *basename = g_path_get_basename(file_path);
+		u8 = purple_utf8_try_convert(basename);
+		g_free(basename);
+		file_name = u8;
+	}
+
+	uni = g_utf8_to_utf16(file_name, -1, NULL, &len, NULL);
+
+	if (u8) {
+		g_free(u8);
+		file_name = NULL;
+		u8 = NULL;
+	}
+
+	preview = purple_xfer_get_thumbnail(xfer, &preview_len);
+	header = g_malloc(sizeof(MsnFileContext) + preview_len);
 
-		if (my_obj != NULL)
-		{
-			PurpleStoredImage *img = msn_object_get_image(my_obj);
-			data = purple_imgstore_get_data(img);
-			len = purple_imgstore_get_size(img);
-		}
+	header->length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1);
+	header->version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */
+	header->file_size = GUINT64_TO_LE(size);
+	if (preview)
+		header->type = GUINT32_TO_LE(0);
+	else
+		header->type = GUINT32_TO_LE(1);
 
-		purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info);
+	len = MIN(len, MAX_FILE_NAME_LEN);
+	for (currentChar = 0; currentChar < len; currentChar++) {
+		header->file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]);
+	}
+	memset(&header->file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2);
+
+	memset(&header->unknown1, 0, sizeof(header->unknown1));
+	header->unknown2 = GUINT32_TO_LE(0xffffffff);
+	if (preview) {
+		memcpy(&header->preview, preview, preview_len);
+	}
+	header->preview[preview_len] = '\0';
+
+	g_free(uni);
+	ret = purple_base64_encode((const guchar *)header, sizeof(MsnFileContext) + preview_len);
+	g_free(header);
+	return ret;
+}
 
-		/* Free one window slot */
-		session->userlist->buddy_icon_window++;
+void
+msn_request_ft(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	MsnSlpLink *slplink;
+	char *context;
+	const char *fn;
+	const char *fp;
+
+	fn = purple_xfer_get_filename(xfer);
+	fp = purple_xfer_get_local_filename(xfer);
+
+	slplink = xfer->data;
+
+	g_return_if_fail(slplink != NULL);
+	g_return_if_fail(fp != NULL);
+
+	slpcall = msn_slpcall_new(slplink);
+	msn_slpcall_init(slpcall, MSN_SLPCALL_DC);
 
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "request_user_display(): buddy_icon_window++ yields =%d\n",
-			                  session->userlist->buddy_icon_window);
+	slpcall->session_init_cb = send_file_cb;
+	slpcall->end_cb = msn_xfer_end_cb;
+	slpcall->cb = msn_xfer_completed_cb;
+	slpcall->xfer = xfer;
+	purple_xfer_ref(slpcall->xfer);
+
+	slpcall->pending = TRUE;
 
-		msn_release_buddy_icon_request(session->userlist);
-	}
+	purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel);
+	purple_xfer_set_read_fnc(xfer, msn_xfer_read);
+	purple_xfer_set_write_fnc(xfer, msn_xfer_write);
+
+	xfer->data = slpcall;
+
+	context = gen_context(xfer, fn, fp);
+
+	msn_slpcall_invite(slpcall, MSN_FT_GUID, P2P_APPID_FILE, context);
+	msn_slplink_unref(slplink);
+
+	g_free(context);
 }
+