summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Laager <rlaager@pidgin.im>2007-09-16 18:05:12 +0000
committerRichard Laager <rlaager@pidgin.im>2007-09-16 18:05:12 +0000
commit7b9eb07afd61674aadc02c6a5f4e6574fba4bd28 (patch)
tree43aa057aa806559338fa540b552a41da1f089aa3
parent77554a1cd2a24bf957fb698fcc575acde9004b1d (diff)
parent4197fe0ac8dd7e8a0ed048baa1097e8a5f01cc72 (diff)
downloadpidgin-7b9eb07afd61674aadc02c6a5f4e6574fba4bd28.tar.gz
explicit merge of '221dc581b55597e5a2f7a1c47916bd43aa196e79'
and '69b6246d3f949678ed8791d189fbd400f3fa737c'
-rw-r--r--.mtn-ignore1
-rw-r--r--doc/SIGNAL-HOWTO.dox2
-rw-r--r--libpurple/account.c20
-rw-r--r--libpurple/account.h22
-rw-r--r--libpurple/certificate.c25
-rw-r--r--libpurple/connection.c56
-rw-r--r--libpurple/connection.h12
-rw-r--r--libpurple/conversation.h4
-rw-r--r--libpurple/gaim-compat.h2
-rw-r--r--libpurple/notify.h5
-rw-r--r--libpurple/plugin.h2
-rw-r--r--libpurple/prefs.c10
-rw-r--r--libpurple/protocols/gg/gg.c5
-rw-r--r--libpurple/protocols/jabber/Makefile.am16
-rw-r--r--libpurple/protocols/jabber/Makefile.mingw1
-rw-r--r--libpurple/protocols/jabber/adhoccommands.c307
-rw-r--r--libpurple/protocols/jabber/adhoccommands.h39
-rw-r--r--libpurple/protocols/jabber/buddy.c575
-rw-r--r--libpurple/protocols/jabber/buddy.h18
-rw-r--r--libpurple/protocols/jabber/caps.c530
-rw-r--r--libpurple/protocols/jabber/caps.h49
-rw-r--r--libpurple/protocols/jabber/chat.c4
-rw-r--r--libpurple/protocols/jabber/disco.c105
-rw-r--r--libpurple/protocols/jabber/iq.c9
-rw-r--r--libpurple/protocols/jabber/jabber.c543
-rw-r--r--libpurple/protocols/jabber/jabber.h90
-rw-r--r--libpurple/protocols/jabber/jutil.h2
-rw-r--r--libpurple/protocols/jabber/libxmpp.c99
-rw-r--r--libpurple/protocols/jabber/message.c83
-rw-r--r--libpurple/protocols/jabber/message.h5
-rw-r--r--libpurple/protocols/jabber/parser.c9
-rw-r--r--libpurple/protocols/jabber/parser.h1
-rw-r--r--libpurple/protocols/jabber/pep.c127
-rw-r--r--libpurple/protocols/jabber/pep.h85
-rw-r--r--libpurple/protocols/jabber/ping.c80
-rw-r--r--libpurple/protocols/jabber/ping.h35
-rw-r--r--libpurple/protocols/jabber/presence.c209
-rw-r--r--libpurple/protocols/jabber/presence.h3
-rw-r--r--libpurple/protocols/jabber/usermood.c214
-rw-r--r--libpurple/protocols/jabber/usermood.h37
-rw-r--r--libpurple/protocols/jabber/usernick.c100
-rw-r--r--libpurple/protocols/jabber/usernick.h32
-rw-r--r--libpurple/protocols/jabber/usertune.c122
-rw-r--r--libpurple/protocols/jabber/usertune.h43
-rw-r--r--libpurple/protocols/jabber/xdata.c79
-rw-r--r--libpurple/protocols/jabber/xdata.h7
-rw-r--r--libpurple/protocols/msn/msn.c6
-rw-r--r--libpurple/protocols/myspace/zap.c20
-rw-r--r--libpurple/protocols/oscar/family_locate.c6
-rw-r--r--libpurple/protocols/oscar/oscar.c23
-rw-r--r--libpurple/protocols/oscar/peer.c4
-rw-r--r--libpurple/protocols/yahoo/yahoo.c6
-rw-r--r--libpurple/prpl.h9
-rw-r--r--libpurple/server.c8
-rw-r--r--libpurple/server.h4
-rw-r--r--libpurple/status.h10
-rw-r--r--libpurple/xmlnode.c1
-rw-r--r--libpurple/xmlnode.h2
-rw-r--r--pidgin/gtkblist.h2
-rw-r--r--pidgin/gtkconv.c3
-rw-r--r--pidgin/gtkdialogs.c11
-rw-r--r--pidgin/gtkutils.h4
-rw-r--r--pidgin/pixmaps/logo.pngbin19308 -> 29503 bytes
-rw-r--r--pidgin/plugins/win32/transparency/win2ktrans.c31
-rw-r--r--po/POTFILES.in19
65 files changed, 3717 insertions, 276 deletions
diff --git a/.mtn-ignore b/.mtn-ignore
index bd2099a60e..3f37c2513a 100644
--- a/.mtn-ignore
+++ b/.mtn-ignore
@@ -1,3 +1,4 @@
+(.*/)?\.svn
.*/?Makefile(\.in)?$
(.*/)?TAGS$
.*/?.*\.pc$
diff --git a/doc/SIGNAL-HOWTO.dox b/doc/SIGNAL-HOWTO.dox
index 15fda66ec3..3007415ca2 100644
--- a/doc/SIGNAL-HOWTO.dox
+++ b/doc/SIGNAL-HOWTO.dox
@@ -78,7 +78,7 @@
@todo Describe this more.
- @See value.h
+ @see value.h
@section connect Connecting to the signal
Once the signal is registered, you can connect callbacks to it. First, you
diff --git a/libpurple/account.c b/libpurple/account.c
index 021c4eb11b..75a239ee55 100644
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -913,6 +913,15 @@ purple_account_destroy(PurpleAccount *account)
}
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_account_register(PurpleAccount *account)
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 --git a/libpurple/account.h b/libpurple/account.h
index 7248ef3478..a99ca8aef4 100644
--- a/libpurple/account.h
+++ b/libpurple/account.h
@@ -36,6 +36,8 @@ typedef struct _PurpleAccount PurpleAccount;
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 @@ struct _PurpleAccount
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_destroy(PurpleAccount *account);
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_connect(PurpleAccount *account);
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 --git a/libpurple/certificate.c b/libpurple/certificate.c
index e5fd07ba0f..37b5957865 100644
--- a/libpurple/certificate.c
+++ b/libpurple/certificate.c
@@ -1256,6 +1256,9 @@ x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
}
/* For when we've never communicated with this party before */
+/* TODO: Need ways to specify possibly multiple problems with a cert, or at
+ least reprioritize them. For example, maybe the signature ought to be
+ checked BEFORE the hostname checking? */
static void
x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
{
@@ -1296,7 +1299,27 @@ x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
return;
} /* if (name mismatch) */
-
+ /* TODO: Figure out a way to check for a bad signature, as opposed to
+ "not self-signed" */
+ if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
+ gchar *msg;
+
+ purple_debug_info("certificate/x509/tls_cached",
+ "Certificate for %s is self-signed.\n",
+ vrq->subject_name);
+
+ /* Prompt the user to authenticate the certificate */
+ /* vrq will be completed by user_auth */
+ msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+ "is self-signed. It cannot be "
+ "automatically checked."),
+ vrq->subject_name);
+
+ x509_tls_cached_user_auth(vrq,msg);
+
+ g_free(msg);
+ return;
+ } /* if (name mismatch) */
/* Next, check that the certificate chain is valid */
if ( ! purple_certificate_check_signature_chain(chain) ) {
diff --git a/libpurple/connection.c b/libpurple/connection.c
index d4518cfa00..a518aecb15 100644
--- a/libpurple/connection.c
+++ b/libpurple/connection.c
@@ -158,6 +158,62 @@ purple_connection_new(PurpleAccount *account, gboolean regist, const char *passw
}
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 --git a/libpurple/connection.h b/libpurple/connection.h
index cf0c627e21..4fc5db6c68 100644
--- a/libpurple/connection.h
+++ b/libpurple/connection.h
@@ -171,6 +171,18 @@ void purple_connection_new(PurpleAccount *account, gboolean regist,
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 --git a/libpurple/conversation.h b/libpurple/conversation.h
index 8ab5f979c1..3854e8c38a 100644
--- a/libpurple/conversation.h
+++ b/libpurple/conversation.h
@@ -184,7 +184,7 @@ struct _PurpleConversationUiOps
time_t mtime);
/** Add @a cbuddies to a chat.
- * @param cbuddies A @C GList of #PurpleConvChatBuddy structs.
+ * @param cbuddies A @c GList of #PurpleConvChatBuddy structs.
* @param new_arrivals Whether join notices should be shown.
* (Join notices are actually written to the
* conversation by #purple_conv_chat_add_users().)
@@ -200,7 +200,7 @@ struct _PurpleConversationUiOps
void (*chat_rename_user)(PurpleConversation *conv, const char *old_name,
const char *new_name, const char *new_alias);
/** Remove @a users from a chat.
- * @param users A @C GList of <tt>const char *</tt>s.
+ * @param users A @c GList of <tt>const char *</tt>s.
* @see purple_conv_chat_rename_user()
*/
void (*chat_remove_users)(PurpleConversation *conv, GList *users);
diff --git a/libpurple/gaim-compat.h b/libpurple/gaim-compat.h
index 34eba0ccd5..bdec0d6490 100644
--- a/libpurple/gaim-compat.h
+++ b/libpurple/gaim-compat.h
@@ -1654,7 +1654,7 @@
#define gaim_request_choice_varg purple_request_choice_varg
#define gaim_request_action purple_request_action
#define gaim_request_action_varg purple_request_action_varg
-#define gaim_request_fields purple_request_fields
+#define gaim_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, user_data) purple_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, NULL, NULL, NULL, user_data)
#define gaim_request_close purple_request_close
#define gaim_request_close_with_handle purple_request_close_with_handle
diff --git a/libpurple/notify.h b/libpurple/notify.h
index 398cffbf58..32696e8ef9 100644
--- a/libpurple/notify.h
+++ b/libpurple/notify.h
@@ -213,6 +213,11 @@ void *purple_notify_searchresults(PurpleConnection *gc, const char *title,
PurpleNotifySearchResults *results, PurpleNotifyCloseCallback cb,
gpointer user_data);
+/**
+ * Frees a PurpleNotifySearchResults object.
+ *
+ * @param results The PurpleNotifySearchResults to free.
+ */
void purple_notify_searchresults_free(PurpleNotifySearchResults *results);
/**
diff --git a/libpurple/plugin.h b/libpurple/plugin.h
index a083656b0c..b6c3b67c82 100644
--- a/libpurple/plugin.h
+++ b/libpurple/plugin.h
@@ -188,6 +188,8 @@ struct _PurplePluginAction {
/** 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 --git a/libpurple/prefs.c b/libpurple/prefs.c
index 552b7e1de7..35321f60ab 100644
--- a/libpurple/prefs.c
+++ b/libpurple/prefs.c
@@ -329,9 +329,13 @@ prefs_start_element_handler (GMarkupParseContext *context,
purple_prefs_set_string_list(pref_name_full->str, NULL);
break;
case PURPLE_PREF_PATH:
- decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
- purple_prefs_set_path(pref_name_full->str, decoded);
- g_free(decoded);
+ if (pref_value) {
+ decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
+ purple_prefs_set_path(pref_name_full->str, decoded);
+ g_free(decoded);
+ } else {
+ purple_prefs_set_path(pref_name_full->str, NULL);
+ }
break;
case PURPLE_PREF_PATH_LIST:
purple_prefs_set_path_list(pref_name_full->str, NULL);
diff --git a/libpurple/protocols/gg/gg.c b/libpurple/protocols/gg/gg.c
index 8cbda9b9d0..5100588531 100644
--- a/libpurple/protocols/gg/gg.c
+++ b/libpurple/protocols/gg/gg.c
@@ -411,6 +411,8 @@ static void ggp_callback_register_account_ok(PurpleConnection *gc,
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 @@ static void ggp_callback_register_account_ok(PurpleConnection *gc,
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 --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am
index b11f77d87a..a4ac0463b6 100644
--- a/libpurple/protocols/jabber/Makefile.am
+++ b/libpurple/protocols/jabber/Makefile.am
@@ -27,6 +27,8 @@ JABBERSOURCES = auth.c \
oob.h \
parser.c \
parser.h \
+ ping.c \
+ ping.h \
presence.c \
presence.h \
roster.c \
@@ -34,7 +36,19 @@ JABBERSOURCES = auth.c \
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 --git a/libpurple/protocols/jabber/Makefile.mingw b/libpurple/protocols/jabber/Makefile.mingw
index a02f95b55c..40709aae7a 100644
--- a/libpurple/protocols/jabber/Makefile.mingw
+++ b/libpurple/protocols/jabber/Makefile.mingw
@@ -53,6 +53,7 @@ C_SRC = auth.c \
message.c \
oob.c \
parser.c \
+ ping.c \
presence.c \
roster.c \
si.c \
diff --git a/libpurple/protocols/jabber/adhoccommands.c b/libpurple/protocols/jabber/adhoccommands.c
new file mode 100644
index 0000000000..dc96d69e9f
--- /dev/null
+++ b/libpurple/protocols/jabber/adhoccommands.c
@@ -0,0 +1,307 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <assert.h>
+#include <string.h>
+#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 --git a/libpurple/protocols/jabber/adhoccommands.h b/libpurple/protocols/jabber/adhoccommands.h
new file mode 100644
index 0000000000..46559ee249
--- /dev/null
+++ b/libpurple/protocols/jabber/adhoccommands.h
@@ -0,0 +1,39 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/buddy.c b/libpurple/protocols/jabber/buddy.c
index f8f31eb334..dc8ac592a3 100644
--- a/libpurple/protocols/jabber/buddy.c
+++ b/libpurple/protocols/jabber/buddy.c
@@ -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 @@ JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *re
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 @@ JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *re
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 @@ void jabber_buddy_resource_free(JabberBuddyResource *jbr)
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 @@ void jabber_set_info(PurpleConnection *gc, const char *info)
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 @@ void jabber_set_info(PurpleConnection *gc, const char *info)
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_info(PurpleConnection *gc, const char *info)
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 @@ static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
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 @@ static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
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 @@ static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
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 @@ static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
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 @@ static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
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 @@ static GList *jabber_buddy_menu(PurpleBuddy *buddy)
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 @@ static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fi
* 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 @@ static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpoi
_("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 @@ void jabber_user_search_begin(PurplePluginAction *action)
_("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 --git a/libpurple/protocols/jabber/buddy.h b/libpurple/protocols/jabber/buddy.h
index 355f3ab5b3..4fa1f41511 100644
--- a/libpurple/protocols/jabber/buddy.h
+++ b/libpurple/protocols/jabber/buddy.h
@@ -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 @@ typedef enum {
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 @@ typedef struct _JabberBuddy {
} subscription;
} JabberBuddy;
+typedef struct _JabberAdHocCommands {
+ char *jid;
+ char *node;
+ char *name;
+} JabberAdHocCommands;
+
typedef struct _JabberBuddyResource {
JabberBuddy *jb;
char *name;
@@ -71,6 +81,8 @@ typedef struct _JabberBuddyResource {
char *name;
char *os;
} client;
+ JabberCapsClientInfo *caps;
+ GList *commands;
} JabberBuddyResource;
void jabber_buddy_free(JabberBuddy *jb);
@@ -92,6 +104,7 @@ GList *jabber_blist_node_menu(PurpleBlistNode *node);
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 @@ const char *jabber_buddy_state_get_show(JabberBuddyState state);
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 --git a/libpurple/protocols/jabber/caps.c b/libpurple/protocols/jabber/caps.c
new file mode 100644
index 0000000000..f1dd619e2c
--- /dev/null
+++ b/libpurple/protocols/jabber/caps.c
@@ -0,0 +1,530 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <string.h>
+#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 --git a/libpurple/protocols/jabber/caps.h b/libpurple/protocols/jabber/caps.h
new file mode 100644
index 0000000000..07cf0baaaf
--- /dev/null
+++ b/libpurple/protocols/jabber/caps.h
@@ -0,0 +1,49 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/chat.c b/libpurple/protocols/jabber/chat.c
index f6cc109ee6..0b1a20b469 100644
--- a/libpurple/protocols/jabber/chat.c
+++ b/libpurple/protocols/jabber/chat.c
@@ -261,7 +261,7 @@ void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
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 @@ void jabber_chat_change_nick(JabberChat *chat, const char *nick)
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 --git a/libpurple/protocols/jabber/disco.c b/libpurple/protocols/jabber/disco.c
index 3fa4773ab9..525bb0b8ad 100644
--- a/libpurple/protocols/jabber/disco.c
+++ b/libpurple/protocols/jabber/disco.c
@@ -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 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
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 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
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#<ext> 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 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
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 @@ void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) {
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_disco_finish_server_info_result_cb(JabberStream *js)
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 @@ jabber_disco_server_info_result_cb(JabberStream *js, xmlnode *packet, gpointer d
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 @@ jabber_disco_server_info_result_cb(JabberStream *js, xmlnode *packet, gpointer d
} 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 --git a/libpurple/protocols/jabber/iq.c b/libpurple/protocols/jabber/iq.c
index a4333b92ff..0136203fca 100644
--- a/libpurple/protocols/jabber/iq.c
+++ b/libpurple/protocols/jabber/iq.c
@@ -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 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet)
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 --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index d143baad3f..a37c632103 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -51,12 +51,20 @@
#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 <assert.h>
+
+#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)
{
@@ -80,6 +88,8 @@ jabber_session_initialized_cb(JabberStream *js, xmlnode *packet, gpointer data)
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 @@ static void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
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 jabber_stream_handle_error(JabberStream *js, xmlnode *packet)
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);
-
- 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") &&
+ 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") &&
!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);
}
}
@@ -453,6 +466,9 @@ jabber_login_callback_ssl(gpointer data, PurpleSslConnection *gsc,
jabber_send_raw(js, "<?xml version='1.0' ?>", -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 @@ jabber_login(PurpleAccount *account)
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"));
@@ -614,6 +631,8 @@ conn_close_cb(gpointer data)
JabberStream *js = data;
PurpleAccount *account = purple_connection_get_account(js->gc);
+ jabber_parser_free(js);
+
purple_account_disconnect(account);
return FALSE;
@@ -628,12 +647,21 @@ jabber_connection_schedule_close(JabberStream *js)
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")) {
+ 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 @@ jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
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);
}
+ 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,6 +731,24 @@ jabber_register_cb(JabberStream *js, PurpleRequestFields *fields)
flds; flds = flds->next) {
PurpleRequestField *field = flds->data;
const char *id = purple_request_field_get_id(field);
+ 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 {
const char *value = purple_request_field_string_get_value(field);
if(!strcmp(id, "username")) {
@@ -701,73 +783,97 @@ jabber_register_cb(JabberStream *js, PurpleRequestFields *fields)
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);
+ 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);
+ 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;
- if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
- return;
-
- if(js->registration) {
+ 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)
/* get rid of the login thingy */
purple_connection_set_state(js->gc, PURPLE_CONNECTED);
query = xmlnode_get_child(packet, "query");
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);
+ 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"))) {
@@ -778,8 +884,12 @@ void jabber_register_parse(JabberStream *js, xmlnode *packet)
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;
}
}
@@ -791,18 +901,32 @@ void jabber_register_parse(JabberStream *js, xmlnode *packet)
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);
+ 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);
+ else
+ field = purple_request_field_string_new("password", _("Password"),
+ NULL, FALSE);
+
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);
+ else
+ field = purple_request_field_string_new("name", _("Name"),
+ NULL, FALSE);
purple_request_field_group_add_field(group, field);
}
if(xmlnode_get_child(query, "email")) {
@@ -860,23 +984,45 @@ void jabber_register_parse(JabberStream *js, xmlnode *packet)
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 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);
}
-}
void jabber_register_start(JabberStream *js)
{
@@ -886,6 +1032,14 @@ void jabber_register_start(JabberStream *js)
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 @@ void jabber_register_account(PurpleAccount *account)
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"));
@@ -957,6 +1112,65 @@ void jabber_register_account(PurpleAccount *account)
}
}
+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 @@ void jabber_close(PurpleConnection *gc)
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 @@ void jabber_close(PurpleConnection *gc)
#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 @@ void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
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 @@ void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
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 @@ void jabber_idle_set(PurpleConnection *gc, int idle)
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 @@ void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboole
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 @@ void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboole
}
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 @@ GList *jabber_status_types(PurpleAccount *account)
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 @@ GList *jabber_status_types(PurpleAccount *account)
_("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 @@ GList *jabber_status_types(PurpleAccount *account)
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 @@ GList *jabber_status_types(PurpleAccount *account)
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 @@ GList *jabber_status_types(PurpleAccount *account)
_("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,6 +1737,8 @@ static void jabber_password_change(PurplePluginAction *action)
GList *jabber_actions(PurplePlugin *plugin, gpointer context)
{
+ PurpleConnection *gc = (PurpleConnection *) context;
+ JabberStream *js = gc->proto_data;
GList *m = NULL;
PurplePluginAction *act;
@@ -1396,6 +1756,14 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context)
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;
}
@@ -1800,6 +2168,71 @@ static PurpleCmdRet jabber_cmd_chat_msg(PurpleConversation *conv,
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;
@@ -1877,6 +2310,16 @@ void jabber_register_commands(void)
"prpl-jabber", jabber_cmd_chat_msg,
_("msg &lt;user&gt; &lt;message&gt;: 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 &lt;jid&gt;: 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
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index 58deb8fc5b..6817337d76 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -22,23 +22,6 @@
#ifndef _PURPLE_JABBER_H_
#define _PURPLE_JABBER_H_
-#include <libxml/parser.h>
-#include <glib.h>
-#include "circbuffer.h"
-#include "connection.h"
-#include "dnssrv.h"
-#include "roomlist.h"
-#include "sslconn.h"
-
-#include "jutil.h"
-#include "xmlnode.h"
-
-#ifdef HAVE_CYRUS_SASL
-#include <sasl/sasl.h>
-#endif
-
-#define CAPS0115_NODE "http://pidgin.im/caps"
-
typedef enum {
JABBER_CAP_NONE = 0,
JABBER_CAP_XHTML = 1 << 0,
@@ -57,19 +40,43 @@ typedef enum {
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 <libxml/parser.h>
+#include <glib.h>
+#include "circbuffer.h"
+#include "connection.h"
+#include "dnssrv.h"
+#include "roomlist.h"
+#include "sslconn.h"
+
+#include "jutil.h"
+#include "xmlnode.h"
+#include "buddy.h"
+
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
+#define CAPS0115_NODE "http://pidgin.im/caps"
+
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;
@@ -153,11 +160,49 @@ typedef struct _JabberStream
int sasl_maxbuf;
GString *sasl_mechs;
+ gboolean unregistration;
+ PurpleAccountUnregistrationCb unregistration_cb;
+ void *unregistration_user_data;
+
gboolean vcard_fetched;
-} JabberStream;
+ /* 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;
+};
+
+typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
+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_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_get_next_id(JabberStream *js);
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_login(PurpleAccount *account);
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 --git a/libpurple/protocols/jabber/jutil.h b/libpurple/protocols/jabber/jutil.h
index e92a3bfc52..12ff64eab5 100644
--- a/libpurple/protocols/jabber/jutil.h
+++ b/libpurple/protocols/jabber/jutil.h
@@ -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 --git a/libpurple/protocols/jabber/libxmpp.c b/libpurple/protocols/jabber/libxmpp.c
index 07da2595b5..9bc17ceafe 100644
--- a/libpurple/protocols/jabber/libxmpp.c
+++ b/libpurple/protocols/jabber/libxmpp.c
@@ -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 @@ static PurplePluginProtocolInfo prpl_info =
#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 @@ static PurplePluginProtocolInfo prpl_info =
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 PurplePluginInfo info =
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);
-
- 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 ... */
+ 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);
+ sasl_client_init(NULL);
#endif
- jabber_register_commands();
+ jabber_register_commands();
+
+ jabber_iq_init();
+ jabber_pep_init();
+
+ jabber_tune_init();
+ jabber_caps_init();
- 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 --git a/libpurple/protocols/jabber/message.c b/libpurple/protocols/jabber/message.c
index ece86e0660..b021ae9487 100644
--- a/libpurple/protocols/jabber/message.c
+++ b/libpurple/protocols/jabber/message.c
@@ -30,6 +30,7 @@
#include "google.h"
#include "message.h"
#include "xmlnode.h"
+#include "pep.h"
void jabber_message_free(JabberMessage *jm)
{
@@ -147,9 +148,13 @@ static void handle_chat(JabberMessage *jm)
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 @@ static void handle_error(JabberMessage *jm)
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,22 +343,25 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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);
@@ -335,21 +373,28 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
*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 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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)
@@ -411,6 +460,9 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
}
}
+ if(jm->hasBuzz)
+ handle_buzz(jm);
+
switch(jm->type) {
case JABBER_MESSAGE_NORMAL:
case JABBER_MESSAGE_CHAT:
@@ -425,6 +477,9 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_message_send(JabberMessage *jm)
type = "error";
break;
case JABBER_MESSAGE_OTHER:
+ default:
type = NULL;
break;
}
@@ -689,3 +745,8 @@ void jabber_message_conv_closed(JabberStream *js, const char *who)
jabber_message_send(jm);
jabber_message_free(jm);
}
+
+gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace) {
+ return js->allowBuzz;
+}
+
diff --git a/libpurple/protocols/jabber/message.h b/libpurple/protocols/jabber/message.h
index 2d0ba37a7f..56c89819b1 100644
--- a/libpurple/protocols/jabber/message.h
+++ b/libpurple/protocols/jabber/message.h
@@ -35,10 +35,12 @@ typedef struct _JabberMessage {
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 @@ typedef struct _JabberMessage {
JM_STATE_GONE
} chat_state;
GList *etc;
+ GList *eventitems;
} JabberMessage;
void jabber_message_free(JabberMessage *jm);
@@ -75,4 +78,6 @@ int jabber_message_send_chat(PurpleConnection *gc, int id, const char *message,
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 --git a/libpurple/protocols/jabber/parser.c b/libpurple/protocols/jabber/parser.c
index 82933df1f4..bd28f0f0d4 100644
--- a/libpurple/protocols/jabber/parser.c
+++ b/libpurple/protocols/jabber/parser.c
@@ -63,7 +63,7 @@ jabber_parser_element_start_libxml(void *user_data,
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 @@ jabber_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
} 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 @@ jabber_parser_setup(JabberStream *js)
* 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 @@ jabber_parser_setup(JabberStream *js)
}
}
-
void jabber_parser_process(JabberStream *js, const char *buf, int len)
{
if (js->context == NULL) {
diff --git a/libpurple/protocols/jabber/parser.h b/libpurple/protocols/jabber/parser.h
index 729b3a5997..cfc30e55eb 100644
--- a/libpurple/protocols/jabber/parser.h
+++ b/libpurple/protocols/jabber/parser.h
@@ -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 --git a/libpurple/protocols/jabber/pep.c b/libpurple/protocols/jabber/pep.c
new file mode 100644
index 0000000000..e82c259c1e
--- /dev/null
+++ b/libpurple/protocols/jabber/pep.c
@@ -0,0 +1,127 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <string.h>
+#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 --git a/libpurple/protocols/jabber/pep.h b/libpurple/protocols/jabber/pep.h
new file mode 100644
index 0000000000..dd43f80c6a
--- /dev/null
+++ b/libpurple/protocols/jabber/pep.h
@@ -0,0 +1,85 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 &lt;items/>-tag with the &lt;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 &lt;publish node='http://jabber.org/protocol/tune'/> with an &lt;item/> as subnode
+ */
+void jabber_pep_publish(JabberStream *js, xmlnode *publish);
+
+#endif /* _PURPLE_JABBER_PEP_H_ */
diff --git a/libpurple/protocols/jabber/ping.c b/libpurple/protocols/jabber/ping.c
new file mode 100644
index 0000000000..4339c27dd1
--- /dev/null
+++ b/libpurple/protocols/jabber/ping.c
@@ -0,0 +1,80 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/ping.h b/libpurple/protocols/jabber/ping.h
new file mode 100644
index 0000000000..377452f56c
--- /dev/null
+++ b/libpurple/protocols/jabber/ping.h
@@ -0,0 +1,35 @@
+/**
+ * @file ping.h utility functions
+ *
+ * purple
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/presence.c b/libpurple/protocols/jabber/presence.c
index d57039fcbe..aed27991cb 100644
--- a/libpurple/protocols/jabber/presence.c
+++ b/libpurple/protocols/jabber/presence.c
@@ -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 @@ void jabber_presence_send(PurpleAccount *account, PurpleStatus *status)
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 @@ void jabber_presence_send(PurpleAccount *account, PurpleStatus *status)
}
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 <presence> 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);
+
+ 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);
+ }
-
- 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);
+
+ 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 *jabber_presence_create(JabberBuddyState state, const char *msg, int pri
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 @@ static void jabber_vcard_parse_avatar(JabberStream *js, xmlnode *packet, gpointe
}
}
+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 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
} 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 --git a/libpurple/protocols/jabber/presence.h b/libpurple/protocols/jabber/presence.h
index da96b7c539..961c56ddc7 100644
--- a/libpurple/protocols/jabber/presence.h
+++ b/libpurple/protocols/jabber/presence.h
@@ -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 --git a/libpurple/protocols/jabber/usermood.c b/libpurple/protocols/jabber/usermood.c
new file mode 100644
index 0000000000..30f7e09c1e
--- /dev/null
+++ b/libpurple/protocols/jabber/usermood.c
@@ -0,0 +1,214 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <assert.h>
+#include <string.h>
+#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 --git a/libpurple/protocols/jabber/usermood.h b/libpurple/protocols/jabber/usermood.h
new file mode 100644
index 0000000000..425c8065ca
--- /dev/null
+++ b/libpurple/protocols/jabber/usermood.h
@@ -0,0 +1,37 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/usernick.c b/libpurple/protocols/jabber/usernick.c
new file mode 100644
index 0000000000..fa8d9bbf57
--- /dev/null
+++ b/libpurple/protocols/jabber/usernick.c
@@ -0,0 +1,100 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <assert.h>
+#include <string.h>
+#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 --git a/libpurple/protocols/jabber/usernick.h b/libpurple/protocols/jabber/usernick.h
new file mode 100644
index 0000000000..60a7e03026
--- /dev/null
+++ b/libpurple/protocols/jabber/usernick.h
@@ -0,0 +1,32 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/usertune.c b/libpurple/protocols/jabber/usertune.c
new file mode 100644
index 0000000000..03145e1f60
--- /dev/null
+++ b/libpurple/protocols/jabber/usertune.c
@@ -0,0 +1,122 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 <assert.h>
+#include <string.h>
+#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 --git a/libpurple/protocols/jabber/usertune.h b/libpurple/protocols/jabber/usertune.h
new file mode 100644
index 0000000000..9457b29480
--- /dev/null
+++ b/libpurple/protocols/jabber/usertune.h
@@ -0,0 +1,43 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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 --git a/libpurple/protocols/jabber/xdata.c b/libpurple/protocols/jabber/xdata.c
index f9adfdadfb..9ffa071eff 100644
--- a/libpurple/protocols/jabber/xdata.c
+++ b/libpurple/protocols/jabber/xdata.c
@@ -37,22 +37,39 @@ typedef enum {
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 @@ static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFi
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 @@ void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb
char *value = NULL;
if(!type)
- continue;
+ type = "text-single";
if(!var && strcmp(type, "fixed"))
continue;
@@ -191,8 +236,6 @@ void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb
value = xmlnode_get_data(valuenode);
- /* XXX: handle <required/> */
-
if(!strcmp(type, "text-private")) {
if((valuenode = xmlnode_get_child(fn, "value")))
value = xmlnode_get_data(valuenode);
@@ -324,6 +367,26 @@ void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb
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 --git a/libpurple/protocols/jabber/xdata.h b/libpurple/protocols/jabber/xdata.h
index 023134634e..e99dcaf482 100644
--- a/libpurple/protocols/jabber/xdata.h
+++ b/libpurple/protocols/jabber/xdata.h
@@ -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 --git a/libpurple/protocols/msn/msn.c b/libpurple/protocols/msn/msn.c
index ddba43a397..b2377a8653 100644
--- a/libpurple/protocols/msn/msn.c
+++ b/libpurple/protocols/msn/msn.c
@@ -129,8 +129,8 @@ msn_attention_types(PurpleAccount *account)
if (!list) {
attn = g_new0(PurpleAttentionType, 1);
attn->name = _("nudge");
- attn->incoming_description = _("nudged");
- attn->outgoing_description = _("Nudging");
+ attn->incoming_description = _("%s has nudged you!");
+ attn->outgoing_description = _("Nudging %s...");
list = g_list_append(list, attn);
}
@@ -2257,6 +2257,7 @@ static PurplePluginProtocolInfo prpl_info =
NULL, /* whiteboard_prpl_ops */
NULL, /* send_raw */
NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
#ifdef MSN_USE_ATTENTION_API
msn_send_attention, /* send_attention */
@@ -2266,7 +2267,6 @@ static PurplePluginProtocolInfo prpl_info =
NULL,
NULL,
#endif
- NULL,
NULL
};
diff --git a/libpurple/protocols/myspace/zap.c b/libpurple/protocols/myspace/zap.c
index 9960422245..29eb4ebfbb 100644
--- a/libpurple/protocols/myspace/zap.c
+++ b/libpurple/protocols/myspace/zap.c
@@ -41,16 +41,16 @@ msim_attention_types(PurpleAccount *acct)
types = g_list_append(types, attn);
/* TODO: icons for each zap */
- _MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking"));
- _MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing"));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Zap"), _("%s has zapped you!"), _("Zapping %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Whack"), _("%s has whacked you!"), _("Whacking %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Torch"), _("%s has torched you!"), _("Torching %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Smooch"), _("%s has smooched you!"), _("Smooching %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Hug"), _("%s has hugged you!"), _("Hugging %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Slap"), _("%s has slapped you!"), _("Slapping %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Goose"), _("%s has goosed you!"), _("Goosing %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("High-five"), _("%s has high-fived you!"), _("High-fiving %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Punk"), _("%s has punk'd you!"), _("Punking %s..."));
+ _MSIM_ADD_NEW_ATTENTION(NULL, _("Raspberry"), _("%s has raspberried you!"), _("Raspberrying %s..."));
}
return types;
diff --git a/libpurple/protocols/oscar/family_locate.c b/libpurple/protocols/oscar/family_locate.c
index 1c99af79fb..0ac23f4746 100644
--- a/libpurple/protocols/oscar/family_locate.c
+++ b/libpurple/protocols/oscar/family_locate.c
@@ -636,13 +636,11 @@ aim_info_extract(OscarData *od, ByteStream *bs, aim_userinfo_t *outinfo)
* Parse out the Type-Length-Value triples as they're found.
*/
for (curtlv = 0; curtlv < tlvcnt; curtlv++) {
- guint16 type;
- guint8 number, length;
+ guint16 type, length;
int endpos;
type = byte_stream_get16(bs);
- number = byte_stream_get8(bs);
- length = byte_stream_get8(bs);
+ length = byte_stream_get16(bs);
endpos = byte_stream_curpos(bs) + MIN(length, byte_stream_empty(bs));
diff --git a/libpurple/protocols/oscar/oscar.c b/libpurple/protocols/oscar/oscar.c
index 18a7b0de40..ea6527365e 100644
--- a/libpurple/protocols/oscar/oscar.c
+++ b/libpurple/protocols/oscar/oscar.c
@@ -2125,28 +2125,11 @@ incomingim_chan2(OscarData *od, FlapConnection *conn, aim_userinfo_t *userinfo,
}
else if (args->status == AIM_RENDEZVOUS_CONNECTED)
{
- /* Remote user has accepted our peer request */
- PeerConnection *conn;
-
- conn = peer_connection_find_by_cookie(od, userinfo->sn, args->cookie);
/*
- * If conn is NULL it means we haven't tried to create
- * a connection with that user. They may be trying to
- * do something malicious.
+ * Remote user has accepted our peer request. If we
+ * wanted to we could look up the PeerConnection using
+ * args->cookie, but we don't need to do anything here.
*/
- if (conn != NULL)
- {
- if (conn->listenerfd != -1)
- {
- /*
- * If they are connecting directly to us then
- * continue the peer negotiation by
- * accepting connections on our listener port.
- */
- conn->watcher_incoming = purple_input_add(conn->listenerfd,
- PURPLE_INPUT_READ, peer_connection_listen_cb, conn);
- }
- }
}
}
diff --git a/libpurple/protocols/oscar/peer.c b/libpurple/protocols/oscar/peer.c
index 393916fdd1..25198c5328 100644
--- a/libpurple/protocols/oscar/peer.c
+++ b/libpurple/protocols/oscar/peer.c
@@ -671,6 +671,10 @@ peer_connection_establish_listener_cb(int listenerfd, gpointer data)
account = purple_connection_get_account(gc);
conn->listenerfd = listenerfd;
+ /* Watch for new connections on our listener socket */
+ conn->watcher_incoming = purple_input_add(conn->listenerfd,
+ PURPLE_INPUT_READ, peer_connection_listen_cb, conn);
+
/* Send the "please connect to me!" ICBM */
bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM);
if (bos_conn == NULL)
diff --git a/libpurple/protocols/yahoo/yahoo.c b/libpurple/protocols/yahoo/yahoo.c
index 2f2d382ab3..4b1f410041 100644
--- a/libpurple/protocols/yahoo/yahoo.c
+++ b/libpurple/protocols/yahoo/yahoo.c
@@ -4130,8 +4130,8 @@ GList *yahoo_attention_types(PurpleAccount *account)
/* This is index number YAHOO_BUZZ. */
attn = g_new0(PurpleAttentionType, 1);
attn->name = _("buzz");
- attn->incoming_description = _("buzzed");
- attn->outgoing_description = _("Buzzing");
+ attn->incoming_description = _("%s has buzzed you!");
+ attn->outgoing_description = _("Buzzing %s...");
list = g_list_append(list, attn);
}
@@ -4345,6 +4345,7 @@ static PurplePluginProtocolInfo prpl_info =
&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 @@ static PurplePluginProtocolInfo prpl_info =
#endif
/* padding */
- NULL,
NULL
};
diff --git a/libpurple/prpl.h b/libpurple/prpl.h
index a5654ff281..0310e38656 100644
--- a/libpurple/prpl.h
+++ b/libpurple/prpl.h
@@ -347,11 +347,16 @@ struct _PurplePluginProtocolInfo
/* 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);
+ GList *(*get_attention_types)(PurpleAccount *acct);
- void (*_purple_reserved3)(void);
void (*_purple_reserved4)(void);
};
diff --git a/libpurple/server.c b/libpurple/server.c
index a6e8e6b99d..8784ead789 100644
--- a/libpurple/server.c
+++ b/libpurple/server.c
@@ -290,9 +290,9 @@ serv_send_attention(PurpleConnection *gc, const char *who, guint type_code)
attn = purple_get_attention_type_from_code(gc->account, type_code);
if (attn && attn->outgoing_description) {
- description = g_strdup_printf(_("Attention! %s %s."), attn->outgoing_description, who);
+ description = g_strdup_printf(attn->outgoing_description, who);
} else {
- description = g_strdup(_("Attention!"));
+ description = g_strdup_printf(_("Requesting %s's attention..."), who);
}
flags = PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM;
@@ -328,9 +328,9 @@ serv_got_attention(PurpleConnection *gc, const char *who, guint type_code)
* it next to the attention command. And if it is null, display a generic icon. */
if (attn && attn->incoming_description) {
- description = g_strdup_printf(_("Attention! You have been %s."), attn->incoming_description);
+ description = g_strdup_printf(attn->incoming_description, who);
} else {
- description = g_strdup(_("Attention!"));
+ description = g_strdup(_("%s has requested your attention!"));
}
purple_debug_info("server", "serv_got_attention: got '%s' from %s\n",
diff --git a/libpurple/server.h b/libpurple/server.h
index 6f3d36e4fe..713690b241 100644
--- a/libpurple/server.h
+++ b/libpurple/server.h
@@ -64,7 +64,7 @@ PurpleAttentionType *purple_get_attention_type_from_code(PurpleAccount *account,
*
* @param gc The connection to send the message on.
* @param who Whose attention to request.
- * @param type An index into the prpl's attention_types list determining the type
+ * @param type_code An index into the prpl's attention_types list determining the type
* of the attention request command to send. 0 if prpl only defines one
* (for example, Yahoo and MSN), but some protocols define more (MySpaceIM).
*
@@ -77,7 +77,7 @@ void serv_send_attention(PurpleConnection *gc, const char *who, guint type_code)
*
* @param gc The connection that received the attention message.
* @param who Who requested your attention.
- * @param type An index into the prpl's attention_types list determining the type
+ * @param type_code An index into the prpl's attention_types list determining the type
* of the attention request command to send.
*/
void serv_got_attention(PurpleConnection *gc, const char *who, guint type_code);
diff --git a/libpurple/status.h b/libpurple/status.h
index 3164663138..13203a87e5 100644
--- a/libpurple/status.h
+++ b/libpurple/status.h
@@ -113,6 +113,16 @@ typedef enum
#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 --git a/libpurple/xmlnode.c b/libpurple/xmlnode.c
index 92b6183eb2..df4f14b504 100644
--- a/libpurple/xmlnode.c
+++ b/libpurple/xmlnode.c
@@ -636,6 +636,7 @@ xmlnode_copy(const xmlnode *src)
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 --git a/libpurple/xmlnode.h b/libpurple/xmlnode.h
index a822858d2f..856530bf11 100644
--- a/libpurple/xmlnode.h
+++ b/libpurple/xmlnode.h
@@ -262,7 +262,7 @@ xmlnode *xmlnode_from_str(const char *str, gssize size);
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.
*/
diff --git a/pidgin/gtkblist.h b/pidgin/gtkblist.h
index 0a4aba9720..695be1a964 100644
--- a/pidgin/gtkblist.h
+++ b/pidgin/gtkblist.h
@@ -378,7 +378,7 @@ gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gbool
* This tooltip will be destroyed the next time this function is called, or when XXXX
* is called
*
- * @param buddy The buddy to show a tooltip for
+ * @param node The buddy list node to show a tooltip for
* @param widget The widget to draw the tooltip on
*/
void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget);
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
index ae9317eecd..7f28051221 100644
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -9026,6 +9026,9 @@ pidgin_conv_window_remove_gtkconv(PidginWindow *win, PidginConversation *gtkconv
win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv);
+ g_signal_handlers_disconnect_matched(win->window, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, gtkconv);
+
if (win->gtkconvs && win->gtkconvs->next == NULL)
pidgin_conv_tab_pack(win, win->gtkconvs->data);
diff --git a/pidgin/gtkdialogs.c b/pidgin/gtkdialogs.c
index bdc1a703a2..868780f573 100644
--- a/pidgin/gtkdialogs.c
+++ b/pidgin/gtkdialogs.c
@@ -95,8 +95,7 @@ static struct developer developers[] = {
/* Order: Alphabetical by Last Name */
static struct developer patch_writers[] = {
{"John 'rekkanoryo' Bailey", NULL, NULL},
- {"Peter 'Bleeter' Lawler", NULL, NULL},
- {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL},
+ {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL},
{"Peter 'Fmoo' Ruibal", NULL, NULL},
{"Gabriel 'Nix' Schulhof", NULL, NULL},
{"Will 'resiak' Thompson", NULL, NULL},
@@ -122,6 +121,7 @@ static struct developer retired_developers[] = {
static struct developer retired_patch_writers[] = {
{"Felipe 'shx' Contreras", NULL, NULL},
{"Decklin Foster", NULL, NULL},
+ {"Peter 'Bleeter' Lawler", NULL, NULL},
{"Robert 'Robot101' McQueen", NULL, NULL},
{"Benjamin Miller", NULL, NULL},
{NULL, NULL, NULL}
@@ -305,14 +305,14 @@ pidgin_logo_versionize(GdkPixbuf **original, GtkWidget *widget) {
context = gtk_widget_get_pango_context(widget);
layout = pango_layout_new(context);
- markup = g_strdup_printf("<span foreground=\"#FFFFFF\">%s</span>", VERSION);
+ markup = g_strdup_printf("<span foreground=\"#000000\">%s</span>", VERSION);
pango_layout_set_font_description(layout, style->font_desc);
pango_layout_set_markup(layout, markup, strlen(markup));
g_free(markup);
pango_layout_get_pixel_size(layout, &lwidth, &lheight);
gdk_draw_layout(GDK_DRAWABLE(pixmap), style->bg_gc[GTK_STATE_NORMAL],
- width - (lwidth + 3), height - (lheight + 1), layout);
+ width - (lwidth + 3), 1, layout);
g_object_unref(G_OBJECT(layout));
*original = gdk_pixbuf_get_from_drawable(NULL, pixmap, NULL,
@@ -396,6 +396,9 @@ void pidgin_dialogs_about()
g_string_append(str, "<FONT SIZE=\"4\">URL:</FONT> <A HREF=\""
PURPLE_WEBSITE "\">" PURPLE_WEBSITE "</A><BR/><BR/>");
+ g_string_append(str, "<FONT SIZE=\"4\">FAQ:</FONT> <A HREF=\""
+ "http://developer.pidgin.im/wiki/FAQ\">"
+ "http://developer.pidgin.im/wiki/FAQ</A><BR/><BR/>");
g_string_append_printf(str, _("<FONT SIZE=\"4\">IRC:</FONT> "
"#pidgin on irc.freenode.net<BR><BR>"));
diff --git a/pidgin/gtkutils.h b/pidgin/gtkutils.h
index f363eaad4b..ed20d86901 100644
--- a/pidgin/gtkutils.h
+++ b/pidgin/gtkutils.h
@@ -311,7 +311,7 @@ void pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidge
* The default filter function for screenname autocomplete.
*
* @param completion_entry The completion entry to filter.
- * @param online_accounts If this is @c FALSE, only the autocompletion entries
+ * @param all_accounts If this is @c FALSE, only the autocompletion entries
* which belong to an online account will be filtered.
* @return Returns @c TRUE if the autocompletion entry is filtered.
*/
@@ -435,7 +435,7 @@ void pidgin_set_accessible_relations(GtkWidget *w, GtkWidget *l);
* @param y Address of the gint representing the vertical position
* where the menu shall be drawn. This is an output parameter.
* @param push_in This is an output parameter?
- * @param user_data Not used by this particular position function.
+ * @param data Not used by this particular position function.
*/
void pidgin_menu_position_func_helper(GtkMenu *menu, gint *x, gint *y,
gboolean *push_in, gpointer data);
diff --git a/pidgin/pixmaps/logo.png b/pidgin/pixmaps/logo.png
index c70bbd6878..13d9964faa 100644
--- a/pidgin/pixmaps/logo.png
+++ b/pidgin/pixmaps/logo.png
Binary files differ
diff --git a/pidgin/plugins/win32/transparency/win2ktrans.c b/pidgin/plugins/win32/transparency/win2ktrans.c
index f31a3c7524..f99a813b4a 100644
--- a/pidgin/plugins/win32/transparency/win2ktrans.c
+++ b/pidgin/plugins/win32/transparency/win2ktrans.c
@@ -1,23 +1,24 @@
/*
- * purple - Transparency plugin
+ * Pidgin - Transparency plugin
*
- * copyright (c) 1998-2002, rob flynn <rob@marko.net>
- * copyright (c) 2002-2003, Herman Bloggs <hermanator12002@yahoo.com>
- * copyright (c) 2005, Daniel Atallah <daniel_atallah@yahoo.com>
+ * Copyright (C) 1998-2002, Rob Flynn <rob@marko.net>
+ * Copyright (C) 2002-2003, Herman Bloggs <hermanator12002@yahoo.com>
+ * Copyright (C) 2005, Daniel Atallah <daniel_atallah@yahoo.com>
*
- * 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 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.
+ * 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-1301 usa
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
*
*/
#ifndef _WIN32_WINNT
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3a4d0fcd65..3d0878a4a2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,7 @@
finch/finch.c
finch/gntaccount.c
finch/gntblist.c
+finch/gntcertmgr.c
finch/gntconn.c
finch/gntconv.c
finch/gntdebug.c
@@ -34,6 +35,7 @@ finch/plugins/gnthistory.c
finch/plugins/lastlog.c
libpurple/account.c
libpurple/blist.c
+libpurple/certificate.c
libpurple/connection.c
libpurple/conversation.c
libpurple/dbus-server.c
@@ -61,9 +63,9 @@ libpurple/plugins/perl/perl.c
libpurple/plugins/psychic.c
libpurple/plugins/signals-test.c
libpurple/plugins/simple.c
-libpurple/plugins/ssl/ssl.c
libpurple/plugins/ssl/ssl-gnutls.c
libpurple/plugins/ssl/ssl-nss.c
+libpurple/plugins/ssl/ssl.c
libpurple/plugins/statenotify.c
libpurple/plugins/tcl/tcl.c
libpurple/protocols/bonjour/bonjour.c
@@ -75,6 +77,7 @@ libpurple/protocols/irc/dcc_send.c
libpurple/protocols/irc/irc.c
libpurple/protocols/irc/msgs.c
libpurple/protocols/irc/parse.c
+libpurple/protocols/jabber/adhoccommands.c
libpurple/protocols/jabber/auth.c
libpurple/protocols/jabber/buddy.c
libpurple/protocols/jabber/chat.c
@@ -85,6 +88,8 @@ libpurple/protocols/jabber/parser.c
libpurple/protocols/jabber/presence.c
libpurple/protocols/jabber/roster.c
libpurple/protocols/jabber/si.c
+libpurple/protocols/jabber/usermood.c
+libpurple/protocols/jabber/usernick.c
libpurple/protocols/jabber/xdata.c
libpurple/protocols/msn/dialog.c
libpurple/protocols/msn/error.c
@@ -97,6 +102,8 @@ libpurple/protocols/msn/state.c
libpurple/protocols/msn/switchboard.c
libpurple/protocols/msn/userlist.c
libpurple/protocols/myspace/myspace.c
+libpurple/protocols/myspace/user.c
+libpurple/protocols/myspace/zap.c
libpurple/protocols/novell/nmuser.c
libpurple/protocols/novell/novell.c
libpurple/protocols/oscar/flap_connection.c
@@ -145,17 +152,18 @@ libpurple/protocols/silc10/wb.c
libpurple/protocols/simple/simple.c
libpurple/protocols/toc/toc.c
libpurple/protocols/yahoo/yahoo.c
-libpurple/protocols/yahoo/yahoochat.c
libpurple/protocols/yahoo/yahoo_doodle.c
libpurple/protocols/yahoo/yahoo_filexfer.c
libpurple/protocols/yahoo/yahoo_packet.c
libpurple/protocols/yahoo/yahoo_profile.c
+libpurple/protocols/yahoo/yahoochat.c
libpurple/protocols/yahoo/ycht.c
libpurple/protocols/zephyr/zephyr.c
libpurple/proxy.c
libpurple/request.h
libpurple/savedstatuses.c
libpurple/server.c
+libpurple/sslconn.c
libpurple/status.c
libpurple/util.c
pidgin.desktop.in
@@ -163,6 +171,7 @@ pidgin/eggtrayicon.c
pidgin/gtkaccount.c
pidgin/gtkblist.c
pidgin/gtkcellview.c
+pidgin/gtkcertmgr.c
pidgin/gtkconn.c
pidgin/gtkconv.c
pidgin/gtkdebug.c
@@ -186,8 +195,8 @@ pidgin/gtksound.c
pidgin/gtkstatusbox.c
pidgin/gtkutils.c
pidgin/gtkwhiteboard.c
-pidgin/pidgincombobox.c
pidgin/pidgin.h
+pidgin/pidgincombobox.c
pidgin/pidginstock.c
pidgin/pixmaps/emotes/default/24/default.theme.in
pidgin/pixmaps/emotes/none/none.theme.in
@@ -199,11 +208,11 @@ pidgin/plugins/gestures/gestures.c
pidgin/plugins/gevolution/add_buddy_dialog.c
pidgin/plugins/gevolution/assoc-buddy.c
pidgin/plugins/gevolution/eds-utils.c
-pidgin/plugins/gevolution/gevolution.c
pidgin/plugins/gevolution/gevo-util.c
+pidgin/plugins/gevolution/gevolution.c
pidgin/plugins/gevolution/new_person_dialog.c
-pidgin/plugins/gtkbuddynote.c
pidgin/plugins/gtk-signals-test.c
+pidgin/plugins/gtkbuddynote.c
pidgin/plugins/history.c
pidgin/plugins/iconaway.c
pidgin/plugins/mailchk.c