Mon, 12 Sep 2016 08:55:35 -0500
gg: Protect against issues when closing while connecting
Since the GIOStream is cancelled when data is freed, any cancelled
callbacks are called after such data is freed. This patch guards against
cancelled calls by safely returning without accessing any freed data if
the connection has been cancelled (aka closed).
Futhermore, if GG tries to connect and is quickly disconnected,
ggp_tcpsocket_close() is never called. As far as I can tell, it's an
existing bug, but PurpleSockets both work differently when closing and
are closed by the connection if any leak. So the issue wasn't a major
problem. This patch lessens the issue by guarding against it, but it
should be fixed at some point.
/** * @file msg.c Message functions * * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * 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 "internal.h" #include "debug.h" #include "msn.h" #include "msg.h" #include "msnutils.h" #include "slpmsg.h" #include "slpmsg_part.h" MsnMessage * msn_message_new(MsnMsgType type) { MsnMessage *msg; msg = g_new0(MsnMessage, 1); msg->type = type; if (purple_debug_is_verbose()) purple_debug_info("msn", "message new (%p)(%d)\n", msg, type); msg->header_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); msn_message_ref(msg); return msg; } /** * Destroys a message. * * @param msg The message to destroy. */ static void msn_message_destroy(MsnMessage *msg) { g_return_if_fail(msg != NULL); if (purple_debug_is_verbose()) purple_debug_info("msn", "message destroy (%p)\n", msg); g_free(msg->remote_user); g_free(msg->body); g_free(msg->content_type); g_free(msg->charset); g_hash_table_destroy(msg->header_table); g_list_free(msg->header_list); if (msg->part) msn_slpmsgpart_unref(msg->part); g_free(msg); } MsnMessage * msn_message_ref(MsnMessage *msg) { g_return_val_if_fail(msg != NULL, NULL); msg->ref_count++; if (purple_debug_is_verbose()) purple_debug_info("msn", "message ref (%p)[%u]\n", msg, msg->ref_count); return msg; } void msn_message_unref(MsnMessage *msg) { g_return_if_fail(msg != NULL); g_return_if_fail(msg->ref_count > 0); msg->ref_count--; if (purple_debug_is_verbose()) purple_debug_info("msn", "message unref (%p)[%u]\n", msg, msg->ref_count); if (msg->ref_count == 0) msn_message_destroy(msg); } MsnMessage * msn_message_new_plain(const char *message) { MsnMessage *msg; char *message_cr; msg = msn_message_new(MSN_MSG_TEXT); msg->retries = 1; msn_message_set_header(msg, "User-Agent", PACKAGE_NAME "/" VERSION); msn_message_set_content_type(msg, "text/plain"); msn_message_set_charset(msg, "UTF-8"); msn_message_set_flag(msg, 'A'); msn_message_set_header(msg, "X-MMS-IM-Format", "FN=Segoe%20UI; EF=; CO=0; CS=1;PF=0"); message_cr = purple_str_add_cr(message); msn_message_set_bin_data(msg, message_cr, strlen(message_cr)); g_free(message_cr); return msg; } MsnMessage * msn_message_new_msnslp(void) { MsnMessage *msg; msg = msn_message_new(MSN_MSG_SLP); msn_message_set_header(msg, "User-Agent", NULL); msn_message_set_flag(msg, 'D'); msn_message_set_content_type(msg, "application/x-msnmsgrp2p"); return msg; } MsnMessage * msn_message_new_nudge(void) { MsnMessage *msg; msg = msn_message_new(MSN_MSG_NUDGE); msn_message_set_content_type(msg, "text/x-msnmsgr-datacast"); msn_message_set_flag(msg, 'N'); msn_message_set_bin_data(msg, "ID: 1\r\n", 7); return msg; } void msn_message_parse_payload(MsnMessage *msg, const char *payload, size_t payload_len, const char *line_dem,const char *body_dem) { char *tmp_base, *tmp; const char *content_type; char *end; char **elems, **cur, **tokens; g_return_if_fail(payload != NULL); tmp_base = tmp = g_malloc(payload_len + 1); memcpy(tmp_base, payload, payload_len); tmp_base[payload_len] = '\0'; /* Find the end of the headers */ end = strstr(tmp, body_dem); /* TODO? some clients use \r delimiters instead of \r\n, the official client * doesn't send such messages, but does handle receiving them. We'll just * avoid crashing for now */ if (end == NULL) { g_free(tmp_base); g_return_if_reached(); } /* NUL-terminate the end of the headers - it'll get skipped over below */ *end = '\0'; /* Split the headers and parse each one */ elems = g_strsplit(tmp, line_dem, 0); for (cur = elems; *cur != NULL; cur++) { const char *key, *value; /* If this line starts with whitespace, it's been folded from the previous line and won't have ':'. */ if ((**cur == ' ') || (**cur == '\t')) { tokens = g_strsplit(g_strchug(*cur), "=\"", 2); key = tokens[0]; value = tokens[1]; /* The only one I care about is 'boundary' (which is folded from the key 'Content-Type'), so only process that. */ if (!strcmp(key, "boundary") && value) { char *end = strchr(value, '\"'); if (end) { *end = '\0'; msn_message_set_header(msg, key, value); } } g_strfreev(tokens); continue; } tokens = g_strsplit(*cur, ": ", 2); key = tokens[0]; value = tokens[1]; if (!strcmp(key, "MIME-Version")) { /* Ignore MIME-Version header */ } else if (!strcmp(key, "Content-Type")) { char *charset, *c; if (value && (c = strchr(value, ';')) != NULL) { if ((charset = strchr(c, '=')) != NULL) { charset++; msn_message_set_charset(msg, charset); } *c = '\0'; } msn_message_set_content_type(msg, value); } else { msn_message_set_header(msg, key, value); } g_strfreev(tokens); } g_strfreev(elems); /* Proceed to the end of the "\r\n\r\n" */ tmp = end + strlen(body_dem); /* Now we *should* be at the body. */ content_type = msn_message_get_content_type(msg); if (payload_len - (tmp - tmp_base) > 0) { msg->body_len = payload_len - (tmp - tmp_base); g_free(msg->body); msg->body = g_malloc(msg->body_len + 1); memcpy(msg->body, tmp, msg->body_len); msg->body[msg->body_len] = '\0'; } if (msg->body && content_type && purple_str_has_prefix(content_type, "text/")) { char *body = NULL; if (msg->charset == NULL || g_str_equal(msg->charset, "UTF-8")) { /* Charset is UTF-8 */ if (!g_utf8_validate(msg->body, msg->body_len, NULL)) { purple_debug_warning("msn", "Message contains invalid " "UTF-8. Attempting to salvage.\n"); body = purple_utf8_salvage(msg->body); payload_len = strlen(body); } } else { /* Charset is something other than UTF-8 */ GError *err = NULL; body = g_convert(msg->body, msg->body_len, "UTF-8", msg->charset, NULL, &payload_len, &err); if (!body || err) { purple_debug_warning("msn", "Unable to convert message from " "%s to UTF-8: %s\n", msg->charset, err ? err->message : "Unknown error"); if (err) g_error_free(err); /* Fallback to ISO-8859-1 */ g_free(body); body = g_convert(msg->body, msg->body_len, "UTF-8", "ISO-8859-1", NULL, &payload_len, NULL); if (!body) { g_free(msg->body); msg->body = NULL; msg->body_len = 0; } } } if (body) { g_free(msg->body); msg->body = body; msg->body_len = payload_len; msn_message_set_charset(msg, "UTF-8"); } } g_free(tmp_base); } MsnMessage * msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd) { MsnMessage *msg; g_return_val_if_fail(cmd != NULL, NULL); msg = msn_message_new(MSN_MSG_UNKNOWN); msg->remote_user = g_strdup(cmd->params[0]); /* msg->size = atoi(cmd->params[2]); */ msg->cmd = cmd; return msg; } char * msn_message_gen_payload(MsnMessage *msg, size_t *ret_size) { GString *payload; GList *l; size_t body_len; const void *body; g_return_val_if_fail(msg != NULL, NULL); /* 8192 is a reasonable guess at a large enough buffer to avoid realloc */ payload = g_string_sized_new(8192); /* Standard header */ if (msg->charset == NULL) { g_string_append_printf(payload, "MIME-Version: 1.0\r\n" "Content-Type: %s\r\n", msg->content_type); } else { g_string_append_printf(payload, "MIME-Version: 1.0\r\n" "Content-Type: %s; charset=%s\r\n", msg->content_type, msg->charset); } /* Headers */ for (l = msg->header_list; l != NULL; l = l->next) { const char *key; const char *value; key = l->data; value = msn_message_get_header_value(msg, key); g_string_append_printf(payload, "%s: %s\r\n", key, value); } /* End of headers */ g_string_append(payload, "\r\n"); /* Body */ body = msn_message_get_bin_data(msg, &body_len); if (body != NULL) { g_string_append_len(payload, body, body_len); } if (ret_size != NULL) { /* Use MIN to truncate the payload to 1664 bytes? Why do we do this? It seems like it will lead to brokenness. */ *ret_size = MIN(payload->len, 1664); } return g_string_free(payload, FALSE); } void msn_message_set_flag(MsnMessage *msg, char flag) { g_return_if_fail(msg != NULL); g_return_if_fail(flag != 0); msg->flag = flag; } char msn_message_get_flag(const MsnMessage *msg) { g_return_val_if_fail(msg != NULL, 0); return msg->flag; } void msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len) { g_return_if_fail(msg != NULL); /* There is no need to waste memory on data we cannot send anyway */ if (len > 1664) len = 1664; g_free(msg->body); if (data != NULL && len > 0) { msg->body = g_malloc(len + 1); memcpy(msg->body, data, len); msg->body[len] = '\0'; msg->body_len = len; } else { msg->body = NULL; msg->body_len = 0; } } const void * msn_message_get_bin_data(const MsnMessage *msg, size_t *len) { g_return_val_if_fail(msg != NULL, NULL); if (len) *len = msg->body_len; return msg->body; } void msn_message_set_content_type(MsnMessage *msg, const char *type) { g_return_if_fail(msg != NULL); g_free(msg->content_type); msg->content_type = g_strdup(type); } const char * msn_message_get_content_type(const MsnMessage *msg) { g_return_val_if_fail(msg != NULL, NULL); return msg->content_type; } void msn_message_set_charset(MsnMessage *msg, const char *charset) { g_return_if_fail(msg != NULL); g_free(msg->charset); msg->charset = g_strdup(charset); } const char * msn_message_get_charset(const MsnMessage *msg) { g_return_val_if_fail(msg != NULL, NULL); return msg->charset; } void msn_message_set_header(MsnMessage *msg, const char *name, const char *value) { const char *temp; char *new_name; g_return_if_fail(msg != NULL); g_return_if_fail(name != NULL); temp = msn_message_get_header_value(msg, name); if (value == NULL) { if (temp != NULL) { GList *l; for (l = msg->header_list; l != NULL; l = l->next) { if (!g_ascii_strcasecmp(l->data, name)) { msg->header_list = g_list_remove(msg->header_list, l->data); break; } } g_hash_table_remove(msg->header_table, name); } return; } new_name = g_strdup(name); g_hash_table_insert(msg->header_table, new_name, g_strdup(value)); if (temp == NULL) msg->header_list = g_list_append(msg->header_list, new_name); } const char * msn_message_get_header_value(const MsnMessage *msg, const char *name) { g_return_val_if_fail(msg != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); return g_hash_table_lookup(msg->header_table, name); } GHashTable * msn_message_get_hashtable_from_body(const MsnMessage *msg) { GHashTable *table; size_t body_len; const char *body; char **elems, **cur, **tokens, *body_str; g_return_val_if_fail(msg != NULL, NULL); table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); body = msn_message_get_bin_data(msg, &body_len); g_return_val_if_fail(body != NULL, NULL); body_str = g_strndup(body, body_len); elems = g_strsplit(body_str, "\r\n", 0); g_free(body_str); for (cur = elems; *cur != NULL; cur++) { if (**cur == '\0') break; tokens = g_strsplit(*cur, ": ", 2); if (tokens[0] != NULL && tokens[1] != NULL) { g_hash_table_insert(table, tokens[0], tokens[1]); g_free(tokens); } else g_strfreev(tokens); } g_strfreev(elems); return table; } char * msn_message_to_string(MsnMessage *msg) { size_t body_len; const char *body; g_return_val_if_fail(msg != NULL, NULL); g_return_val_if_fail(msg->type == MSN_MSG_TEXT, NULL); body = msn_message_get_bin_data(msg, &body_len); return g_strndup(body, body_len); } void msn_message_show_readable(MsnMessage *msg, const char *info, gboolean text_body) { GString *str; size_t body_len; const char *body; GList *l; g_return_if_fail(msg != NULL); str = g_string_new(NULL); /* Standard header. */ if (msg->charset == NULL) { g_string_append_printf(str, "MIME-Version: 1.0\r\n" "Content-Type: %s\r\n", msg->content_type); } else { g_string_append_printf(str, "MIME-Version: 1.0\r\n" "Content-Type: %s; charset=%s\r\n", msg->content_type, msg->charset); } for (l = msg->header_list; l; l = l->next) { char *key; const char *value; key = l->data; value = msn_message_get_header_value(msg, key); g_string_append_printf(str, "%s: %s\r\n", key, value); } g_string_append(str, "\r\n"); body = msn_message_get_bin_data(msg, &body_len); if (body != NULL) { if (msg->type == MSN_MSG_TEXT) { g_string_append_len(str, body, body_len); g_string_append(str, "\r\n"); } else { size_t i; for (i = 0; i < body_len; i++, body++) { g_string_append_printf(str, "%02x ", (unsigned char)*body); if (i % 16 == 0 && i != 0) g_string_append_c(str, '\n'); } g_string_append_c(str, '\n'); } } purple_debug_info("msn", "Message %s:\n{%s}\n", info, str->str); g_string_free(str, TRUE); } /************************************************************************** * Message Handlers **************************************************************************/ void msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { PurpleConnection *gc; const char *body; char *body_enc; char *body_final; size_t body_len; const char *passport; const char *value; gc = purple_account_get_connection(cmdproc->session->account); body = msn_message_get_bin_data(msg, &body_len); body_enc = g_markup_escape_text(body, body_len); passport = msg->remote_user; if (!strcmp(passport, "messenger@microsoft.com") && strstr(body, "immediate security update")) { return; } #if 0 if ((value = msn_message_get_header_value(msg, "User-Agent")) != NULL) { purple_debug_misc("msn", "User-Agent = '%s'\n", value); } #endif if ((value = msn_message_get_header_value(msg, "X-MMS-IM-Format")) != NULL) { char *pre, *post; msn_parse_format(value, &pre, &post); body_final = g_strdup_printf("%s%s%s", pre ? pre : "", body_enc ? body_enc : "", post ? post : ""); g_free(pre); g_free(post); g_free(body_enc); } else { body_final = body_enc; } if (cmdproc->servconn->type == MSN_SERVCONN_SB) { MsnSwitchBoard *swboard = cmdproc->data; swboard->flag |= MSN_SB_FLAG_IM; if (swboard->current_users > 1 || ((swboard->conv != NULL) && PURPLE_IS_CHAT_CONVERSATION(swboard->conv))) { /* If current_users is always ok as it should then there is no need to * check if this is a chat. */ if (swboard->current_users <= 1) purple_debug_misc("msn", "plain_msg: current_users(%d)\n", swboard->current_users); purple_serv_got_chat_in(gc, swboard->chat_id, passport, PURPLE_MESSAGE_RECV, body_final, time(NULL)); if (swboard->conv == NULL) { swboard->conv = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, swboard->chat_id)); swboard->flag |= MSN_SB_FLAG_IM; } } else if (!g_str_equal(passport, purple_account_get_username(purple_connection_get_account(gc)))) { /* Don't im ourselves ... */ purple_serv_got_im(gc, passport, body_final, 0, time(NULL)); if (swboard->conv == NULL) { swboard->conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account( passport, purple_connection_get_account(gc))); swboard->flag |= MSN_SB_FLAG_IM; } } } else { purple_serv_got_im(gc, passport, body_final, 0, time(NULL)); } g_free(body_final); } void msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { PurpleConnection *gc; char *passport; gc = purple_account_get_connection(cmdproc->session->account); passport = msg->remote_user; if (msn_message_get_header_value(msg, "TypingUser") == NULL) return; if (cmdproc->servconn->type == MSN_SERVCONN_SB) { MsnSwitchBoard *swboard = cmdproc->data; if (swboard->current_users == 1) { purple_serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT, PURPLE_IM_TYPING); } } else { purple_serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT, PURPLE_IM_TYPING); } } static void datacast_inform_user(MsnSwitchBoard *swboard, const char *who, const char *msg, const char *filename) { char *username, *str; PurpleAccount *account; PurpleBuddy *b; PurpleConnection *pc; gboolean chat; account = swboard->session->account; pc = purple_account_get_connection(account); if ((b = purple_blist_find_buddy(account, who)) != NULL) username = g_markup_escape_text(purple_buddy_get_alias(b), -1); else username = g_markup_escape_text(who, -1); str = g_strdup_printf(msg, username, filename); g_free(username); swboard->flag |= MSN_SB_FLAG_IM; if (swboard->current_users > 1) chat = TRUE; else chat = FALSE; if (swboard->conv == NULL) { if (chat) swboard->conv = PURPLE_CONVERSATION(purple_conversations_find_chat( purple_account_get_connection(account), swboard->chat_id)); else { swboard->conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account( who, account)); if (swboard->conv == NULL) swboard->conv = PURPLE_CONVERSATION(purple_im_conversation_new(account, who)); } } if (chat) purple_serv_got_chat_in(pc, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(swboard->conv)), who, PURPLE_MESSAGE_RECV|PURPLE_MESSAGE_SYSTEM, str, time(NULL)); else purple_serv_got_im(pc, who, str, PURPLE_MESSAGE_RECV|PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(str); } /* TODO: Make these not be such duplicates of each other */ static void got_wink_cb(MsnSlpCall *slpcall, const guchar *data, gsize size) { FILE *f = NULL; char *path = NULL; const char *who = slpcall->slplink->remote_user; purple_debug_info("msn", "Received wink from %s\n", who); if ((f = purple_mkstemp(&path, TRUE)) && (fwrite(data, 1, size, f) == size)) { datacast_inform_user(slpcall->slplink->swboard, who, _("%s sent a wink. <a href='msn-wink://%s'>Click here to play it</a>"), path); } else { purple_debug_error("msn", "Couldn\'t create temp file to store wink\n"); datacast_inform_user(slpcall->slplink->swboard, who, _("%s sent a wink, but it could not be saved"), NULL); } if (f) fclose(f); g_free(path); } static void got_voiceclip_cb(MsnSlpCall *slpcall, const guchar *data, gsize size) { FILE *f = NULL; char *path = NULL; const char *who = slpcall->slplink->remote_user; purple_debug_info("msn", "Received voice clip from %s\n", who); if ((f = purple_mkstemp(&path, TRUE)) && (fwrite(data, 1, size, f) == size)) { datacast_inform_user(slpcall->slplink->swboard, who, _("%s sent a voice clip. <a href='audio://%s'>Click here to play it</a>"), path); } else { purple_debug_error("msn", "Couldn\'t create temp file to store sound\n"); datacast_inform_user(slpcall->slplink->swboard, who, _("%s sent a voice clip, but it could not be saved"), NULL); } if (f) fclose(f); g_free(path); } void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { MsnSession *session; MsnSlpLink *slplink; MsnP2PVersion p2p; session = cmdproc->servconn->session; slplink = msn_session_get_slplink(session, msg->remote_user); if (slplink->swboard == NULL) { /* * We will need swboard in order to change its flags. If its * NULL, something has probably gone wrong earlier on. I * didn't want to do this, but MSN 7 is somehow causing us * to crash here, I couldn't reproduce it to debug more, * and people are reporting bugs. Hopefully this doesn't * cause more crashes. Stu. */ if (cmdproc->data == NULL) g_warning("msn_p2p_msg cmdproc->data was NULL\n"); else { slplink->swboard = (MsnSwitchBoard *)cmdproc->data; slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); } } p2p = msn_slplink_get_p2p_version(slplink); msg->part = msn_slpmsgpart_new_from_data(p2p, msg->body, msg->body_len); if (msg->part) msn_slplink_process_msg(slplink, msg->part); else purple_debug_warning("msn", "P2P message failed to parse.\n"); } static void got_emoticon(MsnSlpCall *slpcall, const guchar *data, gsize size) { PurpleConversation *conv; PurpleSmiley *smiley; PurpleImage *image; MsnSwitchBoard *swboard; const gchar *shortcut; swboard = slpcall->slplink->swboard; conv = swboard->conv; shortcut = slpcall->data_info; purple_debug_info("msn", "got smiley: %s", shortcut); if (!conv) return; smiley = purple_conversation_get_remote_smiley(conv, shortcut); g_return_if_fail(smiley); image = purple_smiley_get_image(smiley); /* FIXME: it would be better if we wrote the data as we received it * instead of all at once, calling write multiple times and close once * at the very end. */ purple_image_transfer_write(image, (gpointer)data, size); purple_image_transfer_close(image); } void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { MsnSession *session; MsnSlpLink *slplink; MsnSwitchBoard *swboard; MsnObject *obj; char **tokens; char *smile, *body_str; const char *body, *who; /*, *sha1;*/ guint tok; size_t body_len; PurpleConversation *conv; session = cmdproc->servconn->session; if (!purple_account_get_bool(session->account, "custom_smileys", TRUE)) return; swboard = cmdproc->data; conv = swboard->conv; body = msn_message_get_bin_data(msg, &body_len); if (!body || !body_len) return; body_str = g_strndup(body, body_len); /* MSN Messenger 7 may send more than one MSNObject in a single message... * Maybe 10 tokens is a reasonable max value. */ tokens = g_strsplit(body_str, "\t", 10); g_free(body_str); for (tok = 0; tok < 9; tok += 2) { PurpleSmiley *smiley; if (tokens[tok] == NULL || tokens[tok + 1] == NULL) { break; } smile = tokens[tok]; obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1])); if (obj == NULL) break; who = msn_object_get_creator(obj); #if 0 sha1 = msn_object_get_sha1(obj); #endif slplink = msn_session_get_slplink(session, who); if (slplink->swboard != swboard) { if (slplink->swboard != NULL) /* * Apparently we're using a different switchboard now or * something? I don't know if this is normal, but it * definitely happens. So make sure the old switchboard * doesn't still have a reference to us. */ slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink); slplink->swboard = swboard; slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); } /* If the conversation doesn't exist then this is a custom smiley * used in the first message in a MSN conversation: we need to create * the conversation now, otherwise the custom smiley won't be shown. * This happens because every PurpleConversation has its own smiley * tree: if the conversation doesn't exist then we cannot associate * the new smiley with its PurpleConversation. */ if (!conv) { conv = PURPLE_CONVERSATION(purple_im_conversation_new(session->account, who)); } smiley = purple_conversation_add_remote_smiley(conv, smile); if (smiley) { /* if not - it was already present */ /* TODO: cache lookup by "sha1" */ /* XXX: maybe handle end_cb and smiley download failures? */ purple_debug_info("msn", "requesting smiley: %s", smile); msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj); } msn_object_destroy(obj, FALSE); obj = NULL; who = NULL; #if 0 sha1 = NULL; #endif } g_strfreev(tokens); } void msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { GHashTable *body; const char *id; body = msn_message_get_hashtable_from_body(msg); id = g_hash_table_lookup(body, "ID"); if (!strcmp(id, "1")) { /* Nudge */ PurpleAccount *account; const char *user; PurpleConnection *gc; account = cmdproc->session->account; user = msg->remote_user; gc = purple_account_get_connection(account); if (cmdproc->servconn->type == MSN_SERVCONN_SB) { MsnSwitchBoard *swboard = cmdproc->data; if (swboard->current_users > 1 || ((swboard->conv != NULL) && PURPLE_IS_CHAT_CONVERSATION(swboard->conv))) purple_protocol_got_attention_in_chat(gc, swboard->chat_id, user, MSN_NUDGE); else purple_protocol_got_attention(gc, user, MSN_NUDGE); } else { purple_protocol_got_attention(gc, user, MSN_NUDGE); } } else if (!strcmp(id, "2")) { /* Wink */ MsnSession *session; MsnSlpLink *slplink; MsnObject *obj; const char *who; const char *data; session = cmdproc->session; data = g_hash_table_lookup(body, "Data"); obj = msn_object_new_from_string(data); who = msn_object_get_creator(obj); slplink = msn_session_get_slplink(session, who); msn_slplink_request_object(slplink, data, got_wink_cb, NULL, obj); msn_object_destroy(obj, FALSE); } else if (!strcmp(id, "3")) { /* Voiceclip */ MsnSession *session; MsnSlpLink *slplink; MsnObject *obj; const char *who; const char *data; session = cmdproc->session; data = g_hash_table_lookup(body, "Data"); obj = msn_object_new_from_string(data); who = msn_object_get_creator(obj); slplink = msn_session_get_slplink(session, who); msn_slplink_request_object(slplink, data, got_voiceclip_cb, NULL, obj); msn_object_destroy(obj, FALSE); } else if (!strcmp(id, "4")) { /* Action */ } else { purple_debug_warning("msn", "Got unknown datacast with ID %s.\n", id); } g_hash_table_destroy(body); } void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { GHashTable *body; const gchar *command; const gchar *cookie; gboolean accepted = FALSE; g_return_if_fail(cmdproc != NULL); g_return_if_fail(msg != NULL); body = msn_message_get_hashtable_from_body(msg); if (body == NULL) { purple_debug_warning("msn", "Unable to parse invite msg body.\n"); return; } /* * GUID is NOT always present but Invitation-Command and Invitation-Cookie * are mandatory. */ command = g_hash_table_lookup(body, "Invitation-Command"); cookie = g_hash_table_lookup(body, "Invitation-Cookie"); if (command == NULL || cookie == NULL) { purple_debug_warning("msn", "Invalid invitation message: either Invitation-Command " "or Invitation-Cookie is missing or invalid.\n" ); return; } else if (!strcmp(command, "INVITE")) { const gchar *guid = g_hash_table_lookup(body, "Application-GUID"); if (guid == NULL) { purple_debug_warning("msn", "Invite msg missing Application-GUID.\n"); accepted = TRUE; } else if (!strcmp(guid, MSN_FT_GUID)) { } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { purple_debug_info("msn", "Computer call\n"); if (cmdproc->session) { PurpleIMConversation *im = NULL; gchar *from = msg->remote_user; gchar *buf = NULL; if (from) im = purple_conversations_find_im_with_account( from, cmdproc->session->account); if (im) buf = g_strdup_printf( _("%s sent you a voice chat " "invite, which is not yet " "supported."), from); if (buf) { purple_conversation_write_system_message( PURPLE_CONVERSATION(im), buf, PURPLE_MESSAGE_NOTIFY); g_free(buf); } } } else { const gchar *application = g_hash_table_lookup(body, "Application-Name"); purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n", guid, application ? application : "(null)"); } if (!accepted) { MsnSwitchBoard *swboard = cmdproc->data; char *text; MsnMessage *cancel; cancel = msn_message_new(MSN_MSG_TEXT); msn_message_set_content_type(cancel, "text/x-msmsgsinvite"); msn_message_set_charset(cancel, "UTF-8"); msn_message_set_flag(cancel, 'U'); text = g_strdup_printf("Invitation-Command: CANCEL\r\n" "Invitation-Cookie: %s\r\n" "Cancel-Code: REJECT_NOT_INSTALLED\r\n", cookie); msn_message_set_bin_data(cancel, text, strlen(text)); g_free(text); msn_switchboard_send_msg(swboard, cancel, TRUE); msn_message_unref(cancel); } } else if (!strcmp(command, "CANCEL")) { const gchar *code = g_hash_table_lookup(body, "Cancel-Code"); purple_debug_info("msn", "MSMSGS invitation cancelled: %s.\n", code ? code : "no reason given"); } else { /* * Some other already established invitation session. * Can be retrieved by Invitation-Cookie. */ } g_hash_table_destroy(body); } /* Only called from chats. Handwritten messages for IMs come as a SLP message */ void msn_handwritten_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { const char *body; size_t body_len; body = msn_message_get_bin_data(msg, &body_len); msn_switchboard_show_ink(cmdproc->data, msg->remote_user, body); }