view libpurple/protocols/msn/msg.c @ 30725:de59c181e6aa

MsnUserEndpoint->id isn't actually used, so let's not store it in the struct. It's used as the key in the endpoints hashtable, so hopefully we can get away with keeping only one copy of it
author Mark Doliner <mark@kingant.net>
date Thu, 22 Apr 2010 20:51:24 +0000
parents c5a7516418c7
children f8ec889c5bce
line wrap: on
line source

/**
 * @file msg.c Message functions
 *
 * purple
 *
 * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */
#include "msn.h"
#include "msg.h"
#include "msnutils.h"

MsnMessage *
msn_message_new(MsnMsgType type)
{
	MsnMessage *msg;

	msg = g_new0(MsnMessage, 1);
	msg->type = type;

	if (purple_debug_is_verbose())
		purple_debug_info("msn", "message new (%p)(%d)\n", msg, type);

	msg->header_table = g_hash_table_new_full(g_str_hash, g_str_equal,
											g_free, g_free);

	msn_message_ref(msg);

	return msg;
}

void
msn_message_destroy(MsnMessage *msg)
{
	g_return_if_fail(msg != NULL);

	if (msg->ref_count > 0)
	{
		msn_message_unref(msg);

		return;
	}

	if (purple_debug_is_verbose())
		purple_debug_info("msn", "message destroy (%p)\n", msg);

	g_free(msg->remote_user);
	g_free(msg->body);
	g_free(msg->content_type);
	g_free(msg->charset);

	g_hash_table_destroy(msg->header_table);
	g_list_free(msg->header_list);

	g_free(msg);
}

MsnMessage *
msn_message_ref(MsnMessage *msg)
{
	g_return_val_if_fail(msg != NULL, NULL);

	msg->ref_count++;

	if (purple_debug_is_verbose())
		purple_debug_info("msn", "message ref (%p)[%" G_GSIZE_FORMAT "]\n", msg, msg->ref_count);

	return msg;
}

MsnMessage *
msn_message_unref(MsnMessage *msg)
{
	g_return_val_if_fail(msg != NULL, NULL);
	g_return_val_if_fail(msg->ref_count > 0, NULL);

	msg->ref_count--;

	if (purple_debug_is_verbose())
		purple_debug_info("msn", "message unref (%p)[%" G_GSIZE_FORMAT "]\n", msg, msg->ref_count);

	if (msg->ref_count == 0)
	{
		msn_message_destroy(msg);

		return NULL;
	}

	return msg;
}

MsnMessage *
msn_message_new_plain(const char *message)
{
	MsnMessage *msg;
	char *message_cr;

	msg = msn_message_new(MSN_MSG_TEXT);
	msg->retries = 1;
	msn_message_set_header(msg, "User-Agent", PACKAGE_NAME "/" VERSION);
	msn_message_set_content_type(msg, "text/plain");
	msn_message_set_charset(msg, "UTF-8");
	msn_message_set_flag(msg, 'A');
	msn_message_set_header(msg, "X-MMS-IM-Format",
						 "FN=Segoe%20UI; EF=; CO=0; CS=1;PF=0");

	message_cr = purple_str_add_cr(message);
	msn_message_set_bin_data(msg, message_cr, strlen(message_cr));
	g_free(message_cr);

	return msg;
}

MsnMessage *
msn_message_new_msnslp(void)
{
	MsnMessage *msg;

	msg = msn_message_new(MSN_MSG_SLP);

	msn_message_set_header(msg, "User-Agent", NULL);

	msg->msnslp_message = TRUE;

	msn_message_set_flag(msg, 'D');
	msn_message_set_content_type(msg, "application/x-msnmsgrp2p");

	return msg;
}

MsnMessage *
msn_message_new_nudge(void)
{
	MsnMessage *msg;

	msg = msn_message_new(MSN_MSG_NUDGE);
	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast");
	msn_message_set_flag(msg, 'N');
	msn_message_set_bin_data(msg, "ID: 1\r\n", 7);

	return msg;
}

void
msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len)
{
	MsnSlpHeader header;
	const char *tmp;
	int body_len;

	tmp = body;

	if (len < sizeof(header)) {
		g_return_if_reached();
	}

	/* Extract the binary SLP header */
	memcpy(&header, tmp, sizeof(header));
	tmp += sizeof(header);

	msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
	msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
	msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
	msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
	msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
	msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
	msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
	msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
	msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);

	/* Extract the body */
	body_len = len - (tmp - body);
	/* msg->body_len = msg->msnslp_header.length; */

	if (body_len > 0) {
		msg->body_len = len - (tmp - body);
		msg->body = g_malloc(msg->body_len + 1);
		memcpy(msg->body, tmp, msg->body_len);
		msg->body[msg->body_len] = '\0';
		tmp += body_len;
	}
}

void
msn_message_parse_payload(MsnMessage *msg,
						  const char *payload, size_t payload_len,
						  const char *line_dem,const char *body_dem)
{
	char *tmp_base, *tmp;
	const char *content_type;
	char *end;
	char **elems, **cur, **tokens;

	g_return_if_fail(payload != NULL);
	tmp_base = tmp = g_malloc(payload_len + 1);
	memcpy(tmp_base, payload, payload_len);
	tmp_base[payload_len] = '\0';

	/* Find the end of the headers */
	end = strstr(tmp, body_dem);
	/* TODO? some clients use \r delimiters instead of \r\n, the official client
	 * doesn't send such messages, but does handle receiving them. We'll just
	 * avoid crashing for now */
	if (end == NULL) {
		g_free(tmp_base);
		g_return_if_reached();
	}
	*end = '\0';

	/* Split the headers and parse each one */
	elems = g_strsplit(tmp, line_dem, 0);
	for (cur = elems; *cur != NULL; cur++)
	{
		const char *key, *value;

		/* If this line starts with whitespace, it's been folded from the
		   previous line and won't have ':'. */
		if ((**cur == ' ') || (**cur == '\t')) {
			tokens = g_strsplit(g_strchug(*cur), "=\"", 2);
			key = tokens[0];
			value = tokens[1];

			/* The only one I care about is 'boundary' (which is folded from
			   the key 'Content-Type'), so only process that. */
			if (!strcmp(key, "boundary")) {
				char *end = strchr(value, '\"');
				*end = '\0';
				msn_message_set_header(msg, key, value);
			}

			g_strfreev(tokens);
			continue;
		}

		tokens = g_strsplit(*cur, ": ", 2);

		key = tokens[0];
		value = tokens[1];

		/*if not MIME content ,then return*/
		if (!strcmp(key, "MIME-Version"))
		{
			g_strfreev(tokens);
			continue;
		}

		if (!strcmp(key, "Content-Type"))
		{
			char *charset, *c;

			if ((c = strchr(value, ';')) != NULL)
			{
				if ((charset = strchr(c, '=')) != NULL)
				{
					charset++;
					msn_message_set_charset(msg, charset);
				}

				*c = '\0';
			}

			msn_message_set_content_type(msg, value);
		}
		else
		{
			msn_message_set_header(msg, key, value);
		}

		g_strfreev(tokens);
	}
	g_strfreev(elems);

	/* Proceed to the end of the "\r\n\r\n" */
	tmp = end + strlen(body_dem);

	/* Now we *should* be at the body. */
	content_type = msn_message_get_content_type(msg);

	if (content_type != NULL &&
		!strcmp(content_type, "application/x-msnmsgrp2p"))
	{
		MsnSlpHeader header;
		MsnSlpFooter footer;
		int body_len;

		if (payload_len - (tmp - tmp_base) < sizeof(header)) {
			g_free(tmp_base);
			g_return_if_reached();
		}

		msg->msnslp_message = TRUE;

		/* Extract the binary SLP header */
		memcpy(&header, tmp, sizeof(header));
		tmp += sizeof(header);

		msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
		msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
		msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
		msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
		msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
		msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
		msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
		msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
		msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);

		body_len = payload_len - (tmp - tmp_base) - sizeof(footer);

		/* Extract the body */
		if (body_len > 0) {
			msg->body_len = body_len;
			g_free(msg->body);
			msg->body = g_malloc(msg->body_len + 1);
			memcpy(msg->body, tmp, msg->body_len);
			msg->body[msg->body_len] = '\0';
			tmp += body_len;
		}

		/* Extract the footer */
		if (body_len >= 0) {
			memcpy(&footer, tmp, sizeof(footer));
			tmp += sizeof(footer);
			msg->msnslp_footer.value = GUINT32_FROM_BE(footer.value);
		}
	}
	else
	{
		if (payload_len - (tmp - tmp_base) > 0) {
			msg->body_len = payload_len - (tmp - tmp_base);
			g_free(msg->body);
			msg->body = g_malloc(msg->body_len + 1);
			memcpy(msg->body, tmp, msg->body_len);
			msg->body[msg->body_len] = '\0';
		}

		if ((!content_type || !strcmp(content_type, "text/plain"))
			&& msg->charset == NULL) {
			char *body = g_convert(msg->body, msg->body_len, "UTF-8",
			                       "ISO-8859-1", NULL, &msg->body_len, NULL);
			g_free(msg->body);
			msg->body = body;
			msg->charset = g_strdup("UTF-8");
		}
	}

	g_free(tmp_base);
}

MsnMessage *
msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd)
{
	MsnMessage *msg;

	g_return_val_if_fail(cmd != NULL, NULL);

	msg = msn_message_new(MSN_MSG_UNKNOWN);

	msg->remote_user = g_strdup(cmd->params[0]);
	/* msg->size = atoi(cmd->params[2]); */
	msg->cmd = cmd;

	return msg;
}

char *
msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size)
{
	MsnSlpHeader header;

	char *tmp, *base;
	const void *body;
	size_t len, body_len;

	g_return_val_if_fail(msg != NULL, NULL);

	len = MSN_BUF_LEN;

	base = tmp = g_malloc(len + 1);

	body = msn_message_get_bin_data(msg, &body_len);

	header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
	header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
	header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
	header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
	header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
	header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
	header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
	header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
	header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);

	memcpy(tmp, &header, 48);
	tmp += 48;

	if (body != NULL)
	{
		memcpy(tmp, body, body_len);
		tmp += body_len;
	}

	if (ret_size != NULL)
		*ret_size = tmp - base;

	return base;
}

char *
msn_message_gen_payload(MsnMessage *msg, size_t *ret_size)
{
	GList *l;
	char *n, *base, *end;
	int len;
	size_t body_len = 0;
	const void *body;

	g_return_val_if_fail(msg != NULL, NULL);

	len = MSN_BUF_LEN;

	base = n = end = g_malloc(len + 1);
	end += len;

	/* Standard header. */
	if (msg->charset == NULL)
	{
		g_snprintf(n, len,
				   "MIME-Version: 1.0\r\n"
				   "Content-Type: %s\r\n",
				   msg->content_type);
	}
	else
	{
		g_snprintf(n, len,
				   "MIME-Version: 1.0\r\n"
				   "Content-Type: %s; charset=%s\r\n",
				   msg->content_type, msg->charset);
	}

	n += strlen(n);

	for (l = msg->header_list; l != NULL; l = l->next)
	{
		const char *key;
		const char *value;

		key = l->data;
		value = msn_message_get_header_value(msg, key);

		g_snprintf(n, end - n, "%s: %s\r\n", key, value);
		n += strlen(n);
	}

	n += g_strlcpy(n, "\r\n", end - n);

	body = msn_message_get_bin_data(msg, &body_len);

	if (msg->msnslp_message)
	{
		MsnSlpHeader header;
		MsnSlpFooter footer;

		header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
		header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
		header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
		header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
		header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
		header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
		header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
		header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
		header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);

		memcpy(n, &header, 48);
		n += 48;

		if (body != NULL)
		{
			memcpy(n, body, body_len);

			n += body_len;
		}

		footer.value = GUINT32_TO_BE(msg->msnslp_footer.value);

		memcpy(n, &footer, 4);
		n += 4;
	}
	else
	{
		if (body != NULL)
		{
			memcpy(n, body, body_len);
			n += body_len;
			*n = '\0';
		}
	}

	if (ret_size != NULL)
	{
		*ret_size = n - base;

		if (*ret_size > 1664)
			*ret_size = 1664;
	}

	return base;
}

void
msn_message_set_flag(MsnMessage *msg, char flag)
{
	g_return_if_fail(msg != NULL);
	g_return_if_fail(flag != 0);

	msg->flag = flag;
}

char
msn_message_get_flag(const MsnMessage *msg)
{
	g_return_val_if_fail(msg != NULL, 0);

	return msg->flag;
}

void
msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len)
{
	g_return_if_fail(msg != NULL);

	/* There is no need to waste memory on data we cannot send anyway */
	if (len > 1664)
		len = 1664;

	if (msg->body != NULL)
		g_free(msg->body);

	if (data != NULL && len > 0)
	{
		msg->body = g_malloc(len + 1);
		memcpy(msg->body, data, len);
		msg->body[len] = '\0';
		msg->body_len = len;
	}
	else
	{
		msg->body = NULL;
		msg->body_len = 0;
	}
}

const void *
msn_message_get_bin_data(const MsnMessage *msg, size_t *len)
{
	g_return_val_if_fail(msg != NULL, NULL);

	if (len)
		*len = msg->body_len;

	return msg->body;
}

void
msn_message_set_content_type(MsnMessage *msg, const char *type)
{
	g_return_if_fail(msg != NULL);

	g_free(msg->content_type);
	msg->content_type = g_strdup(type);
}

const char *
msn_message_get_content_type(const MsnMessage *msg)
{
	g_return_val_if_fail(msg != NULL, NULL);

	return msg->content_type;
}

void
msn_message_set_charset(MsnMessage *msg, const char *charset)
{
	g_return_if_fail(msg != NULL);

	g_free(msg->charset);
	msg->charset = g_strdup(charset);
}

const char *
msn_message_get_charset(const MsnMessage *msg)
{
	g_return_val_if_fail(msg != NULL, NULL);

	return msg->charset;
}

void
msn_message_set_header(MsnMessage *msg, const char *name, const char *value)
{
	const char *temp;
	char *new_name;

	g_return_if_fail(msg != NULL);
	g_return_if_fail(name != NULL);

	temp = msn_message_get_header_value(msg, name);

	if (value == NULL)
	{
		if (temp != NULL)
		{
			GList *l;

			for (l = msg->header_list; l != NULL; l = l->next)
			{
				if (!g_ascii_strcasecmp(l->data, name))
				{
					msg->header_list = g_list_remove(msg->header_list, l->data);

					break;
				}
			}

			g_hash_table_remove(msg->header_table, name);
		}

		return;
	}

	new_name = g_strdup(name);

	g_hash_table_insert(msg->header_table, new_name, g_strdup(value));

	if (temp == NULL)
		msg->header_list = g_list_append(msg->header_list, new_name);
}

const char *
msn_message_get_header_value(const MsnMessage *msg, const char *name)
{
	g_return_val_if_fail(msg != NULL, NULL);
	g_return_val_if_fail(name != NULL, NULL);

	return g_hash_table_lookup(msg->header_table, name);
}

GHashTable *
msn_message_get_hashtable_from_body(const MsnMessage *msg)
{
	GHashTable *table;
	size_t body_len;
	const char *body;
	char **elems, **cur, **tokens, *body_str;

	g_return_val_if_fail(msg != NULL, NULL);

	table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

	body = msn_message_get_bin_data(msg, &body_len);

	g_return_val_if_fail(body != NULL, NULL);

	body_str = g_strndup(body, body_len);
	elems = g_strsplit(body_str, "\r\n", 0);
	g_free(body_str);

	for (cur = elems; *cur != NULL; cur++)
	{
		if (**cur == '\0')
			break;

		tokens = g_strsplit(*cur, ": ", 2);

		if (tokens[0] != NULL && tokens[1] != NULL) {
			g_hash_table_insert(table, tokens[0], tokens[1]);
			g_free(tokens);
		} else
			g_strfreev(tokens);
	}

	g_strfreev(elems);

	return table;
}

char *
msn_message_to_string(MsnMessage *msg)
{
	size_t body_len;
	const char *body;

	g_return_val_if_fail(msg != NULL, NULL);
	g_return_val_if_fail(msg->type == MSN_MSG_TEXT, NULL);

	body = msn_message_get_bin_data(msg, &body_len);

	return g_strndup(body, body_len);
}

void
msn_message_show_readable(MsnMessage *msg, const char *info,
						  gboolean text_body)
{
	GString *str;
	size_t body_len;
	const char *body;
	GList *l;

	g_return_if_fail(msg != NULL);

	str = g_string_new(NULL);

	/* Standard header. */
	if (msg->charset == NULL)
	{
		g_string_append_printf(str,
				   "MIME-Version: 1.0\r\n"
				   "Content-Type: %s\r\n",
				   msg->content_type);
	}
	else
	{
		g_string_append_printf(str,
				   "MIME-Version: 1.0\r\n"
				   "Content-Type: %s; charset=%s\r\n",
				   msg->content_type, msg->charset);
	}

	for (l = msg->header_list; l; l = l->next)
	{
		char *key;
		const char *value;

		key = l->data;
		value = msn_message_get_header_value(msg, key);

		g_string_append_printf(str, "%s: %s\r\n", key, value);
	}

	g_string_append(str, "\r\n");

	body = msn_message_get_bin_data(msg, &body_len);

	if (msg->msnslp_message)
	{
		g_string_append_printf(str, "Session ID: %u\r\n", msg->msnslp_header.session_id);
		g_string_append_printf(str, "ID:         %u\r\n", msg->msnslp_header.id);
		g_string_append_printf(str, "Offset:     %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.offset);
		g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.total_size);
		g_string_append_printf(str, "Length:     %u\r\n", msg->msnslp_header.length);
		g_string_append_printf(str, "Flags:      0x%x\r\n", msg->msnslp_header.flags);
		g_string_append_printf(str, "ACK ID:     %u\r\n", msg->msnslp_header.ack_id);
		g_string_append_printf(str, "SUB ID:     %u\r\n", msg->msnslp_header.ack_sub_id);
		g_string_append_printf(str, "ACK Size:   %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.ack_size);

		if (purple_debug_is_verbose() && body != NULL)
		{
			if (text_body)
			{
				g_string_append_len(str, body, body_len);
				if (body[body_len - 1] == '\0')
				{
					str->len--;
					g_string_append(str, " 0x00");
				}
				g_string_append(str, "\r\n");
			}
			else
			{
				int i;
				for (i = 0; i < msg->body_len; i++)
				{
					g_string_append_printf(str, "%.2hhX ", body[i]);
					if ((i % 16) == 15)
						g_string_append(str, "\r\n");
				}
				g_string_append(str, "\r\n");
			}
		}

		g_string_append_printf(str, "Footer:     %u\r\n", msg->msnslp_footer.value);
	}
	else
	{
		if (body != NULL)
		{
			g_string_append_len(str, body, body_len);
			g_string_append(str, "\r\n");
		}
	}

	purple_debug_info("msn", "Message %s:\n{%s}\n", info, str->str);

	g_string_free(str, TRUE);
}

/**************************************************************************
 * Message Handlers
 **************************************************************************/
void
msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
{
	PurpleConnection *gc;
	const char *body;
	char *body_enc;
	char *body_final;
	size_t body_len;
	const char *passport;
	const char *value;

	gc = cmdproc->session->account->gc;

	body = msn_message_get_bin_data(msg, &body_len);
	body_enc = g_markup_escape_text(body, body_len);

	passport = msg->remote_user;

	if (!strcmp(passport, "messenger@microsoft.com") &&
		strstr(body, "immediate security update"))
	{
		return;
	}

#if 0
	if ((value = msn_message_get_header_value(msg, "User-Agent")) != NULL)
	{
		purple_debug_misc("msn", "User-Agent = '%s'\n", value);
	}
#endif

	if ((value = msn_message_get_header_value(msg, "X-MMS-IM-Format")) != NULL)
	{
		char *pre, *post;

		msn_parse_format(value, &pre, &post);

		body_final = g_strdup_printf("%s%s%s", pre ? pre : "",
									 body_enc ? body_enc : "", post ? post : "");

		g_free(pre);
		g_free(post);
		g_free(body_enc);
	}
	else
	{
		body_final = body_enc;
	}

	if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
		MsnSwitchBoard *swboard = cmdproc->data;

		swboard->flag |= MSN_SB_FLAG_IM;

		if (swboard->current_users > 1 ||
			((swboard->conv != NULL) &&
			 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
		{
			/* If current_users is always ok as it should then there is no need to
			 * check if this is a chat. */
			if (swboard->current_users <= 1)
				purple_debug_misc("msn", "plain_msg: current_users(%d)\n",
								swboard->current_users);

			serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final,
							 time(NULL));
			if (swboard->conv == NULL)
			{
				swboard->conv = purple_find_chat(gc, swboard->chat_id);
				swboard->flag |= MSN_SB_FLAG_IM;
			}
		}
		else
		{
			serv_got_im(gc, passport, body_final, 0, time(NULL));
			if (swboard->conv == NULL)
			{
				swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
										passport, purple_connection_get_account(gc));
				swboard->flag |= MSN_SB_FLAG_IM;
			}
		}

	} else {
		serv_got_im(gc, passport, body_final, 0, time(NULL));
	}

	g_free(body_final);
}

void
msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
{
	PurpleConnection *gc;
	char *passport;

	gc = cmdproc->session->account->gc;
	passport = msg->remote_user;

	if (msn_message_get_header_value(msg, "TypingUser") == NULL)
		return;

	if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
		MsnSwitchBoard *swboard = cmdproc->data;

		if (swboard->current_users == 1)
		{
			serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
							PURPLE_TYPING);
		}

	} else {
		serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
						PURPLE_TYPING);
	}
}

static void
datacast_inform_user(MsnSwitchBoard *swboard, const char *who,
                     const char *msg, const char *filename)
{
	char *username, *str;
	PurpleAccount *account;
	PurpleBuddy *b;

	account = swboard->session->account;

	if ((b = purple_find_buddy(account, who)) != NULL)
		username = g_markup_escape_text(purple_buddy_get_alias(b), -1);
	else
		username = g_markup_escape_text(who, -1);
	str = g_strdup_printf(msg, username, filename);
	g_free(username);

	if (swboard->conv == NULL) {
		if (swboard->current_users > 1) 
			swboard->conv = purple_find_chat(account->gc, swboard->chat_id);
		else {
			swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
									who, account);
			if (swboard->conv == NULL)
				swboard->conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
		}
	}
	swboard->flag |= MSN_SB_FLAG_IM;

	purple_conversation_write(swboard->conv, NULL, str, PURPLE_MESSAGE_SYSTEM, time(NULL));
	g_free(str);

}

/* TODO: Make these not be such duplicates of each other */
static void 
got_wink_cb(MsnSlpCall *slpcall, const guchar *data, gsize size)
{
	FILE *f;
	char *path = NULL;
	const char *who = slpcall->slplink->remote_user;
	purple_debug_info("msn", "Received wink from %s\n", who);

	if ((f = purple_mkstemp(&path, TRUE))) {
		fwrite(data, size, 1, f);
		fclose(f);
		datacast_inform_user(slpcall->slplink->swboard,
		                     who,
		                     _("%s sent a wink. <a href='msn-wink://%s'>Click here to play it</a>"),
		                     path);
	} else {
		purple_debug_error("msn", "Couldn\'t create temp file to store wink\n");
		datacast_inform_user(slpcall->slplink->swboard,
		                     who,
		                     _("%s sent a wink, but it could not be saved"),
		                     NULL);
	} 
	g_free(path);
}

static void 
got_voiceclip_cb(MsnSlpCall *slpcall, const guchar *data, gsize size)
{
	FILE *f;
	char *path = NULL;
	const char *who = slpcall->slplink->remote_user;
	purple_debug_info("msn", "Received voice clip from %s\n", who);

	if ((f = purple_mkstemp(&path, TRUE))) {
		fwrite(data, size, 1, f);
		fclose(f);
		datacast_inform_user(slpcall->slplink->swboard,
		                     who,
		                     _("%s sent a voice clip. <a href='audio://%s'>Click here to play it</a>"),
		                     path);
	} else {
		purple_debug_error("msn", "Couldn\'t create temp file to store sound\n");
		datacast_inform_user(slpcall->slplink->swboard,
		                     who,
		                     _("%s sent a voice clip, but it could not be saved"),
		                     NULL);
	} 
	g_free(path);
}

void
msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
{
	GHashTable *body;
	const char *id;
	body = msn_message_get_hashtable_from_body(msg);

	id = g_hash_table_lookup(body, "ID");

	if (!strcmp(id, "1")) {
		/* Nudge */
		PurpleAccount *account;
		const char *user;

		account = cmdproc->session->account;
		user = msg->remote_user;

		if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
			MsnSwitchBoard *swboard = cmdproc->data;
			if (swboard->current_users > 1 ||
				((swboard->conv != NULL) &&
				 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
				purple_prpl_got_attention_in_chat(account->gc, swboard->chat_id, user, MSN_NUDGE);

			else
				purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
		} else {
			purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
		}

	} else if (!strcmp(id, "2")) {
		/* Wink */
		MsnSession *session;
		MsnSlpLink *slplink;
		MsnObject *obj;
		const char *who;
		const char *data;

		session = cmdproc->session;

		data = g_hash_table_lookup(body, "Data");
		obj = msn_object_new_from_string(data);
		who = msn_object_get_creator(obj);

		slplink = msn_session_get_slplink(session, who);
		msn_slplink_request_object(slplink, data, got_wink_cb, NULL, obj);

		msn_object_destroy(obj);


	} else if (!strcmp(id, "3")) {
		/* Voiceclip */
		MsnSession *session;
		MsnSlpLink *slplink;
		MsnObject *obj;
		const char *who;
		const char *data;

		session = cmdproc->session;

		data = g_hash_table_lookup(body, "Data");
		obj = msn_object_new_from_string(data);
		who = msn_object_get_creator(obj);

		slplink = msn_session_get_slplink(session, who);
		msn_slplink_request_object(slplink, data, got_voiceclip_cb, NULL, obj);

		msn_object_destroy(obj);

	} else if (!strcmp(id, "4")) {
		/* Action */

	} else {
		purple_debug_warning("msn", "Got unknown datacast with ID %s.\n", id);
	}

	g_hash_table_destroy(body);
}

void
msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
{
	GHashTable *body;
	const gchar *guid;
	gboolean accepted = FALSE;

	g_return_if_fail(cmdproc != NULL);
	g_return_if_fail(msg != NULL);

	body = msn_message_get_hashtable_from_body(msg);

	if (body == NULL) {
		purple_debug_warning("msn",
				"Unable to parse invite msg body.\n");
		return;
	}

	guid = g_hash_table_lookup(body, "Application-GUID");

	if (guid == NULL) {
		const gchar *cmd = g_hash_table_lookup(
				body, "Invitation-Command");

		if (cmd && !strcmp(cmd, "CANCEL")) {
			const gchar *code = g_hash_table_lookup(
					body, "Cancel-Code");
			purple_debug_info("msn",
					"MSMSGS invitation cancelled: %s.\n",
					code ? code : "no reason given");
		} else
			purple_debug_warning("msn", "Invite msg missing "
					"Application-GUID.\n");

		accepted = TRUE;

	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
		purple_debug_info("msn", "Computer call\n");

		if (cmdproc->session) {
			PurpleConversation *conv = NULL;
			gchar *from = msg->remote_user;
			gchar *buf = NULL;

			if (from)
				conv = purple_find_conversation_with_account(
						PURPLE_CONV_TYPE_IM, from,
						cmdproc->session->account);
			if (conv)
				buf = g_strdup_printf(
						_("%s sent you a voice chat "
						"invite, which is not yet "
						"supported."), from);
			if (buf) {
				purple_conversation_write(conv, NULL, buf,
						PURPLE_MESSAGE_SYSTEM |
						PURPLE_MESSAGE_NOTIFY,
						time(NULL));
				g_free(buf);
			}
		}
	} else {
		const gchar *application = g_hash_table_lookup(body, "Application-Name");
		purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
		                     guid, application ? application : "(null)");
	}

	if (!accepted) {
		const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie");
		if (cookie) {
			MsnSwitchBoard *swboard = cmdproc->data;
			char *text;
			MsnMessage *cancel;

			cancel = msn_message_new(MSN_MSG_TEXT);
			msn_message_set_content_type(cancel, "text/x-msmsgsinvite");
			msn_message_set_charset(cancel, "UTF-8");
			msn_message_set_flag(cancel, 'U');

			text = g_strdup_printf("Invitation-Command: CANCEL\r\n"
			                       "Invitation-Cookie: %s\r\n"
			                       "Cancel-Code: REJECT_NOT_INSTALLED\r\n",
			                       cookie);
			msn_message_set_bin_data(cancel, text, strlen(text));
			g_free(text);

			msn_switchboard_send_msg(swboard, cancel, TRUE);
			msn_message_destroy(cancel);
		}
	}

	g_hash_table_destroy(body);
}

/* Only called from chats. Handwritten messages for IMs come as a SLP message */
void
msn_handwritten_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
{
	const char *body;
	size_t body_len;

	body = msn_message_get_bin_data(msg, &body_len);
	msn_switchboard_show_ink(cmdproc->data, msg->remote_user, body);
}