* purple - Jabber Protocol Plugin * 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 * 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 "smiley-custom.h" #include "smiley-parser.h" #include "adhoccommands.h" #include "google/google.h" PurpleConversation *conv; } JabberMessageRemoteSmileyAddData; static GString *jm_body_with_oob(JabberMessage *jm) { GString *body = g_string_new(""); g_string_append(body, jm->xhtml); g_string_append(body, jm->body); for(etc = jm->etc; etc; etc = etc->next) { PurpleXmlNode *x = etc->data; const char *xmlns = purple_xmlnode_get_namespace(x); if(purple_strequal(xmlns, NS_OOB_X_DATA)) { PurpleXmlNode *url, *desc; url = purple_xmlnode_get_child(x, "url"); desc = purple_xmlnode_get_child(x, "desc"); urltxt = purple_xmlnode_get_data(url); desctxt = desc ? purple_xmlnode_get_data(desc) : urltxt; if(body->len && !purple_strequal(body->str, urltxt)) g_string_append_printf(body, "<br/><a href='%s'>%s</a>", g_string_printf(body, "<a href='%s'>%s</a>", void jabber_message_free(JabberMessage *jm) g_list_free(jm->eventitems); static void handle_chat(JabberMessage *jm) JabberID *jid = jabber_id_new(jm->from); JabberBuddyResource *jbr; account = purple_connection_get_account(gc); jb = jabber_buddy_find(jm->js, jm->from, TRUE); jbr = jabber_buddy_find_resource(jb, jid->resource); if (jbr && jm->chat_state != JM_STATE_NONE) jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED; purple_serv_got_typing(gc, jm->from, 0, PURPLE_IM_TYPING); purple_serv_got_typing(gc, jm->from, 0, PURPLE_IM_TYPED); PurpleIMConversation *im = purple_conversations_find_im_with_account( if (im && jid->node && jid->domain) { g_snprintf(buf, sizeof(buf), "%s@%s", jid->node, jid->domain); if ((buddy = purple_blist_find_buddy(account, buf))) { who = purple_buddy_get_alias(buddy); escaped = g_markup_escape_text(who, -1); g_snprintf(buf, sizeof(buf), _("%s has left the conversation."), escaped); /* At some point when we restructure PurpleConversation, * this should be able to be implemented by removing the * user from the conversation like we do with chats now. */ purple_conversation_write_system_message( PURPLE_CONVERSATION(im), buf, 0); purple_serv_got_typing_stopped(gc, jm->from); purple_serv_got_typing_stopped(gc, jm->from); if (jm->js->googletalk && jm->body && jm->xhtml == NULL) { jm->body = jabber_google_format_to_html(jm->body); body = jm_body_with_oob(jm); * We received a message from a specific resource, so * we probably want a reply to go to this specific * resource (i.e. bind/lock the conversation to this * This works because purple_im_conversation_send gets the name * from purple_conversation_get_name() PurpleIMConversation *im; im = purple_conversations_find_im_with_account(jm->from, account); if (im && !purple_strequal(jm->from, purple_conversation_get_name(PURPLE_CONVERSATION(im)))) { purple_debug_info("jabber", "Binding conversation to %s\n", purple_conversation_set_name(PURPLE_CONVERSATION(im), jm->from); /* Treat SUPPORTED as a terminal with no escape :) */ if (jbr->chat_states != JABBER_CHAT_STATES_SUPPORTED) { if (jm->chat_state != JM_STATE_NONE) jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED; jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; jbr->thread_id = g_strdup(jm->thread_id); purple_serv_got_im(gc, jm->from, body->str, 0, jm->sent); g_string_free(body, TRUE); static void handle_headline(JabberMessage *jm) if(!jm->xhtml && !jm->body) return; /* ignore headlines without any content */ body = jm_body_with_oob(jm); title = g_strdup_printf(_("Message from %s"), jm->from); purple_notify_formatted(jm->js->gc, title, jm->subject ? jm->subject : title, NULL, body->str, NULL, NULL); g_string_free(body, TRUE); static void handle_groupchat(JabberMessage *jm) JabberID *jid = jabber_id_new(jm->from); PurpleMessageFlags messageFlags = 0; chat = jabber_chat_find(jm->js, jid->node, jid->domain); purple_chat_conversation_set_topic(chat->conv, jid->resource, messageFlags |= PURPLE_MESSAGE_NO_LOG; if(!jm->xhtml && !jm->body) { tmp = g_markup_escape_text(jm->subject, -1); tmp2 = purple_markup_linkify(tmp); msg = g_strdup_printf(_("%s has set the topic to: %s"), jid->resource, tmp2); msg = g_strdup_printf(_("The topic is: %s"), tmp2); purple_conversation_write_system_message(PURPLE_CONVERSATION(chat->conv), if(jm->xhtml || jm->body) { purple_serv_got_chat_in(jm->js->gc, chat->id, jid->resource, messageFlags | (jm->delayed ? PURPLE_MESSAGE_DELAYED : 0), jm->xhtml ? jm->xhtml : jm->body, jm->sent); purple_conversation_write_system_message( PURPLE_CONVERSATION(chat->conv), jm->xhtml ? jm->xhtml : jm->body, messageFlags); static void handle_groupchat_invite(JabberMessage *jm) JabberID *jid = jabber_id_new(jm->to); components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); g_hash_table_replace(components, "room", g_strdup(jid->node)); g_hash_table_replace(components, "server", g_strdup(jid->domain)); g_hash_table_replace(components, "handle", g_strdup(jm->js->user->node)); g_hash_table_replace(components, "password", g_strdup(jm->password)); purple_serv_got_chat_invite(jm->js->gc, jm->to, jm->from, jm->body, components); static void handle_error(JabberMessage *jm) buf = g_strdup_printf(_("Message delivery to %s failed: %s"), jm->from, jm->error ? jm->error : ""); purple_notify_formatted(jm->js->gc, _("XMPP Message Error"), _("XMPP Message Error"), buf, jm->xhtml ? jm->xhtml : jm->body, NULL, NULL); static void handle_buzz(JabberMessage *jm) { /* Delayed buzz MUST NOT be accepted */ /* Reject buzz when it's not enabled */ account = purple_connection_get_account(jm->js->gc); if (purple_blist_find_buddy(account, jm->from) == NULL) return; /* Do not accept buzzes from unknown people */ /* xmpp only has 1 attention type, so index is 0 */ purple_protocol_got_attention(jm->js->gc, jm->from, 0); /* used internally by the functions below */ jabber_message_get_refs_from_xmlnode_internal(const PurpleXmlNode *message, for (child = purple_xmlnode_get_child(message, "img") ; child ; child = purple_xmlnode_get_next_twin(child)) { const gchar *src = purple_xmlnode_get_attrib(child, "src"); if (g_str_has_prefix(src, "cid:")) { const gchar *cid = src + 4; /* if we haven't "fetched" this yet... */ if (!g_hash_table_lookup(table, cid)) { /* take a copy of the cid and let the SmileyRef own it... */ gchar *temp_cid = g_strdup(cid); JabberSmileyRef *ref = g_new0(JabberSmileyRef, 1); const gchar *alt = purple_xmlnode_get_attrib(child, "alt"); /* if there is no "alt" string, use the cid... include the entire src, eg. "cid:.." to avoid linkification */ if (alt && alt[0] != '\0') { /* workaround for when "alt" is set to the value of the CID (which Jabbim seems to do), to avoid it showing up if (purple_email_is_valid(alt)) { ref->alt = g_strdup_printf("smiley:%s", alt); ref->alt = g_strdup(alt); ref->alt = g_strdup(src); g_hash_table_insert(table, temp_cid, ref); for (child = message->child ; child ; child = child->next) { jabber_message_get_refs_from_xmlnode_internal(child, table); jabber_message_get_refs_steal(gpointer key, gpointer value, gpointer user_data) GList **refs = (GList **) user_data; JabberSmileyRef *ref = (JabberSmileyRef *) value; *refs = g_list_append(*refs, ref); jabber_message_get_refs_from_xmlnode(const PurpleXmlNode *message) GHashTable *unique_refs = g_hash_table_new(g_str_hash, g_str_equal); jabber_message_get_refs_from_xmlnode_internal(message, unique_refs); (void) g_hash_table_foreach_steal(unique_refs, jabber_message_get_refs_steal, (gpointer) &refs); g_hash_table_destroy(unique_refs); jabber_message_xml_to_string_strip_img_smileys(PurpleXmlNode *xhtml) gchar *markup = purple_xmlnode_to_str(xhtml, NULL); int len = strlen(markup); GString *out = g_string_new(NULL); /* this is a bit cludgy, maybe there is a better way to do this... we need to find all <img> tags within the XHTML and replace those tags with the value of their "alt" attributes */ if (g_str_has_prefix(&(markup[pos]), "<img")) { PurpleXmlNode *img = NULL; for (; pos2 < len ; pos2++) { if (g_str_has_prefix(&(markup[pos2]), "/>")) { } else if (g_str_has_prefix(&(markup[pos2]), "</img>")) { /* note, if the above loop didn't find the end of the <img> tag, it the parsed string will be until the end of the input string, in which case purple_xmlnode_from_str will bail out and return NULL, in this case the "if" statement below doesn't trigger and the text is copied unchanged */ img = purple_xmlnode_from_str(&(markup[pos]), pos2 - pos); src = purple_xmlnode_get_attrib(img, "src"); if (g_str_has_prefix(src, "cid:")) { const gchar *alt = purple_xmlnode_get_attrib(img, "alt"); /* if the "alt" attribute is empty, put the cid as smiley string */ if (alt && alt[0] != '\0') { /* if the "alt" is the same as the CID, as Jabbim does, this prevents linkification... */ if (purple_email_is_valid(alt)) { gchar *safe_alt = g_strdup_printf("smiley:%s", alt); out = g_string_append(out, safe_alt); gchar *alt_escaped = g_markup_escape_text(alt, -1); out = g_string_append(out, alt_escaped); out = g_string_append(out, src); out = g_string_append_c(out, markup[pos]); purple_xmlnode_free(img); out = g_string_append_c(out, markup[pos]); return g_string_free(out, FALSE); jabber_message_add_remote_smileys(JabberStream *js, const gchar *who, const PurpleXmlNode *message) for (data_tag = purple_xmlnode_get_child_with_namespace(message, "data", NS_BOB) ; data_tag = purple_xmlnode_get_next_twin(data_tag)) { const gchar *cid = purple_xmlnode_get_attrib(data_tag, "cid"); const JabberData *data = jabber_data_find_remote_by_cid(js, who, cid); if (!data && cid != NULL) { /* we haven't cached this already, let's add it */ JabberData *new_data = jabber_data_create_from_xml(data_tag); jabber_data_associate_remote(js, who, new_data); jabber_message_remote_smiley_got(JabberData *jdata, gchar *alt, gpointer d) { JabberMessageRemoteSmileyAddData *data = (JabberMessageRemoteSmileyAddData *)d; PurpleSmiley *smiley = NULL; purple_debug_info("jabber", "smiley data retrieved successfully"); smiley = purple_smiley_new_from_data( jabber_data_get_data(jdata), jabber_data_get_size(jdata) purple_conversation_add_smiley(data->conv, smiley); g_object_unref(G_OBJECT(smiley)); purple_debug_error("jabber", "failed retrieving smiley data"); g_object_unref(G_OBJECT(data->conv)); g_slice_free(JabberMessageRemoteSmileyAddData, data); jabber_message_remote_smiley_add(JabberStream *js, PurpleConversation *conv, const gchar *from, const gchar *shortcut, const gchar *cid) PurpleSmiley *smiley = NULL; const JabberData *jdata = NULL; purple_debug_misc("jabber", "about to add remote smiley %s to the conv", smiley = purple_conversation_get_smiley(conv, shortcut); if(PURPLE_IS_SMILEY(smiley)) { purple_debug_misc("jabber", "smiley was already present"); /* TODO: cache lookup by "cid" */ jdata = jabber_data_find_remote_by_cid(js, from, cid); purple_debug_info("jabber", "smiley data is already known"); smiley = purple_smiley_new_from_data( jabber_data_get_data(jdata), jabber_data_get_size(jdata) purple_conversation_add_smiley(conv, smiley); g_object_unref(G_OBJECT(smiley)); JabberMessageRemoteSmileyAddData *data = NULL; data = g_slice_new(JabberMessageRemoteSmileyAddData); data->conv = g_object_ref(conv); data->shortcut = g_strdup(shortcut); purple_debug_info("jabber", "smiley data is unknown, " jabber_data_request(js, cid, from, NULL, FALSE, jabber_message_remote_smiley_got, data); void jabber_message_parse(JabberStream *js, PurpleXmlNode *packet) const char *id, *from, *to, *type; from = purple_xmlnode_get_attrib(packet, "from"); id = purple_xmlnode_get_attrib(packet, "id"); to = purple_xmlnode_get_attrib(packet, "to"); type = purple_xmlnode_get_attrib(packet, "type"); signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js->gc), "jabber-receiving-message", js->gc, type, id, from, to, packet)); jm = g_new0(JabberMessage, 1); jm->chat_state = JM_STATE_NONE; if(purple_strequal(type, "normal")) jm->type = JABBER_MESSAGE_NORMAL; else if(purple_strequal(type, "chat")) jm->type = JABBER_MESSAGE_CHAT; else if(purple_strequal(type, "groupchat")) jm->type = JABBER_MESSAGE_GROUPCHAT; else if(purple_strequal(type, "headline")) jm->type = JABBER_MESSAGE_HEADLINE; else if(purple_strequal(type, "error")) jm->type = JABBER_MESSAGE_ERROR; jm->type = JABBER_MESSAGE_OTHER; jm->type = JABBER_MESSAGE_NORMAL; jm->from = g_strdup(from); for(child = packet->child; child; child = child->next) { const char *xmlns = purple_xmlnode_get_namespace(child); if(child->type != PURPLE_XMLNODE_TYPE_TAG) if(purple_strequal(child->name, "error")) { const char *code = purple_xmlnode_get_attrib(child, "code"); char *text = purple_xmlnode_get_data(child); PurpleXmlNode *enclosed_text_node; if ((enclosed_text_node = purple_xmlnode_get_child(child, "text"))) text = purple_xmlnode_get_data(enclosed_text_node); code_txt = g_strdup_printf(_("(Code %s)"), code); jm->error = g_strdup_printf("%s%s%s", text && code_txt ? " " : "", code_txt ? code_txt : ""); } else if (xmlns == NULL) { /* QuLogic: Not certain this is correct, but it would have happened with the previous code. */ if(purple_strequal(child->name, "x")) jm->etc = g_list_append(jm->etc, child); /* The following tests expect xmlns != NULL */ } else if(purple_strequal(child->name, "subject") && purple_strequal(xmlns, NS_XMPP_CLIENT)) { jm->subject = purple_xmlnode_get_data(child); jm->subject = g_strdup(""); } else if(purple_strequal(child->name, "thread") && purple_strequal(xmlns, NS_XMPP_CLIENT)) { jm->thread_id = purple_xmlnode_get_data(child); } else if(purple_strequal(child->name, "body") && purple_strequal(xmlns, NS_XMPP_CLIENT)) { char *msg = purple_xmlnode_get_data(child); char *escaped = purple_markup_escape_text(msg, -1); jm->body = purple_strdup_withhtml(escaped); } else if(purple_strequal(child->name, "html") && purple_strequal(xmlns, NS_XHTML_IM)) { if(!jm->xhtml && purple_xmlnode_get_child(child, "body")) { PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); PurpleConversation *conv = NULL; GList *smiley_refs = NULL, *it; gchar *reformatted_xhtml; if (purple_account_get_bool(account, "custom_smileys", TRUE)) { /* find a list of smileys ("cid" and "alt" text pairs) occuring in the message */ smiley_refs = jabber_message_get_refs_from_xmlnode(child); purple_debug_info("jabber", "found %d smileys\n", g_list_length(smiley_refs)); if (jm->type == JABBER_MESSAGE_GROUPCHAT) { JabberID *jid = jabber_id_new(jm->from); chat = jabber_chat_find(js, jid->node, jid->domain); conv = PURPLE_CONVERSATION(chat->conv); } else if (jm->type == JABBER_MESSAGE_NORMAL || jm->type == JABBER_MESSAGE_CHAT) { purple_conversations_find_with_account(from, account); /* we need to create the conversation here */ conv = PURPLE_CONVERSATION( purple_im_conversation_new(account, from)); /* process any newly provided smileys */ jabber_message_add_remote_smileys(js, to, packet); purple_xmlnode_strip_prefixes(child); /* reformat xhtml so that img tags with a "cid:" src gets translated to the bare text of the emoticon (the "alt" attrib) */ /* this is done also when custom smiley retrieval is turned off, this way the receiver always sees the shortcut instead */ jabber_message_xml_to_string_strip_img_smileys(child); jm->xhtml = reformatted_xhtml; /* add known custom emoticons to the conversation */ /* note: if there were no smileys in the incoming message, or if receiving custom smileys is turned off, smiley_refs will for (it = smiley_refs; it; it = g_list_next(it)) { JabberSmileyRef *ref = it->data; jabber_message_remote_smiley_add(js, conv, from, ref->alt, ref->cid); g_list_free(smiley_refs); /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention * treated \n as a newline for compatibility with other protocols for (c = jm->xhtml; *c != '\0'; c++) { } else if(purple_strequal(child->name, "active") && purple_strequal(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_ACTIVE; } else if(purple_strequal(child->name, "composing") && purple_strequal(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_COMPOSING; } else if(purple_strequal(child->name, "paused") && purple_strequal(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_PAUSED; } else if(purple_strequal(child->name, "inactive") && purple_strequal(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_INACTIVE; } else if(purple_strequal(child->name, "gone") && purple_strequal(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_GONE; } else if(purple_strequal(child->name, "event") && purple_strequal(xmlns,"http://jabber.org/protocol/pubsub#event")) { jm->type = JABBER_MESSAGE_EVENT; for(items = purple_xmlnode_get_child(child,"items"); items; items = items->next) jm->eventitems = g_list_append(jm->eventitems, items); } else if(purple_strequal(child->name, "attention") && purple_strequal(xmlns, NS_ATTENTION)) { } else if(purple_strequal(child->name, "delay") && purple_strequal(xmlns, NS_DELAYED_DELIVERY)) { const char *timestamp = purple_xmlnode_get_attrib(child, "stamp"); jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL); } else if(purple_strequal(child->name, "x")) { if(purple_strequal(xmlns, NS_DELAYED_DELIVERY_LEGACY)) { const char *timestamp = purple_xmlnode_get_attrib(child, "stamp"); jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL); } else if(purple_strequal(xmlns, "jabber:x:conference") && jm->type != JABBER_MESSAGE_GROUPCHAT_INVITE && jm->type != JABBER_MESSAGE_ERROR) { const char *jid = purple_xmlnode_get_attrib(child, "jid"); const char *reason = purple_xmlnode_get_attrib(child, "reason"); const char *password = purple_xmlnode_get_attrib(child, "password"); jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; jm->body = g_strdup(reason); jm->password = g_strdup(password); } else if(purple_strequal(xmlns, "http://jabber.org/protocol/muc#user") && jm->type != JABBER_MESSAGE_ERROR) { PurpleXmlNode *invite = purple_xmlnode_get_child(child, "invite"); PurpleXmlNode *reason, *password; const char *jid = purple_xmlnode_get_attrib(invite, "from"); jm->from = g_strdup(jid); if((reason = purple_xmlnode_get_child(invite, "reason"))) { jm->body = purple_xmlnode_get_data(reason); if((password = purple_xmlnode_get_child(child, "password"))) { jm->password = purple_xmlnode_get_data(password); jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; jm->etc = g_list_append(jm->etc, child); } else if (purple_strequal(child->name, "query")) { const char *node = purple_xmlnode_get_attrib(child, "node"); if (purple_strequal(xmlns, NS_DISCO_ITEMS) && purple_strequal(node, "http://jabber.org/protocol/commands")) { jabber_adhoc_got_list(js, jm->from, child); case JABBER_MESSAGE_OTHER: purple_debug_info("jabber", "Received message of unknown type: %s\n", type); /* Fall-through is intentional */ case JABBER_MESSAGE_NORMAL: case JABBER_MESSAGE_CHAT: case JABBER_MESSAGE_HEADLINE: case JABBER_MESSAGE_GROUPCHAT: case JABBER_MESSAGE_GROUPCHAT_INVITE: handle_groupchat_invite(jm); case JABBER_MESSAGE_EVENT: case JABBER_MESSAGE_ERROR: jabber_conv_support_custom_smileys(JabberStream *js, PurpleConversation *conv, if (PURPLE_IS_IM_CONVERSATION(conv)) { jb = jabber_buddy_find(js, who, FALSE); return jabber_buddy_has_capability(jb, NS_BOB); } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) { chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); /* do not attempt to send custom smileys in a MUC with more than 10 people, to avoid getting too many BoB requests */ return jabber_chat_get_num_participants(chat) <= 10 && jabber_chat_all_participants_have_capability(chat, jabber_message_smileyify_cb(GString *out, PurpleSmiley *smiley, PurpleConversation *_empty, gpointer _unused) PurpleXmlNode *smiley_node; shortcut = purple_smiley_get_shortcut(smiley); data = jabber_data_find_local_by_alt(shortcut); smiley_node = jabber_data_get_xhtml_im(data, shortcut); node_xml = purple_xmlnode_to_str(smiley_node, NULL); g_string_append(out, node_xml); purple_xmlnode_free(smiley_node); jabber_message_smileyfy_xhtml(JabberMessage *jm, const char *xhtml) PurpleAccount *account = purple_connection_get_account(jm->js->gc); GList *found_smileys, *it, *it_next; PurpleConversation *conv; gboolean has_too_large_smiley = FALSE; gchar *smileyfied_xhtml = NULL; conv = purple_conversations_find_with_account(jm->to, account); if (!jabber_conv_support_custom_smileys(jm->js, conv, jm->to)) found_smileys = purple_smiley_parser_find( purple_smiley_custom_get_list(), xhtml, TRUE); for (it = found_smileys; it; it = it_next) { PurpleSmiley *smiley = it->data; it_next = g_list_next(it); if (purple_image_get_data_size(PURPLE_IMAGE(smiley)) > JABBER_DATA_MAX_SIZE) { has_too_large_smiley = TRUE; purple_debug_warning("jabber", "Refusing to send " "smiley %s (too large, max is %d)", purple_smiley_get_shortcut(smiley), found_smileys = g_list_delete_link(found_smileys, it); if (has_too_large_smiley) { purple_conversation_write_system_message(conv, _("A custom smiley in the message is too large to send."), for (it = found_smileys; it; it = g_list_next(it)) { PurpleSmiley *smiley = it->data; const gchar *shortcut = purple_smiley_get_shortcut(smiley); /* the object has been sent before */ if (jabber_data_find_local_by_alt(shortcut)) mimetype = purple_image_get_mimetype(PURPLE_IMAGE(smiley)); purple_debug_error("jabber", "unknown mime type for image"); jdata = jabber_data_create_from_data( purple_image_get_data(PURPLE_IMAGE(smiley)), purple_image_get_data_size(PURPLE_IMAGE(smiley)), mimetype, FALSE, jm->js); purple_debug_info("jabber", "cache local smiley alt=%s, cid=%s", shortcut, jabber_data_get_cid(jdata)); jabber_data_associate_local(jdata, shortcut); g_list_free(found_smileys); smileyfied_xhtml = purple_smiley_parser_replace( purple_smiley_custom_get_list(), xhtml, jabber_message_smileyify_cb, NULL); void jabber_message_send(JabberMessage *jm) PurpleXmlNode *message, *child; message = purple_xmlnode_new("message"); case JABBER_MESSAGE_NORMAL: case JABBER_MESSAGE_CHAT: case JABBER_MESSAGE_GROUPCHAT_INVITE: case JABBER_MESSAGE_HEADLINE: case JABBER_MESSAGE_GROUPCHAT: case JABBER_MESSAGE_ERROR: case JABBER_MESSAGE_OTHER: purple_xmlnode_set_attrib(message, "type", type); purple_xmlnode_set_attrib(message, "id", jm->id); purple_xmlnode_set_attrib(message, "to", jm->to); child = purple_xmlnode_new_child(message, "thread"); purple_xmlnode_insert_data(child, jm->thread_id, -1); child = purple_xmlnode_new_child(message, "active"); child = purple_xmlnode_new_child(message, "composing"); child = purple_xmlnode_new_child(message, "paused"); child = purple_xmlnode_new_child(message, "inactive"); child = purple_xmlnode_new_child(message, "gone"); purple_xmlnode_set_namespace(child, "http://jabber.org/protocol/chatstates"); child = purple_xmlnode_new_child(message, "subject"); purple_xmlnode_insert_data(child, jm->subject, -1); child = purple_xmlnode_new_child(message, "body"); purple_xmlnode_insert_data(child, jm->body, -1); if ((child = purple_xmlnode_from_str(jm->xhtml, -1))) { purple_xmlnode_insert_child(message, child); purple_debug_error("jabber", "XHTML translation/validation failed, returning: %s\n", jabber_send(jm->js, message); purple_xmlnode_free(message); * Compare the XHTML and plain strings passed in for "equality". Any HTML markup * other than <br/> (matches a newline) in the XHTML will cause this to return jabber_xhtml_plain_equal(const char *xhtml_escaped, char *xhtml = purple_unescape_html(xhtml_escaped); while (xhtml[i] && plain[j]) { if (xhtml[i] == plain[j]) { if (plain[j] == '\n' && !strncmp(xhtml+i, "<br/>", 5)) { /* Are we at the end of both strings? */ ret = (xhtml[i] == plain[j]) && (xhtml[i] == '\0'); int jabber_message_send_im(PurpleConnection *gc, PurpleMessage *msg) JabberBuddyResource *jbr; const gchar *rcpt = purple_message_get_recipient(msg); if (!rcpt || purple_message_is_empty(msg)) resource = jabber_get_resource(rcpt); jb = jabber_buddy_find(purple_connection_get_protocol_data(gc), rcpt, TRUE); jbr = jabber_buddy_find_resource(jb, resource); jm = g_new0(JabberMessage, 1); jm->js = purple_connection_get_protocol_data(gc); jm->type = JABBER_MESSAGE_CHAT; jm->chat_state = JM_STATE_ACTIVE; jm->id = jabber_get_next_id(jm->js); jm->thread_id = jbr->thread_id; if (jbr->chat_states == JABBER_CHAT_STATES_UNSUPPORTED) jm->chat_state = JM_STATE_NONE; /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states) jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */ tmp = purple_utf8_strip_unprintables(purple_message_get_contents(msg)); purple_markup_html_to_xhtml(tmp, &xhtml, &jm->body); tmp = jabber_message_smileyfy_xhtml(jm, xhtml); * For backward compatibility with user expectations or for those not on * the user's roster, allow sending XHTML-IM markup. if (!jbr || !jbr->caps.info || jabber_resource_has_capability(jbr, NS_XHTML_IM)) { if (!jabber_xhtml_plain_equal(xhtml, jm->body)) /* Wrap the message in <p/> for great interoperability justice. */ jm->xhtml = g_strdup_printf("<html xmlns='" NS_XHTML_IM "'><body xmlns='" NS_XHTML "'><p>%s</p></body></html>", xhtml); int jabber_message_send_chat(PurpleConnection *gc, int id, PurpleMessage *msg) if (!gc || purple_message_is_empty(msg)) js = purple_connection_get_protocol_data(gc); chat = jabber_chat_find_by_id(js, id); jm = g_new0(JabberMessage, 1); jm->js = purple_connection_get_protocol_data(gc); jm->type = JABBER_MESSAGE_GROUPCHAT; jm->to = g_strdup_printf("%s@%s", chat->room, chat->server); jm->id = jabber_get_next_id(jm->js); tmp = purple_utf8_strip_unprintables(purple_message_get_contents(msg)); purple_markup_html_to_xhtml(tmp, &xhtml, &jm->body); tmp = jabber_message_smileyfy_xhtml(jm, xhtml); if (chat->xhtml && !jabber_xhtml_plain_equal(xhtml, jm->body)) /* Wrap the message in <p/> for greater interoperability justice. */ jm->xhtml = g_strdup_printf("<html xmlns='" NS_XHTML_IM "'><body xmlns='" NS_XHTML "'><p>%s</p></body></html>", xhtml); unsigned int jabber_send_typing(PurpleConnection *gc, const char *who, PurpleIMTypingState state) JabberBuddyResource *jbr; js = purple_connection_get_protocol_data(gc); jb = jabber_buddy_find(js, who, TRUE); resource = jabber_get_resource(who); jbr = jabber_buddy_find_resource(jb, resource); /* We know this entity doesn't support chat states */ if (jbr && jbr->chat_states == JABBER_CHAT_STATES_UNSUPPORTED) /* *If* we don't have presence /and/ the buddy can't see our * presence, don't send typing notifications. if (!jbr && !(jb->subscription & JABBER_SUB_FROM)) /* TODO: figure out threading */ jm = g_new0(JabberMessage, 1); jm->type = JABBER_MESSAGE_CHAT; jm->id = jabber_get_next_id(jm->js); if(PURPLE_IM_TYPING == state) jm->chat_state = JM_STATE_COMPOSING; else if(PURPLE_IM_TYPED == state) jm->chat_state = JM_STATE_PAUSED; jm->chat_state = JM_STATE_ACTIVE; /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states) jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */ gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *namespace) { gboolean jabber_custom_smileys_isenabled(JabberStream *js, const gchar *namespace) PurpleAccount *account = purple_connection_get_account(js->gc); return purple_account_get_bool(account, "custom_smileys", TRUE);