2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "smiley-custom.h"
28 #include "smiley-parser.h"
31 #include "adhoccommands.h"
35 #include "google/google.h"
44 void jabber_message_free(JabberMessage
*jm
)
54 g_free(jm
->thread_id
);
56 g_list_free(jm
->eventitems
);
61 static void handle_chat(JabberMessage
*jm
)
63 JabberID
*jid
= jabber_id_new(jm
->from
);
66 PurpleAccount
*account
;
68 JabberBuddyResource
*jbr
;
74 account
= purple_connection_get_account(gc
);
76 jb
= jabber_buddy_find(jm
->js
, jm
->from
, TRUE
);
77 jbr
= jabber_buddy_find_resource(jb
, jid
->resource
);
79 if(!jm
->xhtml
&& !jm
->body
) {
80 if (jbr
&& jm
->chat_state
!= JM_STATE_NONE
)
81 jbr
->chat_states
= JABBER_CHAT_STATES_SUPPORTED
;
83 if(JM_STATE_COMPOSING
== jm
->chat_state
) {
84 purple_serv_got_typing(gc
, jm
->from
, 0, PURPLE_IM_TYPING
);
85 } else if(JM_STATE_PAUSED
== jm
->chat_state
) {
86 purple_serv_got_typing(gc
, jm
->from
, 0, PURPLE_IM_TYPED
);
87 } else if(JM_STATE_GONE
== jm
->chat_state
) {
88 PurpleIMConversation
*im
= purple_conversations_find_im_with_account(
90 if (im
&& jid
->node
&& jid
->domain
) {
94 g_snprintf(buf
, sizeof(buf
), "%s@%s", jid
->node
, jid
->domain
);
96 if ((buddy
= purple_blist_find_buddy(account
, buf
))) {
100 who
= purple_buddy_get_alias(buddy
);
101 escaped
= g_markup_escape_text(who
, -1);
103 g_snprintf(buf
, sizeof(buf
),
104 _("%s has left the conversation."), escaped
);
107 /* At some point when we restructure PurpleConversation,
108 * this should be able to be implemented by removing the
109 * user from the conversation like we do with chats now. */
110 purple_conversation_write_system_message(
111 PURPLE_CONVERSATION(im
), buf
, 0);
114 purple_serv_got_typing_stopped(gc
, jm
->from
);
117 purple_serv_got_typing_stopped(gc
, jm
->from
);
122 * We received a message from a specific resource, so
123 * we probably want a reply to go to this specific
124 * resource (i.e. bind/lock the conversation to this
127 * This works because purple_im_conversation_send gets the name
128 * from purple_conversation_get_name()
130 PurpleIMConversation
*im
;
132 im
= purple_conversations_find_im_with_account(jm
->from
, account
);
133 if (im
&& !g_str_equal(jm
->from
,
134 purple_conversation_get_name(PURPLE_CONVERSATION(im
)))) {
135 purple_debug_info("jabber", "Binding conversation to %s\n",
137 purple_conversation_set_name(PURPLE_CONVERSATION(im
), jm
->from
);
142 /* Treat SUPPORTED as a terminal with no escape :) */
143 if (jbr
->chat_states
!= JABBER_CHAT_STATES_SUPPORTED
) {
144 if (jm
->chat_state
!= JM_STATE_NONE
)
145 jbr
->chat_states
= JABBER_CHAT_STATES_SUPPORTED
;
147 jbr
->chat_states
= JABBER_CHAT_STATES_UNSUPPORTED
;
150 g_free(jbr
->thread_id
);
151 jbr
->thread_id
= g_strdup(jbr
->thread_id
);
154 if (jm
->js
->googletalk
&& jm
->xhtml
== NULL
) {
155 char *tmp
= jm
->body
;
156 jm
->body
= jabber_google_format_to_html(jm
->body
);
159 purple_serv_got_im(gc
, jm
->from
, jm
->xhtml
? jm
->xhtml
: jm
->body
, 0, jm
->sent
);
165 static void handle_headline(JabberMessage
*jm
)
171 if(!jm
->xhtml
&& !jm
->body
)
172 return; /* ignore headlines without any content */
174 body
= g_string_new("");
175 title
= g_strdup_printf(_("Message from %s"), jm
->from
);
178 g_string_append(body
, jm
->xhtml
);
180 g_string_append(body
, jm
->body
);
182 for(etc
= jm
->etc
; etc
; etc
= etc
->next
) {
183 PurpleXmlNode
*x
= etc
->data
;
184 const char *xmlns
= purple_xmlnode_get_namespace(x
);
185 if(xmlns
&& !strcmp(xmlns
, NS_OOB_X_DATA
)) {
186 PurpleXmlNode
*url
, *desc
;
187 char *urltxt
, *desctxt
;
189 url
= purple_xmlnode_get_child(x
, "url");
190 desc
= purple_xmlnode_get_child(x
, "desc");
195 urltxt
= purple_xmlnode_get_data(url
);
196 desctxt
= purple_xmlnode_get_data(desc
);
198 /* I'm all about ugly hacks */
199 if(body
->len
&& jm
->body
&& !strcmp(body
->str
, jm
->body
))
200 g_string_printf(body
, "<a href='%s'>%s</a>",
203 g_string_append_printf(body
, "<br/><a href='%s'>%s</a>",
211 purple_notify_formatted(jm
->js
->gc
, title
, jm
->subject
? jm
->subject
: title
,
212 NULL
, body
->str
, NULL
, NULL
);
215 g_string_free(body
, TRUE
);
218 static void handle_groupchat(JabberMessage
*jm
)
220 JabberID
*jid
= jabber_id_new(jm
->from
);
222 PurpleMessageFlags messageFlags
= 0;
227 chat
= jabber_chat_find(jm
->js
, jid
->node
, jid
->domain
);
233 purple_chat_conversation_set_topic(chat
->conv
, jid
->resource
,
235 messageFlags
|= PURPLE_MESSAGE_NO_LOG
;
236 if(!jm
->xhtml
&& !jm
->body
) {
237 char *msg
, *tmp
, *tmp2
;
238 tmp
= g_markup_escape_text(jm
->subject
, -1);
239 tmp2
= purple_markup_linkify(tmp
);
241 msg
= g_strdup_printf(_("%s has set the topic to: %s"), jid
->resource
, tmp2
);
243 msg
= g_strdup_printf(_("The topic is: %s"), tmp2
);
244 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat
->conv
),
252 if(jm
->xhtml
|| jm
->body
) {
254 purple_serv_got_chat_in(jm
->js
->gc
, chat
->id
, jid
->resource
,
255 messageFlags
| (jm
->delayed
? PURPLE_MESSAGE_DELAYED
: 0),
256 jm
->xhtml
? jm
->xhtml
: jm
->body
, jm
->sent
);
258 purple_conversation_write_system_message(
259 PURPLE_CONVERSATION(chat
->conv
),
260 jm
->xhtml
? jm
->xhtml
: jm
->body
, messageFlags
);
266 static void handle_groupchat_invite(JabberMessage
*jm
)
268 GHashTable
*components
;
269 JabberID
*jid
= jabber_id_new(jm
->to
);
274 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
276 g_hash_table_replace(components
, "room", g_strdup(jid
->node
));
277 g_hash_table_replace(components
, "server", g_strdup(jid
->domain
));
278 g_hash_table_replace(components
, "handle", g_strdup(jm
->js
->user
->node
));
279 g_hash_table_replace(components
, "password", g_strdup(jm
->password
));
282 purple_serv_got_chat_invite(jm
->js
->gc
, jm
->to
, jm
->from
, jm
->body
, components
);
285 static void handle_error(JabberMessage
*jm
)
292 buf
= g_strdup_printf(_("Message delivery to %s failed: %s"),
293 jm
->from
, jm
->error
? jm
->error
: "");
295 purple_notify_formatted(jm
->js
->gc
, _("XMPP Message Error"), _("XMPP Message Error"), buf
,
296 jm
->xhtml
? jm
->xhtml
: jm
->body
, NULL
, NULL
);
301 static void handle_buzz(JabberMessage
*jm
) {
302 PurpleAccount
*account
;
304 /* Delayed buzz MUST NOT be accepted */
308 /* Reject buzz when it's not enabled */
309 if(!jm
->js
->allowBuzz
)
312 account
= purple_connection_get_account(jm
->js
->gc
);
314 if (purple_blist_find_buddy(account
, jm
->from
) == NULL
)
315 return; /* Do not accept buzzes from unknown people */
317 /* xmpp only has 1 attention type, so index is 0 */
318 purple_protocol_got_attention(jm
->js
->gc
, jm
->from
, 0);
321 /* used internally by the functions below */
329 jabber_message_get_refs_from_xmlnode_internal(const PurpleXmlNode
*message
,
332 PurpleXmlNode
*child
;
334 for (child
= purple_xmlnode_get_child(message
, "img") ; child
;
335 child
= purple_xmlnode_get_next_twin(child
)) {
336 const gchar
*src
= purple_xmlnode_get_attrib(child
, "src");
338 if (g_str_has_prefix(src
, "cid:")) {
339 const gchar
*cid
= src
+ 4;
341 /* if we haven't "fetched" this yet... */
342 if (!g_hash_table_lookup(table
, cid
)) {
343 /* take a copy of the cid and let the SmileyRef own it... */
344 gchar
*temp_cid
= g_strdup(cid
);
345 JabberSmileyRef
*ref
= g_new0(JabberSmileyRef
, 1);
346 const gchar
*alt
= purple_xmlnode_get_attrib(child
, "alt");
348 /* if there is no "alt" string, use the cid...
349 include the entire src, eg. "cid:.." to avoid linkification */
350 if (alt
&& alt
[0] != '\0') {
351 /* workaround for when "alt" is set to the value of the
352 CID (which Jabbim seems to do), to avoid it showing up
353 as an mailto: link */
354 if (purple_email_is_valid(alt
)) {
355 ref
->alt
= g_strdup_printf("smiley:%s", alt
);
357 ref
->alt
= g_strdup(alt
);
360 ref
->alt
= g_strdup(src
);
362 g_hash_table_insert(table
, temp_cid
, ref
);
367 for (child
= message
->child
; child
; child
= child
->next
) {
368 jabber_message_get_refs_from_xmlnode_internal(child
, table
);
373 jabber_message_get_refs_steal(gpointer key
, gpointer value
, gpointer user_data
)
375 GList
**refs
= (GList
**) user_data
;
376 JabberSmileyRef
*ref
= (JabberSmileyRef
*) value
;
378 *refs
= g_list_append(*refs
, ref
);
384 jabber_message_get_refs_from_xmlnode(const PurpleXmlNode
*message
)
387 GHashTable
*unique_refs
= g_hash_table_new(g_str_hash
, g_str_equal
);
389 jabber_message_get_refs_from_xmlnode_internal(message
, unique_refs
);
390 (void) g_hash_table_foreach_steal(unique_refs
,
391 jabber_message_get_refs_steal
, (gpointer
) &refs
);
392 g_hash_table_destroy(unique_refs
);
397 jabber_message_xml_to_string_strip_img_smileys(PurpleXmlNode
*xhtml
)
399 gchar
*markup
= purple_xmlnode_to_str(xhtml
, NULL
);
400 int len
= strlen(markup
);
402 GString
*out
= g_string_new(NULL
);
405 /* this is a bit cludgy, maybe there is a better way to do this...
406 we need to find all <img> tags within the XHTML and replace those
407 tags with the value of their "alt" attributes */
408 if (g_str_has_prefix(&(markup
[pos
]), "<img")) {
409 PurpleXmlNode
*img
= NULL
;
413 for (; pos2
< len
; pos2
++) {
414 if (g_str_has_prefix(&(markup
[pos2
]), "/>")) {
417 } else if (g_str_has_prefix(&(markup
[pos2
]), "</img>")) {
423 /* note, if the above loop didn't find the end of the <img> tag,
424 it the parsed string will be until the end of the input string,
425 in which case purple_xmlnode_from_str will bail out and return NULL,
426 in this case the "if" statement below doesn't trigger and the
427 text is copied unchanged */
428 img
= purple_xmlnode_from_str(&(markup
[pos
]), pos2
- pos
);
429 src
= purple_xmlnode_get_attrib(img
, "src");
431 if (g_str_has_prefix(src
, "cid:")) {
432 const gchar
*alt
= purple_xmlnode_get_attrib(img
, "alt");
433 /* if the "alt" attribute is empty, put the cid as smiley string */
434 if (alt
&& alt
[0] != '\0') {
435 /* if the "alt" is the same as the CID, as Jabbim does,
436 this prevents linkification... */
437 if (purple_email_is_valid(alt
)) {
438 gchar
*safe_alt
= g_strdup_printf("smiley:%s", alt
);
439 out
= g_string_append(out
, safe_alt
);
442 gchar
*alt_escaped
= g_markup_escape_text(alt
, -1);
443 out
= g_string_append(out
, alt_escaped
);
447 out
= g_string_append(out
, src
);
451 out
= g_string_append_c(out
, markup
[pos
]);
455 purple_xmlnode_free(img
);
458 out
= g_string_append_c(out
, markup
[pos
]);
464 return g_string_free(out
, FALSE
);
468 jabber_message_add_remote_smileys(JabberStream
*js
, const gchar
*who
,
469 const PurpleXmlNode
*message
)
471 PurpleXmlNode
*data_tag
;
472 for (data_tag
= purple_xmlnode_get_child_with_namespace(message
, "data", NS_BOB
) ;
474 data_tag
= purple_xmlnode_get_next_twin(data_tag
)) {
475 const gchar
*cid
= purple_xmlnode_get_attrib(data_tag
, "cid");
476 const JabberData
*data
= jabber_data_find_remote_by_cid(js
, who
, cid
);
478 if (!data
&& cid
!= NULL
) {
479 /* we haven't cached this already, let's add it */
480 JabberData
*new_data
= jabber_data_create_from_xml(data_tag
);
483 jabber_data_associate_remote(js
, who
, new_data
);
490 jabber_message_remote_smiley_got(JabberData
*data
, gchar
*alt
, gpointer _smiley
)
492 PurpleSmiley
*smiley
= _smiley
;
493 PurpleImage
*image
= purple_smiley_get_image(smiley
);
495 g_free(alt
); /* we really don't need it */
498 purple_debug_info("jabber",
499 "smiley data retrieved successfully");
500 purple_image_transfer_write(image
, jabber_data_get_data(data
),
501 jabber_data_get_size(data
));
502 purple_image_transfer_close(image
);
504 purple_debug_error("jabber", "failed retrieving smiley data");
505 purple_image_transfer_failed(image
);
508 g_object_unref(smiley
);
512 jabber_message_remote_smiley_add(JabberStream
*js
, PurpleConversation
*conv
,
513 const gchar
*from
, const gchar
*shortcut
, const gchar
*cid
)
515 PurpleSmiley
*smiley
;
516 const JabberData
*jdata
;
518 purple_debug_misc("jabber", "about to add remote smiley %s to the conv",
521 smiley
= purple_conversation_add_remote_smiley(conv
, shortcut
);
523 purple_debug_misc("jabber", "smiley was already present");
527 /* TODO: cache lookup by "cid" */
529 jdata
= jabber_data_find_remote_by_cid(js
, from
, cid
);
531 PurpleImage
*image
= purple_smiley_get_image(smiley
);
533 purple_debug_info("jabber", "smiley data is already known");
535 purple_image_transfer_write(image
, jabber_data_get_data(jdata
),
536 jabber_data_get_size(jdata
));
537 purple_image_transfer_close(image
);
539 gchar
*alt
= g_strdup(shortcut
); /* it it really necessary? */
541 purple_debug_info("jabber", "smiley data is unknown, "
542 "need to request it");
544 g_object_ref(smiley
);
545 jabber_data_request(js
, cid
, from
, alt
, FALSE
,
546 jabber_message_remote_smiley_got
, smiley
);
550 void jabber_message_parse(JabberStream
*js
, PurpleXmlNode
*packet
)
553 const char *id
, *from
, *to
, *type
;
554 PurpleXmlNode
*child
;
555 gboolean signal_return
;
557 from
= purple_xmlnode_get_attrib(packet
, "from");
558 id
= purple_xmlnode_get_attrib(packet
, "id");
559 to
= purple_xmlnode_get_attrib(packet
, "to");
560 type
= purple_xmlnode_get_attrib(packet
, "type");
562 signal_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js
->gc
),
563 "jabber-receiving-message", js
->gc
, type
, id
, from
, to
, packet
));
567 jm
= g_new0(JabberMessage
, 1);
569 jm
->sent
= time(NULL
);
571 jm
->chat_state
= JM_STATE_NONE
;
574 if(!strcmp(type
, "normal"))
575 jm
->type
= JABBER_MESSAGE_NORMAL
;
576 else if(!strcmp(type
, "chat"))
577 jm
->type
= JABBER_MESSAGE_CHAT
;
578 else if(!strcmp(type
, "groupchat"))
579 jm
->type
= JABBER_MESSAGE_GROUPCHAT
;
580 else if(!strcmp(type
, "headline"))
581 jm
->type
= JABBER_MESSAGE_HEADLINE
;
582 else if(!strcmp(type
, "error"))
583 jm
->type
= JABBER_MESSAGE_ERROR
;
585 jm
->type
= JABBER_MESSAGE_OTHER
;
587 jm
->type
= JABBER_MESSAGE_NORMAL
;
590 jm
->from
= g_strdup(from
);
591 jm
->to
= g_strdup(to
);
592 jm
->id
= g_strdup(id
);
594 for(child
= packet
->child
; child
; child
= child
->next
) {
595 const char *xmlns
= purple_xmlnode_get_namespace(child
);
596 if(child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
599 if(!strcmp(child
->name
, "error")) {
600 const char *code
= purple_xmlnode_get_attrib(child
, "code");
601 char *code_txt
= NULL
;
602 char *text
= purple_xmlnode_get_data(child
);
604 PurpleXmlNode
*enclosed_text_node
;
606 if ((enclosed_text_node
= purple_xmlnode_get_child(child
, "text")))
607 text
= purple_xmlnode_get_data(enclosed_text_node
);
611 code_txt
= g_strdup_printf(_("(Code %s)"), code
);
614 jm
->error
= g_strdup_printf("%s%s%s",
616 text
&& code_txt
? " " : "",
617 code_txt
? code_txt
: "");
621 } else if (xmlns
== NULL
) {
622 /* QuLogic: Not certain this is correct, but it would have happened
623 with the previous code. */
624 if(!strcmp(child
->name
, "x"))
625 jm
->etc
= g_list_append(jm
->etc
, child
);
626 /* The following tests expect xmlns != NULL */
628 } else if(!strcmp(child
->name
, "subject") && !strcmp(xmlns
, NS_XMPP_CLIENT
)) {
630 jm
->subject
= purple_xmlnode_get_data(child
);
632 jm
->subject
= g_strdup("");
634 } else if(!strcmp(child
->name
, "thread") && !strcmp(xmlns
, NS_XMPP_CLIENT
)) {
636 jm
->thread_id
= purple_xmlnode_get_data(child
);
637 } else if(!strcmp(child
->name
, "body") && !strcmp(xmlns
, NS_XMPP_CLIENT
)) {
639 char *msg
= purple_xmlnode_get_data(child
);
640 char *escaped
= purple_markup_escape_text(msg
, -1);
641 jm
->body
= purple_strdup_withhtml(escaped
);
645 } else if(!strcmp(child
->name
, "html") && !strcmp(xmlns
, NS_XHTML_IM
)) {
646 if(!jm
->xhtml
&& purple_xmlnode_get_child(child
, "body")) {
649 const PurpleConnection
*gc
= js
->gc
;
650 PurpleAccount
*account
= purple_connection_get_account(gc
);
651 PurpleConversation
*conv
= NULL
;
652 GList
*smiley_refs
= NULL
, *it
;
653 gchar
*reformatted_xhtml
;
655 if (purple_account_get_bool(account
, "custom_smileys", TRUE
)) {
656 /* find a list of smileys ("cid" and "alt" text pairs)
657 occuring in the message */
658 smiley_refs
= jabber_message_get_refs_from_xmlnode(child
);
659 purple_debug_info("jabber", "found %d smileys\n",
660 g_list_length(smiley_refs
));
663 if (jm
->type
== JABBER_MESSAGE_GROUPCHAT
) {
664 JabberID
*jid
= jabber_id_new(jm
->from
);
665 JabberChat
*chat
= NULL
;
668 chat
= jabber_chat_find(js
, jid
->node
, jid
->domain
);
670 conv
= PURPLE_CONVERSATION(chat
->conv
);
673 } else if (jm
->type
== JABBER_MESSAGE_NORMAL
||
674 jm
->type
== JABBER_MESSAGE_CHAT
) {
676 purple_conversations_find_with_account(from
, account
);
678 /* we need to create the conversation here */
679 conv
= PURPLE_CONVERSATION(
680 purple_im_conversation_new(account
, from
));
685 /* process any newly provided smileys */
686 jabber_message_add_remote_smileys(js
, to
, packet
);
689 purple_xmlnode_strip_prefixes(child
);
691 /* reformat xhtml so that img tags with a "cid:" src gets
692 translated to the bare text of the emoticon (the "alt" attrib) */
693 /* this is done also when custom smiley retrieval is turned off,
694 this way the receiver always sees the shortcut instead */
696 jabber_message_xml_to_string_strip_img_smileys(child
);
698 jm
->xhtml
= reformatted_xhtml
;
700 /* add known custom emoticons to the conversation */
701 /* note: if there were no smileys in the incoming message, or
702 if receiving custom smileys is turned off, smiley_refs will
704 for (it
= smiley_refs
; it
; it
= g_list_next(it
)) {
705 JabberSmileyRef
*ref
= it
->data
;
708 jabber_message_remote_smiley_add(js
,
709 conv
, from
, ref
->alt
, ref
->cid
);
716 g_list_free(smiley_refs
);
718 /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention
719 * treated \n as a newline for compatibility with other protocols
721 for (c
= jm
->xhtml
; *c
!= '\0'; c
++) {
726 } else if(!strcmp(child
->name
, "active") && !strcmp(xmlns
,"http://jabber.org/protocol/chatstates")) {
727 jm
->chat_state
= JM_STATE_ACTIVE
;
728 } else if(!strcmp(child
->name
, "composing") && !strcmp(xmlns
,"http://jabber.org/protocol/chatstates")) {
729 jm
->chat_state
= JM_STATE_COMPOSING
;
730 } else if(!strcmp(child
->name
, "paused") && !strcmp(xmlns
,"http://jabber.org/protocol/chatstates")) {
731 jm
->chat_state
= JM_STATE_PAUSED
;
732 } else if(!strcmp(child
->name
, "inactive") && !strcmp(xmlns
,"http://jabber.org/protocol/chatstates")) {
733 jm
->chat_state
= JM_STATE_INACTIVE
;
734 } else if(!strcmp(child
->name
, "gone") && !strcmp(xmlns
,"http://jabber.org/protocol/chatstates")) {
735 jm
->chat_state
= JM_STATE_GONE
;
736 } else if(!strcmp(child
->name
, "event") && !strcmp(xmlns
,"http://jabber.org/protocol/pubsub#event")) {
737 PurpleXmlNode
*items
;
738 jm
->type
= JABBER_MESSAGE_EVENT
;
739 for(items
= purple_xmlnode_get_child(child
,"items"); items
; items
= items
->next
)
740 jm
->eventitems
= g_list_append(jm
->eventitems
, items
);
741 } else if(!strcmp(child
->name
, "attention") && !strcmp(xmlns
, NS_ATTENTION
)) {
743 } else if(!strcmp(child
->name
, "delay") && !strcmp(xmlns
, NS_DELAYED_DELIVERY
)) {
744 const char *timestamp
= purple_xmlnode_get_attrib(child
, "stamp");
747 jm
->sent
= purple_str_to_time(timestamp
, TRUE
, NULL
, NULL
, NULL
);
748 } else if(!strcmp(child
->name
, "x")) {
749 if(!strcmp(xmlns
, NS_DELAYED_DELIVERY_LEGACY
)) {
750 const char *timestamp
= purple_xmlnode_get_attrib(child
, "stamp");
753 jm
->sent
= purple_str_to_time(timestamp
, TRUE
, NULL
, NULL
, NULL
);
754 } else if(!strcmp(xmlns
, "jabber:x:conference") &&
755 jm
->type
!= JABBER_MESSAGE_GROUPCHAT_INVITE
&&
756 jm
->type
!= JABBER_MESSAGE_ERROR
) {
757 const char *jid
= purple_xmlnode_get_attrib(child
, "jid");
759 const char *reason
= purple_xmlnode_get_attrib(child
, "reason");
760 const char *password
= purple_xmlnode_get_attrib(child
, "password");
762 jm
->type
= JABBER_MESSAGE_GROUPCHAT_INVITE
;
764 jm
->to
= g_strdup(jid
);
768 jm
->body
= g_strdup(reason
);
772 g_free(jm
->password
);
773 jm
->password
= g_strdup(password
);
776 } else if(!strcmp(xmlns
, "http://jabber.org/protocol/muc#user") &&
777 jm
->type
!= JABBER_MESSAGE_ERROR
) {
778 PurpleXmlNode
*invite
= purple_xmlnode_get_child(child
, "invite");
780 PurpleXmlNode
*reason
, *password
;
781 const char *jid
= purple_xmlnode_get_attrib(invite
, "from");
784 jm
->from
= g_strdup(jid
);
785 if((reason
= purple_xmlnode_get_child(invite
, "reason"))) {
787 jm
->body
= purple_xmlnode_get_data(reason
);
789 if((password
= purple_xmlnode_get_child(child
, "password"))) {
790 g_free(jm
->password
);
791 jm
->password
= purple_xmlnode_get_data(password
);
794 jm
->type
= JABBER_MESSAGE_GROUPCHAT_INVITE
;
797 jm
->etc
= g_list_append(jm
->etc
, child
);
799 } else if (g_str_equal(child
->name
, "query")) {
800 const char *node
= purple_xmlnode_get_attrib(child
, "node");
801 if (purple_strequal(xmlns
, NS_DISCO_ITEMS
)
802 && purple_strequal(node
, "http://jabber.org/protocol/commands")) {
803 jabber_adhoc_got_list(js
, jm
->from
, child
);
812 case JABBER_MESSAGE_OTHER
:
813 purple_debug_info("jabber",
814 "Received message of unknown type: %s\n", type
);
815 /* Fall-through is intentional */
816 case JABBER_MESSAGE_NORMAL
:
817 case JABBER_MESSAGE_CHAT
:
820 case JABBER_MESSAGE_HEADLINE
:
823 case JABBER_MESSAGE_GROUPCHAT
:
824 handle_groupchat(jm
);
826 case JABBER_MESSAGE_GROUPCHAT_INVITE
:
827 handle_groupchat_invite(jm
);
829 case JABBER_MESSAGE_EVENT
:
830 jabber_handle_event(jm
);
832 case JABBER_MESSAGE_ERROR
:
836 jabber_message_free(jm
);
840 jabber_conv_support_custom_smileys(JabberStream
*js
,
841 PurpleConversation
*conv
,
847 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
848 jb
= jabber_buddy_find(js
, who
, FALSE
);
850 return jabber_buddy_has_capability(jb
, NS_BOB
);
854 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
855 chat
= jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv
));
857 /* do not attempt to send custom smileys in a MUC with more than
858 10 people, to avoid getting too many BoB requests */
859 return jabber_chat_get_num_participants(chat
) <= 10 &&
860 jabber_chat_all_participants_have_capability(chat
,
871 jabber_message_smileyify_cb(GString
*out
, PurpleSmiley
*smiley
,
872 PurpleConversation
*_empty
, gpointer _unused
)
874 const gchar
*shortcut
;
875 const JabberData
*data
;
876 PurpleXmlNode
*smiley_node
;
879 shortcut
= purple_smiley_get_shortcut(smiley
);
880 data
= jabber_data_find_local_by_alt(shortcut
);
885 smiley_node
= jabber_data_get_xhtml_im(data
, shortcut
);
886 node_xml
= purple_xmlnode_to_str(smiley_node
, NULL
);
888 g_string_append(out
, node_xml
);
890 purple_xmlnode_free(smiley_node
);
897 jabber_message_smileyfy_xhtml(JabberMessage
*jm
, const char *xhtml
)
899 PurpleAccount
*account
= purple_connection_get_account(jm
->js
->gc
);
900 GList
*found_smileys
, *it
, *it_next
;
901 PurpleConversation
*conv
;
902 gboolean has_too_large_smiley
= FALSE
;
903 gchar
*smileyfied_xhtml
= NULL
;
905 conv
= purple_conversations_find_with_account(jm
->to
, account
);
907 if (!jabber_conv_support_custom_smileys(jm
->js
, conv
, jm
->to
))
910 found_smileys
= purple_smiley_parser_find(
911 purple_smiley_custom_get_list(), xhtml
, TRUE
);
915 for (it
= found_smileys
; it
; it
= it_next
) {
916 PurpleSmiley
*smiley
= it
->data
;
917 PurpleImage
*smiley_image
;
918 gboolean valid
= TRUE
;
920 it_next
= g_list_next(it
);
922 smiley_image
= purple_smiley_get_image(smiley
);
925 purple_debug_warning("jabber", "broken smiley %s",
926 purple_smiley_get_shortcut(smiley
));
927 } else if (purple_image_get_size(smiley_image
) >
928 JABBER_DATA_MAX_SIZE
)
930 has_too_large_smiley
= TRUE
;
932 purple_debug_warning("jabber", "Refusing to send "
933 "smiley %s (too large, max is %d)",
934 purple_smiley_get_shortcut(smiley
),
935 JABBER_DATA_MAX_SIZE
);
939 found_smileys
= g_list_delete_link(found_smileys
, it
);
942 if (has_too_large_smiley
) {
943 purple_conversation_write_system_message(conv
,
944 _("A custom smiley in the message is too large to send."),
945 PURPLE_MESSAGE_ERROR
);
951 for (it
= found_smileys
; it
; it
= g_list_next(it
)) {
952 PurpleSmiley
*smiley
= it
->data
;
953 PurpleImage
*smiley_image
;
954 const gchar
*shortcut
= purple_smiley_get_shortcut(smiley
);
955 const gchar
*mimetype
;
958 /* the object has been sent before */
959 if (jabber_data_find_local_by_alt(shortcut
))
962 smiley_image
= purple_smiley_get_image(smiley
);
963 g_assert(smiley_image
!= NULL
);
965 mimetype
= purple_image_get_mimetype(smiley_image
);
967 purple_debug_error("jabber",
968 "unknown mime type for image");
972 jdata
= jabber_data_create_from_data(
973 purple_image_get_data(smiley_image
),
974 purple_image_get_size(smiley_image
),
975 mimetype
, FALSE
, jm
->js
);
977 purple_debug_info("jabber", "cache local smiley alt=%s, cid=%s",
978 shortcut
, jabber_data_get_cid(jdata
));
979 jabber_data_associate_local(jdata
, shortcut
);
982 g_list_free(found_smileys
);
984 smileyfied_xhtml
= purple_smiley_parser_replace(
985 purple_smiley_custom_get_list(), xhtml
,
986 jabber_message_smileyify_cb
, NULL
);
988 return smileyfied_xhtml
;
991 void jabber_message_send(JabberMessage
*jm
)
993 PurpleXmlNode
*message
, *child
;
994 const char *type
= NULL
;
996 message
= purple_xmlnode_new("message");
999 case JABBER_MESSAGE_NORMAL
:
1002 case JABBER_MESSAGE_CHAT
:
1003 case JABBER_MESSAGE_GROUPCHAT_INVITE
:
1006 case JABBER_MESSAGE_HEADLINE
:
1009 case JABBER_MESSAGE_GROUPCHAT
:
1012 case JABBER_MESSAGE_ERROR
:
1015 case JABBER_MESSAGE_OTHER
:
1022 purple_xmlnode_set_attrib(message
, "type", type
);
1025 purple_xmlnode_set_attrib(message
, "id", jm
->id
);
1027 purple_xmlnode_set_attrib(message
, "to", jm
->to
);
1030 child
= purple_xmlnode_new_child(message
, "thread");
1031 purple_xmlnode_insert_data(child
, jm
->thread_id
, -1);
1035 switch(jm
->chat_state
)
1037 case JM_STATE_ACTIVE
:
1038 child
= purple_xmlnode_new_child(message
, "active");
1040 case JM_STATE_COMPOSING
:
1041 child
= purple_xmlnode_new_child(message
, "composing");
1043 case JM_STATE_PAUSED
:
1044 child
= purple_xmlnode_new_child(message
, "paused");
1046 case JM_STATE_INACTIVE
:
1047 child
= purple_xmlnode_new_child(message
, "inactive");
1050 child
= purple_xmlnode_new_child(message
, "gone");
1057 purple_xmlnode_set_namespace(child
, "http://jabber.org/protocol/chatstates");
1060 child
= purple_xmlnode_new_child(message
, "subject");
1061 purple_xmlnode_insert_data(child
, jm
->subject
, -1);
1065 child
= purple_xmlnode_new_child(message
, "body");
1066 purple_xmlnode_insert_data(child
, jm
->body
, -1);
1070 if ((child
= purple_xmlnode_from_str(jm
->xhtml
, -1))) {
1071 purple_xmlnode_insert_child(message
, child
);
1073 purple_debug_error("jabber",
1074 "XHTML translation/validation failed, returning: %s\n",
1079 jabber_send(jm
->js
, message
);
1081 purple_xmlnode_free(message
);
1085 * Compare the XHTML and plain strings passed in for "equality". Any HTML markup
1086 * other than <br/> (matches a newline) in the XHTML will cause this to return
1090 jabber_xhtml_plain_equal(const char *xhtml_escaped
,
1096 char *xhtml
= purple_unescape_html(xhtml_escaped
);
1098 while (xhtml
[i
] && plain
[j
]) {
1099 if (xhtml
[i
] == plain
[j
]) {
1105 if (plain
[j
] == '\n' && !strncmp(xhtml
+i
, "<br/>", 5)) {
1115 /* Are we at the end of both strings? */
1116 ret
= (xhtml
[i
] == plain
[j
]) && (xhtml
[i
] == '\0');
1121 int jabber_message_send_im(PurpleConnection
*gc
, PurpleMessage
*msg
)
1125 JabberBuddyResource
*jbr
;
1129 const gchar
*rcpt
= purple_message_get_recipient(msg
);
1131 if (!rcpt
|| purple_message_is_empty(msg
))
1134 resource
= jabber_get_resource(rcpt
);
1136 jb
= jabber_buddy_find(purple_connection_get_protocol_data(gc
), rcpt
, TRUE
);
1137 jbr
= jabber_buddy_find_resource(jb
, resource
);
1141 jm
= g_new0(JabberMessage
, 1);
1142 jm
->js
= purple_connection_get_protocol_data(gc
);
1143 jm
->type
= JABBER_MESSAGE_CHAT
;
1144 jm
->chat_state
= JM_STATE_ACTIVE
;
1145 jm
->to
= g_strdup(rcpt
);
1146 jm
->id
= jabber_get_next_id(jm
->js
);
1150 jm
->thread_id
= jbr
->thread_id
;
1152 if (jbr
->chat_states
== JABBER_CHAT_STATES_UNSUPPORTED
)
1153 jm
->chat_state
= JM_STATE_NONE
;
1155 /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
1156 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
1160 tmp
= purple_utf8_strip_unprintables(purple_message_get_contents(msg
));
1161 purple_markup_html_to_xhtml(tmp
, &xhtml
, &jm
->body
);
1164 tmp
= jabber_message_smileyfy_xhtml(jm
, xhtml
);
1171 * For backward compatibility with user expectations or for those not on
1172 * the user's roster, allow sending XHTML-IM markup.
1174 if (!jbr
|| !jbr
->caps
.info
||
1175 jabber_resource_has_capability(jbr
, NS_XHTML_IM
)) {
1176 if (!jabber_xhtml_plain_equal(xhtml
, jm
->body
))
1177 /* Wrap the message in <p/> for great interoperability justice. */
1178 jm
->xhtml
= g_strdup_printf("<html xmlns='" NS_XHTML_IM
"'><body xmlns='" NS_XHTML
"'><p>%s</p></body></html>", xhtml
);
1183 jabber_message_send(jm
);
1184 jabber_message_free(jm
);
1188 int jabber_message_send_chat(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
)
1196 if (!gc
|| purple_message_is_empty(msg
))
1199 js
= purple_connection_get_protocol_data(gc
);
1200 chat
= jabber_chat_find_by_id(js
, id
);
1205 jm
= g_new0(JabberMessage
, 1);
1206 jm
->js
= purple_connection_get_protocol_data(gc
);
1207 jm
->type
= JABBER_MESSAGE_GROUPCHAT
;
1208 jm
->to
= g_strdup_printf("%s@%s", chat
->room
, chat
->server
);
1209 jm
->id
= jabber_get_next_id(jm
->js
);
1211 tmp
= purple_utf8_strip_unprintables(purple_message_get_contents(msg
));
1212 purple_markup_html_to_xhtml(tmp
, &xhtml
, &jm
->body
);
1214 tmp
= jabber_message_smileyfy_xhtml(jm
, xhtml
);
1220 if (chat
->xhtml
&& !jabber_xhtml_plain_equal(xhtml
, jm
->body
))
1221 /* Wrap the message in <p/> for greater interoperability justice. */
1222 jm
->xhtml
= g_strdup_printf("<html xmlns='" NS_XHTML_IM
"'><body xmlns='" NS_XHTML
"'><p>%s</p></body></html>", xhtml
);
1226 jabber_message_send(jm
);
1227 jabber_message_free(jm
);
1232 unsigned int jabber_send_typing(PurpleConnection
*gc
, const char *who
, PurpleIMTypingState state
)
1237 JabberBuddyResource
*jbr
;
1240 js
= purple_connection_get_protocol_data(gc
);
1241 jb
= jabber_buddy_find(js
, who
, TRUE
);
1245 resource
= jabber_get_resource(who
);
1246 jbr
= jabber_buddy_find_resource(jb
, resource
);
1249 /* We know this entity doesn't support chat states */
1250 if (jbr
&& jbr
->chat_states
== JABBER_CHAT_STATES_UNSUPPORTED
)
1253 /* *If* we don't have presence /and/ the buddy can't see our
1254 * presence, don't send typing notifications.
1256 if (!jbr
&& !(jb
->subscription
& JABBER_SUB_FROM
))
1259 /* TODO: figure out threading */
1260 jm
= g_new0(JabberMessage
, 1);
1262 jm
->type
= JABBER_MESSAGE_CHAT
;
1263 jm
->to
= g_strdup(who
);
1264 jm
->id
= jabber_get_next_id(jm
->js
);
1266 if(PURPLE_IM_TYPING
== state
)
1267 jm
->chat_state
= JM_STATE_COMPOSING
;
1268 else if(PURPLE_IM_TYPED
== state
)
1269 jm
->chat_state
= JM_STATE_PAUSED
;
1271 jm
->chat_state
= JM_STATE_ACTIVE
;
1273 /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
1274 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
1276 jabber_message_send(jm
);
1277 jabber_message_free(jm
);
1282 gboolean
jabber_buzz_isenabled(JabberStream
*js
, const gchar
*namespace) {
1283 return js
->allowBuzz
;
1286 gboolean
jabber_custom_smileys_isenabled(JabberStream
*js
, const gchar
*namespace)
1288 const PurpleConnection
*gc
= js
->gc
;
1289 PurpleAccount
*account
= purple_connection_get_account(gc
);
1291 return purple_account_get_bool(account
, "custom_smileys", TRUE
);