Tue, 04 Feb 2014 00:30:47 +0100
Style: fix gg braces (part 1)
/* 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 "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 #define GGP_IMAGE_REPLACEMENT "<img id=\"gg-pending-image-" GGP_IMAGE_ID_FORMAT "\">" #define GGP_IMAGE_DESTINATION "<img src=\"" PURPLE_STORED_IMAGE_PROTOCOL "%u\">" 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; GList *pending_images; 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 { GList *pending_messages; }; 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 gboolean ggp_message_request_images(PurpleConnection *gc, 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_list_free_full(sdata->pending_messages, (GDestroyNotify)ggp_message_got_data_free); 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_list_free_full(msg->pending_images, g_free); g_free(msg->text); g_free(msg); } static void ggp_message_request_images_got(PurpleConnection *gc, uint64_t id, int stored_id, gpointer _msg) { ggp_message_session_data *sdata = ggp_message_get_sdata(gc); ggp_message_got_data *msg = _msg; GList *m_it, *i_it; gchar *tmp, *tag_search, *tag_replace; m_it = g_list_find(sdata->pending_messages, msg); if (!m_it) { purple_debug_error("gg", "ggp_message_request_images_got: " "message %p is not in queue\n", msg); return; } i_it = g_list_find_custom(msg->pending_images, &id, ggp_int64_compare); if (!i_it) { purple_debug_error("gg", "ggp_message_request_images_got: " "image " GGP_IMAGE_ID_FORMAT " is not present in this " "message\n", id); return; } tag_search = g_strdup_printf(GGP_IMAGE_REPLACEMENT, id); tag_replace = g_strdup_printf(GGP_IMAGE_DESTINATION, stored_id); tmp = msg->text; msg->text = purple_strreplace(msg->text, tag_search, tag_replace); g_free(tmp); g_free(tag_search); g_free(tag_replace); g_free(i_it->data); msg->pending_images = g_list_delete_link(msg->pending_images, i_it); if (msg->pending_images != NULL) { purple_debug_info("gg", "ggp_message_request_images_got: " "got image " GGP_IMAGE_ID_FORMAT ", but the message " "contains more of them\n", id); return; } ggp_message_got_display(gc, msg); ggp_message_got_data_free(msg); sdata->pending_messages = g_list_delete_link(sdata->pending_messages, m_it); } static gboolean ggp_message_request_images(PurpleConnection *gc, ggp_message_got_data *msg) { ggp_message_session_data *sdata = ggp_message_get_sdata(gc); GList *it; if (msg->pending_images == NULL) return FALSE; it = msg->pending_images; while (it) { ggp_image_request(gc, msg->user, *(uint64_t*)it->data, ggp_message_request_images_got, msg); it = g_list_next(it); } sdata->pending_messages = g_list_append(sdata->pending_messages, msg); return TRUE; } 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); if (ggp_message_request_images(gc, msg)) return; 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); if (ggp_message_request_images(gc, msg)) return; 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) { 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); const gchar *me = purple_account_get_username( purple_connection_get_account(gc)); purple_conversation_write(PURPLE_CONVERSATION(im), me, msg->text, PURPLE_MESSAGE_SEND, msg->time); } 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; int stored_id; name = g_match_info_fetch(info, 1); if (sscanf(name, "%" G_GINT64_MODIFIER "x", &id) != 1) id = 0; g_free(name); if (!id) { g_string_append(res, "["); g_string_append(res, _("broken image")); g_string_append(res, "]"); return FALSE; } stored_id = ggp_image_get_cached(msg->gc, id); if (stored_id > 0) { purple_debug_info("gg", "ggp_message_format_from_gg_found_img: " "getting image " GGP_IMAGE_ID_FORMAT " from cache\n", id); replacement = g_strdup_printf(GGP_IMAGE_DESTINATION, stored_id); g_string_append(res, replacement); g_free(replacement); return FALSE; } if (NULL == g_list_find_custom(msg->pending_images, &id, ggp_int64_compare)) { msg->pending_images = g_list_append(msg->pending_images, ggp_uint64dup(id)); } replacement = g_strdup_printf(GGP_IMAGE_REPLACEMENT, 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; /* TODO: verbose * purple_debug_info("gg", "ggp formatting text: [%s]\n", 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, " ", " "); /* 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; int stored_id = -1; ggp_image_prepare_result res = -1; if ((val = g_hash_table_lookup(attribs, "src")) != NULL && g_str_has_prefix(val, PURPLE_STORED_IMAGE_PROTOCOL)) { val += strlen(PURPLE_STORED_IMAGE_PROTOCOL); if (sscanf(val, "%u", &stored_id) != 1) stored_id = -1; } if (stored_id >= 0) res = ggp_image_prepare(conv, stored_id, &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(conv, "", _("Image is too large, please try " "smaller one."), PURPLE_MESSAGE_ERROR, time(NULL)); } else { purple_conversation_write(conv, "", _("Image cannot be sent."), PURPLE_MESSAGE_ERROR, time(NULL)); } 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 ((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 ((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); /* TODO: verbose * purple_debug_info("gg", "reformatted text: [%s]\n", text_new); */ return text_new; } int ggp_message_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) { GGPInfo *info = purple_connection_get_protocol_data(gc); PurpleIMConversation *im; ggp_buddy_data *buddy_data; gchar *gg_msg; gboolean succ; /* TODO: return -ENOTCONN, if not connected */ if (message == NULL || message[0] == '\0') 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), message); /* 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; }