# HG changeset patch # User Andreas Monitzer # Date 1189031534 0 # Node ID 1d2002a5735e0e43c80db8587e505c8f22d0f67a # Parent e21002d106ab69a06c838a82771cb582328456eb# Parent b0733d5d76216b57ff28e2000220740ef0cb4f17 propagate from branch 'im.pidgin.pidgin' (head 996cf0c57149ba6e1c714ebb1f11d5d4bac8fb68) to branch 'im.pidgin.soc.2007.xmpp' (head cdf63b6603891b8cd3e7f629ef5a9a927a153550) diff -r e21002d106ab -r 1d2002a5735e .mtn-ignore --- a/.mtn-ignore Wed Sep 05 12:56:35 2007 +0000 +++ b/.mtn-ignore Wed Sep 05 22:32:14 2007 +0000 @@ -1,3 +1,4 @@ +(.*/)?\.svn .*/?Makefile(\.in)?$ (.*/)?TAGS$ .*/?.*\.pc$ diff -r e21002d106ab -r 1d2002a5735e libpurple/account.c --- a/libpurple/account.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/account.c Wed Sep 05 22:32:14 2007 +0000 @@ -913,6 +913,15 @@ } void +purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data) +{ + g_return_if_fail(account != NULL); + + account->registration_cb = cb; + account->registration_cb_user_data = user_data; +} + +void purple_account_register(PurpleAccount *account) { g_return_if_fail(account != NULL); @@ -923,6 +932,17 @@ purple_connection_new(account, TRUE, purple_account_get_password(account)); } +void +purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) +{ + g_return_if_fail(account != NULL); + + purple_debug_info("account", "Unregistering account %s\n", + purple_account_get_username(account)); + + purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data); +} + static void request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields) { diff -r e21002d106ab -r 1d2002a5735e libpurple/account.h --- a/libpurple/account.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/account.h Wed Sep 05 22:32:14 2007 +0000 @@ -36,6 +36,8 @@ typedef gboolean (*PurpleFilterAccountFunc)(PurpleAccount *account); typedef void (*PurpleAccountRequestAuthorizationCb)(void *); +typedef void (*PurpleAccountRegistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data); +typedef void (*PurpleAccountUnregistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data); #include "connection.h" #include "log.h" @@ -136,6 +138,8 @@ PurpleLog *system_log; /**< The system log */ void *ui_data; /**< The UI can put data here. */ + PurpleAccountRegistrationCb registration_cb; + void *registration_cb_user_data; }; #ifdef __cplusplus @@ -172,6 +176,15 @@ void purple_account_connect(PurpleAccount *account); /** + * Sets the callback for successful registration. + * + * @param account The account for which this callback should be used + * @param cb The callback + * @param user_data The user data passed to the callback + */ +void purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data); + +/** * Registers an account. * * @param account The account to register. @@ -179,6 +192,15 @@ void purple_account_register(PurpleAccount *account); /** + * Unregisters an account (deleting it from the server). + * + * @param account The account to unregister. + * @param cb Optional callback to be called when unregistration is complete + * @param user_data user data to pass to the callback + */ +void purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data); + +/** * Disconnects from an account. * * @param account The account to disconnect from. diff -r e21002d106ab -r 1d2002a5735e libpurple/connection.c --- a/libpurple/connection.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/connection.c Wed Sep 05 22:32:14 2007 +0000 @@ -158,6 +158,62 @@ } void +purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data) +{ + /* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when posssible. */ + PurpleConnection *gc; + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + + g_return_if_fail(account != NULL); + + prpl = purple_find_prpl(purple_account_get_protocol_id(account)); + + if (prpl != NULL) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + else { + gchar *message; + + message = g_strdup_printf(_("Missing protocol plugin for %s"), + purple_account_get_username(account)); + purple_notify_error(NULL, _("Unregistration Error"), message, NULL); + g_free(message); + return; + } + + if (!purple_account_is_disconnected(account)) { + prpl_info->unregister_user(account, cb, user_data); + return; + } + + if (((password == NULL) || (*password == '\0')) && + !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && + !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) + { + purple_debug_error("connection", "Can not connect to account %s without " + "a password.\n", purple_account_get_username(account)); + return; + } + + gc = g_new0(PurpleConnection, 1); + PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection); + + gc->prpl = prpl; + if ((password != NULL) && (*password != '\0')) + gc->password = g_strdup(password); + purple_connection_set_account(gc, account); + purple_connection_set_state(gc, PURPLE_CONNECTING); + connections = g_list_append(connections, gc); + purple_account_set_connection(account, gc); + + purple_signal_emit(purple_connections_get_handle(), "signing-on", gc); + + purple_debug_info("connection", "Unregistering. gc = %p\n", gc); + + prpl_info->unregister_user(account, cb, user_data); +} + +void purple_connection_destroy(PurpleConnection *gc) { PurpleAccount *account; diff -r e21002d106ab -r 1d2002a5735e libpurple/connection.h --- a/libpurple/connection.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/connection.h Wed Sep 05 22:32:14 2007 +0000 @@ -171,6 +171,18 @@ const char *password); /** + * This function should only be called by purple_account_unregister() + * in account.c. + * + * Tries to unregister the account on the server. If the account is not + * connected, also creates a new connection. + * + * @param account The account to unregister + * @param password The password to use. + */ +void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data); + +/** * Disconnects and destroys a PurpleConnection. * * This function should only be called by purple_account_disconnect() diff -r e21002d106ab -r 1d2002a5735e libpurple/plugin.h --- a/libpurple/plugin.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/plugin.h Wed Sep 05 22:32:14 2007 +0000 @@ -188,6 +188,8 @@ /** NULL for plugin actions menu, set to the PurpleConnection for account actions menu */ gpointer context; + + gpointer user_data; }; #define PURPLE_PLUGIN_HAS_ACTIONS(plugin) \ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/gg/gg.c Wed Sep 05 22:32:14 2007 +0000 @@ -411,6 +411,8 @@ purple_notify_info(NULL, _("New Gadu-Gadu Account Registered"), _("Registration completed successfully!"), NULL); + if(account->registration_cb) + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); /* TODO: the currently open Accounts Window will not be updated withthe * new username and etc, we need to somehow have it refresh at this * point @@ -420,6 +422,9 @@ purple_connection_destroy(gc); exit_err: + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); + gg_register_free(h); g_free(email); g_free(p1); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -27,6 +27,8 @@ oob.h \ parser.c \ parser.h \ + ping.c \ + ping.h \ presence.c \ presence.h \ roster.c \ @@ -34,7 +36,19 @@ si.c \ si.h \ xdata.c \ - xdata.h + xdata.h \ + caps.c \ + caps.h \ + adhoccommands.c \ + adhoccommands.h \ + pep.c \ + pep.h \ + usermood.c \ + usermood.h \ + usernick.c \ + usernick.h \ + usertune.c \ + usertune.h AM_CFLAGS = $(st) diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -53,6 +53,7 @@ message.c \ oob.c \ parser.c \ + ping.c \ presence.c \ roster.c \ si.c \ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/adhoccommands.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,307 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "adhoccommands.h" +#include +#include +#include "internal.h" +#include "xdata.h" +#include "iq.h" +#include "request.h" + +static void do_adhoc_ignoreme(JabberStream *js, ...) { + /* we don't have to do anything */ +} + +typedef struct _JabberAdHocActionInfo { + char *sessionid; + char *who; + char *node; + GList *actionslist; +} JabberAdHocActionInfo; + +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + const char *node; + xmlnode *query, *item; + JabberID *jabberid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + + if(strcmp(type, "result")) + return; + + query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); + if(!query) + return; + node = xmlnode_get_attrib(query,"node"); + if(!node || strcmp(node, "http://jabber.org/protocol/commands")) + return; + + if((jabberid = jabber_id_new(from))) { + if(jabberid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jabberid->resource); + jabber_id_free(jabberid); + } + + if(!jbr) + return; + + if(jbr->commands) { + /* since the list we just received is complete, wipe the old one */ + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + } + + for(item = query->child; item; item = item->next) { + JabberAdHocCommands *cmd; + if(item->type != XMLNODE_TYPE_TAG) + continue; + if(strcmp(item->name, "item")) + continue; + cmd = g_new0(JabberAdHocCommands, 1); + + cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid")); + cmd->node = g_strdup(xmlnode_get_attrib(item,"node")); + cmd->name = g_strdup(xmlnode_get_attrib(item,"name")); + + jbr->commands = g_list_append(jbr->commands,cmd); + } +} + +static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data); + +static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) { + xmlnode *command; + GList *action; + JabberAdHocActionInfo *actionInfo = user_data; + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + jabber_iq_set_callback(iq, jabber_adhoc_parse, NULL); + + xmlnode_set_attrib(iq->node, "to", actionInfo->who); + command = xmlnode_new_child(iq->node,"command"); + xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); + xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid); + xmlnode_set_attrib(command,"node",actionInfo->node); + if(actionhandle) + xmlnode_set_attrib(command,"action",actionhandle); + xmlnode_insert_child(command,result); + + for(action = actionInfo->actionslist; action; action = g_list_next(action)) { + char *handle = action->data; + g_free(handle); + } + g_list_free(actionInfo->actionslist); + g_free(actionInfo->sessionid); + g_free(actionInfo->who); + g_free(actionInfo->node); + + jabber_iq_send(iq); +} + +static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data) { + xmlnode *command = xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands"); + const char *status = xmlnode_get_attrib(command,"status"); + xmlnode *xdata = xmlnode_get_child_with_namespace(command,"x","jabber:x:data"); + const char *type = xmlnode_get_attrib(packet,"type"); + + if(type && !strcmp(type,"error")) { + char *msg = jabber_parse_error(js, packet); + if(!msg) + msg = g_strdup(_("Unknown Error")); + + purple_notify_error(NULL, _("Ad-Hoc Command Failed"), + _("Ad-Hoc Command Failed"), msg); + g_free(msg); + return; + } + if(!type || strcmp(type,"result")) + return; + + if(!status) + return; + + if(!strcmp(status,"completed")) { + /* display result */ + xmlnode *note = xmlnode_get_child(command,"note"); + + if(note) + purple_notify_info(NULL, xmlnode_get_attrib(packet, "from"), xmlnode_get_data(note), NULL); + + if(xdata) + jabber_x_data_request(js, xdata, (jabber_x_data_cb)do_adhoc_ignoreme, NULL); + return; + } + if(!strcmp(status,"executing")) { + /* this command needs more steps */ + xmlnode *actions, *action; + int actionindex = 0; + GList *actionslist = NULL; + JabberAdHocActionInfo *actionInfo; + if(!xdata) + return; /* shouldn't happen */ + + actions = xmlnode_get_child(command,"actions"); + if(!actions) { + JabberXDataAction *defaultaction = g_new0(JabberXDataAction, 1); + defaultaction->name = g_strdup(_("execute")); + defaultaction->handle = g_strdup("execute"); + actionslist = g_list_append(actionslist, defaultaction); + } else { + const char *defaultactionhandle = xmlnode_get_attrib(actions, "execute"); + int index = 0; + for(action = actions->child; action; action = action->next, ++index) { + if(action->type == XMLNODE_TYPE_TAG) { + JabberXDataAction *newaction = g_new0(JabberXDataAction, 1); + newaction->name = g_strdup(_(action->name)); + newaction->handle = g_strdup(action->name); + actionslist = g_list_append(actionslist, newaction); + if(defaultactionhandle && !strcmp(defaultactionhandle, action->name)) + actionindex = index; + } + } + } + + actionInfo = g_new0(JabberAdHocActionInfo, 1); + actionInfo->sessionid = g_strdup(xmlnode_get_attrib(command,"sessionid")); + actionInfo->who = g_strdup(xmlnode_get_attrib(packet,"from")); + actionInfo->node = g_strdup(xmlnode_get_attrib(command,"node")); + actionInfo->actionslist = actionslist; + + jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo); + } +} + +void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data) { + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { + JabberAdHocCommands *cmd = data; + PurpleBuddy *buddy = (PurpleBuddy *) node; + JabberStream *js = purple_account_get_connection(buddy->account)->proto_data; + + jabber_adhoc_execute(js, cmd); + } +} + +static void jabber_adhoc_server_got_list_cb(JabberStream *js, xmlnode *packet, gpointer data) { + xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#items"); + xmlnode *item; + + if(!query) + return; + + /* clean current list (just in case there is one) */ + while(js->commands) { + JabberAdHocCommands *cmd = js->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->node); + g_free(cmd); + js->commands = g_list_delete_link(js->commands, js->commands); + } + + /* re-fill list */ + for(item = query->child; item; item = item->next) { + JabberAdHocCommands *cmd; + if(item->type != XMLNODE_TYPE_TAG) + continue; + if(strcmp(item->name, "item")) + continue; + cmd = g_new0(JabberAdHocCommands, 1); + cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid")); + cmd->node = g_strdup(xmlnode_get_attrib(item,"node")); + cmd->name = g_strdup(xmlnode_get_attrib(item,"name")); + + js->commands = g_list_append(js->commands,cmd); + } +} + +void jabber_adhoc_server_get_list(JabberStream *js) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#items"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + + xmlnode_set_attrib(iq->node,"to",js->user->domain); + xmlnode_set_attrib(query,"node","http://jabber.org/protocol/commands"); + + jabber_iq_set_callback(iq,jabber_adhoc_server_got_list_cb,NULL); + jabber_iq_send(iq); +} + +void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd) { + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *command = xmlnode_new_child(iq->node,"command"); + xmlnode_set_attrib(iq->node,"to",cmd->jid); + xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); + xmlnode_set_attrib(command,"node",cmd->node); + xmlnode_set_attrib(command,"action","execute"); + + jabber_iq_set_callback(iq,jabber_adhoc_parse,NULL); + + jabber_iq_send(iq); +} + +void jabber_adhoc_server_execute(PurplePluginAction *action) { + JabberAdHocCommands *cmd = action->user_data; + if(cmd) { + PurpleConnection *gc = (PurpleConnection *) action->context; + JabberStream *js = gc->proto_data; + + jabber_adhoc_execute(js, cmd); + } +} + +void jabber_adhoc_init_server_commands(JabberStream *js, GList **m) { + GList *cmdlst; + JabberBuddy *jb; + + /* also add commands for other clients connected to the same account on another resource */ + char *accountname = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + if((jb = jabber_buddy_find(js, accountname, TRUE))) { + GList *iter; + for(iter = jb->resources; iter; iter = g_list_next(iter)) { + JabberBuddyResource *jbr = iter->data; + GList *riter; + for(riter = jbr->commands; riter; riter = g_list_next(riter)) { + JabberAdHocCommands *cmd = riter->data; + char *cmdname = g_strdup_printf("%s (%s)",cmd->name,jbr->name); + PurplePluginAction *act = purple_plugin_action_new(cmdname, jabber_adhoc_server_execute); + act->user_data = cmd; + *m = g_list_append(*m, act); + g_free(cmdname); + } + } + } + g_free(accountname); + + /* now add server commands */ + for(cmdlst = js->commands; cmdlst; cmdlst = g_list_next(cmdlst)) { + JabberAdHocCommands *cmd = cmdlst->data; + PurplePluginAction *act = purple_plugin_action_new(cmd->name, jabber_adhoc_server_execute); + act->user_data = cmd; + *m = g_list_append(*m, act); + } +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/adhoccommands.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,39 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_ADHOCCOMMANDS_H_ +#define _PURPLE_JABBER_ADHOCCOMMANDS_H_ + +#include "jabber.h" + +/* Implementation of XEP-0050 */ + +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data); + +void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd); + +void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data); + +void jabber_adhoc_server_get_list(JabberStream *js); + +void jabber_adhoc_init_server_commands(JabberStream *js, GList **m); + +#endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Wed Sep 05 22:32:14 2007 +0000 @@ -34,6 +34,8 @@ #include "iq.h" #include "presence.h" #include "xdata.h" +#include "pep.h" +#include "adhoccommands.h" typedef struct { long idle_seconds; @@ -116,7 +118,6 @@ int priority, JabberBuddyState state, const char *status) { JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); - if(!jbr) { jbr = g_new0(JabberBuddyResource, 1); jbr->jb = jb; @@ -128,7 +129,7 @@ jbr->state = state; if(jbr->status) g_free(jbr->status); - if (status) + if (status) jbr->status = g_markup_escape_text(status, -1); else jbr->status = NULL; @@ -141,6 +142,17 @@ g_return_if_fail(jbr != NULL); jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr); + + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + + jabber_caps_free_clientinfo(jbr->caps); g_free(jbr->name); g_free(jbr->status); @@ -411,7 +423,7 @@ if ((img = purple_buddy_icons_find_account_icon(gc->account))) { gconstpointer avatar_data; gsize avatar_len; - xmlnode *photo, *binval; + xmlnode *photo, *binval, *type; gchar *enc; int i; unsigned char hashval[20]; @@ -424,6 +436,8 @@ xmlnode_free(photo); } photo = xmlnode_new_child(vc_node, "PHOTO"); + type = xmlnode_new_child(photo, "TYPE"); + xmlnode_insert_data(type, "image/png", -1); binval = xmlnode_new_child(photo, "BINVAL"); enc = purple_base64_encode(avatar_data, avatar_len); @@ -452,9 +466,135 @@ void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) { + PurplePresence *gpresence; + PurpleStatus *status; + + if(((JabberStream*)gc->proto_data)->pep) { + /* XEP-0084: User Avatars */ + if(img) { + /* A PNG header, including the IHDR, but nothing else */ + const struct { + guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */ + struct { + guint32 length; /* must be 0x0d */ + guchar type[4]; /* must be 'I' 'H' 'D' 'R' */ + guint32 width; + guint32 height; + guchar bitdepth; + guchar colortype; + guchar compression; + guchar filter; + guchar interlace; + } ihdr; + } *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */ + + /* check if the data is a valid png file (well, at least to some extend) */ + if(png->signature[0] == 0x89 && + png->signature[1] == 0x50 && + png->signature[2] == 0x4e && + png->signature[3] == 0x47 && + png->signature[4] == 0x0d && + png->signature[5] == 0x0a && + png->signature[6] == 0x1a && + png->signature[7] == 0x0a && + ntohl(png->ihdr.length) == 0x0d && + png->ihdr.type[0] == 'I' && + png->ihdr.type[1] == 'H' && + png->ihdr.type[2] == 'D' && + png->ihdr.type[3] == 'R') { + /* parse PNG header to get the size of the image (yes, this is required) */ + guint32 width = ntohl(png->ihdr.width); + guint32 height = ntohl(png->ihdr.height); + xmlnode *publish, *item, *data, *metadata, *info; + char *lengthstring, *widthstring, *heightstring; + + /* compute the sha1 hash */ + PurpleCipherContext *ctx; + unsigned char digest[20]; + char *hash; + char *base64avatar; + + ctx = purple_cipher_context_new_by_name("sha1", NULL); + purple_cipher_context_append(ctx, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + + /* convert digest to a string */ + hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]); + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA); + + item = xmlnode_new_child(publish, "item"); + xmlnode_set_attrib(item, "id", hash); + + data = xmlnode_new_child(item, "data"); + xmlnode_set_namespace(data,AVATARNAMESPACEDATA); + + base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img)); + xmlnode_insert_data(data,base64avatar,-1); + g_free(base64avatar); + + /* publish the avatar itself */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + + /* next step: publish the metadata */ + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA); + + item = xmlnode_new_child(publish, "item"); + xmlnode_set_attrib(item, "id", hash); + + metadata = xmlnode_new_child(item, "metadata"); + xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA); + + info = xmlnode_new_child(metadata, "info"); + xmlnode_set_attrib(info, "id", hash); + xmlnode_set_attrib(info, "type", "image/png"); + lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img)); + xmlnode_set_attrib(info, "bytes", lengthstring); + g_free(lengthstring); + widthstring = g_strdup_printf("%u", width); + xmlnode_set_attrib(info, "width", widthstring); + g_free(widthstring); + heightstring = g_strdup_printf("%u", height); + xmlnode_set_attrib(info, "height", heightstring); + g_free(lengthstring); + + /* publish the metadata */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + + g_free(hash); + } else { /* if(img) */ + /* remove the metadata */ + xmlnode *metadata, *item; + xmlnode *publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA); + + item = xmlnode_new_child(publish, "item"); + + metadata = xmlnode_new_child(item, "metadata"); + xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA); + + xmlnode_new_child(metadata, "stop"); + + /* publish the metadata */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + } + } else { + purple_debug(PURPLE_DEBUG_ERROR, "jabber", + "jabber_set_buddy_icon received non-png data"); + } + } + + /* even when the image is not png, we can still publish the vCard, since this + one doesn't require a specific image type */ + + /* publish vCard for those poor older clients */ jabber_set_info(gc, purple_account_get_user_info(gc->account)); - jabber_presence_send(gc->account, NULL); + gpresence = purple_account_get_presence(gc->account); + status = purple_presence_get_active_status(gpresence); + jabber_presence_send(gc->account, status); } /* @@ -659,6 +799,123 @@ purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); } } + if(jbr && jbr->caps) { + GString *tmp = g_string_new(""); + GList *iter; + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + const char *feature = iter->data; + + if(!strcmp(feature, "jabber:iq:last")) + feature = _("Last Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#info")) + feature = _("Service Discovery Info"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#items")) + feature = _("Service Discovery Items"); + else if(!strcmp(feature, "http://jabber.org/protocol/address")) + feature = _("Extended Stanza Addressing"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc")) + feature = _("Multi-User Chat"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc#user")) + feature = _("Multi-User Chat Extended Presence Information"); + else if(!strcmp(feature, "http://jabber.org/protocol/ibb")) + feature = _("In-Band Bytestreams"); + else if(!strcmp(feature, "http://jabber.org/protocol/commands")) + feature = _("Ad-Hoc Commands"); + else if(!strcmp(feature, "http://jabber.org/protocol/pubsub")) + feature = _("PubSub Service"); + else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams")) + feature = _("SOCKS5 Bytestreams"); + else if(!strcmp(feature, "jabber:x:oob")) + feature = _("Out of Band Data"); + else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im")) + feature = _("XHTML-IM"); + else if(!strcmp(feature, "jabber:iq:register")) + feature = _("In-Band Registration"); + else if(!strcmp(feature, "http://jabber.org/protocol/geoloc")) + feature = _("User Location"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html")) + feature = _("User Avatar"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatstates")) + feature = _("Chat State Notifications"); + else if(!strcmp(feature, "jabber:iq:version")) + feature = _("Software Version"); + else if(!strcmp(feature, "http://jabber.org/protocol/si")) + feature = _("Stream Initiation"); + else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer")) + feature = _("File Transfer"); + else if(!strcmp(feature, "http://jabber.org/protocol/mood")) + feature = _("User Mood"); + else if(!strcmp(feature, "http://jabber.org/protocol/activity")) + feature = _("User Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/caps")) + feature = _("Entity Capabilities"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html")) + feature = _("Encrypted Session Negotiations"); + else if(!strcmp(feature, "http://jabber.org/protocol/tune")) + feature = _("User Tune"); + else if(!strcmp(feature, "http://jabber.org/protocol/rosterx")) + feature = _("Roster Item Exchange"); + else if(!strcmp(feature, "http://jabber.org/protocol/reach")) + feature = _("Reachability Address"); + else if(!strcmp(feature, "http://jabber.org/protocol/profile")) + feature = _("User Profile"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns")) + feature = _("Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns")) + feature = _("Jingle Audio"); + else if(!strcmp(feature, "http://jabber.org/protocol/nick")) + feature = _("User Nickname"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp")) + feature = _("Jingle ICE UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp")) + feature = _("Jingle ICE TCP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns")) + feature = _("Jingle Raw UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns")) + feature = _("Jingle Video"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns")) + feature = _("Jingle DTMF"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns")) + feature = _("Message Receipts"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns")) + feature = _("Public Key Publishing"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatting")) + feature = _("User Chatting"); + else if(!strcmp(feature, "http://jabber.org/protocol/browsing")) + feature = _("User Browsing"); + else if(!strcmp(feature, "http://jabber.org/protocol/gaming")) + feature = _("User Gaming"); + else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) + feature = _("User Viewing"); + else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + feature = _("Ping"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) + feature = _("Stanza Encryption"); + else if(!strcmp(feature, "urn:xmpp:time")) + feature = _("Entity Time"); + else if(!strcmp(feature, "urn:xmpp:delay")) + feature = _("Delayed Delivery"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns")) + feature = _("Collaborative Data Objects"); + else if(!strcmp(feature, "http://jabber.org/protocol/fileshare")) + feature = _("File Repository and Sharing"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns")) + feature = _("STUN Service Discovery for Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns")) + feature = _("Simplified Encrypted Session Negotiation"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns")) + feature = _("Hop Check"); + else if(g_str_has_suffix(feature, "+notify")) + feature = NULL; + + if(feature) + g_string_append_printf(tmp, "%s\n", feature); + } + if(strlen(tmp->str) > 0) + purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str); + + g_string_free(tmp, TRUE); + } } else { for(resources = jbi->jb->resources; resources; resources = resources->next) { char *purdy = NULL; @@ -700,6 +957,123 @@ purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); } } + if(jbr && jbr->caps) { + GString *tmp = g_string_new(""); + GList *iter; + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + const char *feature = iter->data; + + if(!strcmp(feature, "jabber:iq:last")) + feature = _("Last Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#info")) + feature = _("Service Discovery Info"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#items")) + feature = _("Service Discovery Items"); + else if(!strcmp(feature, "http://jabber.org/protocol/address")) + feature = _("Extended Stanza Addressing"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc")) + feature = _("Multi-User Chat"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc#user")) + feature = _("Multi-User Chat Extended Presence Information"); + else if(!strcmp(feature, "http://jabber.org/protocol/ibb")) + feature = _("In-Band Bytestreams"); + else if(!strcmp(feature, "http://jabber.org/protocol/commands")) + feature = _("Ad-Hoc Commands"); + else if(!strcmp(feature, "http://jabber.org/protocol/pubsub")) + feature = _("PubSub Service"); + else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams")) + feature = _("SOCKS5 Bytestreams"); + else if(!strcmp(feature, "jabber:x:oob")) + feature = _("Out of Band Data"); + else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im")) + feature = _("XHTML-IM"); + else if(!strcmp(feature, "jabber:iq:register")) + feature = _("In-Band Registration"); + else if(!strcmp(feature, "http://jabber.org/protocol/geoloc")) + feature = _("User Location"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html")) + feature = _("User Avatar"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatstates")) + feature = _("Chat State Notifications"); + else if(!strcmp(feature, "jabber:iq:version")) + feature = _("Software Version"); + else if(!strcmp(feature, "http://jabber.org/protocol/si")) + feature = _("Stream Initiation"); + else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer")) + feature = _("File Transfer"); + else if(!strcmp(feature, "http://jabber.org/protocol/mood")) + feature = _("User Mood"); + else if(!strcmp(feature, "http://jabber.org/protocol/activity")) + feature = _("User Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/caps")) + feature = _("Entity Capabilities"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html")) + feature = _("Encrypted Session Negotiations"); + else if(!strcmp(feature, "http://jabber.org/protocol/tune")) + feature = _("User Tune"); + else if(!strcmp(feature, "http://jabber.org/protocol/rosterx")) + feature = _("Roster Item Exchange"); + else if(!strcmp(feature, "http://jabber.org/protocol/reach")) + feature = _("Reachability Address"); + else if(!strcmp(feature, "http://jabber.org/protocol/profile")) + feature = _("User Profile"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns")) + feature = _("Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns")) + feature = _("Jingle Audio"); + else if(!strcmp(feature, "http://jabber.org/protocol/nick")) + feature = _("User Nickname"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp")) + feature = _("Jingle ICE UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp")) + feature = _("Jingle ICE TCP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns")) + feature = _("Jingle Raw UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns")) + feature = _("Jingle Video"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns")) + feature = _("Jingle DTMF"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns")) + feature = _("Message Receipts"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns")) + feature = _("Public Key Publishing"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatting")) + feature = _("User Chatting"); + else if(!strcmp(feature, "http://jabber.org/protocol/browsing")) + feature = _("User Browsing"); + else if(!strcmp(feature, "http://jabber.org/protocol/gaming")) + feature = _("User Gaming"); + else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) + feature = _("User Viewing"); + else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + feature = _("Ping"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) + feature = _("Stanza Encryption"); + else if(!strcmp(feature, "urn:xmpp:time")) + feature = _("Entity Time"); + else if(!strcmp(feature, "urn:xmpp:delay")) + feature = _("Delayed Delivery"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns")) + feature = _("Collaborative Data Objects"); + else if(!strcmp(feature, "http://jabber.org/protocol/fileshare")) + feature = _("File Repository and Sharing"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns")) + feature = _("STUN Service Discovery for Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns")) + feature = _("Simplified Encrypted Session Negotiation"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns")) + feature = _("Hop Check"); + else if(g_str_has_suffix(feature, "+notify")) + feature = NULL; + + if(feature) + g_string_append_printf(tmp, "%s\n", feature); + } + if(strlen(tmp->str) > 0) + purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str); + + g_string_free(tmp, TRUE); + } } } @@ -1023,6 +1397,109 @@ jabber_buddy_info_show_if_ready(jbi); } +typedef struct _JabberBuddyAvatarUpdateURLInfo { + JabberStream *js; + char *from; + char *id; +} JabberBuddyAvatarUpdateURLInfo; + +static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { + JabberBuddyAvatarUpdateURLInfo *info = user_data; + if(!url_text) { + purple_debug(PURPLE_DEBUG_ERROR, "jabber", + "do_buddy_avatar_update_fromurl got error \"%s\"", error_message); + return; + } + + purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id); + g_free(info->from); + g_free(info->id); + g_free(info); +} + +static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) { + xmlnode *item, *data; + const char *checksum; + char *b64data; + void *img; + size_t size; + if(!items) + return; + + item = xmlnode_get_child(items, "item"); + if(!item) + return; + + data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA); + if(!data) + return; + + checksum = xmlnode_get_attrib(item,"id"); + if(!checksum) + return; + + b64data = xmlnode_get_data(data); + if(!b64data) + return; + + img = purple_base64_decode(b64data, &size); + if(!img) + return; + + purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum); +} + +void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) { + PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from); + const char *checksum; + xmlnode *item, *metadata; + if(!buddy) + return; + + checksum = purple_buddy_icons_get_checksum_for_user(buddy); + item = xmlnode_get_child(items,"item"); + metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA); + if(!metadata) + return; + /* check if we have received a stop */ + if(xmlnode_get_child(metadata, "stop")) { + purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL); + } else { + xmlnode *info, *goodinfo = NULL; + + /* iterate over all info nodes to get one we can use */ + for(info = metadata->child; info; info = info->next) { + if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) { + const char *type = xmlnode_get_attrib(info,"type"); + const char *id = xmlnode_get_attrib(info,"id"); + + if(checksum && id && !strcmp(id, checksum)) { + /* we already have that avatar, so we don't have to do anything */ + goodinfo = NULL; + break; + } + /* We'll only pick the png one for now. It's a very nice image format anyways. */ + if(type && id && !goodinfo && !strcmp(type, "image/png")) + goodinfo = info; + } + } + if(goodinfo) { + const char *url = xmlnode_get_attrib(goodinfo,"url"); + const char *id = xmlnode_get_attrib(goodinfo,"id"); + + /* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */ + if(!url) + jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data); + else { + JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1); + info->js = js; + info->from = g_strdup(from); + info->id = g_strdup(id); + purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info); + } + } + } +} static void jabber_buddy_info_resource_free(gpointer data) { @@ -1295,7 +1772,7 @@ status = purple_presence_get_active_status(gpresence); purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(state, msg, priority); + presence = jabber_presence_create_js(js, state, msg, priority); g_free(msg); @@ -1389,12 +1866,54 @@ jabber_presence_subscription_set(js, buddy->name, "unsubscribe"); } +static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) { + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* simply create a directed presence of the current status */ + PurpleBuddy *buddy = (PurpleBuddy *) node; + PurpleConnection *gc = purple_account_get_connection(buddy->account); + JabberStream *js = gc->proto_data; + PurpleAccount *account = purple_connection_get_account(gc); + PurplePresence *gpresence = purple_account_get_presence(account); + PurpleStatus *status = purple_presence_get_active_status(gpresence); + xmlnode *presence; + JabberBuddyState state; + char *msg; + int priority; + + purple_status_to_jabber(status, &state, &msg, &priority); + presence = jabber_presence_create_js(js, state, msg, priority); + + g_free(msg); + + xmlnode_set_attrib(presence, "to", buddy->name); + + jabber_send(js, presence); + xmlnode_free(presence); + } +} + +static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) { + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* simply create a directed unavailable presence */ + PurpleBuddy *buddy = (PurpleBuddy *) node; + JabberStream *js = purple_account_get_connection(buddy->account)->proto_data; + xmlnode *presence; + + presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0); + + xmlnode_set_attrib(presence, "to", buddy->name); + + jabber_send(js, presence); + xmlnode_free(presence); + } +} static GList *jabber_buddy_menu(PurpleBuddy *buddy) { PurpleConnection *gc = purple_account_get_connection(buddy->account); JabberStream *js = gc->proto_data; JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE); + GList *jbrs; GList *m = NULL; PurpleMenuAction *act; @@ -1439,6 +1958,38 @@ NULL, NULL); m = g_list_append(m, act); } + + /* + * This if-condition implements parts of XEP-0100: Gateway Interaction + * + * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info. + * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume + * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since + * people don't tend to have a server or other service there. + */ + if (g_utf8_strchr(buddy->name, -1, '@') == NULL) { + act = purple_menu_action_new(_("Log In"), + PURPLE_CALLBACK(jabber_buddy_login), + NULL, NULL); + m = g_list_append(m, act); + act = purple_menu_action_new(_("Log Out"), + PURPLE_CALLBACK(jabber_buddy_logout), + NULL, NULL); + m = g_list_append(m, act); + } + + /* add all ad hoc commands to the action menu */ + for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) { + JabberBuddyResource *jbr = jbrs->data; + GList *commands; + if (!jbr->commands) + continue; + for(commands = jbr->commands; commands; commands = g_list_next(commands)) { + JabberAdHocCommands *cmd = commands->data; + act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL); + m = g_list_append(m, act); + } + } return m; } @@ -1728,10 +2279,10 @@ * in purple-i18n@lists.sourceforge.net (March 2006) */ static const char * jabber_user_dir_comments [] = { - /* current comment from Jabber User Directory users.jabber.org */ - N_("Find a contact by entering the search criteria in the given fields. " - "Note: Each field supports wild card searches (%)"), - NULL + /* current comment from Jabber User Directory users.jabber.org */ + N_("Find a contact by entering the search criteria in the given fields. " + "Note: Each field supports wild card searches (%)"), + NULL }; #endif @@ -1824,14 +2375,14 @@ _("Search for XMPP users"), instructions, fields, _("Search"), G_CALLBACK(user_search_cb), _("Cancel"), G_CALLBACK(user_search_cancel_cb), - NULL, NULL, NULL, + purple_connection_get_account(js->gc), NULL, NULL, usi); g_free(instructions); } } -static void jabber_user_search_ok(JabberStream *js, const char *directory) +void jabber_user_search(JabberStream *js, const char *directory) { JabberIq *iq; @@ -1858,7 +2409,7 @@ _("Select a user directory to search"), js->user_directories ? js->user_directories->data : NULL, FALSE, FALSE, NULL, - _("Search Directory"), PURPLE_CALLBACK(jabber_user_search_ok), + _("Search Directory"), PURPLE_CALLBACK(jabber_user_search), _("Cancel"), NULL, NULL, NULL, NULL, js); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.h Wed Sep 05 22:32:14 2007 +0000 @@ -22,8 +22,6 @@ #ifndef _PURPLE_JABBER_BUDDY_H_ #define _PURPLE_JABBER_BUDDY_H_ -#include "jabber.h" - typedef enum { JABBER_BUDDY_STATE_UNKNOWN = -2, JABBER_BUDDY_STATE_ERROR = -1, @@ -35,6 +33,12 @@ JABBER_BUDDY_STATE_DND } JabberBuddyState; +#include "jabber.h" +#include "caps.h" + +#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data" +#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata" + typedef struct _JabberBuddy { GList *resources; char *error_msg; @@ -53,6 +57,12 @@ } subscription; } JabberBuddy; +typedef struct _JabberAdHocCommands { + char *jid; + char *node; + char *name; +} JabberAdHocCommands; + typedef struct _JabberBuddyResource { JabberBuddy *jb; char *name; @@ -71,6 +81,8 @@ char *name; char *os; } client; + JabberCapsClientInfo *caps; + GList *commands; } JabberBuddyResource; void jabber_buddy_free(JabberBuddy *jb); @@ -92,6 +104,7 @@ void jabber_set_info(PurpleConnection *gc, const char *info); void jabber_setup_set_info(PurplePluginAction *action); void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img); +void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items); const char *jabber_buddy_state_get_name(JabberBuddyState state); const char *jabber_buddy_state_get_status_id(JabberBuddyState state); @@ -99,6 +112,7 @@ JabberBuddyState jabber_buddy_status_id_get_state(const char *id); JabberBuddyState jabber_buddy_show_get_state(const char *id); +void jabber_user_search(JabberStream *js, const char *directory); void jabber_user_search_begin(PurplePluginAction *); void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/caps.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,530 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "caps.h" +#include +#include "internal.h" +#include "util.h" +#include "iq.h" + +#define JABBER_CAPS_FILENAME "xmpp-caps.xml" + +static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ + +typedef struct _JabberCapsKey { + char *node; + char *ver; +} JabberCapsKey; + +typedef struct _JabberCapsValueExt { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +} JabberCapsValueExt; + +typedef struct _JabberCapsValue { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ + GHashTable *ext; /* char * -> JabberCapsValueExt */ +} JabberCapsValue; + +static guint jabber_caps_hash(gconstpointer key) { + const JabberCapsKey *name = key; + guint nodehash = g_str_hash(name->node); + guint verhash = g_str_hash(name->ver); + + return nodehash ^ verhash; +} + +static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) { + const JabberCapsKey *name1 = v1; + const JabberCapsKey *name2 = v2; + + return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0; +} + +static void jabber_caps_destroy_key(gpointer key) { + JabberCapsKey *keystruct = key; + g_free(keystruct->node); + g_free(keystruct->ver); + g_free(keystruct); +} + +static void jabber_caps_destroy_value(gpointer value) { + JabberCapsValue *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_hash_table_destroy(valuestruct->ext); + g_free(valuestruct); +} + +static void jabber_caps_ext_destroy_value(gpointer value) { + JabberCapsValueExt *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_free(valuestruct); +} + +static void jabber_caps_load(void); + +void jabber_caps_init(void) { + capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value); + jabber_caps_load(); +} + +static void jabber_caps_load(void) { + xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); + xmlnode *client; + if(!capsdata || strcmp(capsdata->name, "capabilities")) + return; + + for(client = capsdata->child; client; client = client->next) { + if(client->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(client->name, "client")) { + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + xmlnode *child; + key->node = g_strdup(xmlnode_get_attrib(client,"node")); + key->ver = g_strdup(xmlnode_get_attrib(client,"ver")); + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + for(child = client->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } else if(!strcmp(child->name,"ext")) { + const char *identifier = xmlnode_get_attrib(child, "identifier"); + if(identifier) { + xmlnode *extchild; + + JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1); + + for(extchild = child->child; extchild; extchild = extchild->next) { + if(extchild->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(extchild->name,"feature")) { + const char *var = xmlnode_get_attrib(extchild, "var"); + if(!var) + continue; + extvalue->features = g_list_append(extvalue->features,g_strdup(var)); + } else if(!strcmp(extchild->name,"identity")) { + const char *category = xmlnode_get_attrib(extchild, "category"); + const char *type = xmlnode_get_attrib(extchild, "type"); + const char *name = xmlnode_get_attrib(extchild, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + extvalue->identities = g_list_append(extvalue->identities,id); + } + } + g_hash_table_replace(value->ext, g_strdup(identifier), extvalue); + } + } + } + g_hash_table_replace(capstable, key, value); + } + } +} + +static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) { + const char *extname = key; + JabberCapsValueExt *props = value; + xmlnode *root = user_data; + xmlnode *ext = xmlnode_new_child(root,"ext"); + GList *iter; + + xmlnode_set_attrib(ext,"identifier",extname); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(ext, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(ext, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } +} + +static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) { + JabberCapsKey *clientinfo = key; + JabberCapsValue *props = value; + xmlnode *root = user_data; + xmlnode *client = xmlnode_new_child(root,"client"); + GList *iter; + + xmlnode_set_attrib(client,"node",clientinfo->node); + xmlnode_set_attrib(client,"ver",clientinfo->ver); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(client, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(client, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } + + g_hash_table_foreach(props->ext,jabber_caps_store_ext,client); +} + +static void jabber_caps_store(void) { + xmlnode *root = xmlnode_new("capabilities"); + g_hash_table_foreach(capstable, jabber_caps_store_client, root); + purple_util_write_data_to_file(JABBER_CAPS_FILENAME, xmlnode_to_formatted_str(root, NULL), -1); +} + +/* this function assumes that all information is available locally */ +static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) { + JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1); + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *caps; + GList *iter; + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + caps = g_hash_table_lookup(capstable,key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + /* join all information */ + for(iter = caps->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter = caps->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + + for(iter = ext; iter; iter = g_list_next(iter)) { + const char *extname = iter->data; + JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,extname); + + if(extinfo) { + GList *iter2; + for(iter2 = extinfo->identities; iter2; iter2 = g_list_next(iter2)) { + JabberCapsIdentity *id = iter2->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter2 = extinfo->features; iter2; iter2 = g_list_next(iter2)) { + const char *feat = iter2->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + } + } + return result; +} + +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) { + if(!clientinfo) + return; + while(clientinfo->identities) { + JabberCapsIdentity *id = clientinfo->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + clientinfo->identities = g_list_delete_link(clientinfo->identities,clientinfo->identities); + } + while(clientinfo->features) { + char *feat = clientinfo->features->data; + g_free(feat); + + clientinfo->features = g_list_delete_link(clientinfo->features,clientinfo->features); + } + + g_free(clientinfo); +} + +typedef struct _jabber_caps_cbplususerdata { + jabber_caps_get_info_cb cb; + gpointer user_data; + + char *who; + char *node; + char *ver; + GList *ext; + unsigned extOutstanding; +} jabber_caps_cbplususerdata; + +static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) { + if(userdata->extOutstanding == 0) { + userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data); + g_free(userdata->who); + g_free(userdata->node); + g_free(userdata->ver); + while(userdata->ext) { + g_free(userdata->ext->data); + userdata->ext = g_list_delete_link(userdata->ext,userdata->ext); + } + g_free(userdata); + } +} + +static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1); + JabberCapsValue *client; + JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1); + const char *node = xmlnode_get_attrib(query, "node"); + const char *key; + + --userdata->extOutstanding; + + if(node) { + clientkey->node = g_strdup(userdata->node); + clientkey->ver = g_strdup(userdata->ver); + + client = g_hash_table_lookup(capstable,clientkey); + + g_free(clientkey->node); + g_free(clientkey->ver); + g_free(clientkey); + + /* split node by #, key either points to \0 or the correct ext afterwards */ + for(key = node; key[0] != '\0'; ++key) { + if(key[0] == '#') { + ++key; + break; + } + } + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(client->ext, g_strdup(key), value); + + jabber_caps_store(); + } + + jabber_caps_get_info_check_completion(userdata); +} + +static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + GList *iter; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + key->node = g_strdup(userdata->node); + key->ver = g_strdup(userdata->ver); + + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(capstable, key, value); + + /* fetch all exts */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *node = g_strdup_printf("%s#%s", userdata->node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", node); + g_free(node); + xmlnode_set_attrib(iq->node, "to", userdata->who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + + jabber_caps_store(); + + jabber_caps_get_info_check_completion(userdata); +} + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) { + JabberCapsValue *client; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + char *originalext = g_strdup(ext); + char *oneext, *ctx; + jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1); + userdata->cb = cb; + userdata->user_data = user_data; + userdata->who = g_strdup(who); + userdata->node = g_strdup(node); + userdata->ver = g_strdup(ver); + + if(originalext) + for(oneext = strtok_r(originalext, " ", &ctx); oneext; oneext = strtok_r(NULL, " ", &ctx)) { + userdata->ext = g_list_append(userdata->ext,g_strdup(oneext)); + ++userdata->extOutstanding; + } + g_free(originalext); + + key->node = g_strdup(node); + key->ver = g_strdup(ver); + + client = g_hash_table_lookup(capstable, key); + + g_free(key->node); + g_free(key->ver); + g_free(key); + + if(!client) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *nodever = g_strdup_printf("%s#%s", node, ver); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata); + jabber_iq_send(iq); + } else { + GList *iter; + /* fetch unknown exts only */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data); + JabberIq *iq; + xmlnode *query; + char *nodever; + + if(extvalue) { + /* we already have this ext, don't bother with it */ + --userdata->extOutstanding; + continue; + } + + iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata); + jabber_iq_send(iq); + } + /* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */ + jabber_caps_get_info_check_completion(userdata); + } +} + diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/caps.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,49 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_CAPS_H_ +#define _PURPLE_JABBER_CAPS_H_ + +typedef struct _JabberCapsClientInfo JabberCapsClientInfo; + +#include "jabber.h" + +/* Implementation of XEP-0115 */ + +typedef struct _JabberCapsIdentity { + char *category; + char *type; + char *name; +} JabberCapsIdentity; + +struct _JabberCapsClientInfo { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +}; + +typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data); + +void jabber_caps_init(void); + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data); +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo); + +#endif /* _PURPLE_JABBER_CAPS_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/chat.c Wed Sep 05 22:32:14 2007 +0000 @@ -261,7 +261,7 @@ purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(state, msg, priority); + presence = jabber_presence_create_js(js, state, msg, priority); full_jid = g_strdup_printf("%s/%s", room_jid, handle); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); @@ -634,7 +634,7 @@ purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(state, msg, priority); + presence = jabber_presence_create_js(chat->js, state, msg, priority); full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Sep 05 22:32:14 2007 +0000 @@ -30,15 +30,19 @@ #include "jabber.h" #include "presence.h" #include "roster.h" +#include "pep.h" +#include "adhoccommands.h" + struct _jabber_disco_info_cb_data { gpointer data; JabberDiscoInfoCallback *callback; }; -#define SUPPORT_FEATURE(x) \ +#define SUPPORT_FEATURE(x) { \ feature = xmlnode_new_child(query, "feature"); \ - xmlnode_set_attrib(feature, "var", x); + xmlnode_set_attrib(feature, "var", x); \ +} void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { @@ -72,7 +76,6 @@ xmlnode_set_attrib(query, "node", node); if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) { - identity = xmlnode_new_child(query, "identity"); xmlnode_set_attrib(identity, "category", "client"); xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console, @@ -98,18 +101,62 @@ SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") SUPPORT_FEATURE("urn:xmpp:ping") + SUPPORT_FEATURE("http://www.xmpp.org/extensions/xep-0199.html#ns") + + if(!node) { /* non-caps disco#info, add all enabled extensions */ + GList *features; + for(features = jabber_features; features; features = features->next) { + JabberFeature *feat = (JabberFeature*)features->data; + if(feat->is_enabled == NULL || feat->is_enabled(js, feat->shortname, feat->namespace) == TRUE) + SUPPORT_FEATURE(feat->namespace); + } + } } else { - xmlnode *error, *inf; - - /* XXX: gross hack, implement jabber_iq_set_type or something */ - xmlnode_set_attrib(iq->node, "type", "error"); - iq->type = JABBER_IQ_ERROR; - - error = xmlnode_new_child(query, "error"); - xmlnode_set_attrib(error, "code", "404"); - xmlnode_set_attrib(error, "type", "cancel"); - inf = xmlnode_new_child(error, "item-not-found"); - xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); + const char *ext = NULL; + unsigned pos; + unsigned nodelen = strlen(node); + unsigned capslen = strlen(CAPS0115_NODE); + /* do a basic plausability check */ + if(nodelen > capslen+1) { + /* verify that the string is CAPS0115# and get the pointer to the ext part */ + for(pos = 0; pos < capslen+1; ++pos) { + if(pos == capslen) { + if(node[pos] == '#') + ext = &node[pos+1]; + else + break; + } else if(node[pos] != CAPS0115_NODE[pos]) + break; + } + + if(ext != NULL) { + /* look for that ext */ + GList *features; + for(features = jabber_features; features; features = features->next) { + JabberFeature *feat = (JabberFeature*)features->data; + if(!strcmp(feat->shortname, ext)) { + SUPPORT_FEATURE(feat->namespace); + break; + } + } + if(features == NULL) + ext = NULL; + } + } + + if(ext == NULL) { + xmlnode *error, *inf; + + /* XXX: gross hack, implement jabber_iq_set_type or something */ + xmlnode_set_attrib(iq->node, "type", "error"); + iq->type = JABBER_IQ_ERROR; + + error = xmlnode_new_child(query, "error"); + xmlnode_set_attrib(error, "code", "404"); + xmlnode_set_attrib(error, "type", "cancel"); + inf = xmlnode_new_child(error, "item-not-found"); + xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); + } } jabber_iq_send(iq); @@ -165,6 +212,11 @@ capabilities |= JABBER_CAP_IQ_SEARCH; else if(!strcmp(var, "jabber:iq:register")) capabilities |= JABBER_CAP_IQ_REGISTER; + else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + capabilities |= JABBER_CAP_PING; + else if(!strcmp(var, "http://jabber.org/protocol/commands")) { + capabilities |= JABBER_CAP_ADHOC; + } } } @@ -208,6 +260,17 @@ if(type && !strcmp(type, "get")) { JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "http://jabber.org/protocol/disco#items"); + + /* preserve node */ + xmlnode *iq_query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + if(iq_query) { + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); + if(query) { + const char *node = xmlnode_get_attrib(query,"node"); + if(node) + xmlnode_set_attrib(iq_query,"node",node); + } + } jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); @@ -227,7 +290,13 @@ jabber_roster_request(js); } - /* when we get the roster back, we'll send our initial presence */ + /* Send initial presence; this will trigger receipt of presence for contacts on the roster */ + jabber_presence_send(js->gc->account, NULL); + + if (js->server_caps & JABBER_CAP_ADHOC) { + /* The server supports ad-hoc commands, so let's request the list */ + jabber_adhoc_server_get_list(js); + } } static void @@ -260,9 +329,11 @@ child = xmlnode_get_next_twin(child)) { const char *category, *type, *name; category = xmlnode_get_attrib(child, "category"); + type = xmlnode_get_attrib(child, "type"); + if(category && type && !strcmp(category, "pubsub") && !strcmp(type,"pep")) + js->pep = TRUE; if (!category || strcmp(category, "server")) continue; - type = xmlnode_get_attrib(child, "type"); if (!type || strcmp(type, "im")) continue; @@ -291,6 +362,8 @@ } else if (!strcmp("google:roster", var)) { js->server_caps |= JABBER_CAP_GOOGLE_ROSTER; jabber_google_roster_init(js); + } else if (!strcmp("http://jabber.org/protocol/commands", var)) { + js->server_caps |= JABBER_CAP_ADHOC; } } diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/iq.c --- a/libpurple/protocols/jabber/iq.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/iq.c Wed Sep 05 22:32:14 2007 +0000 @@ -31,6 +31,8 @@ #include "oob.h" #include "roster.h" #include "si.h" +#include "ping.h" +#include "adhoccommands.h" #ifdef _WIN32 #include "utsname.h" @@ -343,6 +345,13 @@ jabber_gmail_poke(js, packet); return; } + + purple_debug_info("jabber", "jabber_iq_parse\n"); + + if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) { + jabber_ping_parse(js, packet); + return; + } /* If we get here, send the default error reply mandated by XMPP-CORE */ if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) { diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Sep 05 22:32:14 2007 +0000 @@ -10,12 +10,12 @@ * * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "internal.h" @@ -51,19 +51,27 @@ #include "presence.h" #include "jabber.h" #include "roster.h" +#include "ping.h" #include "si.h" #include "xdata.h" +#include "pep.h" +#include "adhoccommands.h" -#define JABBER_CONNECT_STEPS (js->gsc ? 8 : 5) +#include + +#define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) static PurplePlugin *my_protocol = NULL; +GList *jabber_features; + +static void jabber_unregister_account_cb(JabberStream *js); static void jabber_stream_init(JabberStream *js) { char *open_stream; open_stream = g_strdup_printf("", js->user->domain); @@ -80,6 +88,8 @@ const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "result")) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + if(js->unregistration) + jabber_unregister_account_cb(js); } else { purple_connection_error(js->gc, _("Error initializing session")); } @@ -132,6 +142,9 @@ if(xmlnode_get_child(packet, "starttls")) { if(jabber_process_starttls(js, packet)) return; + } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) { + purple_connection_error(js->gc, _("You require encryption, but it is not available on this server.")); + return; } if(js->registration) { @@ -169,49 +182,49 @@ static void tls_init(JabberStream *js); -void jabber_process_packet(JabberStream *js, xmlnode *packet) +void jabber_process_packet(JabberStream *js, xmlnode **packet) { const char *xmlns; - purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, &packet); + purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, packet); /* if the signal leaves us with a null packet, we're done */ - if(NULL == packet) + if(NULL == *packet) return; - xmlns = xmlnode_get_namespace(packet); + xmlns = xmlnode_get_namespace(*packet); - if(!strcmp(packet->name, "iq")) { - jabber_iq_parse(js, packet); - } else if(!strcmp(packet->name, "presence")) { - jabber_presence_parse(js, packet); - } else if(!strcmp(packet->name, "message")) { - jabber_message_parse(js, packet); - } else if(!strcmp(packet->name, "stream:features")) { - jabber_stream_features_parse(js, packet); - } else if (!strcmp(packet->name, "features") && + if(!strcmp((*packet)->name, "iq")) { + jabber_iq_parse(js, *packet); + } else if(!strcmp((*packet)->name, "presence")) { + jabber_presence_parse(js, *packet); + } else if(!strcmp((*packet)->name, "message")) { + jabber_message_parse(js, *packet); + } else if(!strcmp((*packet)->name, "stream:features")) { + jabber_stream_features_parse(js, *packet); + } else if (!strcmp((*packet)->name, "features") && !strcmp(xmlns, "http://etherx.jabber.org/streams")) { - jabber_stream_features_parse(js, packet); - } else if(!strcmp(packet->name, "stream:error") || - (!strcmp(packet->name, "error") && + jabber_stream_features_parse(js, *packet); + } else if(!strcmp((*packet)->name, "stream:error") || + (!strcmp((*packet)->name, "error") && !strcmp(xmlns, "http://etherx.jabber.org/streams"))) { - jabber_stream_handle_error(js, packet); - } else if(!strcmp(packet->name, "challenge")) { + jabber_stream_handle_error(js, *packet); + } else if(!strcmp((*packet)->name, "challenge")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_challenge(js, packet); - } else if(!strcmp(packet->name, "success")) { + jabber_auth_handle_challenge(js, *packet); + } else if(!strcmp((*packet)->name, "success")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_success(js, packet); - } else if(!strcmp(packet->name, "failure")) { + jabber_auth_handle_success(js, *packet); + } else if(!strcmp((*packet)->name, "failure")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_failure(js, packet); - } else if(!strcmp(packet->name, "proceed")) { + jabber_auth_handle_failure(js, *packet); + } else if(!strcmp((*packet)->name, "proceed")) { if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc) tls_init(js); } else { purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n", - packet->name); + (*packet)->name); } } @@ -448,11 +461,14 @@ } js = gc->proto_data; - + if(js->state == JABBER_STREAM_CONNECTING) jabber_send_raw(js, "", -1); jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING); purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc); + + /* Tell the app that we're doing encryption */ + jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); } @@ -554,6 +570,7 @@ js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); js->write_buffer = purple_circ_buffer_new(512); + js->old_length = -1; if(!js->user) { purple_connection_error(gc, _("Invalid XMPP ID")); @@ -613,6 +630,8 @@ { JabberStream *js = data; PurpleAccount *account = purple_connection_get_account(js->gc); + + jabber_parser_free(js); purple_account_disconnect(account); @@ -628,12 +647,21 @@ static void jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { + PurpleAccount *account = purple_connection_get_account(js->gc); const char *type = xmlnode_get_attrib(packet, "type"); char *buf; + char *to = data; if(!strcmp(type, "result")) { - buf = g_strdup_printf(_("Registration of %s@%s successful"), + if(js->registration) { + buf = g_strdup_printf(_("Registration of %s@%s successful"), js->user->node, js->user->domain); + if(account->registration_cb) + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); + } + else + buf = g_strdup_printf(_("Registration to %s successful"), + to); purple_notify_info(NULL, _("Registration Successful"), _("Registration Successful"), buf); g_free(buf); @@ -646,20 +674,56 @@ purple_notify_error(NULL, _("Registration Failed"), _("Registration Failed"), msg); g_free(msg); + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); } - jabber_connection_schedule_close(js); + g_free(to); + if(js->registration) + jabber_connection_schedule_close(js); } static void -jabber_register_cb(JabberStream *js, PurpleRequestFields *fields) +jabber_unregistration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + const char *type = xmlnode_get_attrib(packet, "type"); + char *buf; + char *to = data; + + if(!strcmp(type, "result")) { + buf = g_strdup_printf(_("Registration from %s successfully removed"), + to); + purple_notify_info(NULL, _("Unregistration Successful"), + _("Unregistration Successful"), buf); + g_free(buf); + } else { + char *msg = jabber_parse_error(js, packet); + + if(!msg) + msg = g_strdup(_("Unknown Error")); + + purple_notify_error(NULL, _("Unregistration Failed"), + _("Unregistration Failed"), msg); + g_free(msg); + } + g_free(to); +} + +typedef struct _JabberRegisterCBData { + JabberStream *js; + char *who; +} JabberRegisterCBData; + +static void +jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields) { GList *groups, *flds; xmlnode *query, *y; JabberIq *iq; char *username; - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); + iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register"); query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",cbdata->who); for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) { @@ -667,215 +731,297 @@ flds; flds = flds->next) { PurpleRequestField *field = flds->data; const char *id = purple_request_field_get_id(field); - const char *value = purple_request_field_string_get_value(field); - - if(!strcmp(id, "username")) { - y = xmlnode_new_child(query, "username"); - } else if(!strcmp(id, "password")) { - y = xmlnode_new_child(query, "password"); - } else if(!strcmp(id, "name")) { - y = xmlnode_new_child(query, "name"); - } else if(!strcmp(id, "email")) { - y = xmlnode_new_child(query, "email"); - } else if(!strcmp(id, "nick")) { - y = xmlnode_new_child(query, "nick"); - } else if(!strcmp(id, "first")) { - y = xmlnode_new_child(query, "first"); - } else if(!strcmp(id, "last")) { - y = xmlnode_new_child(query, "last"); - } else if(!strcmp(id, "address")) { - y = xmlnode_new_child(query, "address"); - } else if(!strcmp(id, "city")) { - y = xmlnode_new_child(query, "city"); - } else if(!strcmp(id, "state")) { - y = xmlnode_new_child(query, "state"); - } else if(!strcmp(id, "zip")) { - y = xmlnode_new_child(query, "zip"); - } else if(!strcmp(id, "phone")) { - y = xmlnode_new_child(query, "phone"); - } else if(!strcmp(id, "url")) { - y = xmlnode_new_child(query, "url"); - } else if(!strcmp(id, "date")) { - y = xmlnode_new_child(query, "date"); + if(!strcmp(id,"unregister")) { + gboolean value = purple_request_field_bool_get_value(field); + if(value) { + /* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it + (there's no "remove child" function for xmlnode) */ + jabber_iq_free(iq); + iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register"); + query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",cbdata->who); + xmlnode_new_child(query, "remove"); + + jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who); + + jabber_iq_send(iq); + g_free(cbdata); + return; + } } else { - continue; - } - xmlnode_insert_data(y, value, -1); - if(!strcmp(id, "username")) { - if(js->user->node) - g_free(js->user->node); - js->user->node = g_strdup(value); + const char *value = purple_request_field_string_get_value(field); + + if(!strcmp(id, "username")) { + y = xmlnode_new_child(query, "username"); + } else if(!strcmp(id, "password")) { + y = xmlnode_new_child(query, "password"); + } else if(!strcmp(id, "name")) { + y = xmlnode_new_child(query, "name"); + } else if(!strcmp(id, "email")) { + y = xmlnode_new_child(query, "email"); + } else if(!strcmp(id, "nick")) { + y = xmlnode_new_child(query, "nick"); + } else if(!strcmp(id, "first")) { + y = xmlnode_new_child(query, "first"); + } else if(!strcmp(id, "last")) { + y = xmlnode_new_child(query, "last"); + } else if(!strcmp(id, "address")) { + y = xmlnode_new_child(query, "address"); + } else if(!strcmp(id, "city")) { + y = xmlnode_new_child(query, "city"); + } else if(!strcmp(id, "state")) { + y = xmlnode_new_child(query, "state"); + } else if(!strcmp(id, "zip")) { + y = xmlnode_new_child(query, "zip"); + } else if(!strcmp(id, "phone")) { + y = xmlnode_new_child(query, "phone"); + } else if(!strcmp(id, "url")) { + y = xmlnode_new_child(query, "url"); + } else if(!strcmp(id, "date")) { + y = xmlnode_new_child(query, "date"); + } else { + continue; + } + xmlnode_insert_data(y, value, -1); + if(cbdata->js->registration && !strcmp(id, "username")) { + if(cbdata->js->user->node) + g_free(cbdata->js->user->node); + cbdata->js->user->node = g_strdup(value); + } + if(cbdata->js->registration && !strcmp(id, "password")) + purple_account_set_password(cbdata->js->gc->account, value); } } } - username = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, - js->user->resource); - purple_account_set_username(js->gc->account, username); - g_free(username); + if(cbdata->js->registration) { + username = g_strdup_printf("%s@%s/%s", cbdata->js->user->node, cbdata->js->user->domain, + cbdata->js->user->resource); + purple_account_set_username(cbdata->js->gc->account, username); + g_free(username); + } - jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL); + jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who); jabber_iq_send(iq); - + g_free(cbdata); } static void -jabber_register_cancel_cb(JabberStream *js, PurpleRequestFields *fields) +jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields) { - jabber_connection_schedule_close(js); + PurpleAccount *account = purple_connection_get_account(cbdata->js->gc); + if(account && cbdata->js->registration) { + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); + jabber_connection_schedule_close(cbdata->js); + } + g_free(cbdata->who); + g_free(cbdata); } static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data) { xmlnode *query; JabberIq *iq; + char *to = data; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",to); xmlnode_insert_child(query, result); - jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL); + jabber_iq_set_callback(iq, jabber_registration_result_cb, to); jabber_iq_send(iq); } void jabber_register_parse(JabberStream *js, xmlnode *packet) { + PurpleAccount *account = purple_connection_get_account(js->gc); const char *type; + const char *from = xmlnode_get_attrib(packet, "from"); + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + xmlnode *query, *x, *y; + char *instructions; + JabberRegisterCBData *cbdata; + gboolean registered = FALSE; + if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) return; - if(js->registration) { - PurpleRequestFields *fields; - PurpleRequestFieldGroup *group; - PurpleRequestField *field; - xmlnode *query, *x, *y; - char *instructions; - + if(js->registration) /* get rid of the login thingy */ purple_connection_set_state(js->gc, PURPLE_CONNECTED); - query = xmlnode_get_child(packet, "query"); + query = xmlnode_get_child(packet, "query"); - if(xmlnode_get_child(query, "registered")) { + if(xmlnode_get_child(query, "registered")) { + registered = TRUE; + + if(js->registration) { purple_notify_error(NULL, _("Already Registered"), _("Already Registered"), NULL); + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); jabber_connection_schedule_close(js); return; } + } - if((x = xmlnode_get_child_with_namespace(packet, "x", - "jabber:x:data"))) { - jabber_x_data_request(js, x, jabber_register_x_data_cb, NULL); - return; - } else if((x = xmlnode_get_child_with_namespace(packet, "x", - "jabber:x:oob"))) { - xmlnode *url; + if((x = xmlnode_get_child_with_namespace(packet, "x", + "jabber:x:data"))) { + jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from)); + return; + } else if((x = xmlnode_get_child_with_namespace(packet, "x", + "jabber:x:oob"))) { + xmlnode *url; - if((url = xmlnode_get_child(x, "url"))) { - char *href; - if((href = xmlnode_get_data(url))) { - purple_notify_uri(NULL, href); - g_free(href); + if((url = xmlnode_get_child(x, "url"))) { + char *href; + if((href = xmlnode_get_data(url))) { + purple_notify_uri(NULL, href); + g_free(href); + if(js->registration) { js->gc->wants_to_die = TRUE; + if(account->registration_cb) /* succeeded, but we have no login info */ + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); jabber_connection_schedule_close(js); - return; } + return; } } + } - /* as a last resort, use the old jabber:iq:register syntax */ + /* as a last resort, use the old jabber:iq:register syntax */ - fields = purple_request_fields_new(); - group = purple_request_field_group_new(NULL); - purple_request_fields_add_group(fields, group); + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + if(js->registration) field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE); - purple_request_field_group_add_field(group, field); + else + field = purple_request_field_string_new("username", _("Username"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + + if(js->registration) field = purple_request_field_string_new("password", _("Password"), purple_connection_get_password(js->gc), FALSE); - purple_request_field_string_set_masked(field, TRUE); - purple_request_field_group_add_field(group, field); + else + field = purple_request_field_string_new("password", _("Password"), + NULL, FALSE); - if(xmlnode_get_child(query, "name")) { + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + + if(xmlnode_get_child(query, "name")) { + if(js->registration) field = purple_request_field_string_new("name", _("Name"), purple_account_get_alias(js->gc->account), FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "email")) { - field = purple_request_field_string_new("email", _("E-mail"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "nick")) { - field = purple_request_field_string_new("nick", _("Nickname"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "first")) { - field = purple_request_field_string_new("first", _("First name"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "last")) { - field = purple_request_field_string_new("last", _("Last name"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "address")) { - field = purple_request_field_string_new("address", _("Address"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "city")) { - field = purple_request_field_string_new("city", _("City"), + else + field = purple_request_field_string_new("name", _("Name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "state")) { - field = purple_request_field_string_new("state", _("State"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "zip")) { - field = purple_request_field_string_new("zip", _("Postal code"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "phone")) { - field = purple_request_field_string_new("phone", _("Phone"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "url")) { - field = purple_request_field_string_new("url", _("URL"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "date")) { - field = purple_request_field_string_new("date", _("Date"), - NULL, FALSE); - purple_request_field_group_add_field(group, field); - } + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "email")) { + field = purple_request_field_string_new("email", _("E-mail"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "nick")) { + field = purple_request_field_string_new("nick", _("Nickname"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "first")) { + field = purple_request_field_string_new("first", _("First name"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "last")) { + field = purple_request_field_string_new("last", _("Last name"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "address")) { + field = purple_request_field_string_new("address", _("Address"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "city")) { + field = purple_request_field_string_new("city", _("City"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "state")) { + field = purple_request_field_string_new("state", _("State"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "zip")) { + field = purple_request_field_string_new("zip", _("Postal code"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "phone")) { + field = purple_request_field_string_new("phone", _("Phone"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "url")) { + field = purple_request_field_string_new("url", _("URL"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "date")) { + field = purple_request_field_string_new("date", _("Date"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + } + if(registered) { + field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE); + purple_request_field_group_add_field(group, field); + } - if((y = xmlnode_get_child(query, "instructions"))) - instructions = xmlnode_get_data(y); - else - instructions = g_strdup(_("Please fill out the information below " - "to register your new account.")); + if((y = xmlnode_get_child(query, "instructions"))) + instructions = xmlnode_get_data(y); + else if(registered) + instructions = g_strdup(_("Please fill out the information below " + "to change your account registration.")); + else + instructions = g_strdup(_("Please fill out the information below " + "to register your new account.")); + + cbdata = g_new0(JabberRegisterCBData, 1); + cbdata->js = js; + cbdata->who = g_strdup(from); + if(js->registration) purple_request_fields(js->gc, _("Register New XMPP Account"), _("Register New XMPP Account"), instructions, fields, _("Register"), G_CALLBACK(jabber_register_cb), _("Cancel"), G_CALLBACK(jabber_register_cancel_cb), purple_connection_get_account(js->gc), NULL, NULL, - js); + cbdata); + else { + char *title = registered?g_strdup_printf(_("Change Account Registration at %s"), from) + :g_strdup_printf(_("Register New Account at %s"), from); + purple_request_fields(js->gc, title, + title, instructions, fields, + registered?_("Change Registration"):_("Register"), G_CALLBACK(jabber_register_cb), + _("Cancel"), G_CALLBACK(jabber_register_cancel_cb), + purple_connection_get_account(js->gc), NULL, NULL, + cbdata); + g_free(title); + } - g_free(instructions); - } + g_free(instructions); } void jabber_register_start(JabberStream *js) @@ -886,6 +1032,14 @@ jabber_iq_send(iq); } +void jabber_register_gateway(JabberStream *js, const char *gateway) { + JabberIq *iq; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register"); + xmlnode_set_attrib(iq->node, "to", gateway); + jabber_iq_send(iq); +} + void jabber_register_account(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); @@ -904,6 +1058,7 @@ g_free, g_free); js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); + js->old_length = -1; if(!js->user) { purple_connection_error(gc, _("Invalid XMPP ID")); @@ -945,18 +1100,77 @@ if(!js->gsc) { if (connect_server[0]) { jabber_login_connect(js, js->user->domain, server, - purple_account_get_int(account, - "port", 5222)); + purple_account_get_int(account, + "port", 5222)); } else { js->srv_query_data = purple_srv_resolve("xmpp-client", - "tcp", - js->user->domain, - srv_resolved_cb, - js); + "tcp", + js->user->domain, + srv_resolved_cb, + js); } } } +static void jabber_unregister_account_iq_cb(JabberStream *js, xmlnode *packet, gpointer data) { + PurpleAccount *account = purple_connection_get_account(js->gc); + const char *type = xmlnode_get_attrib(packet,"type"); + if(!strcmp(type,"error")) { + char *msg = jabber_parse_error(js, packet); + + purple_notify_error(js->gc, _("Error unregistering account"), + _("Error unregistering account"), msg); + g_free(msg); + if(js->unregistration_cb) + js->unregistration_cb(account, FALSE, js->unregistration_user_data); + } else if(!strcmp(type,"result")) { + purple_notify_info(js->gc, _("Account successfully unregistered"), + _("Account successfully unregistered"), NULL); + if(js->unregistration_cb) + js->unregistration_cb(account, TRUE, js->unregistration_user_data); + } +} + +static void jabber_unregister_account_cb(JabberStream *js) { + JabberIq *iq; + xmlnode *query; + assert(js->unregistration); + + iq = jabber_iq_new_query(js,JABBER_IQ_SET,"jabber:iq:register"); + assert(iq); + query = xmlnode_get_child_with_namespace(iq->node,"query","jabber:iq:register"); + assert(query); + xmlnode_new_child(query,"remove"); + + xmlnode_set_attrib(iq->node,"to",js->user->domain); + jabber_iq_set_callback(iq,jabber_unregister_account_iq_cb,NULL); + + jabber_iq_send(iq); +} + +void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) { + PurpleConnection *gc = purple_account_get_connection(account); + JabberStream *js; + + if(gc->state != PURPLE_CONNECTED) { + if(gc->state != PURPLE_CONNECTING) + jabber_login(account); + js = gc->proto_data; + js->unregistration = TRUE; + js->unregistration_cb = cb; + js->unregistration_user_data = user_data; + return; + } + + js = gc->proto_data; + assert(!js->unregistration); /* don't allow multiple calls */ + js->unregistration = TRUE; + js->unregistration_cb = cb; + js->unregistration_user_data = user_data; + + jabber_unregister_account_cb(js); +} + void jabber_close(PurpleConnection *gc) { JabberStream *js = gc->proto_data; @@ -984,6 +1198,8 @@ jabber_buddy_remove_all_pending_buddy_info_requests(js); + jabber_parser_free(js); + if(js->iq_callbacks) g_hash_table_destroy(js->iq_callbacks); if(js->disco_callbacks) @@ -1019,9 +1235,32 @@ #endif if(js->serverFQDN) g_free(js->serverFQDN); + while(js->commands) { + JabberAdHocCommands *cmd = js->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + js->commands = g_list_delete_link(js->commands, js->commands); + } g_free(js->server_name); g_free(js->gmail_last_time); g_free(js->gmail_last_tid); + if(js->old_msg) + g_free(js->old_msg); + if(js->old_avatarhash) + g_free(js->old_avatarhash); + if(js->old_artist) + g_free(js->old_artist); + if(js->old_title) + g_free(js->old_title); + if(js->old_source) + g_free(js->old_source); + if(js->old_uri) + g_free(js->old_uri); + if(js->old_track) + g_free(js->old_track); + g_free(js); gc->proto_data = NULL; @@ -1042,9 +1281,13 @@ js->gsc ? 5 : 2, JABBER_CONNECT_STEPS); jabber_stream_init(js); break; + case JABBER_STREAM_INITIALIZING_ENCRYPTION: + purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"), + 6, JABBER_CONNECT_STEPS); + break; case JABBER_STREAM_AUTHENTICATING: purple_connection_update_progress(js->gc, _("Authenticating"), - js->gsc ? 6 : 3, JABBER_CONNECT_STEPS); + js->gsc ? 7 : 3, JABBER_CONNECT_STEPS); if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) { jabber_register_start(js); } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { @@ -1053,7 +1296,7 @@ break; case JABBER_STREAM_REINITIALIZING: purple_connection_update_progress(js->gc, _("Re-initializing Stream"), - (js->gsc ? 7 : 4), JABBER_CONNECT_STEPS); + (js->gsc ? 8 : 4), JABBER_CONNECT_STEPS); /* The stream will be reinitialized later, in jabber_recv_cb_ssl() */ js->reinit = TRUE; @@ -1080,6 +1323,38 @@ js->idle = idle ? time(NULL) - idle : idle; } +void jabber_add_feature(const char *shortname, const char *namespace, JabberFeatureEnabled cb) { + JabberFeature *feat; + + assert(shortname != NULL); + assert(namespace != NULL); + + feat = g_new0(JabberFeature,1); + feat->shortname = g_strdup(shortname); + feat->namespace = g_strdup(namespace); + feat->is_enabled = cb; + + /* try to remove just in case it already exists in the list */ + jabber_remove_feature(shortname); + + jabber_features = g_list_append(jabber_features, feat); +} + +void jabber_remove_feature(const char *shortname) { + GList *feature; + for(feature = jabber_features; feature; feature = feature->next) { + JabberFeature *feat = (JabberFeature*)feature->data; + if(!strcmp(feat->shortname, shortname)) { + g_free(feat->shortname); + g_free(feat->namespace); + + g_free(feature->data); + feature = g_list_delete_link(feature, feature); + break; + } + } +} + const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b) { return "jabber"; @@ -1154,6 +1429,9 @@ GList *l; if (full) { + PurpleStatus *status; + PurpleValue *value; + if(jb->subscription & JABBER_SUB_FROM) { if(jb->subscription & JABBER_SUB_TO) sub = _("Both"); @@ -1171,6 +1449,21 @@ } purple_notify_user_info_add_pair(user_info, _("Subscription"), sub); + + status = purple_presence_get_active_status(purple_buddy_get_presence(b)); + value = purple_status_get_attr_value(status, "mood"); + if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) { + const char *mood = purple_value_get_string(value); + + value = purple_status_get_attr_value(status, "moodtext"); + if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) { + char *moodplustext = g_strdup_printf("%s (%s)",mood,purple_value_get_string(value)); + + purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext); + g_free(moodplustext); + } else + purple_notify_user_info_add_pair(user_info, _("Mood"), mood); + } } for(l=jb->resources; l; l = l->next) { @@ -1233,6 +1526,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1243,6 +1549,19 @@ _("Chatty"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1253,6 +1572,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1263,6 +1595,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1273,6 +1618,19 @@ _("Do Not Disturb"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1379,23 +1737,33 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context) { + PurpleConnection *gc = (PurpleConnection *) context; + JabberStream *js = gc->proto_data; GList *m = NULL; PurplePluginAction *act; act = purple_plugin_action_new(_("Set User Info..."), - jabber_setup_set_info); + jabber_setup_set_info); m = g_list_append(m, act); /* if (js->protocol_options & CHANGE_PASSWORD) { */ act = purple_plugin_action_new(_("Change Password..."), - jabber_password_change); + jabber_password_change); m = g_list_append(m, act); /* } */ act = purple_plugin_action_new(_("Search for Users..."), - jabber_user_search_begin); + jabber_user_search_begin); m = g_list_append(m, act); + purple_debug_info("jabber", "jabber_actions: have pep: %s\n", js->pep?"YES":"NO"); + + if(js->pep) + jabber_pep_init_actions(&m); + + if(js->commands) + jabber_adhoc_init_server_commands(js, &m); + return m; } @@ -1690,10 +2058,10 @@ return PURPLE_CMD_RET_FAILED; if (strcmp(args[1], "owner") != 0 && - strcmp(args[1], "admin") != 0 && - strcmp(args[1], "member") != 0 && - strcmp(args[1], "outcast") != 0 && - strcmp(args[1], "none") != 0) { + strcmp(args[1], "admin") != 0 && + strcmp(args[1], "member") != 0 && + strcmp(args[1], "outcast") != 0 && + strcmp(args[1], "none") != 0) { *error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[1]); return PURPLE_CMD_RET_FAILED; } @@ -1715,16 +2083,16 @@ return PURPLE_CMD_RET_FAILED; if (strcmp(args[1], "moderator") != 0 && - strcmp(args[1], "participant") != 0 && - strcmp(args[1], "visitor") != 0 && - strcmp(args[1], "none") != 0) { + strcmp(args[1], "participant") != 0 && + strcmp(args[1], "visitor") != 0 && + strcmp(args[1], "none") != 0) { *error = g_strdup_printf(_("Unknown role: \"%s\""), args[1]); return PURPLE_CMD_RET_FAILED; } if (!jabber_chat_role_user(chat, args[0], args[1])) { *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"), - args[1], args[0]); + args[1], args[0]); return PURPLE_CMD_RET_FAILED; } @@ -1800,6 +2168,71 @@ return PURPLE_CMD_RET_OK; } +static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv, + const char *cmd, char **args, char **error, void *data) +{ + if(!args || !args[0]) + return PURPLE_CMD_RET_FAILED; + + if(!jabber_ping_jid(conv, args[0])) { + *error = g_strdup_printf(_("Unable to ping user %s"), args[0]); + return PURPLE_CMD_RET_FAILED; + } + + return PURPLE_CMD_RET_OK; +} + +static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv, + const char *cmd, char **args, char **error, void *data) +{ + JabberStream *js = conv->account->gc->proto_data; + xmlnode *msg, *buzz; + JabberBuddy *jb; + JabberBuddyResource *jbr; + char *to; + GList *iter; + + if(!args || !args[0]) + return PURPLE_CMD_RET_FAILED; + + jb = jabber_buddy_find(js, args[0], FALSE); + if(!jb) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + + jbr = jabber_buddy_find_resource(jb, NULL); + if(!jbr) { + *error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + if(!jbr->caps) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) { + msg = xmlnode_new("message"); + to = g_strdup_printf("%s/%s", args[0], jbr->name); + xmlnode_set_attrib(msg,"to",to); + g_free(to); + + /* avoid offline storage */ + xmlnode_set_attrib(msg,"type","headline"); + + buzz = xmlnode_new_child(msg,"attention"); + xmlnode_set_namespace(buzz,"http://www.xmpp.org/extensions/xep-0224.html#ns"); + + jabber_send(js,msg); + xmlnode_free(msg); + + return PURPLE_CMD_RET_OK; + } + } + *error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), args[0]); + return PURPLE_CMD_RET_FAILED; +} + gboolean jabber_offline_message(const PurpleBuddy *buddy) { return TRUE; @@ -1808,79 +2241,89 @@ void jabber_register_commands(void) { 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); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_chat_config, + _("config: Configure a chat room."), NULL); 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); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_chat_config, + _("configure: Configure a chat room."), NULL); 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); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_chat_nick, + _("nick <new nickname>: Change your nickname."), + NULL); 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 [room]: Leave the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_part, _("part [room]: Leave the room."), + NULL); 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); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_chat_register, + _("register: Register with a chat room."), NULL); /* XXX: there needs to be a core /topic cmd, methinks */ purple_cmd_register("topic", "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_topic, - _("topic [new topic]: View or change the topic."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_topic, + _("topic [new topic]: View or change the topic."), + NULL); purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_ban, - _("ban <user> [room]: Ban a user from the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_ban, + _("ban <user> [room]: Ban a user from the room."), + NULL); purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_affiliate, - _("affiliate <user> <owner|admin|member|outcast|none>: Set a user's affiliation with the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_affiliate, + _("affiliate <user> <owner|admin|member|outcast|none>: Set a user's affiliation with the room."), + NULL); purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_role, - _("role <user> <moderator|participant|visitor|none>: Set a user's role in the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_role, + _("role <user> <moderator|participant|visitor|none>: Set a user's role in the room."), + NULL); purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_invite, - _("invite <user> [message]: Invite a user to the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_invite, + _("invite <user> [message]: Invite a user to the room."), + NULL); purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_join, - _("join: <room> [server]: Join a chat on this server."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_join, + _("join: <room> [server]: Join a chat on this server."), + NULL); purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL, - PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | - PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_kick, - _("kick <user> [room]: Kick a user from the room."), - NULL); + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", + jabber_cmd_chat_kick, + _("kick <user> [room]: Kick a user from the room."), + NULL); 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); + 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); + purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL, + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | + PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_ping, + _("ping <jid>: Ping a user/component/server."), + NULL); + purple_cmd_register("buzz", "s", PURPLE_CMD_P_PRPL, + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_buzz, + _("buzz: Buzz a user to get their attention"), NULL); } void jabber_init_plugin(PurplePlugin *plugin) { - my_protocol = plugin; + my_protocol = plugin; } diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Sep 05 22:32:14 2007 +0000 @@ -12,16 +12,42 @@ * * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _PURPLE_JABBER_H_ #define _PURPLE_JABBER_H_ +typedef enum { + JABBER_CAP_NONE = 0, + JABBER_CAP_XHTML = 1 << 0, + JABBER_CAP_COMPOSING = 1 << 1, + JABBER_CAP_SI = 1 << 2, + JABBER_CAP_SI_FILE_XFER = 1 << 3, + JABBER_CAP_BYTESTREAMS = 1 << 4, + JABBER_CAP_IBB = 1 << 5, + JABBER_CAP_CHAT_STATES = 1 << 6, + JABBER_CAP_IQ_SEARCH = 1 << 7, + JABBER_CAP_IQ_REGISTER = 1 << 8, + + /* Google Talk extensions: + * http://code.google.com/apis/talk/jep_extensions/extensions.html + */ + JABBER_CAP_GMAIL_NOTIFY = 1 << 9, + JABBER_CAP_GOOGLE_ROSTER = 1 << 10, + + JABBER_CAP_PING = 1 << 11, + JABBER_CAP_ADHOC = 1 << 12, + + JABBER_CAP_RETRIEVED = 1 << 31 +} JabberCapabilities; + +typedef struct _JabberStream JabberStream; + #include #include #include "circbuffer.h" @@ -32,6 +58,7 @@ #include "jutil.h" #include "xmlnode.h" +#include "buddy.h" #ifdef HAVE_CYRUS_SASL #include @@ -40,36 +67,16 @@ #define CAPS0115_NODE "http://pidgin.im/caps" typedef enum { - JABBER_CAP_NONE = 0, - JABBER_CAP_XHTML = 1 << 0, - JABBER_CAP_COMPOSING = 1 << 1, - JABBER_CAP_SI = 1 << 2, - JABBER_CAP_SI_FILE_XFER = 1 << 3, - JABBER_CAP_BYTESTREAMS = 1 << 4, - JABBER_CAP_IBB = 1 << 5, - JABBER_CAP_CHAT_STATES = 1 << 6, - JABBER_CAP_IQ_SEARCH = 1 << 7, - JABBER_CAP_IQ_REGISTER = 1 << 8, - - /* Google Talk extensions: - * http://code.google.com/apis/talk/jep_extensions/extensions.html - */ - JABBER_CAP_GMAIL_NOTIFY = 1 << 9, - JABBER_CAP_GOOGLE_ROSTER = 1 << 10, - - JABBER_CAP_RETRIEVED = 1 << 31 -} JabberCapabilities; - -typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, JABBER_STREAM_INITIALIZING, + JABBER_STREAM_INITIALIZING_ENCRYPTION, JABBER_STREAM_AUTHENTICATING, JABBER_STREAM_REINITIALIZING, JABBER_STREAM_CONNECTED } JabberStreamState; -typedef struct _JabberStream +struct _JabberStream { int fd; @@ -152,12 +159,50 @@ int sasl_state; int sasl_maxbuf; GString *sasl_mechs; - + + gboolean unregistration; + PurpleAccountUnregistrationCb unregistration_cb; + void *unregistration_user_data; + gboolean vcard_fetched; + + /* does the local server support PEP? */ + gboolean pep; + + /* Is Buzz enabled? */ + gboolean allowBuzz; + + /* A list of JabberAdHocCommands supported by the server */ + GList *commands; + + /* last presence update to check for differences */ + JabberBuddyState old_state; + char *old_msg; + int old_priority; + char *old_avatarhash; + + /* same for user tune */ + char *old_artist; + char *old_title; + char *old_source; + char *old_uri; + int old_length; + char *old_track; +}; -} JabberStream; +typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace); -void jabber_process_packet(JabberStream *js, xmlnode *packet); +typedef struct _JabberFeature +{ + gchar *shortname; + gchar *namespace; + JabberFeatureEnabled *is_enabled; +} JabberFeature; + +/* what kind of additional features as returned from disco#info are supported? */ +extern GList *jabber_features; + +void jabber_process_packet(JabberStream *js, xmlnode **packet); void jabber_send(JabberStream *js, xmlnode *data); void jabber_send_raw(JabberStream *js, const char *data, int len); @@ -170,6 +215,9 @@ char *jabber_parse_error(JabberStream *js, xmlnode *packet); +void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */ +void jabber_remove_feature(const gchar *shortname); + /** PRPL functions */ const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b); const char* jabber_list_emblem(PurpleBuddy *b); @@ -180,7 +228,9 @@ void jabber_close(PurpleConnection *gc); void jabber_idle_set(PurpleConnection *gc, int idle); void jabber_keepalive(PurpleConnection *gc); +void jabber_register_gateway(JabberStream *js, const char *gateway); void jabber_register_account(PurpleAccount *account); +void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data); void jabber_convo_closed(PurpleConnection *gc, const char *who); PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name); gboolean jabber_offline_message(const PurpleBuddy *buddy); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/jutil.h Wed Sep 05 22:32:14 2007 +0000 @@ -22,6 +22,8 @@ #ifndef _PURPLE_JABBER_JUTIL_H_ #define _PURPLE_JABBER_JUTIL_H_ +#include "account.h" + typedef struct _JabberID { char *node; char *domain; diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Sep 05 22:32:14 2007 +0000 @@ -39,6 +39,9 @@ #include "message.h" #include "presence.h" #include "google.h" +#include "pep.h" +#include "usertune.h" +#include "caps.h" static PurplePluginProtocolInfo prpl_info = { @@ -52,7 +55,7 @@ #endif NULL, /* user_splits */ NULL, /* protocol_options */ - {"png,gif,jpeg", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ + {"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ jabber_list_icon, /* list_icon */ jabber_list_emblem, /* list_emblems */ jabber_status_text, /* status_text */ @@ -111,11 +114,11 @@ NULL, /* whiteboard_prpl_ops */ jabber_prpl_send_raw, /* send_raw */ jabber_roomlist_room_serialize, /* roomlist_room_serialize */ + jabber_unregister_account, /* unregister_user */ /* padding */ NULL, NULL, - NULL, NULL }; @@ -191,49 +194,63 @@ static void init_plugin(PurplePlugin *plugin) { - PurpleAccountUserSplit *split; - PurpleAccountOption *option; - + PurpleAccountUserSplit *split; + PurpleAccountOption *option; + /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */ - split = purple_account_user_split_new(_("Domain"), NULL, '@'); - purple_account_user_split_set_reverse(split, FALSE); - prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - - split = purple_account_user_split_new(_("Resource"), "Home", '/'); - purple_account_user_split_set_reverse(split, FALSE); - prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - - option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - option = purple_account_option_bool_new( - _("Allow plaintext auth over unencrypted streams"), - "auth_plain_in_clear", FALSE); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); + split = purple_account_user_split_new(_("Domain"), NULL, '@'); + purple_account_user_split_set_reverse(split, FALSE); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + + split = purple_account_user_split_new(_("Resource"), "Home", '/'); + purple_account_user_split_set_reverse(split, FALSE); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + + option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new( + _("Allow plaintext auth over unencrypted streams"), + "auth_plain_in_clear", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_int_new(_("Connect port"), "port", 5222); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_string_new(_("Connect server"), + "connect_server", NULL); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + + 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 + sasl_client_init(NULL); +#endif + jabber_register_commands(); + + jabber_iq_init(); + jabber_pep_init(); + + jabber_tune_init(); + jabber_caps_init(); - option = purple_account_option_int_new(_("Connect port"), "port", 5222); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - option = purple_account_option_string_new(_("Connect server"), - "connect_server", NULL); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - - 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 - sasl_client_init(NULL); -#endif - jabber_register_commands(); - - jabber_iq_init(); + jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled); + + jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata); } diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/message.c --- a/libpurple/protocols/jabber/message.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/message.c Wed Sep 05 22:32:14 2007 +0000 @@ -10,12 +10,12 @@ * * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "internal.h" @@ -30,6 +30,7 @@ #include "google.h" #include "message.h" #include "xmlnode.h" +#include "pep.h" void jabber_message_free(JabberMessage *jm) { @@ -61,7 +62,7 @@ if(jabber_find_unnormalized_conv(jm->from, jm->js->gc->account)) { from = g_strdup(jm->from); - } else if(jid->node) { + } else if(jid->node) { if(jid->resource) { PurpleConversation *conv; @@ -99,13 +100,13 @@ escaped = g_markup_escape_text(who, -1); g_snprintf(buf, sizeof(buf), - _("%s has left the conversation."), escaped); + _("%s has left the conversation."), escaped); /* At some point when we restructure PurpleConversation, * this should be able to be implemented by removing the * user from the conversation like we do with chats now. */ purple_conversation_write(conv, "", buf, - PURPLE_MESSAGE_SYSTEM, time(NULL)); + PURPLE_MESSAGE_SYSTEM, time(NULL)); } } serv_got_typing_stopped(jm->js->gc, from); @@ -147,9 +148,13 @@ static void handle_headline(JabberMessage *jm) { char *title; - GString *body = g_string_new(""); + GString *body; GList *etc; + + if(!jm->xhtml && !jm->body) + return; /* ignore headlines without any content */ + body = g_string_new(""); title = g_strdup_printf(_("Message from %s"), jm->from); if(jm->xhtml) @@ -273,6 +278,36 @@ g_free(buf); } +static void handle_buzz(JabberMessage *jm) { + PurpleBuddy *buddy; + PurpleAccount *account; + PurpleConversation *c; + char *username, *str; + + /* Delayed buzz MUST NOT be accepted */ + if(jm->delayed) + return; + + /* Reject buzz when it's not enabled */ + if(!jm->js->allowBuzz) + return; + + account = purple_connection_get_account(jm->js->gc); + + if ((buddy = purple_find_buddy(account, jm->from)) != NULL) + username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1); + else + return; /* Do not accept buzzes from unknown people */ + + c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, jm->from); + + str = g_strdup_printf(_("%s just sent you a Buzz!"), username); + + purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL)); + g_free(username); + g_free(str); +} + void jabber_message_parse(JabberStream *js, xmlnode *packet) { JabberMessage *jm; @@ -308,48 +343,58 @@ jm->id = g_strdup(xmlnode_get_attrib(packet, "id")); for(child = packet->child; child; child = child->next) { + const char *xmlns = xmlnode_get_namespace(child); + if(!xmlns) + xmlns = ""; if(child->type != XMLNODE_TYPE_TAG) continue; - if(!strcmp(child->name, "subject")) { + if(!strcmp(child->name, "subject") && !strcmp(xmlns,"jabber:client")) { if(!jm->subject) jm->subject = xmlnode_get_data(child); - } else if(!strcmp(child->name, "thread")) { + } else if(!strcmp(child->name, "thread") && !strcmp(xmlns,"jabber:client")) { if(!jm->thread_id) jm->thread_id = xmlnode_get_data(child); - } else if(!strcmp(child->name, "body")) { + } else if(!strcmp(child->name, "body") && !strcmp(xmlns,"jabber:client")) { if(!jm->body) { char *msg = xmlnode_to_str(child, NULL); jm->body = purple_strdup_withhtml(msg); g_free(msg); } - } else if(!strcmp(child->name, "html")) { + } else if(!strcmp(child->name, "html") && !strcmp(xmlns,"http://jabber.org/protocol/xhtml-im")) { if(!jm->xhtml && xmlnode_get_child(child, "body")) { char *c; jm->xhtml = xmlnode_to_str(child, NULL); - /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention - * treated \n as a newline for compatibility with other protocols + /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention + * treated \n as a newline for compatibility with other protocols */ for (c = jm->xhtml; *c != '\0'; c++) { if (*c == '\n') *c = ' '; } } - } else if(!strcmp(child->name, "active")) { + } else if(!strcmp(child->name, "active") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_ACTIVE; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "composing")) { + } else if(!strcmp(child->name, "composing") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_COMPOSING; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "paused")) { + } else if(!strcmp(child->name, "paused") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_PAUSED; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "inactive")) { + } else if(!strcmp(child->name, "inactive") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_INACTIVE; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "gone")) { + } else if(!strcmp(child->name, "gone") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_GONE; jm->typing_style |= JM_TS_JEP_0085; + } else if(!strcmp(child->name, "event") && !strcmp(xmlns,"http://jabber.org/protocol/pubsub#event")) { + xmlnode *items; + jm->type = JABBER_MESSAGE_EVENT; + for(items = xmlnode_get_child(child,"items"); items; items = items->next) + jm->eventitems = g_list_append(jm->eventitems, items); + } else if(!strcmp(child->name, "attention") && !strcmp(xmlns,"http://www.xmpp.org/extensions/xep-0224.html#ns")) { + jm->hasBuzz = TRUE; } else if(!strcmp(child->name, "error")) { const char *code = xmlnode_get_attrib(child, "code"); char *code_txt = NULL; @@ -364,8 +409,12 @@ g_free(code_txt); g_free(text); + } else if(!strcmp(child->name, "delay") && xmlns && !strcmp(xmlns,"urn:xmpp:delay")) { + const char *timestamp = xmlnode_get_attrib(child, "stamp"); + jm->delayed = TRUE; + if(timestamp) + jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL); } else if(!strcmp(child->name, "x")) { - const char *xmlns = xmlnode_get_namespace(child); if(xmlns && !strcmp(xmlns, "jabber:x:event")) { if(xmlnode_get_child(child, "composing")) { if(jm->chat_state == JM_STATE_ACTIVE) @@ -410,6 +459,9 @@ } } } + + if(jm->hasBuzz) + handle_buzz(jm); switch(jm->type) { case JABBER_MESSAGE_NORMAL: @@ -425,6 +477,9 @@ case JABBER_MESSAGE_GROUPCHAT_INVITE: handle_groupchat_invite(jm); break; + case JABBER_MESSAGE_EVENT: + jabber_handle_event(jm); + break; case JABBER_MESSAGE_ERROR: handle_error(jm); break; @@ -461,6 +516,7 @@ type = "error"; break; case JABBER_MESSAGE_OTHER: + default: type = NULL; break; } @@ -689,3 +745,8 @@ jabber_message_send(jm); jabber_message_free(jm); } + +gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace) { + return js->allowBuzz; +} + diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/message.h --- a/libpurple/protocols/jabber/message.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/message.h Wed Sep 05 22:32:14 2007 +0000 @@ -12,12 +12,12 @@ * * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _PURPLE_JABBER_MESSAGE_H_ #define _PURPLE_JABBER_MESSAGE_H_ @@ -35,10 +35,12 @@ JABBER_MESSAGE_HEADLINE, JABBER_MESSAGE_ERROR, JABBER_MESSAGE_GROUPCHAT_INVITE, + JABBER_MESSAGE_EVENT, JABBER_MESSAGE_OTHER } type; time_t sent; gboolean delayed; + gboolean hasBuzz; char *id; char *from; char *to; @@ -61,6 +63,7 @@ JM_STATE_GONE } chat_state; GList *etc; + GList *eventitems; } JabberMessage; void jabber_message_free(JabberMessage *jm); @@ -75,4 +78,6 @@ unsigned int jabber_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state); void jabber_message_conv_closed(JabberStream *js, const char *who); +gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace); + #endif /* _PURPLE_JABBER_MESSAGE_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/parser.c --- a/libpurple/protocols/jabber/parser.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/parser.c Wed Sep 05 22:32:14 2007 +0000 @@ -63,7 +63,7 @@ if(js->protocol_version == JABBER_PROTO_0_9) js->auth_type = JABBER_AUTH_IQ_AUTH; - if(js->state == JABBER_STREAM_INITIALIZING) + if(js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); } else { @@ -113,7 +113,7 @@ } else { xmlnode *packet = js->current; js->current = NULL; - jabber_process_packet(js, packet); + jabber_process_packet(js, &packet); xmlnode_free(packet); } } @@ -174,6 +174,10 @@ * the parser context when you try to use it (this way, it can figure * out the encoding at creation time. So, setting up the parser is * just a matter of destroying any current parser. */ + jabber_parser_free(js); +} + +void jabber_parser_free(JabberStream *js) { if (js->context) { xmlParseChunk(js->context, NULL,0,1); xmlFreeParserCtxt(js->context); @@ -181,7 +185,6 @@ } } - void jabber_parser_process(JabberStream *js, const char *buf, int len) { if (js->context == NULL) { diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/parser.h --- a/libpurple/protocols/jabber/parser.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/parser.h Wed Sep 05 22:32:14 2007 +0000 @@ -25,6 +25,7 @@ #include "jabber.h" void jabber_parser_setup(JabberStream *js); +void jabber_parser_free(JabberStream *js); void jabber_parser_process(JabberStream *js, const char *buf, int len); #endif /* _PURPLE_JABBER_PARSER_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/pep.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/pep.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,127 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pep.h" +#include "iq.h" +#include +#include "usermood.h" +#include "usernick.h" + +static GHashTable *pep_handlers = NULL; + +void jabber_pep_init(void) { + if(!pep_handlers) { + pep_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + /* register PEP handlers */ + jabber_mood_init(); + jabber_nick_init(); + } +} + +void jabber_pep_init_actions(GList **m) { + /* register the PEP-specific actions */ + jabber_mood_init_action(m); + jabber_nick_init_action(m); +} + +void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc) { + gchar *notifyns = g_strdup_printf("%s+notify", xmlns); + jabber_add_feature(shortname, notifyns, NULL); /* receiving PEPs is always supported */ + g_free(notifyns); + g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc); +} + +static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) { + const char *from = xmlnode_get_attrib(packet,"from"); + xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub#event"); + xmlnode *items = NULL; + JabberPEPHandler *cb = data; + + if(pubsub) + items = xmlnode_get_child(pubsub, "items"); + + cb(js, from, items); +} + +void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb) { + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); + xmlnode *pubsub, *items, *item; + + xmlnode_set_attrib(iq->node,"to",to); + pubsub = xmlnode_new_child(iq->node,"pubsub"); + + xmlnode_set_namespace(pubsub,"http://jabber.org/protocol/pubsub"); + + items = xmlnode_new_child(pubsub, "items"); + xmlnode_set_attrib(items,"node",node); + + item = xmlnode_new_child(items, "item"); + if(id) + xmlnode_set_attrib(item, "id", id); + + jabber_iq_set_callback(iq,do_pep_iq_request_item_callback,(gpointer)cb); + + jabber_iq_send(iq); +} + +gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace) { + return js->pep; +} + +void jabber_handle_event(JabberMessage *jm) { + /* this may be called even when the own server doesn't support pep! */ + JabberPEPHandler *jph; + GList *itemslist; + char *jid = jabber_get_bare_jid(jm->from); + + for(itemslist = jm->eventitems; itemslist; itemslist = itemslist->next) { + xmlnode *items = (xmlnode*)itemslist->data; + const char *nodename = xmlnode_get_attrib(items,"node"); + + if(nodename && (jph = g_hash_table_lookup(pep_handlers, nodename))) + jph(jm->js, jid, items); + } + + /* discard items we don't have a handler for */ + g_free(jid); +} + +void jabber_pep_publish(JabberStream *js, xmlnode *publish) { + JabberIq *iq; + + if(js->pep != TRUE) { + /* ignore when there's no PEP support on the server */ + xmlnode_free(publish); + return; + } + + iq = jabber_iq_new(js, JABBER_IQ_SET); + + xmlnode *pubsub = xmlnode_new("pubsub"); + xmlnode_set_namespace(pubsub, "http://jabber.org/protocol/pubsub"); + + xmlnode_insert_child(pubsub, publish); + + xmlnode_insert_child(iq->node, pubsub); + + jabber_iq_send(iq); +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/pep.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/pep.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,85 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_PEP_H_ +#define _PURPLE_JABBER_PEP_H_ + +#include "jabber.h" +#include "message.h" +#include "buddy.h" + +void jabber_pep_init(void); + +void jabber_pep_init_actions(GList **m); + +/* + * Callback for receiving PEP events. + * + * @parameter js The JabberStream this item was received on + * @parameter items The <items/>-tag with the <item/>-children + */ +typedef void (JabberPEPHandler)(JabberStream *js, const char *from, xmlnode *items); + +/* + * Registers a callback for PEP events. Also automatically announces this receiving capability via disco#info. + * Don't forget to use jabber_add_feature when supporting the sending of PEP events of this type. + * + * @parameter shortname A short name for this feature for XEP-0115. It has no semantic meaning, it just has to be unique. + * @parameter xmlns The namespace for this event + * @parameter handlerfunc The callback to be used when receiving an event with this namespace + */ +void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc); + +/* + * Request a specific item from another PEP node. + * + * @parameter js The JabberStream that should be used + * @parameter to The target PEP node + * @parameter node The node name of the item that is requested + * @parameter id The item id of the requested item (may be NULL) + * @parameter cb The callback to be used when this item is received + * + * The items element passed to the callback will be NULL if any error occured (like a permission error, node doesn't exist etc.) + */ +void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb); + +/* + * Default callback that can be used for namespaces which should only be enabled when PEP is supported + * + * @parameter js The JabberStream struct for this connection + * @parameter shortname The namespace's shortname (for caps), ignored. + * @parameter namespace The namespace that's queried, ignored. + * + * @returns TRUE when PEP is enabled, FALSE otherwise + */ +gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace); + +void jabber_handle_event(JabberMessage *jm); + +/* + * Publishes PEP item(s) + * + * @parameter js The JabberStream associated with the connection this event should be published + * @parameter publish The publish node. This could be for example <publish node='http://jabber.org/protocol/tune'/> with an <item/> as subnode + */ +void jabber_pep_publish(JabberStream *js, xmlnode *publish); + +#endif /* _PURPLE_JABBER_PEP_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/ping.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/ping.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,80 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "debug.h" +#include "xmlnode.h" + +#include "jabber.h" +#include "ping.h" +#include "iq.h" + +void +jabber_ping_parse(JabberStream *js, xmlnode *packet) +{ + JabberIq *iq; + + purple_debug_info("jabber", "jabber_ping_parse\n"); + + iq = jabber_iq_new(js, JABBER_IQ_RESULT); + + xmlnode_set_attrib(iq->node, "to", xmlnode_get_attrib(packet, "from") ); + + jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + + jabber_iq_send(iq); +} + +static void jabber_ping_result_cb(JabberStream *js, xmlnode *packet, + gpointer data) +{ + const char *type = xmlnode_get_attrib(packet, "type"); + + purple_debug_info("jabber", "jabber_ping_result_cb\n"); + if(type && !strcmp(type, "result")) { + purple_debug_info("jabber", "PONG!\n"); + } else { + purple_debug_info("jabber", "(not supported)\n"); + } +} + +gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid) +{ + JabberIq *iq; + xmlnode *ping; + + purple_debug_info("jabber", "jabber_ping_jid\n"); + + iq = jabber_iq_new(conv->account->gc->proto_data, JABBER_IQ_GET); + xmlnode_set_attrib(iq->node, "to", jid); + + ping = xmlnode_new_child(iq->node, "ping"); + xmlnode_set_namespace(ping, "urn:xmpp:ping"); + + jabber_iq_set_callback(iq, jabber_ping_result_cb, NULL); + jabber_iq_send(iq); + + + + return TRUE; +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/ping.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/ping.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,35 @@ +/** + * @file ping.h utility functions + * + * purple + * + * Copyright (C) 2003, Nathan Walp + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _PURPLE_JABBER_PING_H_ +#define _PURPLE_JABBER_PING_H_ + +#include "jabber.h" +#include "conversation.h" + +void jabber_ping_parse(JabberStream *js, + xmlnode *packet); + + +gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid); + + +#endif /* _PURPLE_JABBER_PING_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed Sep 05 22:32:14 2007 +0000 @@ -36,6 +36,9 @@ #include "presence.h" #include "iq.h" #include "jutil.h" +#include "adhoccommands.h" + +#include "usertune.h" static void chats_send_presence_foreach(gpointer key, gpointer val, @@ -101,6 +104,9 @@ char *stripped = NULL; JabberBuddyState state; int priority; + const char *artist, *title, *source, *uri, *track; + int length; + gboolean allowBuzz; if(NULL == status) { PurplePresence *gpresence = purple_account_get_presence(account); @@ -127,28 +133,95 @@ } purple_status_to_jabber(status, &state, &stripped, &priority); - + + /* check for buzz support */ + allowBuzz = purple_status_get_attr_boolean(status,"buzz"); + /* changing the buzz state has to trigger a re-broadcasting of the presence for caps */ + +#define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \ + (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b))) + /* check if there are any differences to the and send them in that case */ + if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) || + js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) { + js->allowBuzz = allowBuzz; + presence = jabber_presence_create_js(js, state, stripped, priority); - presence = jabber_presence_create(state, stripped, priority); - g_free(stripped); + if(js->avatar_hash) { + x = xmlnode_new_child(presence, "x"); + xmlnode_set_namespace(x, "vcard-temp:x:update"); + photo = xmlnode_new_child(x, "photo"); + xmlnode_insert_data(photo, js->avatar_hash, -1); + } + + jabber_send(js, presence); - if(js->avatar_hash) { - x = xmlnode_new_child(presence, "x"); - xmlnode_set_namespace(x, "vcard-temp:x:update"); - photo = xmlnode_new_child(x, "photo"); - xmlnode_insert_data(photo, js->avatar_hash, -1); + g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence); + xmlnode_free(presence); + + /* update old values */ + + if(js->old_msg) + g_free(js->old_msg); + if(js->old_avatarhash) + g_free(js->old_avatarhash); + js->old_msg = g_strdup(stripped); + js->old_avatarhash = g_strdup(js->avatar_hash); + js->old_state = state; + js->old_priority = priority; + g_free(stripped); } - - jabber_send(js, presence); - - g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence); - xmlnode_free(presence); - + + /* next, check if there are any changes to the tune values */ + artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST); + title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE); + source = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM); + uri = purple_status_get_attr_string(status, PURPLE_TUNE_URL); + track = purple_status_get_attr_string(status, PURPLE_TUNE_TRACK); + length = (!purple_status_get_attr_value(status, PURPLE_TUNE_TIME))?-1:purple_status_get_attr_int(status, PURPLE_TUNE_TIME); + + if(CHANGED(artist, js->old_artist) || CHANGED(title, js->old_title) || CHANGED(source, js->old_source) || + CHANGED(uri, js->old_uri) || CHANGED(track, js->old_track) || (length != js->old_length)) { + PurpleJabberTuneInfo tuneinfo = { + (char*)artist, + (char*)title, + (char*)source, + (char*)track, + length, + (char*)uri + }; + jabber_tune_set(js->gc, &tuneinfo); + + /* update old values */ + if(js->old_artist) + g_free(js->old_artist); + if(js->old_title) + g_free(js->old_title); + if(js->old_source) + g_free(js->old_source); + if(js->old_uri) + g_free(js->old_uri); + if(js->old_track) + g_free(js->old_track); + js->old_artist = g_strdup(artist); + js->old_title = g_strdup(title); + js->old_source = g_strdup(source); + js->old_uri = g_strdup(uri); + js->old_length = length; + js->old_track = g_strdup(track); + } + +#undef CHANGED(a,b) + jabber_presence_fake_to_self(js, status); } xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority) { + return jabber_presence_create_js(NULL, state, msg, priority); +} + +xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority) +{ xmlnode *show, *status, *presence, *pri, *c; const char *show_string = NULL; @@ -183,7 +256,39 @@ xmlnode_set_namespace(c, "http://jabber.org/protocol/caps"); xmlnode_set_attrib(c, "node", CAPS0115_NODE); xmlnode_set_attrib(c, "ver", VERSION); - + + if(js != NULL) { + /* add the extensions */ + char extlist[1024]; + unsigned remaining = 1023; /* one less for the \0 */ + GList *feature; + + extlist[0] = '\0'; + for(feature = jabber_features; feature && remaining > 0; feature = feature->next) { + JabberFeature *feat = (JabberFeature*)feature->data; + unsigned featlen; + + if(feat->is_enabled != NULL && feat->is_enabled(js, feat->shortname, feat->namespace) == FALSE) + continue; /* skip this feature */ + + featlen = strlen(feat->shortname); + + /* cut off when we don't have any more space left in our buffer (too bad) */ + if(featlen > remaining) + break; + + strncat(extlist,feat->shortname,remaining); + remaining -= featlen; + if(feature->next) { /* no space at the end */ + strncat(extlist," ",remaining); + --remaining; + } + } + /* did we add anything? */ + if(remaining < 1023) + xmlnode_set_attrib(c, "ext", extlist); + } + return presence; } @@ -252,6 +357,35 @@ } } +typedef struct _JabberPresenceCapabilities { + JabberStream *js; + JabberBuddyResource *jbr; + char *from; +} JabberPresenceCapabilities; + +static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) { + JabberPresenceCapabilities *userdata = user_data; + GList *iter; + + if(userdata->jbr->caps) + jabber_caps_free_clientinfo(userdata->jbr->caps); + userdata->jbr->caps = info; + + for(iter = info->features; iter; iter = g_list_next(iter)) { + if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) { + JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + xmlnode_set_attrib(iq->node, "to", userdata->from); + xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands"); + + jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL); + jabber_iq_send(iq); + break; + } + } + g_free(user_data); +} + void jabber_presence_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); @@ -273,6 +407,7 @@ xmlnode *y; gboolean muc = FALSE; char *avatar_hash = NULL; + xmlnode *caps = NULL; if(!(jb = jabber_buddy_find(js, from, TRUE))) return; @@ -335,8 +470,10 @@ 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); @@ -347,6 +484,11 @@ priority = atoi(p); g_free(p); } + } else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) { + /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ + delayed = TRUE; + } else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) { + caps = y; /* store for later, when creating buddy resource */ } else if(!strcmp(y->name, "x")) { const char *xmlns = xmlnode_get_namespace(y); if(xmlns && !strcmp(xmlns, "jabber:x:delay")) { @@ -524,18 +666,22 @@ g_free(room_jid); } else { buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "", - jid->node ? "@" : "", jid->domain); + jid->node ? "@" : "", jid->domain); if((b = purple_find_buddy(js->gc->account, buddy_name)) == NULL) { - purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n", - buddy_name, purple_account_get_username(js->gc->account), js->gc->account); - jabber_id_free(jid); - g_free(avatar_hash); - g_free(buddy_name); - g_free(status); - return; + if(!jid->node || strcmp(jid->node,js->user->node) || strcmp(jid->domain,js->user->domain)) { + purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n", + buddy_name, purple_account_get_username(js->gc->account), js->gc->account); + jabber_id_free(jid); + g_free(avatar_hash); + g_free(buddy_name); + 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(avatar_hash) { + 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; @@ -573,6 +719,19 @@ } else { jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state, status); + if(caps) { + const char *node = xmlnode_get_attrib(caps,"node"); + const char *ver = xmlnode_get_attrib(caps,"ver"); + const char *ext = xmlnode_get_attrib(caps,"ext"); + + if(node && ver) { + JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1); + userdata->js = js; + userdata->jbr = jbr; + userdata->from = g_strdup(from); + jabber_caps_get_info(js, from, node, ver, ext, jabber_presence_set_capabilities, userdata); + } + } } if((found_jbr = jabber_buddy_find_resource(jb, NULL))) { diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/presence.h --- a/libpurple/protocols/jabber/presence.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/presence.h Wed Sep 05 22:32:14 2007 +0000 @@ -27,7 +27,8 @@ #include "xmlnode.h" void jabber_presence_send(PurpleAccount *account, PurpleStatus *status); -xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); +xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); /* DEPRECATED */ +xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority); void jabber_presence_parse(JabberStream *js, xmlnode *packet); void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type); diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usermood.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usermood.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,214 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "usermood.h" +#include "pep.h" +#include +#include +#include "internal.h" +#include "request.h" + +static const char *moodstrings[] = { + "afraid", + "amazed", + "angry", + "annoyed", + "anxious", + "aroused", + "ashamed", + "bored", + "brave", + "calm", + "cold", + "confused", + "contented", + "cranky", + "curious", + "depressed", + "disappointed", + "disgusted", + "distracted", + "embarrassed", + "excited", + "flirtatious", + "frustrated", + "grumpy", + "guilty", + "happy", + "hot", + "humbled", + "humiliated", + "hungry", + "hurt", + "impressed", + "in_awe", + "in_love", + "indignant", + "interested", + "intoxicated", + "invincible", + "jealous", + "lonely", + "mean", + "moody", + "nervous", + "neutral", + "offended", + "playful", + "proud", + "relieved", + "remorseful", + "restless", + "sad", + "sarcastic", + "serious", + "shocked", + "shy", + "sick", + "sleepy", + "stressed", + "surprised", + "thirsty", + "worried", + NULL +}; + +static void jabber_mood_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + const char *newmood = NULL; + char *moodtext = NULL; + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *moodinfo, *mood; + /* ignore the mood of people not on our buddy list */ + if (!buddy || !item) + return; + + mood = xmlnode_get_child_with_namespace(item, "mood", "http://jabber.org/protocol/mood"); + if (!mood) + return; + for (moodinfo = mood->child; moodinfo; moodinfo = moodinfo->next) { + if (moodinfo->type == XMLNODE_TYPE_TAG) { + if (!strcmp(moodinfo->name, "text")) { + if (!moodtext) /* only pick the first one */ + moodtext = xmlnode_get_data(moodinfo); + } else { + int i; + for (i = 0; moodstrings[i]; ++i) { + /* verify that the mood is known (valid) */ + if (!strcmp(moodinfo->name, moodstrings[i])) { + newmood = moodstrings[i]; + break; + } + } + } + if (newmood != NULL && moodtext != NULL) + break; + } + } + if (newmood != NULL) { + JabberBuddyResource *resource = jabber_buddy_find_resource(buddy, NULL); + if(!resource) { /* huh? */ + if (moodtext) + g_free(moodtext); + return; + } + const char *status_id = jabber_buddy_state_get_status_id(resource->state); + + purple_prpl_got_user_status(js->gc->account, from, status_id, "mood", _(newmood), "moodtext", moodtext?moodtext:"", NULL); + } + if (moodtext) + g_free(moodtext); +} + +void jabber_mood_init(void) { + jabber_add_feature("mood", "http://jabber.org/protocol/mood", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("moodn", "http://jabber.org/protocol/mood", jabber_mood_cb); +} + +static void do_mood_set_from_fields(PurpleConnection *gc, PurpleRequestFields *fields) { + JabberStream *js = gc->proto_data; + + jabber_mood_set(js, moodstrings[purple_request_fields_get_choice(fields, "mood")], purple_request_fields_get_string(fields, "text")); +} + +static void do_mood_set_mood(PurplePluginAction *action) { + PurpleConnection *gc = (PurpleConnection *) action->context; + + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + int i; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_choice_new("mood", + _("Mood"), 0); + + for(i = 0; moodstrings[i]; ++i) + purple_request_field_choice_add(field, _(moodstrings[i])); + + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + + field = purple_request_field_string_new("text", + _("Description"), NULL, + FALSE); + purple_request_field_group_add_field(group, field); + + purple_request_fields(gc, _("Edit User Mood"), + _("Edit User Mood"), + _("Please select your mood from the list."), + fields, + _("Set"), G_CALLBACK(do_mood_set_from_fields), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); + +} + +void jabber_mood_init_action(GList **m) { + PurplePluginAction *act = purple_plugin_action_new(_("Set Mood..."), do_mood_set_mood); + *m = g_list_append(*m, act); +} + +void jabber_mood_set(JabberStream *js, const char *mood, const char *text) { + xmlnode *publish, *moodnode; + + assert(mood != NULL); + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood"); + moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood"); + xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood"); + xmlnode_new_child(moodnode, mood); + + if (text && text[0] != '\0') { + xmlnode *textnode = xmlnode_new_child(moodnode, "text"); + xmlnode_insert_data(textnode, text, -1); + } + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usermood.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usermood.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,37 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERMOOD_H_ +#define _PURPLE_JABBER_USERMOOD_H_ + +#include "jabber.h" + +/* Implementation of XEP-0107 */ + +void jabber_mood_init(void); + +void jabber_mood_init_action(GList **m); + +void jabber_mood_set(JabberStream *js, + const char *mood, /* must be one of the valid strings defined in the XEP */ + const char *text /* might be NULL */); + +#endif /* _PURPLE_JABBER_USERMOOD_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usernick.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usernick.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,100 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "usernick.h" +#include "pep.h" +#include +#include +#include "internal.h" +#include "request.h" +#include "status.h" + +static void jabber_nick_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *nick; + const char *nickname = NULL; + + /* ignore the tune of people not on our buddy list */ + if (!buddy || !item) + return; + + nick = xmlnode_get_child_with_namespace(item, "nick", "http://jabber.org/protocol/nick"); + if (!nick) + return; + nickname = xmlnode_get_data(nick); + + serv_got_alias(js->gc, from, nickname); +} + +static void do_nick_set(JabberStream *js, const char *nick) { + xmlnode *publish, *nicknode; + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/nick"); + nicknode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "nick"); + xmlnode_set_namespace(nicknode, "http://jabber.org/protocol/nick"); + + if(nick && nick[0] != '\0') + xmlnode_insert_data(nicknode, nick, -1); + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +} + +static void do_nick_got_own_nick_cb(JabberStream *js, const char *from, xmlnode *items) { + const char *oldnickname = NULL; + xmlnode *item = xmlnode_get_child(items,"item"); + + if(item) { + xmlnode *nick = xmlnode_get_child_with_namespace(item,"nick","http://jabber.org/protocol/nick"); + if(nick) + oldnickname = xmlnode_get_data(nick); + } + + purple_request_input(js->gc, _("Set User Nickname"), _("Please specify a new nickname for you."), + _("This information is visible to all contacts on your contact list, so choose something appropriate."), + oldnickname, FALSE, FALSE, NULL, _("Set"), PURPLE_CALLBACK(do_nick_set), _("Cancel"), NULL, + purple_connection_get_account(js->gc), NULL, NULL, js); +} + +static void do_nick_set_nick(PurplePluginAction *action) { + PurpleConnection *gc = (PurpleConnection *) action->context; + JabberStream *js = gc->proto_data; + char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + + /* since the nickname might have been changed by another resource of this account, we always have to request the old one + from the server to present as the default for the new one */ + jabber_pep_request_item(js, jid, "http://jabber.org/protocol/nick", NULL, do_nick_got_own_nick_cb); + g_free(jid); +} + +void jabber_nick_init(void) { + jabber_add_feature("nick", "http://jabber.org/protocol/nick", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("nickn", "http://jabber.org/protocol/nick", jabber_nick_cb); +} + +void jabber_nick_init_action(GList **m) { + PurplePluginAction *act = purple_plugin_action_new(_("Set Nickname..."), do_nick_set_nick); + *m = g_list_append(*m, act); +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usernick.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usernick.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,32 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERNICK_H_ +#define _PURPLE_JABBER_USERNICK_H_ + +#include "jabber.h" + +/* Implementation of XEP-0172 */ + +void jabber_nick_init(void); +void jabber_nick_init_action(GList **m); + +#endif /* _PURPLE_JABBER_USERNICK_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usertune.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usertune.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,122 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "usertune.h" +#include "pep.h" +#include +#include +#include "internal.h" +#include "request.h" +#include "status.h" + +static void jabber_tune_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *tuneinfo, *tune; + PurpleJabberTuneInfo tuneinfodata; + JabberBuddyResource *resource; + const char *status_id; + + /* ignore the tune of people not on our buddy list */ + if (!buddy || !item) + return; + + tuneinfodata.artist = ""; + tuneinfodata.title = ""; + tuneinfodata.album = ""; + tuneinfodata.track = ""; + tuneinfodata.time = -1; + tuneinfodata.url = ""; + + tune = xmlnode_get_child_with_namespace(item, "tune", "http://jabber.org/protocol/tune"); + if (!tune) + return; + for (tuneinfo = tune->child; tuneinfo; tuneinfo = tuneinfo->next) { + if (tuneinfo->type == XMLNODE_TYPE_TAG) { + if (!strcmp(tuneinfo->name, "artist")) { + if (tuneinfodata.artist[0] == '\0') /* only pick the first one */ + tuneinfodata.artist = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "length")) { + if (tuneinfodata.time == -1) { + char *length = xmlnode_get_data(tuneinfo); + if (length) + tuneinfodata.time = strtol(length, NULL, 10); + } + } else if (!strcmp(tuneinfo->name, "source")) { + if (tuneinfodata.album[0] == '\0') /* only pick the first one */ + tuneinfodata.album = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "title")) { + if (tuneinfodata.title[0] == '\0') /* only pick the first one */ + tuneinfodata.title = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "track")) { + if (tuneinfodata.track[0] == '\0') /* only pick the first one */ + tuneinfodata.track = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "uri")) { + if (tuneinfodata.url[0] == '\0') /* only pick the first one */ + tuneinfodata.url = xmlnode_get_data(tuneinfo); + } + } + } + resource = jabber_buddy_find_resource(buddy, NULL); + if(!resource) + return; /* huh? */ + status_id = jabber_buddy_state_get_status_id(resource->state); + + purple_prpl_got_user_status(js->gc->account, from, status_id, PURPLE_TUNE_ARTIST, tuneinfodata.artist, PURPLE_TUNE_TITLE, tuneinfodata.title, PURPLE_TUNE_ALBUM, tuneinfodata.album, PURPLE_TUNE_TRACK, tuneinfodata.track, PURPLE_TUNE_TIME, tuneinfodata.time, PURPLE_TUNE_URL, tuneinfodata.url, NULL); +} + +void jabber_tune_init(void) { + jabber_add_feature("tune", "http://jabber.org/protocol/tune", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("tunen", "http://jabber.org/protocol/tune", jabber_tune_cb); +} + +void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo) { + xmlnode *publish, *tunenode; + JabberStream *js = gc->proto_data; + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/tune"); + tunenode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "tune"); + xmlnode_set_namespace(tunenode, "http://jabber.org/protocol/tune"); + + if(tuneinfo) { + if(tuneinfo->artist && tuneinfo->artist[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "artist"),tuneinfo->artist,-1); + if(tuneinfo->title && tuneinfo->title[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "title"),tuneinfo->title,-1); + if(tuneinfo->album && tuneinfo->album[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "source"),tuneinfo->album,-1); + if(tuneinfo->url && tuneinfo->url[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "uri"),tuneinfo->url,-1); + if(tuneinfo->time >= 0) { + char *length = g_strdup_printf("%d", tuneinfo->time); + xmlnode_insert_data(xmlnode_new_child(tunenode, "length"),length,-1); + g_free(length); + } + if(tuneinfo->track && tuneinfo->track[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "track"),tuneinfo->track,-1); + } + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +} diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/usertune.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usertune.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,43 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERTUNE_H_ +#define _PURPLE_JABBER_USERTUNE_H_ + +#include "jabber.h" + +/* Implementation of XEP-0118 */ + +typedef struct _PurpleJabberTuneInfo PurpleJabberTuneInfo; +struct _PurpleJabberTuneInfo { + char *artist; + char *title; + char *album; + char *track; /* either the index of the track in the album or the URL for a stream */ + int time; /* in seconds, -1 for unknown */ + char *url; +}; + +void jabber_tune_init(void); + +void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo); + +#endif /* _PURPLE_JABBER_USERTUNE_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/xdata.c --- a/libpurple/protocols/jabber/xdata.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.c Wed Sep 05 22:32:14 2007 +0000 @@ -37,22 +37,39 @@ struct jabber_x_data_data { GHashTable *fields; GSList *values; - jabber_x_data_cb cb; + jabber_x_data_action_cb cb; gpointer user_data; JabberStream *js; + GList *actions; + PurpleRequestFieldGroup *actiongroup; }; static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) { xmlnode *result = xmlnode_new("x"); - jabber_x_data_cb cb = data->cb; + jabber_x_data_action_cb cb = data->cb; gpointer user_data = data->user_data; JabberStream *js = data->js; GList *groups, *flds; + char *actionhandle = NULL; + gboolean hasActions = (data->actions != NULL); xmlnode_set_namespace(result, "jabber:x:data"); xmlnode_set_attrib(result, "type", "submit"); for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) { + if(groups->data == data->actiongroup) { + for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) { + PurpleRequestField *field = flds->data; + const char *id = purple_request_field_get_id(field); + int handleindex; + if(strcmp(id, "libpurple:jabber:xdata:actions")) + continue; + handleindex = purple_request_field_choice_get_value(field); + actionhandle = g_strdup(g_list_nth_data(data->actions, handleindex)); + break; + } + continue; + } for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) { xmlnode *fieldnode, *valuenode; PurpleRequestField *field = flds->data; @@ -127,31 +144,59 @@ g_free(data->values->data); data->values = g_slist_delete_link(data->values, data->values); } + if (data->actions) { + GList *action; + for(action = data->actions; action; action = g_list_next(action)) { + g_free(action->data); + } + g_list_free(data->actions); + } g_free(data); - cb(js, result, user_data); + if (hasActions) { + cb(js, result, actionhandle, user_data); + g_free(actionhandle); + } else + ((jabber_x_data_cb)cb)(js, result, user_data); } static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) { xmlnode *result = xmlnode_new("x"); - jabber_x_data_cb cb = data->cb; + jabber_x_data_action_cb cb = data->cb; gpointer user_data = data->user_data; JabberStream *js = data->js; + gboolean hasActions = FALSE; g_hash_table_destroy(data->fields); while(data->values) { g_free(data->values->data); data->values = g_slist_delete_link(data->values, data->values); } + if (data->actions) { + hasActions = TRUE; + GList *action; + for(action = data->actions; action; action = g_list_next(action)) { + g_free(action->data); + } + g_list_free(data->actions); + } g_free(data); xmlnode_set_namespace(result, "jabber:x:data"); xmlnode_set_attrib(result, "type", "cancel"); - cb(js, result, user_data); + if (hasActions) + cb(js, result, NULL, user_data); + else + ((jabber_x_data_cb)cb)(js, result, user_data); } void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data) { + return jabber_x_data_request_with_actions(js, packet, NULL, 0, (jabber_x_data_action_cb)cb, user_data); +} + +void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data) +{ void *handle; xmlnode *fn, *x; PurpleRequestFields *fields; @@ -180,7 +225,7 @@ char *value = NULL; if(!type) - continue; + type = "text-single"; if(!var && strcmp(type, "fixed")) continue; @@ -191,8 +236,6 @@ value = xmlnode_get_data(valuenode); - /* XXX: handle */ - if(!strcmp(type, "text-private")) { if((valuenode = xmlnode_get_child(fn, "value"))) value = xmlnode_get_data(valuenode); @@ -324,6 +367,26 @@ g_free(value); } + + if(field && xmlnode_get_child(fn, "required")) + purple_request_field_set_required(field,TRUE); + } + + if(actions != NULL) { + PurpleRequestField *actionfield; + GList *action; + data->actiongroup = group = purple_request_field_group_new(_("Actions")); + purple_request_fields_add_group(fields, group); + actionfield = purple_request_field_choice_new("libpurple:jabber:xdata:actions", _("Select an action"), defaultaction); + + for(action = actions; action; action = g_list_next(action)) { + JabberXDataAction *a = action->data; + + purple_request_field_choice_add(actionfield, a->name); + data->actions = g_list_append(data->actions, g_strdup(a->handle)); + } + purple_request_field_set_required(actionfield,TRUE); + purple_request_field_group_add_field(group, actionfield); } if((x = xmlnode_get_child(packet, "title"))) diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/jabber/xdata.h --- a/libpurple/protocols/jabber/xdata.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.h Wed Sep 05 22:32:14 2007 +0000 @@ -25,7 +25,14 @@ #include "jabber.h" #include "xmlnode.h" +typedef struct _JabberXDataAction { + char *name; + char *handle; +} JabberXDataAction; + typedef void (*jabber_x_data_cb)(JabberStream *js, xmlnode *result, gpointer user_data); +typedef void (*jabber_x_data_action_cb)(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data); void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data); +void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data); #endif /* _PURPLE_JABBER_XDATA_H_ */ diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/msn/msn.c Wed Sep 05 22:32:14 2007 +0000 @@ -2138,6 +2138,7 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ #ifdef MSN_USE_ATTENTION_API msn_send_attention, /* send_attention */ @@ -2147,7 +2148,6 @@ NULL, NULL, #endif - NULL, NULL }; diff -r e21002d106ab -r 1d2002a5735e libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Wed Sep 05 22:32:14 2007 +0000 @@ -4345,6 +4345,7 @@ &yahoo_whiteboard_prpl_ops, NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ #ifdef YAHOO_USE_ATTENTION_API yahoo_send_attention, @@ -4355,7 +4356,6 @@ #endif /* padding */ - NULL, NULL }; diff -r e21002d106ab -r 1d2002a5735e libpurple/prpl.h --- a/libpurple/prpl.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/prpl.h Wed Sep 05 22:32:14 2007 +0000 @@ -347,12 +347,17 @@ /* room list serialize */ char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room); + /* Remove the user from the server. (This is only at the bottom to keep binary compatibility.) + * The account can either be connected or disconnected. After the removal is finished, + * the connection will stay open and has to be closed! + */ + void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data); + /* Attention API for sending & receiving zaps/nudges/buzzes etc. */ gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type); GList *(*attention_types)(PurpleAccount *acct); - void (*_purple_reserved3)(void); - void (*_purple_reserved4)(void); + void (*_purple_reserved1)(void); }; #define PURPLE_IS_PROTOCOL_PLUGIN(plugin) \ diff -r e21002d106ab -r 1d2002a5735e libpurple/sslconn.c diff -r e21002d106ab -r 1d2002a5735e libpurple/sslconn.h diff -r e21002d106ab -r 1d2002a5735e libpurple/status.h --- a/libpurple/status.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/status.h Wed Sep 05 22:32:14 2007 +0000 @@ -113,6 +113,16 @@ #include "conversation.h" #include "value.h" +#define PURPLE_TUNE_ARTIST "tune_artist" +#define PURPLE_TUNE_TITLE "tune_title" +#define PURPLE_TUNE_ALBUM "tune_album" +#define PURPLE_TUNE_GENRE "tune_genre" +#define PURPLE_TUNE_COMMENT "tune_comment" +#define PURPLE_TUNE_TRACK "tune_track" +#define PURPLE_TUNE_TIME "tune_time" +#define PURPLE_TUNE_YEAR "tune_year" +#define PURPLE_TUNE_URL "tune_url" + #ifdef __cplusplus extern "C" { #endif diff -r e21002d106ab -r 1d2002a5735e libpurple/xmlnode.c --- a/libpurple/xmlnode.c Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/xmlnode.c Wed Sep 05 22:32:14 2007 +0000 @@ -636,6 +636,7 @@ g_return_val_if_fail(src != NULL, NULL); ret = new_node(src->name, src->type); + ret->xmlns = g_strdup(src->xmlns); if(src->data) { if(src->data_sz) { ret->data = g_memdup(src->data, src->data_sz); diff -r e21002d106ab -r 1d2002a5735e libpurple/xmlnode.h --- a/libpurple/xmlnode.h Wed Sep 05 12:56:35 2007 +0000 +++ b/libpurple/xmlnode.h Wed Sep 05 22:32:14 2007 +0000 @@ -262,7 +262,7 @@ xmlnode *xmlnode_copy(const xmlnode *src); /** - * Frees a node and all of it's children. + * Frees a node and all of its children. * * @param node The node to free. */