changeset 25502:ee966d2dc151

propagate from branch 'im.pidgin.pidgin' (head dff846c19b3c14ce3094dda9a5b72c70e6e11121) to branch 'im.pidgin.pidgin.yaz' (head 96dcf99b9a4d5aec09c2173b5f2f901d96330dbd)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sat, 09 Jun 2007 04:12:47 +0000
parents be1c61b5f30d (current diff) cf0e01adfaa9 (diff)
children 7577706bde9c
files libpurple/protocols/jabber/jabber.c
diffstat 13 files changed, 452 insertions(+), 260 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Fri Jun 08 03:19:13 2007 +0000
+++ b/COPYRIGHT	Sat Jun 09 04:12:47 2007 +0000
@@ -226,6 +226,7 @@
 Lalo Martins
 John Matthews
 Simo Mattila
+Michal Matyska
 Ryan McCabe
 Peter McCurdy
 Kurt McKee
--- a/libpurple/protocols/bonjour/bonjour.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Sat Jun 09 04:12:47 2007 +0000
@@ -267,6 +267,7 @@
 bonjour_convo_closed(PurpleConnection *connection, const char *who)
 {
 	PurpleBuddy *buddy = purple_find_buddy(connection->account, who);
+	BonjourBuddy *bb;
 
 	if (buddy == NULL)
 	{
@@ -277,7 +278,9 @@
 		return;
 	}
 
-	bonjour_jabber_close_conversation(((BonjourData*)(connection->proto_data))->jabber_data, buddy);
+	bb = buddy->proto_data;
+	bonjour_jabber_close_conversation(bb->conversation);
+	bb->conversation = NULL;
 }
 
 static char *
@@ -320,6 +323,8 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
+	/* These shouldn't happen here because they are allocated in _init() */
+
 	g_free(default_firstname);
 	g_free(default_lastname);
 	g_free(default_hostname);
--- a/libpurple/protocols/bonjour/buddy.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Sat Jun 09 04:12:47 2007 +0000
@@ -178,11 +178,8 @@
 	g_free(buddy->node);
 	g_free(buddy->ver);
 
-	if (buddy->conversation != NULL)
-	{
-		g_free(buddy->conversation->buddy_name);
-		g_free(buddy->conversation);
-	}
+	bonjour_jabber_close_conversation(buddy->conversation);
+	buddy->conversation = NULL;
 
 #ifdef USE_BONJOUR_APPLE
 	if (buddy->txt_query != NULL)
--- a/libpurple/protocols/bonjour/jabber.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sat Jun 09 04:12:47 2007 +0000
@@ -28,7 +28,6 @@
 #endif
 #include <sys/types.h>
 #include <glib.h>
-#include <glib/gprintf.h>
 #include <unistd.h>
 #include <fcntl.h>
 
@@ -45,39 +44,10 @@
 #include "bonjour.h"
 #include "buddy.h"
 
-static gint
-_connect_to_buddy(PurpleBuddy *gb)
-{
-	gint socket_fd;
-	struct sockaddr_in buddy_address;
-	BonjourBuddy *bb = gb->proto_data;
-
-	purple_debug_info("bonjour", "Connecting to buddy %s at %s:%d.\n",
-		purple_buddy_get_name(gb), bb->ip ? bb->ip : "(null)", bb->port_p2pj);
-
-	/* Create a socket and make it non-blocking */
-	socket_fd = socket(PF_INET, SOCK_STREAM, 0);
-	if (socket_fd < 0) {
-		purple_debug_warning("bonjour", "Error opening socket: %s\n", strerror(errno));
-		return -1;
-	}
-
-	buddy_address.sin_family = PF_INET;
-	buddy_address.sin_port = htons(bb->port_p2pj);
-	inet_aton(bb->ip, &(buddy_address.sin_addr));
-	memset(&(buddy_address.sin_zero), '\0', 8);
-
-	/* TODO: make this nonblocking before connecting */
-	if (connect(socket_fd, (struct sockaddr*)&buddy_address, sizeof(struct sockaddr)) == 0)
-		fcntl(socket_fd, F_SETFL, O_NONBLOCK);
-	else {
-		purple_debug_warning("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n", purple_buddy_get_name(gb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, strerror(errno));
-		close(socket_fd);
-		socket_fd = -1;
-	}
-
-	return socket_fd;
-}
+#define STREAM_END "</stream:stream>"
+/* TODO: specify version='1.0' and send stream features */
+#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
+		"<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
 
 #if 0 /* this isn't used anywhere... */
 static const char *
@@ -104,6 +74,19 @@
 }
 #endif
 
+static BonjourJabberConversation *
+bonjour_jabber_conv_new() {
+
+	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
+	bconv->socket = -1;
+	bconv->tx_buf = purple_circ_buffer_new(512);
+	bconv->tx_handler = -1;
+	bconv->rx_handler = -1;
+
+	return bconv;
+}
+
+
 static const char *
 _font_size_ichat_to_purple(int size)
 {
@@ -125,9 +108,9 @@
 }
 
 static void
-_jabber_parse_and_write_message_to_ui(char *message, PurpleConnection *connection, PurpleBuddy *gb)
+_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb)
 {
-	xmlnode *message_node, *body_node, *html_node, *events_node;
+	xmlnode *body_node, *html_node, *events_node;
 	char *body, *html_body = NULL;
 	const char *ichat_balloon_color = NULL;
 	const char *ichat_text_color = NULL;
@@ -136,16 +119,9 @@
 	const char *font_color = NULL;
 	gboolean composing_event = FALSE;
 
-	/* Parsing of the message */
-	message_node = xmlnode_from_str(message, strlen(message));
-	if (message_node == NULL)
+	body_node = xmlnode_get_child(message_node, "body");
+	if (body_node == NULL)
 		return;
-
-	body_node = xmlnode_get_child(message_node, "body");
-	if (body_node == NULL) {
-		xmlnode_free(message_node);
-		return;
-	}
 	body = xmlnode_get_data(body_node);
 
 	html_node = xmlnode_get_child(message_node, "html");
@@ -171,11 +147,8 @@
 				font_color = xmlnode_get_attrib(html_body_font_node, "color");
 				html_body = xmlnode_get_data(html_body_font_node);
 				if (html_body == NULL)
-				{
-					gint garbage = -1;
 					/* This is the kind of formated messages that Purple creates */
-					html_body = xmlnode_to_str(html_body_font_node, &garbage);
-				}
+					html_body = xmlnode_to_str(html_body_font_node, NULL);
 			}
 		}
 	}
@@ -184,14 +157,11 @@
 	if (events_node != NULL)
 	{
 		if (xmlnode_get_child(events_node, "composing") != NULL)
-		{
 			composing_event = TRUE;
-		}
 		if (xmlnode_get_child(events_node, "id") != NULL)
 		{
 			/* The user is just typing */
 			/* TODO: Deal with typing notification */
-			xmlnode_free(message_node);
 			g_free(body);
 			g_free(html_body);
 			return;
@@ -207,31 +177,30 @@
 		if (font_size == NULL) font_size = "3";
 		if (ichat_text_color == NULL) ichat_text_color = "#000000";
 		if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
-		body = g_strconcat("<font face='", font_face, "' size='", font_size, "' color='", ichat_text_color,
-							"' back='", ichat_balloon_color, "'>", html_body, "</font>", NULL);
+		body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
+				       font_face, font_size, ichat_text_color, ichat_balloon_color,
+				       html_body);
 	}
 
 	/* TODO: Should we do something with "composing_event" here? */
 
 	/* Send the message to the UI */
-	serv_got_im(connection, gb->name, body, 0, time(NULL));
+	serv_got_im(connection, pb->name, body, 0, time(NULL));
 
-	/* Free all the strings and nodes (the attributes are freed with their nodes) */
-	xmlnode_free(message_node);
 	g_free(body);
 	g_free(html_body);
 }
 
 struct _check_buddy_by_address_t {
 	const char *address;
-	PurpleBuddy **gb;
+	PurpleBuddy **pb;
 	BonjourJabber *bj;
 };
 
 static void
 _check_buddy_by_address(gpointer key, gpointer value, gpointer data)
 {
-	PurpleBuddy *gb = value;
+	PurpleBuddy *pb = value;
 	BonjourBuddy *bb;
 	struct _check_buddy_by_address_t *cbba = data;
 
@@ -240,11 +209,11 @@
 	 * is the same as the account requesting the check then continue to determine
 	 * whether the buddies IP matches the target IP.
 	 */
-	if (cbba->bj->account == gb->account)
+	if (cbba->bj->account == pb->account)
 	{
-		bb = gb->proto_data;
+		bb = pb->proto_data;
 		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
-			*(cbba->gb) = gb;
+			*(cbba->pb) = pb;
 	}
 }
 
@@ -279,24 +248,96 @@
 	return total_message_length;
 }
 
-static gint
-_send_data(gint socket, char *message)
+static void
+_send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
-	gint message_len = strlen(message);
-	gint partial_sent = 0;
-	gchar *partial_message = message;
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+	int ret, writelen;
+
+	/* TODO: Make sure that the stream has been established before sending */
+
+	writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);
+
+	if (writelen == 0) {
+		purple_input_remove(bconv->tx_handler);
+		bconv->tx_handler = -1;
+		return;
+	}
 
-	while ((partial_sent = send(socket, partial_message, message_len, 0)) < message_len)
-	{
-		if (partial_sent != -1) {
-			partial_message += partial_sent;
-			message_len -= partial_sent;
-		} else {
-			return -1;
-		}
+	ret = send(bconv->socket, bconv->tx_buf->outptr, writelen, 0);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		PurpleConversation *conv;
+		const char *error = strerror(errno);
+
+		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
+				   purple_buddy_get_name(pb), error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send message."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return;
 	}
 
-	return strlen(message);
+	purple_circ_buffer_mark_read(bconv->tx_buf, ret);
+}
+
+static gint
+_send_data(PurpleBuddy *pb, char *message)
+{
+	gint ret;
+	int len = strlen(message);
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+
+	/* If we're not ready to actually send, append it to the buffer */
+	if (bconv->tx_handler != -1
+			|| bconv->connect_data != NULL
+			|| !bconv->stream_started
+			|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+		ret = -1;
+		errno = EAGAIN;
+	} else {
+		ret = send(bconv->socket, message, len, 0);
+	}
+
+	if (ret == -1 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
+		PurpleConversation *conv;
+		const char *error = strerror(errno);
+
+		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
+				   purple_buddy_get_name(pb), error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send message."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return -1;
+	}
+
+	if (ret < len) {
+		if (bconv->tx_handler == -1)
+			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
+				_send_data_write_cb, pb);
+		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
+	}
+
+	return ret;
 }
 
 static void
@@ -304,17 +345,22 @@
 {
 	char *message = NULL;
 	gint message_length;
-	PurpleBuddy *gb = data;
-	PurpleAccount *account = gb->account;
-	PurpleConversation *conversation;
-	BonjourBuddy *bb = gb->proto_data;
+	PurpleBuddy *pb = data;
+	PurpleAccount *account = pb->account;
+	BonjourBuddy *bb = pb->proto_data;
 	gboolean closed_conversation = FALSE;
 	xmlnode *message_node;
 
 	/* Read the data from the socket */
 	if ((message_length = _read_data(socket, &message)) == -1) {
 		/* There have been an error reading from the socket */
-		/* TODO: Shouldn't we handle the error if it isn't EAGAIN? */
+		if (errno != EAGAIN) {
+			bonjour_jabber_close_conversation(bb->conversation);
+			bb->conversation = NULL;
+
+			/* I guess we really don't need to notify the user.
+			 * If they try to send another message it'll reconnect */
+		}
 		return;
 	} else if (message_length == 0) { /* The other end has closed the socket */
 		closed_conversation = TRUE;
@@ -330,62 +376,106 @@
 	/* Parse the message into an XMLnode for analysis */
 	message_node = xmlnode_from_str(message, strlen(message));
 
-	/* Check if the start of the stream has been received, if not check that the current */
-	/* data is the start of the stream */
-	if (!(bb->conversation->stream_started))
-	{
-		/* Check if this is the start of the stream */
-		if ((message_node != NULL) &&
-			g_ascii_strcasecmp(xmlnode_get_attrib(message_node, "xmlns"), "jabber:client") &&
-			(xmlnode_get_attrib(message_node,"xmlns:stream") != NULL))
-		{
-			bb->conversation->stream_started = TRUE;
-		}
-		else
-		{
-			/* TODO: This needs to be nonblocking! */
-			if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1)
-				purple_debug_error("bonjour", "Unable to start a conversation with %s\n", bb->name);
-			else
-				bb->conversation->stream_started = TRUE;
-		}
-	}
-
 	/*
 	 * Check that this is not the end of the conversation.  This is
 	 * using a magic string, but xmlnode won't play nice when just
 	 * parsing an end tag
 	 */
-	if (purple_str_has_prefix(message, STREAM_END) || (closed_conversation == TRUE)) {
-		char *closed_conv_message;
+	if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) {
+		PurpleConversation *conv;
 
 		/* Close the socket, clear the watcher and free memory */
-		if (bb->conversation != NULL) {
-			close(bb->conversation->socket);
-			purple_input_remove(bb->conversation->watcher_id);
-			g_free(bb->conversation->buddy_name);
-			g_free(bb->conversation);
-			bb->conversation = NULL;
-		}
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
 
 		/* Inform the user that the conversation has been closed */
-		conversation = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, gb->name, account);
-		closed_conv_message = g_strdup_printf(_("%s has closed the conversation."), gb->name);
-		purple_conversation_write(conversation, NULL, closed_conv_message, PURPLE_MESSAGE_SYSTEM, time(NULL));
-		g_free(closed_conv_message);
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account);
+		if (conv != NULL) {
+			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+		}
+	} else if (message_node != NULL) {
+		/* Parse the message to get the data and send to the ui */
+		_jabber_parse_and_write_message_to_ui(message_node, account->gc, pb);
 	} else {
-		/* Parse the message to get the data and send to the ui */
-		_jabber_parse_and_write_message_to_ui(message, account->gc, gb);
+		/* TODO: Deal with receiving only a partial message */
 	}
 
+	g_free(message);
 	if (message_node != NULL)
 		xmlnode_free(message_node);
 }
 
+struct _stream_start_data {
+	char *msg;
+	PurpleInputFunction tx_handler_cb;
+};
+
+static void
+_start_stream(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	struct _stream_start_data *ss = bb->conversation->stream_data;
+	int len, ret;
+
+	len = strlen(ss->msg);
+
+	/* Start Stream */
+	ret = send(source, ss->msg, len, 0);
+
+	if (ret == -1 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		const char *err = strerror(errno);
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+
+		return;
+	}
+
+	/* This is EXTREMELY unlikely to happen */
+	if (ret < len) {
+		char *tmp = g_strdup(ss->msg + ret);
+		g_free(ss->msg);
+		ss->msg = tmp;
+		return;
+	}
+
+	/* Stream started; process the send buffer if there is one*/
+	purple_input_remove(bb->conversation->tx_handler);
+	bb->conversation->tx_handler= -1;
+
+	bb->conversation->stream_started = TRUE;
+
+	g_free(ss->msg);
+	g_free(ss);
+	bb->conversation->stream_data = NULL;
+
+	if (ss->tx_handler_cb) {
+		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
+			ss->tx_handler_cb, pb);
+		/* We can probably write the data now. */
+		(ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE);
+	}
+}
+
 static void
 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 	struct sockaddr_in their_addr; /* connector's address information */
 	socklen_t sin_size = sizeof(struct sockaddr);
 	int client_socket;
@@ -407,36 +497,63 @@
 	address_text = inet_ntoa(their_addr.sin_addr);
 	cbba = g_new0(struct _check_buddy_by_address_t, 1);
 	cbba->address = address_text;
-	cbba->gb = &gb;
+	cbba->pb = &pb;
 	cbba->bj = data;
 	g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
 	g_free(cbba);
-	if (gb == NULL)
+	if (pb == NULL)
 	{
 		purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n");
 		close(client_socket);
 		return;
 	}
-	bb = gb->proto_data;
+	bb = pb->proto_data;
 
 	/* Check if the conversation has been previously started */
 	if (bb->conversation == NULL)
 	{
-		bb->conversation = g_new(BonjourJabberConversation, 1);
+		int ret, len;
+		char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
+			purple_buddy_get_name(pb));
+
+		len = strlen(stream_start);
+
+		/* Start the stream */
+		ret = send(client_socket, stream_start, len, 0);
+
+		if (ret == -1 && errno == EAGAIN)
+			ret = 0;
+		else if (ret <= 0) {
+			const char *err = strerror(errno);
+
+			purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+					   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+			close(client_socket);
+			g_free(stream_start);
+
+			return;
+		}
+
+		bb->conversation = bonjour_jabber_conv_new();
 		bb->conversation->socket = client_socket;
-		bb->conversation->stream_started = FALSE;
-		bb->conversation->buddy_name = g_strdup(gb->name);
-		bb->conversation->message_id = 1;
+		bb->conversation->rx_handler = purple_input_add(client_socket,
+			PURPLE_INPUT_READ, _client_socket_handler, pb);
 
-		if (bb->conversation->stream_started == FALSE) {
-			/* Start the stream */
-			send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0);
+		/* This is unlikely to happen */
+		if (ret < len) {
+			struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+			ss->msg = g_strdup(stream_start + ret);
+			ss->tx_handler_cb = NULL; /* We have nothing to write yet */
+			bb->conversation->stream_data = ss;
+			/* Finish sending the stream start */
+			bb->conversation->tx_handler = purple_input_add(client_socket,
+				PURPLE_INPUT_WRITE, _start_stream, pb);
+		} else {
 			bb->conversation->stream_started = TRUE;
 		}
 
-		/* Open a watcher for the client socket */
-		bb->conversation->watcher_id = purple_input_add(client_socket, PURPLE_INPUT_READ,
-								_client_socket_handler, gb);
+		g_free(stream_start);
 	} else {
 		close(client_socket);
 	}
@@ -514,134 +631,199 @@
 	return data->port;
 }
 
+static void
+_connected_to_buddy(gpointer data, gint source, const gchar *error)
+{
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	int len, ret;
+	char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb));
+
+	bb->conversation->connect_data = NULL;
+
+	if (source < 0) {
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return;
+	}
+
+	len = strlen(stream_start);
+
+	/* Start the stream and send queued messages */
+	ret = send(source, stream_start, len, 0);
+
+	if (ret == -1 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
+		const char *err = strerror(errno);
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		close(source);
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+
+		g_free(stream_start);
+
+		return;
+	}
+
+	bb->conversation->socket = source;
+	bb->conversation->rx_handler = purple_input_add(source,
+		PURPLE_INPUT_READ, _client_socket_handler, pb);
+
+	/* This is unlikely to happen */
+	if (ret < len) {
+		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+		ss->msg = g_strdup(stream_start + ret);
+		ss->tx_handler_cb = _send_data_write_cb;
+		bb->conversation->stream_data = ss;
+		/* Finish sending the stream start */
+		bb->conversation->tx_handler = purple_input_add(source,
+			PURPLE_INPUT_WRITE, _start_stream, pb);
+	}
+	/* Process the send buffer */
+	else {
+		bb->conversation->stream_started = TRUE;
+		/* Watch for when we can write the buffered messages */
+		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
+			_send_data_write_cb, pb);
+		/* We can probably write the data now. */
+		_send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
+	}
+
+	g_free(stream_start);
+}
+
 int
 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
 {
-	xmlnode *message_node = NULL;
-	gchar *message = NULL;
-	gint message_length = -1;
-	xmlnode *message_body_node = NULL;
-	xmlnode *message_html_node = NULL;
-	xmlnode *message_html_body_node = NULL;
-	xmlnode *message_html_body_font_node = NULL;
-	xmlnode *message_x_node = NULL;
-	PurpleBuddy *gb = NULL;
-	BonjourBuddy *bb = NULL;
-	PurpleConversation *conversation = NULL;
-	char *message_from_ui = NULL;
-	char *stripped_message = NULL;
-	gint ret;
+	xmlnode *message_node, *node, *node2;
+	gchar *message;
+	PurpleBuddy *pb;
+	BonjourBuddy *bb;
+	int ret;
 
-	gb = purple_find_buddy(data->account, to);
-	if (gb == NULL) {
+	pb = purple_find_buddy(data->account, to);
+	if (pb == NULL) {
 		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
 		/* You can not send a message to an offline buddy */
 		return -10000;
 	}
 
-	bb = (BonjourBuddy *)gb->proto_data;
+	bb = pb->proto_data;
 
 	/* Check if there is a previously open conversation */
 	if (bb->conversation == NULL)
 	{
-		int socket = _connect_to_buddy(gb);
-		if (socket < 0)
-			return -10001;
+		PurpleProxyConnectData *connect_data;
 
-		bb->conversation = g_new(BonjourJabberConversation, 1);
-		bb->conversation->socket = socket;
-		bb->conversation->stream_started = FALSE;
-		bb->conversation->buddy_name = g_strdup(gb->name);
-		bb->conversation->watcher_id = purple_input_add(bb->conversation->socket,
-				PURPLE_INPUT_READ, _client_socket_handler, gb);
-	}
+		/* Make sure that the account always has a proxy of "none".
+		 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
+		static PurpleProxyInfo *tmp_none_proxy_info = NULL;
+		if (!tmp_none_proxy_info) {
+			tmp_none_proxy_info = purple_proxy_info_new();
+			purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
+		}
+		purple_account_set_proxy_info(data->account, tmp_none_proxy_info);
 
-	/* Enclose the message from the UI within a "font" node */
-	message_body_node = xmlnode_new("body");
-	stripped_message = purple_markup_strip_html(body);
-	xmlnode_insert_data(message_body_node, stripped_message, strlen(stripped_message));
-	g_free(stripped_message);
-
-	message_from_ui = g_strconcat("<font>", body, "</font>", NULL);
-	message_html_body_font_node = xmlnode_from_str(message_from_ui, strlen(message_from_ui));
-	g_free(message_from_ui);
+		connect_data =
+			purple_proxy_connect(data->account->gc, data->account, bb->ip,
+					     bb->port_p2pj, _connected_to_buddy, pb);
 
-	message_html_body_node = xmlnode_new("body");
-	xmlnode_insert_child(message_html_body_node, message_html_body_font_node);
+		if (connect_data == NULL) {
+			purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
+			return -10001;
+		}
 
-	message_html_node = xmlnode_new("html");
-	xmlnode_set_attrib(message_html_node, "xmlns", "http://www.w3.org/1999/xhtml");
-	xmlnode_insert_child(message_html_node, message_html_body_node);
-
-	message_x_node = xmlnode_new("x");
-	xmlnode_set_attrib(message_x_node, "xmlns", "jabber:x:event");
-	xmlnode_insert_child(message_x_node, xmlnode_new("composing"));
+		bb->conversation = bonjour_jabber_conv_new();
+		bb->conversation->connect_data = connect_data;
+		/* We don't want _send_data() to register the tx_handler;
+		 * that neeeds to wait until we're actually connected. */
+		bb->conversation->tx_handler = 0;
+	}
 
 	message_node = xmlnode_new("message");
-	xmlnode_set_attrib(message_node, "to", ((BonjourBuddy*)(gb->proto_data))->name);
-	xmlnode_set_attrib(message_node, "from", data->account->username);
+	xmlnode_set_attrib(message_node, "to", bb->name);
+	xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
 	xmlnode_set_attrib(message_node, "type", "chat");
-	xmlnode_insert_child(message_node, message_body_node);
-	xmlnode_insert_child(message_node, message_html_node);
-	xmlnode_insert_child(message_node, message_x_node);
+
+	/* Enclose the message from the UI within a "font" node */
+	node = xmlnode_new_child(message_node, "body");
+	message = purple_markup_strip_html(body);
+	xmlnode_insert_data(node, message, strlen(message));
+	g_free(message);
+
+	node = xmlnode_new_child(message_node, "html");
+	xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml");
 
-	message = xmlnode_to_str(message_node, &message_length);
+	node = xmlnode_new_child(node, "body");
+	message = g_strdup_printf("<font>%s</font>", body);
+	node2 = xmlnode_from_str(message, strlen(message));
+	g_free(message);
+	xmlnode_insert_child(node, node2);
+
+	node = xmlnode_new_child(message_node, "x");
+	xmlnode_set_namespace(node, "jabber:x:event");
+	xmlnode_insert_child(node, xmlnode_new("composing"));
+
+	message = xmlnode_to_str(message_node, NULL);
 	xmlnode_free(message_node);
 
-	/* Check if the stream for the conversation has been started */
-	if (bb->conversation->stream_started == FALSE)
-	{
-		/* Start the stream */
-		if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1)
-		{
-			purple_debug_error("bonjour", "Unable to start a conversation\n");
-			purple_debug_warning("bonjour", "send error: %s\n", strerror(errno));
-			conversation = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, data->account);
-			purple_conversation_write(conversation, NULL,
-						  _("Unable to send the message, the conversation couldn't be started."),
-						  PURPLE_MESSAGE_SYSTEM, time(NULL));
-			close(bb->conversation->socket);
-			purple_input_remove(bb->conversation->watcher_id);
+	ret = _send_data(pb, message) >= 0;
 
-			/* Free all the data related to the conversation */
-			g_free(bb->conversation->buddy_name);
-			g_free(bb->conversation);
-			bb->conversation = NULL;
-			g_free(message);
-			return 0;
-		}
-
-		bb->conversation->stream_started = TRUE;
-	}
-
-	/* Send the message */
-	ret = (_send_data(bb->conversation->socket, message) == -1);
 	g_free(message);
 
-	if (ret == -1)
-		return -10000;
-
-	return 1;
+	return ret;
 }
 
 void
-bonjour_jabber_close_conversation(BonjourJabber *data, PurpleBuddy *gb)
+bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
 {
-	BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data;
-
-	if (bb->conversation != NULL)
+	if (bconv != NULL)
 	{
-		/* Send the end of the stream to the other end of the conversation */
-		send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0);
-
 		/* Close the socket and remove the watcher */
-		close(bb->conversation->socket);
-		purple_input_remove(bb->conversation->watcher_id);
+		if (bconv->socket >= 0) {
+			/* Send the end of the stream to the other end of the conversation */
+			if (bconv->stream_started)
+				send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
+			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
+			close(bconv->socket);
+		}
+		if (bconv->rx_handler != -1)
+			purple_input_remove(bconv->rx_handler);
+		if (bconv->tx_handler > 0)
+			purple_input_remove(bconv->tx_handler);
 
 		/* Free all the data related to the conversation */
-		g_free(bb->conversation->buddy_name);
-		g_free(bb->conversation);
-		bb->conversation = NULL;
+		purple_circ_buffer_destroy(bconv->tx_buf);
+		if (bconv->connect_data != NULL)
+			purple_proxy_connect_cancel(bconv->connect_data);
+		if (bconv->stream_data != NULL) {
+			struct _stream_start_data *ss = bconv->stream_data;
+			g_free(ss->msg);
+			g_free(ss);
+		}
+		g_free(bconv);
 	}
 }
 
@@ -657,12 +839,15 @@
 	/* Close all the conversation sockets and remove all the watchers after sending end streams */
 	if (data->account->gc != NULL)
 	{
-		GSList *buddies;
-		GSList *l;
+		GSList *buddies, *l;
 
-		buddies = purple_find_buddies(data->account, data->account->username);
-		for (l = buddies; l; l = l->next)
-			bonjour_jabber_close_conversation(data, l->data);
+		buddies = purple_find_buddies(data->account, purple_account_get_username(data->account));
+		for (l = buddies; l; l = l->next) {
+			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
+			bonjour_jabber_close_conversation(bb->conversation);
+			bb->conversation = NULL;
+		}
+
 		g_slist_free(buddies);
 	}
 }
--- a/libpurple/protocols/bonjour/jabber.h	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Sat Jun 09 04:12:47 2007 +0000
@@ -27,9 +27,7 @@
 #define _BONJOUR_JABBER_H_
 
 #include "account.h"
-
-#define STREAM_END "</stream:stream>"
-#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">"
+#include "circbuffer.h"
 
 typedef struct _BonjourJabber
 {
@@ -42,10 +40,12 @@
 typedef struct _BonjourJabberConversation
 {
 	gint socket;
-	gint watcher_id;
-	gchar* buddy_name;
+	guint rx_handler;
+	guint tx_handler;
+	PurpleCircBuffer *tx_buf;
 	gboolean stream_started;
-	gint message_id;
+	PurpleProxyConnectData *connect_data;
+	gpointer stream_data;
 } BonjourJabberConversation;
 
 /**
@@ -58,7 +58,7 @@
 
 int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body);
 
-void bonjour_jabber_close_conversation(BonjourJabber *data, PurpleBuddy *gb);
+void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
 void bonjour_jabber_stop(BonjourJabber *data);
 
--- a/libpurple/protocols/bonjour/mdns_common.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Sat Jun 09 04:12:47 2007 +0000
@@ -162,7 +162,7 @@
 
 #ifdef USE_BONJOUR_APPLE
 	/* hack: for win32, we need to stop listening to the advertisement pipe too */
-	purple_input_remove(data->advertisement_fd);
+	purple_input_remove(data->advertisement_handler);
 
 	DNSServiceRefDeallocate(data->advertisement);
 	DNSServiceRefDeallocate(data->browser);
--- a/libpurple/protocols/bonjour/mdns_types.h	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_types.h	Sat Jun 09 04:12:47 2007 +0000
@@ -38,7 +38,7 @@
 	DNSServiceRef advertisement;
 	DNSServiceRef browser;
 
-	int advertisement_fd; /* hack... windows bonjour is broken, so we have to have this */
+	int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
 #else /* USE_BONJOUR_HOWL */
 	sw_discovery session;
 	sw_discovery_oid session_id;
--- a/libpurple/protocols/bonjour/mdns_win32.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Sat Jun 09 04:12:47 2007 +0000
@@ -280,7 +280,7 @@
 		{
 			/* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
 			gint advertisement_fd = DNSServiceRefSockFD(data->advertisement);
-			data->advertisement_fd = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement);
+			data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement);
 		}
 	}
 
--- a/libpurple/protocols/jabber/jabber.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Jun 09 04:12:47 2007 +0000
@@ -1075,8 +1075,10 @@
 			/* lets make sure our buddy icon is up to date
 			 * before we go letting people know we're here */
 			img = purple_buddy_icons_find_account_icon(js->gc->account);
-			jabber_set_buddy_icon(js->gc, img);
-			purple_imgstore_unref(img);
+			if(NULL != img) {
+				jabber_set_buddy_icon(js->gc, img);
+				purple_imgstore_unref(img);
+			}
 
 			/* now we can alert the core that we're ready to send status */
 			purple_connection_set_state(js->gc, PURPLE_CONNECTED);
--- a/libpurple/protocols/simple/simple.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Sat Jun 09 04:12:47 2007 +0000
@@ -1243,7 +1243,7 @@
 					foundxpidf = TRUE;
 				if(tmp2) {
 					*tmp2 = ',';
-					tmp = tmp2;
+					tmp = tmp2 + 1;
 					while(*tmp == ' ') tmp++;
 				} else
 					tmp = 0;
--- a/libpurple/win32/libc_interface.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/libpurple/win32/libc_interface.c	Sat Jun 09 04:12:47 2007 +0000
@@ -1,6 +1,6 @@
 /*
  * purple
- * 
+ *
  * Copyright (C) 2002-2003, Herman Bloggs <hermanator12002@yahoo.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -74,7 +74,7 @@
 	int ret;
 
 	ret = connect( socket, addr, length );
-	
+
 	if( ret == SOCKET_ERROR ) {
 		errno = WSAGetLastError();
 		if( errno == WSAEWOULDBLOCK )
@@ -129,6 +129,8 @@
 	if ((ret = sendto(socket, buf, len, flags, to, tolen)
 			) == SOCKET_ERROR) {
 		errno = WSAGetLastError();
+		if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
+			errno = EAGAIN;
 		return -1;
 	}
 	return ret;
@@ -302,7 +304,7 @@
 	if(wpurple_is_socket(fd)) {
 		if((ret = recv(fd, buf, size, 0)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
-			if(errno == WSAEWOULDBLOCK)
+			if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 				errno = EAGAIN;
 			return -1;
 		}
@@ -330,7 +332,7 @@
 
 	if (ret == SOCKET_ERROR) {
 		errno = WSAGetLastError();
-		if(errno == WSAEWOULDBLOCK)
+		if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 			errno = EAGAIN;
 		return -1;
 	}
@@ -350,7 +352,7 @@
 
 	if((ret = recv(fd, buf, len, flags)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
-			if(errno == WSAEWOULDBLOCK)
+			if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 				errno = EAGAIN;
 			return -1;
 	} else {
@@ -392,7 +394,7 @@
 		z->tz_minuteswest = _timezone/60;
 		z->tz_dsttime = _daylight;
 	}
-	
+
 	if (p != 0) {
 		_ftime(&timebuffer);
 	   	p->tv_sec = timebuffer.time;			/* seconds since 1-1-1970 */
@@ -1044,7 +1046,7 @@
  * Returns: zero if the pathname refers to an existing file system
  * object that has all the tested permissions, or -1 otherwise or on
  * error.
- * 
+ *
  * Since: 2.8
  */
 int
@@ -1056,7 +1058,7 @@
       wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
       int retval;
       int save_errno;
-      
+
       if (wfilename == NULL)
 	{
 	  errno = EINVAL;
@@ -1072,7 +1074,7 @@
       return retval;
     }
   else
-    {    
+    {
       gchar *cp_filename = g_locale_from_utf8 (filename, -1, NULL, NULL, NULL);
       int retval;
       int save_errno;
--- a/pidgin/gtkaccount.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/pidgin/gtkaccount.c	Sat Jun 09 04:12:47 2007 +0000
@@ -1024,7 +1024,7 @@
 	if (dialog->proxy_frame != NULL)
 		gtk_widget_destroy(dialog->proxy_frame);
 
-	frame = pidgin_make_frame(parent, _("Pro_xy Options"));
+	frame = pidgin_make_frame(parent, _("Proxy Options"));
 	dialog->proxy_frame = gtk_widget_get_parent(gtk_widget_get_parent(frame));
 
 	gtk_box_reorder_child(GTK_BOX(parent), dialog->proxy_frame, 1);
--- a/pidgin/gtkpounce.c	Fri Jun 08 03:19:13 2007 +0000
+++ b/pidgin/gtkpounce.c	Sat Jun 09 04:12:47 2007 +0000
@@ -783,7 +783,7 @@
 	gtk_widget_show(table);
 
 	dialog->on_away =
-		gtk_check_button_new_with_mnemonic(_("P_ounce only when my status is not available"));
+		gtk_check_button_new_with_mnemonic(_("P_ounce only when my status is not Available"));
 	gtk_table_attach(GTK_TABLE(table), dialog->on_away, 0, 1, 0, 1,
 					 GTK_FILL, 0, 0, 0);