diff libpurple/protocols/jabber/buddy.c @ 19694:1d2002a5735e

propagate from branch 'im.pidgin.pidgin' (head 996cf0c57149ba6e1c714ebb1f11d5d4bac8fb68) to branch 'im.pidgin.soc.2007.xmpp' (head cdf63b6603891b8cd3e7f629ef5a9a927a153550)
author Andreas Monitzer <pidgin@monitzer.com>
date Wed, 05 Sep 2007 22:32:14 +0000
parents b0733d5d7621
children 481749fc0b6b
line wrap: on
line diff
--- a/libpurple/protocols/jabber/buddy.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Sep 05 22:32:14 2007 +0000
@@ -34,6 +34,8 @@
 #include "iq.h"
 #include "presence.h"
 #include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
 
 typedef struct {
 	long idle_seconds;
@@ -116,7 +118,6 @@
 		int priority, JabberBuddyState state, const char *status)
 {
 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
-
 	if(!jbr) {
 		jbr = g_new0(JabberBuddyResource, 1);
 		jbr->jb = jb;
@@ -128,7 +129,7 @@
 	jbr->state = state;
 	if(jbr->status)
 		g_free(jbr->status);
-        if (status)
+	if (status)
 		jbr->status = g_markup_escape_text(status, -1);
 	else
 		jbr->status = NULL;
@@ -141,6 +142,17 @@
 	g_return_if_fail(jbr != NULL);
 
 	jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
+	
+	while(jbr->commands) {
+		JabberAdHocCommands *cmd = jbr->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->name);
+		g_free(cmd);
+		jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
+	}
+	
+	jabber_caps_free_clientinfo(jbr->caps);
 
 	g_free(jbr->name);
 	g_free(jbr->status);
@@ -411,7 +423,7 @@
 		if ((img = purple_buddy_icons_find_account_icon(gc->account))) {
 			gconstpointer avatar_data;
 			gsize avatar_len;
-			xmlnode *photo, *binval;
+			xmlnode *photo, *binval, *type;
 			gchar *enc;
 			int i;
 			unsigned char hashval[20];
@@ -424,6 +436,8 @@
 				xmlnode_free(photo);
 			}
 			photo = xmlnode_new_child(vc_node, "PHOTO");
+			type = xmlnode_new_child(photo, "TYPE");
+			xmlnode_insert_data(type, "image/png", -1);
 			binval = xmlnode_new_child(photo, "BINVAL");
 			enc = purple_base64_encode(avatar_data, avatar_len);
 
@@ -452,9 +466,135 @@
 
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
+	PurplePresence *gpresence;
+	PurpleStatus *status;
+	
+	if(((JabberStream*)gc->proto_data)->pep) {
+		/* XEP-0084: User Avatars */
+		if(img) {
+			/* A PNG header, including the IHDR, but nothing else */
+			const struct {
+				guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
+				struct {
+					guint32 length; /* must be 0x0d */
+					guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
+					guint32 width;
+					guint32 height;
+					guchar bitdepth;
+					guchar colortype;
+					guchar compression;
+					guchar filter;
+					guchar interlace;
+				} ihdr;
+			} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
+
+			/* check if the data is a valid png file (well, at least to some extend) */
+			if(png->signature[0] == 0x89 &&
+			   png->signature[1] == 0x50 &&
+			   png->signature[2] == 0x4e &&
+			   png->signature[3] == 0x47 &&
+			   png->signature[4] == 0x0d &&
+			   png->signature[5] == 0x0a &&
+			   png->signature[6] == 0x1a &&
+			   png->signature[7] == 0x0a &&
+			   ntohl(png->ihdr.length) == 0x0d &&
+			   png->ihdr.type[0] == 'I' &&
+			   png->ihdr.type[1] == 'H' &&
+			   png->ihdr.type[2] == 'D' &&
+			   png->ihdr.type[3] == 'R') {
+				/* parse PNG header to get the size of the image (yes, this is required) */
+				guint32 width = ntohl(png->ihdr.width);
+				guint32 height = ntohl(png->ihdr.height);
+				xmlnode *publish, *item, *data, *metadata, *info;
+				char *lengthstring, *widthstring, *heightstring;
+				
+				/* compute the sha1 hash */
+				PurpleCipherContext *ctx;
+				unsigned char digest[20];
+				char *hash;
+				char *base64avatar;
+				
+				ctx = purple_cipher_context_new_by_name("sha1", NULL);
+				purple_cipher_context_append(ctx, purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+				
+				/* convert digest to a string */
+				hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]);
+				
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				data = xmlnode_new_child(item, "data");
+				xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
+				
+				base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				xmlnode_insert_data(data,base64avatar,-1);
+				g_free(base64avatar);
+				
+				/* publish the avatar itself */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				/* next step: publish the metadata */
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				info = xmlnode_new_child(metadata, "info");
+				xmlnode_set_attrib(info, "id", hash);
+				xmlnode_set_attrib(info, "type", "image/png");
+				lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img));
+				xmlnode_set_attrib(info, "bytes", lengthstring);
+				g_free(lengthstring);
+				widthstring = g_strdup_printf("%u", width);
+				xmlnode_set_attrib(info, "width", widthstring);
+				g_free(widthstring);
+				heightstring = g_strdup_printf("%u", height);
+				xmlnode_set_attrib(info, "height", heightstring);
+				g_free(lengthstring);
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				g_free(hash);
+			} else { /* if(img) */
+				/* remove the metadata */
+				xmlnode *metadata, *item;
+				xmlnode *publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				xmlnode_new_child(metadata, "stop");
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+			}
+		} else {
+			purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+						 "jabber_set_buddy_icon received non-png data");
+		}
+	}
+
+	/* even when the image is not png, we can still publish the vCard, since this
+	   one doesn't require a specific image type */
+
+	/* publish vCard for those poor older clients */
 	jabber_set_info(gc, purple_account_get_user_info(gc->account));
 
-	jabber_presence_send(gc->account, NULL);
+	gpresence = purple_account_get_presence(gc->account);
+	status = purple_presence_get_active_status(gpresence);
+	jabber_presence_send(gc->account, status);
 }
 
 /*
@@ -659,6 +799,123 @@
 				purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 			}
 		}
+		if(jbr && jbr->caps) {
+			GString *tmp = g_string_new("");
+			GList *iter;
+			for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+				const char *feature = iter->data;
+				
+				if(!strcmp(feature, "jabber:iq:last"))
+					feature = _("Last Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+					feature = _("Service Discovery Info");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+					feature = _("Service Discovery Items");
+				else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+					feature = _("Extended Stanza Addressing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+					feature = _("Multi-User Chat");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+					feature = _("Multi-User Chat Extended Presence Information");
+				else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+					feature = _("In-Band Bytestreams");
+				else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+					feature = _("Ad-Hoc Commands");
+				else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+					feature = _("PubSub Service");
+				else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+					feature = _("SOCKS5 Bytestreams");
+				else if(!strcmp(feature, "jabber:x:oob"))
+					feature = _("Out of Band Data");
+				else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+					feature = _("XHTML-IM");
+				else if(!strcmp(feature, "jabber:iq:register"))
+					feature = _("In-Band Registration");
+				else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+					feature = _("User Location");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+					feature = _("User Avatar");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+					feature = _("Chat State Notifications");
+				else if(!strcmp(feature, "jabber:iq:version"))
+					feature = _("Software Version");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+					feature = _("Stream Initiation");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+					feature = _("File Transfer");
+				else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+					feature = _("User Mood");
+				else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+					feature = _("User Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+					feature = _("Entity Capabilities");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+					feature = _("Encrypted Session Negotiations");
+				else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+					feature = _("User Tune");
+				else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+					feature = _("Roster Item Exchange");
+				else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+					feature = _("Reachability Address");
+				else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+					feature = _("User Profile");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+					feature = _("Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+					feature = _("Jingle Audio");
+				else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+					feature = _("User Nickname");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+					feature = _("Jingle ICE UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+					feature = _("Jingle ICE TCP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+					feature = _("Jingle Raw UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+					feature = _("Jingle Video");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+					feature = _("Jingle DTMF");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+					feature = _("Message Receipts");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+					feature = _("Public Key Publishing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+					feature = _("User Chatting");
+				else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+					feature = _("User Browsing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+					feature = _("User Gaming");
+				else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+					feature = _("User Viewing");
+				else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+					feature = _("Ping");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+					feature = _("Stanza Encryption");
+				else if(!strcmp(feature, "urn:xmpp:time"))
+					feature = _("Entity Time");
+				else if(!strcmp(feature, "urn:xmpp:delay"))
+					feature = _("Delayed Delivery");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+					feature = _("Collaborative Data Objects");
+				else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+					feature = _("File Repository and Sharing");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+					feature = _("STUN Service Discovery for Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+					feature = _("Simplified Encrypted Session Negotiation");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+					feature = _("Hop Check");
+				else if(g_str_has_suffix(feature, "+notify"))
+					feature = NULL;
+				
+				if(feature)
+					g_string_append_printf(tmp, "%s\n", feature);
+			}
+			if(strlen(tmp->str) > 0)
+				purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+			
+			g_string_free(tmp, TRUE);
+		}
 	} else {
 		for(resources = jbi->jb->resources; resources; resources = resources->next) {
 			char *purdy = NULL;
@@ -700,6 +957,123 @@
 					purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 				}
 			}
+			if(jbr && jbr->caps) {
+				GString *tmp = g_string_new("");
+				GList *iter;
+				for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+					const char *feature = iter->data;
+					
+					if(!strcmp(feature, "jabber:iq:last"))
+						feature = _("Last Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+						feature = _("Service Discovery Info");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+						feature = _("Service Discovery Items");
+					else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+						feature = _("Extended Stanza Addressing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+						feature = _("Multi-User Chat");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+						feature = _("Multi-User Chat Extended Presence Information");
+					else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+						feature = _("In-Band Bytestreams");
+					else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+						feature = _("Ad-Hoc Commands");
+					else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+						feature = _("PubSub Service");
+					else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+						feature = _("SOCKS5 Bytestreams");
+					else if(!strcmp(feature, "jabber:x:oob"))
+						feature = _("Out of Band Data");
+					else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+						feature = _("XHTML-IM");
+					else if(!strcmp(feature, "jabber:iq:register"))
+						feature = _("In-Band Registration");
+					else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+						feature = _("User Location");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+						feature = _("User Avatar");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+						feature = _("Chat State Notifications");
+					else if(!strcmp(feature, "jabber:iq:version"))
+						feature = _("Software Version");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+						feature = _("Stream Initiation");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+						feature = _("File Transfer");
+					else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+						feature = _("User Mood");
+					else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+						feature = _("User Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+						feature = _("Entity Capabilities");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+						feature = _("Encrypted Session Negotiations");
+					else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+						feature = _("User Tune");
+					else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+						feature = _("Roster Item Exchange");
+					else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+						feature = _("Reachability Address");
+					else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+						feature = _("User Profile");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+						feature = _("Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+						feature = _("Jingle Audio");
+					else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+						feature = _("User Nickname");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+						feature = _("Jingle ICE UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+						feature = _("Jingle ICE TCP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+						feature = _("Jingle Raw UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+						feature = _("Jingle Video");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+						feature = _("Jingle DTMF");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+						feature = _("Message Receipts");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+						feature = _("Public Key Publishing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+						feature = _("User Chatting");
+					else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+						feature = _("User Browsing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+						feature = _("User Gaming");
+					else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+						feature = _("User Viewing");
+					else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+						feature = _("Ping");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+						feature = _("Stanza Encryption");
+					else if(!strcmp(feature, "urn:xmpp:time"))
+						feature = _("Entity Time");
+					else if(!strcmp(feature, "urn:xmpp:delay"))
+						feature = _("Delayed Delivery");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+						feature = _("Collaborative Data Objects");
+					else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+						feature = _("File Repository and Sharing");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+						feature = _("STUN Service Discovery for Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+						feature = _("Simplified Encrypted Session Negotiation");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+						feature = _("Hop Check");
+					else if(g_str_has_suffix(feature, "+notify"))
+						feature = NULL;
+					
+					if(feature)
+						g_string_append_printf(tmp, "%s\n", feature);
+				}
+				if(strlen(tmp->str) > 0)
+					purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+				
+				g_string_free(tmp, TRUE);
+			}
 		}
 	}
 
@@ -1023,6 +1397,109 @@
 	jabber_buddy_info_show_if_ready(jbi);
 }
 
+typedef struct _JabberBuddyAvatarUpdateURLInfo {
+	JabberStream *js;
+	char *from;
+	char *id;
+} JabberBuddyAvatarUpdateURLInfo;
+
+static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
+	JabberBuddyAvatarUpdateURLInfo *info = user_data;
+	if(!url_text) {
+		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+					 "do_buddy_avatar_update_fromurl got error \"%s\"", error_message);
+		return;
+	}
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
+	g_free(info->from);
+	g_free(info->id);
+	g_free(info);
+}
+
+static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) {
+	xmlnode *item, *data;
+	const char *checksum;
+	char *b64data;
+	void *img;
+	size_t size;
+	if(!items)
+		return;
+	
+	item = xmlnode_get_child(items, "item");
+	if(!item)
+		return;
+	
+	data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA);
+	if(!data)
+		return;
+	
+	checksum = xmlnode_get_attrib(item,"id");
+	if(!checksum)
+		return;
+	
+	b64data = xmlnode_get_data(data);
+	if(!b64data)
+		return;
+	
+	img = purple_base64_decode(b64data, &size);
+	if(!img)
+		return;
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
+}
+
+void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) {
+	PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
+	const char *checksum;
+	xmlnode *item, *metadata;
+	if(!buddy)
+		return;
+	
+	checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+	item = xmlnode_get_child(items,"item");
+	metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA);
+	if(!metadata)
+		return;
+	/* check if we have received a stop */
+	if(xmlnode_get_child(metadata, "stop")) {
+		purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
+	} else {
+		xmlnode *info, *goodinfo = NULL;
+		
+		/* iterate over all info nodes to get one we can use */
+		for(info = metadata->child; info; info = info->next) {
+			if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
+				const char *type = xmlnode_get_attrib(info,"type");
+				const char *id = xmlnode_get_attrib(info,"id");
+				
+				if(checksum && id && !strcmp(id, checksum)) {
+					/* we already have that avatar, so we don't have to do anything */
+					goodinfo = NULL;
+					break;
+				}
+				/* We'll only pick the png one for now. It's a very nice image format anyways. */
+				if(type && id && !goodinfo && !strcmp(type, "image/png"))
+					goodinfo = info;
+			}
+		}
+		if(goodinfo) {
+			const char *url = xmlnode_get_attrib(goodinfo,"url");
+			const char *id = xmlnode_get_attrib(goodinfo,"id");
+			
+			/* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */
+			if(!url)
+				jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data);
+			else {
+				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
+				info->js = js;
+				info->from = g_strdup(from);
+				info->id = g_strdup(id);
+				purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info);
+			}
+		}
+	}
+}
 
 static void jabber_buddy_info_resource_free(gpointer data)
 {
@@ -1295,7 +1772,7 @@
 	status    = purple_presence_get_active_status(gpresence);
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(js, state, msg, priority);
 
 	g_free(msg);
 
@@ -1389,12 +1866,54 @@
 	jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
 }
 
+static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed presence of the current status */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		PurpleConnection *gc = purple_account_get_connection(buddy->account);
+		JabberStream *js = gc->proto_data;
+		PurpleAccount *account = purple_connection_get_account(gc);
+		PurplePresence *gpresence = purple_account_get_presence(account);
+		PurpleStatus *status = purple_presence_get_active_status(gpresence);
+		xmlnode *presence;
+		JabberBuddyState state;
+		char *msg;
+		int priority;
+		
+		purple_status_to_jabber(status, &state, &msg, &priority);
+		presence = jabber_presence_create_js(js, state, msg, priority);
+		
+		g_free(msg);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
+
+static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed unavailable presence */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		JabberStream *js = purple_account_get_connection(buddy->account)->proto_data;
+		xmlnode *presence;
+		
+		presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
 
 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
 {
 	PurpleConnection *gc = purple_account_get_connection(buddy->account);
 	JabberStream *js = gc->proto_data;
 	JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
+	GList *jbrs;
 
 	GList *m = NULL;
 	PurpleMenuAction *act;
@@ -1439,6 +1958,38 @@
 		                           NULL, NULL);
 		m = g_list_append(m, act);
 	}
+	
+	/*
+	 * This if-condition implements parts of XEP-0100: Gateway Interaction
+	 *
+	 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
+	 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
+	 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
+	 * people don't tend to have a server or other service there.
+	 */
+	if (g_utf8_strchr(buddy->name, -1, '@') == NULL) {
+		act = purple_menu_action_new(_("Log In"),
+									 PURPLE_CALLBACK(jabber_buddy_login),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+		act = purple_menu_action_new(_("Log Out"),
+									 PURPLE_CALLBACK(jabber_buddy_logout),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+	}
+	
+	/* add all ad hoc commands to the action menu */
+	for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
+		JabberBuddyResource *jbr = jbrs->data;
+		GList *commands;
+		if (!jbr->commands)
+			continue;
+		for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
+			JabberAdHocCommands *cmd = commands->data;
+			act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
+			m = g_list_append(m, act);
+		}
+	}
 
 	return m;
 }
@@ -1728,10 +2279,10 @@
  * in purple-i18n@lists.sourceforge.net (March 2006)
  */
 static const char * jabber_user_dir_comments [] = {
-       /* current comment from Jabber User Directory users.jabber.org */
-       N_("Find a contact by entering the search criteria in the given fields. "
-          "Note: Each field supports wild card searches (%)"),
-       NULL
+	/* current comment from Jabber User Directory users.jabber.org */
+	N_("Find a contact by entering the search criteria in the given fields. "
+	   "Note: Each field supports wild card searches (%)"),
+	NULL
 };
 #endif
 
@@ -1824,14 +2375,14 @@
 				_("Search for XMPP users"), instructions, fields,
 				_("Search"), G_CALLBACK(user_search_cb),
 				_("Cancel"), G_CALLBACK(user_search_cancel_cb),
-				NULL, NULL, NULL,
+				purple_connection_get_account(js->gc), NULL, NULL,
 				usi);
 
 		g_free(instructions);
 	}
 }
 
-static void jabber_user_search_ok(JabberStream *js, const char *directory)
+void jabber_user_search(JabberStream *js, const char *directory)
 {
 	JabberIq *iq;
 
@@ -1858,7 +2409,7 @@
 			_("Select a user directory to search"),
 			js->user_directories ? js->user_directories->data : NULL,
 			FALSE, FALSE, NULL,
-			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search_ok),
+			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
 			_("Cancel"), NULL,
 			NULL, NULL, NULL,
 			js);