# HG changeset patch # User Elliott Sales de Andrade # Date 1268197955 0 # Node ID 01ed1337dddbdb8ec25af1218a74837c6db23eaf # Parent 9472a82eb553ab53690619dfe57df2d7c39ebe52# Parent 9d1794da1d1087f9659a951f44c2d1733afe86e6 merge of '60c9671daa2b521f9de4c0980f8798876959570e' and '78026d85fc783da9d0cb93283729d22e5248c42b' diff -r 9472a82eb553 -r 01ed1337dddb ChangeLog --- a/ChangeLog Wed Mar 10 05:10:43 2010 +0000 +++ b/ChangeLog Wed Mar 10 05:12:35 2010 +0000 @@ -32,6 +32,11 @@ * X-Status (Custom ICQ status icon) support (Andrew Ivanov, Tomáš Kebert, Yuriy Yevgrafov, and trac users bob007, salieff, and nops) + XMPP: + * Direct messages to a specific resource only upon receipt of a message + with content (as opposed to a typing notification, etc). (Thanks to + rjoly for testing) + version 2.6.6 (02/18/2010): libpurple: * Fix 'make check' on OS X. (David Fang) diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/bosh.c --- a/libpurple/protocols/jabber/bosh.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/bosh.c Wed Mar 10 05:12:35 2010 +0000 @@ -521,7 +521,7 @@ } if (version) { - const char *dot = strstr(version, "."); + const char *dot = strchr(version, '.'); int major, minor = 0; purple_debug_info("jabber", "BOSH connection manager version %s\n", version); diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/buddy.c Wed Mar 10 05:12:35 2010 +0000 @@ -1824,7 +1824,8 @@ if(!jb) return m; - if (js->protocol_version == JABBER_PROTO_0_9 && jb != js->user_jb) { + if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 && + jb != js->user_jb) { if(jb->invisible & JABBER_INVIS_BUDDY) { act = purple_menu_action_new(_("Un-hide From"), PURPLE_CALLBACK(jabber_buddy_make_visible), diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/data.c --- a/libpurple/protocols/jabber/data.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/data.c Wed Mar 10 05:12:35 2010 +0000 @@ -39,7 +39,7 @@ JabberStream *js) { JabberData *data = g_new0(JabberData, 1); - gchar *checksum = purple_util_get_image_checksum(rawdata, size); + gchar *checksum = jabber_calculate_data_sha1sum(rawdata, size); gchar cid[256]; g_snprintf(cid, sizeof(cid), "sha1+%s@bob.xmpp.org", checksum); diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Mar 10 05:12:35 2010 +0000 @@ -74,7 +74,10 @@ GList *jabber_features = NULL; GList *jabber_identities = NULL; -static GSList *jabber_cmds = NULL; + +static GHashTable *jabber_cmds = NULL; /* PurplePlugin * => GSList of ids */ + +static gint plugin_ref = 0; static void jabber_unregister_account_cb(JabberStream *js); static void try_srv_connect(JabberStream *js); @@ -867,7 +870,8 @@ js->old_length = 0; js->keepalive_timeout = 0; /* Set the default protocol version to 1.0. Overridden in parser.c. */ - js->protocol_version = JABBER_PROTO_1_0; + js->protocol_version.major = 1; + js->protocol_version.minor = 0; js->sessions = NULL; js->stun_ip = NULL; js->stun_port = 0; @@ -3330,6 +3334,8 @@ } else { jabber_mood_set(js, args[0], args[1]); } + + return PURPLE_CMD_RET_OK; } else { /* account does not support PEP, can't set a mood */ purple_conversation_write(conv, NULL, @@ -3337,44 +3343,43 @@ PURPLE_MESSAGE_ERROR, time(NULL)); return PURPLE_CMD_RET_FAILED; } - - return PURPLE_CMD_STATUS_OK; } -void jabber_register_commands(void) +static void jabber_register_commands(PurplePlugin *plugin) { + GSList *commands = NULL; PurpleCmdId id; id = purple_cmd_register("config", "", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-jabber", jabber_cmd_chat_config, _("config: Configure a chat room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-jabber", jabber_cmd_chat_config, _("configure: Configure a chat room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-jabber", jabber_cmd_chat_nick, _("nick <new nickname>: Change your nickname."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", jabber_cmd_chat_part, _("part [message]: Leave the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("register", "", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-jabber", jabber_cmd_chat_register, _("register: Register with a chat room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); /* XXX: there needs to be a core /topic cmd, methinks */ id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL, @@ -3383,7 +3388,7 @@ jabber_cmd_chat_topic, _("topic [new topic]: View or change the topic."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3391,7 +3396,7 @@ jabber_cmd_chat_ban, _("ban <user> [reason]: Ban a user from the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3399,7 +3404,7 @@ jabber_cmd_chat_affiliate, _("affiliate <owner|admin|member|outcast|none> [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3407,7 +3412,7 @@ jabber_cmd_chat_role, _("role <moderator|participant|visitor|none> [nick1] [nick2] ...: Get the users with a role or set users' role with the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3415,7 +3420,7 @@ jabber_cmd_chat_invite, _("invite <user> [message]: Invite a user to the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3423,7 +3428,7 @@ jabber_cmd_chat_join, _("join: <room> [password]: Join a chat on this server."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | @@ -3431,14 +3436,14 @@ jabber_cmd_chat_kick, _("kick <user> [reason]: Kick a user from the room."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-jabber", jabber_cmd_chat_msg, _("msg <user> <message>: Send a private message to another user."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | @@ -3446,31 +3451,39 @@ "prpl-jabber", jabber_cmd_ping, _("ping <jid>: Ping a user/component/server."), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", jabber_cmd_buzz, _("buzz: Buzz a user to get their attention"), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("mood", "ws", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", jabber_cmd_mood, _("mood: Set current user mood"), NULL); - jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); + commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); + + g_hash_table_insert(jabber_cmds, plugin, commands); } -void jabber_unregister_commands(void) +static void cmds_free_func(gpointer value) { - while (jabber_cmds != NULL) { - purple_cmd_unregister(GPOINTER_TO_UINT(jabber_cmds->data)); - jabber_cmds = g_slist_delete_link(jabber_cmds, jabber_cmds); + GSList *commands = value; + while (commands) { + purple_cmd_unregister(GPOINTER_TO_UINT(commands->data)); + commands = g_slist_delete_link(commands, commands); } } +static void jabber_unregister_commands(PurplePlugin *plugin) +{ + g_hash_table_remove(jabber_cmds, plugin); +} + /* IPC functions */ /** @@ -3519,14 +3532,46 @@ jabber_caps_broadcast_change(); } -void -jabber_init_plugin(PurplePlugin *plugin) +static void +jabber_do_init(void) { GHashTable *ui_info = purple_core_get_ui_info(); const gchar *ui_type; const gchar *type = "pc"; /* default client type, if unknown or unspecified */ const gchar *ui_name = NULL; +#ifdef HAVE_CYRUS_SASL + /* We really really only want to do this once per process */ + static gboolean sasl_initialized = FALSE; +#ifdef _WIN32 + UINT old_error_mode; + gchar *sasldir; +#endif + int ret; +#endif + + /* XXX - If any other plugin wants SASL this won't be good ... */ +#ifdef HAVE_CYRUS_SASL + if (!sasl_initialized) { + sasl_initialized = TRUE; +#ifdef _WIN32 + sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL); + sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir); + g_free(sasldir); + /* Suppress error popups for failing to load sasl plugins */ + old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS); +#endif + if ((ret = sasl_client_init(NULL)) != SASL_OK) { + purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret); + } +#ifdef _WIN32 + /* Restore the original error mode */ + SetErrorMode(old_error_mode); +#endif + } +#endif + + jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func); ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL; if (ui_type) { @@ -3590,7 +3635,53 @@ G_CALLBACK(jabber_caps_broadcast_change), NULL); #endif + /* reverse order of unload_plugin */ + jabber_iq_init(); + jabber_presence_init(); + jabber_caps_init(); + /* PEP things should be init via jabber_pep_init, not here */ + jabber_pep_init(); + jabber_data_init(); + jabber_bosh_init(); + + /* TODO: Implement adding and retrieving own features via IPC API */ + + jabber_ibb_init(); + jabber_si_init(); + jabber_auth_init(); +} + +static void +jabber_do_uninit(void) +{ + /* reverse order of jabber_do_init */ + jabber_bosh_uninit(); + jabber_data_uninit(); + jabber_si_uninit(); + jabber_ibb_uninit(); + /* PEP things should be uninit via jabber_pep_uninit, not here */ + jabber_pep_uninit(); + jabber_caps_uninit(); + jabber_presence_uninit(); + jabber_iq_uninit(); + + jabber_auth_uninit(); + jabber_features_destroy(); + jabber_identities_destroy(); + + g_hash_table_destroy(jabber_cmds); + jabber_cmds = NULL; +} + +void jabber_plugin_init(PurplePlugin *plugin) +{ + ++plugin_ref; + + if (plugin_ref == 1) + jabber_do_init(); + + jabber_register_commands(plugin); /* IPC functions */ purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature), @@ -3605,7 +3696,6 @@ NULL, 1, purple_value_new(PURPLE_TYPE_STRING)); - /* Modifying these? Look at libxmpp.c:load_plugin for the signal versions */ purple_plugin_ipc_register(plugin, "register_namespace_watcher", PURPLE_CALLBACK(jabber_iq_signal_register), purple_marshal_VOID__POINTER_POINTER, @@ -3619,14 +3709,95 @@ NULL, 2, purple_value_new(PURPLE_TYPE_STRING), /* node */ purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ + + purple_signal_register(plugin, "jabber-register-namespace-watcher", + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), /* node */ + purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ + + purple_signal_register(plugin, "jabber-unregister-namespace-watcher", + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), /* node */ + purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ + + purple_signal_connect(plugin, "jabber-register-namespace-watcher", + plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL); + purple_signal_connect(plugin, "jabber-unregister-namespace-watcher", + plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL); + + + purple_signal_register(plugin, "jabber-receiving-xmlnode", + purple_marshal_VOID__POINTER_POINTER, NULL, 2, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + + purple_signal_register(plugin, "jabber-sending-xmlnode", + purple_marshal_VOID__POINTER_POINTER, NULL, 2, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + + /* + * Do not remove this or the plugin will fail. Completely. You have been + * warned! + */ + purple_signal_connect_priority(plugin, "jabber-sending-xmlnode", + plugin, PURPLE_CALLBACK(jabber_send_signal_cb), + NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST); + + purple_signal_register(plugin, "jabber-sending-text", + purple_marshal_VOID__POINTER_POINTER, NULL, 2, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new_outgoing(PURPLE_TYPE_STRING)); + + purple_signal_register(plugin, "jabber-receiving-message", + purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_BOOLEAN), 6, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_STRING), /* type */ + purple_value_new(PURPLE_TYPE_STRING), /* id */ + purple_value_new(PURPLE_TYPE_STRING), /* from */ + purple_value_new(PURPLE_TYPE_STRING), /* to */ + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + + purple_signal_register(plugin, "jabber-receiving-iq", + purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_BOOLEAN), 5, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_STRING), /* type */ + purple_value_new(PURPLE_TYPE_STRING), /* id */ + purple_value_new(PURPLE_TYPE_STRING), /* from */ + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + + purple_signal_register(plugin, "jabber-watched-iq", + purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_BOOLEAN), 5, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_STRING), /* type */ + purple_value_new(PURPLE_TYPE_STRING), /* id */ + purple_value_new(PURPLE_TYPE_STRING), /* from */ + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */ + + purple_signal_register(plugin, "jabber-receiving-presence", + purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_BOOLEAN), 4, + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_STRING), /* type */ + purple_value_new(PURPLE_TYPE_STRING), /* from */ + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); } -void -jabber_uninit_plugin(PurplePlugin *plugin) +void jabber_plugin_uninit(PurplePlugin *plugin) { + g_return_if_fail(plugin_ref > 0); + + purple_signals_unregister_by_instance(plugin); purple_plugin_ipc_unregister_all(plugin); - jabber_auth_uninit(); - jabber_features_destroy(); - jabber_identities_destroy(); + jabber_unregister_commands(plugin); + + --plugin_ref; + if (plugin_ref == 0) + jabber_do_uninit(); } diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Mar 10 05:12:35 2010 +0000 @@ -105,9 +105,9 @@ xmlParserCtxt *context; xmlnode *current; - enum { - JABBER_PROTO_0_9, - JABBER_PROTO_1_0 + struct { + guint8 major; + guint8 minor; } protocol_version; JabberSaslMech *auth_mech; @@ -376,10 +376,7 @@ PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who); gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who); -void jabber_register_commands(void); -void jabber_unregister_commands(void); - -void jabber_init_plugin(PurplePlugin *plugin); -void jabber_uninit_plugin(PurplePlugin *plugin); +void jabber_plugin_init(PurplePlugin *plugin); +void jabber_plugin_uninit(PurplePlugin *plugin); #endif /* PURPLE_JABBER_H_ */ diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Mar 10 05:12:35 2010 +0000 @@ -132,104 +132,14 @@ static gboolean load_plugin(PurplePlugin *plugin) { - purple_signal_register(plugin, "jabber-receiving-xmlnode", - purple_marshal_VOID__POINTER_POINTER, NULL, 2, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); - - purple_signal_register(plugin, "jabber-sending-xmlnode", - purple_marshal_VOID__POINTER_POINTER, NULL, 2, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); - - /* - * Do not remove this or the plugin will fail. Completely. You have been - * warned! - */ - purple_signal_connect_priority(plugin, "jabber-sending-xmlnode", - plugin, PURPLE_CALLBACK(jabber_send_signal_cb), - NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST); - - purple_signal_register(plugin, "jabber-sending-text", - purple_marshal_VOID__POINTER_POINTER, NULL, 2, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new_outgoing(PURPLE_TYPE_STRING)); - - purple_signal_register(plugin, "jabber-receiving-message", - purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER, - purple_value_new(PURPLE_TYPE_BOOLEAN), 6, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new(PURPLE_TYPE_STRING), /* type */ - purple_value_new(PURPLE_TYPE_STRING), /* id */ - purple_value_new(PURPLE_TYPE_STRING), /* from */ - purple_value_new(PURPLE_TYPE_STRING), /* to */ - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); - - purple_signal_register(plugin, "jabber-receiving-iq", - purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, - purple_value_new(PURPLE_TYPE_BOOLEAN), 5, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new(PURPLE_TYPE_STRING), /* type */ - purple_value_new(PURPLE_TYPE_STRING), /* id */ - purple_value_new(PURPLE_TYPE_STRING), /* from */ - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); - - purple_signal_register(plugin, "jabber-watched-iq", - purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, - purple_value_new(PURPLE_TYPE_BOOLEAN), 5, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new(PURPLE_TYPE_STRING), /* type */ - purple_value_new(PURPLE_TYPE_STRING), /* id */ - purple_value_new(PURPLE_TYPE_STRING), /* from */ - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */ - - /* Modifying these? Look at jabber_init_plugin for the ipc versions */ - purple_signal_register(plugin, "jabber-register-namespace-watcher", - purple_marshal_VOID__POINTER_POINTER, - NULL, 2, - purple_value_new(PURPLE_TYPE_STRING), /* node */ - purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ - - purple_signal_register(plugin, "jabber-unregister-namespace-watcher", - purple_marshal_VOID__POINTER_POINTER, - NULL, 2, - purple_value_new(PURPLE_TYPE_STRING), /* node */ - purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ - - purple_signal_connect(plugin, "jabber-register-namespace-watcher", - plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL); - purple_signal_connect(plugin, "jabber-unregister-namespace-watcher", - plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL); - - purple_signal_register(plugin, "jabber-receiving-presence", - purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER, - purple_value_new(PURPLE_TYPE_BOOLEAN), 4, - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), - purple_value_new(PURPLE_TYPE_STRING), /* type */ - purple_value_new(PURPLE_TYPE_STRING), /* from */ - purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + jabber_plugin_init(plugin); return TRUE; } static gboolean unload_plugin(PurplePlugin *plugin) { - purple_signals_unregister_by_instance(plugin); - - /* reverse order of init_plugin */ - jabber_bosh_uninit(); - jabber_data_uninit(); - jabber_si_uninit(); - jabber_ibb_uninit(); - /* PEP things should be uninit via jabber_pep_uninit, not here */ - jabber_pep_uninit(); - jabber_caps_uninit(); - jabber_iq_uninit(); - - jabber_unregister_commands(); - - /* Stay on target...stay on target... Almost there... */ - jabber_uninit_plugin(plugin); + jabber_plugin_uninit(plugin); return TRUE; } @@ -339,13 +249,6 @@ static void init_plugin(PurplePlugin *plugin) { -#ifdef HAVE_CYRUS_SASL -#ifdef _WIN32 - UINT old_error_mode; - gchar *sasldir; -#endif - int ret; -#endif PurpleAccountUserSplit *split; PurpleAccountOption *option; @@ -402,42 +305,9 @@ option); my_protocol = plugin; - jabber_init_plugin(plugin); purple_prefs_remove("/plugins/prpl/jabber"); - /* XXX - If any other plugin wants SASL this won't be good ... */ -#ifdef HAVE_CYRUS_SASL -#ifdef _WIN32 - sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL); - sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir); - g_free(sasldir); - /* Suppress error popups for failing to load sasl plugins */ - old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS); -#endif - if ((ret = sasl_client_init(NULL)) != SASL_OK) { - purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret); - } -#ifdef _WIN32 - /* Restore the original error mode */ - SetErrorMode(old_error_mode); -#endif -#endif - jabber_register_commands(); - - /* reverse order of unload_plugin */ - jabber_iq_init(); - jabber_caps_init(); - /* PEP things should be init via jabber_pep_init, not here */ - jabber_pep_init(); - jabber_data_init(); - jabber_bosh_init(); - - /* TODO: Implement adding and retrieving own features via IPC API */ - - jabber_ibb_init(); - jabber_si_init(); - purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(xmpp_uri_handler), NULL); } diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/message.c --- a/libpurple/protocols/jabber/message.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/message.c Wed Mar 10 05:12:35 2010 +0000 @@ -74,24 +74,6 @@ jb = jabber_buddy_find(jm->js, jm->from, TRUE); jbr = jabber_buddy_find_resource(jb, jid->resource); - if (jid->resource) { - /* - * 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 resource). - * - * This works because purple_conv_im_send gets the name from - * purple_conversation_get_name() - */ - PurpleConversation *conv; - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, jm->from, account); - if (conv && !g_str_equal(jm->from, purple_conversation_get_name(conv))) { - purple_debug_info("jabber", "Binding conversation to %s\n", jm->from); - purple_conversation_set_name(conv, jm->from); - } - } - if(!jm->xhtml && !jm->body) { if (jbr) { if (jm->chat_state != JM_STATE_NONE) @@ -137,6 +119,28 @@ serv_got_typing_stopped(gc, jm->from); } } else { + if (jid->resource) { + /* + * 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 + * resource). + * + * This works because purple_conv_im_send gets the name + * from purple_conversation_get_name() + */ + PurpleConversation *conv; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + jm->from, account); + if (conv && !g_str_equal(jm->from, + purple_conversation_get_name(conv))) { + purple_debug_info("jabber", "Binding conversation to %s\n", + jm->from); + purple_conversation_set_name(conv, jm->from); + } + } + if(jbr) { if (jm->chat_state != JM_STATE_NONE) jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED; diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/parser.c --- a/libpurple/protocols/jabber/parser.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/parser.c Wed Mar 10 05:12:35 2010 +0000 @@ -44,14 +44,28 @@ if(!element_name) { return; } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { - js->protocol_version = JABBER_PROTO_0_9; + js->protocol_version.major = 0; + js->protocol_version.minor = 9; for(i=0; i < nb_attributes * 5; i += 5) { int attrib_len = attributes[i+4] - attributes[i+3]; char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len); - if(!xmlStrcmp(attributes[i], (xmlChar*) "version") - && !strcmp(attrib, "1.0")) { - js->protocol_version = JABBER_PROTO_1_0; + if(!xmlStrcmp(attributes[i], (xmlChar*) "version")) { + const char *dot = strchr(attrib, '.'); + + js->protocol_version.major = atoi(attrib); + js->protocol_version.minor = dot ? atoi(dot + 1) : 0; + + if (js->protocol_version.major > 1) + /* TODO: Send error */ + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("XMPP Version Mismatch")); + + if (js->protocol_version.major == 0 && js->protocol_version.minor != 9) { + purple_debug_warning("jabber", "Treating version %s as 0.9 for backward " + "compatibility\n", attrib); + } g_free(attrib); } else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) { g_free(js->stream_id); @@ -255,7 +269,8 @@ } } - if (js->protocol_version == JABBER_PROTO_0_9 && !js->gc->disconnect_timeout && + if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 && + !js->gc->disconnect_timeout && (js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)) { /* diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed Mar 10 05:12:35 2010 +0000 @@ -43,6 +43,37 @@ #include "usermood.h" #include "usertune.h" +static GHashTable *presence_handlers = NULL; + +static const struct { + const char *name; + JabberPresenceType type; +} jabber_presence_types[] = { + { "error", JABBER_PRESENCE_ERROR }, + { "probe", JABBER_PRESENCE_PROBE }, + { "unavailable", JABBER_PRESENCE_UNAVAILABLE }, + { "subscribe", JABBER_PRESENCE_SUBSCRIBE }, + { "subscribed", JABBER_PRESENCE_SUBSCRIBED }, + { "unsubscribe", JABBER_PRESENCE_UNSUBSCRIBE }, + { "unsubscribed", JABBER_PRESENCE_UNSUBSCRIBED } + /* { NULL, JABBER_PRESENCE_AVAILABLE } the default */ +}; + +static JabberPresenceType +str_to_presence_type(const char *type) +{ + int i; + + if (type == NULL) + return JABBER_PRESENCE_AVAILABLE; + + for (i = 0; i < G_N_ELEMENTS(jabber_presence_types); ++i) + if (g_str_equal(type, jabber_presence_types[i].name)) + return jabber_presence_types[i].type; + + purple_debug_warning("jabber", "Unknown presence type '%s'\n", type); + return JABBER_PRESENCE_AVAILABLE; +} static void chats_send_presence_foreach(gpointer key, gpointer val, gpointer user_data) @@ -476,7 +507,6 @@ purple_prpl_got_media_caps( purple_connection_get_account(userdata->js->gc), userdata->from); - if (info == NULL) goto out; @@ -508,573 +538,498 @@ g_free(userdata); } +gboolean +handle_presence_chat(JabberStream *js, JabberPresence *presence, xmlnode *packet) +{ + static int i = 1; + PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE; + JabberChat *chat = presence->chat; + + if (presence->state == JABBER_BUDDY_STATE_ERROR) { + char *title, *msg = jabber_parse_error(js, packet, NULL); + + if (!chat->conv) { + title = g_strdup_printf(_("Error joining chat %s"), presence->from); + purple_serv_got_join_chat_failed(js->gc, chat->components); + } else { + title = g_strdup_printf(_("Error in chat %s"), presence->from); + if (g_hash_table_size(chat->members) == 0) + serv_got_chat_left(js->gc, chat->id); + } + purple_notify_error(js->gc, title, title, msg); + g_free(title); + g_free(msg); + + if (g_hash_table_size(chat->members) == 0) + /* Only destroy the chat if the error happened while joining */ + jabber_chat_destroy(chat); + return FALSE; + } + + if (presence->type == JABBER_PRESENCE_AVAILABLE) { + const char *jid = NULL; + const char *affiliation = NULL; + const char *role = NULL; + gboolean is_our_resource = FALSE; /* Is the presence about us? */ + JabberBuddyResource *jbr; + + /* + * XEP-0045 mandates the presence to include a resource (which is + * treated as the chat nick). Some non-compliant servers allow + * joining without a nick. + */ + if (!presence->jid_from->resource) + return FALSE; + + if (presence->chat_info.item) { + jid = xmlnode_get_attrib(presence->chat_info.item, "jid"); + affiliation = xmlnode_get_attrib(presence->chat_info.item, "affiliation"); + role = xmlnode_get_attrib(presence->chat_info.item, "role"); + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110))) + is_our_resource = TRUE; + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(201))) { + chat->config_dialog_type = PURPLE_REQUEST_ACTION; + chat->config_dialog_handle = + purple_request_action(js->gc, + _("Create New Room"), + _("Create New Room"), + _("You are creating a new room. Would" + " you like to configure it, or" + " accept the default settings?"), + /* Default Action */ 1, + purple_connection_get_account(js->gc), NULL, chat->conv, + chat, 2, + _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure), + _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room)); + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(210))) { + /* server rewrote room-nick */ + g_free(chat->handle); + chat->handle = g_strdup(presence->jid_from->resource); + } + + if (purple_strequal(affiliation, "owner")) + flags |= PURPLE_CBFLAGS_FOUNDER; + if (role) { + if (g_str_equal(role, "moderator")) + flags |= PURPLE_CBFLAGS_OP; + else if (g_str_equal(role, "participant")) + flags |= PURPLE_CBFLAGS_VOICE; + } + + if(!chat->conv) { + char *room_jid = g_strdup_printf("%s@%s", presence->jid_from->node, presence->jid_from->domain); + chat->id = i++; + chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid); + purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle); + + jabber_chat_disco_traffic(chat); + g_free(room_jid); + } + + jbr = jabber_buddy_track_resource(presence->jb, presence->jid_from->resource, presence->priority, presence->state, presence->status); + jbr->commands_fetched = TRUE; + + jabber_chat_track_handle(chat, presence->jid_from->resource, jid, affiliation, role); + + if(!jabber_chat_find_buddy(chat->conv, presence->jid_from->resource)) + purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource, + jid, flags, !presence->delayed); + else + purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource, + flags); + } else if (presence->type == JABBER_PRESENCE_UNAVAILABLE) { + gboolean nick_change = FALSE; + gboolean kick = FALSE; + gboolean is_our_resource = FALSE; /* Is the presence about us? */ + + const char *jid = NULL; + + /* If the chat nick is invalid, we haven't yet joined, or we've + * already left (it was probably us leaving after we closed the + * chat), we don't care. + */ + if (!presence->jid_from->resource || !chat->conv || chat->left) { + if (chat->left && + presence->jid_from->resource && chat->handle && !strcmp(presence->jid_from->resource, chat->handle)) + jabber_chat_destroy(chat); + return FALSE; + } + + is_our_resource = (0 == g_utf8_collate(presence->jid_from->resource, chat->handle)); + + jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource); + + if (presence->chat_info.item) + jid = xmlnode_get_attrib(presence->chat_info.item, "jid"); + + if (chat->muc) { + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110))) + is_our_resource = TRUE; + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(301))) { + /* XXX: We got banned. YAY! (No GIR, that's bad) */ + } + + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(303))) { + const char *nick = NULL; + if (presence->chat_info.item) + nick = xmlnode_get_attrib(presence->chat_info.item, "nick"); + + /* nick change */ + if (nick) { + purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n"); + } else { + nick_change = TRUE; + + if (g_str_equal(presence->jid_from->resource, chat->handle)) { + /* Changing our own nickname */ + g_free(chat->handle); + chat->handle = g_strdup(nick); + } + + purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), + presence->jid_from->resource, + nick); + jabber_chat_remove_handle(chat, + presence->jid_from->resource); + } + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(307))) { + /* Someone was kicked from the room */ + const char *actor = NULL; + char *reason = NULL; + char *tmp; + + kick = TRUE; + + if (presence->chat_info.item) { + xmlnode *node; + + node = xmlnode_get_child(presence->chat_info.item, "actor"); + if (node) + actor = xmlnode_get_attrib(node, "jid"); + node = xmlnode_get_child(presence->chat_info.item, "reason"); + if (node) + reason = xmlnode_get_data(node); + } + + if (reason == NULL) + reason = g_strdup(_("No reason")); + + if (is_our_resource) { + if (actor) + tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"), + actor, reason); + else + tmp = g_strdup_printf(_("You have been kicked: (%s)"), + reason); + } else { + if (actor) + tmp = g_strdup_printf(_("Kicked by %s (%s)"), + actor, reason); + else + tmp = g_strdup_printf(_("Kicked (%s)"), + reason); + } + + g_free(presence->status); + presence->status = tmp; + + g_free(reason); + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(321))) { + /* XXX: removed due to an affiliation change */ + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(322))) { + /* XXX: removed because room is now members-only */ + } + + if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(332))) { + /* XXX: removed due to system shutdown */ + } + } + + /* + * Possibly another connected resource of our JID (see XEP-0045 + * v1.24 section 7.1.10) being disconnected. Should be + * distinguished by the item_jid. + * Also possibly works around bits of an Openfire bug. See + * #8319. + */ + if (is_our_resource && jid && !purple_strequal(presence->to, jid)) { + /* TODO: When the above is a loop, this needs to still act + * sanely for all cases (this code is a little fragile). */ + if (!kick && !nick_change) + /* Presumably, kicks and nick changes also affect us. */ + is_our_resource = FALSE; + } + + if(!nick_change) { + if (is_our_resource) { + if (kick) + purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource, + presence->status, PURPLE_MESSAGE_SYSTEM, time(NULL)); + + serv_got_chat_left(js->gc, chat->id); + jabber_chat_destroy(chat); + } else { + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource, + presence->status); + jabber_chat_remove_handle(chat, presence->jid_from->resource); + } + } + } + + return TRUE; +} + +gboolean +handle_presence_contact(JabberStream *js, JabberPresence *presence) +{ + JabberBuddyResource *jbr; + PurpleAccount *account; + PurpleBuddy *b; + char *buddy_name; + PurpleConversation *conv; + + buddy_name = jabber_id_get_bare_jid(presence->jid_from); + + account = purple_connection_get_account(js->gc); + b = purple_find_buddy(account, buddy_name); + + /* + * Unbind/unlock from sending messages to a specific resource on + * presence changes. This is locked to a specific resource when + * receiving a message (in message.c). + */ + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + buddy_name, account); + if (conv) { + purple_debug_info("jabber", "Changed conversation binding from %s to %s\n", + purple_conversation_get_name(conv), buddy_name); + purple_conversation_set_name(conv, buddy_name); + } + + if (b == NULL) { + if (presence->jb != js->user_jb) { + purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n", + buddy_name, purple_account_get_username(account), account); + return FALSE; + } else { + /* this is a different resource of our own account. Resume even when this account isn't on our blist */ + } + } + + if(b && presence->vcard_avatar_hash) { + const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b); + if(!avatar_hash2 || strcmp(presence->vcard_avatar_hash, avatar_hash2)) { + JabberIq *iq; + xmlnode *vcard; + + /* XXX this is a crappy way of trying to prevent + * someone from spamming us with presence packets + * and causing us to DoS ourselves...what we really + * need is a queue system that can throttle itself, + * but i'm too tired to write that right now */ + if(!g_slist_find(js->pending_avatar_requests, presence->jb)) { + + js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, presence->jb); + + iq = jabber_iq_new(js, JABBER_IQ_GET); + xmlnode_set_attrib(iq->node, "to", buddy_name); + vcard = xmlnode_new_child(iq->node, "vCard"); + xmlnode_set_namespace(vcard, "vcard-temp"); + + jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL); + jabber_iq_send(iq); + } + } + } + + if (presence->state == JABBER_BUDDY_STATE_ERROR || + presence->type == JABBER_PRESENCE_UNAVAILABLE || + presence->type == JABBER_PRESENCE_UNSUBSCRIBED) { + jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource); + } else { + jbr = jabber_buddy_track_resource(presence->jb, + presence->jid_from->resource, presence->priority, + presence->state, presence->status); + jbr->idle = presence->idle ? time(NULL) - presence->idle : 0; + } + + jbr = jabber_buddy_find_resource(presence->jb, NULL); + if (jbr) { + jabber_google_presence_incoming(js, buddy_name, jbr); + purple_prpl_got_user_status(account, buddy_name, + jabber_buddy_state_get_status_id(jbr->state), + "priority", jbr->priority, + "message", jbr->status, + NULL); + purple_prpl_got_user_idle(account, buddy_name, + jbr->idle, jbr->idle); + if (presence->nickname) + serv_got_alias(js->gc, buddy_name, presence->nickname); + } else { + purple_prpl_got_user_status(account, buddy_name, + jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE), + presence->status ? "message" : NULL, presence->status, + NULL); + } + g_free(buddy_name); + + return TRUE; +} + void jabber_presence_parse(JabberStream *js, xmlnode *packet) { - const char *from; const char *type; - char *status = NULL; - int priority = 0; - JabberID *jid; - JabberChat *chat; - JabberBuddy *jb; - JabberBuddyResource *jbr = NULL, *found_jbr = NULL; - PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE; - gboolean delayed = FALSE; - const gchar *stamp = NULL; /* from element */ - PurpleAccount *account; - PurpleBuddy *b = NULL; - char *buddy_name; - JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN; - xmlnode *y; - char *avatar_hash = NULL; - xmlnode *caps = NULL; - int idle = 0; - gchar *nickname = NULL; - gboolean signal_return; + JabberBuddyResource *jbr = NULL; + gboolean signal_return, ret; + JabberPresence presence; + xmlnode *child; - from = xmlnode_get_attrib(packet, "from"); + memset(&presence, 0, sizeof(presence)); + /* defaults */ + presence.state = JABBER_BUDDY_STATE_UNKNOWN; + presence.sent = time(NULL); + /* interesting values */ + presence.from = xmlnode_get_attrib(packet, "from"); + presence.to = xmlnode_get_attrib(packet, "to"); type = xmlnode_get_attrib(packet, "type"); - - jb = jabber_buddy_find(js, from, TRUE); - g_return_if_fail(jb != NULL); + presence.type = str_to_presence_type(type); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), - "jabber-receiving-presence", js->gc, type, from, packet)); - if (signal_return) - return; + presence.jb = jabber_buddy_find(js, presence.from, TRUE); + g_return_if_fail(presence.jb != NULL); - account = purple_connection_get_account(js->gc); - - jid = jabber_id_new(from); - if (jid == NULL) { + presence.jid_from = jabber_id_new(presence.from); + if (presence.jid_from == NULL) { purple_debug_error("jabber", "Ignoring presence with malformed 'from' " - "JID: %s\n", from); + "JID: %s\n", presence.from); return; } - if(jb->error_msg) { - g_free(jb->error_msg); - jb->error_msg = NULL; + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), + "jabber-receiving-presence", js->gc, type, presence.from, packet)); + if (signal_return) { + goto out; + } + + presence.chat = jabber_chat_find(js, presence.jid_from->node, + presence.jid_from->domain); + if(presence.jb->error_msg) { + g_free(presence.jb->error_msg); + presence.jb->error_msg = NULL; } - if (type == NULL) { - xmlnode *show; - char *show_data = NULL; - - state = JABBER_BUDDY_STATE_ONLINE; - - show = xmlnode_get_child(packet, "show"); - if (show) { - show_data = xmlnode_get_data(show); - if (show_data) { - state = jabber_buddy_show_get_state(show_data); - g_free(show_data); - } else - purple_debug_warning("jabber", " present on presence, " - "but no contents!\n"); - } - } else if (g_str_equal(type, "error")) { + if (presence.type == JABBER_PRESENCE_AVAILABLE) { + presence.state = JABBER_BUDDY_STATE_ONLINE; + } else if (presence.type == JABBER_PRESENCE_ERROR) { + /* TODO: Is this handled properly? Should it be treated as per-jbr? */ char *msg = jabber_parse_error(js, packet, NULL); - - state = JABBER_BUDDY_STATE_ERROR; - jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence")); - } else if (g_str_equal(type, "subscribe")) { + presence.state = JABBER_BUDDY_STATE_ERROR; + presence.jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence")); + } else if (presence.type == JABBER_PRESENCE_SUBSCRIBE) { + /* TODO: Move to handle_subscribe() (so nick is extracted by the + * PresenceHandler */ struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1); gboolean onlist = FALSE; + PurpleAccount *account; PurpleBuddy *buddy; - JabberBuddy *jb = NULL; xmlnode *nick; - buddy = purple_find_buddy(account, from); + account = purple_connection_get_account(js->gc); + buddy = purple_find_buddy(account, presence.from); nick = xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick"); if (nick) - nickname = xmlnode_get_data(nick); + presence.nickname = xmlnode_get_data(nick); if (buddy) { - jb = jabber_buddy_find(js, from, TRUE); - if ((jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING))) + if ((presence.jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING))) onlist = TRUE; } jap->gc = js->gc; - jap->who = g_strdup(from); + jap->who = g_strdup(presence.from); jap->js = js; - purple_account_request_authorization(account, from, NULL, nickname, + purple_account_request_authorization(account, presence.from, NULL, presence.nickname, NULL, onlist, authorize_add_cb, deny_add_cb, jap); - g_free(nickname); - jabber_id_free(jid); - return; - } else if (g_str_equal(type, "subscribed")) { - /* we've been allowed to see their presence, but we don't care */ - jabber_id_free(jid); - return; - } else if (g_str_equal(type, "unsubscribe")) { + goto out; + } else if (presence.type == JABBER_PRESENCE_SUBSCRIBED) { + /* This case (someone has approved our subscribe request) is handled + * by the roster push the server sends along with this. + */ + goto out; + } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBE) { /* XXX I'm not sure this is the right way to handle this, it * might be better to add "unsubscribe" to the presence status * if lower down, but I'm not sure. */ /* they are unsubscribing from our presence, we don't care */ /* Well, maybe just a little, we might want/need to start * acknowledging this (and the others) at some point. */ - jabber_id_free(jid); - return; - } else if (g_str_equal(type, "probe")) { + goto out; + } else if (presence.type == JABBER_PRESENCE_PROBE) { purple_debug_warning("jabber", "Ignoring presence probe\n"); - jabber_id_free(jid); - return; - } else if (g_str_equal(type, "unavailable")) { - state = JABBER_BUDDY_STATE_UNAVAILABLE; - } else if (g_str_equal(type, "unsubscribed")) { - state = JABBER_BUDDY_STATE_UNKNOWN; + goto out; + } else if (presence.type == JABBER_PRESENCE_UNAVAILABLE) { + presence.state = JABBER_BUDDY_STATE_UNAVAILABLE; + } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBED) { + presence.state = JABBER_BUDDY_STATE_UNKNOWN; } else { purple_debug_warning("jabber", "Ignoring presence with invalid type " "'%s'\n", type); - jabber_id_free(jid); - return; + goto out; } - - for(y = packet->child; y; y = y->next) { - const char *xmlns; - if(y->type != XMLNODE_TYPE_TAG) - continue; - xmlns = xmlnode_get_namespace(y); - - if(!strcmp(y->name, "status")) { - g_free(status); - status = xmlnode_get_data(y); - } else if(!strcmp(y->name, "priority")) { - char *p = xmlnode_get_data(y); - if(p) { - priority = atoi(p); - g_free(p); - } - } else if(xmlns == NULL) { - /* The rest of the cases used to check xmlns individually. */ + for (child = packet->child; child; child = child->next) { + char *key; + JabberPresenceHandler *pih; + if (child->type != XMLNODE_TYPE_TAG) continue; - } else if(!strcmp(y->name, "delay") && !strcmp(xmlns, NS_DELAYED_DELIVERY)) { - /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ - delayed = TRUE; - stamp = xmlnode_get_attrib(y, "stamp"); - } else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) { - caps = y; /* store for later, when creating buddy resource */ - } else if (g_str_equal(y->name, "nick") && g_str_equal(xmlns, "http://jabber.org/protocol/nick")) { - nickname = xmlnode_get_data(y); - } else if(!strcmp(y->name, "x")) { - if(!strcmp(xmlns, NS_DELAYED_DELIVERY_LEGACY)) { - /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ - delayed = TRUE; - stamp = xmlnode_get_attrib(y, "stamp"); - } else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user")) { - } else if(!strcmp(xmlns, "vcard-temp:x:update")) { - xmlnode *photo = xmlnode_get_child(y, "photo"); - if(photo) { - g_free(avatar_hash); - avatar_hash = xmlnode_get_data(photo); - } - } - } else if (!strcmp(y->name, "query") && - !strcmp(xmlnode_get_namespace(y), NS_LAST_ACTIVITY)) { - /* resource has specified idle */ - const gchar *seconds = xmlnode_get_attrib(y, "seconds"); - if (seconds) { - /* we may need to take "delayed" into account here */ - idle = atoi(seconds); - } - } - } - - if (idle && delayed && stamp) { - /* if we have a delayed presence, we need to add the delay to the idle - value */ - time_t offset = time(NULL) - purple_str_to_time(stamp, TRUE, NULL, NULL, - NULL); - purple_debug_info("jabber", "got delay %s yielding %ld s offset\n", - stamp, offset); - idle += offset; + + key = g_strdup_printf("%s %s", child->name, xmlnode_get_namespace(child)); + pih = g_hash_table_lookup(presence_handlers, key); + g_free(key); + if (pih) + pih(js, &presence, child); } - /* DEALING WITH CHATS */ - if(jid->node && (chat = jabber_chat_find(js, jid->node, jid->domain))) { - static int i = 1; - - if(state == JABBER_BUDDY_STATE_ERROR) { - char *title, *msg = jabber_parse_error(js, packet, NULL); - - if (!chat->conv) { - title = g_strdup_printf(_("Error joining chat %s"), from); - purple_serv_got_join_chat_failed(js->gc, chat->components); - } else { - title = g_strdup_printf(_("Error in chat %s"), from); - if (g_hash_table_size(chat->members) == 0) - serv_got_chat_left(js->gc, chat->id); - } - purple_notify_error(js->gc, title, title, msg); - g_free(title); - g_free(msg); - - if (g_hash_table_size(chat->members) == 0) - /* Only destroy the chat if the error happened while joining */ - jabber_chat_destroy(chat); - jabber_id_free(jid); - g_free(status); - g_free(avatar_hash); - g_free(nickname); - return; - } - - if (type == NULL) { - xmlnode *x; - const char *real_jid = NULL; - const char *affiliation = NULL; - const char *role = NULL; - gboolean is_our_resource = FALSE; /* Is the presence about us? */ - - /* - * XEP-0045 mandates the presence to include a resource (which is - * treated as the chat nick). Some non-compliant servers allow - * joining without a nick. - */ - if (!jid->resource) { - jabber_id_free(jid); - g_free(avatar_hash); - g_free(nickname); - g_free(status); - return; - } - - x = xmlnode_get_child_with_namespace(packet, "x", - "http://jabber.org/protocol/muc#user"); - if (x) { - xmlnode *status_node; - xmlnode *item_node; - - for (status_node = xmlnode_get_child(x, "status"); status_node; - status_node = xmlnode_get_next_twin(status_node)) { - const char *code = xmlnode_get_attrib(status_node, "code"); - if (!code) - continue; - - if (g_str_equal(code, "110")) { - is_our_resource = TRUE; - } else if (g_str_equal(code, "201")) { - if ((chat = jabber_chat_find(js, jid->node, jid->domain))) { - chat->config_dialog_type = PURPLE_REQUEST_ACTION; - chat->config_dialog_handle = - purple_request_action(js->gc, - _("Create New Room"), - _("Create New Room"), - _("You are creating a new room. Would" - " you like to configure it, or" - " accept the default settings?"), - /* Default Action */ 1, - account, NULL, chat->conv, - chat, 2, - _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure), - _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room)); - } - } else if (g_str_equal(code, "210")) { - /* server rewrote room-nick */ - if((chat = jabber_chat_find(js, jid->node, jid->domain))) { - g_free(chat->handle); - chat->handle = g_strdup(jid->resource); - } - } - } - - item_node = xmlnode_get_child(x, "item"); - if (item_node) { - real_jid = xmlnode_get_attrib(item_node, "jid"); - affiliation = xmlnode_get_attrib(item_node, "affiliation"); - role = xmlnode_get_attrib(item_node, "role"); - - if (purple_strequal(affiliation, "owner")) - flags |= PURPLE_CBFLAGS_FOUNDER; - if (role) { - if (g_str_equal(role, "moderator")) - flags |= PURPLE_CBFLAGS_OP; - else if (g_str_equal(role, "participant")) - flags |= PURPLE_CBFLAGS_VOICE; - } - } - } - - if(!chat->conv) { - char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain); - chat->id = i++; - chat->muc = (x != NULL); - chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid); - purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle); - - jabber_chat_disco_traffic(chat); - g_free(room_jid); - } - - jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state, - status); - jbr->commands_fetched = TRUE; - - jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role); - - if(!jabber_chat_find_buddy(chat->conv, jid->resource)) - purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, - real_jid, flags, !delayed); - else - purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), jid->resource, - flags); - } else if (g_str_equal(type, "unavailable")) { - xmlnode *x; - gboolean nick_change = FALSE; - gboolean kick = FALSE; - gboolean is_our_resource = FALSE; /* Is the presence about us? */ - - /* If the chat nick is invalid, we haven't yet joined, or we've - * already left (it was probably us leaving after we closed the - * chat), we don't care. - */ - if (!jid->resource || !chat->conv || chat->left) { - if (chat->left && - jid->resource && chat->handle && !strcmp(jid->resource, chat->handle)) - jabber_chat_destroy(chat); - jabber_id_free(jid); - g_free(status); - g_free(avatar_hash); - g_free(nickname); - return; - } - - is_our_resource = (0 == g_utf8_collate(jid->resource, chat->handle)); - - jabber_buddy_remove_resource(jb, jid->resource); - - x = xmlnode_get_child_with_namespace(packet, "x", - "http://jabber.org/protocol/muc#user"); - if (chat->muc && x) { - const char *nick; - const char *item_jid = NULL; - const char *to; - xmlnode *stat; - xmlnode *item; - - item = xmlnode_get_child(x, "item"); - if (item) - item_jid = xmlnode_get_attrib(item, "jid"); - - for (stat = xmlnode_get_child(x, "status"); stat; - stat = xmlnode_get_next_twin(stat)) { - const char *code = xmlnode_get_attrib(stat, "code"); - - if (!code) - continue; - - if (g_str_equal(code, "110")) { - is_our_resource = TRUE; - } else if(!strcmp(code, "301")) { - /* XXX: we got banned */ - } else if(!strcmp(code, "303") && item && - (nick = xmlnode_get_attrib(item, "nick"))) { - nick_change = TRUE; - if(!strcmp(jid->resource, chat->handle)) { - g_free(chat->handle); - chat->handle = g_strdup(nick); - } - - /* TODO: This should probably be moved out of the loop */ - purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick); - jabber_chat_remove_handle(chat, jid->resource); - continue; - } else if(!strcmp(code, "307")) { - /* Someone was kicked from the room */ - xmlnode *reason = NULL, *actor = NULL; - const char *actor_name = NULL; - char *reason_text = NULL; - char *tmp; - - kick = TRUE; - - if (item) { - reason = xmlnode_get_child(item, "reason"); - actor = xmlnode_get_child(item, "actor"); - - if (reason != NULL) - reason_text = xmlnode_get_data(reason); - if (actor != NULL) - actor_name = xmlnode_get_attrib(actor, "jid"); - } - - if (reason_text == NULL) - reason_text = g_strdup(_("No reason")); - - if (is_our_resource) { - if (actor_name != NULL) - tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"), - actor_name, reason_text); - else - tmp = g_strdup_printf(_("You have been kicked: (%s)"), - reason_text); - } else { - if (actor_name != NULL) - tmp = g_strdup_printf(_("Kicked by %s (%s)"), - actor_name, reason_text); - else - tmp = g_strdup_printf(_("Kicked (%s)"), - reason_text); - } - - g_free(reason_text); - g_free(status); - status = tmp; - } else if(!strcmp(code, "321")) { - /* XXX: removed due to an affiliation change */ - } else if(!strcmp(code, "322")) { - /* XXX: removed because room is now members-only */ - } else if(!strcmp(code, "332")) { - /* XXX: removed due to system shutdown */ - } - } - - /* - * Possibly another connected resource of our JID (see XEP-0045 - * v1.24 section 7.1.10) being disconnected. Should be - * distinguished by the item_jid. - * Also possibly works around bits of an Openfire bug. See - * #8319. - */ - to = xmlnode_get_attrib(packet, "to"); - if (is_our_resource && item_jid && !purple_strequal(to, item_jid)) { - /* TODO: When the above is a loop, this needs to still act - * sanely for all cases (this code is a little fragile). */ - if (!kick && !nick_change) - /* Presumably, kicks and nick changes also affect us. */ - is_our_resource = FALSE; - } - } - if(!nick_change) { - if (is_our_resource) { - if (kick) - purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), jid->resource, - status, PURPLE_MESSAGE_SYSTEM, time(NULL)); - - serv_got_chat_left(js->gc, chat->id); - jabber_chat_destroy(chat); - } else { - purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, - status); - jabber_chat_remove_handle(chat, jid->resource); - } - } - } else { - /* A type that isn't available or unavailable */ - purple_debug_error("jabber", "MUC presence with bad type: %s\n", - type); - - jabber_id_free(jid); - g_free(avatar_hash); - g_free(status); - g_free(nickname); - g_return_if_reached(); - } - /* End of DEALING WITH CHATS...about 5000 lines ago */ - } else { - /* DEALING WITH CONTACT (i.e. not a chat) */ - PurpleConversation *conv; - - buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "", - jid->node ? "@" : "", jid->domain); - - /* - * Unbind/unlock from sending messages to a specific resource on - * presence changes. This is locked to a specific resource when - * receiving a message (in message.c). - */ - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, - buddy_name, account); - if (conv) { - purple_debug_info("jabber", "Changed conversation binding from %s to %s\n", - purple_conversation_get_name(conv), buddy_name); - purple_conversation_set_name(conv, buddy_name); - } - - if((b = purple_find_buddy(account, buddy_name)) == NULL) { - if (jb != js->user_jb) { - purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n", - buddy_name, purple_account_get_username(account), account); - jabber_id_free(jid); - g_free(avatar_hash); - g_free(buddy_name); - g_free(nickname); - g_free(status); - return; - } else { - /* this is a different resource of our own account. Resume even when this account isn't on our blist */ - } - } - - if(b && avatar_hash) { - const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b); - if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) { - JabberIq *iq; - xmlnode *vcard; - - /* XXX this is a crappy way of trying to prevent - * someone from spamming us with presence packets - * and causing us to DoS ourselves...what we really - * need is a queue system that can throttle itself, - * but i'm too tired to write that right now */ - if(!g_slist_find(js->pending_avatar_requests, jb)) { - - js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, jb); - - iq = jabber_iq_new(js, JABBER_IQ_GET); - xmlnode_set_attrib(iq->node, "to", buddy_name); - vcard = xmlnode_new_child(iq->node, "vCard"); - xmlnode_set_namespace(vcard, "vcard-temp"); - - jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL); - jabber_iq_send(iq); - } - } - } - - if(state == JABBER_BUDDY_STATE_ERROR || - (type && (g_str_equal(type, "unavailable") || - g_str_equal(type, "unsubscribed")))) { - jabber_buddy_remove_resource(jb, jid->resource); - } else { - jbr = jabber_buddy_track_resource(jb, jid->resource, priority, - state, status); - if (idle) { - jbr->idle = time(NULL) - idle; - } else { - jbr->idle = 0; - } - } - - if((found_jbr = jabber_buddy_find_resource(jb, NULL))) { - jabber_google_presence_incoming(js, buddy_name, found_jbr); - purple_prpl_got_user_status(account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL); - purple_prpl_got_user_idle(account, buddy_name, found_jbr->idle, found_jbr->idle); - if (nickname) - serv_got_alias(js->gc, buddy_name, nickname); - } else { - purple_prpl_got_user_status(account, buddy_name, "offline", status ? "message" : NULL, status, NULL); - } - g_free(buddy_name); + if (presence.delayed && presence.idle) { + /* Delayed and idle, so update idle time */ + presence.idle = presence.idle + (time(NULL) - presence.sent); } - if (caps && !type) { + /* TODO: Handle tracking jb(r) here? */ + + if (presence.chat) + ret = handle_presence_chat(js, &presence, packet); + else + ret = handle_presence_contact(js, &presence); + if (!ret) + goto out; + + if (presence.caps && presence.type == JABBER_PRESENCE_AVAILABLE) { /* handle Entity Capabilities (XEP-0115) */ - const char *node = xmlnode_get_attrib(caps, "node"); - const char *ver = xmlnode_get_attrib(caps, "ver"); - const char *hash = xmlnode_get_attrib(caps, "hash"); - const char *ext = xmlnode_get_attrib(caps, "ext"); + const char *node = xmlnode_get_attrib(presence.caps, "node"); + const char *ver = xmlnode_get_attrib(presence.caps, "ver"); + const char *hash = xmlnode_get_attrib(presence.caps, "hash"); + const char *ext = xmlnode_get_attrib(presence.caps, "ext"); /* v1.3 uses: node, ver, and optionally ext. * v1.5 uses: node, ver, and hash. */ if (node && *node && ver && *ver) { gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL; - jbr = jabber_buddy_find_resource(jb, jid->resource); + jbr = jabber_buddy_find_resource(presence.jb, presence.jid_from->resource); /* Look it up if we don't already have all this information */ if (!jbr || !jbr->caps.info || @@ -1084,9 +1039,9 @@ !jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) { JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1); userdata->js = js; - userdata->jb = jb; - userdata->from = g_strdup(from); - jabber_caps_get_info(js, from, node, ver, hash, exts, + userdata->jb = presence.jb; + userdata->from = g_strdup(presence.from); + jabber_caps_get_info(js, presence.from, node, ver, hash, exts, (jabber_caps_get_info_cb)jabber_presence_set_capabilities, userdata); } else { @@ -1096,10 +1051,12 @@ } } - g_free(nickname); - g_free(status); - jabber_id_free(jid); - g_free(avatar_hash); +out: + g_free(presence.nickname); + g_free(presence.status); + jabber_id_free(presence.jid_from); + g_free(presence.nickname); + g_free(presence.vcard_avatar_hash); } void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type) @@ -1142,3 +1099,170 @@ *priority = purple_status_get_attr_int(status, "priority"); } } + +/* Incoming presence handlers */ +static void +parse_priority(JabberStream *js, JabberPresence *presence, xmlnode *priority) +{ + char *p = xmlnode_get_data(priority); + + if (presence->priority != 0) + purple_debug_warning("jabber", "presence stanza received with multiple " + "priority children!?\n"); + + if (p) { + presence->priority = atoi(p); + g_free(p); + } else + purple_debug_warning("jabber", "Empty in presence!\n"); +} + +static void +parse_show(JabberStream *js, JabberPresence *presence, xmlnode *show) +{ + char *cdata; + + if (presence->type != JABBER_PRESENCE_AVAILABLE) { + purple_debug_warning("jabber", " present on presence, but " + "type is not default ('available')\n"); + return; + } + + cdata = xmlnode_get_data(show); + if (cdata) { + presence->state = jabber_buddy_show_get_state(cdata); + g_free(cdata); + } else + purple_debug_warning("jabber", " present on presence, but " + "no contents!\n"); +} + +static void +parse_status(JabberStream *js, JabberPresence *presence, xmlnode *status) +{ + /* TODO: Check/track language attribute? */ + + g_free(presence->status); + presence->status = xmlnode_get_data(status); +} + +static void +parse_delay(JabberStream *js, JabberPresence *presence, xmlnode *delay) +{ + /* XXX: compare the time. Can happen on presence stanzas that aren't + * actually delayed. + */ + const char *stamp = xmlnode_get_attrib(delay, "stamp"); + presence->delayed = TRUE; + presence->sent = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL); +} + +static void +parse_idle(JabberStream *js, JabberPresence *presence, xmlnode *query) +{ + const gchar *seconds = xmlnode_get_attrib(query, "seconds"); + if (seconds) { + presence->idle = atoi(seconds); + if (presence->idle < 0) { + purple_debug_warning("jabber", "Received bogus idle time %s\n", seconds); + presence->idle = 0; + } + } +} + +static void +parse_caps(JabberStream *js, JabberPresence *presence, xmlnode *c) +{ + /* TODO: Move the rest of the caps handling in here, after changing the + * the "do we have details about this (node, ver) and exts" to not + * require the jbr to be present (since that happens later). + */ + presence->caps = c; +} + +static void +parse_nickname(JabberStream *js, JabberPresence *presence, xmlnode *nick) +{ + g_free(presence->nickname); + presence->nickname = xmlnode_get_data(nick); +} + +static void +parse_vcard_avatar(JabberStream *js, JabberPresence *presence, xmlnode *x) +{ + xmlnode *photo = xmlnode_get_child(x, "photo"); + if (photo) { + g_free(presence->vcard_avatar_hash); + presence->vcard_avatar_hash = xmlnode_get_data(photo); + } +} + +static void +parse_muc_user(JabberStream *js, JabberPresence *presence, xmlnode *x) +{ + xmlnode *status; + + if (presence->chat == NULL) { + purple_debug_warning("jabber", "Ignoring MUC gloop on non-MUC presence\n"); + return; + } + + if (presence->chat->conv == NULL) + presence->chat->muc = TRUE; + + for (status = xmlnode_get_child(x, "status"); status; + status = xmlnode_get_next_twin(status)) { + const char *code = xmlnode_get_attrib(status, "code"); + int val; + if (!code) + continue; + + val = atoi(code); + if (val == 0 || val < 0) { + purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n", + code); + continue; + } + + presence->chat_info.codes = g_slist_prepend(presence->chat_info.codes, GINT_TO_POINTER(val)); + } + + presence->chat_info.item = xmlnode_get_child(x, "item"); +} + +void jabber_presence_register_handler(const char *node, const char *xmlns, + JabberPresenceHandler *handler) +{ + /* + * This is valid because nodes nor namespaces cannot have spaces in them + * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and + * http://www.w3.org/TR/REC-xml-names/) + */ + char *key = g_strdup_printf("%s %s", node, xmlns); + g_hash_table_replace(presence_handlers, key, handler); +} + +void jabber_presence_init(void) +{ + presence_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + /* Core RFC things */ + jabber_presence_register_handler("priority", "jabber:client", parse_priority); + jabber_presence_register_handler("show", "jabber:client", parse_show); + jabber_presence_register_handler("status", "jabber:client", parse_status); + + /* XEPs */ + jabber_presence_register_handler("c", "http://jabber.org/protocol/caps", parse_caps); + jabber_presence_register_handler("delay", NS_DELAYED_DELIVERY, parse_delay); + jabber_presence_register_handler("nick", "http://jabber.org/protocol/nick", parse_nickname); + jabber_presence_register_handler("query", NS_LAST_ACTIVITY, parse_idle); + jabber_presence_register_handler("x", NS_DELAYED_DELIVERY_LEGACY, parse_delay); + jabber_presence_register_handler("x", "http://jabber.org/protocol/muc#user", parse_muc_user); + jabber_presence_register_handler("x", "vcard-temp:x:update", parse_vcard_avatar); +} + +void jabber_presence_uninit(void) +{ + g_hash_table_destroy(presence_handlers); + presence_handlers = NULL; +} diff -r 9472a82eb553 -r 01ed1337dddb libpurple/protocols/jabber/presence.h --- a/libpurple/protocols/jabber/presence.h Wed Mar 10 05:10:43 2010 +0000 +++ b/libpurple/protocols/jabber/presence.h Wed Mar 10 05:12:35 2010 +0000 @@ -24,10 +24,63 @@ #ifndef PURPLE_JABBER_PRESENCE_H_ #define PURPLE_JABBER_PRESENCE_H_ +typedef enum { + JABBER_PRESENCE_ERROR = -2, + JABBER_PRESENCE_PROBE = -1, + JABBER_PRESENCE_AVAILABLE, + JABBER_PRESENCE_UNAVAILABLE, + JABBER_PRESENCE_SUBSCRIBE, + JABBER_PRESENCE_SUBSCRIBED, + JABBER_PRESENCE_UNSUBSCRIBE, + JABBER_PRESENCE_UNSUBSCRIBED +} JabberPresenceType; + +typedef struct _JabberPresenceChatInfo JabberPresenceChatInfo; +typedef struct _JabberPresence JabberPresence; + #include "buddy.h" +#include "chat.h" #include "jabber.h" +#include "jutil.h" #include "xmlnode.h" +struct _JabberPresenceChatInfo { + GSList *codes; + xmlnode *item; +}; + +struct _JabberPresence { + JabberPresenceType type; + JabberID *jid_from; + const char *from; + const char *to; + const char *id; + + JabberBuddy *jb; + JabberChat *chat; + JabberPresenceChatInfo chat_info; + xmlnode *caps; /* TODO: Temporary, see presence.c:parse_caps */ + + JabberBuddyState state; + gchar *status; + int priority; + + char *vcard_avatar_hash; + char *nickname; + + gboolean delayed; + time_t sent; + int idle; +}; + +typedef void (JabberPresenceHandler)(JabberStream *js, JabberPresence *presence, + xmlnode *child); +void jabber_presence_register_handler(const char *node, const char *xmlns, + JabberPresenceHandler *handler); + +void jabber_presence_init(void); +void jabber_presence_uninit(void); + void jabber_set_status(PurpleAccount *account, PurpleStatus *status); /**