3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * Component written by Tomek Wasilczyk (http://www.wasilczyk.pl).
9 * This file is dual-licensed under the GPL2+ and the X11 (MIT) licences.
10 * As a recipient of this file you may choose, which license to receive the
11 * code under. As a contributor, you have to ensure the new code is
12 * compatible with both.
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "message-prpl.h"
31 #include <glibcompat.h>
32 #include <image-store.h>
39 #define GGP_GG10_DEFAULT_FORMAT "<span style=\"color:#000000; " \
40 "font-family:'MS Shell Dlg 2'; font-size:9pt; \">"
41 #define GGP_GG10_DEFAULT_FORMAT_REPLACEMENT "<span>"
42 #define GGP_GG11_FORCE_COMPAT FALSE
48 GGP_MESSAGE_GOT_TYPE_IM
,
49 GGP_MESSAGE_GOT_TYPE_CHAT
,
50 GGP_MESSAGE_GOT_TYPE_MULTILOGON
59 } ggp_message_got_data
;
65 } ggp_message_global_data
;
67 static ggp_message_global_data global_data
;
69 struct _ggp_message_session_data
81 static ggp_font
* ggp_font_new(void);
82 static ggp_font
* ggp_font_clone(ggp_font
*font
);
83 static void ggp_font_free(gpointer font
);
85 static PurpleIMConversation
* ggp_message_get_conv(PurpleConnection
*gc
,
87 static void ggp_message_got_data_free(ggp_message_got_data
*msg
);
88 static void ggp_message_got_display(PurpleConnection
*gc
,
89 ggp_message_got_data
*msg
);
90 static void ggp_message_format_from_gg(ggp_message_got_data
*msg
,
95 void ggp_message_setup_global(void)
97 global_data
.re_html_tag
= g_regex_new(
98 "<(/)?([a-zA-Z]+)( [^>]+)?>",
99 G_REGEX_OPTIMIZE
, 0, NULL
);
100 global_data
.re_gg_img
= g_regex_new(
101 "<img name=\"([0-9a-fA-F]+)\"/?>",
102 G_REGEX_OPTIMIZE
, 0, NULL
);
105 void ggp_message_cleanup_global(void)
107 g_regex_unref(global_data
.re_html_tag
);
108 g_regex_unref(global_data
.re_gg_img
);
111 static inline ggp_message_session_data
*
112 ggp_message_get_sdata(PurpleConnection
*gc
)
114 GGPInfo
*accdata
= purple_connection_get_protocol_data(gc
);
115 return accdata
->message_data
;
118 void ggp_message_setup(PurpleConnection
*gc
)
120 GGPInfo
*accdata
= purple_connection_get_protocol_data(gc
);
121 ggp_message_session_data
*sdata
= g_new0(ggp_message_session_data
, 1);
123 accdata
->message_data
= sdata
;
126 void ggp_message_cleanup(PurpleConnection
*gc
)
128 ggp_message_session_data
*sdata
= ggp_message_get_sdata(gc
);
133 static ggp_font
* ggp_font_new(void)
137 font
= g_new0(ggp_font
, 1);
144 static ggp_font
* ggp_font_clone(ggp_font
* font
)
146 ggp_font
*clone
= g_new0(ggp_font
, 1);
149 clone
->face
= g_strdup(font
->face
);
154 static void ggp_font_free(gpointer _font
)
156 ggp_font
*font
= _font
;
164 static PurpleIMConversation
* ggp_message_get_conv(PurpleConnection
*gc
,
167 PurpleAccount
*account
= purple_connection_get_account(gc
);
168 PurpleIMConversation
*im
;
169 const gchar
*who
= ggp_uin_to_str(uin
);
171 im
= purple_conversations_find_im_with_account(who
, account
);
174 im
= purple_im_conversation_new(account
, who
);
178 static void ggp_message_got_data_free(ggp_message_got_data
*msg
)
184 void ggp_message_got(PurpleConnection
*gc
, const struct gg_event_msg
*ev
)
186 ggp_message_got_data
*msg
= g_new0(ggp_message_got_data
, 1);
189 msg
->time
= ev
->time
;
190 msg
->user
= ev
->sender
;
192 if (ev
->chat_id
!= 0) {
193 msg
->type
= GGP_MESSAGE_GOT_TYPE_CHAT
;
194 msg
->chat_id
= ev
->chat_id
;
196 msg
->type
= GGP_MESSAGE_GOT_TYPE_IM
;
199 ggp_message_format_from_gg(msg
, ev
->xhtml_message
);
201 ggp_message_got_display(gc
, msg
);
202 ggp_message_got_data_free(msg
);
205 void ggp_message_got_multilogon(PurpleConnection
*gc
,
206 const struct gg_event_msg
*ev
)
208 ggp_message_got_data
*msg
= g_new0(ggp_message_got_data
, 1);
211 msg
->time
= ev
->time
;
212 msg
->user
= ev
->sender
; /* not really a sender*/
214 if (ev
->chat_id
!= 0) {
215 msg
->type
= GGP_MESSAGE_GOT_TYPE_CHAT
;
216 msg
->chat_id
= ev
->chat_id
;
218 msg
->type
= GGP_MESSAGE_GOT_TYPE_MULTILOGON
;
221 ggp_message_format_from_gg(msg
, ev
->xhtml_message
);
223 ggp_message_got_display(gc
, msg
);
224 ggp_message_got_data_free(msg
);
227 static void ggp_message_got_display(PurpleConnection
*gc
,
228 ggp_message_got_data
*msg
)
230 if (msg
->type
== GGP_MESSAGE_GOT_TYPE_IM
) {
231 purple_serv_got_im(gc
, ggp_uin_to_str(msg
->user
), msg
->text
,
232 PURPLE_MESSAGE_RECV
, msg
->time
);
233 } else if (msg
->type
== GGP_MESSAGE_GOT_TYPE_CHAT
) {
234 ggp_chat_got_message(gc
, msg
->chat_id
, msg
->text
, msg
->time
,
236 } else if (msg
->type
== GGP_MESSAGE_GOT_TYPE_MULTILOGON
) {
237 PurpleIMConversation
*im
= ggp_message_get_conv(gc
, msg
->user
);
240 pmsg
= purple_message_new_outgoing(NULL
, msg
->text
, 0);
241 purple_message_set_time(pmsg
, msg
->time
);
243 purple_conversation_write_message(PURPLE_CONVERSATION(im
), pmsg
);
245 purple_debug_error("gg", "ggp_message_got_display: "
246 "unexpected message type: %d\n", msg
->type
);
249 static gboolean
ggp_message_format_from_gg_found_img(const GMatchInfo
*info
,
250 GString
*res
, gpointer data
)
252 ggp_message_got_data
*msg
= data
;
253 gchar
*name
, *replacement
;
258 name
= g_match_info_fetch(info
, 1);
259 if (sscanf(name
, "%" G_GINT64_MODIFIER
"x", &id
) != 1)
263 /* TODO: stock broken image? */
264 g_string_append_printf(res
, "[%s]", _("broken image"));
268 image
= ggp_image_request(msg
->gc
, msg
->user
, id
);
270 purple_debug_warning("gg", "ggp_message_format_from_gg_"
271 "found_img: couldn't request image");
272 g_string_append_printf(res
, "[%s]", _("broken image"));
276 image_id
= purple_image_store_add_weak(image
);
277 replacement
= g_strdup_printf("<img src=\""
278 PURPLE_IMAGE_STORE_PROTOCOL
"%u\">", image_id
);
279 g_string_append(res
, replacement
);
285 static void ggp_message_format_from_gg(ggp_message_got_data
*msg
,
288 gchar
*text_new
, *tmp
;
291 msg
->text
= g_strdup("");
295 text_new
= g_strdup(text
);
296 purple_str_strip_char(text_new
, '\r');
299 text_new
= purple_strreplace(text_new
, GGP_GG10_DEFAULT_FORMAT
,
300 GGP_GG10_DEFAULT_FORMAT_REPLACEMENT
);
304 text_new
= g_regex_replace_eval(global_data
.re_gg_img
, text_new
, -1, 0,
305 0, ggp_message_format_from_gg_found_img
, msg
, NULL
);
308 msg
->text
= text_new
;
311 gchar
* ggp_message_format_to_gg(PurpleConversation
*conv
, const gchar
*text
)
313 gchar
*text_new
, *tmp
;
314 GList
*rt
= NULL
; /* reformatted text */
317 GList
*pending_objects
= NULL
;
318 GList
*font_stack
= NULL
;
319 static int html_sizes_pt
[7] = { 7, 8, 9, 10, 12, 14, 16 };
321 ggp_font
*font_new
, *font_current
, *font_base
;
322 gboolean font_changed
= FALSE
;
323 gboolean in_any_tag
= FALSE
;
325 if (purple_debug_is_verbose())
326 purple_debug_info("gg", "ggp formatting text: [%s]", text
);
329 font_base
= ggp_font_new();
330 font_current
= ggp_font_new();
331 font_new
= ggp_font_new();
333 /* GG11 doesn't use nbsp, it just print spaces */
334 text_new
= purple_strreplace(text
, " ", " ");
336 /* add end-of-message tag */
337 if (strstr(text_new
, "<eom>") != NULL
) {
339 text_new
= purple_strreplace(text_new
, "<eom>", "");
341 purple_debug_warning("gg", "ggp_message_format_to_gg: "
342 "unexpected <eom> tag\n");
345 text_new
= g_strdup_printf("%s<eom></eom>", text_new
);
348 g_regex_match(global_data
.re_html_tag
, text_new
, 0, &match
);
349 while (g_match_info_matches(match
)) {
350 int m_start
, m_end
, m_pos
;
352 gchar
*tag_str
, *attribs_str
;
354 gboolean text_before
;
356 /* reading tag and its contents */
357 g_match_info_fetch_pos(match
, 0, &m_start
, &m_end
);
358 g_assert(m_start
>= 0 && m_end
>= 0);
359 text_before
= ((guint
)m_start
> pos
);
360 g_match_info_fetch_pos(match
, 1, &m_pos
, NULL
);
361 tag_close
= (m_pos
>= 0);
362 tag_str
= g_match_info_fetch(match
, 2);
363 tag
= ggp_html_parse_tag(tag_str
);
364 attribs_str
= g_match_info_fetch(match
, 3);
365 g_match_info_next(match
, NULL
);
367 if (tag
== GGP_HTML_TAG_UNKNOWN
) {
368 purple_debug_warning("gg", "ggp_message_format_to_gg: "
369 "uknown tag %s\n", tag_str
);
372 /* closing *all* formatting-related tags (GG11 weirness)
373 * and adding pending objects */
374 if ((text_before
&& (font_changed
|| pending_objects
)) ||
375 (tag
== GGP_HTML_TAG_EOM
&& tag_close
))
377 font_changed
= FALSE
;
380 if (font_current
->s
&& !GGP_GG11_FORCE_COMPAT
)
381 rt
= g_list_prepend(rt
,
384 rt
= g_list_prepend(rt
,
387 rt
= g_list_prepend(rt
,
390 rt
= g_list_prepend(rt
,
392 rt
= g_list_prepend(rt
, g_strdup("</span>"));
394 if (pending_objects
) {
395 rt
= g_list_concat(pending_objects
, rt
);
396 pending_objects
= NULL
;
400 /* opening formatting-related tags again */
401 if (text_before
&& !in_any_tag
) {
403 GList
*styles
= NULL
;
404 gboolean has_size
= (font_new
->size
> 0 &&
405 font_new
->size
<= 7 && font_new
->size
!= 3);
408 styles
= g_list_append(styles
, g_strdup_printf(
410 html_sizes_pt
[font_new
->size
- 1]));
412 styles
= g_list_append(styles
, g_strdup_printf(
413 "font-family:%s;", font_new
->face
));
414 if (font_new
->bgcolor
>= 0 && !GGP_GG11_FORCE_COMPAT
)
415 styles
= g_list_append(styles
, g_strdup_printf(
416 "background-color:#%06x;",
418 if (font_new
->color
>= 0)
419 styles
= g_list_append(styles
, g_strdup_printf(
420 "color:#%06x;", font_new
->color
));
423 gchar
*combined
= ggp_strjoin_list(" ", styles
);
424 g_list_free_full(styles
, g_free
);
425 style
= g_strdup_printf(" style=\"%s\"",
429 style
= g_strdup("");
430 rt
= g_list_prepend(rt
, g_strdup_printf("<span%s>",
435 rt
= g_list_prepend(rt
, g_strdup("<b>"));
437 rt
= g_list_prepend(rt
, g_strdup("<i>"));
439 rt
= g_list_prepend(rt
, g_strdup("<u>"));
440 if (font_new
->s
&& !GGP_GG11_FORCE_COMPAT
)
441 rt
= g_list_prepend(rt
, g_strdup("<s>"));
443 ggp_font_free(font_current
);
444 font_current
= font_new
;
445 font_new
= ggp_font_clone(font_current
);
450 rt
= g_list_prepend(rt
,
451 g_strndup(text_new
+ pos
, m_start
- pos
));
454 /* set formatting of a following text */
455 if (tag
== GGP_HTML_TAG_B
) {
456 font_changed
|= (font_new
->b
!= !tag_close
);
457 font_new
->b
= !tag_close
;
458 } else if (tag
== GGP_HTML_TAG_I
) {
459 font_changed
|= (font_new
->i
!= !tag_close
);
460 font_new
->i
= !tag_close
;
461 } else if (tag
== GGP_HTML_TAG_U
) {
462 font_changed
|= (font_new
->u
!= !tag_close
);
463 font_new
->u
= !tag_close
;
464 } else if (tag
== GGP_HTML_TAG_S
) {
465 font_changed
|= (font_new
->s
!= !tag_close
);
466 font_new
->s
= !tag_close
;
467 } else if (tag
== GGP_HTML_TAG_IMG
&& !tag_close
) {
468 GHashTable
*attribs
= ggp_html_tag_attribs(attribs_str
);
471 ggp_image_prepare_result res
= -1;
472 PurpleImage
*image
= NULL
;
474 val
= g_hash_table_lookup(attribs
, "src");
476 image
= purple_image_store_get_from_uri(val
);
479 res
= ggp_image_prepare(conv
, image
, &id
);
481 if (res
== GGP_IMAGE_PREPARE_OK
) {
482 pending_objects
= g_list_prepend(
483 pending_objects
, g_strdup_printf(
484 "<img name=\"" GGP_IMAGE_ID_FORMAT
486 } else if (res
== GGP_IMAGE_PREPARE_TOO_BIG
) {
487 purple_conversation_write_system_message(conv
,
488 _("Image is too large, please try "
489 "smaller one."), PURPLE_MESSAGE_ERROR
);
491 purple_conversation_write_system_message(conv
,
492 _("Image cannot be sent."),
493 PURPLE_MESSAGE_ERROR
);
496 g_hash_table_destroy(attribs
);
497 } else if (tag
== GGP_HTML_TAG_FONT
&& !tag_close
) {
498 GHashTable
*attribs
= ggp_html_tag_attribs(attribs_str
);
501 font_stack
= g_list_prepend(font_stack
,
502 ggp_font_clone(font_new
));
504 if ((val
= g_hash_table_lookup(attribs
, "size")) != NULL
505 && val
[0] >= '1' && val
[0] <= '7' &&
508 int size
= val
[0] - '0';
509 font_changed
|= (font_new
->size
!= size
);
510 font_new
->size
= size
;
513 if ((val
= g_hash_table_lookup(attribs
, "face"))
517 (g_strcmp0(font_new
->face
, val
) != 0);
518 g_free(font_new
->face
);
519 font_new
->face
= g_strdup(val
);
522 if ((val
= g_hash_table_lookup(attribs
, "color"))
523 != NULL
&& val
[0] == '#' && strlen(val
) == 7)
525 int color
= ggp_html_decode_color(val
);
526 font_changed
|= (font_new
->color
!= color
);
527 font_new
->color
= color
;
530 g_hash_table_destroy(attribs
);
532 else if ((tag
== GGP_HTML_TAG_SPAN
|| tag
== GGP_HTML_TAG_DIV
)
535 GHashTable
*attribs
, *styles
= NULL
;
539 attribs
= ggp_html_tag_attribs(attribs_str
);
541 font_stack
= g_list_prepend(font_stack
,
542 ggp_font_clone(font_new
));
543 if (tag
== GGP_HTML_TAG_DIV
)
544 pending_objects
= g_list_prepend(
545 pending_objects
, g_strdup("<br>"));
547 style
= g_hash_table_lookup(attribs
, "style");
549 styles
= ggp_html_css_attribs(style
);
551 if (styles
&& (val
= g_hash_table_lookup(styles
,
552 "background-color")) != NULL
)
554 int color
= ggp_html_decode_color(val
);
555 font_changed
|= (font_new
->bgcolor
!= color
);
556 font_new
->bgcolor
= color
;
559 if (styles
&& (val
= g_hash_table_lookup(styles
,
562 int color
= ggp_html_decode_color(val
);
563 font_changed
|= (font_new
->color
!= color
);
564 font_new
->color
= color
;
568 g_hash_table_destroy(styles
);
569 g_hash_table_destroy(attribs
);
571 else if ((tag
== GGP_HTML_TAG_FONT
|| tag
== GGP_HTML_TAG_SPAN
572 || tag
== GGP_HTML_TAG_DIV
) && tag_close
)
576 ggp_font_free(font_new
);
578 font_new
= (ggp_font
*)font_stack
->data
;
579 font_stack
= g_list_delete_link(
580 font_stack
, font_stack
);
583 font_new
= ggp_font_clone(font_base
);
584 } else if (tag
== GGP_HTML_TAG_BR
) {
585 pending_objects
= g_list_prepend(pending_objects
,
587 } else if (tag
== GGP_HTML_TAG_HR
) {
588 pending_objects
= g_list_prepend(pending_objects
,
589 g_strdup("<br><span>---</span><br>"));
590 } else if (tag
== GGP_HTML_TAG_A
|| tag
== GGP_HTML_TAG_EOM
) {
592 } else if (tag
== GGP_HTML_TAG_UNKNOWN
) {
593 purple_debug_warning("gg", "ggp_message_format_to_gg: "
594 "uknown tag %s\n", tag_str
);
596 purple_debug_error("gg", "ggp_message_format_to_gg: "
597 "not handled tag %s\n", tag_str
);
604 g_match_info_free(match
);
606 if (pos
< strlen(text_new
) || in_any_tag
) {
607 purple_debug_fatal("gg", "ggp_message_format_to_gg: "
608 "end of message not reached\n");
611 /* releasing fonts recources */
612 ggp_font_free(font_new
);
613 ggp_font_free(font_current
);
614 ggp_font_free(font_base
);
615 g_list_free_full(font_stack
, ggp_font_free
);
617 /* combining reformatted text info one string */
618 rt
= g_list_reverse(rt
);
620 text_new
= ggp_strjoin_list("", rt
);
621 g_list_free_full(rt
, g_free
);
623 if (purple_debug_is_verbose())
624 purple_debug_info("gg", "reformatted text: [%s]", text_new
);
629 int ggp_message_send_im(PurpleConnection
*gc
, PurpleMessage
*msg
)
631 GGPInfo
*info
= purple_connection_get_protocol_data(gc
);
632 PurpleIMConversation
*im
;
633 ggp_buddy_data
*buddy_data
;
636 const gchar
*rcpt
= purple_message_get_recipient(msg
);
638 /* TODO: return -ENOTCONN, if not connected */
640 if (purple_message_is_empty(msg
))
643 buddy_data
= ggp_buddy_get_data(purple_blist_find_buddy(
644 purple_connection_get_account(gc
), rcpt
));
646 if (buddy_data
->blocked
)
649 im
= purple_conversations_find_im_with_account(
650 rcpt
, purple_connection_get_account(gc
));
652 gg_msg
= ggp_message_format_to_gg(PURPLE_CONVERSATION(im
),
653 purple_message_get_contents(msg
));
655 /* TODO: splitting messages */
656 if (strlen(gg_msg
) > GG_MSG_MAXSIZE
) {
661 succ
= (gg_send_message_html(info
->session
, GG_CLASS_CHAT
,
662 ggp_str_to_uin(rcpt
), (unsigned char *)gg_msg
) >= 0);
666 return succ
? 1 : -1;