mark PurpleImageClass as private
[pidgin-git.git] / libpurple / protocols / gg / message-prpl.c
blobf728028c19b34adbdcb769b9594992bb19d92ae9
1 /* purple
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
5 * source distribution.
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"
30 #include <debug.h>
31 #include <glibcompat.h>
32 #include <image-store.h>
34 #include "gg.h"
35 #include "chat.h"
36 #include "utils.h"
37 #include "html.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
44 typedef struct
46 enum
48 GGP_MESSAGE_GOT_TYPE_IM,
49 GGP_MESSAGE_GOT_TYPE_CHAT,
50 GGP_MESSAGE_GOT_TYPE_MULTILOGON
51 } type;
53 uin_t user;
54 gchar *text;
55 time_t time;
56 uint64_t chat_id;
58 PurpleConnection *gc;
59 } ggp_message_got_data;
61 typedef struct
63 GRegex *re_html_tag;
64 GRegex *re_gg_img;
65 } ggp_message_global_data;
67 static ggp_message_global_data global_data;
69 struct _ggp_message_session_data
73 typedef struct
75 int size;
76 gchar *face;
77 int color, bgcolor;
78 gboolean b, i, u, s;
79 } ggp_font;
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,
86 uin_t uin);
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,
91 const gchar *text);
93 /**************/
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);
130 g_free(sdata);
133 static ggp_font * ggp_font_new(void)
135 ggp_font *font;
137 font = g_new0(ggp_font, 1);
138 font->color = -1;
139 font->bgcolor = -1;
141 return font;
144 static ggp_font * ggp_font_clone(ggp_font * font)
146 ggp_font *clone = g_new0(ggp_font, 1);
148 *clone = *font;
149 clone->face = g_strdup(font->face);
151 return clone;
154 static void ggp_font_free(gpointer _font)
156 ggp_font *font = _font;
158 g_free(font->face);
159 g_free(font);
162 /**/
164 static PurpleIMConversation * ggp_message_get_conv(PurpleConnection *gc,
165 uin_t uin)
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);
172 if (im)
173 return im;
174 im = purple_im_conversation_new(account, who);
175 return im;
178 static void ggp_message_got_data_free(ggp_message_got_data *msg)
180 g_free(msg->text);
181 g_free(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);
188 msg->gc = gc;
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;
195 } else {
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);
210 msg->gc = gc;
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;
217 } else {
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,
235 msg->user);
236 } else if (msg->type == GGP_MESSAGE_GOT_TYPE_MULTILOGON) {
237 PurpleIMConversation *im = ggp_message_get_conv(gc, msg->user);
238 PurpleMessage *pmsg;
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);
244 } else
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;
254 int64_t id;
255 PurpleImage *image;
256 guint image_id;
258 name = g_match_info_fetch(info, 1);
259 if (sscanf(name, "%" G_GINT64_MODIFIER "x", &id) != 1)
260 id = 0;
261 g_free(name);
262 if (!id) {
263 /* TODO: stock broken image? */
264 g_string_append_printf(res, "[%s]", _("broken image"));
265 return FALSE;
268 image = ggp_image_request(msg->gc, msg->user, id);
269 if (!image) {
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"));
273 return FALSE;
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);
280 g_free(replacement);
282 return FALSE;
285 static void ggp_message_format_from_gg(ggp_message_got_data *msg,
286 const gchar *text)
288 gchar *text_new, *tmp;
290 if (text == NULL) {
291 msg->text = g_strdup("");
292 return;
295 text_new = g_strdup(text);
296 purple_str_strip_char(text_new, '\r');
298 tmp = text_new;
299 text_new = purple_strreplace(text_new, GGP_GG10_DEFAULT_FORMAT,
300 GGP_GG10_DEFAULT_FORMAT_REPLACEMENT);
301 g_free(tmp);
303 tmp = text_new;
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);
306 g_free(tmp);
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 */
315 GMatchInfo *match;
316 guint pos = 0;
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);
328 /* default font */
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, "&nbsp;", " ");
336 /* add end-of-message tag */
337 if (strstr(text_new, "<eom>") != NULL) {
338 tmp = text_new;
339 text_new = purple_strreplace(text_new, "<eom>", "");
340 g_free(tmp);
341 purple_debug_warning("gg", "ggp_message_format_to_gg: "
342 "unexpected <eom> tag\n");
344 tmp = text_new;
345 text_new = g_strdup_printf("%s<eom></eom>", text_new);
346 g_free(tmp);
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;
351 gboolean tag_close;
352 gchar *tag_str, *attribs_str;
353 ggp_html_tag tag;
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;
378 if (in_any_tag) {
379 in_any_tag = FALSE;
380 if (font_current->s && !GGP_GG11_FORCE_COMPAT)
381 rt = g_list_prepend(rt,
382 g_strdup("</s>"));
383 if (font_current->u)
384 rt = g_list_prepend(rt,
385 g_strdup("</u>"));
386 if (font_current->i)
387 rt = g_list_prepend(rt,
388 g_strdup("</i>"));
389 if (font_current->b)
390 rt = g_list_prepend(rt,
391 g_strdup("</b>"));
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) {
402 gchar *style;
403 GList *styles = NULL;
404 gboolean has_size = (font_new->size > 0 &&
405 font_new->size <= 7 && font_new->size != 3);
407 if (has_size)
408 styles = g_list_append(styles, g_strdup_printf(
409 "font-size:%dpt;",
410 html_sizes_pt[font_new->size - 1]));
411 if (font_new->face)
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;",
417 font_new->bgcolor));
418 if (font_new->color >= 0)
419 styles = g_list_append(styles, g_strdup_printf(
420 "color:#%06x;", font_new->color));
422 if (styles) {
423 gchar *combined = ggp_strjoin_list(" ", styles);
424 g_list_free_full(styles, g_free);
425 style = g_strdup_printf(" style=\"%s\"",
426 combined);
427 g_free(combined);
428 } else
429 style = g_strdup("");
430 rt = g_list_prepend(rt, g_strdup_printf("<span%s>",
431 style));
432 g_free(style);
434 if (font_new->b)
435 rt = g_list_prepend(rt, g_strdup("<b>"));
436 if (font_new->i)
437 rt = g_list_prepend(rt, g_strdup("<i>"));
438 if (font_new->u)
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);
447 in_any_tag = TRUE;
449 if (text_before) {
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);
469 gchar *val = NULL;
470 uint64_t id;
471 ggp_image_prepare_result res = -1;
472 PurpleImage *image = NULL;
474 val = g_hash_table_lookup(attribs, "src");
475 if (val)
476 image = purple_image_store_get_from_uri(val);
478 if (image != NULL)
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
485 "\">", id));
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);
490 } else {
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);
499 gchar *val = NULL;
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' &&
506 val[1] == '\0')
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"))
514 != NULL)
516 font_changed |=
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)
533 && !tag_close)
535 GHashTable *attribs, *styles = NULL;
536 gchar *style = NULL;
537 gchar *val = 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");
548 if (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,
560 "color")) != NULL)
562 int color = ggp_html_decode_color(val);
563 font_changed |= (font_new->color != color);
564 font_new->color = color;
567 if (styles)
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)
574 font_changed = TRUE;
576 ggp_font_free(font_new);
577 if (font_stack) {
578 font_new = (ggp_font*)font_stack->data;
579 font_stack = g_list_delete_link(
580 font_stack, font_stack);
582 else
583 font_new = ggp_font_clone(font_base);
584 } else if (tag == GGP_HTML_TAG_BR) {
585 pending_objects = g_list_prepend(pending_objects,
586 g_strdup("<br>"));
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) {
591 /* do nothing */
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);
595 } else {
596 purple_debug_error("gg", "ggp_message_format_to_gg: "
597 "not handled tag %s\n", tag_str);
600 pos = m_end;
601 g_free(tag_str);
602 g_free(attribs_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);
619 g_free(text_new);
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);
626 return 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;
634 gchar *gg_msg;
635 gboolean succ;
636 const gchar *rcpt = purple_message_get_recipient(msg);
638 /* TODO: return -ENOTCONN, if not connected */
640 if (purple_message_is_empty(msg))
641 return 0;
643 buddy_data = ggp_buddy_get_data(purple_blist_find_buddy(
644 purple_connection_get_account(gc), rcpt));
646 if (buddy_data->blocked)
647 return -1;
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) {
657 g_free(gg_msg);
658 return -E2BIG;
661 succ = (gg_send_message_html(info->session, GG_CLASS_CHAT,
662 ggp_str_to_uin(rcpt), (unsigned char *)gg_msg) >= 0);
664 g_free(gg_msg);
666 return succ ? 1 : -1;