libpurple/protocols/gg/message-prpl.c

Thu, 22 May 2014 21:04:56 +0200

author
Tomasz Wasilczyk <twasilczyk@pidgin.im>
date
Thu, 22 May 2014 21:04:56 +0200
changeset 36089
c035b9a63457
parent 36076
a0e5b68ff4ef
child 36098
4951752ad038
permissions
-rw-r--r--

Use purple_conversation_write_system_message where appropriate

/* 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.
 *
 * Component written by Tomek Wasilczyk (http://www.wasilczyk.pl).
 *
 * This file is dual-licensed under the GPL2+ and the X11 (MIT) licences.
 * As a recipient of this file you may choose, which license to receive the
 * code under. As a contributor, you have to ensure the new code is
 * compatible with both.
 *
 * 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 "message-prpl.h"

#include <debug.h>
#include <glibcompat.h>
#include <image-store.h>

#include "gg.h"
#include "chat.h"
#include "utils.h"
#include "html.h"

#define GGP_GG10_DEFAULT_FORMAT "<span style=\"color:#000000; " \
	"font-family:'MS Shell Dlg 2'; font-size:9pt; \">"
#define GGP_GG10_DEFAULT_FORMAT_REPLACEMENT "<span>"
#define GGP_GG11_FORCE_COMPAT FALSE

typedef struct
{
	enum
	{
		GGP_MESSAGE_GOT_TYPE_IM,
		GGP_MESSAGE_GOT_TYPE_CHAT,
		GGP_MESSAGE_GOT_TYPE_MULTILOGON
	} type;

	uin_t user;
	gchar *text;
	time_t time;
	uint64_t chat_id;

	PurpleConnection *gc;
} ggp_message_got_data;

typedef struct
{
	GRegex *re_html_tag;
	GRegex *re_gg_img;
} ggp_message_global_data;

static ggp_message_global_data global_data;

struct _ggp_message_session_data
{
};

typedef struct
{
	int size;
	gchar *face;
	int color, bgcolor;
	gboolean b, i, u, s;
} ggp_font;

static ggp_font * ggp_font_new(void);
static ggp_font * ggp_font_clone(ggp_font *font);
static void ggp_font_free(gpointer font);

static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc,
	uin_t uin);
static void ggp_message_got_data_free(ggp_message_got_data *msg);
static void ggp_message_got_display(PurpleConnection *gc,
	ggp_message_got_data *msg);
static void ggp_message_format_from_gg(ggp_message_got_data *msg,
	const gchar *text);

/**************/

void ggp_message_setup_global(void)
{
	global_data.re_html_tag = g_regex_new(
		"<(/)?([a-zA-Z]+)( [^>]+)?>",
		G_REGEX_OPTIMIZE, 0, NULL);
	global_data.re_gg_img = g_regex_new(
		"<img name=\"([0-9a-fA-F]+)\"/?>",
		G_REGEX_OPTIMIZE, 0, NULL);
}

void ggp_message_cleanup_global(void)
{
	g_regex_unref(global_data.re_html_tag);
	g_regex_unref(global_data.re_gg_img);
}

static inline ggp_message_session_data *
ggp_message_get_sdata(PurpleConnection *gc)
{
	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
	return accdata->message_data;
}

void ggp_message_setup(PurpleConnection *gc)
{
	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
	ggp_message_session_data *sdata = g_new0(ggp_message_session_data, 1);

	accdata->message_data = sdata;
}

void ggp_message_cleanup(PurpleConnection *gc)
{
	ggp_message_session_data *sdata = ggp_message_get_sdata(gc);

	g_free(sdata);
}

static ggp_font * ggp_font_new(void)
{
	ggp_font *font;

	font = g_new0(ggp_font, 1);
	font->color = -1;
	font->bgcolor = -1;

	return font;
}

static ggp_font * ggp_font_clone(ggp_font * font)
{
	ggp_font *clone = g_new0(ggp_font, 1);

	*clone = *font;
	clone->face = g_strdup(font->face);

	return clone;
}

static void ggp_font_free(gpointer _font)
{
	ggp_font *font = _font;

	g_free(font->face);
	g_free(font);
}

/**/

static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc,
	uin_t uin)
{
	PurpleAccount *account = purple_connection_get_account(gc);
	PurpleIMConversation *im;
	const gchar *who = ggp_uin_to_str(uin);

	im = purple_conversations_find_im_with_account(who, account);
	if (im)
		return im;
	im = purple_im_conversation_new(account, who);
	return im;
}

static void ggp_message_got_data_free(ggp_message_got_data *msg)
{
	g_free(msg->text);
	g_free(msg);
}

void ggp_message_got(PurpleConnection *gc, const struct gg_event_msg *ev)
{
	ggp_message_got_data *msg = g_new0(ggp_message_got_data, 1);

	msg->gc = gc;
	msg->time = ev->time;
	msg->user = ev->sender;

#if GGP_ENABLE_GG11
	if (ev->chat_id != 0) {
		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
		msg->chat_id = ev->chat_id;
	} else
#endif
	{
		msg->type = GGP_MESSAGE_GOT_TYPE_IM;
	}

	ggp_message_format_from_gg(msg, ev->xhtml_message);

	ggp_message_got_display(gc, msg);
	ggp_message_got_data_free(msg);
}

void ggp_message_got_multilogon(PurpleConnection *gc,
	const struct gg_event_msg *ev)
{
	ggp_message_got_data *msg = g_new0(ggp_message_got_data, 1);

	msg->gc = gc;
	msg->time = ev->time;
	msg->user = ev->sender; /* not really a sender*/

#if GGP_ENABLE_GG11
	if (ev->chat_id != 0) {
		msg->type = GGP_MESSAGE_GOT_TYPE_CHAT;
		msg->chat_id = ev->chat_id;
	} else
#endif
	{
		msg->type = GGP_MESSAGE_GOT_TYPE_MULTILOGON;
	}

	ggp_message_format_from_gg(msg, ev->xhtml_message);

	ggp_message_got_display(gc, msg);
	ggp_message_got_data_free(msg);
}

static void ggp_message_got_display(PurpleConnection *gc,
	ggp_message_got_data *msg)
{
	if (msg->type == GGP_MESSAGE_GOT_TYPE_IM) {
		purple_serv_got_im(gc, ggp_uin_to_str(msg->user), msg->text,
			PURPLE_MESSAGE_RECV, msg->time);
	}
#if GGP_ENABLE_GG11
	else if (msg->type == GGP_MESSAGE_GOT_TYPE_CHAT) {
		ggp_chat_got_message(gc, msg->chat_id, msg->text, msg->time,
			msg->user);
	}
#endif
	else if (msg->type == GGP_MESSAGE_GOT_TYPE_MULTILOGON) {
		PurpleIMConversation *im = ggp_message_get_conv(gc, msg->user);
		PurpleMessage *pmsg;

		pmsg = purple_message_new(NULL, msg->text, PURPLE_MESSAGE_SEND);
		purple_message_set_time(pmsg, msg->time);

		purple_conversation_write_message(PURPLE_CONVERSATION(im), pmsg);
	} else
		purple_debug_error("gg", "ggp_message_got_display: "
			"unexpected message type: %d\n", msg->type);
}

static gboolean ggp_message_format_from_gg_found_img(const GMatchInfo *info,
	GString *res, gpointer data)
{
	ggp_message_got_data *msg = data;
	gchar *name, *replacement;
	int64_t id;
	PurpleImage *image;
	guint image_id;

	name = g_match_info_fetch(info, 1);
	if (sscanf(name, "%" G_GINT64_MODIFIER "x", &id) != 1)
		id = 0;
	g_free(name);
	if (!id) {
		/* TODO: stock broken image? */
		g_string_append_printf(res, "[%s]", _("broken image"));
		return FALSE;
	}

	image = ggp_image_request(msg->gc, msg->user, id);
	if (!image) {
		purple_debug_warning("gg", "ggp_message_format_from_gg_"
			"found_img: couldn't request image");
		g_string_append_printf(res, "[%s]", _("broken image"));
		return FALSE;
	}

	image_id = purple_image_store_add_weak(image);
	replacement = g_strdup_printf("<img src=\""
		PURPLE_IMAGE_STORE_PROTOCOL "%u\">", image_id);
	g_string_append(res, replacement);
	g_free(replacement);

	return FALSE;
}

static void ggp_message_format_from_gg(ggp_message_got_data *msg,
	const gchar *text)
{
	gchar *text_new, *tmp;

	if (text == NULL) {
		msg->text = g_strdup("");
		return;
	}

	text_new = g_strdup(text);
	purple_str_strip_char(text_new, '\r');

	tmp = text_new;
	text_new = purple_strreplace(text_new, GGP_GG10_DEFAULT_FORMAT,
		GGP_GG10_DEFAULT_FORMAT_REPLACEMENT);
	g_free(tmp);

	tmp = text_new;
	text_new = g_regex_replace_eval(global_data.re_gg_img, text_new, -1, 0,
		0, ggp_message_format_from_gg_found_img, msg, NULL);
	g_free(tmp);

	msg->text = text_new;
}

gchar * ggp_message_format_to_gg(PurpleConversation *conv, const gchar *text)
{
	gchar *text_new, *tmp;
	GList *rt = NULL; /* reformatted text */
	GMatchInfo *match;
	guint pos = 0;
	GList *pending_objects = NULL;
	GList *font_stack = NULL;
	static int html_sizes_pt[7] = { 7, 8, 9, 10, 12, 14, 16 };

	ggp_font *font_new, *font_current, *font_base;
	gboolean font_changed = FALSE;
	gboolean in_any_tag = FALSE;

	if (purple_debug_is_verbose())
		purple_debug_info("gg", "ggp formatting text: [%s]", text);

	/* default font */
	font_base = ggp_font_new();
	font_current = ggp_font_new();
	font_new = ggp_font_new();

	/* GG11 doesn't use nbsp, it just print spaces */
	text_new = purple_strreplace(text, "&nbsp;", " ");

	/* add end-of-message tag */
	if (strstr(text_new, "<eom>") != NULL) {
		tmp = text_new;
		text_new = purple_strreplace(text_new, "<eom>", "");
		g_free(tmp);
		purple_debug_warning("gg", "ggp_message_format_to_gg: "
			"unexpected <eom> tag\n");
	}
	tmp = text_new;
	text_new = g_strdup_printf("%s<eom></eom>", text_new);
	g_free(tmp);

	g_regex_match(global_data.re_html_tag, text_new, 0, &match);
	while (g_match_info_matches(match)) {
		int m_start, m_end, m_pos;
		gboolean tag_close;
		gchar *tag_str, *attribs_str;
		ggp_html_tag tag;
		gboolean text_before;

		/* reading tag and its contents */
		g_match_info_fetch_pos(match, 0, &m_start, &m_end);
		g_assert(m_start >= 0 && m_end >= 0);
		text_before = ((guint)m_start > pos);
		g_match_info_fetch_pos(match, 1, &m_pos, NULL);
		tag_close = (m_pos >= 0);
		tag_str = g_match_info_fetch(match, 2);
		tag = ggp_html_parse_tag(tag_str);
		attribs_str = g_match_info_fetch(match, 3);
		g_match_info_next(match, NULL);

		if (tag == GGP_HTML_TAG_UNKNOWN) {
			purple_debug_warning("gg", "ggp_message_format_to_gg: "
				"uknown tag %s\n", tag_str);
		}

		/* closing *all* formatting-related tags (GG11 weirness)
		 * and adding pending objects */
		if ((text_before && (font_changed || pending_objects)) ||
			(tag == GGP_HTML_TAG_EOM && tag_close))
		{
			font_changed = FALSE;
			if (in_any_tag) {
				in_any_tag = FALSE;
				if (font_current->s && !GGP_GG11_FORCE_COMPAT)
					rt = g_list_prepend(rt,
						g_strdup("</s>"));
				if (font_current->u)
					rt = g_list_prepend(rt,
						g_strdup("</u>"));
				if (font_current->i)
					rt = g_list_prepend(rt,
						g_strdup("</i>"));
				if (font_current->b)
					rt = g_list_prepend(rt,
						g_strdup("</b>"));
				rt = g_list_prepend(rt, g_strdup("</span>"));
			}
			if (pending_objects) {
				rt = g_list_concat(pending_objects, rt);
				pending_objects = NULL;
			}
		}

		/* opening formatting-related tags again */
		if (text_before && !in_any_tag) {
			gchar *style;
			GList *styles = NULL;
			gboolean has_size = (font_new->size > 0 &&
				font_new->size <= 7 && font_new->size != 3);

			if (has_size)
				styles = g_list_append(styles, g_strdup_printf(
					"font-size:%dpt;",
					html_sizes_pt[font_new->size - 1]));
			if (font_new->face)
				styles = g_list_append(styles, g_strdup_printf(
					"font-family:%s;", font_new->face));
			if (font_new->bgcolor >= 0 && !GGP_GG11_FORCE_COMPAT)
				styles = g_list_append(styles, g_strdup_printf(
					"background-color:#%06x;",
					font_new->bgcolor));
			if (font_new->color >= 0)
				styles = g_list_append(styles, g_strdup_printf(
					"color:#%06x;", font_new->color));

			if (styles) {
				gchar *combined = ggp_strjoin_list(" ", styles);
				g_list_free_full(styles, g_free);
				style = g_strdup_printf(" style=\"%s\"",
					combined);
				g_free(combined);
			} else
				style = g_strdup("");
			rt = g_list_prepend(rt, g_strdup_printf("<span%s>",
				style));
			g_free(style);

			if (font_new->b)
				rt = g_list_prepend(rt, g_strdup("<b>"));
			if (font_new->i)
				rt = g_list_prepend(rt, g_strdup("<i>"));
			if (font_new->u)
				rt = g_list_prepend(rt, g_strdup("<u>"));
			if (font_new->s && !GGP_GG11_FORCE_COMPAT)
				rt = g_list_prepend(rt, g_strdup("<s>"));

			ggp_font_free(font_current);
			font_current = font_new;
			font_new = ggp_font_clone(font_current);

			in_any_tag = TRUE;
		}
		if (text_before) {
			rt = g_list_prepend(rt,
				g_strndup(text_new + pos, m_start - pos));
		}

		/* set formatting of a following text */
		if (tag == GGP_HTML_TAG_B) {
			font_changed |= (font_new->b != !tag_close);
			font_new->b = !tag_close;
		} else if (tag == GGP_HTML_TAG_I) {
			font_changed |= (font_new->i != !tag_close);
			font_new->i = !tag_close;
		} else if (tag == GGP_HTML_TAG_U) {
			font_changed |= (font_new->u != !tag_close);
			font_new->u = !tag_close;
		} else if (tag == GGP_HTML_TAG_S) {
			font_changed |= (font_new->s != !tag_close);
			font_new->s = !tag_close;
		} else if (tag == GGP_HTML_TAG_IMG && !tag_close) {
			GHashTable *attribs = ggp_html_tag_attribs(attribs_str);
			gchar *val = NULL;
			uint64_t id;
			ggp_image_prepare_result res = -1;
			PurpleImage *image = NULL;

			val = g_hash_table_lookup(attribs, "src");
			if (val)
				image = purple_image_store_get_from_uri(val);

			if (image != NULL)
				res = ggp_image_prepare(conv, image, &id);

			if (res == GGP_IMAGE_PREPARE_OK) {
				pending_objects = g_list_prepend(
					pending_objects, g_strdup_printf(
					"<img name=\"" GGP_IMAGE_ID_FORMAT
					"\">", id));
			} else if (res == GGP_IMAGE_PREPARE_TOO_BIG) {
				purple_conversation_write_system_message(conv,
					_("Image is too large, please try "
					"smaller one."), PURPLE_MESSAGE_ERROR);
			} else {
				purple_conversation_write_system_message(conv,
					_("Image cannot be sent."),
					PURPLE_MESSAGE_ERROR);
			}

			g_hash_table_destroy(attribs);
		} else if (tag == GGP_HTML_TAG_FONT && !tag_close) {
			GHashTable *attribs = ggp_html_tag_attribs(attribs_str);
			gchar *val = NULL;

			font_stack = g_list_prepend(font_stack,
				ggp_font_clone(font_new));

			if ((val = g_hash_table_lookup(attribs, "size")) != NULL
				&& val[0] >= '1' && val[0] <= '7' &&
				val[1] == '\0')
			{
				int size = val[0] - '0';
				font_changed |= (font_new->size != size);
				font_new->size = size;
			}

			if ((val = g_hash_table_lookup(attribs, "face"))
				!= NULL)
			{
				font_changed |=
					(g_strcmp0(font_new->face, val) != 0);
				g_free(font_new->face);
				font_new->face = g_strdup(val);
			}

			if ((val = g_hash_table_lookup(attribs, "color"))
				!= NULL && val[0] == '#' && strlen(val) == 7)
			{
				int color = ggp_html_decode_color(val);
				font_changed |= (font_new->color != color);
				font_new->color = color;
			}

			g_hash_table_destroy(attribs);
		}
		else if ((tag == GGP_HTML_TAG_SPAN || tag == GGP_HTML_TAG_DIV)
			&& !tag_close)
		{
			GHashTable *attribs, *styles = NULL;
			gchar *style = NULL;
			gchar *val = NULL;

			attribs = ggp_html_tag_attribs(attribs_str);

			font_stack = g_list_prepend(font_stack,
				ggp_font_clone(font_new));
			if (tag == GGP_HTML_TAG_DIV)
				pending_objects = g_list_prepend(
					pending_objects, g_strdup("<br>"));

			style = g_hash_table_lookup(attribs, "style");
			if (style)
				styles = ggp_html_css_attribs(style);

			if (styles && (val = g_hash_table_lookup(styles,
				"background-color")) != NULL)
			{
				int color = ggp_html_decode_color(val);
				font_changed |= (font_new->bgcolor != color);
				font_new->bgcolor = color;
			}

			if (styles && (val = g_hash_table_lookup(styles,
				"color")) != NULL)
			{
				int color = ggp_html_decode_color(val);
				font_changed |= (font_new->color != color);
				font_new->color = color;
			}

			if (styles)
				g_hash_table_destroy(styles);
			g_hash_table_destroy(attribs);
		}
		else if ((tag == GGP_HTML_TAG_FONT || tag == GGP_HTML_TAG_SPAN
			|| tag == GGP_HTML_TAG_DIV) && tag_close)
		{
			font_changed = TRUE;

			ggp_font_free(font_new);
			if (font_stack) {
				font_new = (ggp_font*)font_stack->data;
				font_stack = g_list_delete_link(
					font_stack, font_stack);
			}
			else
				font_new = ggp_font_clone(font_base);
		} else if (tag == GGP_HTML_TAG_BR) {
			pending_objects = g_list_prepend(pending_objects,
				g_strdup("<br>"));
		} else if (tag == GGP_HTML_TAG_HR) {
			pending_objects = g_list_prepend(pending_objects,
				g_strdup("<br><span>---</span><br>"));
		} else if (tag == GGP_HTML_TAG_A || tag == GGP_HTML_TAG_EOM) {
			/* do nothing */
		} else if (tag == GGP_HTML_TAG_UNKNOWN) {
			purple_debug_warning("gg", "ggp_message_format_to_gg: "
				"uknown tag %s\n", tag_str);
		} else {
			purple_debug_error("gg", "ggp_message_format_to_gg: "
				"not handled tag %s\n", tag_str);
		}

		pos = m_end;
		g_free(tag_str);
		g_free(attribs_str);
	}
	g_match_info_free(match);

	if (pos < strlen(text_new) || in_any_tag) {
		purple_debug_fatal("gg", "ggp_message_format_to_gg: "
			"end of message not reached\n");
	}

	/* releasing fonts recources */
	ggp_font_free(font_new);
	ggp_font_free(font_current);
	ggp_font_free(font_base);
	g_list_free_full(font_stack, ggp_font_free);

	/* combining reformatted text info one string */
	rt = g_list_reverse(rt);
	g_free(text_new);
	text_new = ggp_strjoin_list("", rt);
	g_list_free_full(rt, g_free);

	if (purple_debug_is_verbose())
		purple_debug_info("gg", "reformatted text: [%s]", text_new);

	return text_new;
}

int ggp_message_send_im(PurpleConnection *gc, PurpleMessage *msg)
{
	GGPInfo *info = purple_connection_get_protocol_data(gc);
	PurpleIMConversation *im;
	ggp_buddy_data *buddy_data;
	gchar *gg_msg;
	gboolean succ;
	const gchar *who = purple_message_get_who(msg);

	/* TODO: return -ENOTCONN, if not connected */

	if (purple_message_is_empty(msg))
		return 0;

	buddy_data = ggp_buddy_get_data(purple_blist_find_buddy(
		purple_connection_get_account(gc), who));

	if (buddy_data->blocked)
		return -1;

	im = purple_conversations_find_im_with_account(
		who, purple_connection_get_account(gc));

	gg_msg = ggp_message_format_to_gg(PURPLE_CONVERSATION(im),
		purple_message_get_contents(msg));

	/* TODO: splitting messages */
	if (strlen(gg_msg) > GG_MSG_MAXSIZE) {
		g_free(gg_msg);
		return -E2BIG;
	}

#if GGP_ENABLE_GG11
	succ = (gg_send_message_html(info->session, GG_CLASS_CHAT,
		ggp_str_to_uin(who), (unsigned char *)gg_msg) >= 0);
#else
	{
		gchar *plain = purple_markup_strip_html(gg_msg);
		succ = (gg_send_message(info->session, GG_CLASS_CHAT,
			ggp_str_to_uin(who), (unsigned char *)plain) >= 0);
		g_free(plain);
	}
#endif

	g_free(gg_msg);

	return succ ? 1 : -1;
}

mercurial