* 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 * 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 * 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" #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 GGP_MESSAGE_GOT_TYPE_CHAT, GGP_MESSAGE_GOT_TYPE_MULTILOGON } ggp_message_global_data; static ggp_message_global_data global_data; struct _ggp_message_session_data 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, 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, 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); static ggp_font * ggp_font_new(void) font = g_new0(ggp_font, 1); static ggp_font * ggp_font_clone(ggp_font * font) ggp_font *clone = g_new0(ggp_font, 1); clone->face = g_strdup(font->face); static void ggp_font_free(gpointer _font) static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc, 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); im = purple_im_conversation_new(account, who); static void ggp_message_got_data_free(ggp_message_got_data *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->type = GGP_MESSAGE_GOT_TYPE_CHAT; msg->chat_id = ev->chat_id; 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->user = ev->sender; /* not really a sender*/ msg->type = GGP_MESSAGE_GOT_TYPE_CHAT; msg->chat_id = ev->chat_id; 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); } else if (msg->type == GGP_MESSAGE_GOT_TYPE_CHAT) { ggp_chat_got_message(gc, msg->chat_id, msg->text, msg->time, } else if (msg->type == GGP_MESSAGE_GOT_TYPE_MULTILOGON) { PurpleIMConversation *im = ggp_message_get_conv(gc, msg->user); pmsg = purple_message_new_outgoing(NULL, msg->text, 0); purple_message_set_time(pmsg, msg->time); purple_conversation_write_message(PURPLE_CONVERSATION(im), pmsg); 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; name = g_match_info_fetch(info, 1); if (sscanf(name, "%" G_GINT64_MODIFIER "x", &id) != 1) /* TODO: stock broken image? */ g_string_append_printf(res, "[%s]", _("broken image")); image = ggp_image_request(msg->gc, msg->user, id); purple_debug_warning("gg", "ggp_message_format_from_gg_" "found_img: couldn't request image"); g_string_append_printf(res, "[%s]", _("broken image")); 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); static void ggp_message_format_from_gg(ggp_message_got_data *msg, msg->text = g_strdup(""); text_new = g_strdup(text); purple_str_strip_char(text_new, '\r'); text_new = purple_strreplace(text_new, GGP_GG10_DEFAULT_FORMAT, GGP_GG10_DEFAULT_FORMAT_REPLACEMENT); 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); gchar * ggp_message_format_to_gg(PurpleConversation *conv, const gchar *text) GList *rt = NULL; /* reformatted text */ 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); 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, " ", " "); /* add end-of-message tag */ if (strstr(text_new, "<eom>") != NULL) { text_new = purple_strreplace(text_new, "<eom>", ""); purple_debug_warning("gg", "ggp_message_format_to_gg: " "unexpected <eom> tag\n"); text_new = g_strdup_printf("%s<eom></eom>", text_new); 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; gchar *tag_str, *attribs_str; /* 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)) if (font_current->s && !GGP_GG11_FORCE_COMPAT) rt = g_list_prepend(rt, g_strdup("</span>")); rt = g_list_concat(pending_objects, rt); /* opening formatting-related tags again */ if (text_before && !in_any_tag) { gboolean has_size = (font_new->size > 0 && font_new->size <= 7 && font_new->size != 3); styles = g_list_append(styles, g_strdup_printf( html_sizes_pt[font_new->size - 1])); 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;", if (font_new->color >= 0) styles = g_list_append(styles, g_strdup_printf( "color:#%06x;", font_new->color)); gchar *combined = ggp_strjoin_list(" ", styles); g_list_free_full(styles, g_free); style = g_strdup_printf(" style=\"%s\"", rt = g_list_prepend(rt, g_strdup_printf("<span%s>", rt = g_list_prepend(rt, g_strdup("<b>")); rt = g_list_prepend(rt, g_strdup("<i>")); 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_new = ggp_font_clone(font_current); 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); ggp_image_prepare_result res = -1; PurpleImage *image = NULL; val = g_hash_table_lookup(attribs, "src"); image = purple_image_store_get_from_uri(val); 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 } 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); purple_conversation_write_system_message(conv, _("Image cannot be sent."), g_hash_table_destroy(attribs); } else if (tag == GGP_HTML_TAG_FONT && !tag_close) { GHashTable *attribs = ggp_html_tag_attribs(attribs_str); 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' && font_changed |= (font_new->size != size); if ((val = g_hash_table_lookup(attribs, "face")) (g_strcmp0(font_new->face, val) != 0); 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); g_hash_table_destroy(attribs); else if ((tag == GGP_HTML_TAG_SPAN || tag == GGP_HTML_TAG_DIV) GHashTable *attribs, *styles = 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"); 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, int color = ggp_html_decode_color(val); font_changed |= (font_new->color != color); 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_new = (ggp_font*)font_stack->data; font_stack = g_list_delete_link( font_new = ggp_font_clone(font_base); } else if (tag == GGP_HTML_TAG_BR) { pending_objects = g_list_prepend(pending_objects, } 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) { } else if (tag == GGP_HTML_TAG_UNKNOWN) { purple_debug_warning("gg", "ggp_message_format_to_gg: " "uknown tag %s\n", tag_str); purple_debug_error("gg", "ggp_message_format_to_gg: " "not handled tag %s\n", tag_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_current); ggp_font_free(font_base); g_list_free_full(font_stack, ggp_font_free); /* combining reformatted text info one string */ 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); int ggp_message_send_im(PurpleConnection *gc, PurpleMessage *msg) GGPInfo *info = purple_connection_get_protocol_data(gc); PurpleIMConversation *im; ggp_buddy_data *buddy_data; const gchar *rcpt = purple_message_get_recipient(msg); /* TODO: return -ENOTCONN, if not connected */ if (purple_message_is_empty(msg)) buddy_data = ggp_buddy_get_data(purple_blist_find_buddy( purple_connection_get_account(gc), rcpt)); im = purple_conversations_find_im_with_account( rcpt, 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) { succ = (gg_send_message_html(info->session, GG_CLASS_CHAT, ggp_str_to_uin(rcpt), (unsigned char *)gg_msg) >= 0);